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