406 lines
11 KiB
HTML
406 lines
11 KiB
HTML
<!--
|
|
@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/flattened-nodes-observer.html">
|
|
<link rel="import" href="../utils/flush.html">
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
const p = Element.prototype;
|
|
/**
|
|
* @const {function(this:Node, string): boolean}
|
|
*/
|
|
const normalizedMatchesSelector = p.matches || p.matchesSelector ||
|
|
p.mozMatchesSelector || p.msMatchesSelector ||
|
|
p.oMatchesSelector || p.webkitMatchesSelector;
|
|
|
|
/**
|
|
* Cross-platform `element.matches` shim.
|
|
*
|
|
* @function matchesSelector
|
|
* @memberof Polymer.dom
|
|
* @param {!Node} node Node to check selector against
|
|
* @param {string} selector Selector to match
|
|
* @return {boolean} True if node matched selector
|
|
*/
|
|
const matchesSelector = function(node, selector) {
|
|
return normalizedMatchesSelector.call(node, selector);
|
|
};
|
|
|
|
/**
|
|
* Node API wrapper class returned from `Polymer.dom.(target)` when
|
|
* `target` is a `Node`.
|
|
*
|
|
* @memberof Polymer
|
|
*/
|
|
class DomApi {
|
|
|
|
/**
|
|
* @param {Node} node Node for which to create a Polymer.dom helper object.
|
|
*/
|
|
constructor(node) {
|
|
this.node = node;
|
|
}
|
|
|
|
/**
|
|
* Returns an instance of `Polymer.FlattenedNodesObserver` that
|
|
* listens for node changes on this element.
|
|
*
|
|
* @param {function(!Element, { target: !Element, addedNodes: !Array<!Element>, removedNodes: !Array<!Element> }):void} callback Called when direct or distributed children
|
|
* of this element changes
|
|
* @return {!Polymer.FlattenedNodesObserver} Observer instance
|
|
*/
|
|
observeNodes(callback) {
|
|
return new Polymer.FlattenedNodesObserver(this.node, callback);
|
|
}
|
|
|
|
/**
|
|
* Disconnects an observer previously created via `observeNodes`
|
|
*
|
|
* @param {!Polymer.FlattenedNodesObserver} observerHandle Observer instance
|
|
* to disconnect.
|
|
* @return {void}
|
|
*/
|
|
unobserveNodes(observerHandle) {
|
|
observerHandle.disconnect();
|
|
}
|
|
|
|
/**
|
|
* Provided as a backwards-compatible API only. This method does nothing.
|
|
* @return {void}
|
|
*/
|
|
notifyObserver() {}
|
|
|
|
/**
|
|
* Returns true if the provided node is contained with this element's
|
|
* light-DOM children or shadow root, including any nested shadow roots
|
|
* of children therein.
|
|
*
|
|
* @param {Node} node Node to test
|
|
* @return {boolean} Returns true if the given `node` is contained within
|
|
* this element's light or shadow DOM.
|
|
*/
|
|
deepContains(node) {
|
|
if (this.node.contains(node)) {
|
|
return true;
|
|
}
|
|
let n = node;
|
|
let doc = node.ownerDocument;
|
|
// walk from node to `this` or `document`
|
|
while (n && n !== doc && n !== this.node) {
|
|
// use logical parentnode, or native ShadowRoot host
|
|
n = n.parentNode || n.host;
|
|
}
|
|
return n === this.node;
|
|
}
|
|
|
|
/**
|
|
* Returns the root node of this node. Equivalent to `getRoodNode()`.
|
|
*
|
|
* @return {Node} Top most element in the dom tree in which the node
|
|
* exists. If the node is connected to a document this is either a
|
|
* shadowRoot or the document; otherwise, it may be the node
|
|
* itself or a node or document fragment containing it.
|
|
*/
|
|
getOwnerRoot() {
|
|
return this.node.getRootNode();
|
|
}
|
|
|
|
/**
|
|
* For slot elements, returns the nodes assigned to the slot; otherwise
|
|
* an empty array. It is equivalent to `<slot>.addignedNodes({flatten:true})`.
|
|
*
|
|
* @return {!Array<!Node>} Array of assigned nodes
|
|
*/
|
|
getDistributedNodes() {
|
|
return (this.node.localName === 'slot') ?
|
|
this.node.assignedNodes({flatten: true}) :
|
|
[];
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all slots this element was distributed to.
|
|
*
|
|
* @return {!Array<!HTMLSlotElement>} Description
|
|
*/
|
|
getDestinationInsertionPoints() {
|
|
let ip$ = [];
|
|
let n = this.node.assignedSlot;
|
|
while (n) {
|
|
ip$.push(n);
|
|
n = n.assignedSlot;
|
|
}
|
|
return ip$;
|
|
}
|
|
|
|
/**
|
|
* Calls `importNode` on the `ownerDocument` for this node.
|
|
*
|
|
* @param {!Node} node Node to import
|
|
* @param {boolean} deep True if the node should be cloned deeply during
|
|
* import
|
|
* @return {Node} Clone of given node imported to this owner document
|
|
*/
|
|
importNode(node, deep) {
|
|
let doc = this.node instanceof Document ? this.node :
|
|
this.node.ownerDocument;
|
|
return doc.importNode(node, deep);
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!Node>} Returns a flattened list of all child nodes and
|
|
* nodes assigned to child slots.
|
|
*/
|
|
getEffectiveChildNodes() {
|
|
return Polymer.FlattenedNodesObserver.getFlattenedNodes(this.node);
|
|
}
|
|
|
|
/**
|
|
* Returns a filtered list of flattened child elements for this element based
|
|
* on the given selector.
|
|
*
|
|
* @param {string} selector Selector to filter nodes against
|
|
* @return {!Array<!HTMLElement>} List of flattened child elements
|
|
*/
|
|
queryDistributedElements(selector) {
|
|
let c$ = this.getEffectiveChildNodes();
|
|
let list = [];
|
|
for (let i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
|
|
if ((c.nodeType === Node.ELEMENT_NODE) &&
|
|
matchesSelector(c, selector)) {
|
|
list.push(c);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* For shadow roots, returns the currently focused element within this
|
|
* shadow root.
|
|
*
|
|
* @return {Node|undefined} Currently focused element
|
|
*/
|
|
get activeElement() {
|
|
let node = this.node;
|
|
return node._activeElement !== undefined ? node._activeElement : node.activeElement;
|
|
}
|
|
}
|
|
|
|
function forwardMethods(proto, methods) {
|
|
for (let i=0; i < methods.length; i++) {
|
|
let method = methods[i];
|
|
/* eslint-disable valid-jsdoc */
|
|
proto[method] = /** @this {DomApi} */ function() {
|
|
return this.node[method].apply(this.node, arguments);
|
|
};
|
|
/* eslint-enable */
|
|
}
|
|
}
|
|
|
|
function forwardReadOnlyProperties(proto, properties) {
|
|
for (let i=0; i < properties.length; i++) {
|
|
let name = properties[i];
|
|
Object.defineProperty(proto, name, {
|
|
get: function() {
|
|
const domApi = /** @type {DomApi} */(this);
|
|
return domApi.node[name];
|
|
},
|
|
configurable: true
|
|
});
|
|
}
|
|
}
|
|
|
|
function forwardProperties(proto, properties) {
|
|
for (let i=0; i < properties.length; i++) {
|
|
let name = properties[i];
|
|
Object.defineProperty(proto, name, {
|
|
get: function() {
|
|
const domApi = /** @type {DomApi} */(this);
|
|
return domApi.node[name];
|
|
},
|
|
set: function(value) {
|
|
/** @type {DomApi} */ (this).node[name] = value;
|
|
},
|
|
configurable: true
|
|
});
|
|
}
|
|
}
|
|
|
|
forwardMethods(DomApi.prototype, [
|
|
'cloneNode', 'appendChild', 'insertBefore', 'removeChild',
|
|
'replaceChild', 'setAttribute', 'removeAttribute',
|
|
'querySelector', 'querySelectorAll'
|
|
]);
|
|
|
|
forwardReadOnlyProperties(DomApi.prototype, [
|
|
'parentNode', 'firstChild', 'lastChild',
|
|
'nextSibling', 'previousSibling', 'firstElementChild',
|
|
'lastElementChild', 'nextElementSibling', 'previousElementSibling',
|
|
'childNodes', 'children', 'classList'
|
|
]);
|
|
|
|
forwardProperties(DomApi.prototype, [
|
|
'textContent', 'innerHTML'
|
|
]);
|
|
|
|
|
|
/**
|
|
* Event API wrapper class returned from `Polymer.dom.(target)` when
|
|
* `target` is an `Event`.
|
|
*/
|
|
class EventApi {
|
|
constructor(event) {
|
|
this.event = event;
|
|
}
|
|
|
|
/**
|
|
* Returns the first node on the `composedPath` of this event.
|
|
*
|
|
* @return {!EventTarget} The node this event was dispatched to
|
|
*/
|
|
get rootTarget() {
|
|
return this.event.composedPath()[0];
|
|
}
|
|
|
|
/**
|
|
* Returns the local (re-targeted) target for this event.
|
|
*
|
|
* @return {!EventTarget} The local (re-targeted) target for this event.
|
|
*/
|
|
get localTarget() {
|
|
return this.event.target;
|
|
}
|
|
|
|
/**
|
|
* Returns the `composedPath` for this event.
|
|
* @return {!Array<!EventTarget>} The nodes this event propagated through
|
|
*/
|
|
get path() {
|
|
return this.event.composedPath();
|
|
}
|
|
}
|
|
|
|
Polymer.DomApi = DomApi;
|
|
|
|
/**
|
|
* @function
|
|
* @param {boolean=} deep
|
|
* @return {!Node}
|
|
*/
|
|
Polymer.DomApi.prototype.cloneNode;
|
|
/**
|
|
* @function
|
|
* @param {!Node} node
|
|
* @return {!Node}
|
|
*/
|
|
Polymer.DomApi.prototype.appendChild;
|
|
/**
|
|
* @function
|
|
* @param {!Node} newChild
|
|
* @param {Node} refChild
|
|
* @return {!Node}
|
|
*/
|
|
Polymer.DomApi.prototype.insertBefore;
|
|
/**
|
|
* @function
|
|
* @param {!Node} node
|
|
* @return {!Node}
|
|
*/
|
|
Polymer.DomApi.prototype.removeChild;
|
|
/**
|
|
* @function
|
|
* @param {!Node} oldChild
|
|
* @param {!Node} newChild
|
|
* @return {!Node}
|
|
*/
|
|
Polymer.DomApi.prototype.replaceChild;
|
|
/**
|
|
* @function
|
|
* @param {string} name
|
|
* @param {string} value
|
|
* @return {void}
|
|
*/
|
|
Polymer.DomApi.prototype.setAttribute;
|
|
/**
|
|
* @function
|
|
* @param {string} name
|
|
* @return {void}
|
|
*/
|
|
Polymer.DomApi.prototype.removeAttribute;
|
|
/**
|
|
* @function
|
|
* @param {string} selector
|
|
* @return {?Element}
|
|
*/
|
|
Polymer.DomApi.prototype.querySelector;
|
|
/**
|
|
* @function
|
|
* @param {string} selector
|
|
* @return {!NodeList<!Element>}
|
|
*/
|
|
Polymer.DomApi.prototype.querySelectorAll;
|
|
|
|
/**
|
|
* Legacy DOM and Event manipulation API wrapper factory used to abstract
|
|
* differences between native Shadow DOM and "Shady DOM" when polyfilling on
|
|
* older browsers.
|
|
*
|
|
* Note that in Polymer 2.x use of `Polymer.dom` is no longer required and
|
|
* in the majority of cases simply facades directly to the standard native
|
|
* API.
|
|
*
|
|
* @namespace
|
|
* @summary Legacy DOM and Event manipulation API wrapper factory used to
|
|
* abstract differences between native Shadow DOM and "Shady DOM."
|
|
* @memberof Polymer
|
|
* @param {(Node|Event)=} obj Node or event to operate on
|
|
* @return {!DomApi|!EventApi} Wrapper providing either node API or event API
|
|
*/
|
|
Polymer.dom = function(obj) {
|
|
obj = obj || document;
|
|
if (!obj.__domApi) {
|
|
let helper;
|
|
if (obj instanceof Event) {
|
|
helper = new EventApi(obj);
|
|
} else {
|
|
helper = new DomApi(obj);
|
|
}
|
|
obj.__domApi = helper;
|
|
}
|
|
return obj.__domApi;
|
|
};
|
|
|
|
Polymer.dom.matchesSelector = matchesSelector;
|
|
|
|
/**
|
|
* Forces several classes of asynchronously queued tasks to flush:
|
|
* - Debouncers added via `Polymer.enqueueDebouncer`
|
|
* - ShadyDOM distribution
|
|
*
|
|
* This method facades to `Polymer.flush`.
|
|
*
|
|
* @memberof Polymer.dom
|
|
*/
|
|
Polymer.dom.flush = Polymer.flush;
|
|
|
|
/**
|
|
* Adds a `Polymer.Debouncer` to a list of globally flushable tasks.
|
|
*
|
|
* This method facades to `Polymer.enqueueDebouncer`.
|
|
*
|
|
* @memberof Polymer.dom
|
|
* @param {Polymer.Debouncer} debouncer Debouncer to enqueue
|
|
*/
|
|
Polymer.dom.addDebouncer = Polymer.enqueueDebouncer;
|
|
})();
|
|
</script>
|