| OLD | NEW |
| (Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2017 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 <link rel="import" href="../utils/boot.html"> |
| 11 <link rel="import" href="../utils/array-splice.html"> |
| 12 <link rel="import" href="../utils/async.html"> |
| 13 <script> |
| 14 (function() { |
| 15 'use strict'; |
| 16 |
| 17 function isSlot(node) { |
| 18 return (node.localName === 'slot'); |
| 19 } |
| 20 |
| 21 /** |
| 22 * Class that listens for changes (additions or removals) to |
| 23 * "flattened nodes" on a given `node`. The list of flattened nodes consists |
| 24 * of a node's children and, for any children that are `<slot>` elements, |
| 25 * the expanded flattened list of `assignedNodes`. |
| 26 * For example, if the observed node has children `<a></a><slot></slot><b></b>
` |
| 27 * and the `<slot>` has one `<div>` assigned to it, then the flattened |
| 28 * nodes list is `<a></a><div></div><b></b>`. If the `<slot>` has other |
| 29 * `<slot>` elements assigned to it, these are flattened as well. |
| 30 * |
| 31 * The provided `callback` is called whenever any change to this list |
| 32 * of flattened nodes occurs, where an addition or removal of a node is |
| 33 * considered a change. The `callback` is called with one argument, an object |
| 34 * containing an array of any `addedNodes` and `removedNodes`. |
| 35 * |
| 36 * Note: the callback is called asynchronous to any changes |
| 37 * at a microtask checkpoint. This is because observation is performed using |
| 38 * `MutationObserver` and the `<slot>` element's `slotchange` event which |
| 39 * are asynchronous. |
| 40 * |
| 41 * @memberof Polymer |
| 42 * @param {Node} target Node on which to listen for changes. |
| 43 * @param {Function} callback Function called when there are additions |
| 44 * or removals from the target's list of flattened nodes. |
| 45 * @summary Class that listens for changes (additions or removals) to |
| 46 * "flattened nodes" on a given `node`. |
| 47 */ |
| 48 class FlattenedNodesObserver { |
| 49 |
| 50 /** |
| 51 * Returns the list of flattened nodes for the given `node`. |
| 52 * This list consists of a node's children and, for any children |
| 53 * that are `<slot>` elements, the expanded flattened list of `assignedNodes
`. |
| 54 * For example, if the observed node has children `<a></a><slot></slot><b></
b>` |
| 55 * and the `<slot>` has one `<div>` assigned to it, then the flattened |
| 56 * nodes list is `<a></a><div></div><b></b>`. If the `<slot>` has other |
| 57 * `<slot>` elements assigned to it, these are flattened as well. |
| 58 * |
| 59 * @param {Node} node The node for which to return the list of flattened nod
es. |
| 60 * @return {Array} The list of flattened nodes for the given `node`. |
| 61 */ |
| 62 static getFlattenedNodes(node) { |
| 63 if (isSlot(node)) { |
| 64 return node.assignedNodes({flatten: true}); |
| 65 } else { |
| 66 return Array.from(node.childNodes).map(node => { |
| 67 if (isSlot(node)) { |
| 68 return node.assignedNodes({flatten: true}); |
| 69 } else { |
| 70 return [node]; |
| 71 } |
| 72 }).reduce((a, b) => a.concat(b), []); |
| 73 } |
| 74 } |
| 75 |
| 76 constructor(target, callback) { |
| 77 /** @type {MutationObserver} */ |
| 78 this._shadyChildrenObserver = null; |
| 79 /** @type {MutationObserver} */ |
| 80 this._nativeChildrenObserver = null; |
| 81 this._connected = false; |
| 82 this._target = target; |
| 83 this.callback = callback; |
| 84 this._effectiveNodes = []; |
| 85 this._observer = null; |
| 86 this._scheduled = false; |
| 87 this._boundSchedule = () => { |
| 88 this._schedule(); |
| 89 } |
| 90 this.connect(); |
| 91 this._schedule(); |
| 92 } |
| 93 |
| 94 /** |
| 95 * Activates an observer. This method is automatically called when |
| 96 * a `FlattenedNodesObserver` is created. It should only be called to |
| 97 * re-activate an observer that has been deactivated via the `disconnect` me
thod. |
| 98 */ |
| 99 connect() { |
| 100 if (isSlot(this._target)) { |
| 101 this._listenSlots([this._target]); |
| 102 } else { |
| 103 this._listenSlots(this._target.children); |
| 104 if (window.ShadyDOM) { |
| 105 this._shadyChildrenObserver = |
| 106 ShadyDOM.observeChildren(this._target, (mutations) => { |
| 107 this._processMutations(mutations); |
| 108 }); |
| 109 } else { |
| 110 this._nativeChildrenObserver = |
| 111 new MutationObserver((mutations) => { |
| 112 this._processMutations(mutations); |
| 113 }); |
| 114 this._nativeChildrenObserver.observe(this._target, {childList: true}); |
| 115 } |
| 116 } |
| 117 this._connected = true; |
| 118 } |
| 119 |
| 120 /** |
| 121 * Deactivates the flattened nodes observer. After calling this method |
| 122 * the observer callback will not be called when changes to flattened nodes |
| 123 * occur. The `connect` method may be subsequently called to reactivate |
| 124 * the observer. |
| 125 */ |
| 126 disconnect() { |
| 127 if (isSlot(this._target)) { |
| 128 this._unlistenSlots([this._target]); |
| 129 } else { |
| 130 this._unlistenSlots(this._target.children); |
| 131 if (window.ShadyDOM && this._shadyChildrenObserver) { |
| 132 ShadyDOM.unobserveChildren(this._shadyChildrenObserver); |
| 133 this._shadyChildrenObserver = null; |
| 134 } else if (this._nativeChildrenObserver) { |
| 135 this._nativeChildrenObserver.disconnect(); |
| 136 this._nativeChildrenObserver = null; |
| 137 } |
| 138 } |
| 139 this._connected = false; |
| 140 } |
| 141 |
| 142 _schedule() { |
| 143 if (!this._scheduled) { |
| 144 this._scheduled = true; |
| 145 Polymer.Async.microTask.run(() => this.flush()); |
| 146 } |
| 147 } |
| 148 |
| 149 _processMutations(mutations) { |
| 150 this._processSlotMutations(mutations); |
| 151 this.flush(); |
| 152 } |
| 153 |
| 154 _processSlotMutations(mutations) { |
| 155 if (mutations) { |
| 156 for (let i=0; i < mutations.length; i++) { |
| 157 let mutation = mutations[i]; |
| 158 if (mutation.addedNodes) { |
| 159 this._listenSlots(mutation.addedNodes); |
| 160 } |
| 161 if (mutation.removedNodes) { |
| 162 this._unlistenSlots(mutation.removedNodes); |
| 163 } |
| 164 } |
| 165 } |
| 166 } |
| 167 |
| 168 /** |
| 169 * Flushes the observer causing any pending changes to be immediately |
| 170 * delivered the observer callback. By default these changes are delivered |
| 171 * asynchronously at the next microtask checkpoint. |
| 172 * |
| 173 * @return {boolean} Returns true if any pending changes caused the observer |
| 174 * callback to run. |
| 175 */ |
| 176 flush() { |
| 177 if (!this._connected) { |
| 178 return; |
| 179 } |
| 180 if (window.ShadyDOM) { |
| 181 ShadyDOM.flush(); |
| 182 } |
| 183 if (this._nativeChildrenObserver) { |
| 184 this._processSlotMutations(this._nativeChildrenObserver.takeRecords()); |
| 185 } else if (this.shadyChildrenObserver) { |
| 186 this._processSlotMutations(this._shadyChildrenObserver.takeRecords()); |
| 187 } |
| 188 this._scheduled = false; |
| 189 let info = { |
| 190 target: this._target, |
| 191 addedNodes: [], |
| 192 removedNodes: [] |
| 193 }; |
| 194 let newNodes = this.constructor.getFlattenedNodes(this._target); |
| 195 let splices = Polymer.ArraySplice.calculateSplices(newNodes, |
| 196 this._effectiveNodes); |
| 197 // process removals |
| 198 for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) { |
| 199 for (let j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) { |
| 200 info.removedNodes.push(n); |
| 201 } |
| 202 } |
| 203 // process adds |
| 204 for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) { |
| 205 for (let j=s.index; j < s.index + s.addedCount; j++) { |
| 206 info.addedNodes.push(newNodes[j]); |
| 207 } |
| 208 } |
| 209 // update cache |
| 210 this._effectiveNodes = newNodes; |
| 211 let didFlush = false; |
| 212 if (info.addedNodes.length || info.removedNodes.length) { |
| 213 didFlush = true; |
| 214 this.callback.call(this._target, info); |
| 215 } |
| 216 return didFlush; |
| 217 } |
| 218 |
| 219 _listenSlots(nodeList) { |
| 220 for (let i=0; i < nodeList.length; i++) { |
| 221 let n = nodeList[i]; |
| 222 if (isSlot(n)) { |
| 223 n.addEventListener('slotchange', this._boundSchedule); |
| 224 } |
| 225 } |
| 226 } |
| 227 |
| 228 _unlistenSlots(nodeList) { |
| 229 for (let i=0; i < nodeList.length; i++) { |
| 230 let n = nodeList[i]; |
| 231 if (isSlot(n)) { |
| 232 n.removeEventListener('slotchange', this._boundSchedule); |
| 233 } |
| 234 } |
| 235 } |
| 236 |
| 237 } |
| 238 |
| 239 Polymer.FlattenedNodesObserver = FlattenedNodesObserver; |
| 240 |
| 241 })(); |
| 242 </script> |
| OLD | NEW |