fix(editor): include custom tools in pointer capture (#11826)

This commit is contained in:
David Luzar
2026-08-04 19:39:21 +02:00
committed by GitHub
parent ab0255f21e
commit 1da120e914
2 changed files with 53 additions and 5 deletions
+9 -5
View File
@@ -1015,10 +1015,11 @@ class App extends React.Component<AppProps, AppState> {
/**
* Whether the active tool captures the primary pointer instead of the
* view-mode drag-to-pan — the laser always does; while non-interactive,
* any tool allowed via `interaction.enabled.tools` does. (Editing tools
* capture the pointer trivially since view mode implies they're not
* active; this predicate only matters where view-mode gates apply.)
* view-mode drag-to-pan — the laser and host-implemented custom tools do;
* while non-interactive, any tool allowed via
* `interaction.enabled.tools` does. (Editing tools capture the pointer
* trivially since view mode implies they're not active; this predicate only
* matters where view-mode gates apply.)
*/
public isActiveToolPointerCapturing(): boolean {
if (!this.isInteractionEnabled()) {
@@ -1026,7 +1027,10 @@ class App extends React.Component<AppProps, AppState> {
// is inert — including the laser
return this.isToolSupported(this.state.activeTool.type);
}
return this.state.activeTool.type === "laser";
return (
this.state.activeTool.type === "laser" ||
this.state.activeTool.type === "custom"
);
}
/** Whether Excalidraw's full default UI is rendered. */
@@ -1,4 +1,5 @@
import React from "react";
import { vi } from "vitest";
import { CURSOR_TYPE, KEYS } from "@excalidraw/common";
@@ -8,6 +9,7 @@ import { API } from "./helpers/api";
import { Keyboard, Pointer, UI } from "./helpers/ui";
import { getTextEditor, updateTextEditor } from "./queries/dom";
import {
act,
fireEvent,
render,
GlobalTestState,
@@ -59,6 +61,48 @@ describe("view mode", () => {
});
});
it("dispatches pointer events to custom tools in view mode", () => {
const onPointerDown = vi.fn();
const onPointerUp = vi.fn();
GlobalTestState.renderResult.rerender(
<Excalidraw
handleKeyboardGlobally={true}
onPointerDown={onPointerDown}
onPointerUp={onPointerUp}
/>,
);
API.setAppState({ viewModeEnabled: true });
act(() => {
h.app.setActiveTool({
type: "custom",
customType: "comment",
locked: true,
});
});
expect(h.app.isInteractionEnabled()).toBe(true);
const { scrollX, scrollY } = h.state;
mouse.reset();
mouse.downAt(50, 50);
mouse.moveTo(100, 100);
mouse.upAt(100, 100);
expect(onPointerDown).toHaveBeenCalledTimes(1);
expect(onPointerDown.mock.calls[0][0]).toMatchObject({
type: "custom",
customType: "comment",
});
expect(onPointerUp).toHaveBeenCalledTimes(1);
expect(onPointerUp.mock.calls[0][0]).toMatchObject({
type: "custom",
customType: "comment",
});
expect([h.state.scrollX, h.state.scrollY]).toEqual([scrollX, scrollY]);
});
it("cursor should stay as grabbing type when hovering over canvas elements", async () => {
// create a rectangle, then hover over it – cursor should be
// move type for mouse and grab for touch & pen