source: imaps-frontend/node_modules/resize-observer-polyfill/src/ResizeObserverController.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 6.8 KB
Line 
1import isBrowser from './utils/isBrowser.js';
2import throttle from './utils/throttle.js';
3
4// Minimum delay before invoking the update of observers.
5const REFRESH_DELAY = 20;
6
7// A list of substrings of CSS properties used to find transition events that
8// might affect dimensions of observed elements.
9const transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];
10
11// Check if MutationObserver is available.
12const mutationObserverSupported = typeof MutationObserver !== 'undefined';
13
14/**
15 * Singleton controller class which handles updates of ResizeObserver instances.
16 */
17export default class ResizeObserverController {
18 /**
19 * Indicates whether DOM listeners have been added.
20 *
21 * @private {boolean}
22 */
23 connected_ = false;
24
25 /**
26 * Tells that controller has subscribed for Mutation Events.
27 *
28 * @private {boolean}
29 */
30 mutationEventsAdded_ = false;
31
32 /**
33 * Keeps reference to the instance of MutationObserver.
34 *
35 * @private {MutationObserver}
36 */
37 mutationsObserver_ = null;
38
39 /**
40 * A list of connected observers.
41 *
42 * @private {Array<ResizeObserverSPI>}
43 */
44 observers_ = [];
45
46 /**
47 * Holds reference to the controller's instance.
48 *
49 * @private {ResizeObserverController}
50 */
51 static instance_ = null;
52
53 /**
54 * Creates a new instance of ResizeObserverController.
55 *
56 * @private
57 */
58 constructor() {
59 this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);
60 this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);
61 }
62
63 /**
64 * Adds observer to observers list.
65 *
66 * @param {ResizeObserverSPI} observer - Observer to be added.
67 * @returns {void}
68 */
69 addObserver(observer) {
70 if (!~this.observers_.indexOf(observer)) {
71 this.observers_.push(observer);
72 }
73
74 // Add listeners if they haven't been added yet.
75 if (!this.connected_) {
76 this.connect_();
77 }
78 }
79
80 /**
81 * Removes observer from observers list.
82 *
83 * @param {ResizeObserverSPI} observer - Observer to be removed.
84 * @returns {void}
85 */
86 removeObserver(observer) {
87 const observers = this.observers_;
88 const index = observers.indexOf(observer);
89
90 // Remove observer if it's present in registry.
91 if (~index) {
92 observers.splice(index, 1);
93 }
94
95 // Remove listeners if controller has no connected observers.
96 if (!observers.length && this.connected_) {
97 this.disconnect_();
98 }
99 }
100
101 /**
102 * Invokes the update of observers. It will continue running updates insofar
103 * it detects changes.
104 *
105 * @returns {void}
106 */
107 refresh() {
108 const changesDetected = this.updateObservers_();
109
110 // Continue running updates if changes have been detected as there might
111 // be future ones caused by CSS transitions.
112 if (changesDetected) {
113 this.refresh();
114 }
115 }
116
117 /**
118 * Updates every observer from observers list and notifies them of queued
119 * entries.
120 *
121 * @private
122 * @returns {boolean} Returns "true" if any observer has detected changes in
123 * dimensions of it's elements.
124 */
125 updateObservers_() {
126 // Collect observers that have active observations.
127 const activeObservers = this.observers_.filter(observer => {
128 return observer.gatherActive(), observer.hasActive();
129 });
130
131 // Deliver notifications in a separate cycle in order to avoid any
132 // collisions between observers, e.g. when multiple instances of
133 // ResizeObserver are tracking the same element and the callback of one
134 // of them changes content dimensions of the observed target. Sometimes
135 // this may result in notifications being blocked for the rest of observers.
136 activeObservers.forEach(observer => observer.broadcastActive());
137
138 return activeObservers.length > 0;
139 }
140
141 /**
142 * Initializes DOM listeners.
143 *
144 * @private
145 * @returns {void}
146 */
147 connect_() {
148 // Do nothing if running in a non-browser environment or if listeners
149 // have been already added.
150 if (!isBrowser || this.connected_) {
151 return;
152 }
153
154 // Subscription to the "Transitionend" event is used as a workaround for
155 // delayed transitions. This way it's possible to capture at least the
156 // final state of an element.
157 document.addEventListener('transitionend', this.onTransitionEnd_);
158
159 window.addEventListener('resize', this.refresh);
160
161 if (mutationObserverSupported) {
162 this.mutationsObserver_ = new MutationObserver(this.refresh);
163
164 this.mutationsObserver_.observe(document, {
165 attributes: true,
166 childList: true,
167 characterData: true,
168 subtree: true
169 });
170 } else {
171 document.addEventListener('DOMSubtreeModified', this.refresh);
172
173 this.mutationEventsAdded_ = true;
174 }
175
176 this.connected_ = true;
177 }
178
179 /**
180 * Removes DOM listeners.
181 *
182 * @private
183 * @returns {void}
184 */
185 disconnect_() {
186 // Do nothing if running in a non-browser environment or if listeners
187 // have been already removed.
188 if (!isBrowser || !this.connected_) {
189 return;
190 }
191
192 document.removeEventListener('transitionend', this.onTransitionEnd_);
193 window.removeEventListener('resize', this.refresh);
194
195 if (this.mutationsObserver_) {
196 this.mutationsObserver_.disconnect();
197 }
198
199 if (this.mutationEventsAdded_) {
200 document.removeEventListener('DOMSubtreeModified', this.refresh);
201 }
202
203 this.mutationsObserver_ = null;
204 this.mutationEventsAdded_ = false;
205 this.connected_ = false;
206 }
207
208 /**
209 * "Transitionend" event handler.
210 *
211 * @private
212 * @param {TransitionEvent} event
213 * @returns {void}
214 */
215 onTransitionEnd_({propertyName = ''}) {
216 // Detect whether transition may affect dimensions of an element.
217 const isReflowProperty = transitionKeys.some(key => {
218 return !!~propertyName.indexOf(key);
219 });
220
221 if (isReflowProperty) {
222 this.refresh();
223 }
224 }
225
226 /**
227 * Returns instance of the ResizeObserverController.
228 *
229 * @returns {ResizeObserverController}
230 */
231 static getInstance() {
232 if (!this.instance_) {
233 this.instance_ = new ResizeObserverController();
234 }
235
236 return this.instance_;
237 }
238}
Note: See TracBrowser for help on using the repository browser.