Skip to content

Commit

Permalink
fix: cursor being leaked outside of canvas (#3161)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelle committed Mar 3, 2021
1 parent f295ba9 commit c77c9ce
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 52 deletions.
4 changes: 2 additions & 2 deletions src/actions/actionFinalize.tsx
Expand Up @@ -18,7 +18,7 @@ import { isBindingElement } from "../element/typeChecks";

export const actionFinalize = register({
name: "finalize",
perform: (elements, appState) => {
perform: (elements, appState, _, { canvas }) => {
if (appState.editingLinearElement) {
const {
elementId,
Expand Down Expand Up @@ -126,7 +126,7 @@ export const actionFinalize = register({
(!appState.elementLocked && appState.elementType !== "draw") ||
!multiPointElement
) {
resetCursor();
resetCursor(canvas);
}
return {
elements: newElements,
Expand Down
4 changes: 3 additions & 1 deletion src/components/Actions.tsx
Expand Up @@ -151,10 +151,12 @@ const LIBRARY_ICON = (
);

export const ShapesSwitcher = ({
canvas,
elementType,
setAppState,
isLibraryOpen,
}: {
canvas: HTMLCanvasElement | null;
elementType: ExcalidrawElement["type"];
setAppState: React.Component<any, AppState>["setState"];
isLibraryOpen: boolean;
Expand Down Expand Up @@ -185,7 +187,7 @@ export const ShapesSwitcher = ({
multiElement: null,
selectedElementIds: {},
});
setCursorForShape(value);
setCursorForShape(canvas, value);
setAppState({});
}}
/>
Expand Down
77 changes: 43 additions & 34 deletions src/components/App.tsx
Expand Up @@ -172,6 +172,7 @@ import {
ResolvablePromise,
resolvablePromise,
sceneCoordsToViewportCoords,
setCursor,
setCursorForShape,
tupleToCoors,
viewportCoordsToSceneCoords,
Expand Down Expand Up @@ -1440,16 +1441,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
isHoldingSpace = true;
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
}
});

private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
if (event.key === KEYS.SPACE) {
if (this.state.elementType === "selection") {
resetCursor();
resetCursor(this.canvas);
} else {
setCursorForShape(this.state.elementType);
setCursorForShape(this.canvas, this.state.elementType);
this.setState({
selectedElementIds: {},
selectedGroupIds: {},
Expand All @@ -1475,7 +1476,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {

private selectShapeTool(elementType: AppState["elementType"]) {
if (!isHoldingSpace) {
setCursorForShape(elementType);
setCursorForShape(this.canvas, elementType);
}
if (isToolIcon(document.activeElement)) {
document.activeElement.blur();
Expand Down Expand Up @@ -1601,7 +1602,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
editingElement: null,
});
if (this.state.elementLocked) {
setCursorForShape(this.state.elementType);
setCursorForShape(this.canvas, this.state.elementType);
}
}),
element,
Expand Down Expand Up @@ -1782,7 +1783,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return;
}

resetCursor();
resetCursor(this.canvas);

const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
event,
Expand Down Expand Up @@ -1814,7 +1815,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
}

resetCursor();
resetCursor(this.canvas);

if (!event[KEYS.CTRL_OR_CMD]) {
this.startTextEditing({
Expand Down Expand Up @@ -1880,9 +1881,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
if (!this.state.draggingElement && !this.state.multiElement) {
if (isOverScrollBar) {
resetCursor();
resetCursor(this.canvas);
} else {
setCursorForShape(this.state.elementType);
setCursorForShape(this.canvas, this.state.elementType);
}
}

Expand Down Expand Up @@ -1933,7 +1934,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const { points, lastCommittedPoint } = multiElement;
const lastPoint = points[points.length - 1];

setCursorForShape(this.state.elementType);
setCursorForShape(this.canvas, this.state.elementType);

if (lastPoint === lastCommittedPoint) {
// if we haven't yet created a temp point and we're beyond commit-zone
Expand All @@ -1950,7 +1951,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
points: [...points, [scenePointerX - rx, scenePointerY - ry]],
});
} 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
// point exists. Thus do nothing (don't add/remove points).
}
Expand All @@ -1964,13 +1965,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
lastCommittedPoint[1],
) < LINE_CONFIRM_THRESHOLD
) {
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
setCursor(this.canvas, CURSOR_TYPE.POINTER);
mutateElement(multiElement, {
points: points.slice(0, -1),
});
} else {
if (isPathALoop(points, this.state.zoom.value)) {
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
setCursor(this.canvas, CURSOR_TYPE.POINTER);
}
// update last uncommitted point
mutateElement(multiElement, {
Expand Down Expand Up @@ -2013,8 +2014,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
elementWithTransformHandleType &&
elementWithTransformHandleType.transformHandleType
) {
document.documentElement.style.cursor = getCursorForResizingElement(
elementWithTransformHandleType,
setCursor(
this.canvas,
getCursorForResizingElement(elementWithTransformHandleType),
);
return;
}
Expand All @@ -2027,9 +2029,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
event.pointerType,
);
if (transformHandleType) {
document.documentElement.style.cursor = getCursorForResizingElement({
transformHandleType,
});
setCursor(
this.canvas,
getCursorForResizingElement({
transformHandleType,
}),
);
return;
}
}
Expand All @@ -2039,21 +2044,22 @@ class App extends React.Component<ExcalidrawProps, AppState> {
scenePointer.y,
);
if (this.state.elementType === "text") {
document.documentElement.style.cursor = isTextElement(hitElement)
? CURSOR_TYPE.TEXT
: CURSOR_TYPE.CROSSHAIR;
setCursor(
this.canvas,
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
);
} else if (isOverScrollBar) {
document.documentElement.style.cursor = CURSOR_TYPE.AUTO;
setCursor(this.canvas, CURSOR_TYPE.AUTO);
} else if (
hitElement ||
this.isHittingCommonBoundingBoxOfSelectedElements(
scenePointer,
selectedElements,
)
) {
document.documentElement.style.cursor = CURSOR_TYPE.MOVE;
setCursor(this.canvas, CURSOR_TYPE.MOVE);
} else {
document.documentElement.style.cursor = CURSOR_TYPE.AUTO;
setCursor(this.canvas, CURSOR_TYPE.AUTO);
}
};

Expand Down Expand Up @@ -2226,7 +2232,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
let nextPastePrevented = false;
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;
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
const deltaX = lastX - event.clientX;
Expand Down Expand Up @@ -2278,7 +2284,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
lastPointerUp = null;
isPanning = false;
if (!isHoldingSpace) {
setCursorForShape(this.state.elementType);
setCursorForShape(this.canvas, this.state.elementType);
}
this.setState({
cursorButton: "up",
Expand Down Expand Up @@ -2394,7 +2400,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {

const onPointerUp = withBatchedUpdates(() => {
isDraggingScrollBar = false;
setCursorForShape(this.state.elementType);
setCursorForShape(this.canvas, this.state.elementType);
lastPointerUp = null;
this.setState({
cursorButton: "up",
Expand Down Expand Up @@ -2457,9 +2463,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
);
}
if (pointerDownState.resize.handleType) {
document.documentElement.style.cursor = getCursorForResizingElement({
transformHandleType: pointerDownState.resize.handleType,
});
setCursor(
this.canvas,
getCursorForResizingElement({
transformHandleType: pointerDownState.resize.handleType,
}),
);
pointerDownState.resize.isResizing = true;
pointerDownState.resize.offset = tupleToCoors(
getResizeOffsetXY(
Expand Down Expand Up @@ -2624,7 +2633,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
insertAtParentCenter: !event.altKey,
});

resetCursor();
resetCursor(this.canvas);
if (!this.state.elementLocked) {
this.setState({
elementType: "selection",
Expand Down Expand Up @@ -2681,7 +2690,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
mutateElement(multiElement, {
lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
});
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
setCursor(this.canvas, CURSOR_TYPE.POINTER);
} else {
const [gridX, gridY] = getGridPoint(
pointerDownState.origin.x,
Expand Down Expand Up @@ -3216,7 +3225,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
this.setState({ suggestedBindings: [], startBoundElement: null });
if (!elementLocked && elementType !== "draw") {
resetCursor();
resetCursor(this.canvas);
this.setState((prevState) => ({
draggingElement: null,
elementType: "selection",
Expand Down Expand Up @@ -3387,7 +3396,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}

if (!elementLocked && elementType !== "draw") {
resetCursor();
resetCursor(this.canvas);
this.setState({
draggingElement: null,
suggestedBindings: [],
Expand Down
1 change: 1 addition & 0 deletions src/components/LayerUI.tsx
Expand Up @@ -516,6 +516,7 @@ const LayerUI = ({
{heading}
<Stack.Row gap={1}>
<ShapesSwitcher
canvas={canvas}
elementType={appState.elementType}
setAppState={setAppState}
isLibraryOpen={appState.isLibraryOpen}
Expand Down
1 change: 1 addition & 0 deletions src/components/MobileMenu.tsx
Expand Up @@ -57,6 +57,7 @@ export const MobileMenu = ({
{heading}
<Stack.Row gap={1}>
<ShapesSwitcher
canvas={canvas}
elementType={appState.elementType}
setAppState={setAppState}
isLibraryOpen={appState.isLibraryOpen}
Expand Down
2 changes: 0 additions & 2 deletions src/components/TopErrorBoundary.tsx
@@ -1,6 +1,5 @@
import React from "react";
import * as Sentry from "@sentry/browser";
import { resetCursor } from "../utils";
import { t } from "../i18n";

interface TopErrorBoundaryState {
Expand All @@ -24,7 +23,6 @@ export class TopErrorBoundary extends React.Component<
}

componentDidCatch(error: Error, errorInfo: any) {
resetCursor();
const _localStorage: any = {};
for (const [key, value] of Object.entries({ ...localStorage })) {
try {
Expand Down
8 changes: 0 additions & 8 deletions src/element/resizeElements.ts
Expand Up @@ -21,7 +21,6 @@ import {
import { isLinearElement, isTextElement } from "./typeChecks";
import { mutateElement } from "./mutateElement";
import { getPerfectElementSize } from "./sizeHelpers";
import { getCursorForResizingElement } from "./resizeTest";
import { measureText, getFontString } from "../utils";
import { updateBoundElements } from "./binding";
import {
Expand Down Expand Up @@ -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;
} else if (selectedElements.length > 1) {
if (transformHandleType === "rotation") {
Expand Down
24 changes: 19 additions & 5 deletions src/utils.ts
Expand Up @@ -160,15 +160,29 @@ export const removeSelection = () => {

export const distance = (x: number, y: number) => Math.abs(x - y);

export const resetCursor = () => {
document.documentElement.style.cursor = "";
export const resetCursor = (canvas: HTMLCanvasElement | null) => {
if (canvas) {
canvas.style.cursor = "";
}
};

export const setCursor = (canvas: HTMLCanvasElement | null, cursor: string) => {
if (canvas) {
canvas.style.cursor = cursor;
}
};

export const setCursorForShape = (shape: string) => {
export const setCursorForShape = (
canvas: HTMLCanvasElement | null,
shape: string,
) => {
if (!canvas) {
return;
}
if (shape === "selection") {
resetCursor();
resetCursor(canvas);
} else {
document.documentElement.style.cursor = CURSOR_TYPE.CROSSHAIR;
canvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
}
};

Expand Down

1 comment on commit c77c9ce

@vercel
Copy link

@vercel vercel bot commented on c77c9ce Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.