PageSourceSearch

https://www.safedriving.or.kr/resources/anyid/libs/polymer/3.5.2/lib/utils/flattened-nodes-observer.js

js safedriving.or.kr collected 2026-09-26 04:01:45 UTC 10,536 bytes, 318 lines download raw bytes

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

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.