Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ckeditor/ckeditor5-utils

Package Overview
Dependencies
Maintainers
1
Versions
700
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-utils - npm Package Compare versions

Comparing version 35.0.1 to 35.1.0

2

CHANGELOG.md

@@ -169,3 +169,3 @@ Changelog

* Vairous fixes in the API docs. Thanks to [@denisname](https://github.com/denisname)!
* Various fixes in the API docs. Thanks to [@denisname](https://github.com/denisname)!

@@ -172,0 +172,0 @@

{
"name": "@ckeditor/ckeditor5-utils",
"version": "35.0.1",
"version": "35.1.0",
"description": "Miscellaneous utilities used by CKEditor 5.",

@@ -17,6 +17,6 @@ "keywords": [

"devDependencies": {
"@ckeditor/ckeditor5-build-classic": "^35.0.1",
"@ckeditor/ckeditor5-editor-classic": "^35.0.1",
"@ckeditor/ckeditor5-core": "^35.0.1",
"@ckeditor/ckeditor5-engine": "^35.0.1",
"@ckeditor/ckeditor5-build-classic": "^35.1.0",
"@ckeditor/ckeditor5-editor-classic": "^35.1.0",
"@ckeditor/ckeditor5-core": "^35.1.0",
"@ckeditor/ckeditor5-engine": "^35.1.0",
"@types/lodash-es": "^4.17.6",

@@ -23,0 +23,0 @@ "typescript": "^4.6.4"

@@ -8,7 +8,6 @@ /**

*/
import EmitterMixin from './emittermixin';
import { Emitter } from './emittermixin';
import CKEditorError from './ckeditorerror';
import uid from './uid';
import isIterable from './isiterable';
import mix from './mix';
/**

@@ -26,3 +25,3 @@ * Collections are ordered sets of objects. Items in the collection can be retrieved by their indexes

*/
class Collection {
export default class Collection extends Emitter {
/**

@@ -65,2 +64,3 @@ * Creates a new Collection instance.

constructor(initialItemsOrOptions = {}, options = {}) {
super();
const hasInitialItems = isIterable(initialItemsOrOptions);

@@ -621,3 +621,1 @@ if (!hasInitialItems) {

}
mix(Collection, EmitterMixin);
export default Collection;

@@ -5,11 +5,10 @@ /**

*/
/* eslint-disable new-cap */
/**
* @module utils/dom/emittermixin
*/
import { default as EmitterMixin, _getEmitterListenedTo, _setEmitterId } from '../emittermixin';
import { _getEmitterListenedTo, _setEmitterId, Emitter as BaseEmitter } from '../emittermixin';
import uid from '../uid';
import isNode from './isnode';
import isWindow from './iswindow';
import { extend } from 'lodash-es';
import mix from '../mix';
/**

@@ -24,5 +23,5 @@ * Mixin that injects the DOM events API into its host. It provides the API

* import DomEmitterMixin from '../utils/dom/emittermixin.js';
* import { Emitter } from '../utils/emittermixin.js';
*
* class SomeView {}
* mix( SomeView, DomEmitterMixin );
* class SomeView extends DomEmitterMixin( Emitter ) {}
*

@@ -38,95 +37,75 @@ * const view = new SomeView();

*/
const DomEmitterMixin = extend({}, EmitterMixin, {
/**
* Registers a callback function to be executed when an event is fired in a specific Emitter or DOM Node.
* It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#listenTo}.
*
* @param {module:utils/emittermixin~Emitter|Node|Window} emitter The object that fires the event.
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
* @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
* @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
* and prevents blocking browser's main thread by this event handler.
*/
listenTo(emitter, event, callback, options = {}) {
// Check if emitter is an instance of DOM Node. If so, use corresponding ProxyEmitter (or create one if not existing).
if (isNode(emitter) || isWindow(emitter)) {
const proxyOptions = {
capture: !!options.useCapture,
passive: !!options.usePassive
};
const proxyEmitter = this._getProxyEmitter(emitter, proxyOptions) || new ProxyEmitter(emitter, proxyOptions);
this.listenTo(proxyEmitter, event, callback, options);
export default function DomEmitterMixin(base) {
class Mixin extends base {
listenTo(emitter, event, callback, options = {}) {
// Check if emitter is an instance of DOM Node. If so, use corresponding ProxyEmitter (or create one if not existing).
if (isNode(emitter) || isWindow(emitter)) {
const proxyOptions = {
capture: !!options.useCapture,
passive: !!options.usePassive
};
const proxyEmitter = this._getProxyEmitter(emitter, proxyOptions) || new ProxyEmitter(emitter, proxyOptions);
this.listenTo(proxyEmitter, event, callback, options);
}
else {
// Execute parent class method with Emitter (or ProxyEmitter) instance.
BaseEmitter.prototype.listenTo.call(this, emitter, event, callback, options);
}
}
else {
// Execute parent class method with Emitter (or ProxyEmitter) instance.
EmitterMixin.listenTo.call(this, emitter, event, callback, options);
}
},
/**
* Stops listening for events. It can be used at different levels:
* It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#listenTo}.
*
* * To stop listening to a specific callback.
* * To stop listening to a specific event.
* * To stop listening to all events fired by a specific object.
* * To stop listening to all events fired by all object.
*
* @param {module:utils/emittermixin~Emitter|Node|Window} [emitter] The object to stop listening to.
* If omitted, stops it for all objects.
* @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
* for all events from `emitter`.
* @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
* `event`.
*/
stopListening(emitter, event, callback) {
// Check if the emitter is an instance of DOM Node. If so, forward the call to the corresponding ProxyEmitters.
if (isNode(emitter) || isWindow(emitter)) {
const proxyEmitters = this._getAllProxyEmitters(emitter);
for (const proxy of proxyEmitters) {
this.stopListening(proxy, event, callback);
stopListening(emitter, event, callback) {
// Check if the emitter is an instance of DOM Node. If so, forward the call to the corresponding ProxyEmitters.
if (isNode(emitter) || isWindow(emitter)) {
const proxyEmitters = this._getAllProxyEmitters(emitter);
for (const proxy of proxyEmitters) {
this.stopListening(proxy, event, callback);
}
}
else {
// Execute parent class method with Emitter (or ProxyEmitter) instance.
BaseEmitter.prototype.stopListening.call(this, emitter, event, callback);
}
}
else {
// Execute parent class method with Emitter (or ProxyEmitter) instance.
EmitterMixin.stopListening.call(this, emitter, event, callback);
/**
* Retrieves ProxyEmitter instance for given DOM Node residing in this Host and given options.
*
* @private
* @param {Node|Window} node DOM Node of the ProxyEmitter.
* @param {Object} [options] Additional options.
* @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
* @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
* and prevents blocking browser's main thread by this event handler.
* @returns {module:utils/dom/emittermixin~ProxyEmitter|null} ProxyEmitter instance bound to the DOM Node.
*/
_getProxyEmitter(node, options) {
return _getEmitterListenedTo(this, getProxyEmitterId(node, options));
}
},
/**
* Retrieves ProxyEmitter instance for given DOM Node residing in this Host and given options.
*
* @private
* @param {Node|Window} node DOM Node of the ProxyEmitter.
* @param {Object} [options] Additional options.
* @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
* @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
* and prevents blocking browser's main thread by this event handler.
* @returns {module:utils/dom/emittermixin~ProxyEmitter|null} ProxyEmitter instance bound to the DOM Node.
*/
_getProxyEmitter(node, options) {
return _getEmitterListenedTo(this, getProxyEmitterId(node, options));
},
/**
* Retrieves all the ProxyEmitter instances for given DOM Node residing in this Host.
*
* @private
* @param {Node|Window} node DOM Node of the ProxyEmitter.
* @returns {Array.<module:utils/dom/emittermixin~ProxyEmitter>}
*/
_getAllProxyEmitters(node) {
return [
{ capture: false, passive: false },
{ capture: false, passive: true },
{ capture: true, passive: false },
{ capture: true, passive: true }
].map(options => this._getProxyEmitter(node, options)).filter(proxy => !!proxy);
/**
* Retrieves all the ProxyEmitter instances for given DOM Node residing in this Host.
*
* @private
* @param {Node|Window} node DOM Node of the ProxyEmitter.
* @returns {Array.<module:utils/dom/emittermixin~ProxyEmitter>}
*/
_getAllProxyEmitters(node) {
return [
{ capture: false, passive: false },
{ capture: false, passive: true },
{ capture: true, passive: false },
{ capture: true, passive: true }
].map(options => this._getProxyEmitter(node, options)).filter(proxy => !!proxy);
}
}
return Mixin;
}
export const Emitter = DomEmitterMixin(BaseEmitter);
// Backward compatibility with `mix`
([
'_getProxyEmitter', '_getAllProxyEmitters',
'on', 'once', 'off', 'listenTo',
'stopListening', 'fire', 'delegate', 'stopDelegating',
'_addEventListener', '_removeEventListener'
]).forEach(key => {
DomEmitterMixin[key] = Emitter.prototype[key];
});
export default DomEmitterMixin;
/**

@@ -164,3 +143,3 @@ * Creates a ProxyEmitter instance. Such an instance is a bridge between a DOM Node firing events

*/
class ProxyEmitter {
class ProxyEmitter extends BaseEmitter {
/**

@@ -175,2 +154,3 @@ * @param {Node|Window} node DOM Node that fires events.

constructor(node, options) {
super();
// Set emitter ID to match DOM Node "expando" property.

@@ -243,3 +223,3 @@ _setEmitterId(this, getProxyEmitterId(node, options));

this.attach(event);
EmitterMixin._addEventListener.call(this, event, callback, options);
BaseEmitter.prototype._addEventListener.call(this, event, callback, options);
}

@@ -255,3 +235,3 @@ /**

_removeEventListener(event, callback) {
EmitterMixin._removeEventListener.call(this, event, callback);
BaseEmitter.prototype._removeEventListener.call(this, event, callback);
this.detach(event);

@@ -283,3 +263,2 @@ }

}
mix(ProxyEmitter, EmitterMixin);
// Gets an unique DOM Node identifier. The identifier will be set if not defined.

@@ -286,0 +265,0 @@ //

@@ -23,2 +23,16 @@ /**

*/
export default { window, document };
let global;
// In some environments window and document API might not be available.
try {
global = { window, document };
}
catch (e) {
// It's not possible to mock a window object to simulate lack of a window object without writing extremely convoluted code.
/* istanbul ignore next */
// Let's cast it to not change module's API.
// We only handle this so loading editor in environments without window and document doesn't fail.
// For better DX we shouldn't introduce mixed types and require developers to check the type manually.
// This module should not be used on purpose in any environment outside browser.
global = { window: {}, document: {} };
}
export default global;

@@ -8,2 +8,3 @@ /**

*/
/* eslint-disable new-cap */
import EventInfo from './eventinfo';

@@ -30,247 +31,219 @@ import uid from './uid';

*/
const EmitterMixin = {
/**
* @inheritDoc
*/
on(event, callback, options = {}) {
this.listenTo(this, event, callback, options);
},
/**
* @inheritDoc
*/
once(event, callback, options) {
let wasFired = false;
const onceCallback = (event, ...args) => {
// Ensure the callback is called only once even if the callback itself leads to re-firing the event
// (which would call the callback again).
if (!wasFired) {
wasFired = true;
// Go off() at the first call.
event.off();
// Go with the original callback.
callback.call(this, event, ...args);
}
};
// Make a similar on() call, simply replacing the callback.
this.listenTo(this, event, onceCallback, options);
},
/**
* @inheritDoc
*/
off(event, callback) {
this.stopListening(this, event, callback);
},
/**
* @inheritDoc
*/
listenTo(emitter, event, callback, options = {}) {
let emitterInfo, eventCallbacks;
// _listeningTo contains a list of emitters that this object is listening to.
// This list has the following format:
//
// _listeningTo: {
// emitterId: {
// emitter: emitter,
// callbacks: {
// event1: [ callback1, callback2, ... ]
// ....
// }
// },
// ...
// }
if (!this[_listeningTo]) {
this[_listeningTo] = {};
export default function EmitterMixin(base) {
class Mixin extends base {
on(event, callback, options) {
this.listenTo(this, event, callback, options);
}
const emitters = this[_listeningTo];
if (!_getEmitterId(emitter)) {
_setEmitterId(emitter);
}
const emitterId = _getEmitterId(emitter);
if (!(emitterInfo = emitters[emitterId])) {
emitterInfo = emitters[emitterId] = {
emitter,
callbacks: {}
once(event, callback, options) {
let wasFired = false;
const onceCallback = (event, ...args) => {
// Ensure the callback is called only once even if the callback itself leads to re-firing the event
// (which would call the callback again).
if (!wasFired) {
wasFired = true;
// Go off() at the first call.
event.off();
// Go with the original callback.
callback.call(this, event, ...args);
}
};
// Make a similar on() call, simply replacing the callback.
this.listenTo(this, event, onceCallback, options);
}
if (!(eventCallbacks = emitterInfo.callbacks[event])) {
eventCallbacks = emitterInfo.callbacks[event] = [];
off(event, callback) {
this.stopListening(this, event, callback);
}
eventCallbacks.push(callback);
// Finally register the callback to the event.
addEventListener(this, emitter, event, callback, options);
},
/**
* @inheritDoc
*/
stopListening(emitter, event, callback) {
const emitters = this[_listeningTo];
let emitterId = emitter && _getEmitterId(emitter);
const emitterInfo = (emitters && emitterId) ? emitters[emitterId] : undefined;
const eventCallbacks = (emitterInfo && event) ? emitterInfo.callbacks[event] : undefined;
// Stop if nothing has been listened.
if (!emitters || (emitter && !emitterInfo) || (event && !eventCallbacks)) {
return;
}
// All params provided. off() that single callback.
if (callback) {
removeEventListener(this, emitter, event, callback);
// We must remove callbacks as well in order to prevent memory leaks.
// See https://github.com/ckeditor/ckeditor5/pull/8480
const index = eventCallbacks.indexOf(callback);
if (index !== -1) {
if (eventCallbacks.length === 1) {
delete emitterInfo.callbacks[event];
}
else {
removeEventListener(this, emitter, event, callback);
}
listenTo(emitter, event, callback, options = {}) {
let emitterInfo, eventCallbacks;
// _listeningTo contains a list of emitters that this object is listening to.
// This list has the following format:
//
// _listeningTo: {
// emitterId: {
// emitter: emitter,
// callbacks: {
// event1: [ callback1, callback2, ... ]
// ....
// }
// },
// ...
// }
if (!this[_listeningTo]) {
this[_listeningTo] = {};
}
}
// Only `emitter` and `event` provided. off() all callbacks for that event.
else if (eventCallbacks) {
while ((callback = eventCallbacks.pop())) {
removeEventListener(this, emitter, event, callback);
const emitters = this[_listeningTo];
if (!_getEmitterId(emitter)) {
_setEmitterId(emitter);
}
delete emitterInfo.callbacks[event];
}
// Only `emitter` provided. off() all events for that emitter.
else if (emitterInfo) {
for (event in emitterInfo.callbacks) {
this.stopListening(emitter, event);
const emitterId = _getEmitterId(emitter);
if (!(emitterInfo = emitters[emitterId])) {
emitterInfo = emitters[emitterId] = {
emitter,
callbacks: {}
};
}
delete emitters[emitterId];
if (!(eventCallbacks = emitterInfo.callbacks[event])) {
eventCallbacks = emitterInfo.callbacks[event] = [];
}
eventCallbacks.push(callback);
// Finally register the callback to the event.
addEventListener(this, emitter, event, callback, options);
}
// No params provided. off() all emitters.
else {
for (emitterId in emitters) {
this.stopListening(emitters[emitterId].emitter);
stopListening(emitter, event, callback) {
const emitters = this[_listeningTo];
let emitterId = emitter && _getEmitterId(emitter);
const emitterInfo = (emitters && emitterId) ? emitters[emitterId] : undefined;
const eventCallbacks = (emitterInfo && event) ? emitterInfo.callbacks[event] : undefined;
// Stop if nothing has been listened.
if (!emitters || (emitter && !emitterInfo) || (event && !eventCallbacks)) {
return;
}
delete this[_listeningTo];
}
},
/**
* @inheritDoc
*/
fire(eventOrInfo, ...args) {
try {
const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo(this, eventOrInfo);
const event = eventInfo.name;
let callbacks = getCallbacksForEvent(this, event);
// Record that the event passed this emitter on its path.
eventInfo.path.push(this);
// Handle event listener callbacks first.
if (callbacks) {
// Arguments passed to each callback.
const callbackArgs = [eventInfo, ...args];
// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
// failed if callbacks were added to the queue before currently processed callback.
// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
callbacks = Array.from(callbacks);
for (let i = 0; i < callbacks.length; i++) {
callbacks[i].callback.apply(this, callbackArgs);
// Remove the callback from future requests if off() has been called.
if (eventInfo.off.called) {
// Remove the called mark for the next calls.
delete eventInfo.off.called;
this._removeEventListener(event, callbacks[i].callback);
// All params provided. off() that single callback.
if (callback) {
removeEventListener(this, emitter, event, callback);
// We must remove callbacks as well in order to prevent memory leaks.
// See https://github.com/ckeditor/ckeditor5/pull/8480
const index = eventCallbacks.indexOf(callback);
if (index !== -1) {
if (eventCallbacks.length === 1) {
delete emitterInfo.callbacks[event];
}
// Do not execute next callbacks if stop() was called.
if (eventInfo.stop.called) {
break;
else {
removeEventListener(this, emitter, event, callback);
}
}
}
// Delegate event to other emitters if needed.
const delegations = this[_delegations];
if (delegations) {
const destinations = delegations.get(event);
const passAllDestinations = delegations.get('*');
if (destinations) {
fireDelegatedEvents(destinations, eventInfo, args);
// Only `emitter` and `event` provided. off() all callbacks for that event.
else if (eventCallbacks) {
while ((callback = eventCallbacks.pop())) {
removeEventListener(this, emitter, event, callback);
}
if (passAllDestinations) {
fireDelegatedEvents(passAllDestinations, eventInfo, args);
delete emitterInfo.callbacks[event];
}
// Only `emitter` provided. off() all events for that emitter.
else if (emitterInfo) {
for (event in emitterInfo.callbacks) {
this.stopListening(emitter, event);
}
delete emitters[emitterId];
}
return eventInfo.return;
// No params provided. off() all emitters.
else {
for (emitterId in emitters) {
this.stopListening(emitters[emitterId].emitter);
}
delete this[_listeningTo];
}
}
catch (err) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
CKEditorError.rethrowUnexpectedError(err, this);
}
},
/**
* @inheritDoc
*/
delegate(...events) {
return {
to: (emitter, nameOrFunction) => {
if (!this[_delegations]) {
this[_delegations] = new Map();
fire(eventOrInfo, ...args) {
try {
const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo(this, eventOrInfo);
const event = eventInfo.name;
let callbacks = getCallbacksForEvent(this, event);
// Record that the event passed this emitter on its path.
eventInfo.path.push(this);
// Handle event listener callbacks first.
if (callbacks) {
// Arguments passed to each callback.
const callbackArgs = [eventInfo, ...args];
// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
// failed if callbacks were added to the queue before currently processed callback.
// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
callbacks = Array.from(callbacks);
for (let i = 0; i < callbacks.length; i++) {
callbacks[i].callback.apply(this, callbackArgs);
// Remove the callback from future requests if off() has been called.
if (eventInfo.off.called) {
// Remove the called mark for the next calls.
delete eventInfo.off.called;
this._removeEventListener(event, callbacks[i].callback);
}
// Do not execute next callbacks if stop() was called.
if (eventInfo.stop.called) {
break;
}
}
}
// Originally there was a for..of loop which unfortunately caused an error in Babel that didn't allow
// build an application. See: https://github.com/ckeditor/ckeditor5-react/issues/40.
events.forEach(eventName => {
const destinations = this[_delegations].get(eventName);
if (!destinations) {
this[_delegations].set(eventName, new Map([[emitter, nameOrFunction]]));
// Delegate event to other emitters if needed.
const delegations = this[_delegations];
if (delegations) {
const destinations = delegations.get(event);
const passAllDestinations = delegations.get('*');
if (destinations) {
fireDelegatedEvents(destinations, eventInfo, args);
}
else {
destinations.set(emitter, nameOrFunction);
if (passAllDestinations) {
fireDelegatedEvents(passAllDestinations, eventInfo, args);
}
});
}
return eventInfo.return;
}
};
},
/**
* @inheritDoc
*/
stopDelegating(event, emitter) {
if (!this[_delegations]) {
return;
catch (err) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
CKEditorError.rethrowUnexpectedError(err, this);
}
}
if (!event) {
this[_delegations].clear();
delegate(...events) {
return {
to: (emitter, nameOrFunction) => {
if (!this[_delegations]) {
this[_delegations] = new Map();
}
// Originally there was a for..of loop which unfortunately caused an error in Babel that didn't allow
// build an application. See: https://github.com/ckeditor/ckeditor5-react/issues/40.
events.forEach(eventName => {
const destinations = this[_delegations].get(eventName);
if (!destinations) {
this[_delegations].set(eventName, new Map([[emitter, nameOrFunction]]));
}
else {
destinations.set(emitter, nameOrFunction);
}
});
}
};
}
else if (!emitter) {
this[_delegations].delete(event);
stopDelegating(event, emitter) {
if (!this[_delegations]) {
return;
}
if (!event) {
this[_delegations].clear();
}
else if (!emitter) {
this[_delegations].delete(event);
}
else {
const destinations = this[_delegations].get(event);
if (destinations) {
destinations.delete(emitter);
}
}
}
else {
const destinations = this[_delegations].get(event);
if (destinations) {
destinations.delete(emitter);
_addEventListener(event, callback, options) {
createEventNamespace(this, event);
const lists = getCallbacksListsForNamespace(this, event);
const priority = priorities.get(options.priority);
const callbackDefinition = {
callback,
priority
};
// Add the callback to all callbacks list.
for (const callbacks of lists) {
// Add the callback to the list in the right priority position.
insertToPriorityArray(callbacks, callbackDefinition);
}
}
},
/**
* @inheritDoc
*/
_addEventListener(event, callback, options) {
createEventNamespace(this, event);
const lists = getCallbacksListsForNamespace(this, event);
const priority = priorities.get(options.priority);
const callbackDefinition = {
callback,
priority
};
// Add the callback to all callbacks list.
for (const callbacks of lists) {
// Add the callback to the list in the right priority position.
insertToPriorityArray(callbacks, callbackDefinition);
}
},
/**
* @inheritDoc
*/
_removeEventListener(event, callback) {
const lists = getCallbacksListsForNamespace(this, event);
for (const callbacks of lists) {
for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i].callback == callback) {
// Remove the callback from the list (fixing the next index).
callbacks.splice(i, 1);
i--;
_removeEventListener(event, callback) {
const lists = getCallbacksListsForNamespace(this, event);
for (const callbacks of lists) {
for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i].callback == callback) {
// Remove the callback from the list (fixing the next index).
callbacks.splice(i, 1);
i--;
}
}

@@ -280,4 +253,13 @@ }

}
};
export default EmitterMixin;
return Mixin;
}
export const Emitter = EmitterMixin(Object);
// Backward compatibility with `mix`
([
'on', 'once', 'off', 'listenTo',
'stopListening', 'fire', 'delegate', 'stopDelegating',
'_addEventListener', '_removeEventListener'
]).forEach(key => {
EmitterMixin[key] = Emitter.prototype[key];
});
/**

@@ -461,3 +443,3 @@ * Checks if `listeningEmitter` listens to an emitter with given `listenedToEmitterId` and if so, returns that emitter.

// This is needed in some tests that are using mocks instead of the real objects with EmitterMixin mixed.
listener._addEventListener.call(emitter, event, callback, options);
(listener._addEventListener).call(emitter, event, callback, options);
}

@@ -464,0 +446,0 @@ }

@@ -9,4 +9,19 @@ /**

*/
const userAgent = navigator.userAgent.toLowerCase();
/**
* Safely returns `userAgent` from browser's navigator API in a lower case.
* If navigator API is not available it will return an empty string.
*
* @returns {String}
*/
export function getUserAgent() {
// In some environments navigator API might not be available.
try {
return navigator.userAgent.toLowerCase();
}
catch (e) {
return '';
}
}
const userAgent = getUserAgent();
/**
* A namespace containing environment and browser information.

@@ -13,0 +28,0 @@ *

@@ -6,2 +6,3 @@ /**

/* global setTimeout, clearTimeout */
/* eslint-disable new-cap */
/**

@@ -11,5 +12,4 @@ * @module utils/focustracker

import DomEmitterMixin from './dom/emittermixin';
import ObservableMixin from './observablemixin';
import { Observable } from './observablemixin';
import CKEditorError from './ckeditorerror';
import mix from './mix';
/**

@@ -30,4 +30,5 @@ * Allows observing a group of `Element`s whether at least one of them is focused.

*/
class FocusTracker {
export default class FocusTracker extends DomEmitterMixin(Observable) {
constructor() {
super();
this.set('isFocused', false);

@@ -104,4 +105,1 @@ this.set('focusedElement', null);

}
mix(FocusTracker, DomEmitterMixin);
mix(FocusTracker, ObservableMixin);
export default FocusTracker;

@@ -28,2 +28,3 @@ /**

*
* @depreciated Use mixin pattern, see: https://www.typescriptlang.org/docs/handbook/mixins.html.
* @param {Function} [baseClass] Class which prototype will be extended.

@@ -40,2 +41,5 @@ * @param {Object} [...mixins] Objects from which to get properties.

}
if (typeof mixin == 'function' && (key == 'length' || key == 'name' || key == 'prototype')) {
return;
}
const sourceDescriptor = Object.getOwnPropertyDescriptor(mixin, key);

@@ -42,0 +46,0 @@ sourceDescriptor.enumerable = false;

@@ -5,7 +5,7 @@ /**

*/
/* eslint-disable @typescript-eslint/unified-signatures */
/* eslint-disable @typescript-eslint/unified-signatures, new-cap */
/**
* @module utils/observablemixin
*/
import EmitterMixin from './emittermixin';
import { Emitter } from './emittermixin';
import CKEditorError from './ckeditorerror';

@@ -31,213 +31,212 @@ import { isObject } from 'lodash-es';

*/
const ObservableMixin = {
/**
* @inheritDoc
*/
set(name, value) {
// If the first parameter is an Object, iterate over its properties.
if (isObject(name)) {
Object.keys(name).forEach(property => {
this.set(property, name[property]);
}, this);
return;
}
initObservable(this);
const properties = this[observablePropertiesSymbol];
if ((name in this) && !properties.has(name)) {
/**
* Cannot override an existing property.
*
* This error is thrown when trying to {@link ~Observable#set set} a property with
* a name of an already existing property. For example:
*
* let observable = new Model();
* observable.property = 1;
* observable.set( 'property', 2 ); // throws
*
* observable.set( 'property', 1 );
* observable.set( 'property', 2 ); // ok, because this is an existing property.
*
* @error observable-set-cannot-override
*/
throw new CKEditorError('observable-set-cannot-override', this);
}
Object.defineProperty(this, name, {
enumerable: true,
configurable: true,
get() {
return properties.get(name);
},
set(value) {
const oldValue = properties.get(name);
// Fire `set` event before the new value will be set to make it possible
// to override observable property without affecting `change` event.
// See https://github.com/ckeditor/ckeditor5-utils/issues/171.
let newValue = this.fire('set:' + name, name, value, oldValue);
if (newValue === undefined) {
newValue = value;
}
// Allow undefined as an initial value like A.define( 'x', undefined ) (#132).
// Note: When properties map has no such own property, then its value is undefined.
if (oldValue !== newValue || !properties.has(name)) {
properties.set(name, newValue);
this.fire('change:' + name, name, newValue, oldValue);
}
export default function ObservableMixin(base) {
class Mixin extends base {
set(name, value) {
// If the first parameter is an Object, iterate over its properties.
if (isObject(name)) {
Object.keys(name).forEach(property => {
this.set(property, name[property]);
}, this);
return;
}
});
this[name] = value;
},
/**
* @inheritDoc
*/
bind(...bindProperties) {
if (!bindProperties.length || !isStringArray(bindProperties)) {
/**
* All properties must be strings.
*
* @error observable-bind-wrong-properties
*/
throw new CKEditorError('observable-bind-wrong-properties', this);
}
if ((new Set(bindProperties)).size !== bindProperties.length) {
/**
* Properties must be unique.
*
* @error observable-bind-duplicate-properties
*/
throw new CKEditorError('observable-bind-duplicate-properties', this);
}
initObservable(this);
const boundProperties = this[boundPropertiesSymbol];
bindProperties.forEach(propertyName => {
if (boundProperties.has(propertyName)) {
initObservable(this);
const properties = this[observablePropertiesSymbol];
if ((name in this) && !properties.has(name)) {
/**
* Cannot bind the same property more than once.
* Cannot override an existing property.
*
* @error observable-bind-rebind
* This error is thrown when trying to {@link ~Observable#set set} a property with
* a name of an already existing property. For example:
*
* let observable = new Model();
* observable.property = 1;
* observable.set( 'property', 2 ); // throws
*
* observable.set( 'property', 1 );
* observable.set( 'property', 2 ); // ok, because this is an existing property.
*
* @error observable-set-cannot-override
*/
throw new CKEditorError('observable-bind-rebind', this);
throw new CKEditorError('observable-set-cannot-override', this);
}
});
const bindings = new Map();
// @typedef {Object} Binding
// @property {Array} property Property which is bound.
// @property {Array} to Array of observable–property components of the binding (`{ observable: ..., property: .. }`).
// @property {Array} callback A function which processes `to` components.
bindProperties.forEach(a => {
const binding = { property: a, to: [] };
boundProperties.set(a, binding);
bindings.set(a, binding);
});
// @typedef {Object} BindChain
// @property {Function} to See {@link ~ObservableMixin#_bindTo}.
// @property {Function} toMany See {@link ~ObservableMixin#_bindToMany}.
// @property {module:utils/observablemixin~Observable} _observable The observable which initializes the binding.
// @property {Array} _bindProperties Array of `_observable` properties to be bound.
// @property {Array} _to Array of `to()` observable–properties (`{ observable: toObservable, properties: ...toProperties }`).
// @property {Map} _bindings Stores bindings to be kept in
// {@link ~ObservableMixin#_boundProperties}/{@link ~ObservableMixin#_boundObservables}
// initiated in this binding chain.
return {
to: bindTo,
toMany: bindToMany,
_observable: this,
_bindProperties: bindProperties,
_to: [],
_bindings: bindings
};
},
/**
* @inheritDoc
*/
unbind(...unbindProperties) {
// Nothing to do here if not inited yet.
if (!(this[observablePropertiesSymbol])) {
return;
Object.defineProperty(this, name, {
enumerable: true,
configurable: true,
get() {
return properties.get(name);
},
set(value) {
const oldValue = properties.get(name);
// Fire `set` event before the new value will be set to make it possible
// to override observable property without affecting `change` event.
// See https://github.com/ckeditor/ckeditor5-utils/issues/171.
let newValue = this.fire(`set:${name}`, name, value, oldValue);
if (newValue === undefined) {
newValue = value;
}
// Allow undefined as an initial value like A.define( 'x', undefined ) (#132).
// Note: When properties map has no such own property, then its value is undefined.
if (oldValue !== newValue || !properties.has(name)) {
properties.set(name, newValue);
this.fire(`change:${name}`, name, newValue, oldValue);
}
}
});
this[name] = value;
}
const boundProperties = this[boundPropertiesSymbol];
const boundObservables = this[boundObservablesSymbol];
if (unbindProperties.length) {
if (!isStringArray(unbindProperties)) {
bind(...bindProperties) {
if (!bindProperties.length || !isStringArray(bindProperties)) {
/**
* Properties must be strings.
* All properties must be strings.
*
* @error observable-unbind-wrong-properties
* @error observable-bind-wrong-properties
*/
throw new CKEditorError('observable-unbind-wrong-properties', this);
throw new CKEditorError('observable-bind-wrong-properties', this);
}
unbindProperties.forEach(propertyName => {
const binding = boundProperties.get(propertyName);
// Nothing to do if the binding is not defined
if (!binding) {
return;
if ((new Set(bindProperties)).size !== bindProperties.length) {
/**
* Properties must be unique.
*
* @error observable-bind-duplicate-properties
*/
throw new CKEditorError('observable-bind-duplicate-properties', this);
}
initObservable(this);
const boundProperties = this[boundPropertiesSymbol];
bindProperties.forEach(propertyName => {
if (boundProperties.has(propertyName)) {
/**
* Cannot bind the same property more than once.
*
* @error observable-bind-rebind
*/
throw new CKEditorError('observable-bind-rebind', this);
}
binding.to.forEach(([toObservable, toProperty]) => {
const toProperties = boundObservables.get(toObservable);
const toPropertyBindings = toProperties[toProperty];
toPropertyBindings.delete(binding);
if (!toPropertyBindings.size) {
delete toProperties[toProperty];
});
const bindings = new Map();
// @typedef {Object} Binding
// @property {Array} property Property which is bound.
// @property {Array} to Array of observable–property components of the binding (`{ observable: ..., property: .. }`).
// @property {Array} callback A function which processes `to` components.
bindProperties.forEach(a => {
const binding = { property: a, to: [] };
boundProperties.set(a, binding);
bindings.set(a, binding);
});
// @typedef {Object} BindChain
// @property {Function} to See {@link ~ObservableMixin#_bindTo}.
// @property {Function} toMany See {@link ~ObservableMixin#_bindToMany}.
// @property {module:utils/observablemixin~Observable} _observable The observable which initializes the binding.
// @property {Array} _bindProperties Array of `_observable` properties to be bound.
// @property {Array} _to Array of `to()` observable–properties (`{ observable: toObservable, properties: ...toProperties }`).
// @property {Map} _bindings Stores bindings to be kept in
// {@link ~ObservableMixin#_boundProperties}/{@link ~ObservableMixin#_boundObservables}
// initiated in this binding chain.
return {
to: bindTo,
toMany: bindToMany,
_observable: this,
_bindProperties: bindProperties,
_to: [],
_bindings: bindings
};
}
unbind(...unbindProperties) {
// Nothing to do here if not inited yet.
if (!(this[observablePropertiesSymbol])) {
return;
}
const boundProperties = this[boundPropertiesSymbol];
const boundObservables = this[boundObservablesSymbol];
if (unbindProperties.length) {
if (!isStringArray(unbindProperties)) {
/**
* Properties must be strings.
*
* @error observable-unbind-wrong-properties
*/
throw new CKEditorError('observable-unbind-wrong-properties', this);
}
unbindProperties.forEach(propertyName => {
const binding = boundProperties.get(propertyName);
// Nothing to do if the binding is not defined
if (!binding) {
return;
}
if (!Object.keys(toProperties).length) {
boundObservables.delete(toObservable);
this.stopListening(toObservable, 'change');
}
binding.to.forEach(([toObservable, toProperty]) => {
const toProperties = boundObservables.get(toObservable);
const toPropertyBindings = toProperties[toProperty];
toPropertyBindings.delete(binding);
if (!toPropertyBindings.size) {
delete toProperties[toProperty];
}
if (!Object.keys(toProperties).length) {
boundObservables.delete(toObservable);
this.stopListening(toObservable, 'change');
}
});
boundProperties.delete(propertyName);
});
boundProperties.delete(propertyName);
});
}
else {
boundObservables.forEach((bindings, boundObservable) => {
this.stopListening(boundObservable, 'change');
});
boundObservables.clear();
boundProperties.clear();
}
}
else {
boundObservables.forEach((bindings, boundObservable) => {
this.stopListening(boundObservable, 'change');
decorate(methodName) {
initObservable(this);
const originalMethod = this[methodName];
if (!originalMethod) {
/**
* Cannot decorate an undefined method.
*
* @error observablemixin-cannot-decorate-undefined
* @param {Object} object The object which method should be decorated.
* @param {String} methodName Name of the method which does not exist.
*/
throw new CKEditorError('observablemixin-cannot-decorate-undefined', this, { object: this, methodName });
}
this.on(methodName, (evt, args) => {
evt.return = originalMethod.apply(this, args);
});
boundObservables.clear();
boundProperties.clear();
this[methodName] = function (...args) {
return this.fire(methodName, args);
};
this[methodName][decoratedOriginal] = originalMethod;
if (!this[decoratedMethods]) {
this[decoratedMethods] = [];
}
this[decoratedMethods].push(methodName);
}
},
/**
* @inheritDoc
*/
decorate(methodName) {
initObservable(this);
const originalMethod = this[methodName];
if (!originalMethod) {
/**
* Cannot decorate an undefined method.
*
* @error observablemixin-cannot-decorate-undefined
* @param {Object} object The object which method should be decorated.
* @param {String} methodName Name of the method which does not exist.
*/
throw new CKEditorError('observablemixin-cannot-decorate-undefined', this, { object: this, methodName });
// Override the EmitterMixin stopListening method to be able to clean (and restore) decorated methods.
// This is needed in case of:
// 1. Have x.foo() decorated.
// 2. Call x.stopListening()
// 3. Call x.foo(). Problem: nothing happens (the original foo() method is not executed)
stopListening(emitter, event, callback) {
// Removing all listeners so let's clean the decorated methods to the original state.
if (!emitter && this[decoratedMethods]) {
for (const methodName of this[decoratedMethods]) {
this[methodName] = this[methodName][decoratedOriginal];
}
delete this[decoratedMethods];
}
Emitter.prototype.stopListening.call(this, emitter, event, callback);
}
this.on(methodName, (evt, args) => {
evt.return = originalMethod.apply(this, args);
});
this[methodName] = function (...args) {
return this.fire(methodName, args);
};
this[methodName][decoratedOriginal] = originalMethod;
if (!this[decoratedMethods]) {
this[decoratedMethods] = [];
}
this[decoratedMethods].push(methodName);
},
...EmitterMixin
};
// Override the EmitterMixin stopListening method to be able to clean (and restore) decorated methods.
// This is needed in case of:
// 1. Have x.foo() decorated.
// 2. Call x.stopListening()
// 3. Call x.foo(). Problem: nothing happens (the original foo() method is not executed)
ObservableMixin.stopListening = function (emitter, event, callback) {
// Removing all listeners so let's clean the decorated methods to the original state.
if (!emitter && this[decoratedMethods]) {
for (const methodName of this[decoratedMethods]) {
this[methodName] = this[methodName][decoratedOriginal];
}
delete this[decoratedMethods];
}
EmitterMixin.stopListening.call(this, emitter, event, callback);
};
export default ObservableMixin;
return Mixin;
}
export const Observable = ObservableMixin(Emitter);
// Backward compatibility with `mix`
([
'set', 'bind', 'unbind', 'decorate',
'on', 'once', 'off', 'listenTo',
'stopListening', 'fire', 'delegate', 'stopDelegating',
'_addEventListener', '_removeEventListener'
]).forEach(key => {
ObservableMixin[key] = Observable.prototype[key];
});
// Init symbol properties needed for the observable mechanism to work.

@@ -244,0 +243,0 @@ //

@@ -5,3 +5,2 @@ /**

*/
/* globals window */
/* eslint-disable no-var */

@@ -12,5 +11,6 @@ /**

import CKEditorError from './ckeditorerror';
import global from './dom/global';
/* istanbul ignore else */
if (!window.CKEDITOR_TRANSLATIONS) {
window.CKEDITOR_TRANSLATIONS = {};
if (!global.window.CKEDITOR_TRANSLATIONS) {
global.window.CKEDITOR_TRANSLATIONS = {};
}

@@ -69,11 +69,11 @@ /**

* function addTranslations( language, translations, getPluralForm ) {
* if ( !window.CKEDITOR_TRANSLATIONS ) {
* window.CKEDITOR_TRANSLATIONS = {};
* if ( !global.window.CKEDITOR_TRANSLATIONS ) {
* global.window.CKEDITOR_TRANSLATIONS = {};
* }
* if ( !window.CKEDITOR_TRANSLATIONS[ language ] ) {
* window.CKEDITOR_TRANSLATIONS[ language ] = {};
* if ( !global.window.CKEDITOR_TRANSLATIONS[ language ] ) {
* global.window.CKEDITOR_TRANSLATIONS[ language ] = {};
* }
*
* const languageTranslations = window.CKEDITOR_TRANSLATIONS[ language ];
* const languageTranslations = global.window.CKEDITOR_TRANSLATIONS[ language ];
*

@@ -94,6 +94,6 @@ * languageTranslations.dictionary = languageTranslations.dictionary || {};

export function add(language, translations, getPluralForm) {
if (!window.CKEDITOR_TRANSLATIONS[language]) {
window.CKEDITOR_TRANSLATIONS[language] = {};
if (!global.window.CKEDITOR_TRANSLATIONS[language]) {
global.window.CKEDITOR_TRANSLATIONS[language] = {};
}
const languageTranslations = window.CKEDITOR_TRANSLATIONS[language];
const languageTranslations = global.window.CKEDITOR_TRANSLATIONS[language];
languageTranslations.dictionary = languageTranslations.dictionary || {};

@@ -149,3 +149,3 @@ languageTranslations.getPluralForm = getPluralForm || languageTranslations.getPluralForm;

// This can't be done in the `Locale` class, because the translations comes after the `Locale` class initialization.
language = Object.keys(window.CKEDITOR_TRANSLATIONS)[0];
language = Object.keys(global.window.CKEDITOR_TRANSLATIONS)[0];
}

@@ -160,4 +160,4 @@ const messageId = message.id || message.string;

}
const dictionary = window.CKEDITOR_TRANSLATIONS[language].dictionary;
const getPluralForm = window.CKEDITOR_TRANSLATIONS[language].getPluralForm || (n => n === 1 ? 0 : 1);
const dictionary = global.window.CKEDITOR_TRANSLATIONS[language].dictionary;
const getPluralForm = global.window.CKEDITOR_TRANSLATIONS[language].getPluralForm || (n => n === 1 ? 0 : 1);
const translation = dictionary[messageId];

@@ -177,11 +177,11 @@ if (typeof translation === 'string') {

export function _clear() {
window.CKEDITOR_TRANSLATIONS = {};
global.window.CKEDITOR_TRANSLATIONS = {};
}
// Checks whether the dictionary exists and translation in that dictionary exists.
function hasTranslation(language, messageId) {
return (!!window.CKEDITOR_TRANSLATIONS[language] &&
!!window.CKEDITOR_TRANSLATIONS[language].dictionary[messageId]);
return (!!global.window.CKEDITOR_TRANSLATIONS[language] &&
!!global.window.CKEDITOR_TRANSLATIONS[language].dictionary[messageId]);
}
function getNumberOfLanguages() {
return Object.keys(window.CKEDITOR_TRANSLATIONS).length;
return Object.keys(global.window.CKEDITOR_TRANSLATIONS).length;
}

@@ -10,3 +10,3 @@ /**

import CKEditorError from './ckeditorerror';
const version = '35.0.1';
const version = '35.1.0';
export default version;

@@ -13,0 +13,0 @@ /* istanbul ignore next */

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc