UNPKG

10.4 kBPlain TextView Raw
1let requestAnimationFrame$1 = (function() {
2 if (typeof requestAnimationFrame === "function") {
3 return requestAnimationFrame.bind(window);
4 }
5
6 return function(callback: (now: number)=>void ) {
7 return setTimeout(function() {
8 return callback(Date.now());
9 }, 1000 / 60);
10 };
11})();
12
13let trailingTimeout = 2;
14
15let throttle = function(callback: ()=>void, delay: number) {
16 let leadingCall = false;
17 let trailingCall = false;
18 let lastCallTime = 0;
19
20 function resolvePending() {
21 if (leadingCall) {
22 leadingCall = false;
23
24 callback();
25 }
26
27 if (trailingCall) {
28 proxy();
29 }
30 }
31
32 function timeoutCallback() {
33 requestAnimationFrame$1(resolvePending);
34 }
35
36 function proxy() {
37 let timeStamp = Date.now();
38
39 if (leadingCall) {
40 if (timeStamp - lastCallTime < trailingTimeout) {
41 return;
42 }
43 trailingCall = true;
44 } else {
45 leadingCall = true;
46 trailingCall = false;
47
48 setTimeout(timeoutCallback, delay);
49 }
50
51 lastCallTime = timeStamp;
52 }
53
54 return proxy;
55};
56
57let REFRESH_DELAY = 20;
58
59let getWindowOf = function(target: HTMLElement) {
60 let ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;
61
62 return ownerGlobal || window;
63};
64
65let emptyRect = createRectInit(0, 0, 0, 0);
66
67function toFloat(value: string) {
68 return parseFloat(value) || 0;
69}
70
71function getBordersSize(styles: CSSStyleDeclaration, start: string, end: string) {
72 let positions = [start, end];
73
74 return positions.reduce(function(size, position) {
75 let value: string = styles.getPropertyValue("border-" + position + "-width");
76 return size + toFloat(value);
77 }, 0);
78}
79
80interface Paddings {
81 [property: string]: number;
82 top: number;
83 left: number;
84 right: number;
85 bottom: number;
86}
87
88function getPaddings(styles: CSSStyleDeclaration) {
89 let positions = ["top", "right", "bottom", "left"];
90 let paddings: Paddings = {
91 top: null,
92 left: null,
93 right: null,
94 bottom: null
95 };
96
97 for (let i = 0, list = positions; i < list.length; i += 1) {
98 let position = list[i];
99
100 let value = styles.getPropertyValue("padding-" + position);
101
102 paddings[position] = toFloat(value);
103 }
104
105 return paddings;
106}
107
108function getHTMLElementContentRect(target: HTMLElement) {
109 let clientWidth = target.clientWidth;
110 let clientHeight = target.clientHeight;
111
112 if (!clientWidth && !clientHeight) {
113 return emptyRect;
114 }
115
116 let styles = getWindowOf(target).getComputedStyle(target);
117 let paddings = getPaddings(styles);
118 let horizPad = paddings.left + paddings.right;
119 let vertPad = paddings.top + paddings.bottom;
120
121 let width = toFloat(styles.width);
122 let height = toFloat(styles.height);
123
124 if (styles.boxSizing === "border-box") {
125 if (Math.round(width + horizPad) !== clientWidth) {
126 width -= getBordersSize(styles, "left", "right") + horizPad;
127 }
128
129 if (Math.round(height + vertPad) !== clientHeight) {
130 height -= getBordersSize(styles, "top", "bottom") + vertPad;
131 }
132 }
133
134 let vertScrollbar = Math.round(width + horizPad) - clientWidth;
135 let horizScrollbar = Math.round(height + vertPad) - clientHeight;
136 if (Math.abs(vertScrollbar) !== 1) {
137 width -= vertScrollbar;
138 }
139
140 if (Math.abs(horizScrollbar) !== 1) {
141 height -= horizScrollbar;
142 }
143
144 return createRectInit(paddings.left, paddings.top, width, height);
145}
146
147function getContentRect(target: HTMLElement) {
148 return getHTMLElementContentRect(target);
149}
150
151function createReadOnlyRect(ref: any) {
152 let x = ref.x;
153 let y = ref.y;
154 let width = ref.width;
155 let height = ref.height;
156
157 let Constr = (<any>window).DOMRectReadOnly ? (<any>window).DOMRectReadOnly : Object;
158 let rect = Object.create(Constr.prototype);
159
160 defineConfigurable(rect, {
161 x: x,
162 y: y,
163 width: width,
164 height: height,
165 top: y,
166 right: x + width,
167 bottom: height + y,
168 left: x
169 });
170
171 return rect;
172}
173
174function createRectInit(x: number, y: number, width: number, height: number): any {
175 return { x: x, y: y, width: width, height: height };
176}
177
178class ResizeObserverController {
179
180 connected_ = false;
181 mutationEventsAdded_ = false;
182 mutationsObserver_: MutationObserver = null;
183 observers_: ResizeObserverSPI[] = [];
184 callback_: ()=>void = null;
185
186 constructor() {
187 this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);
188 }
189
190 addObserver(observer: ResizeObserverSPI) {
191 if (this.observers_.indexOf(observer) == -1) {
192 this.observers_.push(observer);
193 }
194
195 // Add listeners if they haven't been added yet.
196 if (!this.connected_) {
197 this.connect_();
198 }
199 }
200
201 removeObserver(observer: ResizeObserverSPI) {
202 let observers = this.observers_;
203 let index = observers.indexOf(observer);
204
205 if (index > -1) {
206 observers.splice(index, 1);
207 }
208
209 if (!observers.length && this.connected_) {
210 this.disconnect_();
211 }
212 }
213
214 refresh() {
215 let changesDetected = this.updateObservers_();
216
217 if (changesDetected) {
218 this.refresh();
219 }
220 }
221 updateObservers_() {
222 let activeObservers = this.observers_.filter(function(observer) {
223 return observer.hasActive();
224 });
225 activeObservers.forEach(function(observer) {
226 return observer.broadcastActive();
227 });
228
229 return activeObservers.length > 0;
230 }
231
232 connect_() {
233 if (this.connected_) {
234 return;
235 }
236
237 window.addEventListener("resize", this.refresh);
238
239 document.addEventListener("DOMSubtreeModified", this.refresh);
240 this.mutationEventsAdded_ = true;
241
242 this.connected_ = true;
243 }
244
245 disconnect_() {
246 if (!this.connected_) {
247 return;
248 }
249
250 window.removeEventListener("resize", this.refresh);
251
252 if (this.mutationsObserver_) {
253 this.mutationsObserver_.disconnect();
254 }
255
256 if (this.mutationEventsAdded_) {
257 document.removeEventListener("DOMSubtreeModified", this.refresh);
258 }
259
260 this.mutationsObserver_ = null;
261 this.mutationEventsAdded_ = false;
262 this.connected_ = false;
263 }
264
265 static instance_: ResizeObserverController = null;
266
267 static getInstance() {
268 if (!this.instance_) {
269 this.instance_ = new ResizeObserverController();
270 }
271
272 return this.instance_;
273 }
274}
275
276let defineConfigurable = function(target: HTMLElement, props: any) {
277 for (let i = 0, list = Object.keys(props); i < list.length; i += 1) {
278 let key = list[i];
279
280 Object.defineProperty(target, key, {
281 value: props[key],
282 enumerable: false,
283 writable: false,
284 configurable: true
285 });
286 }
287
288 return target;
289};
290
291class ResizeObservation {
292 broadcastWidth = 0;
293 broadcastHeight = 0;
294 contentRect_: any = null;
295
296 constructor(public target: HTMLElement) {
297 this.contentRect_ = createRectInit(0, 0, 0, 0);
298 }
299
300 isActive() {
301 let rect = getContentRect(this.target);
302
303 this.contentRect_ = rect;
304
305 return rect.width !== this.broadcastWidth || rect.height !== this.broadcastHeight;
306 }
307
308 broadcastRect() {
309 let rect = this.contentRect_;
310
311 this.broadcastWidth = rect.width;
312 this.broadcastHeight = rect.height;
313
314 return rect;
315 }
316}
317
318class ResizeObserverSPI {
319
320 observation: ResizeObservation = null;
321 callback_: (ctx1: ResizeObserverFallback, p: any, ctx2: ResizeObserverFallback)=>void = null;
322 controller_: ResizeObserverController = null;
323 callbackCtx_: ResizeObserverFallback = null;
324
325 constructor(callback: (ctx1: ResizeObserverFallback, p: any, ctx2: ResizeObserverFallback)=>void, controller: ResizeObserverController, callbackCtx: ResizeObserverFallback) {
326 this.observation = null;
327
328 if (typeof callback !== "function") {
329 throw new TypeError("The callback provided as parameter 1 is not a function.");
330 }
331
332 this.callback_ = callback;
333 this.controller_ = controller;
334 this.callbackCtx_ = callbackCtx;
335 }
336
337 observe(target: HTMLElement) {
338 this.observation = new ResizeObservation(target);
339
340 this.controller_.addObserver(this);
341
342 // Force the update of observations.
343 this.controller_.refresh();
344 }
345
346 disconnect() {
347 this.observation = null;
348 this.controller_.removeObserver(this);
349 }
350
351 broadcastActive() {
352 if (!this.hasActive()) {
353 return;
354 }
355
356 let ctx = this.callbackCtx_;
357
358 this.callback_.call(
359 ctx,
360 {
361 target: this.observation.target,
362 contentRect: createReadOnlyRect(this.observation.broadcastRect())
363 },
364 ctx
365 );
366 }
367
368 hasActive() {
369 return this.observation.isActive();
370 }
371}
372
373class ResizeObserverFallback {
374 observer_: ResizeObserverSPI;
375
376 constructor(callback: (ctx1: ResizeObserverFallback, p: any, ctx2: ResizeObserverFallback)=>void) {
377 let controller = ResizeObserverController.getInstance();
378 let observer = new ResizeObserverSPI(callback, controller, this);
379
380 this.observer_ = observer;
381 }
382
383 observe(element: HTMLElement) {
384 this.observer_.observe(element);
385 }
386
387 disconnect() {
388 this.observer_.disconnect();
389 }
390}
391
392export function observeResize(element: HTMLElement, callback: (entry: any) => void) {
393 if ((<any>window).ResizeObserver) {
394 const ro = new (<any>window).ResizeObserver((entries: any, observer: any) => {
395 for (const entry of entries) {
396 callback(entry);
397 }
398 });
399
400 ro.observe(element);
401 return function unobserve() {
402 ro.disconnect();
403 };
404 } else {
405 const ro = new ResizeObserverFallback((entry: any) => {
406 callback(entry);
407 });
408 ro.observe(element);
409
410 return function unobserve() {
411 ro.disconnect();
412 };
413 }
414}