@ckeditor/ckeditor5-utils 29.1.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": "29.1.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": "^29.1.0",
18
- "@ckeditor/ckeditor5-editor-classic": "^29.1.0",
19
- "@ckeditor/ckeditor5-core": "^29.1.0",
20
- "@ckeditor/ckeditor5-engine": "^29.1.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",
@@ -59,9 +59,7 @@ export default class CKEditorError extends Error {
59
59
  * data object will also be later available under the {@link #data} property.
60
60
  */
61
61
  constructor( errorName, context, data ) {
62
- const message = `${ errorName }${ ( data ? ` ${ JSON.stringify( data ) }` : '' ) }${ getLinkToDocumentationMessage( errorName ) }`;
63
-
64
- super( message );
62
+ super( getErrorMessage( errorName, data ) );
65
63
 
66
64
  /**
67
65
  * @type {String}
@@ -85,6 +83,7 @@ export default class CKEditorError extends Error {
85
83
 
86
84
  /**
87
85
  * Checks if the error is of the `CKEditorError` type.
86
+ * @returns {Boolean}
88
87
  */
89
88
  is( type ) {
90
89
  return type === 'CKEditorError';
@@ -142,7 +141,6 @@ export default class CKEditorError extends Error {
142
141
  *
143
142
  * @param {String} errorName The error name to be logged.
144
143
  * @param {Object} [data] Additional data to be logged.
145
- * @returns {String}
146
144
  */
147
145
  export function logWarning( errorName, data ) {
148
146
  console.warn( ...formatConsoleArguments( errorName, data ) );
@@ -167,16 +165,52 @@ export function logWarning( errorName, data ) {
167
165
  *
168
166
  * @param {String} errorName The error name to be logged.
169
167
  * @param {Object} [data] Additional data to be logged.
170
- * @returns {String}
171
168
  */
172
169
  export function logError( errorName, data ) {
173
170
  console.error( ...formatConsoleArguments( errorName, data ) );
174
171
  }
175
172
 
173
+ // Returns formatted link to documentation message.
174
+ //
175
+ // @private
176
+ // @param {String} errorName
177
+ // @returns {string}
176
178
  function getLinkToDocumentationMessage( errorName ) {
177
179
  return `\nRead more: ${ DOCUMENTATION_URL }#error-${ errorName }`;
178
180
  }
179
181
 
182
+ // Returns formatted error message.
183
+ //
184
+ // @private
185
+ // @param {String} errorName
186
+ // @param {Object} [data]
187
+ // @returns {string}
188
+ function getErrorMessage( errorName, data ) {
189
+ const processedObjects = new WeakSet();
190
+ const circularReferencesReplacer = ( key, value ) => {
191
+ if ( typeof value === 'object' && value !== null ) {
192
+ if ( processedObjects.has( value ) ) {
193
+ return `[object ${ value.constructor.name }]`;
194
+ }
195
+
196
+ processedObjects.add( value );
197
+ }
198
+
199
+ return value;
200
+ };
201
+
202
+ const stringifiedData = data ? ` ${ JSON.stringify( data, circularReferencesReplacer ) }` : '';
203
+ const documentationLink = getLinkToDocumentationMessage( errorName );
204
+
205
+ return errorName + stringifiedData + documentationLink;
206
+ }
207
+
208
+ // Returns formatted console error arguments.
209
+ //
210
+ // @private
211
+ // @param {String} errorName
212
+ // @param {Object} [data]
213
+ // @returns {Array}
180
214
  function formatConsoleArguments( errorName, data ) {
181
215
  const documentationMessage = getLinkToDocumentationMessage( errorName );
182
216
 
package/src/collection.js CHANGED
@@ -56,7 +56,7 @@ export default class Collection {
56
56
  * console.log( collection.get( 'George' ) ); // -> { name: 'George' }
57
57
  * console.log( collection.get( 'John' ) ); // -> { name: 'John' }
58
58
  *
59
- * @param {Iterable.<Object>|Object} initialItemsOrOptions The initial items of the collection or
59
+ * @param {Iterable.<Object>|Object} [initialItemsOrOptions] The initial items of the collection or
60
60
  * the options object.
61
61
  * @param {Object} [options={}] The options object, when the first argument is an array of initial items.
62
62
  * @param {String} [options.idProperty='id'] The name of the property which is used to identify an item.
@@ -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
  *