diff --git a/.eslintrc.json b/.eslintrc.json index c25f61e55a..de64bd22f8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,9 +19,11 @@ "Boolean": "boolean", "Number": "number", "String": "string", - "object": "Object" + "object": "Object", + "array": "Array" } - }] + }], + "no-useless-escape": "off" }, "env": { "browser": true, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..4046195b53 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Code owners for Polymer +* @sorvell @kevinpschaaf @TimvdLippe @azakus \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dcdd713500..8d68054f92 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ @@ -9,8 +9,11 @@ If you are asking a question rather than filing a bug, try one of these instead: #### Live Demo - -http://jsbin.com/luhaxab/1/edit + +https://jsbin.com/luhaxab/1/edit + +https://glitch.com/edit/#!/polymer-repro?path=my-element.html:2:0 + #### Steps to Reproduce + + + + + + diff --git a/lib/mixins/properties-mixin.html b/lib/mixins/properties-mixin.html new file mode 100644 index 0000000000..782311bb8e --- /dev/null +++ b/lib/mixins/properties-mixin.html @@ -0,0 +1,230 @@ + + + + + + + diff --git a/lib/mixins/property-accessors.html b/lib/mixins/property-accessors.html index aad5ee7373..f26f467b12 100644 --- a/lib/mixins/property-accessors.html +++ b/lib/mixins/property-accessors.html @@ -11,7 +11,7 @@ - + \ No newline at end of file + diff --git a/lib/utils/async.html b/lib/utils/async.html index 1283856349..4ddd961c6f 100644 --- a/lib/utils/async.html +++ b/lib/utils/async.html @@ -14,9 +14,6 @@ 'use strict'; - /** @typedef {{run: function(function(), number=):number, cancel: function(number)}} */ - let AsyncInterface; // eslint-disable-line no-unused-vars - // Microtask implemented using Mutation Observer let microtaskCurrHandle = 0; let microtaskLastHandle = 0; @@ -67,30 +64,38 @@ * delay. * * @memberof Polymer.Async.timeOut - * @param {number} delay Time to wait before calling callbacks in ms - * @return {AsyncInterface} An async timeout interface + * @param {number=} delay Time to wait before calling callbacks in ms + * @return {!AsyncInterface} An async timeout interface */ after(delay) { - return { - run(fn) { return setTimeout(fn, delay); }, - cancel: window.clearTimeout.bind(window) + return { + run(fn) { return window.setTimeout(fn, delay); }, + cancel(handle) { + window.clearTimeout(handle); + } }; }, /** * Enqueues a function called in the next task. * * @memberof Polymer.Async.timeOut - * @param {Function} fn Callback to run + * @param {!Function} fn Callback to run + * @param {number=} delay Delay in milliseconds * @return {number} Handle used for canceling task */ - run: window.setTimeout.bind(window), + run(fn, delay) { + return window.setTimeout(fn, delay); + }, /** * Cancels a previously enqueued `timeOut` callback. * * @memberof Polymer.Async.timeOut * @param {number} handle Handle returned from `run` of callback to cancel + * @return {void} */ - cancel: window.clearTimeout.bind(window) + cancel(handle) { + window.clearTimeout(handle); + } }, /** @@ -105,17 +110,22 @@ * Enqueues a function called at `requestAnimationFrame` timing. * * @memberof Polymer.Async.animationFrame - * @param {Function} fn Callback to run + * @param {function(number):void} fn Callback to run * @return {number} Handle used for canceling task */ - run: window.requestAnimationFrame.bind(window), + run(fn) { + return window.requestAnimationFrame(fn); + }, /** * Cancels a previously enqueued `animationFrame` callback. * - * @memberof Polymer.Async.timeOut + * @memberof Polymer.Async.animationFrame * @param {number} handle Handle returned from `run` of callback to cancel + * @return {void} */ - cancel: window.cancelAnimationFrame.bind(window) + cancel(handle) { + window.cancelAnimationFrame(handle); + } }, /** @@ -131,7 +141,7 @@ * Enqueues a function called at `requestIdleCallback` timing. * * @memberof Polymer.Async.idlePeriod - * @param {function(IdleDeadline)} fn Callback to run + * @param {function(!IdleDeadline):void} fn Callback to run * @return {number} Handle used for canceling task */ run(fn) { @@ -144,6 +154,7 @@ * * @memberof Polymer.Async.idlePeriod * @param {number} handle Handle returned from `run` of callback to cancel + * @return {void} */ cancel(handle) { window.cancelIdleCallback ? @@ -172,7 +183,7 @@ * Enqueues a function called at microtask timing. * * @memberof Polymer.Async.microTask - * @param {Function} callback Callback to run + * @param {!Function=} callback Callback to run * @return {number} Handle used for canceling task */ run(callback) { @@ -186,6 +197,7 @@ * * @memberof Polymer.Async.microTask * @param {number} handle Handle returned from `run` of callback to cancel + * @return {void} */ cancel(handle) { const idx = handle - microtaskLastHandle; diff --git a/lib/utils/boot.html b/lib/utils/boot.html index d3e9a1094e..96ee134d27 100644 --- a/lib/utils/boot.html +++ b/lib/utils/boot.html @@ -49,7 +49,7 @@ }; /* eslint-enable */ - window.Polymer.version = '2.2.0'; + window.Polymer.version = '2.4.0'; /* eslint-disable no-unused-vars */ /* diff --git a/lib/utils/debounce.html b/lib/utils/debounce.html index 7fff476678..8344af328f 100644 --- a/lib/utils/debounce.html +++ b/lib/utils/debounce.html @@ -15,9 +15,6 @@ (function() { 'use strict'; - /** @typedef {{run: function(function(), number=):number, cancel: function(number)}} */ - let AsyncModule; // eslint-disable-line no-unused-vars - /** * @summary Collapse multiple callbacks into one invocation after a timer. * @memberof Polymer @@ -33,8 +30,9 @@ * a callback and optional arguments to be passed to the run function * from the async module. * - * @param {!AsyncModule} asyncModule Object with Async interface. + * @param {!AsyncInterface} asyncModule Object with Async interface. * @param {function()} callback Callback to run. + * @return {void} */ setConfig(asyncModule, callback) { this._asyncModule = asyncModule; @@ -46,6 +44,8 @@ } /** * Cancels an active debouncer and returns a reference to itself. + * + * @return {void} */ cancel() { if (this.isActive()) { @@ -55,6 +55,8 @@ } /** * Flushes an active debouncer and returns a reference to itself. + * + * @return {void} */ flush() { if (this.isActive()) { @@ -70,36 +72,36 @@ isActive() { return this._timer != null; } - /** - * Creates a debouncer if no debouncer is passed as a parameter - * or it cancels an active debouncer otherwise. The following - * example shows how a debouncer can be called multiple times within a - * microtask and "debounced" such that the provided callback function is - * called once. Add this method to a custom element: - * - * _debounceWork() { - * this._debounceJob = Polymer.Debouncer.debounce(this._debounceJob, - * Polymer.Async.microTask, () => { - * this._doWork(); - * }); - * } - * - * If the `_debounceWork` method is called multiple times within the same - * microtask, the `_doWork` function will be called only once at the next - * microtask checkpoint. - * - * Note: In testing it is often convenient to avoid asynchrony. To accomplish - * this with a debouncer, you can use `Polymer.enqueueDebouncer` and - * `Polymer.flush`. For example, extend the above example by adding - * `Polymer.enqueueDebouncer(this._debounceJob)` at the end of the - * `_debounceWork` method. Then in a test, call `Polymer.flush` to ensure - * the debouncer has completed. - * - * @param {Debouncer?} debouncer Debouncer object. - * @param {!AsyncModule} asyncModule Object with Async interface - * @param {function()} callback Callback to run. - * @return {!Debouncer} Returns a debouncer object. - */ + /** + * Creates a debouncer if no debouncer is passed as a parameter + * or it cancels an active debouncer otherwise. The following + * example shows how a debouncer can be called multiple times within a + * microtask and "debounced" such that the provided callback function is + * called once. Add this method to a custom element: + * + * _debounceWork() { + * this._debounceJob = Polymer.Debouncer.debounce(this._debounceJob, + * Polymer.Async.microTask, () => { + * this._doWork(); + * }); + * } + * + * If the `_debounceWork` method is called multiple times within the same + * microtask, the `_doWork` function will be called only once at the next + * microtask checkpoint. + * + * Note: In testing it is often convenient to avoid asynchrony. To accomplish + * this with a debouncer, you can use `Polymer.enqueueDebouncer` and + * `Polymer.flush`. For example, extend the above example by adding + * `Polymer.enqueueDebouncer(this._debounceJob)` at the end of the + * `_debounceWork` method. Then in a test, call `Polymer.flush` to ensure + * the debouncer has completed. + * + * @param {Debouncer?} debouncer Debouncer object. + * @param {!AsyncInterface} asyncModule Object with Async interface + * @param {function()} callback Callback to run. + * @return {!Debouncer} Returns a debouncer object. + */ static debounce(debouncer, asyncModule, callback) { if (debouncer instanceof Debouncer) { debouncer.cancel(); diff --git a/lib/utils/flattened-nodes-observer.html b/lib/utils/flattened-nodes-observer.html index 5ea0085a4e..2273b1604f 100644 --- a/lib/utils/flattened-nodes-observer.html +++ b/lib/utils/flattened-nodes-observer.html @@ -16,7 +16,7 @@ /** * Returns true if `node` is a slot element - * @param {HTMLElement} node Node to test. + * @param {Node} node Node to test. * @return {boolean} Returns true if the given `node` is a slot * @private */ @@ -44,6 +44,24 @@ * `MutationObserver` and the `` element's `slotchange` event which * are asynchronous. * + * An example: + * ```js + * class TestSelfObserve extends Polymer.Element { + * static get is() { return 'test-self-observe';} + * connectedCallback() { + * super.connectedCallback(); + * this._observer = new Polymer.FlattenedNodesObserver(this, (info) => { + * this.info = info; + * }); + * } + * disconnectedCallback() { + * super.disconnectedCallback(); + * this._observer.disconnect(); + * } + * } + * customElements.define(TestSelfObserve.is, TestSelfObserve); + * ``` + * * @memberof Polymer * @summary Class that listens for changes (additions or removals) to * "flattened nodes" on a given `node`. @@ -79,22 +97,35 @@ } /** - * @param {Node} target Node on which to listen for changes. - * @param {Function} callback Function called when there are additions + * @param {Element} target Node on which to listen for changes. + * @param {?function(!Element, { target: !Element, addedNodes: !Array, removedNodes: !Array }):void} callback Function called when there are additions * or removals from the target's list of flattened nodes. */ constructor(target, callback) { - /** @type {MutationObserver} */ + /** + * @type {MutationObserver} + * @private + */ this._shadyChildrenObserver = null; - /** @type {MutationObserver} */ + /** + * @type {MutationObserver} + * @private + */ this._nativeChildrenObserver = null; this._connected = false; + /** + * @type {Element} + * @private + */ this._target = target; this.callback = callback; this._effectiveNodes = []; this._observer = null; this._scheduled = false; - /** @type {function()} */ + /** + * @type {function()} + * @private + */ this._boundSchedule = () => { this._schedule(); }; @@ -106,11 +137,13 @@ * Activates an observer. This method is automatically called when * a `FlattenedNodesObserver` is created. It should only be called to * re-activate an observer that has been deactivated via the `disconnect` method. + * + * @return {void} */ connect() { if (isSlot(this._target)) { this._listenSlots([this._target]); - } else { + } else if (this._target.children) { this._listenSlots(this._target.children); if (window.ShadyDOM) { this._shadyChildrenObserver = @@ -133,11 +166,13 @@ * the observer callback will not be called when changes to flattened nodes * occur. The `connect` method may be subsequently called to reactivate * the observer. + * + * @return {void} */ disconnect() { if (isSlot(this._target)) { this._unlistenSlots([this._target]); - } else { + } else if (this._target.children) { this._unlistenSlots(this._target.children); if (window.ShadyDOM && this._shadyChildrenObserver) { ShadyDOM.unobserveChildren(this._shadyChildrenObserver); @@ -150,6 +185,10 @@ this._connected = false; } + /** + * @return {void} + * @private + */ _schedule() { if (!this._scheduled) { this._scheduled = true; @@ -157,11 +196,21 @@ } } + /** + * @param {Array} mutations Mutations signaled by the mutation observer + * @return {void} + * @private + */ _processMutations(mutations) { this._processSlotMutations(mutations); this.flush(); } + /** + * @param {Array} mutations Mutations signaled by the mutation observer + * @return {void} + * @private + */ _processSlotMutations(mutations) { if (mutations) { for (let i=0; i < mutations.length; i++) { @@ -227,6 +276,11 @@ return didFlush; } + /** + * @param {!Array|!NodeList} nodeList Nodes that could change + * @return {void} + * @private + */ _listenSlots(nodeList) { for (let i=0; i < nodeList.length; i++) { let n = nodeList[i]; @@ -236,6 +290,11 @@ } } + /** + * @param {!Array|!NodeList} nodeList Nodes that could change + * @return {void} + * @private + */ _unlistenSlots(nodeList) { for (let i=0; i < nodeList.length; i++) { let n = nodeList[i]; diff --git a/lib/utils/flush.html b/lib/utils/flush.html index 331442292d..3feaad6166 100644 --- a/lib/utils/flush.html +++ b/lib/utils/flush.html @@ -18,7 +18,8 @@ * Adds a `Polymer.Debouncer` to a list of globally flushable tasks. * * @memberof Polymer - * @param {Polymer.Debouncer} debouncer Debouncer to enqueue + * @param {!Polymer.Debouncer} debouncer Debouncer to enqueue + * @return {void} */ Polymer.enqueueDebouncer = function(debouncer) { debouncerQueue.push(debouncer); @@ -44,6 +45,7 @@ * - ShadyDOM distribution * * @memberof Polymer + * @return {void} */ Polymer.flush = function() { let shadyDOM, debouncers; diff --git a/lib/utils/gestures.html b/lib/utils/gestures.html index b741455a9d..738940f406 100644 --- a/lib/utils/gestures.html +++ b/lib/utils/gestures.html @@ -62,9 +62,13 @@ /** * Generate settings for event listeners, dependant on `Polymer.passiveTouchGestures` * + * @param {string} eventName Event name to determine if `{passive}` option is needed * @return {{passive: boolean} | undefined} Options to use for addEventListener and removeEventListener */ - function PASSIVE_TOUCH() { + function PASSIVE_TOUCH(eventName) { + if (isMouseEvent(eventName) || eventName === 'touchend') { + return; + } if (HAS_NATIVE_TA && SUPPORTS_PASSIVE && Polymer.passiveTouchGestures) { return {passive: true}; } else { @@ -76,21 +80,21 @@ let IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/); let GestureRecognizer = function(){}; // eslint-disable-line no-unused-vars - /** @type {function()} */ + /** @type {function(): void} */ GestureRecognizer.prototype.reset; - /** @type {function(MouseEvent) | undefined} */ + /** @type {function(MouseEvent): void | undefined} */ GestureRecognizer.prototype.mousedown; - /** @type {(function(MouseEvent) | undefined)} */ + /** @type {(function(MouseEvent): void | undefined)} */ GestureRecognizer.prototype.mousemove; - /** @type {(function(MouseEvent) | undefined)} */ + /** @type {(function(MouseEvent): void | undefined)} */ GestureRecognizer.prototype.mouseup; - /** @type {(function(TouchEvent) | undefined)} */ + /** @type {(function(TouchEvent): void | undefined)} */ GestureRecognizer.prototype.touchstart; - /** @type {(function(TouchEvent) | undefined)} */ + /** @type {(function(TouchEvent): void | undefined)} */ GestureRecognizer.prototype.touchmove; - /** @type {(function(TouchEvent) | undefined)} */ + /** @type {(function(TouchEvent): void | undefined)} */ GestureRecognizer.prototype.touchend; - /** @type {(function(MouseEvent) | undefined)} */ + /** @type {(function(MouseEvent): void | undefined)} */ GestureRecognizer.prototype.click; // touch will make synthetic mouse events @@ -126,6 +130,7 @@ /** * @param {boolean=} setup True to add, false to remove. + * @return {void} */ function setupTeardownMouseCanceller(setup) { let events = IS_TOUCH_ONLY ? ['click'] : MOUSE_EVENTS; @@ -311,8 +316,9 @@ _findOriginalTarget: function(ev) { // shadowdom if (ev.composedPath) { - const target = /** @type {EventTarget} */(ev.composedPath()[0]); - return target; + const targets = /** @type {!Array} */(ev.composedPath()); + // It shouldn't be, but sometimes targets is empty (window on Safari). + return targets.length > 0 ? targets[0] : ev.target; } // shadydom return ev.target; @@ -321,6 +327,7 @@ /** * @private * @param {Event} ev Event. + * @return {void} */ _handleNative: function(ev) { let handled; @@ -382,6 +389,7 @@ /** * @private * @param {TouchEvent} ev Event. + * @return {void} */ _handleTouchAction: function(ev) { let t = ev.changedTouches[0]; @@ -420,9 +428,9 @@ * Adds an event listener to a node for the given gesture type. * * @memberof Polymer.Gestures - * @param {Node} node Node to add listener on + * @param {!Node} node Node to add listener on * @param {string} evType Gesture type: `down`, `up`, `track`, or `tap` - * @param {Function} handler Event listener function to call + * @param {!function(!Event):void} handler Event listener function to call * @return {boolean} Returns true if a gesture event listener was added. * @this {Gestures} */ @@ -438,9 +446,9 @@ * Removes an event listener from a node for the given gesture type. * * @memberof Polymer.Gestures - * @param {Node} node Node to remove listener from + * @param {!Node} node Node to remove listener from * @param {string} evType Gesture type: `down`, `up`, `track`, or `tap` - * @param {Function} handler Event listener function previously passed to + * @param {!function(!Event):void} handler Event listener function previously passed to * `addListener`. * @return {boolean} Returns true if a gesture event listener was removed. * @this {Gestures} @@ -457,9 +465,10 @@ * automate the event listeners for the native events * * @private - * @param {HTMLElement} node Node on which to add the event. + * @param {!HTMLElement} node Node on which to add the event. * @param {string} evType Event type to add. - * @param {function(Event?)} handler Event handler function. + * @param {function(!Event)} handler Event handler function. + * @return {void} * @this {Gestures} */ _add: function(node, evType, handler) { @@ -481,8 +490,7 @@ gobj[dep] = gd = {_count: 0}; } if (gd._count === 0) { - let options = !isMouseEvent(dep) && PASSIVE_TOUCH(); - node.addEventListener(dep, this._handleNative, options); + node.addEventListener(dep, this._handleNative, PASSIVE_TOUCH(dep)); } gd[name] = (gd[name] || 0) + 1; gd._count = (gd._count || 0) + 1; @@ -497,9 +505,10 @@ * automate event listener removal for native events * * @private - * @param {HTMLElement} node Node on which to remove the event. + * @param {!HTMLElement} node Node on which to remove the event. * @param {string} evType Event type to remove. * @param {function(Event?)} handler Event handler function. + * @return {void} * @this {Gestures} */ _remove: function(node, evType, handler) { @@ -515,8 +524,7 @@ gd[name] = (gd[name] || 1) - 1; gd._count = (gd._count || 1) - 1; if (gd._count === 0) { - let options = !isMouseEvent(dep) && PASSIVE_TOUCH(); - node.removeEventListener(dep, this._handleNative, options); + node.removeEventListener(dep, this._handleNative, PASSIVE_TOUCH(dep)); } } } @@ -529,7 +537,8 @@ * gesture event types. * * @memberof Polymer.Gestures - * @param {GestureRecognizer} recog Gesture recognizer descriptor + * @param {!GestureRecognizer} recog Gesture recognizer descriptor + * @return {void} * @this {Gestures} */ register: function(recog) { @@ -565,12 +574,19 @@ * adding event listeners. * * @memberof Polymer.Gestures - * @param {Element} node Node to set touch action setting on + * @param {!Element} node Node to set touch action setting on * @param {string} value Touch action value + * @return {void} */ setTouchAction: function(node, value) { if (HAS_NATIVE_TA) { - node.style.touchAction = value; + // NOTE: add touchAction async so that events can be added in + // custom element constructors. Otherwise we run afoul of custom + // elements restriction against settings attributes (style) in the + // constructor. + Polymer.Async.microTask.run(() => { + node.style.touchAction = value; + }); } node[TOUCH_ACTION] = value; }, @@ -579,9 +595,10 @@ * Dispatches an event on the `target` element of `type` with the given * `detail`. * @private - * @param {EventTarget} target The element on which to fire an event. + * @param {!EventTarget} target The element on which to fire an event. * @param {string} type The type of event to fire. - * @param {Object=} detail The detail object to populate on the event. + * @param {!Object=} detail The detail object to populate on the event. + * @return {void} */ _fire: function(target, type, detail) { let ev = new Event(type, { bubbles: true, cancelable: true, composed: true }); @@ -601,6 +618,7 @@ * * @memberof Polymer.Gestures * @param {string} evName Event name. + * @return {void} * @this {Gestures} */ prevent: function(evName) { @@ -618,6 +636,7 @@ * Calling this method in production may cause duplicate taps or other Gestures. * * @memberof Polymer.Gestures + * @return {void} */ resetMouseCanceller: function() { if (POINTERSTATE.mouse.mouseIgnoreJob) { @@ -642,7 +661,10 @@ upfn: null }, - /** @this {GestureRecognizer} */ + /** + * @this {GestureRecognizer} + * @return {void} + */ reset: function() { untrackDocument(this.info); }, @@ -650,6 +672,7 @@ /** * @this {GestureRecognizer} * @param {MouseEvent} e + * @return {void} */ mousedown: function(e) { if (!hasLeftMouseButton(e)) { @@ -675,6 +698,7 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchstart: function(e) { this._fire('down', Gestures._findOriginalTarget(e), e.changedTouches[0], e); @@ -682,15 +706,17 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchend: function(e) { this._fire('up', Gestures._findOriginalTarget(e), e.changedTouches[0], e); }, /** * @param {string} type - * @param {EventTarget} target + * @param {!EventTarget} target * @param {Event} event * @param {Function} preventer + * @return {void} */ _fire: function(type, target, event, preventer) { Gestures._fire(target, type, { @@ -733,7 +759,10 @@ prevent: false }, - /** @this {GestureRecognizer} */ + /** + * @this {GestureRecognizer} + * @return {void} + */ reset: function() { this.info.state = 'start'; this.info.started = false; @@ -764,6 +793,7 @@ /** * @this {GestureRecognizer} * @param {MouseEvent} e + * @return {void} */ mousedown: function(e) { if (!hasLeftMouseButton(e)) { @@ -806,6 +836,7 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchstart: function(e) { let ct = e.changedTouches[0]; @@ -815,6 +846,7 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchmove: function(e) { let t = Gestures._findOriginalTarget(e); @@ -834,6 +866,7 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchend: function(e) { let t = Gestures._findOriginalTarget(e); @@ -849,8 +882,9 @@ /** * @this {GestureRecognizer} - * @param {EventTarget} target + * @param {!EventTarget} target * @param {Touch} touch + * @return {void} */ _fire: function(target, touch) { let secondlast = this.info.moves[this.info.moves.length - 2]; @@ -892,13 +926,20 @@ y: NaN, prevent: false }, - /** @this {GestureRecognizer} */ + /** + * @this {GestureRecognizer} + * @return {void} + */ reset: function() { this.info.x = NaN; this.info.y = NaN; this.info.prevent = false; }, - /** @this {GestureRecognizer} */ + /** + * @this {GestureRecognizer} + * @param {MouseEvent} e + * @return {void} + */ save: function(e) { this.info.x = e.clientX; this.info.y = e.clientY; @@ -906,6 +947,7 @@ /** * @this {GestureRecognizer} * @param {MouseEvent} e + * @return {void} */ mousedown: function(e) { if (hasLeftMouseButton(e)) { @@ -915,6 +957,7 @@ /** * @this {GestureRecognizer} * @param {MouseEvent} e + * @return {void} */ click: function(e) { if (hasLeftMouseButton(e)) { @@ -924,6 +967,7 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchstart: function(e) { this.save(e.changedTouches[0], e); @@ -931,6 +975,7 @@ /** * @this {GestureRecognizer} * @param {TouchEvent} e + * @return {void} */ touchend: function(e) { this.forward(e.changedTouches[0], e); @@ -939,12 +984,16 @@ * @this {GestureRecognizer} * @param {Event | Touch} e * @param {Event=} preventer + * @return {void} */ forward: function(e, preventer) { let dx = Math.abs(e.clientX - this.info.x); let dy = Math.abs(e.clientY - this.info.y); // find original target from `preventer` for TouchEvents, or `e` for MouseEvents let t = Gestures._findOriginalTarget(/** @type {Event} */(preventer || e)); + if (!t) { + return; + } // dx,dy can be NaN if `click` has been simulated and there was no `down` for `start` if (isNaN(dx) || isNaN(dy) || (dx <= TAP_DISTANCE && dy <= TAP_DISTANCE) || isSyntheticClick(e)) { // prevent taps from being generated if an event has canceled them diff --git a/lib/utils/html-tag.html b/lib/utils/html-tag.html new file mode 100644 index 0000000000..e6189cde7c --- /dev/null +++ b/lib/utils/html-tag.html @@ -0,0 +1,129 @@ + + + diff --git a/lib/utils/import-href.html b/lib/utils/import-href.html index 24b394159e..80d354bc77 100644 --- a/lib/utils/import-href.html +++ b/lib/utils/import-href.html @@ -36,13 +36,13 @@ * * @memberof Polymer * @param {string} href URL to document to load. - * @param {Function=} onload Callback to notify when an import successfully + * @param {?function(!Event):void=} onload Callback to notify when an import successfully * loaded. - * @param {Function=} onerror Callback to notify when an import + * @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. + * @return {!HTMLLinkElement} The link element for the URL to be loaded. */ Polymer.importHref = function(href, onload, onerror, optAsync) { let link = /** @type {HTMLLinkElement} */ diff --git a/lib/utils/mixin.html b/lib/utils/mixin.html index 525d1bc209..e97b3e2123 100644 --- a/lib/utils/mixin.html +++ b/lib/utils/mixin.html @@ -38,6 +38,7 @@ * @memberof Polymer * @template T * @param {T} mixin ES6 class expression mixin to wrap + * @return {T} * @suppress {invalidCasts} */ Polymer.dedupingMixin = function(mixin) { @@ -68,7 +69,7 @@ return extended; } - return dedupingMixin; + return /** @type {T} */ (dedupingMixin); }; /* eslint-enable valid-jsdoc */ diff --git a/lib/utils/path.html b/lib/utils/path.html index 67c045665a..924c9d0abb 100644 --- a/lib/utils/path.html +++ b/lib/utils/path.html @@ -115,7 +115,7 @@ * Example: * * ``` - * Polymer.Path.translate('foo.bar', 'zot' 'foo.bar.baz') // 'zot.baz' + * Polymer.Path.translate('foo.bar', 'zot', 'foo.bar.baz') // 'zot.baz' * ``` * * @memberof Polymer.Path diff --git a/lib/utils/render-status.html b/lib/utils/render-status.html index 6f44578908..151f74ebc0 100644 --- a/lib/utils/render-status.html +++ b/lib/utils/render-status.html @@ -85,8 +85,9 @@ * * @memberof Polymer.RenderStatus * @param {*} context Context object the callback function will be bound to - * @param {function()} callback Callback function - * @param {Array} args An array of arguments to call the callback function with + * @param {function(...*):void} callback Callback function + * @param {!Array=} args An array of arguments to call the callback function with + * @return {void} */ beforeNextRender: function(context, callback, args) { if (!scheduled) { @@ -106,8 +107,9 @@ * * @memberof Polymer.RenderStatus * @param {*} context Context object the callback function will be bound to - * @param {function()} callback Callback function - * @param {Array} args An array of arguments to call the callback function with + * @param {function(...*):void} callback Callback function + * @param {!Array=} args An array of arguments to call the callback function with + * @return {void} */ afterNextRender: function(context, callback, args) { if (!scheduled) { @@ -121,6 +123,7 @@ * tasks. * * @memberof Polymer.RenderStatus + * @return {void} */ flush: flush diff --git a/lib/utils/resolve-url.html b/lib/utils/resolve-url.html index 357ef6a322..612847b1ce 100644 --- a/lib/utils/resolve-url.html +++ b/lib/utils/resolve-url.html @@ -15,7 +15,6 @@ 'use strict'; let CSS_URL_RX = /(url\()([^)]*)(\))/g; - let ABS_URL = /(^\/)|(^#)|(^[\w-\d]*:)/; let workingURL; let resolveDoc; /** @@ -27,9 +26,6 @@ * @return {string} resolved URL */ function resolveUrl(url, baseURI) { - if (url && ABS_URL.test(url)) { - return url; - } // Lazy feature detection. if (workingURL === undefined) { workingURL = false; diff --git a/lib/utils/settings.html b/lib/utils/settings.html index 7d5163e848..5b0dc42039 100644 --- a/lib/utils/settings.html +++ b/lib/utils/settings.html @@ -17,24 +17,20 @@ 'use strict'; /** - * Legacy settings. + * Sets the global, legacy settings. + * + * @deprecated * @namespace * @memberof Polymer */ - const settings = Polymer.Settings || {}; - settings.useShadow = !(window.ShadyDOM); - settings.useNativeCSSProperties = + Polymer.Settings = Polymer.Settings || {}; + + Polymer.Settings.useShadow = !(window.ShadyDOM); + Polymer.Settings.useNativeCSSProperties = Boolean(!window.ShadyCSS || window.ShadyCSS.nativeCss); - settings.useNativeCustomElements = + Polymer.Settings.useNativeCustomElements = !(window.customElements.polyfillWrapFlushCallback); - /** - * Sets the global, legacy settings. - * - * @deprecated - * @memberof Polymer - */ - Polymer.Settings = settings; /** * Globally settable property that is automatically assigned to @@ -57,6 +53,7 @@ * * @memberof Polymer * @param {string} path The new root path + * @return {void} */ Polymer.setRootPath = function(path) { Polymer.rootPath = path; @@ -89,6 +86,7 @@ * * @memberof Polymer * @param {(function(*,string,string,Node):*)|undefined} newSanitizeDOMValue the global sanitizeDOMValue callback + * @return {void} */ Polymer.setSanitizeDOMValue = function(newSanitizeDOMValue) { Polymer.sanitizeDOMValue = newSanitizeDOMValue; @@ -111,6 +109,7 @@ * * @memberof Polymer * @param {boolean} usePassive enable or disable passive touch gestures globally + * @return {void} */ Polymer.setPassiveTouchGestures = function(usePassive) { Polymer.passiveTouchGestures = usePassive; diff --git a/lib/utils/style-gather.html b/lib/utils/style-gather.html index a48ab2915f..a2bb6feda1 100644 --- a/lib/utils/style-gather.html +++ b/lib/utils/style-gather.html @@ -14,6 +14,7 @@ const MODULE_STYLE_LINK_SELECTOR = 'link[rel=import][type~=css]'; const INCLUDE_ATTR = 'include'; + const SHADY_UNSCOPED_ATTR = 'shady-unscoped'; function importModule(moduleId) { const /** Polymer.DomModule */ PolymerDomModule = customElements.get('dom-module'); @@ -23,6 +24,18 @@ return PolymerDomModule.import(moduleId); } + function styleForImport(importDoc) { + // NOTE: polyfill affordance. + // under the HTMLImports polyfill, there will be no 'body', + // but the import pseudo-doc can be used directly. + let container = importDoc.body ? importDoc.body : importDoc; + const importCss = Polymer.ResolveUrl.resolveCss(container.textContent, + importDoc.baseURI); + const style = document.createElement('style'); + style.textContent = importCss; + return style; + } + /** @typedef {{assetpath: string}} */ let templateWithAssetPath; // eslint-disable-line no-unused-vars @@ -37,15 +50,139 @@ const StyleGather = { /** + * Returns a list of +

${this.is}

+
${this.headerTemplate}
+ [[myProp.stuffThatGoesHere]] +
${this.footerTemplate}
+ `; + } + static get headerTemplate() { return html`

Header

`; } + static get footerTemplate() { return html`

Footer

`; } + } + customElements.define(SuperClass.is, SuperClass); + class SubClass extends SuperClass { + static get is() {return 'sub-class';} + static get template() { + return html` + +
${super.template}
+
\
+ `; + } + constructor() { + super(); + this.myProp = {stuffThatGoesHere: '!stuff that goes here!'}; + } + static get headerTemplate() { return html`

Sub-header

`; } + static get footerTemplate() { return html`

Sub-footer

`; } + } + customElements.define(SubClass.is, SubClass); + + + + + + \ No newline at end of file diff --git a/test/smoke/passive-gestures.html b/test/smoke/passive-gestures.html index 90e7a9363c..cc578a2d52 100644 --- a/test/smoke/passive-gestures.html +++ b/test/smoke/passive-gestures.html @@ -40,11 +40,17 @@ is: 'x-passive', listeners: { 'down': 'prevent', - 'move': 'prevent' + 'move': 'prevent', + 'up': 'prevent', + 'tap': 'allowed', + 'click': 'allowed' }, prevent(e) { e.preventDefault(); - console.log('prevented!'); + console.log('prevented?: ' + e.type + ' ' + e.defaultPrevented); + }, + allowed(e) { + console.log(e.type + ' allowed'); } }); diff --git a/test/unit/attributes-elements.html b/test/unit/attributes-elements.html index 9f8c54343b..99e060edf0 100644 --- a/test/unit/attributes-elements.html +++ b/test/unit/attributes-elements.html @@ -90,7 +90,11 @@ observer: 'attr1Changed' }, shorthandNumber: Number, - shorthandBool: Boolean + shorthandBool: Boolean, + foreignTypeObjectProp: { + type: Object, + value: 'none' + } }, propChangedCount: 0, @@ -166,7 +170,12 @@ readOnly: true }, shorthandNumber: Number, - shorthandBool: Boolean + shorthandBool: Boolean, + foreignTypeObjectProp: { + type: Object, + reflectToAttribute: true, + value: 'none' + } }, _warn: function() { var search = Array.prototype.join.call(arguments, ''); diff --git a/test/unit/attributes.html b/test/unit/attributes.html index 12f957c545..ea08885ac6 100644 --- a/test/unit/attributes.html +++ b/test/unit/attributes.html @@ -50,7 +50,7 @@ shorthand-number="88" behavior-shorthand-bool behavior-shorthand-number="99" - > + foreign-type-object-prop="The quick brown fox"> @@ -81,7 +81,8 @@ shorthand-bool shorthand-number="88" behavior-shorthand-bool - behavior-shorthand-number="99"> + behavior-shorthand-number="99" + foreign-type-object-prop="The quick brown fox"> @@ -106,6 +107,7 @@ assert.strictEqual(element.shorthandBool, undefined); assert.strictEqual(element.behaviorShorthandNumber, undefined); assert.strictEqual(element.behaviorShorthandBool, undefined); + assert.strictEqual(element.foreignTypeObjectProp, 'none'); } function testAttributesMatchCustomConfiguration(element) { @@ -133,6 +135,7 @@ assert.strictEqual(element.shorthandBool, true); assert.strictEqual(element.behaviorShorthandNumber, 99); assert.strictEqual(element.behaviorShorthandBool, true); + assert.strictEqual(element.foreignTypeObjectProp, configuredString); } test('basic default values', function() { @@ -175,6 +178,11 @@ assert.deepEqual(element.object, configuredObject); }); + test('change object attribute to foreign type', function() { + element.setAttribute('object', 'The quick brown fox'); + assert.strictEqual(element.object, 'The quick brown fox'); + }); + test('change array attribute', function() { element.setAttribute('array', '[0, "foo", true, {"foo": "bar"}]'); assert.deepEqual(element.array, configuredArray); @@ -237,6 +245,11 @@ assert.deepEqual(element.object, configuredObject); }); + test('change object attribute to foreign type', function() { + element.setAttribute('object', 'The quick brown fox'); + assert.strictEqual(element.object, 'The quick brown fox'); + }); + test('change array attribute', function() { element.setAttribute('array', '[0, "foo", true, {"foo": "bar"}]'); assert.deepEqual(element.array, configuredArray); diff --git a/test/unit/custom-style.html b/test/unit/custom-style.html index 82a117c1b0..97ff8ac21a 100644 --- a/test/unit/custom-style.html +++ b/test/unit/custom-style.html @@ -483,6 +483,11 @@ assertComputed(el, '11px', 'right'); assertComputed(el, '12px', 'top'); + // Avoid Edge 16 bug with CSS Custom Properties and Fonts. + if (navigator.userAgent.match('Edge/16') && (!window.ShadyCSS || window.ShadyCSS.nativeCss)) { + return; + } + // Because FireFox and Chrome parse font-family differently... var computed = getComputedStyle(el); assert.equal(computed['font-family'].replace(/['"]+/g, ''), 'Varela font'); diff --git a/test/unit/dir.html b/test/unit/dir.html index 26f847f206..0cc008246d 100644 --- a/test/unit/dir.html +++ b/test/unit/dir.html @@ -131,6 +131,54 @@ + + + + + + + + + + + diff --git a/test/unit/dom-if.html b/test/unit/dom-if.html index 70db19ad8e..99239f251a 100644 --- a/test/unit/dom-if.html +++ b/test/unit/dom-if.html @@ -550,27 +550,6 @@ suite('attach/detach tests', function() { - test('remove, append domif', function(done) { - var domif = document.querySelector('#simple'); - domif.if = true; - outerContainer.removeChild(domif); - setTimeout(function() { - var clients = outerContainer.querySelectorAll('x-client'); - assert.equal(clients.length, 0); - outerContainer.appendChild(domif); - setTimeout(function() { - var clients = outerContainer.querySelectorAll('x-client'); - assert.equal(clients[0].uid, 0); - assert.equal(clients[1].uid, 1); - assert.equal(clients[2].uid, 2); - assert.equal(clients[1].previousElementSibling, clients[0]); - assert.equal(clients[2].previousElementSibling, clients[1]); - assert.equal(domif.previousElementSibling, clients[2]); - done(); - }); - }); - }); - test('move domif (clients persist)', function(done) { var domif = document.querySelector('#simple'); domif.if = true; @@ -674,6 +653,28 @@ }); }); + test('remove, append domif', function(done) { + var domif = document.querySelector('#simple'); + var parent = domif.parentNode; + domif.if = true; + parent.removeChild(domif); + setTimeout(function() { + var clients = parent.querySelectorAll('x-client'); + assert.equal(clients.length, 0); + parent.appendChild(domif); + setTimeout(function() { + var clients = parent.querySelectorAll('x-client'); + assert.equal(clients[0].uid, 12); + assert.equal(clients[1].uid, 13); + assert.equal(clients[2].uid, 14); + assert.equal(clients[1].previousElementSibling, clients[0]); + assert.equal(clients[2].previousElementSibling, clients[1]); + assert.equal(domif.previousElementSibling, clients[2]); + done(); + }); + }); + }); + test('move into doc fragment', function(done) { var el = shouldBeRemoved; assert.equal(el.parentNode, removalContainer); diff --git a/test/unit/dom-repeat-elements.html b/test/unit/dom-repeat-elements.html index ef19d13fc0..f992396b4c 100644 --- a/test/unit/dom-repeat-elements.html +++ b/test/unit/dom-repeat-elements.html @@ -367,6 +367,32 @@ }); + + + + + + + + +