@ckeditor/ckeditor5-utils 35.0.1 → 35.2.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.
@@ -5,6 +5,7 @@
5
5
  /**
6
6
  * @module utils/emittermixin
7
7
  */
8
+ /* eslint-disable new-cap */
8
9
  import EventInfo from './eventinfo';
9
10
  import uid from './uid';
10
11
  import priorities from './priorities';
@@ -26,257 +27,238 @@ const _delegations = Symbol('delegations');
26
27
  * @mixin EmitterMixin
27
28
  * @implements module:utils/emittermixin~Emitter
28
29
  */
29
- const EmitterMixin = {
30
- /**
31
- * @inheritDoc
32
- */
33
- on(event, callback, options = {}) {
34
- this.listenTo(this, event, callback, options);
35
- },
36
- /**
37
- * @inheritDoc
38
- */
39
- once(event, callback, options) {
40
- let wasFired = false;
41
- const onceCallback = (event, ...args) => {
42
- // Ensure the callback is called only once even if the callback itself leads to re-firing the event
43
- // (which would call the callback again).
44
- if (!wasFired) {
45
- wasFired = true;
46
- // Go off() at the first call.
47
- event.off();
48
- // Go with the original callback.
49
- callback.call(this, event, ...args);
50
- }
51
- };
52
- // Make a similar on() call, simply replacing the callback.
53
- this.listenTo(this, event, onceCallback, options);
54
- },
55
- /**
56
- * @inheritDoc
57
- */
58
- off(event, callback) {
59
- this.stopListening(this, event, callback);
60
- },
61
- /**
62
- * @inheritDoc
63
- */
64
- listenTo(emitter, event, callback, options = {}) {
65
- let emitterInfo, eventCallbacks;
66
- // _listeningTo contains a list of emitters that this object is listening to.
67
- // This list has the following format:
68
- //
69
- // _listeningTo: {
70
- // emitterId: {
71
- // emitter: emitter,
72
- // callbacks: {
73
- // event1: [ callback1, callback2, ... ]
74
- // ....
75
- // }
76
- // },
77
- // ...
78
- // }
79
- if (!this[_listeningTo]) {
80
- this[_listeningTo] = {};
81
- }
82
- const emitters = this[_listeningTo];
83
- if (!_getEmitterId(emitter)) {
84
- _setEmitterId(emitter);
30
+ export default function EmitterMixin(base) {
31
+ class Mixin extends base {
32
+ on(event, callback, options) {
33
+ this.listenTo(this, event, callback, options);
85
34
  }
86
- const emitterId = _getEmitterId(emitter);
87
- if (!(emitterInfo = emitters[emitterId])) {
88
- emitterInfo = emitters[emitterId] = {
89
- emitter,
90
- callbacks: {}
35
+ once(event, callback, options) {
36
+ let wasFired = false;
37
+ const onceCallback = (event, ...args) => {
38
+ // Ensure the callback is called only once even if the callback itself leads to re-firing the event
39
+ // (which would call the callback again).
40
+ if (!wasFired) {
41
+ wasFired = true;
42
+ // Go off() at the first call.
43
+ event.off();
44
+ // Go with the original callback.
45
+ callback.call(this, event, ...args);
46
+ }
91
47
  };
48
+ // Make a similar on() call, simply replacing the callback.
49
+ this.listenTo(this, event, onceCallback, options);
92
50
  }
93
- if (!(eventCallbacks = emitterInfo.callbacks[event])) {
94
- eventCallbacks = emitterInfo.callbacks[event] = [];
51
+ off(event, callback) {
52
+ this.stopListening(this, event, callback);
95
53
  }
96
- eventCallbacks.push(callback);
97
- // Finally register the callback to the event.
98
- addEventListener(this, emitter, event, callback, options);
99
- },
100
- /**
101
- * @inheritDoc
102
- */
103
- stopListening(emitter, event, callback) {
104
- const emitters = this[_listeningTo];
105
- let emitterId = emitter && _getEmitterId(emitter);
106
- const emitterInfo = (emitters && emitterId) ? emitters[emitterId] : undefined;
107
- const eventCallbacks = (emitterInfo && event) ? emitterInfo.callbacks[event] : undefined;
108
- // Stop if nothing has been listened.
109
- if (!emitters || (emitter && !emitterInfo) || (event && !eventCallbacks)) {
110
- return;
111
- }
112
- // All params provided. off() that single callback.
113
- if (callback) {
114
- removeEventListener(this, emitter, event, callback);
115
- // We must remove callbacks as well in order to prevent memory leaks.
116
- // See https://github.com/ckeditor/ckeditor5/pull/8480
117
- const index = eventCallbacks.indexOf(callback);
118
- if (index !== -1) {
119
- if (eventCallbacks.length === 1) {
120
- delete emitterInfo.callbacks[event];
121
- }
122
- else {
123
- removeEventListener(this, emitter, event, callback);
124
- }
54
+ listenTo(emitter, event, callback, options = {}) {
55
+ let emitterInfo, eventCallbacks;
56
+ // _listeningTo contains a list of emitters that this object is listening to.
57
+ // This list has the following format:
58
+ //
59
+ // _listeningTo: {
60
+ // emitterId: {
61
+ // emitter: emitter,
62
+ // callbacks: {
63
+ // event1: [ callback1, callback2, ... ]
64
+ // ....
65
+ // }
66
+ // },
67
+ // ...
68
+ // }
69
+ if (!this[_listeningTo]) {
70
+ this[_listeningTo] = {};
125
71
  }
126
- }
127
- // Only `emitter` and `event` provided. off() all callbacks for that event.
128
- else if (eventCallbacks) {
129
- while ((callback = eventCallbacks.pop())) {
130
- removeEventListener(this, emitter, event, callback);
72
+ const emitters = this[_listeningTo];
73
+ if (!_getEmitterId(emitter)) {
74
+ _setEmitterId(emitter);
131
75
  }
132
- delete emitterInfo.callbacks[event];
133
- }
134
- // Only `emitter` provided. off() all events for that emitter.
135
- else if (emitterInfo) {
136
- for (event in emitterInfo.callbacks) {
137
- this.stopListening(emitter, event);
76
+ const emitterId = _getEmitterId(emitter);
77
+ if (!(emitterInfo = emitters[emitterId])) {
78
+ emitterInfo = emitters[emitterId] = {
79
+ emitter,
80
+ callbacks: {}
81
+ };
138
82
  }
139
- delete emitters[emitterId];
140
- }
141
- // No params provided. off() all emitters.
142
- else {
143
- for (emitterId in emitters) {
144
- this.stopListening(emitters[emitterId].emitter);
83
+ if (!(eventCallbacks = emitterInfo.callbacks[event])) {
84
+ eventCallbacks = emitterInfo.callbacks[event] = [];
145
85
  }
146
- delete this[_listeningTo];
86
+ eventCallbacks.push(callback);
87
+ // Finally register the callback to the event.
88
+ addEventListener(this, emitter, event, callback, options);
147
89
  }
148
- },
149
- /**
150
- * @inheritDoc
151
- */
152
- fire(eventOrInfo, ...args) {
153
- try {
154
- const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo(this, eventOrInfo);
155
- const event = eventInfo.name;
156
- let callbacks = getCallbacksForEvent(this, event);
157
- // Record that the event passed this emitter on its path.
158
- eventInfo.path.push(this);
159
- // Handle event listener callbacks first.
160
- if (callbacks) {
161
- // Arguments passed to each callback.
162
- const callbackArgs = [eventInfo, ...args];
163
- // Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
164
- // are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
165
- // failed if callbacks were added to the queue before currently processed callback.
166
- // If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
167
- // event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
168
- callbacks = Array.from(callbacks);
169
- for (let i = 0; i < callbacks.length; i++) {
170
- callbacks[i].callback.apply(this, callbackArgs);
171
- // Remove the callback from future requests if off() has been called.
172
- if (eventInfo.off.called) {
173
- // Remove the called mark for the next calls.
174
- delete eventInfo.off.called;
175
- this._removeEventListener(event, callbacks[i].callback);
90
+ stopListening(emitter, event, callback) {
91
+ const emitters = this[_listeningTo];
92
+ let emitterId = emitter && _getEmitterId(emitter);
93
+ const emitterInfo = (emitters && emitterId) ? emitters[emitterId] : undefined;
94
+ const eventCallbacks = (emitterInfo && event) ? emitterInfo.callbacks[event] : undefined;
95
+ // Stop if nothing has been listened.
96
+ if (!emitters || (emitter && !emitterInfo) || (event && !eventCallbacks)) {
97
+ return;
98
+ }
99
+ // All params provided. off() that single callback.
100
+ if (callback) {
101
+ removeEventListener(this, emitter, event, callback);
102
+ // We must remove callbacks as well in order to prevent memory leaks.
103
+ // See https://github.com/ckeditor/ckeditor5/pull/8480
104
+ const index = eventCallbacks.indexOf(callback);
105
+ if (index !== -1) {
106
+ if (eventCallbacks.length === 1) {
107
+ delete emitterInfo.callbacks[event];
176
108
  }
177
- // Do not execute next callbacks if stop() was called.
178
- if (eventInfo.stop.called) {
179
- break;
109
+ else {
110
+ removeEventListener(this, emitter, event, callback);
180
111
  }
181
112
  }
182
113
  }
183
- // Delegate event to other emitters if needed.
184
- const delegations = this[_delegations];
185
- if (delegations) {
186
- const destinations = delegations.get(event);
187
- const passAllDestinations = delegations.get('*');
188
- if (destinations) {
189
- fireDelegatedEvents(destinations, eventInfo, args);
114
+ // Only `emitter` and `event` provided. off() all callbacks for that event.
115
+ else if (eventCallbacks) {
116
+ while ((callback = eventCallbacks.pop())) {
117
+ removeEventListener(this, emitter, event, callback);
190
118
  }
191
- if (passAllDestinations) {
192
- fireDelegatedEvents(passAllDestinations, eventInfo, args);
119
+ delete emitterInfo.callbacks[event];
120
+ }
121
+ // Only `emitter` provided. off() all events for that emitter.
122
+ else if (emitterInfo) {
123
+ for (event in emitterInfo.callbacks) {
124
+ this.stopListening(emitter, event);
193
125
  }
126
+ delete emitters[emitterId];
127
+ }
128
+ // No params provided. off() all emitters.
129
+ else {
130
+ for (emitterId in emitters) {
131
+ this.stopListening(emitters[emitterId].emitter);
132
+ }
133
+ delete this[_listeningTo];
194
134
  }
195
- return eventInfo.return;
196
- }
197
- catch (err) {
198
- // @if CK_DEBUG // throw err;
199
- /* istanbul ignore next */
200
- CKEditorError.rethrowUnexpectedError(err, this);
201
135
  }
202
- },
203
- /**
204
- * @inheritDoc
205
- */
206
- delegate(...events) {
207
- return {
208
- to: (emitter, nameOrFunction) => {
209
- if (!this[_delegations]) {
210
- this[_delegations] = new Map();
136
+ fire(eventOrInfo, ...args) {
137
+ try {
138
+ const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo(this, eventOrInfo);
139
+ const event = eventInfo.name;
140
+ let callbacks = getCallbacksForEvent(this, event);
141
+ // Record that the event passed this emitter on its path.
142
+ eventInfo.path.push(this);
143
+ // Handle event listener callbacks first.
144
+ if (callbacks) {
145
+ // Arguments passed to each callback.
146
+ const callbackArgs = [eventInfo, ...args];
147
+ // Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
148
+ // are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
149
+ // failed if callbacks were added to the queue before currently processed callback.
150
+ // If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
151
+ // event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
152
+ callbacks = Array.from(callbacks);
153
+ for (let i = 0; i < callbacks.length; i++) {
154
+ callbacks[i].callback.apply(this, callbackArgs);
155
+ // Remove the callback from future requests if off() has been called.
156
+ if (eventInfo.off.called) {
157
+ // Remove the called mark for the next calls.
158
+ delete eventInfo.off.called;
159
+ this._removeEventListener(event, callbacks[i].callback);
160
+ }
161
+ // Do not execute next callbacks if stop() was called.
162
+ if (eventInfo.stop.called) {
163
+ break;
164
+ }
165
+ }
211
166
  }
212
- // Originally there was a for..of loop which unfortunately caused an error in Babel that didn't allow
213
- // build an application. See: https://github.com/ckeditor/ckeditor5-react/issues/40.
214
- events.forEach(eventName => {
215
- const destinations = this[_delegations].get(eventName);
216
- if (!destinations) {
217
- this[_delegations].set(eventName, new Map([[emitter, nameOrFunction]]));
167
+ // Delegate event to other emitters if needed.
168
+ const delegations = this[_delegations];
169
+ if (delegations) {
170
+ const destinations = delegations.get(event);
171
+ const passAllDestinations = delegations.get('*');
172
+ if (destinations) {
173
+ fireDelegatedEvents(destinations, eventInfo, args);
218
174
  }
219
- else {
220
- destinations.set(emitter, nameOrFunction);
175
+ if (passAllDestinations) {
176
+ fireDelegatedEvents(passAllDestinations, eventInfo, args);
221
177
  }
222
- });
178
+ }
179
+ return eventInfo.return;
180
+ }
181
+ catch (err) {
182
+ // @if CK_DEBUG // throw err;
183
+ /* istanbul ignore next */
184
+ CKEditorError.rethrowUnexpectedError(err, this);
223
185
  }
224
- };
225
- },
226
- /**
227
- * @inheritDoc
228
- */
229
- stopDelegating(event, emitter) {
230
- if (!this[_delegations]) {
231
- return;
232
- }
233
- if (!event) {
234
- this[_delegations].clear();
235
186
  }
236
- else if (!emitter) {
237
- this[_delegations].delete(event);
187
+ delegate(...events) {
188
+ return {
189
+ to: (emitter, nameOrFunction) => {
190
+ if (!this[_delegations]) {
191
+ this[_delegations] = new Map();
192
+ }
193
+ // Originally there was a for..of loop which unfortunately caused an error in Babel that didn't allow
194
+ // build an application. See: https://github.com/ckeditor/ckeditor5-react/issues/40.
195
+ events.forEach(eventName => {
196
+ const destinations = this[_delegations].get(eventName);
197
+ if (!destinations) {
198
+ this[_delegations].set(eventName, new Map([[emitter, nameOrFunction]]));
199
+ }
200
+ else {
201
+ destinations.set(emitter, nameOrFunction);
202
+ }
203
+ });
204
+ }
205
+ };
238
206
  }
239
- else {
240
- const destinations = this[_delegations].get(event);
241
- if (destinations) {
242
- destinations.delete(emitter);
207
+ stopDelegating(event, emitter) {
208
+ if (!this[_delegations]) {
209
+ return;
210
+ }
211
+ if (!event) {
212
+ this[_delegations].clear();
213
+ }
214
+ else if (!emitter) {
215
+ this[_delegations].delete(event);
216
+ }
217
+ else {
218
+ const destinations = this[_delegations].get(event);
219
+ if (destinations) {
220
+ destinations.delete(emitter);
221
+ }
243
222
  }
244
223
  }
245
- },
246
- /**
247
- * @inheritDoc
248
- */
249
- _addEventListener(event, callback, options) {
250
- createEventNamespace(this, event);
251
- const lists = getCallbacksListsForNamespace(this, event);
252
- const priority = priorities.get(options.priority);
253
- const callbackDefinition = {
254
- callback,
255
- priority
256
- };
257
- // Add the callback to all callbacks list.
258
- for (const callbacks of lists) {
259
- // Add the callback to the list in the right priority position.
260
- insertToPriorityArray(callbacks, callbackDefinition);
224
+ _addEventListener(event, callback, options) {
225
+ createEventNamespace(this, event);
226
+ const lists = getCallbacksListsForNamespace(this, event);
227
+ const priority = priorities.get(options.priority);
228
+ const callbackDefinition = {
229
+ callback,
230
+ priority
231
+ };
232
+ // Add the callback to all callbacks list.
233
+ for (const callbacks of lists) {
234
+ // Add the callback to the list in the right priority position.
235
+ insertToPriorityArray(callbacks, callbackDefinition);
236
+ }
261
237
  }
262
- },
263
- /**
264
- * @inheritDoc
265
- */
266
- _removeEventListener(event, callback) {
267
- const lists = getCallbacksListsForNamespace(this, event);
268
- for (const callbacks of lists) {
269
- for (let i = 0; i < callbacks.length; i++) {
270
- if (callbacks[i].callback == callback) {
271
- // Remove the callback from the list (fixing the next index).
272
- callbacks.splice(i, 1);
273
- i--;
238
+ _removeEventListener(event, callback) {
239
+ const lists = getCallbacksListsForNamespace(this, event);
240
+ for (const callbacks of lists) {
241
+ for (let i = 0; i < callbacks.length; i++) {
242
+ if (callbacks[i].callback == callback) {
243
+ // Remove the callback from the list (fixing the next index).
244
+ callbacks.splice(i, 1);
245
+ i--;
246
+ }
274
247
  }
275
248
  }
276
249
  }
277
250
  }
278
- };
279
- export default EmitterMixin;
251
+ return Mixin;
252
+ }
253
+ export const Emitter = EmitterMixin(Object);
254
+ // Backward compatibility with `mix`
255
+ ([
256
+ 'on', 'once', 'off', 'listenTo',
257
+ 'stopListening', 'fire', 'delegate', 'stopDelegating',
258
+ '_addEventListener', '_removeEventListener'
259
+ ]).forEach(key => {
260
+ EmitterMixin[key] = Emitter.prototype[key];
261
+ });
280
262
  /**
281
263
  * Checks if `listeningEmitter` listens to an emitter with given `listenedToEmitterId` and if so, returns that emitter.
282
264
  * If not, returns `null`.
@@ -455,7 +437,7 @@ function addEventListener(listener, emitter, event, callback, options) {
455
437
  else {
456
438
  // Allow listening on objects that do not implement Emitter interface.
457
439
  // This is needed in some tests that are using mocks instead of the real objects with EmitterMixin mixed.
458
- listener._addEventListener.call(emitter, event, callback, options);
440
+ (listener._addEventListener).call(emitter, event, callback, options);
459
441
  }
460
442
  }
461
443
  // Helper for removing event callback from the emitter.
package/src/env.js CHANGED
@@ -6,7 +6,22 @@
6
6
  /**
7
7
  * @module utils/env
8
8
  */
9
- const userAgent = navigator.userAgent.toLowerCase();
9
+ /**
10
+ * Safely returns `userAgent` from browser's navigator API in a lower case.
11
+ * If navigator API is not available it will return an empty string.
12
+ *
13
+ * @returns {String}
14
+ */
15
+ export function getUserAgent() {
16
+ // In some environments navigator API might not be available.
17
+ try {
18
+ return navigator.userAgent.toLowerCase();
19
+ }
20
+ catch (e) {
21
+ return '';
22
+ }
23
+ }
24
+ const userAgent = getUserAgent();
10
25
  /**
11
26
  * A namespace containing environment and browser information.
12
27
  *
@@ -3,13 +3,13 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
  /* global setTimeout, clearTimeout */
6
+ /* eslint-disable new-cap */
6
7
  /**
7
8
  * @module utils/focustracker
8
9
  */
9
10
  import DomEmitterMixin from './dom/emittermixin';
10
- import ObservableMixin from './observablemixin';
11
+ import { Observable } from './observablemixin';
11
12
  import CKEditorError from './ckeditorerror';
12
- import mix from './mix';
13
13
  /**
14
14
  * Allows observing a group of `Element`s whether at least one of them is focused.
15
15
  *
@@ -25,8 +25,9 @@ import mix from './mix';
25
25
  * @mixes module:utils/dom/emittermixin~EmitterMixin
26
26
  * @mixes module:utils/observablemixin~ObservableMixin
27
27
  */
28
- class FocusTracker {
28
+ export default class FocusTracker extends DomEmitterMixin(Observable) {
29
29
  constructor() {
30
+ super();
30
31
  this.set('isFocused', false);
31
32
  this.set('focusedElement', null);
32
33
  this._elements = new Set();
@@ -98,6 +99,3 @@ class FocusTracker {
98
99
  }, 0);
99
100
  }
100
101
  }
101
- mix(FocusTracker, DomEmitterMixin);
102
- mix(FocusTracker, ObservableMixin);
103
- export default FocusTracker;
@@ -71,7 +71,7 @@ export default class KeystrokeHandler {
71
71
  * the {@link module:utils/keyboard~parseKeystroke} function.
72
72
  * @param {Function} callback A function called with the
73
73
  * {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and
74
- * a helper funcion to call both `preventDefault()` and `stopPropagation()` on the underlying event.
74
+ * a helper function to call both `preventDefault()` and `stopPropagation()` on the underlying event.
75
75
  * @param {Object} [options={}] Additional options.
76
76
  * @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of the keystroke
77
77
  * callback. The higher the priority value the sooner the callback will be executed. Keystrokes having the same priority
package/src/mix.js CHANGED
@@ -25,6 +25,7 @@
25
25
  *
26
26
  * Note: Properties which already exist in the base class will not be overriden.
27
27
  *
28
+ * @depreciated Use mixin pattern, see: https://www.typescriptlang.org/docs/handbook/mixins.html.
28
29
  * @param {Function} [baseClass] Class which prototype will be extended.
29
30
  * @param {Object} [...mixins] Objects from which to get properties.
30
31
  */
@@ -36,6 +37,9 @@ export default function mix(baseClass, ...mixins) {
36
37
  if (key in baseClass.prototype) {
37
38
  return;
38
39
  }
40
+ if (typeof mixin == 'function' && (key == 'length' || key == 'name' || key == 'prototype')) {
41
+ return;
42
+ }
39
43
  const sourceDescriptor = Object.getOwnPropertyDescriptor(mixin, key);
40
44
  sourceDescriptor.enumerable = false;
41
45
  Object.defineProperty(baseClass.prototype, key, sourceDescriptor);