UNPKG

@polymer/polymer

Version:

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

1,019 lines (937 loc) 36.7 kB
<!-- @license Copyright (c) 2017 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <link rel="import" href="../../../shadycss/apply-shim.html"> <link rel="import" href="../mixins/element-mixin.html"> <link rel="import" href="../mixins/gesture-event-listeners.html"> <link rel="import" href="../mixins/dir-mixin.html"> <link rel="import" href="../utils/mixin.html"> <link rel="import" href="../utils/import-href.html"> <link rel="import" href="../utils/render-status.html"> <link rel="import" href="../utils/unresolved.html"> <link rel="import" href="polymer.dom.html"> <script> (function() { 'use strict'; let styleInterface = window.ShadyCSS; /** * Element class mixin that provides Polymer's "legacy" API intended to be * backward-compatible to the greatest extent possible with the API * found on the Polymer 1.x `Polymer.Base` prototype applied to all elements * defined using the `Polymer({...})` function. * * @mixinFunction * @polymer * @appliesMixin Polymer.ElementMixin * @appliesMixin Polymer.GestureEventListeners * @property isAttached {boolean} Set to `true` in this element's * `connectedCallback` and `false` in `disconnectedCallback` * @memberof Polymer * @summary Element class mixin that provides Polymer's "legacy" API */ Polymer.LegacyElementMixin = Polymer.dedupingMixin((base) => { /** * @constructor * @extends {base} * @implements {Polymer_ElementMixin} * @implements {Polymer_GestureEventListeners} * @implements {Polymer_DirMixin} * @private */ const legacyElementBase = Polymer.DirMixin(Polymer.GestureEventListeners(Polymer.ElementMixin(base))); /** * Map of simple names to touch action names * @dict */ const DIRECTION_MAP = { 'x': 'pan-x', 'y': 'pan-y', 'none': 'none', 'all': 'auto' }; /** * @polymer * @mixinClass * @extends {legacyElementBase} * @implements {Polymer_LegacyElementMixin} * @unrestricted */ class LegacyElement extends legacyElementBase { constructor() { super(); /** @type {boolean} */ this.isAttached; /** @type {WeakMap<!Element, !Object<string, !Function>>} */ this.__boundListeners; /** @type {Object<string, Function>} */ this._debouncers; } /** * Forwards `importMeta` from the prototype (i.e. from the info object * passed to `Polymer({...})`) to the static API. * * @return {!Object} The `import.meta` object set on the prototype * @suppress {missingProperties} `this` is always in the instance in * closure for some reason even in a static method, rather than the class */ static get importMeta() { return this.prototype.importMeta; } /** * Legacy callback called during the `constructor`, for overriding * by the user. * @return {void} */ created() {} /** * Provides an implementation of `connectedCallback` * which adds Polymer legacy API's `attached` method. * @return {void} * @override */ connectedCallback() { super.connectedCallback(); this.isAttached = true; this.attached(); } /** * Legacy callback called during `connectedCallback`, for overriding * by the user. * @return {void} */ attached() {} /** * Provides an implementation of `disconnectedCallback` * which adds Polymer legacy API's `detached` method. * @return {void} * @override */ disconnectedCallback() { super.disconnectedCallback(); this.isAttached = false; this.detached(); } /** * Legacy callback called during `disconnectedCallback`, for overriding * by the user. * @return {void} */ detached() {} /** * Provides an override implementation of `attributeChangedCallback` * which adds the Polymer legacy API's `attributeChanged` method. * @param {string} name Name of attribute. * @param {?string} old Old value of attribute. * @param {?string} value Current value of attribute. * @param {?string} namespace Attribute namespace. * @return {void} * @override */ attributeChangedCallback(name, old, value, namespace) { if (old !== value) { super.attributeChangedCallback(name, old, value, namespace); this.attributeChanged(name, old, value); } } /** * Legacy callback called during `attributeChangedChallback`, for overriding * by the user. * @param {string} name Name of attribute. * @param {?string} old Old value of attribute. * @param {?string} value Current value of attribute. * @return {void} */ attributeChanged(name, old, value) {} // eslint-disable-line no-unused-vars /** * Overrides the default `Polymer.PropertyEffects` implementation to * add support for class initialization via the `_registered` callback. * This is called only when the first instance of the element is created. * * @return {void} * @override * @suppress {invalidCasts} */ _initializeProperties() { let proto = Object.getPrototypeOf(this); if (!proto.hasOwnProperty('__hasRegisterFinished')) { this._registered(); // backstop in case the `_registered` implementation does not set this proto.__hasRegisterFinished = true; } 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(); } /** * Called automatically when an element is initializing. * Users may override this method to perform class registration time * work. The implementation should ensure the work is performed * only once for the class. * @protected * @return {void} */ _registered() {} /** * Overrides the default `Polymer.PropertyEffects` implementation to * add support for installing `hostAttributes` and `listeners`. * * @return {void} * @override */ ready() { this._ensureAttributes(); super.ready(); } /** * Ensures an element has required attributes. Called when the element * is being readied via `ready`. Users should override to set the * element's required attributes. The implementation should be sure * to check and not override existing attributes added by * the user of the element. Typically, setting attributes should be left * to the element user and not done here; reasonable exceptions include * setting aria roles and focusability. * @protected * @return {void} */ _ensureAttributes() {} /** * Adds element event listeners. Called when the element * is being readied via `ready`. Users should override to * add any required element event listeners. * In performance critical elements, the work done here should be kept * to a minimum since it is done before the element is rendered. In * these elements, consider adding listeners asynchronously so as not to * block render. * @protected * @return {void} */ _applyListeners() {} /** * Converts a typed JavaScript value to a string. * * Note this method is provided as backward-compatible legacy API * only. It is not directly called by any Polymer features. To customize * how properties are serialized to attributes for attribute bindings and * `reflectToAttribute: true` properties as well as this method, override * the `_serializeValue` method provided by `Polymer.PropertyAccessors`. * * @param {*} value Value to deserialize * @return {string | undefined} Serialized value */ serialize(value) { return this._serializeValue(value); } /** * Converts a string to a typed JavaScript value. * * Note this method is provided as backward-compatible legacy API * only. It is not directly called by any Polymer features. To customize * how attributes are deserialized to properties for in * `attributeChangedCallback`, override `_deserializeValue` method * provided by `Polymer.PropertyAccessors`. * * @param {string} value String to deserialize * @param {*} type Type to deserialize the string to * @return {*} Returns the deserialized value in the `type` given. */ deserialize(value, type) { return this._deserializeValue(value, type); } /** * Serializes a property to its associated attribute. * * Note this method is provided as backward-compatible legacy API * only. It is not directly called by any Polymer features. * * @param {string} property Property name to reflect. * @param {string=} attribute Attribute name to reflect. * @param {*=} value Property value to reflect. * @return {void} */ reflectPropertyToAttribute(property, attribute, value) { this._propertyToAttribute(property, attribute, value); } /** * Sets a typed value to an HTML attribute on a node. * * Note this method is provided as backward-compatible legacy API * only. It is not directly called by any Polymer features. * * @param {*} value Value to serialize. * @param {string} attribute Attribute name to serialize to. * @param {Element} node Element to set attribute to. * @return {void} */ serializeValueToAttribute(value, attribute, node) { this._valueToNodeAttribute(/** @type {Element} */ (node || this), value, attribute); } /** * Copies own properties (including accessor descriptors) from a source * object to a target object. * * @param {Object} prototype Target object to copy properties to. * @param {Object} api Source object to copy properties from. * @return {Object} prototype object that was passed as first argument. */ extend(prototype, api) { if (!(prototype && api)) { return prototype || api; } let n$ = Object.getOwnPropertyNames(api); for (let i=0, n; (i<n$.length) && (n=n$[i]); i++) { let pd = Object.getOwnPropertyDescriptor(api, n); if (pd) { Object.defineProperty(prototype, n, pd); } } return prototype; } /** * Copies props from a source object to a target object. * * Note, this method uses a simple `for...in` strategy for enumerating * properties. To ensure only `ownProperties` are copied from source * to target and that accessor implementations are copied, use `extend`. * * @param {!Object} target Target object to copy properties to. * @param {!Object} source Source object to copy properties from. * @return {!Object} Target object that was passed as first argument. */ mixin(target, source) { for (let i in source) { target[i] = source[i]; } return target; } /** * Sets the prototype of an object. * * Note this method is provided as backward-compatible legacy API * only. It is not directly called by any Polymer features. * @param {Object} object The object on which to set the prototype. * @param {Object} prototype The prototype that will be set on the given * `object`. * @return {Object} Returns the given `object` with its prototype set * to the given `prototype` object. */ chainObject(object, prototype) { if (object && prototype && object !== prototype) { object.__proto__ = prototype; } return object; } /* **** Begin Template **** */ /** * Calls `importNode` on the `content` of the `template` specified and * returns a document fragment containing the imported content. * * @param {HTMLTemplateElement} template HTML template element to instance. * @return {!DocumentFragment} Document fragment containing the imported * template content. */ instanceTemplate(template) { let content = this.constructor._contentForTemplate(template); let dom = /** @type {!DocumentFragment} */ (document.importNode(content, true)); return dom; } /* **** Begin Events **** */ /** * Dispatches a custom event with an optional detail value. * * @param {string} type Name of event type. * @param {*=} detail Detail value containing event-specific * payload. * @param {{ bubbles: (boolean|undefined), cancelable: (boolean|undefined), composed: (boolean|undefined) }=} * options Object specifying options. These may include: * `bubbles` (boolean, defaults to `true`), * `cancelable` (boolean, defaults to false), and * `node` on which to fire the event (HTMLElement, defaults to `this`). * @return {!Event} The new event that was fired. */ fire(type, detail, options) { options = options || {}; detail = (detail === null || detail === undefined) ? {} : detail; let event = new Event(type, { bubbles: options.bubbles === undefined ? true : options.bubbles, cancelable: Boolean(options.cancelable), composed: options.composed === undefined ? true: options.composed }); event.detail = detail; let node = options.node || this; node.dispatchEvent(event); return event; } /** * Convenience method to add an event listener on a given element, * late bound to a named method on this element. * * @param {Element} node Element to add event listener to. * @param {string} eventName Name of event to listen for. * @param {string} methodName Name of handler method on `this` to call. * @return {void} */ listen(node, eventName, methodName) { node = /** @type {!Element} */ (node || this); let hbl = this.__boundListeners || (this.__boundListeners = new WeakMap()); let bl = hbl.get(node); if (!bl) { bl = {}; hbl.set(node, bl); } let key = eventName + methodName; if (!bl[key]) { bl[key] = this._addMethodEventListenerToNode( node, eventName, methodName, this); } } /** * Convenience method to remove an event listener from a given element, * late bound to a named method on this element. * * @param {Element} node Element to remove event listener from. * @param {string} eventName Name of event to stop listening to. * @param {string} methodName Name of handler method on `this` to not call anymore. * @return {void} */ unlisten(node, eventName, methodName) { node = /** @type {!Element} */ (node || this); let bl = this.__boundListeners && this.__boundListeners.get(node); let key = eventName + methodName; let handler = bl && bl[key]; if (handler) { this._removeEventListenerFromNode(node, eventName, handler); bl[key] = null; } } /** * Override scrolling behavior to all direction, one direction, or none. * * Valid scroll directions: * - 'all': scroll in any direction * - 'x': scroll only in the 'x' direction * - 'y': scroll only in the 'y' direction * - 'none': disable scrolling for this node * * @param {string=} direction Direction to allow scrolling * Defaults to `all`. * @param {Element=} node Element to apply scroll direction setting. * Defaults to `this`. * @return {void} */ setScrollDirection(direction, node) { Polymer.Gestures.setTouchAction(/** @type {Element} */ (node || this), DIRECTION_MAP[direction] || 'auto'); } /* **** End Events **** */ /** * Convenience method to run `querySelector` on this local DOM scope. * * This function calls `Polymer.dom(this.root).querySelector(slctr)`. * * @param {string} slctr Selector to run on this local DOM scope * @return {Element} Element found by the selector, or null if not found. */ $$(slctr) { return this.root.querySelector(slctr); } /** * Return the element whose local dom within which this element * is contained. This is a shorthand for * `this.getRootNode().host`. * @this {Element} */ get domHost() { let root = this.getRootNode(); return (root instanceof DocumentFragment) ? /** @type {ShadowRoot} */ (root).host : root; } /** * Force this element to distribute its children to its local dom. * This should not be necessary as of Polymer 2.0.2 and is provided only * for backwards compatibility. * @return {void} */ distributeContent() { if (window.ShadyDOM && this.shadowRoot) { ShadyDOM.flush(); } } /** * Returns a list of nodes that are the effective childNodes. The effective * childNodes list is the same as the element's childNodes except that * any `<content>` elements are replaced with the list of nodes distributed * to the `<content>`, the result of its `getDistributedNodes` method. * @return {!Array<!Node>} List of effective child nodes. * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement */ getEffectiveChildNodes() { const thisEl = /** @type {Element} */ (this); const domApi = /** @type {Polymer.DomApi} */(Polymer.dom(thisEl)); return domApi.getEffectiveChildNodes(); } /** * Returns a list of nodes distributed within this element that match * `selector`. These can be dom children or elements distributed to * children that are insertion points. * @param {string} selector Selector to run. * @return {!Array<!Node>} List of distributed elements that match selector. * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement */ queryDistributedElements(selector) { const thisEl = /** @type {Element} */ (this); const domApi = /** @type {Polymer.DomApi} */(Polymer.dom(thisEl)); return domApi.queryDistributedElements(selector); } /** * Returns a list of elements that are the effective children. The effective * children list is the same as the element's children except that * any `<content>` elements are replaced with the list of elements * distributed to the `<content>`. * * @return {!Array<!Node>} List of effective children. */ getEffectiveChildren() { let list = this.getEffectiveChildNodes(); return list.filter(function(/** @type {!Node} */ n) { return (n.nodeType === Node.ELEMENT_NODE); }); } /** * Returns a string of text content that is the concatenation of the * text content's of the element's effective childNodes (the elements * returned by <a href="#getEffectiveChildNodes>getEffectiveChildNodes</a>. * * @return {string} List of effective children. */ getEffectiveTextContent() { let cn = this.getEffectiveChildNodes(); let tc = []; for (let i=0, c; (c = cn[i]); i++) { if (c.nodeType !== Node.COMMENT_NODE) { tc.push(c.textContent); } } return tc.join(''); } /** * Returns the first effective childNode within this element that * match `selector`. These can be dom child nodes or elements distributed * to children that are insertion points. * @param {string} selector Selector to run. * @return {Node} First effective child node that matches selector. */ queryEffectiveChildren(selector) { let e$ = this.queryDistributedElements(selector); return e$ && e$[0]; } /** * Returns a list of effective childNodes within this element that * match `selector`. These can be dom child nodes or elements distributed * to children that are insertion points. * @param {string} selector Selector to run. * @return {!Array<!Node>} List of effective child nodes that match selector. */ queryAllEffectiveChildren(selector) { return this.queryDistributedElements(selector); } /** * Returns a list of nodes distributed to this element's `<slot>`. * * If this element contains more than one `<slot>` in its local DOM, * an optional selector may be passed to choose the desired content. * * @param {string=} slctr CSS selector to choose the desired * `<slot>`. Defaults to `content`. * @return {!Array<!Node>} List of distributed nodes for the `<slot>`. */ getContentChildNodes(slctr) { let content = this.root.querySelector(slctr || 'slot'); return content ? /** @type {Polymer.DomApi} */(Polymer.dom(content)).getDistributedNodes() : []; } /** * Returns a list of element children distributed to this element's * `<slot>`. * * If this element contains more than one `<slot>` in its * local DOM, an optional selector may be passed to choose the desired * content. This method differs from `getContentChildNodes` in that only * elements are returned. * * @param {string=} slctr CSS selector to choose the desired * `<content>`. Defaults to `content`. * @return {!Array<!HTMLElement>} List of distributed nodes for the * `<slot>`. * @suppress {invalidCasts} */ getContentChildren(slctr) { let children = /** @type {!Array<!HTMLElement>} */(this.getContentChildNodes(slctr).filter(function(n) { return (n.nodeType === Node.ELEMENT_NODE); })); return children; } /** * Checks whether an element is in this element's light DOM tree. * * @param {?Node} node The element to be checked. * @return {boolean} true if node is in this element's light DOM tree. * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement */ isLightDescendant(node) { const thisNode = /** @type {Node} */ (this); return thisNode !== node && thisNode.contains(node) && thisNode.getRootNode() === node.getRootNode(); } /** * Checks whether an element is in this element's local DOM tree. * * @param {!Element} node The element to be checked. * @return {boolean} true if node is in this element's local DOM tree. */ isLocalDescendant(node) { return this.root === node.getRootNode(); } /** * No-op for backwards compatibility. This should now be handled by * ShadyCss library. * @param {*} container Unused * @param {*} shouldObserve Unused * @return {void} */ scopeSubtree(container, shouldObserve) { // eslint-disable-line no-unused-vars } /** * Returns the computed style value for the given property. * @param {string} property The css property name. * @return {string} Returns the computed css property value for the given * `property`. * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement */ getComputedStyleValue(property) { return styleInterface.getComputedStyleValue(/** @type {!Element} */(this), property); } // debounce /** * Call `debounce` to collapse multiple requests for a named task into * one invocation which is made after the wait time has elapsed with * no new request. If no wait time is given, the callback will be called * at microtask timing (guaranteed before paint). * * debouncedClickAction(e) { * // will not call `processClick` more than once per 100ms * this.debounce('click', function() { * this.processClick(); * } 100); * } * * @param {string} jobName String to identify the debounce job. * @param {function():void} callback Function that is called (with `this` * context) when the wait time elapses. * @param {number} wait Optional wait time in milliseconds (ms) after the * last signal that must elapse before invoking `callback` * @return {!Object} Returns a debouncer object on which exists the * following methods: `isActive()` returns true if the debouncer is * active; `cancel()` cancels the debouncer if it is active; * `flush()` immediately invokes the debounced callback if the debouncer * is active. */ debounce(jobName, callback, wait) { this._debouncers = this._debouncers || {}; return this._debouncers[jobName] = Polymer.Debouncer.debounce( this._debouncers[jobName] , wait > 0 ? Polymer.Async.timeOut.after(wait) : Polymer.Async.microTask , callback.bind(this)); } /** * Returns whether a named debouncer is active. * * @param {string} jobName The name of the debouncer started with `debounce` * @return {boolean} Whether the debouncer is active (has not yet fired). */ isDebouncerActive(jobName) { this._debouncers = this._debouncers || {}; let debouncer = this._debouncers[jobName]; return !!(debouncer && debouncer.isActive()); } /** * Immediately calls the debouncer `callback` and inactivates it. * * @param {string} jobName The name of the debouncer started with `debounce` * @return {void} */ flushDebouncer(jobName) { this._debouncers = this._debouncers || {}; let debouncer = this._debouncers[jobName]; if (debouncer) { debouncer.flush(); } } /** * Cancels an active debouncer. The `callback` will not be called. * * @param {string} jobName The name of the debouncer started with `debounce` * @return {void} */ cancelDebouncer(jobName) { this._debouncers = this._debouncers || {}; let debouncer = this._debouncers[jobName]; if (debouncer) { debouncer.cancel(); } } /** * Runs a callback function asynchronously. * * By default (if no waitTime is specified), async callbacks are run at * microtask timing, which will occur before paint. * * @param {!Function} callback The callback function to run, bound to `this`. * @param {number=} waitTime Time to wait before calling the * `callback`. If unspecified or 0, the callback will be run at microtask * timing (before paint). * @return {number} Handle that may be used to cancel the async job. */ async(callback, waitTime) { return waitTime > 0 ? Polymer.Async.timeOut.run(callback.bind(this), waitTime) : ~Polymer.Async.microTask.run(callback.bind(this)); } /** * Cancels an async operation started with `async`. * * @param {number} handle Handle returned from original `async` call to * cancel. * @return {void} */ cancelAsync(handle) { handle < 0 ? Polymer.Async.microTask.cancel(~handle) : Polymer.Async.timeOut.cancel(handle); } // other /** * Convenience method for creating an element and configuring it. * * @param {string} tag HTML element tag to create. * @param {Object=} props Object of properties to configure on the * instance. * @return {!Element} Newly created and configured element. */ create(tag, props) { let elt = document.createElement(tag); if (props) { if (elt.setProperties) { elt.setProperties(props); } else { for (let n in props) { elt[n] = props[n]; } } } return elt; } /** * Convenience method for importing an HTML document imperatively. * * This method creates a new `<link rel="import">` element with * the provided URL and appends it to the document to start loading. * In the `onload` callback, the `import` property of the `link` * element will contain the imported document contents. * * @param {string} href URL to document to load. * @param {?function(!Event):void=} onload Callback to notify when an import successfully * loaded. * @param {?function(!ErrorEvent):void=} onerror Callback to notify when an import * unsuccessfully loaded. * @param {boolean=} optAsync True if the import should be loaded `async`. * Defaults to `false`. * @return {!HTMLLinkElement} The link element for the URL to be loaded. */ importHref(href, onload, onerror, optAsync) { // eslint-disable-line no-unused-vars let loadFn = onload ? onload.bind(this) : null; let errorFn = onerror ? onerror.bind(this) : null; return Polymer.importHref(href, loadFn, errorFn, optAsync); } /** * Polyfill for Element.prototype.matches, which is sometimes still * prefixed. * * @param {string} selector Selector to test. * @param {!Element=} node Element to test the selector against. * @return {boolean} Whether the element matches the selector. */ elementMatches(selector, node) { return Polymer.dom.matchesSelector(/** @type {!Element} */ (node || this), selector); } /** * Toggles an HTML attribute on or off. * * @param {string} name HTML attribute name * @param {boolean=} bool Boolean to force the attribute on or off. * When unspecified, the state of the attribute will be reversed. * @param {Element=} node Node to target. Defaults to `this`. * @return {void} */ toggleAttribute(name, bool, node) { node = /** @type {Element} */ (node || this); if (arguments.length == 1) { bool = !node.hasAttribute(name); } if (bool) { node.setAttribute(name, ''); } else { node.removeAttribute(name); } } /** * Toggles a CSS class on or off. * * @param {string} name CSS class name * @param {boolean=} bool Boolean to force the class on or off. * When unspecified, the state of the class will be reversed. * @param {Element=} node Node to target. Defaults to `this`. * @return {void} */ toggleClass(name, bool, node) { node = /** @type {Element} */ (node || this); if (arguments.length == 1) { bool = !node.classList.contains(name); } if (bool) { node.classList.add(name); } else { node.classList.remove(name); } } /** * Cross-platform helper for setting an element's CSS `transform` property. * * @param {string} transformText Transform setting. * @param {Element=} node Element to apply the transform to. * Defaults to `this` * @return {void} */ transform(transformText, node) { node = /** @type {Element} */ (node || this); node.style.webkitTransform = transformText; node.style.transform = transformText; } /** * Cross-platform helper for setting an element's CSS `translate3d` * property. * * @param {number} x X offset. * @param {number} y Y offset. * @param {number} z Z offset. * @param {Element=} node Element to apply the transform to. * Defaults to `this`. * @return {void} */ translate3d(x, y, z, node) { node = /** @type {Element} */ (node || this); this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node); } /** * Removes an item from an array, if it exists. * * If the array is specified by path, a change notification is * generated, so that observers, data bindings and computed * properties watching that path can update. * * If the array is passed directly, **no change * notification is generated**. * * @param {string | !Array<number|string>} arrayOrPath Path to array from which to remove the item * (or the array itself). * @param {*} item Item to remove. * @return {Array} Array containing item removed. */ arrayDelete(arrayOrPath, item) { let index; if (Array.isArray(arrayOrPath)) { index = arrayOrPath.indexOf(item); if (index >= 0) { return arrayOrPath.splice(index, 1); } } else { let arr = Polymer.Path.get(this, arrayOrPath); index = arr.indexOf(item); if (index >= 0) { return this.splice(arrayOrPath, index, 1); } } return null; } // logging /** * Facades `console.log`/`warn`/`error` as override point. * * @param {string} level One of 'log', 'warn', 'error' * @param {Array} args Array of strings or objects to log * @return {void} */ _logger(level, args) { // accept ['foo', 'bar'] and [['foo', 'bar']] if (Array.isArray(args) && args.length === 1 && Array.isArray(args[0])) { args = args[0]; } switch(level) { case 'log': case 'warn': case 'error': console[level](...args); } } /** * Facades `console.log` as an override point. * * @param {...*} args Array of strings or objects to log * @return {void} */ _log(...args) { this._logger('log', args); } /** * Facades `console.warn` as an override point. * * @param {...*} args Array of strings or objects to log * @return {void} */ _warn(...args) { this._logger('warn', args); } /** * Facades `console.error` as an override point. * * @param {...*} args Array of strings or objects to log * @return {void} */ _error(...args) { this._logger('error', args); } /** * Formats a message using the element type an a method name. * * @param {string} methodName Method name to associate with message * @param {...*} args Array of strings or objects to log * @return {Array} Array with formatting information for `console` * logging. */ _logf(methodName, ...args) { return ['[%s::%s]', this.is, methodName, ...args]; } } LegacyElement.prototype.is = ''; return LegacyElement; }); })(); </script>