refactor: editor events sub/unsub refactor (#7483)
This commit is contained in:
parent
5f40a4cad4
commit
c72e853c85
@ -269,6 +269,7 @@ import {
|
|||||||
isTestEnv,
|
isTestEnv,
|
||||||
easeOut,
|
easeOut,
|
||||||
updateStable,
|
updateStable,
|
||||||
|
addEventListener,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
createSrcDoc,
|
createSrcDoc,
|
||||||
@ -559,6 +560,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
[scrollX: number, scrollY: number, zoom: AppState["zoom"]]
|
[scrollX: number, scrollY: number, zoom: AppState["zoom"]]
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
onRemoveEventListenersEmitter = new Emitter<[]>();
|
||||||
|
|
||||||
constructor(props: AppProps) {
|
constructor(props: AppProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const defaultAppState = getDefaultAppState();
|
const defaultAppState = getDefaultAppState();
|
||||||
@ -2390,63 +2393,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.setState({});
|
this.setState({});
|
||||||
});
|
});
|
||||||
|
|
||||||
private removeEventListeners() {
|
|
||||||
document.removeEventListener(EVENT.POINTER_UP, this.removePointer);
|
|
||||||
document.removeEventListener(EVENT.COPY, this.onCopy);
|
|
||||||
document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
|
||||||
document.removeEventListener(EVENT.CUT, this.onCut);
|
|
||||||
this.excalidrawContainerRef.current?.removeEventListener(
|
|
||||||
EVENT.WHEEL,
|
|
||||||
this.onWheel,
|
|
||||||
);
|
|
||||||
this.nearestScrollableContainer?.removeEventListener(
|
|
||||||
EVENT.SCROLL,
|
|
||||||
this.onScroll,
|
|
||||||
);
|
|
||||||
document.removeEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
|
|
||||||
document.removeEventListener(
|
|
||||||
EVENT.MOUSE_MOVE,
|
|
||||||
this.updateCurrentCursorPosition,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
document.removeEventListener(EVENT.KEYUP, this.onKeyUp);
|
|
||||||
window.removeEventListener(EVENT.RESIZE, this.onResize, false);
|
|
||||||
window.removeEventListener(EVENT.UNLOAD, this.onUnload, false);
|
|
||||||
window.removeEventListener(EVENT.BLUR, this.onBlur, false);
|
|
||||||
this.excalidrawContainerRef.current?.removeEventListener(
|
|
||||||
EVENT.DRAG_OVER,
|
|
||||||
this.disableEvent,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
this.excalidrawContainerRef.current?.removeEventListener(
|
|
||||||
EVENT.DROP,
|
|
||||||
this.disableEvent,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
document.removeEventListener(
|
|
||||||
EVENT.GESTURE_START,
|
|
||||||
this.onGestureStart as any,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
document.removeEventListener(
|
|
||||||
EVENT.GESTURE_CHANGE,
|
|
||||||
this.onGestureChange as any,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
document.removeEventListener(
|
|
||||||
EVENT.GESTURE_END,
|
|
||||||
this.onGestureEnd as any,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
document.removeEventListener(
|
|
||||||
EVENT.FULLSCREENCHANGE,
|
|
||||||
this.onFullscreenChange,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.removeEventListener(EVENT.MESSAGE, this.onWindowMessage, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** generally invoked only if fullscreen was invoked programmatically */
|
/** generally invoked only if fullscreen was invoked programmatically */
|
||||||
private onFullscreenChange = () => {
|
private onFullscreenChange = () => {
|
||||||
if (
|
if (
|
||||||
@ -2460,76 +2406,108 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private removeEventListeners() {
|
||||||
|
this.onRemoveEventListenersEmitter.trigger();
|
||||||
|
}
|
||||||
|
|
||||||
private addEventListeners() {
|
private addEventListeners() {
|
||||||
|
// remove first as we can add event listeners multiple times
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
window.addEventListener(EVENT.MESSAGE, this.onWindowMessage, false);
|
|
||||||
document.addEventListener(EVENT.POINTER_UP, this.removePointer); // #3553
|
// -------------------------------------------------------------------------
|
||||||
document.addEventListener(EVENT.COPY, this.onCopy);
|
// view+edit mode listeners
|
||||||
this.excalidrawContainerRef.current?.addEventListener(
|
// -------------------------------------------------------------------------
|
||||||
EVENT.WHEEL,
|
|
||||||
this.onWheel,
|
|
||||||
{ passive: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.props.handleKeyboardGlobally) {
|
if (this.props.handleKeyboardGlobally) {
|
||||||
document.addEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
|
this.onRemoveEventListenersEmitter.once(
|
||||||
|
addEventListener(document, EVENT.KEYDOWN, this.onKeyDown, false),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
document.addEventListener(EVENT.KEYUP, this.onKeyUp, { passive: true });
|
|
||||||
document.addEventListener(
|
|
||||||
EVENT.MOUSE_MOVE,
|
|
||||||
this.updateCurrentCursorPosition,
|
|
||||||
);
|
|
||||||
// rerender text elements on font load to fix #637 && #1553
|
|
||||||
document.fonts?.addEventListener?.("loadingdone", (event) => {
|
|
||||||
const loadedFontFaces = (event as FontFaceSetLoadEvent).fontfaces;
|
|
||||||
this.fonts.onFontsLoaded(loadedFontFaces);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Safari-only desktop pinch zoom
|
this.onRemoveEventListenersEmitter.once(
|
||||||
document.addEventListener(
|
addEventListener(
|
||||||
EVENT.GESTURE_START,
|
this.excalidrawContainerRef.current,
|
||||||
this.onGestureStart as any,
|
EVENT.WHEEL,
|
||||||
false,
|
this.onWheel,
|
||||||
);
|
{ passive: false },
|
||||||
document.addEventListener(
|
),
|
||||||
EVENT.GESTURE_CHANGE,
|
addEventListener(window, EVENT.MESSAGE, this.onWindowMessage, false),
|
||||||
this.onGestureChange as any,
|
addEventListener(document, EVENT.POINTER_UP, this.removePointer), // #3553
|
||||||
false,
|
addEventListener(document, EVENT.COPY, this.onCopy),
|
||||||
);
|
addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }),
|
||||||
document.addEventListener(
|
addEventListener(
|
||||||
EVENT.GESTURE_END,
|
document,
|
||||||
this.onGestureEnd as any,
|
EVENT.MOUSE_MOVE,
|
||||||
false,
|
this.updateCurrentCursorPosition,
|
||||||
|
),
|
||||||
|
// rerender text elements on font load to fix #637 && #1553
|
||||||
|
addEventListener(document.fonts, "loadingdone", (event) => {
|
||||||
|
const loadedFontFaces = (event as FontFaceSetLoadEvent).fontfaces;
|
||||||
|
this.fonts.onFontsLoaded(loadedFontFaces);
|
||||||
|
}),
|
||||||
|
// Safari-only desktop pinch zoom
|
||||||
|
addEventListener(
|
||||||
|
document,
|
||||||
|
EVENT.GESTURE_START,
|
||||||
|
this.onGestureStart as any,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
addEventListener(
|
||||||
|
document,
|
||||||
|
EVENT.GESTURE_CHANGE,
|
||||||
|
this.onGestureChange as any,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
addEventListener(
|
||||||
|
document,
|
||||||
|
EVENT.GESTURE_END,
|
||||||
|
this.onGestureEnd as any,
|
||||||
|
false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.state.viewModeEnabled) {
|
if (this.state.viewModeEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener(EVENT.FULLSCREENCHANGE, this.onFullscreenChange);
|
// -------------------------------------------------------------------------
|
||||||
document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
// edit-mode listeners only
|
||||||
document.addEventListener(EVENT.CUT, this.onCut);
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
this.onRemoveEventListenersEmitter.once(
|
||||||
|
addEventListener(
|
||||||
|
document,
|
||||||
|
EVENT.FULLSCREENCHANGE,
|
||||||
|
this.onFullscreenChange,
|
||||||
|
),
|
||||||
|
addEventListener(document, EVENT.PASTE, this.pasteFromClipboard),
|
||||||
|
addEventListener(document, EVENT.CUT, this.onCut),
|
||||||
|
addEventListener(window, EVENT.RESIZE, this.onResize, false),
|
||||||
|
addEventListener(window, EVENT.UNLOAD, this.onUnload, false),
|
||||||
|
addEventListener(window, EVENT.BLUR, this.onBlur, false),
|
||||||
|
addEventListener(
|
||||||
|
this.excalidrawContainerRef.current,
|
||||||
|
EVENT.DRAG_OVER,
|
||||||
|
this.disableEvent,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
addEventListener(
|
||||||
|
this.excalidrawContainerRef.current,
|
||||||
|
EVENT.DROP,
|
||||||
|
this.disableEvent,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (this.props.detectScroll) {
|
if (this.props.detectScroll) {
|
||||||
this.nearestScrollableContainer = getNearestScrollableContainer(
|
this.onRemoveEventListenersEmitter.once(
|
||||||
this.excalidrawContainerRef.current!,
|
addEventListener(
|
||||||
);
|
getNearestScrollableContainer(this.excalidrawContainerRef.current!),
|
||||||
this.nearestScrollableContainer.addEventListener(
|
EVENT.SCROLL,
|
||||||
EVENT.SCROLL,
|
this.onScroll,
|
||||||
this.onScroll,
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
|
||||||
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
|
||||||
window.addEventListener(EVENT.BLUR, this.onBlur, false);
|
|
||||||
this.excalidrawContainerRef.current?.addEventListener(
|
|
||||||
EVENT.DRAG_OVER,
|
|
||||||
this.disableEvent,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
this.excalidrawContainerRef.current?.addEventListener(
|
|
||||||
EVENT.DROP,
|
|
||||||
this.disableEvent,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { UnsubscribeCallback } from "./types";
|
||||||
|
|
||||||
type Subscriber<T extends any[]> = (...payload: T) => void;
|
type Subscriber<T extends any[]> = (...payload: T) => void;
|
||||||
|
|
||||||
export class Emitter<T extends any[] = []> {
|
export class Emitter<T extends any[] = []> {
|
||||||
@ -15,7 +17,7 @@ export class Emitter<T extends any[] = []> {
|
|||||||
*
|
*
|
||||||
* @returns unsubscribe function
|
* @returns unsubscribe function
|
||||||
*/
|
*/
|
||||||
on(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
|
on(...handlers: Subscriber<T>[] | Subscriber<T>[][]): UnsubscribeCallback {
|
||||||
const _handlers = handlers
|
const _handlers = handlers
|
||||||
.flat()
|
.flat()
|
||||||
.filter((item) => typeof item === "function");
|
.filter((item) => typeof item === "function");
|
||||||
@ -25,6 +27,17 @@ export class Emitter<T extends any[] = []> {
|
|||||||
return () => this.off(_handlers);
|
return () => this.off(_handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
once(...handlers: Subscriber<T>[] | Subscriber<T>[][]): UnsubscribeCallback {
|
||||||
|
const _handlers = handlers
|
||||||
|
.flat()
|
||||||
|
.filter((item) => typeof item === "function");
|
||||||
|
|
||||||
|
_handlers.push(() => detach());
|
||||||
|
|
||||||
|
const detach = this.on(..._handlers);
|
||||||
|
return detach;
|
||||||
|
}
|
||||||
|
|
||||||
off(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
|
off(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
|
||||||
const _handlers = handlers.flat();
|
const _handlers = handlers.flat();
|
||||||
this.subscribers = this.subscribers.filter(
|
this.subscribers = this.subscribers.filter(
|
||||||
|
13
packages/excalidraw/global.d.ts
vendored
13
packages/excalidraw/global.d.ts
vendored
@ -1,16 +1,3 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
interface Document {
|
|
||||||
fonts?: {
|
|
||||||
ready?: Promise<void>;
|
|
||||||
check?: (font: string, text?: string) => boolean;
|
|
||||||
load?: (font: string, text?: string) => Promise<FontFace[]>;
|
|
||||||
addEventListener?(
|
|
||||||
type: "loading" | "loadingdone" | "loadingerror",
|
|
||||||
listener: (this: Document, ev: Event) => any,
|
|
||||||
): void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
ClipboardItem: any;
|
ClipboardItem: any;
|
||||||
__EXCALIDRAW_SHA__: string | undefined;
|
__EXCALIDRAW_SHA__: string | undefined;
|
||||||
|
@ -641,7 +641,7 @@ export type PointerDownState = Readonly<{
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type UnsubscribeCallback = () => void;
|
export type UnsubscribeCallback = () => void;
|
||||||
|
|
||||||
export type ExcalidrawImperativeAPI = {
|
export type ExcalidrawImperativeAPI = {
|
||||||
updateScene: InstanceType<typeof App>["updateScene"];
|
updateScene: InstanceType<typeof App>["updateScene"];
|
||||||
|
@ -7,7 +7,13 @@ import {
|
|||||||
WINDOWS_EMOJI_FALLBACK_FONT,
|
WINDOWS_EMOJI_FALLBACK_FONT,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
import { FontFamilyValues, FontString } from "./element/types";
|
import { FontFamilyValues, FontString } from "./element/types";
|
||||||
import { ActiveTool, AppState, ToolType, Zoom } from "./types";
|
import {
|
||||||
|
ActiveTool,
|
||||||
|
AppState,
|
||||||
|
ToolType,
|
||||||
|
UnsubscribeCallback,
|
||||||
|
Zoom,
|
||||||
|
} from "./types";
|
||||||
import { unstable_batchedUpdates } from "react-dom";
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
import { ResolutionType } from "./utility-types";
|
import { ResolutionType } from "./utility-types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -992,3 +998,76 @@ export const updateStable = <T extends any[] | Record<string, any>>(
|
|||||||
}
|
}
|
||||||
return nextValue;
|
return nextValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Window
|
||||||
|
export function addEventListener<K extends keyof WindowEventMap>(
|
||||||
|
target: Window & typeof globalThis,
|
||||||
|
type: K,
|
||||||
|
listener: (this: Window, ev: WindowEventMap[K]) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback;
|
||||||
|
export function addEventListener(
|
||||||
|
target: Window & typeof globalThis,
|
||||||
|
type: string,
|
||||||
|
listener: (this: Window, ev: Event) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback;
|
||||||
|
// Document
|
||||||
|
export function addEventListener<K extends keyof DocumentEventMap>(
|
||||||
|
target: Document,
|
||||||
|
type: K,
|
||||||
|
listener: (this: Document, ev: DocumentEventMap[K]) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback;
|
||||||
|
export function addEventListener(
|
||||||
|
target: Document,
|
||||||
|
type: string,
|
||||||
|
listener: (this: Document, ev: Event) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback;
|
||||||
|
// FontFaceSet (document.fonts)
|
||||||
|
export function addEventListener<K extends keyof FontFaceSetEventMap>(
|
||||||
|
target: FontFaceSet,
|
||||||
|
type: K,
|
||||||
|
listener: (this: FontFaceSet, ev: FontFaceSetEventMap[K]) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback;
|
||||||
|
// HTMLElement / mix
|
||||||
|
export function addEventListener<K extends keyof HTMLElementEventMap>(
|
||||||
|
target:
|
||||||
|
| Document
|
||||||
|
| (Window & typeof globalThis)
|
||||||
|
| HTMLElement
|
||||||
|
| undefined
|
||||||
|
| null
|
||||||
|
| false,
|
||||||
|
type: K,
|
||||||
|
listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback;
|
||||||
|
// implem
|
||||||
|
export function addEventListener(
|
||||||
|
/**
|
||||||
|
* allows for falsy values so you don't have to type check when adding
|
||||||
|
* event listeners to optional elements
|
||||||
|
*/
|
||||||
|
target:
|
||||||
|
| Document
|
||||||
|
| (Window & typeof globalThis)
|
||||||
|
| FontFaceSet
|
||||||
|
| HTMLElement
|
||||||
|
| undefined
|
||||||
|
| null
|
||||||
|
| false,
|
||||||
|
type: keyof WindowEventMap | keyof DocumentEventMap | string,
|
||||||
|
listener: (ev: Event) => any,
|
||||||
|
options?: boolean | AddEventListenerOptions,
|
||||||
|
): UnsubscribeCallback {
|
||||||
|
if (!target) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
target?.addEventListener?.(type, listener, options);
|
||||||
|
return () => {
|
||||||
|
target?.removeEventListener?.(type, listener, options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user