From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001
From: kj_sh604
Date: Sun, 15 Mar 2026 16:19:35 -0400
Subject: refactor: packages/
---
packages/excalidraw/cursor.ts | 105 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
create mode 100644 packages/excalidraw/cursor.ts
(limited to 'packages/excalidraw/cursor.ts')
diff --git a/packages/excalidraw/cursor.ts b/packages/excalidraw/cursor.ts
new file mode 100644
index 0000000..759b7a4
--- /dev/null
+++ b/packages/excalidraw/cursor.ts
@@ -0,0 +1,105 @@
+import { CURSOR_TYPE, MIME_TYPES, THEME } from "./constants";
+import OpenColor from "open-color";
+import type { AppState, DataURL } from "./types";
+import { isHandToolActive, isEraserActive } from "./appState";
+
+const laserPointerCursorSVG_tag = ``,
+)}`;
+const laserPointerCursorDataURL_darkMode = `data:${
+ MIME_TYPES.svg
+},${encodeURIComponent(
+ `${laserPointerCursorSVG_tag}${laserPointerCursorBackgroundSVG}${laserPointerCursorIconSVG}`,
+)}`;
+
+export const resetCursor = (interactiveCanvas: HTMLCanvasElement | null) => {
+ if (interactiveCanvas) {
+ interactiveCanvas.style.cursor = "";
+ }
+};
+
+export const setCursor = (
+ interactiveCanvas: HTMLCanvasElement | null,
+ cursor: string,
+) => {
+ if (interactiveCanvas) {
+ interactiveCanvas.style.cursor = cursor;
+ }
+};
+
+let eraserCanvasCache: any;
+let previewDataURL: string;
+export const setEraserCursor = (
+ interactiveCanvas: HTMLCanvasElement | null,
+ theme: AppState["theme"],
+) => {
+ const cursorImageSizePx = 20;
+
+ const drawCanvas = () => {
+ const isDarkTheme = theme === THEME.DARK;
+ eraserCanvasCache = document.createElement("canvas");
+ eraserCanvasCache.theme = theme;
+ eraserCanvasCache.height = cursorImageSizePx;
+ eraserCanvasCache.width = cursorImageSizePx;
+ const context = eraserCanvasCache.getContext("2d")!;
+ context.lineWidth = 1;
+ context.beginPath();
+ context.arc(
+ eraserCanvasCache.width / 2,
+ eraserCanvasCache.height / 2,
+ 5,
+ 0,
+ 2 * Math.PI,
+ );
+ context.fillStyle = isDarkTheme ? OpenColor.black : OpenColor.white;
+ context.fill();
+ context.strokeStyle = isDarkTheme ? OpenColor.white : OpenColor.black;
+ context.stroke();
+ previewDataURL = eraserCanvasCache.toDataURL(MIME_TYPES.svg) as DataURL;
+ };
+ if (!eraserCanvasCache || eraserCanvasCache.theme !== theme) {
+ drawCanvas();
+ }
+
+ setCursor(
+ interactiveCanvas,
+ `url(${previewDataURL}) ${cursorImageSizePx / 2} ${
+ cursorImageSizePx / 2
+ }, auto`,
+ );
+};
+
+export const setCursorForShape = (
+ interactiveCanvas: HTMLCanvasElement | null,
+ appState: Pick,
+) => {
+ if (!interactiveCanvas) {
+ return;
+ }
+ if (appState.activeTool.type === "selection") {
+ resetCursor(interactiveCanvas);
+ } else if (isHandToolActive(appState)) {
+ interactiveCanvas.style.cursor = CURSOR_TYPE.GRAB;
+ } else if (isEraserActive(appState)) {
+ setEraserCursor(interactiveCanvas, appState.theme);
+ // do nothing if image tool is selected which suggests there's
+ // a image-preview set as the cursor
+ // Ignore custom type as well and let host decide
+ } else if (appState.activeTool.type === "laser") {
+ const url =
+ appState.theme === THEME.LIGHT
+ ? laserPointerCursorDataURL_lightMode
+ : laserPointerCursorDataURL_darkMode;
+ interactiveCanvas.style.cursor = `url(${url}), auto`;
+ } else if (!["image", "custom"].includes(appState.activeTool.type)) {
+ interactiveCanvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
+ } else if (appState.activeTool.type !== "image") {
+ interactiveCanvas.style.cursor = CURSOR_TYPE.AUTO;
+ }
+};
--
cgit v1.2.3