1 2/** 3@license 4Copyright (c) 2017 The Polymer Project Authors. All rights reserved. 5This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 6The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 7The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 8Code distributed by Google as part of the polymer project is also 9subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 10*/ 11import '../utils/boot.js'; 12import { wrap } from '../utils/wrap.js'; 13import '../utils/settings.js'; 14import { FlattenedNodesObserver } from '../utils/flattened-nodes-observer.js'; 15export { flush, enqueueDebouncer as addDebouncer } from '../utils/flush.js'; 16/* eslint-disable no-unused-vars */ 17import { Debouncer } from '../utils/debounce.js'; // used in type annotations 18/* eslint-enable no-unused-vars */ 19 20const p = Element.prototype; 21/** 22 * @const {function(this:Node, string): boolean} 23 */ 24const normalizedMatchesSelector = p.matches || p.matchesSelector || 25 p.mozMatchesSelector || p.msMatchesSelector || 26 p.oMatchesSelector || p.webkitMatchesSelector; 27 28/** 29 * Cross-platform `element.matches` shim. 30 * 31 * @function matchesSelector 32 * @param {!Node}
32 node Node to check selector against 33 * @param {string} selector Selector to match 34 * @return {boolean} True if node matched selector 35 */ 36export const matchesSelector = function(node, selector) { 37 return normalizedMatchesSelector.call(node, selector); 38}; 39 40/** 41 * Node API wrapper class returned from `Polymer.dom.(target)` when 42 * `target` is a `Node`. 43 * @implements {PolymerDomApi} 44 * @unrestricted 45 */ 46class DomApiNative { 47 48 /** 49 * @param {!Node} node Node for which to create a Polymer.dom helper object. 50 */ 51 constructor(node) { 52 if (window['ShadyDOM'] && window['ShadyDOM']['inUse']) { 53 window['ShadyDOM']['patch'](node); 54 } 55 this.node = node; 56 } 57 58 /** 59 * Returns an instance of `FlattenedNodesObserver` that 60 * listens for node changes on this element. 61 * 62 * @param {function(this:HTMLElement, { target: !HTMLElement, addedNodes: !Array<!Element>, removedNodes: !Array<!Element> }):void} callback Called when direct or distributed children 63 * of this element changes 64 * @return {!PolymerDomApi.ObserveHandle} Observer instance 65 * @override 66 */ 67 observeNodes(callback) { 68 return new FlattenedNodesObserver( 69 /** @type {!HTMLElement} */(this.node), callback); 70 } 71 72 /** 73 * Disconnects an observer previously created via `observeNodes` 74 * 75 * @param {!PolymerDomApi.ObserveHandle} observerHandle Observer instance 76 * to disconnect. 77 * @return {void} 78 * @override 79 */ 80 unobserveNodes(observerHandle) { 81 observerHandle.disconnect(); 82 } 83 84 /** 85 * Provided as a backwards-compatible API only. This method does nothing. 86 * @return {void} 87 */ 88 notifyObserver() {} 89 90 /** 91 * Returns true if the provided node is contained with this element's 92 * light-DOM children or shadow root, including any nested shadow roots 93 * of children therein. 94 * 95 * @param {Node} node Node to test 96 * @return {boolean} Returns true if the given `node` is contained within 97 * this element's light or shadow DOM. 98 * @override 99 */ 100 deepContains(node) { 101 if (wrap(this.node).contains(node)) { 102 return true; 103 } 104 let n = node; 105 let doc = node.ownerDocument; 106 // walk from node to `this` or `document` 107 while (n && n !== doc && n !== this.node) { 108 // use logical parentnode, or native ShadowRoot host 109 n = wrap(n).parentNode || wrap(n).host; 110 } 111 return n === this.node; 112 } 113 114 /** 115 * Returns the root node of this node. Equivalent to `getRootNode()`. 116 * 117 * @return {!Node} Top most element in the dom tree in which the node 118 * exists. If the node is connected to a document this is either a 119 * shadowRoot or the document; otherwise, it may be the node 120 * itself or a node or document fragment containing it. 121 * @override 122 */ 123 getOwnerRoot() { 124 return wrap(this.node).getRootNode(); 125 } 126 127 /** 128 * For slot elements, returns the nodes assigned to the slot; otherwise 129 * an empty array. It is equivalent to `<slot>.addignedNodes({flatten:true})`. 130 * 131 * @return {!Array<!Node>} Array of assigned nodes 132 * @override 133 */ 134 getDistributedNodes() { 135 return (this.node.localName === 'slot') ? 136 wrap(this.node).assignedNodes({flatten: true}) : 137 []; 138 } 139 140 /** 141 * Returns an array of all slots this element was distributed to. 142 * 143 * @return {!Array<!HTMLSlotElement>} Description 144 * @override 145 */ 146 getDestinationInsertionPoints() { 147 let ip$ = []; 148 let n = wrap(this.node).assignedSlot; 149 while (n) { 150 ip$.push(n); 151 n = wrap(n).assignedSlot; 152 } 153 return ip$; 154 } 155 156 /** 157 * Calls `importNode` on the `ownerDocument` for this node. 158 * 159 * @param {!Node} node Node to import 160 * @param {boolean} deep True if the node should be cloned deeply during 161 * import 162 * @return {Node} Clone of given node imported to this owner document 163 */ 164 importNode(node, deep) { 165 let doc = this.node instanceof Document ? this.node : 166 this.node.ownerDocument; 167 return wrap(doc).importNode(node, deep); 168 } 169 170 /** 171 * @return {!Array<!Node>} Returns a flattened list of all child nodes and 172 * nodes assigned to child slots. 173 * @override 174 */ 175 getEffectiveChildNodes() { 176 return FlattenedNodesObserver.getFlattenedNodes(
177 /** @type {!HTMLElement} */ (this.node)); 178 } 179 180 /** 181 * Returns a filtered list of flattened child elements for this element based 182 * on the given selector. 183 * 184 * @param {string} selector Selector to filter nodes against 185 * @return {!Array<!HTMLElement>} List of flattened child elements 186 * @override 187 */ 188 queryDistributedElements(selector) { 189 let c$ = this.getEffectiveChildNodes(); 190 let list = []; 191 for (let i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) { 192 if ((c.nodeType === Node.ELEMENT_NODE) && 193 matchesSelector(c, selector)) { 194 list.push(c); 195 } 196 } 197 return list; 198 } 199 200 /** 201 * For shadow roots, returns the currently focused element within this 202 * shadow root. 203 * 204 * return {Node|undefined} Currently focused element 205 * @override 206 */ 207 get activeElement() { 208 let node = this.node; 209 return node._activeElement !== undefined ? node._activeElement : node.activeElement; 210 } 211} 212 213function forwardMethods(proto, methods) { 214 for (let i=0; i < methods.length; i++) { 215 let method = methods[i]; 216 /* eslint-disable valid-jsdoc */ 217 proto[method] = /** @this {DomApiNative} */ function() { 218 return this.node[method].apply(this.node, arguments); 219 }; 220 /* eslint-enable */ 221 } 222} 223 224function forwardReadOnlyProperties(proto, properties) { 225 for (let i=0; i < properties.length; i++) { 226 let name = properties[i]; 227 Object.defineProperty(proto, name, { 228 get: function() { 229 const domApi = /** @type {DomApiNative} */(this); 230 return domApi.node[name]; 231 }, 232 configurable: true 233 }); 234 } 235} 236 237function forwardProperties(proto, properties) { 238 for (let i=0; i < properties.length; i++) { 239 let name = properties[i]; 240 Object.defineProperty(proto, name, { 241 /** 242 * @this {DomApiNative} 243 * @return {*} . 244 */ 245 get: function() { 246 return this.node[name]; 247 }, 248 /** 249 * @this {DomApiNative} 250 * @param {*} value . 251 */ 252 set: function(value) { 253 this.node[name] = value; 254 }, 255 configurable: true 256 }); 257 } 258} 259 260 261/** 262 * Event API wrapper class returned from `dom.(target)` when 263 * `target` is an `Event`. 264 */ 265export class EventApi { 266 constructor(event) { 267 this.event = event; 268 } 269 270 /** 271 * Returns the first node on the `composedPath` of this event. 272 * 273 * @return {!EventTarget} The node this event was dispatched to 274 */ 275 get rootTarget() { 276 return this.path[0]; 277 } 278 279 /** 280 * Returns the local (re-targeted) target for this event. 281 * 282 * @return {!EventTarget} The local (re-targeted) target for this event. 283 */ 284 get localTarget() { 285 return this.event.target; 286 } 287 288 /** 289 * Returns the `composedPath` for this event. 290 * @return {!Array<!EventTarget>} The nodes this event propagated through 291 */ 292 get path() { 293 return this.event.composedPath(); 294 } 295} 296 297/** 298 * @function 299 * @param {boolean=} deep 300 * @return {!Node} 301 */ 302DomApiNative.prototype.cloneNode; 303/** 304 * @function 305 * @param {!Node} node 306 * @return {!Node} 307 */ 308DomApiNative.prototype.appendChild; 309/** 310 * @function 311 * @param {!Node} newChild 312 * @param {Node} refChild 313 * @return {!Node} 314 */ 315DomApiNative.prototype.insertBefore; 316/** 317 * @function 318 * @param {!Node} node 319 * @return {!Node} 320 */ 321DomApiNative.prototype.removeChild; 322/** 323 * @function 324 * @param {!Node} oldChild 325 * @param {!Node} newChild 326 * @return {!Node} 327 */ 328DomApiNative.prototype.replaceChild; 329/** 330 * @function 331 * @param {string} name 332 * @param {string} value 333 * @return {void} 334 */ 335DomApiNative.prototype.setAttribute; 336/** 337 * @function 338 * @param {string} name 339 * @return {void} 340 */ 341DomApiNative.prototype.removeAttribute; 342/** 343 * @function 344 * @param {string} selector 345 * @return {?Element} 346 */ 347DomApiNative.prototype.querySelector; 348/** 349 * @function 350 * @param {string} selector 351 * @return {!NodeList<!Element>} 352 */ 353DomApiNative.prototype.querySelectorAll; 354 355/** @type {?Node} */ 356DomApiNative.prototype.parentNode; 357/** @type {?Node} */ 358DomApiNative.prototype.firstChild; 359/** @type {?Node} */ 360DomApiNative.prototype.lastChild; 361/** @type {?Node} */ 362DomApiNative.prototype.nextSibling; 363/** @type {?Node} */ 364DomApiNative.prototype.previousSibling; 365/** @type {?HTMLElement} */ 366DomApiNative.prototype.firstElementChild; 367/** @type {?HTMLElement} */ 368DomApiNative.prototype.lastElementChild; 369/** @type {?HTMLElement} */ 370DomApiNative.prototype.nextElementSibling; 371/** @type {?HTMLElement} */ 372DomApiNative.prototype.previousElementSibling;
373/** @type {!Array<!Node>} */ 374DomApiNative.prototype.childNodes; 375/** @type {!Array<!HTMLElement>} */ 376DomApiNative.prototype.children; 377/** @type {?DOMTokenList} */ 378DomApiNative.prototype.classList; 379 380/** @type {string} */ 381DomApiNative.prototype.textContent; 382/** @type {string} */ 383DomApiNative.prototype.innerHTML; 384 385let DomApiImpl = DomApiNative; 386 387if (window['ShadyDOM'] && window['ShadyDOM']['inUse'] && window['ShadyDOM']['noPatch'] && window['ShadyDOM']['Wrapper']) { 388 389 /** 390 * @private 391 * @extends {HTMLElement} 392 */ 393 class Wrapper extends window['ShadyDOM']['Wrapper'] {} 394 395 // copy bespoke API onto wrapper 396 Object.getOwnPropertyNames(DomApiNative.prototype).forEach((prop) => { 397 if (prop != 'activeElement') { 398 Wrapper.prototype[prop] = DomApiNative.prototype[prop]; 399 } 400 }); 401 402 // Note, `classList` is here only for legacy compatibility since it does not 403 // trigger distribution in v1 Shadow DOM. 404 forwardReadOnlyProperties(Wrapper.prototype, [ 405 'classList' 406 ]); 407 408 DomApiImpl = Wrapper; 409 410 Object.defineProperties(EventApi.prototype, { 411 412 // Returns the "lowest" node in the same root as the event's currentTarget. 413 // When in `noPatch` mode, this must be calculated by walking the event's 414 // path. 415 localTarget: { 416 get() { 417 const current = this.event.currentTarget; 418 const currentRoot = current && dom(current).getOwnerRoot(); 419 const p$ = this.path; 420 for (let i = 0; i < p$.length; i++) { 421 const e = p$[i]; 422 if (dom(e).getOwnerRoot() === currentRoot) { 423 return e; 424 } 425 } 426 }, 427 configurable: true 428 }, 429 430 path: { 431 get() { 432 return window['ShadyDOM']['composedPath'](this.event); 433 }, 434 configurable: true 435 } 436 }); 437 438} else { 439 440 // Methods that can provoke distribution or must return the logical, not 441 // composed tree. 442 forwardMethods(DomApiNative.prototype, [ 443 'cloneNode', 'appendChild', 'insertBefore', 'removeChild', 444 'replaceChild', 'setAttribute', 'removeAttribute', 445 'querySelector', 'querySelectorAll', 'attachShadow' 446 ]); 447 448 // Properties that should return the logical, not composed tree. Note, `classList` 449 // is here only for legacy compatibility since it does not trigger distribution 450 // in v1 Shadow DOM. 451 forwardReadOnlyProperties(DomApiNative.prototype, [ 452 'parentNode', 'firstChild', 'lastChild', 453 'nextSibling', 'previousSibling', 'firstElementChild', 454 'lastElementChild', 'nextElementSibling', 'previousElementSibling', 455 'childNodes', 'children', 'classList', 'shadowRoot' 456 ]); 457 458 forwardProperties(DomApiNative.prototype, [ 459 'textContent', 'innerHTML', 'className' 460 ]); 461} 462 463export const DomApi = DomApiImpl; 464 465/** 466 * Legacy DOM and Event manipulation API wrapper factory used to abstract 467 * differences between native Shadow DOM and "Shady DOM" when polyfilling on 468 * older browsers. 469 * 470 * Note that in Polymer 2.x use of `Polymer.dom` is no longer required and 471 * in the majority of cases simply facades directly to the standard native 472 * API. 473 * 474 * @summary Legacy DOM and Event manipulation API wrapper factory used to 475 * abstract differences between native Shadow DOM and "Shady DOM." 476 * @param {(Node|Event|DomApiNative|EventApi)=} obj Node or event to operate on 477 * @return {!DomApiNative|!EventApi} Wrapper providing either node API or event API 478 */ 479export const dom = function(obj) { 480 obj = obj || document; 481 if (obj instanceof DomApiImpl) {
482 return /** @type {!DomApi} */(obj); 483 } 484 if (obj instanceof EventApi) { 485 return /** @type {!EventApi} */(obj); 486 } 487 let helper = obj['__domApi']; 488 if (!helper) { 489 if (obj instanceof Event) { 490 helper = new EventApi(obj); 491 } else { 492 helper = new DomApiImpl(/** @type {Node} */(obj)); 493 } 494 obj['__domApi'] = helper; 495 } 496 return helper; 497};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.