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 './boot.js'; 11 12import { calculateSplices } from './array-splice.js'; 13import { microTask } from './async.js'; 14import { wrap } from './wrap.js'; 15 16/** 17 * Returns true if `node` is a slot element 18 * @param {!Node} node Node to test. 19 * @return {boolean} Returns true if the given `node` is a slot 20 * @private 21 */ 22function isSlot(node) { 23 return (node.localName === 'slot'); 24} 25 26/** 27 * Class that listens for changes (additions or removals) to 28 * "flattened nodes" on a given `node`. The list of flattened nodes consists 29 * of a node's children and, for any children that are `<slot>` elements, 30 * the expanded flattened list of `assignedNodes`. 31 * For example, if the observed node has children `<a></a><slot></slot><b></b>` 32 * and the `<slot>` has one `<div>` assigned to it, then the flattened 33 * nodes list is `<a></a><div></div><b></b>`. If the `<slot>` has other 34 * `<slot>` elements assigned to it, these are flattened as well. 35 * 36 * The provided `callback` is called whenever any change to this list 37 * of flattened nodes occurs, where an addition or removal of a node is 38 * considered a change. The `callback` is called with one argument, an object 39 * containing an array of any `addedNodes` and `removedNodes`. 40 * 41 * Note: the callback is called asynchronous to any changes 42 * at a microtask checkpoint. This is because observation is performed using 43 * `MutationObserver` and the `<slot>` element's `slotchange` event which 44 * are asynchronous. 45 * 46 * An example: 47 * ```js 48 * class TestSelfObserve extends PolymerElement { 49 * static get is() { return 'test-self-observe';} 50 * connectedCallback() { 51 * super.connectedCallback(); 52 * this._observer = new FlattenedNodesObserver(this, (info) => { 53 * this.info = info; 54 * }); 55 * } 56 * disconnectedCallback() { 57 * super.disconnectedCallback(); 58 * this._observer.disconnect(); 59 * } 60 * } 61 * customElements.define(TestSelfObserve.is, TestSelfObserve); 62 * ``` 63 * 64 * @summary Class that listens for changes (additions or removals) to 65 * "flattened nodes" on a given `node`. 66 * @implements {PolymerDomApi.ObserveHandle} 67 */ 68export let FlattenedNodesObserver = class { 69 70 /** 71 * Returns the list of flattened nodes for the given `node`. 72 * This list consists of a node's children and, for any children 73 * that are `<slot>` elements, the expanded flattened list of `assignedNodes`. 74 * For example, if the observed node has children `<a></a><slot></slot><b></b>` 75 * and the `<slot>` has one `<div>` assigned to it, then the flattened 76 * nodes list is `<a></a><div></div><b></b>`. If the `<slot>` has other 77 * `<slot>` elements assigned to it, these are flattened as well. 78 * 79 * @param {!HTMLElement|!HTMLSlotElement} node The node for which to 80 * return the list of flattened nodes. 81 * @return {!Array<!Node>} The list of flattened nodes for the given `node`. 82 * @nocollapse See https://github.com/google/closure-compiler/issues/2763 83 */ 84 // eslint-disable-next-line 85 static getFlattenedNodes(node) { 86 const wrapped = wrap(node); 87 if (isSlot(node)) {
88 node = /** @type {!HTMLSlotElement} */(node); // eslint-disable-line no-self-assign 89 return wrapped.assignedNodes({flatten: true}); 90 } else { 91 const results = []; 92 for (let i = 0; i < wrapped.childNodes.length; i++) { 93 const node = wrapped.childNodes[i]; 94 if (isSlot(node)) { 95 const slotNode = /** @type {!HTMLSlotElement} */ (node); 96 results.push(...wrap(slotNode).assignedNodes({ flatten: true })); 97 } else { 98 results.push(node); 99 } 100 } 101 return results; 102 } 103 } 104 105 /** 106 * @param {!HTMLElement} target Node on which to listen for changes. 107 * @param {?function(this: Element, { target: !HTMLElement, addedNodes: !Array<!Element>, removedNodes: !Array<!Element> }):void} callback Function called when there are additions 108 * or removals from the target's list of flattened nodes. 109 */ 110 // eslint-disable-next-line 111 constructor(target, callback) { 112 /** 113 * @type {MutationObserver} 114 * @private 115 */ 116 this._shadyChildrenObserver = null; 117 /** 118 * @type {MutationObserver} 119 * @private 120 */ 121 this._nativeChildrenObserver = null; 122 this._connected = false; 123 /** 124 * @type {!HTMLElement} 125 * @private 126 */ 127 this._target = target; 128 this.callback = callback; 129 this._effectiveNodes = []; 130 this._observer = null; 131 this._scheduled = false; 132 /** 133 * @type {function()} 134 * @private 135 */ 136 this._boundSchedule = () => { 137 this._schedule(); 138 }; 139 this.connect(); 140 this._schedule(); 141 } 142 143 /** 144 * Activates an observer. This method is automatically called when 145 * a `FlattenedNodesObserver` is created. It should only be called to 146 * re-activate an observer that has been deactivated via the `disconnect` method. 147 * 148 * @return {void} 149 */ 150 connect() { 151 if (isSlot(this._target)) { 152 this._listenSlots([this._target]); 153 } else if (wrap(this._target).children) { 154 this._listenSlots( 155 /** @type {!NodeList<!Node>} */ (wrap(this._target).children)); 156 if (window.ShadyDOM) { 157 this._shadyChildrenObserver = 158 window.ShadyDOM.observeChildren(this._target, (mutations) => { 159 this._processMutations(mutations); 160 }); 161 } else { 162 this._nativeChildrenObserver = 163 new MutationObserver((mutations) => { 164 this._processMutations(mutations); 165 }); 166 this._nativeChildrenObserver.observe(this._target, {childList: true}); 167 } 168 } 169 this._connected = true; 170 } 171 172 /** 173 * Deactivates the flattened nodes observer. After calling this method 174 * the observer callback will not be called when changes to flattened nodes 175 * occur. The `connect` method may be subsequently called to reactivate 176 * the observer. 177 * 178 * @return {void} 179 * @override 180 */ 181 disconnect() { 182 if (isSlot(this._target)) { 183 this._unlistenSlots([this._target]); 184 } else if (wrap(this._target).children) { 185 this._unlistenSlots( 186 /** @type {!NodeList<!Node>} */ (wrap(this._target).children)); 187 if (window.ShadyDOM && this._shadyChildrenObserver) { 188 window.ShadyDOM.unobserveChildren(this._shadyChildrenObserver); 189 this._shadyChildrenObserver = null; 190 } else if (this._nativeChildrenObserver) { 191 this._nativeChildrenObserver.disconnect(); 192 this._nativeChildrenObserver = null; 193 } 194 } 195 this._connected = false; 196 } 197 198 /** 199 * @return {void} 200 * @private 201 */ 202 _schedule() { 203 if (!this._scheduled) { 204 this._scheduled = true; 205 microTask.run(() => this.flush()); 206 } 207 } 208 209 /** 210 * @param {Array<MutationRecord>} mutations Mutations signaled by the mutation observer 211 * @return {void} 212 * @private 213 */ 214 _processMutations(mutations) { 215 this._processSlotMutations(mutations); 216 this.flush(); 217 } 218 219 /** 220 * @param {Array<MutationRecord>} mutations Mutations signaled by the mutation observer 221 * @return {void} 222 * @private 223 */ 224 _processSlotMutations(mutations) { 225 if (mutations) { 226 for (let i=0; i < mutations.length; i++) { 227 let mutation = mutations[i]; 228 if (mutation.addedNodes) { 229 this._listenSlots(mutation.addedNodes); 230 }
231 if (mutation.removedNodes) { 232 this._unlistenSlots(mutation.removedNodes); 233 } 234 } 235 } 236 } 237 238 /** 239 * Flushes the observer causing any pending changes to be immediately 240 * delivered the observer callback. By default these changes are delivered 241 * asynchronously at the next microtask checkpoint. 242 * 243 * @return {boolean} Returns true if any pending changes caused the observer 244 * callback to run. 245 */ 246 flush() { 247 if (!this._connected) { 248 return false; 249 } 250 if (window.ShadyDOM) { 251 ShadyDOM.flush(); 252 } 253 if (this._nativeChildrenObserver) { 254 this._processSlotMutations(this._nativeChildrenObserver.takeRecords()); 255 } else if (this._shadyChildrenObserver) { 256 this._processSlotMutations(this._shadyChildrenObserver.takeRecords()); 257 } 258 this._scheduled = false; 259 let info = { 260 target: this._target, 261 addedNodes: [], 262 removedNodes: [] 263 }; 264 let newNodes = this.constructor.getFlattenedNodes(this._target); 265 let splices = calculateSplices(newNodes, 266 this._effectiveNodes); 267 // process removals 268 for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) { 269 for (let j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) { 270 info.removedNodes.push(n); 271 } 272 } 273 // process adds 274 for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) { 275 for (let j=s.index; j < s.index + s.addedCount; j++) { 276 info.addedNodes.push(newNodes[j]); 277 } 278 } 279 // update cache 280 this._effectiveNodes = newNodes; 281 let didFlush = false; 282 if (info.addedNodes.length || info.removedNodes.length) { 283 didFlush = true; 284 this.callback.call(this._target, info); 285 } 286 return didFlush; 287 } 288 289 /** 290 * @param {!Array<!Node>|!NodeList<!Node>} nodeList Nodes that could change 291 * @return {void} 292 * @private 293 */ 294 _listenSlots(nodeList) { 295 for (let i=0; i < nodeList.length; i++) { 296 let n = nodeList[i]; 297 if (isSlot(n)) { 298 n.addEventListener('slotchange', this._boundSchedule); 299 } 300 } 301 } 302 303 /** 304 * @param {!Array<!Node>|!NodeList<!Node>} nodeList Nodes that could change 305 * @return {void} 306 * @private 307 */ 308 _unlistenSlots(nodeList) { 309 for (let i=0; i < nodeList.length; i++) { 310 let n = nodeList[i]; 311 if (isSlot(n)) { 312 n.removeEventListener('slotchange', this._boundSchedule); 313 } 314 } 315 } 316 317};
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.