From b8e3002162a74dddfcd29645ee9f6150a29a902a Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Thu, 25 Oct 2018 16:12:23 -0700 Subject: [PATCH] Hybrid compatibility for PolymerDomApi and Polymer.Iconset types. Polymer V1 applications are compiled with hand-written externs at https://github.com/google/closure-compiler/blob/master/contrib/externs/polymer-1.0.js. These externs contain some types that do not exist in the V2 code. PolymerDomApi is the type returned by the V1 Polymer.dom API, but in V2 it returns Polymer.DomApi. This adds the PolymerDomApi interface to the V2 externs, and annotates that Polymer.DomApi implements it. This allows both V1 and V2 code to use the PolymerDomApi type. Similar story for PolymerDomApi.ObserveCallback. Polymer.Iconset would ideally live in the iron-iconset repo, but many packages reference the type without actually depending on that library, so its simpler to just include it here, similar to how it worked for Polymer V1. --- externs/polymer-dom-api-externs.js | 179 ++++++++++++++++++++++++++ externs/polymer-iconset-externs.js | 36 ++++++ gulpfile.js | 1 + lib/legacy/polymer.dom.js | 15 ++- lib/utils/flattened-nodes-observer.js | 2 + 5 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 externs/polymer-dom-api-externs.js create mode 100644 externs/polymer-iconset-externs.js diff --git a/externs/polymer-dom-api-externs.js b/externs/polymer-dom-api-externs.js new file mode 100644 index 0000000000..9b52e3394d --- /dev/null +++ b/externs/polymer-dom-api-externs.js @@ -0,0 +1,179 @@ +/** + * @externs + * @fileoverview Externs for PolymerDomApi for backwards compatibility with + * the Polymer 1 externs. + */ + +/** + * A Polymer DOM API for manipulating DOM such that local DOM and light DOM + * trees are properly maintained. + * + * This type exists only to provide compatibility between compiled hybrid + * Polymer V1 and V2 code. Polymer V2 only code should simply use the DomApi + * class type. + * + * @interface + */ +var PolymerDomApi = function() {}; + +/** + * @param {?Node} node + * @return {boolean} + */ +PolymerDomApi.prototype.deepContains = function(node) {}; + +/** @param {!Node} node */ +PolymerDomApi.prototype.appendChild = function(node) {}; + +/** + * @param {!Node} oldNode + * @param {!Node} newNode + */ +PolymerDomApi.prototype.replaceChild = function(oldNode, newNode) {}; + +/** + * @param {!Node} node + * @param {?Node} beforeNode + */ +PolymerDomApi.prototype.insertBefore = function(node, beforeNode) {}; + +/** @param {!Node} node */ +PolymerDomApi.prototype.removeChild = function(node) {}; + +/** @type {!Array|!NodeList} */ +PolymerDomApi.prototype.children; + +/** @type {!Array|!NodeList} */ +PolymerDomApi.prototype.childNodes; + +/** @type {?Node} */ +PolymerDomApi.prototype.parentNode; + +/** @type {?Node} */ +PolymerDomApi.prototype.firstChild; + +/** @type {?Node} */ +PolymerDomApi.prototype.lastChild; + +/** @type {?HTMLElement} */ +PolymerDomApi.prototype.firstElementChild; + +/** @type {?HTMLElement} */ +PolymerDomApi.prototype.lastElementChild; + +/** @type {?Node} */ +PolymerDomApi.prototype.previousSibling; + +/** @type {?Node} */ +PolymerDomApi.prototype.nextSibling; + +/** @type {?HTMLElement} */ +PolymerDomApi.prototype.previousElementSibling; + +/** @type {?HTMLElement} */ +PolymerDomApi.prototype.nextElementSibling; + +/** @type {string} */ +PolymerDomApi.prototype.textContent; + +/** @type {string} */ +PolymerDomApi.prototype.innerHTML; + +/** @type {?HTMLElement} */ +PolymerDomApi.prototype.activeElement; + +/** + * @param {string} selector + * @return {?Element} + */ +PolymerDomApi.prototype.querySelector = function(selector) {}; + +/** + * @param {string} selector + * @return {!Array|!NodeList} + */ +PolymerDomApi.prototype.querySelectorAll = function(selector) {}; + +/** @return {!Array} */ +PolymerDomApi.prototype.getDistributedNodes = function() {}; + +/** @return {!Array} */ +PolymerDomApi.prototype.getDestinationInsertionPoints = function() {}; + +/** @return {?Node} */ +PolymerDomApi.prototype.getOwnerRoot = function() {}; + +/** + * @param {string} attribute + * @param {string} value + */ +PolymerDomApi.prototype.setAttribute = function(attribute, value) {}; + +/** @param {string} attribute */ +PolymerDomApi.prototype.removeAttribute = function(attribute) {}; + +/** + * @typedef {function(!PolymerDomApi.ObserveInfo)} + */ +PolymerDomApi.ObserveCallback; + +/** + * @typedef {{ + * target: !Node, + * addedNodes: !Array, + * removedNodes: !Array + * }} + */ +PolymerDomApi.ObserveInfo; + +/** + * A virtual type for observer callback handles. + * + * @interface + */ +PolymerDomApi.ObserveHandle = function() {}; + +/** + * @return {void} + */ +PolymerDomApi.ObserveHandle.prototype.disconnect = function() {}; + +/** + * Notifies callers about changes to the element's effective child nodes, + * the same list as returned by `getEffectiveChildNodes`. + * + * @param {!PolymerDomApi.ObserveCallback} callback The supplied callback + * is called with an `info` argument which is an object that provides + * the `target` on which the changes occurred, a list of any nodes + * added in the `addedNodes` array, and nodes removed in the + * `removedNodes` array. + * + * @return {!PolymerDomApi.ObserveHandle} Handle which is the argument to + * `unobserveNodes`. + */ +PolymerDomApi.prototype.observeNodes = function(callback) {}; + +/** + * Stops observing changes to the element's effective child nodes. + * + * @param {!PolymerDomApi.ObserveHandle} handle The handle for the + * callback that should no longer receive notifications. This + * handle is returned from `observeNodes`. + */ +PolymerDomApi.prototype.unobserveNodes = function(handle) {}; + +/** @type {?DOMTokenList} */ +PolymerDomApi.prototype.classList; + +/** + * @param {string} selector + * @return {!Array} + */ +PolymerDomApi.prototype.queryDistributedElements = function(selector) {}; + +/** + * Returns a list of effective child nodes for this element. + * + * @return {!Array} + */ +PolymerDomApi.prototype.getEffectiveChildNodes = function() {}; diff --git a/externs/polymer-iconset-externs.js b/externs/polymer-iconset-externs.js new file mode 100644 index 0000000000..916fee8a7d --- /dev/null +++ b/externs/polymer-iconset-externs.js @@ -0,0 +1,36 @@ +/** + * @externs + * @fileoverview Externs for Polymer.Iconset. + */ + +/** + * The interface that iconsets should obey. Iconsets are registered by setting + * their name in the IronMeta 'iconset' db, and a value of type Polymer.Iconset. + * + * Used by iron-icon but needs to live here since iron-icon, iron-iconset, etc don't + * depend on each other at all and talk only through iron-meta. + * + * @interface + */ +Polymer.Iconset = function() {}; + +/** + * Applies an icon to the given element as a css background image. This + * method does not size the element, and it's usually necessary to set + * the element's height and width so that the background image is visible. + * + * @param {Element} element The element to which the icon is applied. + * @param {string} icon The name of the icon to apply. + * @param {string=} theme (optional) The name or index of the icon to apply. + * @param {number=} scale (optional, defaults to 1) Icon scaling factor. + */ +Polymer.Iconset.prototype.applyIcon = function( + element, icon, theme, scale) {}; + +/** + * Remove an icon from the given element by undoing the changes effected + * by `applyIcon`. + * + * @param {Element} element The element from which the icon is removed. + */ +Polymer.Iconset.prototype.removeIcon = function(element) {}; diff --git a/gulpfile.js b/gulpfile.js index d2fd68f588..047db161bc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -171,6 +171,7 @@ const runClosureOnly = ({lintOnly}) => () => { 'externs/webcomponents-externs.js', 'externs/closure-types.js', 'externs/polymer-externs.js', + 'externs/polymer-dom-api-externs.js', ], extra_annotation_name: [ 'appliesMixin', diff --git a/lib/legacy/polymer.dom.js b/lib/legacy/polymer.dom.js index 392e910d36..e5499be4f7 100644 --- a/lib/legacy/polymer.dom.js +++ b/lib/legacy/polymer.dom.js @@ -39,7 +39,7 @@ export const matchesSelector = function(node, selector) { /** * Node API wrapper class returned from `Polymer.dom.(target)` when * `target` is a `Node`. - * + * @implements {PolymerDomApi} */ export class DomApi { @@ -56,7 +56,8 @@ export class DomApi { * * @param {function(this:HTMLElement, { target: !HTMLElement, addedNodes: !Array, removedNodes: !Array }):void} callback Called when direct or distributed children * of this element changes - * @return {!FlattenedNodesObserver} Observer instance + * @return {!PolymerDomApi.ObserveHandle} Observer instance + * @override */ observeNodes(callback) { return new FlattenedNodesObserver( @@ -66,9 +67,10 @@ export class DomApi { /** * Disconnects an observer previously created via `observeNodes` * - * @param {!FlattenedNodesObserver} observerHandle Observer instance + * @param {!PolymerDomApi.ObserveHandle} observerHandle Observer instance * to disconnect. * @return {void} + * @override */ unobserveNodes(observerHandle) { observerHandle.disconnect(); @@ -88,6 +90,7 @@ export class DomApi { * @param {Node} node Node to test * @return {boolean} Returns true if the given `node` is contained within * this element's light or shadow DOM. + * @override */ deepContains(node) { if (this.node.contains(node)) { @@ -110,6 +113,7 @@ export class DomApi { * exists. If the node is connected to a document this is either a * shadowRoot or the document; otherwise, it may be the node * itself or a node or document fragment containing it. + * @override */ getOwnerRoot() { return this.node.getRootNode(); @@ -120,6 +124,7 @@ export class DomApi { * an empty array. It is equivalent to `.addignedNodes({flatten:true})`. * * @return {!Array} Array of assigned nodes + * @override */ getDistributedNodes() { return (this.node.localName === 'slot') ? @@ -131,6 +136,7 @@ export class DomApi { * Returns an array of all slots this element was distributed to. * * @return {!Array} Description + * @override */ getDestinationInsertionPoints() { let ip$ = []; @@ -159,6 +165,7 @@ export class DomApi { /** * @return {!Array} Returns a flattened list of all child nodes and * nodes assigned to child slots. + * @override */ getEffectiveChildNodes() { return FlattenedNodesObserver.getFlattenedNodes( @@ -171,6 +178,7 @@ export class DomApi { * * @param {string} selector Selector to filter nodes against * @return {!Array} List of flattened child elements + * @override */ queryDistributedElements(selector) { let c$ = this.getEffectiveChildNodes(); @@ -189,6 +197,7 @@ export class DomApi { * shadow root. * * @return {Node|undefined} Currently focused element + * @override */ get activeElement() { let node = this.node; diff --git a/lib/utils/flattened-nodes-observer.js b/lib/utils/flattened-nodes-observer.js index 25ff69a1a4..266945b188 100644 --- a/lib/utils/flattened-nodes-observer.js +++ b/lib/utils/flattened-nodes-observer.js @@ -62,6 +62,7 @@ function isSlot(node) { * * @summary Class that listens for changes (additions or removals) to * "flattened nodes" on a given `node`. + * @implements {PolymerDomApi.ObserveHandle} */ export class FlattenedNodesObserver { @@ -168,6 +169,7 @@ export class FlattenedNodesObserver { * the observer. * * @return {void} + * @override */ disconnect() { if (isSlot(this._target)) {