+ {actionManager.renderAction("eraser", { size: "small" })}
+
diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx
index 6a2d7b1a..73ed47e4 100644
--- a/src/components/MobileMenu.tsx
+++ b/src/components/MobileMenu.tsx
@@ -8,7 +8,7 @@ import { NonDeletedExcalidrawElement } from "../element/types";
import { FixedSideContainer } from "./FixedSideContainer";
import { Island } from "./Island";
import { HintViewer } from "./HintViewer";
-import { calculateScrollCenter } from "../scene";
+import { calculateScrollCenter, getSelectedElements } from "../scene";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import { Section } from "./Section";
import CollabButton from "./CollabButton";
@@ -113,6 +113,12 @@ export const MobileMenu = ({
};
const renderAppToolbar = () => {
+ // Render eraser conditionally in mobile
+ const showEraser =
+ !appState.viewModeEnabled &&
+ !appState.editingElement &&
+ getSelectedElements(elements, appState).length === 0;
+
if (viewModeEnabled) {
return (
{actionManager.renderAction("toggleCanvasMenu")}
{actionManager.renderAction("toggleEditMenu")}
+
{actionManager.renderAction("undo")}
{actionManager.renderAction("redo")}
+ {showEraser && actionManager.renderAction("eraser")}
+
{actionManager.renderAction(
appState.multiElement ? "finalize" : "duplicateSelection",
)}
diff --git a/src/components/icons.tsx b/src/components/icons.tsx
index cc371259..9e0ce7e8 100644
--- a/src/components/icons.tsx
+++ b/src/components/icons.tsx
@@ -934,3 +934,7 @@ export const editIcon = createIcon(
>,
{ width: 640, height: 512 },
);
+
+export const eraser = createIcon(
+
,
+);
diff --git a/src/css/styles.scss b/src/css/styles.scss
index 18f02b95..aef449a4 100644
--- a/src/css/styles.scss
+++ b/src/css/styles.scss
@@ -290,6 +290,16 @@
width: 100%;
box-sizing: border-box;
+
+ .eraser {
+ &.ToolIcon:hover {
+ --icon-fill-color: #fff;
+ --keybinding-color: #fff;
+ }
+ &.active {
+ background-color: var(--color-primary);
+ }
+ }
}
.App-toolbar-content {
@@ -467,7 +477,8 @@
font-family: var(--ui-font);
}
- .undo-redo-buttons {
+ .undo-redo-buttons,
+ .eraser-buttons {
display: grid;
grid-auto-flow: column;
gap: 0.4em;
diff --git a/src/data/restore.ts b/src/data/restore.ts
index b55bd952..c8e18c02 100644
--- a/src/data/restore.ts
+++ b/src/data/restore.ts
@@ -31,8 +31,8 @@ type RestoredAppState = Omit<
>;
export const AllowedExcalidrawElementTypes: Record<
- ExcalidrawElement["type"],
- true
+ AppState["elementType"],
+ boolean
> = {
selection: true,
text: true,
@@ -43,6 +43,7 @@ export const AllowedExcalidrawElementTypes: Record<
image: true,
arrow: true,
freedraw: true,
+ eraser: false,
};
export type RestoredDataState = {
diff --git a/src/element/dragElements.ts b/src/element/dragElements.ts
index b6c5383a..42990b51 100644
--- a/src/element/dragElements.ts
+++ b/src/element/dragElements.ts
@@ -1,4 +1,3 @@
-import { SHAPES } from "../shapes";
import { updateBoundElements } from "./binding";
import { getCommonBounds } from "./bounds";
import { mutateElement } from "./mutateElement";
@@ -93,7 +92,7 @@ export const getDragOffsetXY = (
export const dragNewElement = (
draggingElement: NonDeletedExcalidrawElement,
- elementType: typeof SHAPES[number]["value"],
+ elementType: AppState["elementType"],
originX: number,
originY: number,
x: number,
diff --git a/src/element/showSelectedShapeActions.ts b/src/element/showSelectedShapeActions.ts
index 54528981..7dce2628 100644
--- a/src/element/showSelectedShapeActions.ts
+++ b/src/element/showSelectedShapeActions.ts
@@ -10,5 +10,6 @@ export const showSelectedShapeActions = (
!appState.viewModeEnabled &&
(appState.editingElement ||
getSelectedElements(elements, appState).length ||
- appState.elementType !== "selection"),
+ (appState.elementType !== "selection" &&
+ appState.elementType !== "eraser")),
);
diff --git a/src/element/typeChecks.ts b/src/element/typeChecks.ts
index 8f5627bf..4776fd87 100644
--- a/src/element/typeChecks.ts
+++ b/src/element/typeChecks.ts
@@ -1,3 +1,4 @@
+import { AppState } from "../types";
import {
ExcalidrawElement,
ExcalidrawTextElement,
@@ -60,7 +61,7 @@ export const isLinearElement = (
};
export const isLinearElementType = (
- elementType: ExcalidrawElement["type"],
+ elementType: AppState["elementType"],
): boolean => {
return (
elementType === "arrow" || elementType === "line" // || elementType === "freedraw"
@@ -74,7 +75,7 @@ export const isBindingElement = (
};
export const isBindingElementType = (
- elementType: ExcalidrawElement["type"],
+ elementType: AppState["elementType"],
): boolean => {
return elementType === "arrow";
};
diff --git a/src/locales/en.json b/src/locales/en.json
index e7b2e572..2ce8ea6b 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -196,7 +196,8 @@
"library": "Library",
"lock": "Keep selected tool active after drawing",
"penMode": "Prevent pinch-zoom and accept freedraw input only from pen",
- "link": "Add/ Update link for a selected shape"
+ "link": "Add/ Update link for a selected shape",
+ "eraser": "Eraser"
},
"headings": {
"canvasActions": "Canvas actions",
@@ -221,7 +222,8 @@
"placeImage": "Click to place the image, or click and drag to set its size manually",
"publishLibrary": "Publish your own library",
"bindTextToElement": "Press enter to add text",
- "deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging"
+ "deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
+ "eraserRevert": "Hold Alt to revert the elements marked for deletion"
},
"canvasError": {
"cannotShowPreview": "Cannot show preview",
diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap
index 2b4eee9e..fc8f16e9 100644
--- a/src/tests/__snapshots__/regressionTests.test.tsx.snap
+++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap
@@ -16860,7 +16860,7 @@ Object {
exports[`regression tests two-finger scroll works: [end of test] number of elements 1`] = `0`;
-exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `12`;
+exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `16`;
exports[`regression tests undo/redo drawing an element: [end of test] appState 1`] = `
Object {
@@ -17592,4 +17592,4 @@ Object {
exports[`regression tests zoom hotkeys: [end of test] number of elements 1`] = `0`;
-exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `6`;
\ No newline at end of file
+exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `6`;
diff --git a/src/types.ts b/src/types.ts
index 376cd615..71c2b53e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -77,7 +77,7 @@ export type AppState = {
// (e.g. text element when typing into the input)
editingElement: NonDeletedExcalidrawElement | null;
editingLinearElement: LinearElementEditor | null;
- elementType: typeof SHAPES[number]["value"];
+ elementType: typeof SHAPES[number]["value"] | "eraser";
elementLocked: boolean;
penMode: boolean;
penDetected: boolean;
@@ -384,6 +384,7 @@ export type PointerDownState = Readonly<{
boxSelection: {
hasOccurred: boolean;
};
+ elementIdsToErase: { [key: ExcalidrawElement["id"]]: boolean };
}>;
export type ExcalidrawImperativeAPI = {
diff --git a/src/utils.ts b/src/utils.ts
index 3d3cecd5..6c5e735d 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -224,6 +224,9 @@ export const setCursorForShape = (
}
if (shape === "selection") {
resetCursor(canvas);
+ } else if (shape === "eraser") {
+ resetCursor(canvas);
+
// do nothing if image tool is selected which suggests there's
// a image-preview set as the cursor
} else if (shape !== "image") {