UNPKG

webdash-readme-preview

Version:
740 lines (707 loc) 30.1 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="../utils/boot.html"> <link rel="import" href="../utils/settings.html"> <link rel="import" href="../utils/mixin.html"> <link rel="import" href="../utils/style-gather.html"> <link rel="import" href="../utils/resolve-url.html"> <link rel="import" href="../elements/dom-module.html"> <link rel="import" href="property-effects.html"> <link rel="import" href="properties-mixin.html"> <script> (function() { 'use strict'; /** * Element class mixin that provides the core API for Polymer's meta-programming * features including template stamping, data-binding, attribute deserialization, * and property change observation. * * Subclassers may provide the following static getters to return metadata * used to configure Polymer's features for the class: * * - `static get is()`: When the template is provided via a `dom-module`, * users should return the `dom-module` id from a static `is` getter. If * no template is needed or the template is provided directly via the * `template` getter, there is no need to define `is` for the element. * * - `static get template()`: Users may provide the template directly (as * opposed to via `dom-module`) by implementing a static `template` getter. * The getter may return an `HTMLTemplateElement` or a string, which will * automatically be parsed into a template. * * - `static get properties()`: Should return an object describing * property-related metadata used by Polymer features (key: property name * value: object containing property metadata). Valid keys in per-property * metadata include: * - `type` (String|Number|Object|Array|...): Used by * `attributeChangedCallback` to determine how string-based attributes * are deserialized to JavaScript property values. * - `notify` (boolean): Causes a change in the property to fire a * non-bubbling event called `<property>-changed`. Elements that have * enabled two-way binding to the property use this event to observe changes. * - `readOnly` (boolean): Creates a getter for the property, but no setter. * To set a read-only property, use the private setter method * `_setProperty(property, value)`. * - `observer` (string): Observer method name that will be called when * the property changes. The arguments of the method are * `(value, previousValue)`. * - `computed` (string): String describing method and dependent properties * for computing the value of this property (e.g. `'computeFoo(bar, zot)'`). * Computed properties are read-only by default and can only be changed * via the return value of the computing method. * * - `static get observers()`: Array of strings describing multi-property * observer methods and their dependent properties (e.g. * `'observeABC(a, b, c)'`). * * The base class provides default implementations for the following standard * custom element lifecycle callbacks; users may override these, but should * call the super method to ensure * - `constructor`: Run when the element is created or upgraded * - `connectedCallback`: Run each time the element is connected to the * document * - `disconnectedCallback`: Run each time the element is disconnected from * the document * - `attributeChangedCallback`: Run each time an attribute in * `observedAttributes` is set or removed (note: this element's default * `observedAttributes` implementation will automatically return an array * of dash-cased attributes based on `properties`) * * @mixinFunction * @polymer * @appliesMixin Polymer.PropertyEffects * @appliesMixin Polymer.PropertiesMixin * @memberof Polymer * @property rootPath {string} Set to the value of `Polymer.rootPath`, * which defaults to the main document path * @property importPath {string} Set to the value of the class's static * `importPath` property, which defaults to the path of this element's * `dom-module` (when `is` is used), but can be overridden for other * import strategies. * @summary Element class mixin that provides the core API for Polymer's * meta-programming features. */ Polymer.ElementMixin = Polymer.dedupingMixin(base => { /** * @constructor * @extends {base} * @implements {Polymer_PropertyEffects} * @implements {Polymer_PropertiesMixin} */ const polymerElementBase = Polymer.PropertiesMixin(Polymer.PropertyEffects(base)); /** * Returns a list of properties with default values. * This list is created as an optimization since it is a subset of * the list returned from `_properties`. * This list is used in `_initializeProperties` to set property defaults. * * @param {PolymerElementConstructor} constructor Element class * @return {PolymerElementProperties} Flattened properties for this class * that have default values * @private */ function propertyDefaults(constructor) { if (!constructor.hasOwnProperty( JSCompiler_renameProperty('__propertyDefaults', constructor))) { constructor.__propertyDefaults = null; let props = constructor._properties; for (let p in props) { let info = props[p]; if ('value' in info) { constructor.__propertyDefaults = constructor.__propertyDefaults || {}; constructor.__propertyDefaults[p] = info; } } } return constructor.__propertyDefaults; } /** * Returns a memoized version of the the `observers` array. * @param {PolymerElementConstructor} constructor Element class * @return {Array} Array containing own observers for the given class * @protected */ function ownObservers(constructor) { if (!constructor.hasOwnProperty( JSCompiler_renameProperty('__ownObservers', constructor))) { constructor.__ownObservers = constructor.hasOwnProperty(JSCompiler_renameProperty('observers', constructor)) ? /** @type {PolymerElementConstructor} */ (constructor).observers : null; } return constructor.__ownObservers; } /** * Creates effects for a property. * * Note, once a property has been set to * `readOnly`, `computed`, `reflectToAttribute`, or `notify` * these values may not be changed. For example, a subclass cannot * alter these settings. However, additional `observers` may be added * by subclasses. * * The info object should may contain property metadata as follows: * * * `type`: {function} type to which an attribute matching the property * is deserialized. Note the property is camel-cased from a dash-cased * attribute. For example, 'foo-bar' attribute is deserialized to a * property named 'fooBar'. * * * `readOnly`: {boolean} creates a readOnly property and * makes a private setter for the private of the form '_setFoo' for a * property 'foo', * * * `computed`: {string} creates a computed property. A computed property * also automatically is set to `readOnly: true`. The value is calculated * by running a method and arguments parsed from the given string. For * example 'compute(foo)' will compute a given property when the * 'foo' property changes by executing the 'compute' method. This method * must return the computed value. * * * `reflectToAttribute`: {boolean} If true, the property value is reflected * to an attribute of the same name. Note, the attribute is dash-cased * so a property named 'fooBar' is reflected as 'foo-bar'. * * * `notify`: {boolean} sends a non-bubbling notification event when * the property changes. For example, a property named 'foo' sends an * event named 'foo-changed' with `event.detail` set to the value of * the property. * * * observer: {string} name of a method that runs when the property * changes. The arguments of the method are (value, previousValue). * * Note: Users may want control over modifying property * effects via subclassing. For example, a user might want to make a * reflectToAttribute property not do so in a subclass. We've chosen to * disable this because it leads to additional complication. * For example, a readOnly effect generates a special setter. If a subclass * disables the effect, the setter would fail unexpectedly. * Based on feedback, we may want to try to make effects more malleable * and/or provide an advanced api for manipulating them. * Also consider adding warnings when an effect cannot be changed. * * @param {!PolymerElement} proto Element class prototype to add accessors * and effects to * @param {string} name Name of the property. * @param {Object} info Info object from which to create property effects. * Supported keys: * @param {Object} allProps Flattened map of all properties defined in this * element (including inherited properties) * @return {void} * @private */ function createPropertyFromConfig(proto, name, info, allProps) { // computed forces readOnly... if (info.computed) { info.readOnly = true; } // Note, since all computed properties are readOnly, this prevents // adding additional computed property effects (which leads to a confusing // setup where multiple triggers for setting a property) // While we do have `hasComputedEffect` this is set on the property's // dependencies rather than itself. if (info.computed && !proto._hasReadOnlyEffect(name)) { proto._createComputedProperty(name, info.computed, allProps); } if (info.readOnly && !proto._hasReadOnlyEffect(name)) { proto._createReadOnlyProperty(name, !info.computed); } if (info.reflectToAttribute && !proto._hasReflectEffect(name)) { proto._createReflectedProperty(name); } if (info.notify && !proto._hasNotifyEffect(name)) { proto._createNotifyingProperty(name); } // always add observer if (info.observer) { proto._createPropertyObserver(name, info.observer, allProps[info.observer]); } // always create the mapping from attribute back to property for deserialization. proto._addPropertyToAttributeMap(name); } /** * Process all style elements in the element template. Styles with the * `include` attribute are processed such that any styles in * the associated "style modules" are included in the element template. * @param {PolymerElementConstructor} klass Element class * @param {!HTMLTemplateElement} template Template to process * @param {string} is Name of element * @param {string} baseURI Base URI for element * @private */ function processElementStyles(klass, template, is, baseURI) { const templateStyles = template.content.querySelectorAll('style'); const stylesWithImports = Polymer.StyleGather.stylesFromTemplate(template); // insert styles from <link rel="import" type="css"> at the top of the template const linkedStyles = Polymer.StyleGather.stylesFromModuleImports(is); const firstTemplateChild = template.content.firstElementChild; for (let idx = 0; idx < linkedStyles.length; idx++) { let s = linkedStyles[idx]; s.textContent = klass._processStyleText(s.textContent, baseURI); template.content.insertBefore(s, firstTemplateChild); } // keep track of the last "concrete" style in the template we have encountered let templateStyleIndex = 0; // ensure all gathered styles are actually in this template. for (let i = 0; i < stylesWithImports.length; i++) { let s = stylesWithImports[i]; let templateStyle = templateStyles[templateStyleIndex]; // if the style is not in this template, it's been "included" and // we put a clone of it in the template before the style that included it if (templateStyle !== s) { s = s.cloneNode(true); templateStyle.parentNode.insertBefore(s, templateStyle); } else { templateStyleIndex++; } s.textContent = klass._processStyleText(s.textContent, baseURI); } if (window.ShadyCSS) { window.ShadyCSS.prepareTemplate(template, is); } } /** * @polymer * @mixinClass * @unrestricted * @implements {Polymer_ElementMixin} */ class PolymerElement extends polymerElementBase { /** * Override of PropertiesMixin _finalizeClass to create observers and * find the template. * @return {void} * @protected * @override * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do */ static _finalizeClass() { super._finalizeClass(); if (this.hasOwnProperty( JSCompiler_renameProperty('is', this)) && this.is) { Polymer.telemetry.register(this.prototype); } const observers = ownObservers(this); if (observers) { this.createObservers(observers, this._properties); } // note: create "working" template that is finalized at instance time let template = /** @type {PolymerElementConstructor} */ (this).template; if (template) { if (typeof template === 'string') { let t = document.createElement('template'); t.innerHTML = template; template = t; } else { template = template.cloneNode(true); } } this.prototype._template = template; } /** * Override of PropertiesChanged createProperties to create accessors * and property effects for all of the properties. * @return {void} * @protected * @override */ static createProperties(props) { for (let p in props) { createPropertyFromConfig(this.prototype, p, props[p], props); } } /** * Creates observers for the given `observers` array. * Leverages `PropertyEffects` to create observers. * @param {Object} observers Array of observer descriptors for * this class * @param {Object} dynamicFns Object containing keys for any properties * that are functions and should trigger the effect when the function * reference is changed * @return {void} * @protected */ static createObservers(observers, dynamicFns) { const proto = this.prototype; for (let i=0; i < observers.length; i++) { proto._createMethodObserver(observers[i], dynamicFns); } } /** * Returns the template that will be stamped into this element's shadow root. * * 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`. * * Users may override this getter to return an arbitrary template * (in which case the `is` getter is unnecessary). The template returned * may be either an `HTMLTemplateElement` or a string that will be * automatically parsed into a template. * * Note that when subclassing, if the super class overrode the default * implementation and the subclass would like to provide an alternate * template via a `dom-module`, it should override this getter and * return `Polymer.DomModule.import(this.is, 'template')`. * * If a subclass would like to modify the super class template, it should * clone it rather than modify it in place. If the getter does expensive * work such as cloning/modifying a template, it should memoize the * template for maximum performance: * * let memoizedTemplate; * class MySubClass extends MySuperClass { * static get template() { * if (!memoizedTemplate) { * memoizedTemplate = super.template.cloneNode(true); * let subContent = document.createElement('div'); * subContent.textContent = 'This came from MySubClass'; * memoizedTemplate.content.appendChild(subContent); * } * return memoizedTemplate; * } * } * * @return {HTMLTemplateElement|string} Template to be stamped */ static get template() { if (!this.hasOwnProperty(JSCompiler_renameProperty('_template', this))) { this._template = Polymer.DomModule && Polymer.DomModule.import( /** @type {PolymerElementConstructor}*/ (this).is, 'template') || // note: implemented so a subclass can retrieve the super // template; call the super impl this way so that `this` points // to the superclass. Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.template; } return this._template; } /** * Path matching the url from which the element was imported. * This path is used to resolve url's in template style cssText. * The `importPath` property is also set on element instances and can be * used to create bindings relative to the import path. * Defaults to the path matching the url containing a `dom-module` element * matching this element's static `is` property. * Note, this path should contain a trailing `/`. * * @return {string} The import path for this element class */ static get importPath() { if (!this.hasOwnProperty(JSCompiler_renameProperty('_importPath', this))) { const module = Polymer.DomModule && Polymer.DomModule.import(/** @type {PolymerElementConstructor} */ (this).is); this._importPath = module ? module.assetpath : '' || Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.importPath; } return this._importPath; } constructor() { super(); /** @type {HTMLTemplateElement} */ this._template; /** @type {string} */ this._importPath; /** @type {string} */ this.rootPath; /** @type {string} */ this.importPath; /** @type {StampedTemplate | HTMLElement | ShadowRoot} */ this.root; /** @type {!Object<string, !Element>} */ this.$; } /** * Overrides the default `Polymer.PropertyAccessors` to ensure class * metaprogramming related to property accessors and effects has * completed (calls `finalize`). * * It also initializes any property defaults provided via `value` in * `properties` metadata. * * @return {void} * @override * @suppress {invalidCasts} */ _initializeProperties() { Polymer.telemetry.instanceCount++; this.constructor.finalize(); const importPath = this.constructor.importPath; // note: finalize template when we have access to `localName` to // avoid dependence on `is` for polyfilling styling. this.constructor._finalizeTemplate(/** @type {!HTMLElement} */(this).localName); super._initializeProperties(); // set path defaults this.rootPath = Polymer.rootPath; this.importPath = importPath; // apply property defaults... let p$ = propertyDefaults(this.constructor); if (!p$) { return; } for (let p in p$) { 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)) { let value = typeof info.value == 'function' ? info.value.call(this) : info.value; // Set via `_setProperty` if there is an accessor, to enable // initializing readOnly property defaults if (this._hasAccessor(p)) { this._setPendingProperty(p, value, true); } else { this[p] = value; } } } } /** * Gather style text for a style element in the template. * * @param {string} cssText Text containing styling to process * @param {string} baseURI Base URI to rebase CSS paths against * @return {string} The processed CSS text * @protected */ static _processStyleText(cssText, baseURI) { return Polymer.ResolveUrl.resolveCss(cssText, baseURI); } /** * Configures an element `proto` to function with a given `template`. * The element name `is` and extends `ext` must be specified for ShadyCSS * style scoping. * * @param {string} is Tag name (or type extension name) for this element * @return {void} * @protected */ static _finalizeTemplate(is) { /** @const {HTMLTemplateElement} */ const template = this.prototype._template; if (template && !template.__polymerFinalized) { template.__polymerFinalized = true; const importPath = this.importPath; const baseURI = importPath ? Polymer.ResolveUrl.resolveUrl(importPath) : ''; // e.g. support `include="module-name"`, and ShadyCSS processElementStyles(this, template, is, baseURI); this.prototype._bindTemplate(template); } } /** * Provides a default implementation of the standard Custom Elements * `connectedCallback`. * * The default implementation enables the property effects system and * flushes any pending properties, and updates shimmed CSS properties * when using the ShadyCSS scoping/custom properties polyfill. * * @suppress {missingProperties, invalidCasts} Super may or may not implement the callback * @return {void} */ connectedCallback() { if (window.ShadyCSS && this._template) { window.ShadyCSS.styleElement(/** @type {!HTMLElement} */(this)); } super.connectedCallback(); } /** * Stamps the element template. * * @return {void} * @override */ ready() { if (this._template) { this.root = this._stampTemplate(this._template); this.$ = this.root.$; } super.ready(); } /** * Implements `PropertyEffects`'s `_readyClients` call. Attaches * element dom by calling `_attachDom` with the dom stamped from the * element's template via `_stampTemplate`. Note that this allows * client dom to be attached to the element prior to any observers * running. * * @return {void} * @override */ _readyClients() { if (this._template) { this.root = this._attachDom(/** @type {StampedTemplate} */(this.root)); } // The super._readyClients here sets the clients initialized flag. // We must wait to do this until after client dom is created/attached // so that this flag can be checked to prevent notifications fired // during this process from being handled before clients are ready. super._readyClients(); } /** * Attaches an element's stamped dom to itself. By default, * this method creates a `shadowRoot` and adds the dom to it. * However, this method may be overridden to allow an element * to put its dom in another location. * * @throws {Error} * @suppress {missingReturn} * @param {StampedTemplate} dom to attach to the element. * @return {ShadowRoot} node to which the dom has been attached. */ _attachDom(dom) { if (this.attachShadow) { if (dom) { if (!this.shadowRoot) { this.attachShadow({mode: 'open'}); } this.shadowRoot.appendChild(dom); return this.shadowRoot; } return null; } else { throw new Error('ShadowDOM not available. ' + // TODO(sorvell): move to compile-time conditional when supported 'Polymer.Element can create dom as children instead of in ' + 'ShadowDOM by setting `this.root = this;\` before \`ready\`.'); } } /** * When using the ShadyCSS scoping and custom property shim, causes all * shimmed styles in this element (and its subtree) to be updated * based on current custom property values. * * The optional parameter overrides inline custom property styles with an * object of properties where the keys are CSS properties, and the values * are strings. * * Example: `this.updateStyles({'--color': 'blue'})` * * These properties are retained unless a value of `null` is set. * * Note: This function does not support updating CSS mixins. * You can not dynamically change the value of an `@apply`. * * @param {Object=} properties Bag of custom property key/values to * apply to this element. * @return {void} * @suppress {invalidCasts} */ updateStyles(properties) { if (window.ShadyCSS) { window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */(this), properties); } } /** * Rewrites a given URL relative to a base URL. The base URL defaults to * the original location of the document containing the `dom-module` for * this element. This method will return the same URL before and after * bundling. * * Note that this function performs no resolution for URLs that start * with `/` (absolute URLs) or `#` (hash identifiers). For general purpose * URL resolution, use `window.URL`. * * @param {string} url URL to resolve. * @param {string=} base Optional base URL to resolve against, defaults * to the element's `importPath` * @return {string} Rewritten URL relative to base */ resolveUrl(url, base) { if (!base && this.importPath) { base = Polymer.ResolveUrl.resolveUrl(this.importPath); } return Polymer.ResolveUrl.resolveUrl(url, base); } /** * Overrides `PropertyAccessors` to add map of dynamic functions on * template info, for consumption by `PropertyEffects` template binding * code. This map determines which method templates should have accessors * created for them. * * @override * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do */ static _parseTemplateContent(template, templateInfo, nodeInfo) { templateInfo.dynamicFns = templateInfo.dynamicFns || this._properties; return super._parseTemplateContent(template, templateInfo, nodeInfo); } } return PolymerElement; }); /** * Provides basic tracking of element definitions (registrations) and * instance counts. * * @namespace * @summary Provides basic tracking of element definitions (registrations) and * instance counts. */ Polymer.telemetry = { /** * Total number of Polymer element instances created. * @type {number} */ instanceCount: 0, /** * Array of Polymer element classes that have been finalized. * @type {Array<Polymer.Element>} */ registrations: [], /** * @param {!PolymerElementConstructor} prototype Element prototype to log * @this {this} * @private */ _regLog: function(prototype) { console.log('[' + prototype.is + ']: registered'); }, /** * Registers a class prototype for telemetry purposes. * @param {HTMLElement} prototype Element prototype to register * @this {this} * @protected */ register: function(prototype) { this.registrations.push(prototype); Polymer.log && this._regLog(prototype); }, /** * Logs all elements registered with an `is` to the console. * @public * @this {this} */ dumpRegistrations: function() { this.registrations.forEach(this._regLog); } }; /** * When using the ShadyCSS scoping and custom property shim, causes all * shimmed `styles` (via `custom-style`) in the document (and its subtree) * to be updated based on current custom property values. * * The optional parameter overrides inline custom property styles with an * object of properties where the keys are CSS properties, and the values * are strings. * * Example: `Polymer.updateStyles({'--color': 'blue'})` * * These properties are retained unless a value of `null` is set. * * @param {Object=} props Bag of custom property key/values to * apply to the document. * @return {void} */ Polymer.updateStyles = function(props) { if (window.ShadyCSS) { window.ShadyCSS.styleDocument(props); } }; })(); </script>