feat: add `onChange`, `onPointerDown`, `onPointerUp` api subs (#7154)
parent
9eb89f9960
commit
e7cc2337ea
@ -0,0 +1,47 @@
|
||||
type Subscriber<T extends any[]> = (...payload: T) => void;
|
||||
|
||||
export class Emitter<T extends any[] = []> {
|
||||
public subscribers: Subscriber<T>[] = [];
|
||||
public value: T | undefined;
|
||||
private updateOnChangeOnly: boolean;
|
||||
|
||||
constructor(opts?: { initialState?: T; updateOnChangeOnly?: boolean }) {
|
||||
this.updateOnChangeOnly = opts?.updateOnChangeOnly ?? false;
|
||||
this.value = opts?.initialState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches subscriber
|
||||
*
|
||||
* @returns unsubscribe function
|
||||
*/
|
||||
on(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
|
||||
const _handlers = handlers
|
||||
.flat()
|
||||
.filter((item) => typeof item === "function");
|
||||
|
||||
this.subscribers.push(..._handlers);
|
||||
|
||||
return () => this.off(_handlers);
|
||||
}
|
||||
|
||||
off(...handlers: Subscriber<T>[] | Subscriber<T>[][]) {
|
||||
const _handlers = handlers.flat();
|
||||
this.subscribers = this.subscribers.filter(
|
||||
(handler) => !_handlers.includes(handler),
|
||||
);
|
||||
}
|
||||
|
||||
trigger(...payload: T): any[] {
|
||||
if (this.updateOnChangeOnly && this.value === payload) {
|
||||
return [];
|
||||
}
|
||||
this.value = payload;
|
||||
return this.subscribers.map((handler) => handler(...payload));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.subscribers = [];
|
||||
this.value = undefined;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import { vi } from "vitest";
|
||||
import { Excalidraw } from "../../packages/excalidraw/index";
|
||||
import { ExcalidrawImperativeAPI } from "../../types";
|
||||
import { resolvablePromise } from "../../utils";
|
||||
import { render } from "../test-utils";
|
||||
import { Pointer } from "../helpers/ui";
|
||||
|
||||
describe("event callbacks", () => {
|
||||
const h = window.h;
|
||||
|
||||
let excalidrawAPI: ExcalidrawImperativeAPI;
|
||||
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
beforeEach(async () => {
|
||||
const excalidrawAPIPromise = resolvablePromise<ExcalidrawImperativeAPI>();
|
||||
await render(
|
||||
<Excalidraw ref={(api) => excalidrawAPIPromise.resolve(api as any)} />,
|
||||
);
|
||||
excalidrawAPI = await excalidrawAPIPromise;
|
||||
});
|
||||
|
||||
it("should trigger onChange on render", async () => {
|
||||
const onChange = vi.fn();
|
||||
|
||||
const origBackgroundColor = h.state.viewBackgroundColor;
|
||||
excalidrawAPI.onChange(onChange);
|
||||
excalidrawAPI.updateScene({ appState: { viewBackgroundColor: "red" } });
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
// elements
|
||||
[],
|
||||
// appState
|
||||
expect.objectContaining({
|
||||
viewBackgroundColor: "red",
|
||||
}),
|
||||
// files
|
||||
{},
|
||||
);
|
||||
expect(onChange.mock.lastCall[1].viewBackgroundColor).not.toBe(
|
||||
origBackgroundColor,
|
||||
);
|
||||
});
|
||||
|
||||
it("should trigger onPointerDown/onPointerUp on canvas pointerDown/pointerUp", async () => {
|
||||
const onPointerDown = vi.fn();
|
||||
const onPointerUp = vi.fn();
|
||||
|
||||
excalidrawAPI.onPointerDown(onPointerDown);
|
||||
excalidrawAPI.onPointerUp(onPointerUp);
|
||||
|
||||
mouse.downAt(100);
|
||||
expect(onPointerDown).toHaveBeenCalledTimes(1);
|
||||
expect(onPointerUp).not.toHaveBeenCalled();
|
||||
mouse.up();
|
||||
expect(onPointerDown).toHaveBeenCalledTimes(1);
|
||||
expect(onPointerUp).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue