-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
resizeobserver.ts
157 lines (134 loc) · 5.01 KB
/
resizeobserver.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/resizeobserver
*/
import global from './global.js';
/**
* A helper class which instances allow performing custom actions when native DOM elements are resized.
*
* ```ts
* const editableElement = editor.editing.view.getDomRoot();
*
* const observer = new ResizeObserver( editableElement, entry => {
* console.log( 'The editable element has been resized in DOM.' );
* console.log( entry.target ); // -> editableElement
* console.log( entry.contentRect.width ); // -> e.g. '423px'
* } );
* ```
*
* It uses the [native DOM resize observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* under the hood.
*/
export default class ResizeObserver {
/**
* The element observed by this observer.
*/
private readonly _element: Element;
/**
* The callback executed each time {@link #_element} is resized.
*/
private readonly _callback: ( entry: ResizeObserverEntry ) => void;
/**
* The single native observer instance shared across all {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
*/
private static _observerInstance: InstanceType<typeof global.window.ResizeObserver> | null = null;
/**
* A mapping of native DOM elements and their callbacks shared across all
* {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
*/
private static _elementCallbacks: Map<Element, Set<( entry: ResizeObserverEntry ) => void>> | null = null;
/**
* Creates an instance of the `ResizeObserver` class.
*
* @param element A DOM element that is to be observed for resizing. Note that
* the element must be visible (i.e. not detached from DOM) for the observer to work.
* @param callback A function called when the observed element was resized. It passes
* the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* object with information about the resize event.
*/
constructor( element: Element, callback: ( entry: ResizeObserverEntry ) => void ) {
// **Note**: For the maximum performance, this class ensures only a single instance of the native
// observer is used no matter how many instances of this class were created.
if ( !ResizeObserver._observerInstance ) {
ResizeObserver._createObserver();
}
this._element = element;
this._callback = callback;
ResizeObserver._addElementCallback( element, callback );
ResizeObserver._observerInstance!.observe( element );
}
/**
* The element observed by this observer.
*/
public get element(): Element {
return this._element;
}
/**
* Destroys the observer which disables the `callback` passed to the {@link #constructor}.
*/
public destroy(): void {
ResizeObserver._deleteElementCallback( this._element, this._callback );
}
/**
* Registers a new resize callback for the DOM element.
*/
private static _addElementCallback( element: Element, callback: ( entry: ResizeObserverEntry ) => void ): void {
if ( !ResizeObserver._elementCallbacks ) {
ResizeObserver._elementCallbacks = new Map();
}
let callbacks = ResizeObserver._elementCallbacks.get( element );
if ( !callbacks ) {
callbacks = new Set();
ResizeObserver._elementCallbacks.set( element, callbacks );
}
callbacks.add( callback );
}
/**
* Removes a resize callback from the DOM element. If no callbacks are left
* for the element, it removes the element from the native observer.
*/
private static _deleteElementCallback( element: Element, callback: ( entry: ResizeObserverEntry ) => void ): void {
const callbacks = ResizeObserver._getElementCallbacks( element );
// Remove the element callback. Check if exist first in case someone
// called destroy() twice.
if ( callbacks ) {
callbacks.delete( callback );
// If no callbacks left for the element, also remove the element.
if ( !callbacks.size ) {
ResizeObserver._elementCallbacks!.delete( element );
ResizeObserver._observerInstance!.unobserve( element );
}
}
if ( ResizeObserver._elementCallbacks && !ResizeObserver._elementCallbacks.size ) {
ResizeObserver._observerInstance = null;
ResizeObserver._elementCallbacks = null;
}
}
/**
* Returns are registered resize callbacks for the DOM element.
*/
private static _getElementCallbacks( element: Element ): Set<( entry: ResizeObserverEntry ) => void> | null | undefined {
if ( !ResizeObserver._elementCallbacks ) {
return null;
}
return ResizeObserver._elementCallbacks.get( element );
}
/**
* Creates the single native observer shared across all `ResizeObserver` instances.
*/
private static _createObserver(): void {
ResizeObserver._observerInstance = new global.window.ResizeObserver( entries => {
for ( const entry of entries ) {
const callbacks = ResizeObserver._getElementCallbacks( entry.target );
if ( callbacks ) {
for ( const callback of callbacks ) {
callback( entry );
}
}
}
} );
}
}