editor.js/src/components/utils/events.ts
Peter Savchenko b7b00fd060
chore(onChange): bugfix, batching, improvements of ModificationsObserver (#2349)
* block onchange stash

* improve block filtration

* update tool root

* chore(block): remove willSelect und willUnselect

* onchange events batching

* get rid of CustomEvent extension, create custom event map instead

* improve types of EventsDispatcher

* fix tests

* custom sinon + chai matchers

* improve tests, add mutex for fake cursor

* add test for fake-cursor mutex

* test for batch filtering

* fix caret setting by enter press at the end of the block

* test for detectToolRootChange

* remove resolved todos

* changelog added

* fix tests

* Update CHANGELOG.md

* rename FakeCursorAboutToBeSet -> FakeCursorAboutToBeToggled

* update didMutated statements

* move inputs cache clearing to a separate method

* rm Record inheritance from Event maps

* add type alisases

* rename isElementContainsFakeCursor ->  isFakeCursorInsideContainer

* improve code style
2023-05-12 20:50:48 +03:00

113 lines
3 KiB
TypeScript

import { isEmpty } from '../utils';
/**
* Event Dispatcher event listener
*/
type Listener<Data> = (data?: Data) => void;
/**
* Mapped type with subscriptions list
*
* event name -> array of callbacks
*/
type Subscriptions<EventMap> = {
[Key in keyof EventMap]: Listener<EventMap[Key]>[];
};
/**
* Provides methods for working with Event Bus:
* - {Function} on - appends subscriber to the event. If event doesn't exist - creates new one
* - {Function} emit - fires all subscribers with data
* - {Function off - unsubscribes callback
*/
export default class EventsDispatcher<EventMap> {
/**
* All subscribers grouped by event name
* Object with events` names as key and array of callback functions as value
*/
private subscribers = <Subscriptions<EventMap>>{};
/**
* Subscribe any event on callback
*
* @param eventName - event name
* @param callback - subscriber
*/
public on<Name extends keyof EventMap>(eventName: Name, callback: Listener<EventMap[Name]>): void {
if (!(eventName in this.subscribers)) {
this.subscribers[eventName] = [];
}
// group by events
this.subscribers[eventName].push(callback);
}
/**
* Subscribe any event on callback. Callback will be called once and be removed from subscribers array after call.
*
* @param eventName - event name
* @param callback - subscriber
*/
public once<Name extends keyof EventMap>(eventName: Name, callback: Listener<EventMap[Name]>): void {
if (!(eventName in this.subscribers)) {
this.subscribers[eventName] = [];
}
const wrappedCallback = (data: EventMap[typeof eventName]): void => {
const result = callback(data);
const indexOfHandler = this.subscribers[eventName].indexOf(wrappedCallback);
if (indexOfHandler !== -1) {
this.subscribers[eventName].splice(indexOfHandler, 1);
}
return result;
};
// group by events
this.subscribers[eventName].push(wrappedCallback);
}
/**
* Emit callbacks with passed data
*
* @param eventName - event name
* @param data - subscribers get this data when they were fired
*/
public emit<Name extends keyof EventMap>(eventName: Name, data?: EventMap[Name]): void {
if (isEmpty(this.subscribers) || !this.subscribers[eventName]) {
return;
}
this.subscribers[eventName].reduce((previousData, currentHandler) => {
const newData = currentHandler(previousData);
return newData !== undefined ? newData : previousData;
}, data);
}
/**
* Unsubscribe callback from event
*
* @param eventName - event name
* @param callback - event handler
*/
public off<Name extends keyof EventMap>(eventName: Name, callback: Listener<EventMap[Name]>): void {
for (let i = 0; i < this.subscribers[eventName].length; i++) {
if (this.subscribers[eventName][i] === callback) {
delete this.subscribers[eventName][i];
break;
}
}
}
/**
* Destroyer
* clears subscribers list
*/
public destroy(): void {
this.subscribers = null;
}
}