@ckeditor/ckeditor5-utils 31.0.0 → 31.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-utils",
3
- "version": "31.0.0",
3
+ "version": "31.1.0",
4
4
  "description": "Miscellaneous utilities used by CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -14,12 +14,10 @@
14
14
  "lodash-es": "^4.17.15"
15
15
  },
16
16
  "devDependencies": {
17
- "@ckeditor/ckeditor5-build-classic": "^31.0.0",
18
- "@ckeditor/ckeditor5-editor-classic": "^31.0.0",
19
- "@ckeditor/ckeditor5-core": "^31.0.0",
20
- "@ckeditor/ckeditor5-engine": "^31.0.0",
21
- "assertion-error": "^1.1.0",
22
- "js-beautify": "^1.11.0"
17
+ "@ckeditor/ckeditor5-build-classic": "^31.1.0",
18
+ "@ckeditor/ckeditor5-editor-classic": "^31.1.0",
19
+ "@ckeditor/ckeditor5-core": "^31.1.0",
20
+ "@ckeditor/ckeditor5-engine": "^31.1.0"
23
21
  },
24
22
  "engines": {
25
23
  "node": ">=12.0.0",
@@ -52,19 +52,21 @@ const DomEmitterMixin = extend( {}, EmitterMixin, {
52
52
  * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
53
53
  * and prevents blocking browser's main thread by this event handler.
54
54
  */
55
- listenTo( emitter, ...rest ) {
56
- // Check if emitter is an instance of DOM Node. If so, replace the argument with
57
- // corresponding ProxyEmitter (or create one if not existing).
55
+ listenTo( emitter, event, callback, options = {} ) {
56
+ // Check if emitter is an instance of DOM Node. If so, use corresponding ProxyEmitter (or create one if not existing).
58
57
  if ( isNode( emitter ) || isWindow( emitter ) ) {
59
- const proxy = this._getProxyEmitter( emitter ) || new ProxyEmitter( emitter );
58
+ const proxyOptions = {
59
+ capture: !!options.useCapture,
60
+ passive: !!options.usePassive
61
+ };
60
62
 
61
- proxy.attach( ...rest );
63
+ const proxyEmitter = this._getProxyEmitter( emitter, proxyOptions ) || new ProxyEmitter( emitter, proxyOptions );
62
64
 
63
- emitter = proxy;
65
+ this.listenTo( proxyEmitter, event, callback, options );
66
+ } else {
67
+ // Execute parent class method with Emitter (or ProxyEmitter) instance.
68
+ EmitterMixin.listenTo.call( this, emitter, event, callback, options );
64
69
  }
65
-
66
- // Execute parent class method with Emitter (or ProxyEmitter) instance.
67
- EmitterMixin.listenTo.call( this, emitter, ...rest );
68
70
  },
69
71
 
70
72
  /**
@@ -83,35 +85,49 @@ const DomEmitterMixin = extend( {}, EmitterMixin, {
83
85
  * `event`.
84
86
  */
85
87
  stopListening( emitter, event, callback ) {
86
- // Check if emitter is an instance of DOM Node. If so, replace the argument with corresponding ProxyEmitter.
88
+ // Check if the emitter is an instance of DOM Node. If so, forward the call to the corresponding ProxyEmitters.
87
89
  if ( isNode( emitter ) || isWindow( emitter ) ) {
88
- const proxy = this._getProxyEmitter( emitter );
90
+ const proxyEmitters = this._getAllProxyEmitters( emitter );
89
91
 
90
- // Element has no listeners.
91
- if ( !proxy ) {
92
- return;
92
+ for ( const proxy of proxyEmitters ) {
93
+ this.stopListening( proxy, event, callback );
93
94
  }
94
-
95
- emitter = proxy;
95
+ } else {
96
+ // Execute parent class method with Emitter (or ProxyEmitter) instance.
97
+ EmitterMixin.stopListening.call( this, emitter, event, callback );
96
98
  }
99
+ },
97
100
 
98
- // Execute parent class method with Emitter (or ProxyEmitter) instance.
99
- EmitterMixin.stopListening.call( this, emitter, event, callback );
100
-
101
- if ( emitter instanceof ProxyEmitter ) {
102
- emitter.detach( event );
103
- }
101
+ /**
102
+ * Retrieves ProxyEmitter instance for given DOM Node residing in this Host and given options.
103
+ *
104
+ * @private
105
+ * @param {Node} node DOM Node of the ProxyEmitter.
106
+ * @param {Object} [options] Additional options.
107
+ * @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
108
+ * listener before being dispatched to any EventTarget beneath it in the DOM tree.
109
+ * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
110
+ * and prevents blocking browser's main thread by this event handler.
111
+ * @returns {module:utils/dom/emittermixin~ProxyEmitter|null} ProxyEmitter instance bound to the DOM Node.
112
+ */
113
+ _getProxyEmitter( node, options ) {
114
+ return _getEmitterListenedTo( this, getProxyEmitterId( node, options ) );
104
115
  },
105
116
 
106
117
  /**
107
- * Retrieves ProxyEmitter instance for given DOM Node residing in this Host.
118
+ * Retrieves all the ProxyEmitter instances for given DOM Node residing in this Host.
108
119
  *
109
120
  * @private
110
121
  * @param {Node} node DOM Node of the ProxyEmitter.
111
- * @returns {module:utils/dom/emittermixin~ProxyEmitter} ProxyEmitter instance or null.
122
+ * @returns {Array.<module:utils/dom/emittermixin~ProxyEmitter>}
112
123
  */
113
- _getProxyEmitter( node ) {
114
- return _getEmitterListenedTo( this, getNodeUID( node ) );
124
+ _getAllProxyEmitters( node ) {
125
+ return [
126
+ { capture: false, passive: false },
127
+ { capture: false, passive: true },
128
+ { capture: true, passive: false },
129
+ { capture: true, passive: true }
130
+ ].map( options => this._getProxyEmitter( node, options ) ).filter( proxy => !!proxy );
115
131
  }
116
132
  } );
117
133
 
@@ -120,6 +136,8 @@ export default DomEmitterMixin;
120
136
  /**
121
137
  * Creates a ProxyEmitter instance. Such an instance is a bridge between a DOM Node firing events
122
138
  * and any Host listening to them. It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#on}.
139
+ * There is a separate instance for each combination of modes (useCapture & usePassive). The mode is concatenated with
140
+ * UID stored in HTMLElement to give each instance unique identifier.
123
141
  *
124
142
  * listenTo( click, ... )
125
143
  * +-----------------------------------------+
@@ -128,14 +146,14 @@ export default DomEmitterMixin;
128
146
  * | Host | | +---------------------------------------------+
129
147
  * +----------------------------+ | | removeEventListener( click, ... ) |
130
148
  * | _listeningTo: { | +----------v-------------+ |
131
- * | UID: { | | ProxyEmitter | |
149
+ * | UID+mode: { | | ProxyEmitter | |
132
150
  * | emitter: ProxyEmitter, | +------------------------+ +------------v----------+
133
151
  * | callbacks: { | | events: { | | Node (HTMLElement) |
134
152
  * | click: [ callbacks ] | | click: [ callbacks ] | +-----------------------+
135
153
  * | } | | }, | | data-ck-expando: UID |
136
154
  * | } | | _domNode: Node, | +-----------------------+
137
155
  * | } | | _domListeners: {}, | |
138
- * | +------------------------+ | | _emitterId: UID | |
156
+ * | +------------------------+ | | _emitterId: UID+mode | |
139
157
  * | | DomEmitterMixin | | +--------------^---------+ |
140
158
  * | +------------------------+ | | | |
141
159
  * +--------------^-------------+ | +---------------------------------------------+
@@ -150,14 +168,21 @@ export default DomEmitterMixin;
150
168
  class ProxyEmitter {
151
169
  /**
152
170
  * @param {Node} node DOM Node that fires events.
153
- * @returns {Object} ProxyEmitter instance bound to the DOM Node.
171
+ * @param {Object} [options] Additional options.
172
+ * @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
173
+ * listener before being dispatched to any EventTarget beneath it in the DOM tree.
174
+ * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
175
+ * and prevents blocking browser's main thread by this event handler.
154
176
  */
155
- constructor( node ) {
177
+ constructor( node, options ) {
156
178
  // Set emitter ID to match DOM Node "expando" property.
157
- _setEmitterId( this, getNodeUID( node ) );
179
+ _setEmitterId( this, getProxyEmitterId( node, options ) );
158
180
 
159
181
  // Remember the DOM Node this ProxyEmitter is bound to.
160
182
  this._domNode = node;
183
+
184
+ // And given options.
185
+ this._options = options;
161
186
  }
162
187
  }
163
188
 
@@ -175,31 +200,23 @@ extend( ProxyEmitter.prototype, EmitterMixin, {
175
200
  * It attaches a native DOM listener to the DOM Node. When fired,
176
201
  * a corresponding Emitter event will also fire with DOM Event object as an argument.
177
202
  *
203
+ * **Note**: This is automatically called by the
204
+ * {@link module:utils/emittermixin~EmitterMixin#listenTo `EmitterMixin#listenTo()`}.
205
+ *
178
206
  * @method module:utils/dom/emittermixin~ProxyEmitter#attach
179
207
  * @param {String} event The name of the event.
180
- * @param {Function} callback The function to be called on event.
181
- * @param {Object} [options={}] Additional options.
182
- * @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
183
- * listener before being dispatched to any EventTarget beneath it in the DOM tree.
184
- * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
185
- * and prevents blocking browser's main thread by this event handler.
186
208
  */
187
- attach( event, callback, options = {} ) {
209
+ attach( event ) {
188
210
  // If the DOM Listener for given event already exist it is pointless
189
211
  // to attach another one.
190
212
  if ( this._domListeners && this._domListeners[ event ] ) {
191
213
  return;
192
214
  }
193
215
 
194
- const listenerOptions = {
195
- capture: !!options.useCapture,
196
- passive: !!options.usePassive
197
- };
198
-
199
- const domListener = this._createDomListener( event, listenerOptions );
216
+ const domListener = this._createDomListener( event );
200
217
 
201
218
  // Attach the native DOM listener to DOM Node.
202
- this._domNode.addEventListener( event, domListener, listenerOptions );
219
+ this._domNode.addEventListener( event, domListener, this._options );
203
220
 
204
221
  if ( !this._domListeners ) {
205
222
  this._domListeners = {};
@@ -213,6 +230,9 @@ extend( ProxyEmitter.prototype, EmitterMixin, {
213
230
  /**
214
231
  * Stops executing the callback on the given event.
215
232
  *
233
+ * **Note**: This is automatically called by the
234
+ * {@link module:utils/emittermixin~EmitterMixin#stopListening `EmitterMixin#stopListening()`}.
235
+ *
216
236
  * @method module:utils/dom/emittermixin~ProxyEmitter#detach
217
237
  * @param {String} event The name of the event.
218
238
  */
@@ -228,6 +248,36 @@ extend( ProxyEmitter.prototype, EmitterMixin, {
228
248
  }
229
249
  },
230
250
 
251
+ /**
252
+ * Adds callback to emitter for given event.
253
+ *
254
+ * @protected
255
+ * @method module:utils/dom/emittermixin~ProxyEmitter#_addEventListener
256
+ * @param {String} event The name of the event.
257
+ * @param {Function} callback The function to be called on event.
258
+ * @param {Object} [options={}] Additional options.
259
+ * @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
260
+ * the priority value the sooner the callback will be fired. Events having the same priority are called in the
261
+ * order they were added.
262
+ */
263
+ _addEventListener( event, callback, options ) {
264
+ this.attach( event );
265
+ EmitterMixin._addEventListener.call( this, event, callback, options );
266
+ },
267
+
268
+ /**
269
+ * Removes callback from emitter for given event.
270
+ *
271
+ * @protected
272
+ * @method module:utils/dom/emittermixin~ProxyEmitter#_removeEventListener
273
+ * @param {String} event The name of the event.
274
+ * @param {Function} callback The function to stop being called.
275
+ */
276
+ _removeEventListener( event, callback ) {
277
+ EmitterMixin._removeEventListener.call( this, event, callback );
278
+ this.detach( event );
279
+ },
280
+
231
281
  /**
232
282
  * Creates a native DOM listener callback. When the native DOM event
233
283
  * is fired it will fire corresponding event on this ProxyEmitter.
@@ -236,13 +286,9 @@ extend( ProxyEmitter.prototype, EmitterMixin, {
236
286
  * @private
237
287
  * @method module:utils/dom/emittermixin~ProxyEmitter#_createDomListener
238
288
  * @param {String} event The name of the event.
239
- * @param {Object} [options] Additional options.
240
- * @param {Boolean} [options.capture=false] Indicates whether the listener was created for capturing event.
241
- * @param {Boolean} [options.passive=false] Indicates that the function specified by listener will never call preventDefault()
242
- * and prevents blocking browser's main thread by this event handler.
243
289
  * @returns {Function} The DOM listener callback.
244
290
  */
245
- _createDomListener( event, options ) {
291
+ _createDomListener( event ) {
246
292
  const domListener = domEvt => {
247
293
  this.fire( event, domEvt );
248
294
  };
@@ -251,7 +297,7 @@ extend( ProxyEmitter.prototype, EmitterMixin, {
251
297
  // detach it from the DOM Node, when it is no longer necessary.
252
298
  // See: {@link detach}.
253
299
  domListener.removeListener = () => {
254
- this._domNode.removeEventListener( event, domListener, options );
300
+ this._domNode.removeEventListener( event, domListener, this._options );
255
301
  delete this._domListeners[ event ];
256
302
  };
257
303
 
@@ -268,6 +314,26 @@ function getNodeUID( node ) {
268
314
  return node[ 'data-ck-expando' ] || ( node[ 'data-ck-expando' ] = uid() );
269
315
  }
270
316
 
317
+ // Gets id of the ProxyEmitter for the given node.
318
+ //
319
+ // Combines DOM Node identifier and additional options.
320
+ //
321
+ // @private
322
+ // @param {Node} node
323
+ // @param {Object} options Additional options.
324
+ // @returns {String} ProxyEmitter id.
325
+ function getProxyEmitterId( node, options ) {
326
+ let id = getNodeUID( node );
327
+
328
+ for ( const option of Object.keys( options ).sort() ) {
329
+ if ( options[ option ] ) {
330
+ id += '-' + option;
331
+ }
332
+ }
333
+
334
+ return id;
335
+ }
336
+
271
337
  /**
272
338
  * Interface representing classes which mix in {@link module:utils/dom/emittermixin~EmitterMixin}.
273
339
  *
package/src/env.js CHANGED
@@ -25,6 +25,14 @@ const env = {
25
25
  */
26
26
  isMac: isMac( userAgent ),
27
27
 
28
+ /**
29
+ * Indicates that the application is running on Windows.
30
+ *
31
+ * @static
32
+ * @type {Boolean}
33
+ */
34
+ isWindows: isWindows( userAgent ),
35
+
28
36
  /**
29
37
  * Indicates that the application is running in Firefox (Gecko).
30
38
  *
@@ -41,6 +49,14 @@ const env = {
41
49
  */
42
50
  isSafari: isSafari( userAgent ),
43
51
 
52
+ /**
53
+ * Indicates the the application is running in iOS.
54
+ *
55
+ * @static
56
+ * @type {Boolean}
57
+ */
58
+ isiOS: isiOS( userAgent ),
59
+
44
60
  /**
45
61
  * Indicates that the application is running on Android mobile device.
46
62
  *
@@ -87,6 +103,16 @@ export function isMac( userAgent ) {
87
103
  return userAgent.indexOf( 'macintosh' ) > -1;
88
104
  }
89
105
 
106
+ /**
107
+ * Checks if User Agent represented by the string is running on Windows.
108
+ *
109
+ * @param {String} userAgent **Lowercase** `navigator.userAgent` string.
110
+ * @returns {Boolean} Whether User Agent is running on Windows or not.
111
+ */
112
+ export function isWindows( userAgent ) {
113
+ return userAgent.indexOf( 'windows' ) > -1;
114
+ }
115
+
90
116
  /**
91
117
  * Checks if User Agent represented by the string is Firefox (Gecko).
92
118
  *
@@ -107,6 +133,17 @@ export function isSafari( userAgent ) {
107
133
  return userAgent.indexOf( ' applewebkit/' ) > -1 && userAgent.indexOf( 'chrome' ) === -1;
108
134
  }
109
135
 
136
+ /**
137
+ * Checks if User Agent represented by the string is running in iOS.
138
+ *
139
+ * @param {String} userAgent **Lowercase** `navigator.userAgent` string.
140
+ * @returns {Boolean} Whether User Agent is running in iOS or not.
141
+ */
142
+ export function isiOS( userAgent ) {
143
+ // "Request mobile site" || "Request desktop site".
144
+ return !!userAgent.match( /iphone|ipad/i ) || ( isMac( userAgent ) && navigator.maxTouchPoints > 0 );
145
+ }
146
+
110
147
  /**
111
148
  * Checks if User Agent represented by the string is Android mobile device.
112
149
  *
package/src/version.js CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import CKEditorError from './ckeditorerror';
13
13
 
14
- const version = '31.0.0';
14
+ const version = '31.1.0';
15
15
 
16
16
  export default version;
17
17