fix: cursor being leaked outside of canvas (#3161)
This commit is contained in:
parent
f295ba98c5
commit
c77c9ce65a
@ -18,7 +18,7 @@ import { isBindingElement } from "../element/typeChecks";
|
|||||||
|
|
||||||
export const actionFinalize = register({
|
export const actionFinalize = register({
|
||||||
name: "finalize",
|
name: "finalize",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState, _, { canvas }) => {
|
||||||
if (appState.editingLinearElement) {
|
if (appState.editingLinearElement) {
|
||||||
const {
|
const {
|
||||||
elementId,
|
elementId,
|
||||||
@ -126,7 +126,7 @@ export const actionFinalize = register({
|
|||||||
(!appState.elementLocked && appState.elementType !== "draw") ||
|
(!appState.elementLocked && appState.elementType !== "draw") ||
|
||||||
!multiPointElement
|
!multiPointElement
|
||||||
) {
|
) {
|
||||||
resetCursor();
|
resetCursor(canvas);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
elements: newElements,
|
elements: newElements,
|
||||||
|
@ -151,10 +151,12 @@ const LIBRARY_ICON = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ShapesSwitcher = ({
|
export const ShapesSwitcher = ({
|
||||||
|
canvas,
|
||||||
elementType,
|
elementType,
|
||||||
setAppState,
|
setAppState,
|
||||||
isLibraryOpen,
|
isLibraryOpen,
|
||||||
}: {
|
}: {
|
||||||
|
canvas: HTMLCanvasElement | null;
|
||||||
elementType: ExcalidrawElement["type"];
|
elementType: ExcalidrawElement["type"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
isLibraryOpen: boolean;
|
isLibraryOpen: boolean;
|
||||||
@ -185,7 +187,7 @@ export const ShapesSwitcher = ({
|
|||||||
multiElement: null,
|
multiElement: null,
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
});
|
});
|
||||||
setCursorForShape(value);
|
setCursorForShape(canvas, value);
|
||||||
setAppState({});
|
setAppState({});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -172,6 +172,7 @@ import {
|
|||||||
ResolvablePromise,
|
ResolvablePromise,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
|
setCursor,
|
||||||
setCursorForShape,
|
setCursorForShape,
|
||||||
tupleToCoors,
|
tupleToCoors,
|
||||||
viewportCoordsToSceneCoords,
|
viewportCoordsToSceneCoords,
|
||||||
@ -1440,16 +1441,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
|
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
|
||||||
isHoldingSpace = true;
|
isHoldingSpace = true;
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
|
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
|
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
|
||||||
if (event.key === KEYS.SPACE) {
|
if (event.key === KEYS.SPACE) {
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
} else {
|
} else {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
@ -1475,7 +1476,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
private selectShapeTool(elementType: AppState["elementType"]) {
|
private selectShapeTool(elementType: AppState["elementType"]) {
|
||||||
if (!isHoldingSpace) {
|
if (!isHoldingSpace) {
|
||||||
setCursorForShape(elementType);
|
setCursorForShape(this.canvas, elementType);
|
||||||
}
|
}
|
||||||
if (isToolIcon(document.activeElement)) {
|
if (isToolIcon(document.activeElement)) {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
@ -1601,7 +1602,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
editingElement: null,
|
editingElement: null,
|
||||||
});
|
});
|
||||||
if (this.state.elementLocked) {
|
if (this.state.elementLocked) {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
element,
|
element,
|
||||||
@ -1782,7 +1783,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
|
|
||||||
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||||
event,
|
event,
|
||||||
@ -1814,7 +1815,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
|
|
||||||
if (!event[KEYS.CTRL_OR_CMD]) {
|
if (!event[KEYS.CTRL_OR_CMD]) {
|
||||||
this.startTextEditing({
|
this.startTextEditing({
|
||||||
@ -1880,9 +1881,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
|
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
|
||||||
if (!this.state.draggingElement && !this.state.multiElement) {
|
if (!this.state.draggingElement && !this.state.multiElement) {
|
||||||
if (isOverScrollBar) {
|
if (isOverScrollBar) {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
} else {
|
} else {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1933,7 +1934,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const { points, lastCommittedPoint } = multiElement;
|
const { points, lastCommittedPoint } = multiElement;
|
||||||
const lastPoint = points[points.length - 1];
|
const lastPoint = points[points.length - 1];
|
||||||
|
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
|
|
||||||
if (lastPoint === lastCommittedPoint) {
|
if (lastPoint === lastCommittedPoint) {
|
||||||
// if we haven't yet created a temp point and we're beyond commit-zone
|
// if we haven't yet created a temp point and we're beyond commit-zone
|
||||||
@ -1950,7 +1951,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
points: [...points, [scenePointerX - rx, scenePointerY - ry]],
|
points: [...points, [scenePointerX - rx, scenePointerY - ry]],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
// in this branch, we're inside the commit zone, and no uncommitted
|
// in this branch, we're inside the commit zone, and no uncommitted
|
||||||
// point exists. Thus do nothing (don't add/remove points).
|
// point exists. Thus do nothing (don't add/remove points).
|
||||||
}
|
}
|
||||||
@ -1964,13 +1965,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
lastCommittedPoint[1],
|
lastCommittedPoint[1],
|
||||||
) < LINE_CONFIRM_THRESHOLD
|
) < LINE_CONFIRM_THRESHOLD
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
points: points.slice(0, -1),
|
points: points.slice(0, -1),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (isPathALoop(points, this.state.zoom.value)) {
|
if (isPathALoop(points, this.state.zoom.value)) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
}
|
}
|
||||||
// update last uncommitted point
|
// update last uncommitted point
|
||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
@ -2013,8 +2014,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
elementWithTransformHandleType &&
|
elementWithTransformHandleType &&
|
||||||
elementWithTransformHandleType.transformHandleType
|
elementWithTransformHandleType.transformHandleType
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
setCursor(
|
||||||
elementWithTransformHandleType,
|
this.canvas,
|
||||||
|
getCursorForResizingElement(elementWithTransformHandleType),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2027,9 +2029,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
event.pointerType,
|
event.pointerType,
|
||||||
);
|
);
|
||||||
if (transformHandleType) {
|
if (transformHandleType) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
setCursor(
|
||||||
transformHandleType,
|
this.canvas,
|
||||||
});
|
getCursorForResizingElement({
|
||||||
|
transformHandleType,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2039,11 +2044,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
);
|
);
|
||||||
if (this.state.elementType === "text") {
|
if (this.state.elementType === "text") {
|
||||||
document.documentElement.style.cursor = isTextElement(hitElement)
|
setCursor(
|
||||||
? CURSOR_TYPE.TEXT
|
this.canvas,
|
||||||
: CURSOR_TYPE.CROSSHAIR;
|
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
|
||||||
|
);
|
||||||
} else if (isOverScrollBar) {
|
} else if (isOverScrollBar) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.AUTO;
|
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||||
} else if (
|
} else if (
|
||||||
hitElement ||
|
hitElement ||
|
||||||
this.isHittingCommonBoundingBoxOfSelectedElements(
|
this.isHittingCommonBoundingBoxOfSelectedElements(
|
||||||
@ -2051,9 +2057,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
selectedElements,
|
selectedElements,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.MOVE;
|
setCursor(this.canvas, CURSOR_TYPE.MOVE);
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.AUTO;
|
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2226,7 +2232,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
let nextPastePrevented = false;
|
let nextPastePrevented = false;
|
||||||
const isLinux = /Linux/.test(window.navigator.platform);
|
const isLinux = /Linux/.test(window.navigator.platform);
|
||||||
|
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
|
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||||
let { clientX: lastX, clientY: lastY } = event;
|
let { clientX: lastX, clientY: lastY } = event;
|
||||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||||
const deltaX = lastX - event.clientX;
|
const deltaX = lastX - event.clientX;
|
||||||
@ -2278,7 +2284,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
lastPointerUp = null;
|
lastPointerUp = null;
|
||||||
isPanning = false;
|
isPanning = false;
|
||||||
if (!isHoldingSpace) {
|
if (!isHoldingSpace) {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
@ -2394,7 +2400,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
const onPointerUp = withBatchedUpdates(() => {
|
const onPointerUp = withBatchedUpdates(() => {
|
||||||
isDraggingScrollBar = false;
|
isDraggingScrollBar = false;
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
lastPointerUp = null;
|
lastPointerUp = null;
|
||||||
this.setState({
|
this.setState({
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
@ -2457,9 +2463,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (pointerDownState.resize.handleType) {
|
if (pointerDownState.resize.handleType) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
setCursor(
|
||||||
transformHandleType: pointerDownState.resize.handleType,
|
this.canvas,
|
||||||
});
|
getCursorForResizingElement({
|
||||||
|
transformHandleType: pointerDownState.resize.handleType,
|
||||||
|
}),
|
||||||
|
);
|
||||||
pointerDownState.resize.isResizing = true;
|
pointerDownState.resize.isResizing = true;
|
||||||
pointerDownState.resize.offset = tupleToCoors(
|
pointerDownState.resize.offset = tupleToCoors(
|
||||||
getResizeOffsetXY(
|
getResizeOffsetXY(
|
||||||
@ -2624,7 +2633,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
insertAtParentCenter: !event.altKey,
|
insertAtParentCenter: !event.altKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
if (!this.state.elementLocked) {
|
if (!this.state.elementLocked) {
|
||||||
this.setState({
|
this.setState({
|
||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
@ -2681,7 +2690,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
|
lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
|
||||||
});
|
});
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
} else {
|
} else {
|
||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
@ -3216,7 +3225,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
this.setState({ suggestedBindings: [], startBoundElement: null });
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
||||||
if (!elementLocked && elementType !== "draw") {
|
if (!elementLocked && elementType !== "draw") {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
@ -3387,7 +3396,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!elementLocked && elementType !== "draw") {
|
if (!elementLocked && elementType !== "draw") {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
suggestedBindings: [],
|
suggestedBindings: [],
|
||||||
|
@ -516,6 +516,7 @@ const LayerUI = ({
|
|||||||
{heading}
|
{heading}
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
<ShapesSwitcher
|
<ShapesSwitcher
|
||||||
|
canvas={canvas}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
isLibraryOpen={appState.isLibraryOpen}
|
isLibraryOpen={appState.isLibraryOpen}
|
||||||
|
@ -57,6 +57,7 @@ export const MobileMenu = ({
|
|||||||
{heading}
|
{heading}
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
<ShapesSwitcher
|
<ShapesSwitcher
|
||||||
|
canvas={canvas}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
isLibraryOpen={appState.isLibraryOpen}
|
isLibraryOpen={appState.isLibraryOpen}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import { resetCursor } from "../utils";
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
interface TopErrorBoundaryState {
|
interface TopErrorBoundaryState {
|
||||||
@ -24,7 +23,6 @@ export class TopErrorBoundary extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: any) {
|
componentDidCatch(error: Error, errorInfo: any) {
|
||||||
resetCursor();
|
|
||||||
const _localStorage: any = {};
|
const _localStorage: any = {};
|
||||||
for (const [key, value] of Object.entries({ ...localStorage })) {
|
for (const [key, value] of Object.entries({ ...localStorage })) {
|
||||||
try {
|
try {
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
import { isLinearElement, isTextElement } from "./typeChecks";
|
import { isLinearElement, isTextElement } from "./typeChecks";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { getPerfectElementSize } from "./sizeHelpers";
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||||||
import { getCursorForResizingElement } from "./resizeTest";
|
|
||||||
import { measureText, getFontString } from "../utils";
|
import { measureText, getFontString } from "../utils";
|
||||||
import { updateBoundElements } from "./binding";
|
import { updateBoundElements } from "./binding";
|
||||||
import {
|
import {
|
||||||
@ -105,13 +104,6 @@ export const transformElements = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cursor
|
|
||||||
// FIXME it is not very nice to have this here
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
|
||||||
element,
|
|
||||||
transformHandleType,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (selectedElements.length > 1) {
|
} else if (selectedElements.length > 1) {
|
||||||
if (transformHandleType === "rotation") {
|
if (transformHandleType === "rotation") {
|
||||||
|
24
src/utils.ts
24
src/utils.ts
@ -160,15 +160,29 @@ export const removeSelection = () => {
|
|||||||
|
|
||||||
export const distance = (x: number, y: number) => Math.abs(x - y);
|
export const distance = (x: number, y: number) => Math.abs(x - y);
|
||||||
|
|
||||||
export const resetCursor = () => {
|
export const resetCursor = (canvas: HTMLCanvasElement | null) => {
|
||||||
document.documentElement.style.cursor = "";
|
if (canvas) {
|
||||||
|
canvas.style.cursor = "";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setCursorForShape = (shape: string) => {
|
export const setCursor = (canvas: HTMLCanvasElement | null, cursor: string) => {
|
||||||
|
if (canvas) {
|
||||||
|
canvas.style.cursor = cursor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setCursorForShape = (
|
||||||
|
canvas: HTMLCanvasElement | null,
|
||||||
|
shape: string,
|
||||||
|
) => {
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (shape === "selection") {
|
if (shape === "selection") {
|
||||||
resetCursor();
|
resetCursor(canvas);
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.CROSSHAIR;
|
canvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user