-
- {actionManager.renderAction("toggleEditMenu")}
- {actionManager.renderAction(
- appState.multiElement ? "finalize" : "duplicateSelection",
- )}
- {actionManager.renderAction("deleteSelectedElements")}
-
- {actionManager.renderAction("undo")}
- {actionManager.renderAction("redo")}
-
+
+ {topLeftUI}
+ {topRightUI}
);
};
+ const renderToolbar = () => {
+ return (
+
+ );
+ };
+
return (
<>
{renderSidebars()}
- {!appState.viewModeEnabled &&
- appState.openDialog?.name !== "elementLinkSelector" &&
- renderToolbar()}
+ {/* welcome screen, bottom bar, and top bar all have the same z-index */}
+ {/* ordered in this reverse order so that top bar is on top */}
+
+ {renderWelcomeScreen && }
+
+
-
- {appState.openMenu === "shape" &&
- !appState.viewModeEnabled &&
- appState.openDialog?.name !== "elementLinkSelector" &&
- showSelectedShapeActions(appState, elements) ? (
-
- ) : null}
-
+
+
+
+ {!appState.viewModeEnabled &&
+ appState.openDialog?.name !== "elementLinkSelector" &&
+ renderToolbar()}
+ {appState.scrolledOutside &&
+ !appState.openMenu &&
+ !appState.openSidebar && (
+
+ )}
+
+
+ {renderAppTopBar()}
+
>
);
};
diff --git a/packages/excalidraw/components/MobileToolBar.scss b/packages/excalidraw/components/MobileToolBar.scss
new file mode 100644
index 0000000000..b936c70ebd
--- /dev/null
+++ b/packages/excalidraw/components/MobileToolBar.scss
@@ -0,0 +1,78 @@
+@import "open-color/open-color.scss";
+@import "../css/variables.module.scss";
+
+.excalidraw {
+ .mobile-toolbar {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: 0px;
+ gap: 4px;
+ border-radius: var(--space-factor);
+ overflow-x: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ justify-content: space-between;
+ }
+
+ .mobile-toolbar::-webkit-scrollbar {
+ display: none;
+ }
+
+ .mobile-toolbar .ToolIcon {
+ min-width: 2rem;
+ min-height: 2rem;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+
+ .ToolIcon__icon {
+ width: 2.25rem;
+ height: 2.25rem;
+
+ &:hover {
+ background-color: transparent;
+ }
+ }
+
+ &.active {
+ background: var(
+ --color-surface-primary-container,
+ var(--island-bg-color)
+ );
+ border-color: var(--button-active-border, var(--color-primary-darkest));
+ }
+
+ svg {
+ width: 1rem;
+ height: 1rem;
+ }
+ }
+
+ .mobile-toolbar .App-toolbar__extra-tools-dropdown {
+ min-width: 160px;
+ z-index: var(--zIndex-layerUI);
+ }
+
+ .mobile-toolbar-separator {
+ width: 1px;
+ height: 24px;
+ background: var(--default-border-color);
+ margin: 0 2px;
+ flex-shrink: 0;
+ }
+
+ .mobile-toolbar-undo {
+ display: flex;
+ align-items: center;
+ }
+
+ .mobile-toolbar-undo .ToolIcon {
+ min-width: 32px;
+ min-height: 32px;
+ width: 32px;
+ height: 32px;
+ }
+}
diff --git a/packages/excalidraw/components/MobileToolBar.tsx b/packages/excalidraw/components/MobileToolBar.tsx
new file mode 100644
index 0000000000..093cbd2630
--- /dev/null
+++ b/packages/excalidraw/components/MobileToolBar.tsx
@@ -0,0 +1,471 @@
+import { useState, useEffect, useRef } from "react";
+import clsx from "clsx";
+
+import { KEYS, capitalizeString } from "@excalidraw/common";
+
+import { trackEvent } from "../analytics";
+
+import { t } from "../i18n";
+
+import { isHandToolActive } from "../appState";
+
+import { useTunnels } from "../context/tunnels";
+
+import { HandButton } from "./HandButton";
+import { ToolButton } from "./ToolButton";
+import DropdownMenu from "./dropdownMenu/DropdownMenu";
+import { ToolPopover } from "./ToolPopover";
+
+import {
+ SelectionIcon,
+ FreedrawIcon,
+ EraserIcon,
+ RectangleIcon,
+ ArrowIcon,
+ extraToolsIcon,
+ DiamondIcon,
+ EllipseIcon,
+ LineIcon,
+ TextIcon,
+ ImageIcon,
+ frameToolIcon,
+ EmbedIcon,
+ laserPointerToolIcon,
+ LassoIcon,
+ mermaidLogoIcon,
+ MagicIcon,
+} from "./icons";
+
+import "./ToolIcon.scss";
+import "./MobileToolBar.scss";
+
+import type { AppClassProperties, ToolType, UIAppState } from "../types";
+
+const SHAPE_TOOLS = [
+ {
+ type: "rectangle",
+ icon: RectangleIcon,
+ title: capitalizeString(t("toolBar.rectangle")),
+ },
+ {
+ type: "diamond",
+ icon: DiamondIcon,
+ title: capitalizeString(t("toolBar.diamond")),
+ },
+ {
+ type: "ellipse",
+ icon: EllipseIcon,
+ title: capitalizeString(t("toolBar.ellipse")),
+ },
+] as const;
+
+const SELECTION_TOOLS = [
+ {
+ type: "selection",
+ icon: SelectionIcon,
+ title: capitalizeString(t("toolBar.selection")),
+ },
+ {
+ type: "lasso",
+ icon: LassoIcon,
+ title: capitalizeString(t("toolBar.lasso")),
+ },
+] as const;
+
+const LINEAR_ELEMENT_TOOLS = [
+ {
+ type: "arrow",
+ icon: ArrowIcon,
+ title: capitalizeString(t("toolBar.arrow")),
+ },
+ { type: "line", icon: LineIcon, title: capitalizeString(t("toolBar.line")) },
+] as const;
+
+type MobileToolBarProps = {
+ app: AppClassProperties;
+ onHandToolToggle: () => void;
+ setAppState: React.Component
["setState"];
+};
+
+export const MobileToolBar = ({
+ app,
+ onHandToolToggle,
+ setAppState,
+}: MobileToolBarProps) => {
+ const activeTool = app.state.activeTool;
+ const [isOtherShapesMenuOpen, setIsOtherShapesMenuOpen] = useState(false);
+ const [lastActiveGenericShape, setLastActiveGenericShape] = useState<
+ "rectangle" | "diamond" | "ellipse"
+ >("rectangle");
+ const [lastActiveLinearElement, setLastActiveLinearElement] = useState<
+ "arrow" | "line"
+ >("arrow");
+
+ const toolbarRef = useRef(null);
+
+ // keep lastActiveGenericShape in sync with active tool if user switches via other UI
+ useEffect(() => {
+ if (
+ activeTool.type === "rectangle" ||
+ activeTool.type === "diamond" ||
+ activeTool.type === "ellipse"
+ ) {
+ setLastActiveGenericShape(activeTool.type);
+ }
+ }, [activeTool.type]);
+
+ // keep lastActiveLinearElement in sync with active tool if user switches via other UI
+ useEffect(() => {
+ if (activeTool.type === "arrow" || activeTool.type === "line") {
+ setLastActiveLinearElement(activeTool.type);
+ }
+ }, [activeTool.type]);
+
+ const frameToolSelected = activeTool.type === "frame";
+ const laserToolSelected = activeTool.type === "laser";
+ const embeddableToolSelected = activeTool.type === "embeddable";
+
+ const { TTDDialogTriggerTunnel } = useTunnels();
+
+ const handleToolChange = (toolType: string, pointerType?: string) => {
+ if (app.state.activeTool.type !== toolType) {
+ trackEvent("toolbar", toolType, "ui");
+ }
+
+ if (toolType === "selection") {
+ if (app.state.activeTool.type === "selection") {
+ // Toggle selection tool behavior if needed
+ } else {
+ app.setActiveTool({ type: "selection" });
+ }
+ } else {
+ app.setActiveTool({ type: toolType as ToolType });
+ }
+ };
+
+ const toolbarWidth =
+ toolbarRef.current?.getBoundingClientRect()?.width ?? 0 - 8;
+ const WIDTH = 36;
+ const GAP = 4;
+
+ // hand, selection, freedraw, eraser, rectangle, arrow, others
+ const MIN_TOOLS = 7;
+ const MIN_WIDTH = MIN_TOOLS * WIDTH + (MIN_TOOLS - 1) * GAP;
+ const ADDITIONAL_WIDTH = WIDTH + GAP;
+
+ const showTextToolOutside = toolbarWidth >= MIN_WIDTH + 1 * ADDITIONAL_WIDTH;
+ const showImageToolOutside = toolbarWidth >= MIN_WIDTH + 2 * ADDITIONAL_WIDTH;
+ const showFrameToolOutside = toolbarWidth >= MIN_WIDTH + 3 * ADDITIONAL_WIDTH;
+
+ const extraTools = [
+ "text",
+ "frame",
+ "embeddable",
+ "laser",
+ "magicframe",
+ ].filter((tool) => {
+ if (showImageToolOutside && tool === "image") {
+ return false;
+ }
+ if (showFrameToolOutside && tool === "frame") {
+ return false;
+ }
+ return true;
+ });
+ const extraToolSelected = extraTools.includes(activeTool.type);
+ const extraIcon = extraToolSelected
+ ? activeTool.type === "frame"
+ ? frameToolIcon
+ : activeTool.type === "embeddable"
+ ? EmbedIcon
+ : activeTool.type === "laser"
+ ? laserPointerToolIcon
+ : activeTool.type === "text"
+ ? TextIcon
+ : activeTool.type === "magicframe"
+ ? MagicIcon
+ : extraToolsIcon
+ : extraToolsIcon;
+
+ return (
+
+ {/* Hand Tool */}
+
+
+ {/* Selection Tool */}
+
{
+ if (type === "selection" || type === "lasso") {
+ app.setActiveTool({ type });
+ setAppState({
+ preferredSelectionTool: { type, initialized: true },
+ });
+ }
+ }}
+ displayedOption={
+ SELECTION_TOOLS.find(
+ (tool) => tool.type === app.state.preferredSelectionTool.type,
+ ) || SELECTION_TOOLS[0]
+ }
+ />
+
+ {/* Free Draw */}
+ handleToolChange("freedraw")}
+ />
+
+ {/* Eraser */}
+ handleToolChange("eraser")}
+ />
+
+ {/* Rectangle */}
+ {
+ if (
+ type === "rectangle" ||
+ type === "diamond" ||
+ type === "ellipse"
+ ) {
+ setLastActiveGenericShape(type);
+ app.setActiveTool({ type });
+ }
+ }}
+ displayedOption={
+ SHAPE_TOOLS.find((tool) => tool.type === lastActiveGenericShape) ||
+ SHAPE_TOOLS[0]
+ }
+ />
+
+ {/* Arrow/Line */}
+ {
+ if (type === "arrow" || type === "line") {
+ setLastActiveLinearElement(type);
+ app.setActiveTool({ type });
+ }
+ }}
+ displayedOption={
+ LINEAR_ELEMENT_TOOLS.find(
+ (tool) => tool.type === lastActiveLinearElement,
+ ) || LINEAR_ELEMENT_TOOLS[0]
+ }
+ />
+
+ {/* Text Tool */}
+ {showTextToolOutside && (
+ handleToolChange("text")}
+ />
+ )}
+
+ {/* Image */}
+ {showImageToolOutside && (
+ handleToolChange("image")}
+ />
+ )}
+
+ {/* Frame Tool */}
+ {showFrameToolOutside && (
+ handleToolChange("frame")}
+ />
+ )}
+
+ {/* Other Shapes */}
+
+ setIsOtherShapesMenuOpen(!isOtherShapesMenuOpen)}
+ title={t("toolBar.extraTools")}
+ style={{
+ width: WIDTH,
+ height: WIDTH,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ }}
+ >
+ {extraIcon}
+
+ setIsOtherShapesMenuOpen(false)}
+ onSelect={() => setIsOtherShapesMenuOpen(false)}
+ className="App-toolbar__extra-tools-dropdown"
+ >
+ {!showTextToolOutside && (
+ app.setActiveTool({ type: "text" })}
+ icon={TextIcon}
+ shortcut={KEYS.T.toLocaleUpperCase()}
+ data-testid="toolbar-text"
+ selected={activeTool.type === "text"}
+ >
+ {t("toolBar.text")}
+
+ )}
+
+ {!showImageToolOutside && (
+ app.setActiveTool({ type: "image" })}
+ icon={ImageIcon}
+ data-testid="toolbar-image"
+ selected={activeTool.type === "image"}
+ >
+ {t("toolBar.image")}
+
+ )}
+ {!showFrameToolOutside && (
+ app.setActiveTool({ type: "frame" })}
+ icon={frameToolIcon}
+ shortcut={KEYS.F.toLocaleUpperCase()}
+ data-testid="toolbar-frame"
+ selected={frameToolSelected}
+ >
+ {t("toolBar.frame")}
+
+ )}
+ app.setActiveTool({ type: "embeddable" })}
+ icon={EmbedIcon}
+ data-testid="toolbar-embeddable"
+ selected={embeddableToolSelected}
+ >
+ {t("toolBar.embeddable")}
+
+ app.setActiveTool({ type: "laser" })}
+ icon={laserPointerToolIcon}
+ data-testid="toolbar-laser"
+ selected={laserToolSelected}
+ shortcut={KEYS.K.toLocaleUpperCase()}
+ >
+ {t("toolBar.laser")}
+
+
+ Generate
+
+ {app.props.aiEnabled !== false && }
+ app.setOpenDialog({ name: "ttd", tab: "mermaid" })}
+ icon={mermaidLogoIcon}
+ data-testid="toolbar-embeddable"
+ >
+ {t("toolBar.mermaidToExcalidraw")}
+
+ {app.props.aiEnabled !== false && app.plugins.diagramToCode && (
+ <>
+ app.onMagicframeToolSelect()}
+ icon={MagicIcon}
+ data-testid="toolbar-magicframe"
+ >
+ {t("toolBar.magicframe")}
+ AI
+
+ >
+ )}
+
+
+
+ );
+};
diff --git a/packages/excalidraw/components/ToolPopover.scss b/packages/excalidraw/components/ToolPopover.scss
new file mode 100644
index 0000000000..d049704bb7
--- /dev/null
+++ b/packages/excalidraw/components/ToolPopover.scss
@@ -0,0 +1,18 @@
+@import "../css/variables.module.scss";
+
+.excalidraw {
+ .tool-popover-content {
+ display: flex;
+ flex-direction: row;
+ gap: 0.25rem;
+ border-radius: 0.5rem;
+ background: var(--island-bg-color);
+ box-shadow: var(--shadow-island);
+ padding: 0.5rem;
+ z-index: var(--zIndex-layerUI);
+ }
+
+ &:focus {
+ outline: none;
+ }
+}
diff --git a/packages/excalidraw/components/ToolPopover.tsx b/packages/excalidraw/components/ToolPopover.tsx
new file mode 100644
index 0000000000..81d5726d5a
--- /dev/null
+++ b/packages/excalidraw/components/ToolPopover.tsx
@@ -0,0 +1,120 @@
+import React, { useEffect, useState } from "react";
+import clsx from "clsx";
+
+import { capitalizeString } from "@excalidraw/common";
+
+import * as Popover from "@radix-ui/react-popover";
+
+import { trackEvent } from "../analytics";
+
+import { ToolButton } from "./ToolButton";
+
+import "./ToolPopover.scss";
+
+import type { AppClassProperties } from "../types";
+
+type ToolOption = {
+ type: string;
+ icon: React.ReactNode;
+ title?: string;
+};
+
+type ToolPopoverProps = {
+ app: AppClassProperties;
+ options: readonly ToolOption[];
+ activeTool: { type: string };
+ defaultOption: string;
+ className?: string;
+ namePrefix: string;
+ title: string;
+ "data-testid": string;
+ onToolChange: (type: string) => void;
+ displayedOption: ToolOption;
+ fillable?: boolean;
+};
+
+export const ToolPopover = ({
+ app,
+ options,
+ activeTool,
+ defaultOption,
+ className = "Shape",
+ namePrefix,
+ title,
+ "data-testid": dataTestId,
+ onToolChange,
+ displayedOption,
+ fillable = false,
+}: ToolPopoverProps) => {
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
+ const currentType = activeTool.type;
+ const isActive = displayedOption.type === currentType;
+ const SIDE_OFFSET = 32 / 2 + 10;
+
+ // if currentType is not in options, close popup
+ if (!options.some((o) => o.type === currentType) && isPopupOpen) {
+ setIsPopupOpen(false);
+ }
+
+ // Close popover when user starts interacting with the canvas (pointer down)
+ useEffect(() => {
+ // app.onPointerDownEmitter emits when pointer down happens on canvas area
+ const unsubscribe = app.onPointerDownEmitter.on(() => {
+ setIsPopupOpen(false);
+ });
+ return () => unsubscribe?.();
+ }, [app]);
+
+ return (
+
+
+ o.type === activeTool.type),
+ })}
+ type="radio"
+ icon={displayedOption.icon}
+ checked={isActive}
+ name="editor-current-shape"
+ title={title}
+ aria-label={title}
+ data-testid={dataTestId}
+ onPointerDown={() => {
+ setIsPopupOpen((v) => !v);
+ onToolChange(defaultOption);
+ }}
+ />
+
+
+
+ {options.map(({ type, icon, title }) => (
+ {
+ if (app.state.activeTool.type !== type) {
+ trackEvent("toolbar", type, "ui");
+ }
+ app.setActiveTool({ type: type as any });
+ onToolChange?.(type);
+ }}
+ />
+ ))}
+
+
+ );
+};
diff --git a/packages/excalidraw/components/Toolbar.scss b/packages/excalidraw/components/Toolbar.scss
index 14c4cc174b..3919176bbb 100644
--- a/packages/excalidraw/components/Toolbar.scss
+++ b/packages/excalidraw/components/Toolbar.scss
@@ -44,6 +44,10 @@
var(--button-active-border, var(--color-primary-darkest)) inset;
}
+ &:hover {
+ background-color: transparent;
+ }
+
&--selected,
&--selected:hover {
background: var(--color-primary-light);
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss b/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss
index 95d258c46b..a0a230941d 100644
--- a/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss
@@ -3,24 +3,46 @@
.excalidraw {
.dropdown-menu {
position: absolute;
- top: 100%;
+ top: 2.5rem;
margin-top: 0.5rem;
+ &--placement-top {
+ top: auto;
+ bottom: 100%;
+ margin-top: 0;
+ margin-bottom: 0.5rem;
+ }
+
&--mobile {
- left: 0;
width: 100%;
row-gap: 0.75rem;
+ // When main menu is in the top toolbar, position relative to trigger
+ &.main-menu-dropdown {
+ min-width: 232px;
+ max-width: calc(100vw - var(--editor-container-padding) * 2);
+ margin-top: 0;
+ margin-bottom: 0;
+ z-index: var(--zIndex-layerUI);
+
+ @media screen and (orientation: landscape) {
+ max-width: 232px;
+ }
+ }
+
.dropdown-menu-container {
padding: 8px 8px;
box-sizing: border-box;
- // background-color: var(--island-bg-color);
+ max-height: calc(
+ 100svh - var(--editor-container-padding) * 2 - 2.25rem
+ );
box-shadow: var(--shadow-island);
border-radius: var(--border-radius-lg);
position: relative;
transition: box-shadow 0.5s ease-in-out;
display: flex;
flex-direction: column;
+ overflow-y: auto;
&.zen-mode {
box-shadow: none;
@@ -30,7 +52,7 @@
.dropdown-menu-container {
background-color: var(--island-bg-color);
- max-height: calc(100vh - 150px);
+
overflow-y: auto;
--gap: 2;
}
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx
index e1412e20b1..761d09b3f9 100644
--- a/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx
@@ -17,16 +17,27 @@ import "./DropdownMenu.scss";
const DropdownMenu = ({
children,
open,
+ placement,
}: {
children?: React.ReactNode;
open: boolean;
+ placement?: "top" | "bottom";
}) => {
const MenuTriggerComp = getMenuTriggerComponent(children);
const MenuContentComp = getMenuContentComponent(children);
+
+ // clone the MenuContentComp to pass the placement prop
+ const MenuContentCompWithPlacement =
+ MenuContentComp && React.isValidElement(MenuContentComp)
+ ? React.cloneElement(MenuContentComp as React.ReactElement, {
+ placement,
+ })
+ : MenuContentComp;
+
return (
<>
{MenuTriggerComp}
- {open && MenuContentComp}
+ {open && MenuContentCompWithPlacement}
>
);
};
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx
index de6fc31c18..291f857e80 100644
--- a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx
@@ -17,6 +17,7 @@ const MenuContent = ({
className = "",
onSelect,
style,
+ placement = "bottom",
}: {
children?: React.ReactNode;
onClickOutside?: () => void;
@@ -26,6 +27,7 @@ const MenuContent = ({
*/
onSelect?: (event: Event) => void;
style?: React.CSSProperties;
+ placement?: "top" | "bottom";
}) => {
const device = useDevice();
const menuRef = useRef(null);
@@ -58,6 +60,7 @@ const MenuContent = ({
const classNames = clsx(`dropdown-menu ${className}`, {
"dropdown-menu--mobile": device.editor.isMobile,
+ "dropdown-menu--placement-top": placement === "top",
}).trim();
return (
diff --git a/packages/excalidraw/components/icons.tsx b/packages/excalidraw/components/icons.tsx
index 33e59380c7..3f6c4d1bb1 100644
--- a/packages/excalidraw/components/icons.tsx
+++ b/packages/excalidraw/components/icons.tsx
@@ -2319,22 +2319,10 @@ export const adjustmentsIcon = createIcon(
tablerIconProps,
);
-export const backgroundIcon = createIcon(
-
-
-
-
-
-
-
- ,
- tablerIconProps,
-);
-
export const strokeIcon = createIcon(
-
+
,
tablerIconProps,
);
diff --git a/packages/excalidraw/components/main-menu/MainMenu.tsx b/packages/excalidraw/components/main-menu/MainMenu.tsx
index 7c2b5fb4a1..8ce2a5d69b 100644
--- a/packages/excalidraw/components/main-menu/MainMenu.tsx
+++ b/packages/excalidraw/components/main-menu/MainMenu.tsx
@@ -53,6 +53,8 @@ const MainMenu = Object.assign(
onSelect={composeEventHandlers(onSelect, () => {
setAppState({ openMenu: null });
})}
+ placement="bottom"
+ className={device.editor.isMobile ? "main-menu-dropdown" : ""}
>
{children}
{device.editor.isMobile && appState.collaborators.size > 0 && (
diff --git a/packages/excalidraw/components/shapes.tsx b/packages/excalidraw/components/shapes.tsx
index 56c85bcd42..d46f08a311 100644
--- a/packages/excalidraw/components/shapes.tsx
+++ b/packages/excalidraw/components/shapes.tsx
@@ -89,7 +89,7 @@ export const SHAPES = [
] as const;
export const getToolbarTools = (app: AppClassProperties) => {
- return app.defaultSelectionTool === "lasso"
+ return app.state.preferredSelectionTool.type === "lasso"
? ([
{
value: "lasso",
diff --git a/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss b/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss
index 8e3a010309..96f1ca2df3 100644
--- a/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss
+++ b/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss
@@ -252,16 +252,12 @@
}
}
- @media (max-height: 599px) {
+ &.excalidraw--mobile {
.welcome-screen-center {
- margin-top: 4rem;
- }
- }
- @media (min-height: 600px) and (max-height: 900px) {
- .welcome-screen-center {
- margin-top: 8rem;
+ margin-bottom: 2rem;
}
}
+
@media (max-height: 500px), (max-width: 320px) {
.welcome-screen-center {
display: none;
diff --git a/packages/excalidraw/css/styles.scss b/packages/excalidraw/css/styles.scss
index 2169696ae0..679a5c4cd1 100644
--- a/packages/excalidraw/css/styles.scss
+++ b/packages/excalidraw/css/styles.scss
@@ -44,6 +44,11 @@ body.excalidraw-cursor-resize * {
height: 100%;
width: 100%;
+ button,
+ label {
+ @include buttonNoHighlight;
+ }
+
button {
cursor: pointer;
user-select: none;
@@ -235,27 +240,32 @@ body.excalidraw-cursor-resize * {
z-index: var(--zIndex-layerUI);
display: flex;
flex-direction: column;
- align-items: center;
+ }
+
+ .App-welcome-screen {
+ z-index: var(--zIndex-layerUI);
}
.App-bottom-bar {
position: absolute;
- top: 0;
+ // account for margins
+ width: calc(100% - 28px);
+ max-width: 450px;
bottom: 0;
- left: 0;
- right: 0;
+ left: 50%;
+ transform: translateX(-50%);
--bar-padding: calc(4 * var(--space-factor));
- z-index: 4;
+ z-index: var(--zIndex-layerUI);
display: flex;
- align-items: flex-end;
+ flex-direction: column;
+
pointer-events: none;
+ justify-content: center;
> .Island {
- width: 100%;
- max-width: 100%;
- min-width: 100%;
box-sizing: border-box;
max-height: 100%;
+ padding: 4px;
display: flex;
flex-direction: column;
pointer-events: var(--ui-pointerEvents);
@@ -263,7 +273,8 @@ body.excalidraw-cursor-resize * {
}
.App-toolbar {
- width: 100%;
+ display: flex;
+ justify-content: center;
.eraser {
&.ToolIcon:hover {
@@ -276,16 +287,15 @@ body.excalidraw-cursor-resize * {
}
}
- .App-toolbar-content {
+ .excalidraw-ui-top-left {
display: flex;
align-items: center;
- justify-content: space-between;
- padding: 8px;
+ gap: 0.5rem;
+ }
- .dropdown-menu--mobile {
- bottom: 55px;
- top: auto;
- }
+ .App-toolbar-content {
+ display: flex;
+ flex-direction: column;
}
.App-mobile-menu {
@@ -506,7 +516,7 @@ body.excalidraw-cursor-resize * {
display: none;
}
.scroll-back-to-content {
- bottom: calc(80px + var(--sab, 0));
+ bottom: calc(100px + var(--sab, 0));
z-index: -1;
}
}
diff --git a/packages/excalidraw/css/theme.scss b/packages/excalidraw/css/theme.scss
index 1d6a569665..223cd8eb6e 100644
--- a/packages/excalidraw/css/theme.scss
+++ b/packages/excalidraw/css/theme.scss
@@ -8,6 +8,8 @@
--button-gray-1: #{$oc-gray-2};
--button-gray-2: #{$oc-gray-4};
--button-gray-3: #{$oc-gray-5};
+ --mobile-action-button-bg: rgba(255, 255, 255, 0.35);
+ --mobile-color-border: var(--default-border-color);
--button-special-active-bg-color: #{$oc-green-0};
--dialog-border-color: var(--color-gray-20);
--dropdown-icon: url('data:image/svg+xml,');
@@ -42,6 +44,11 @@
--lg-button-size: 2.25rem;
--lg-icon-size: 1rem;
--editor-container-padding: 1rem;
+ --mobile-action-button-size: 2rem;
+
+ @include isMobile {
+ --editor-container-padding: 0.75rem;
+ }
@media screen and (min-device-width: 1921px) {
--lg-button-size: 2.5rem;
@@ -177,6 +184,8 @@
--button-gray-1: #363636;
--button-gray-2: #272727;
--button-gray-3: #222;
+ --mobile-action-button-bg: var(--island-bg-color);
+ --mobile-color-border: rgba(255, 255, 255, 0.85);
--button-special-active-bg-color: #204624;
--dialog-border-color: var(--color-gray-80);
--dropdown-icon: url('data:image/svg+xml,');
diff --git a/packages/excalidraw/css/variables.module.scss b/packages/excalidraw/css/variables.module.scss
index c360c0dc6b..15d0768adb 100644
--- a/packages/excalidraw/css/variables.module.scss
+++ b/packages/excalidraw/css/variables.module.scss
@@ -122,6 +122,17 @@
color: var(--button-color, var(--color-on-primary-container));
}
}
+
+ @include isMobile() {
+ width: var(--mobile-action-button-size, var(--default-button-size));
+ height: var(--mobile-action-button-size, var(--default-button-size));
+ }
+}
+
+@mixin buttonNoHighlight {
+ -webkit-tap-highlight-color: transparent;
+ -webkit-touch-callout: none;
+ user-select: none;
}
@mixin outlineButtonIconStyles {
@@ -187,4 +198,9 @@
&:active {
box-shadow: 0 0 0 1px var(--color-brand-active);
}
+
+ @include isMobile() {
+ width: var(--mobile-action-button-size, 2rem);
+ height: var(--mobile-action-button-size, 2rem);
+ }
}
diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx
index 1b1f830439..1d599a98ec 100644
--- a/packages/excalidraw/index.tsx
+++ b/packages/excalidraw/index.tsx
@@ -28,6 +28,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
excalidrawAPI,
isCollaborating = false,
onPointerUpdate,
+ renderTopLeftUI,
renderTopRightUI,
langCode = defaultLang.code,
viewModeEnabled,
@@ -120,6 +121,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
excalidrawAPI={excalidrawAPI}
isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate}
+ renderTopLeftUI={renderTopLeftUI}
renderTopRightUI={renderTopRightUI}
langCode={langCode}
viewModeEnabled={viewModeEnabled}
diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap
index da94b4731f..6f4f6fd559 100644
--- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap
+++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap
@@ -956,6 +956,10 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1151,6 +1155,10 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1364,6 +1372,10 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1694,6 +1706,10 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2024,6 +2040,10 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2237,6 +2257,10 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2477,6 +2501,10 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2774,6 +2802,10 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id3": true,
},
@@ -3145,6 +3177,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -3637,6 +3673,10 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -3959,6 +3999,10 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -4281,6 +4325,10 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id3": true,
},
@@ -5565,6 +5613,10 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -6781,6 +6833,10 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -7718,6 +7774,10 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8714,6 +8774,10 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9707,6 +9771,10 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap
index 8e0b5dabe0..d436af1375 100644
--- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap
+++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap
@@ -78,6 +78,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id4": true,
},
@@ -693,6 +697,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id4": true,
},
@@ -1181,6 +1189,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1544,6 +1556,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1910,6 +1926,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2169,6 +2189,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2613,6 +2637,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2915,6 +2943,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -3233,6 +3265,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -3526,6 +3562,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -3811,6 +3851,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -4045,6 +4089,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -4301,6 +4349,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -4571,6 +4623,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -4799,6 +4855,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -5027,6 +5087,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -5273,6 +5337,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -5528,6 +5596,10 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -5782,6 +5854,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id1": true,
},
@@ -6101,7 +6177,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
- "openPopup": "elementBackground",
+ "openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {
@@ -6110,6 +6186,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id8": true,
},
@@ -6536,6 +6616,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id1": true,
},
@@ -6912,6 +6996,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -7220,6 +7308,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -7535,6 +7627,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -7764,6 +7860,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8115,6 +8215,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8466,6 +8570,10 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -8871,6 +8979,10 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9157,6 +9269,10 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9420,6 +9536,10 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9684,6 +9804,10 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9918,6 +10042,10 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -10211,6 +10339,10 @@ exports[`history > multiplayer undo/redo > should override remotely added points
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -10559,6 +10691,10 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -10797,6 +10933,10 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -11241,6 +11381,10 @@ exports[`history > multiplayer undo/redo > should update history entries after r
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -11500,6 +11644,10 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -11734,6 +11882,10 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -11961,7 +12113,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
- "openPopup": "elementStroke",
+ "openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {
@@ -11970,6 +12122,10 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -12375,6 +12531,10 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -12581,6 +12741,10 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -12790,6 +12954,10 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -13087,6 +13255,10 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -13387,6 +13559,10 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": -50,
@@ -13628,6 +13804,10 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -13864,6 +14044,10 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -14100,6 +14284,10 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -14346,6 +14534,10 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -14679,6 +14871,10 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -14845,6 +15041,10 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -15131,6 +15331,10 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -15393,6 +15597,10 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -15533,7 +15741,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
- "openPopup": "elementBackground",
+ "openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {
@@ -15542,6 +15750,10 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -15826,6 +16038,10 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -15984,6 +16200,10 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -16688,6 +16908,10 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -17322,6 +17546,10 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -17956,6 +18184,10 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -18674,6 +18906,10 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -19424,6 +19660,10 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -19903,6 +20143,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id1": true,
},
@@ -20413,6 +20657,10 @@ exports[`history > singleplayer undo/redo > should support element creation, del
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id3": true,
},
@@ -20871,6 +21119,10 @@ exports[`history > singleplayer undo/redo > should support linear element creati
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap
index 761fbc54d9..a33ca9c963 100644
--- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap
+++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap
@@ -79,6 +79,10 @@ exports[`given element A and group of elements B and given both are selected whe
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -504,6 +508,10 @@ exports[`given element A and group of elements B and given both are selected whe
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -919,6 +927,10 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1484,6 +1496,10 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -1690,6 +1706,10 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -2073,6 +2093,10 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -2317,6 +2341,10 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -2496,6 +2524,10 @@ exports[`regression tests > can drag element that covers another element, while
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id6": true,
},
@@ -2820,6 +2852,10 @@ exports[`regression tests > change the properties of a shape > [end of test] app
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -3074,6 +3110,10 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -3314,6 +3354,10 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -3549,6 +3593,10 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id3": true,
},
@@ -3806,6 +3854,10 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id6": true,
},
@@ -4119,6 +4171,10 @@ exports[`regression tests > deleting last but one element in editing group shoul
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -4554,6 +4610,10 @@ exports[`regression tests > deselects group of selected elements on pointer down
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -4836,6 +4896,10 @@ exports[`regression tests > deselects group of selected elements on pointer up w
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -5111,6 +5175,10 @@ exports[`regression tests > deselects selected element on pointer down when poin
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -5318,6 +5386,10 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -5517,6 +5589,10 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -5909,6 +5985,10 @@ exports[`regression tests > drags selected elements from point inside common bou
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -6205,6 +6285,10 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -7060,6 +7144,10 @@ exports[`regression tests > given a group of selected elements with an element t
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id6": true,
@@ -7384,7 +7472,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
- "openPopup": "elementBackground",
+ "openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {
@@ -7393,6 +7481,10 @@ exports[`regression tests > given a selected element A and a not selected elemen
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -7671,6 +7763,10 @@ exports[`regression tests > given selected element A with lower z-index than uns
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -7905,6 +8001,10 @@ exports[`regression tests > given selected element A with lower z-index than uns
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -8144,6 +8244,10 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8323,6 +8427,10 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8502,6 +8610,10 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8681,6 +8793,10 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -8910,6 +9026,10 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9137,6 +9257,10 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9332,6 +9456,10 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9561,6 +9689,10 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9740,6 +9872,10 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -9967,6 +10103,10 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -10146,6 +10286,10 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -10341,6 +10485,10 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -10520,6 +10668,10 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -11050,6 +11202,10 @@ exports[`regression tests > noop interaction after undo shouldn't create history
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -11329,6 +11485,10 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": "-6.25000",
@@ -11451,6 +11611,10 @@ exports[`regression tests > shift click on selected element should deselect it o
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -11650,6 +11814,10 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -11968,6 +12136,10 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id3": true,
@@ -12396,6 +12568,10 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
"id15": true,
@@ -13038,6 +13214,10 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 60,
@@ -13160,6 +13340,10 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id0": true,
},
@@ -13790,6 +13974,10 @@ exports[`regression tests > switches from group of selected elements to another
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id3": true,
"id6": true,
@@ -14128,6 +14316,10 @@ exports[`regression tests > switches selected element on pointer down > [end of
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {
"id3": true,
},
@@ -14391,6 +14583,10 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 20,
@@ -14513,6 +14709,10 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -14904,6 +15104,10 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
@@ -15029,6 +15233,10 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": true,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts
index 65f330ae24..3b4d2eb478 100644
--- a/packages/excalidraw/types.ts
+++ b/packages/excalidraw/types.ts
@@ -316,6 +316,10 @@ export interface AppState {
// indicates if the current tool is temporarily switched on from the selection tool
fromSelection: boolean;
} & ActiveTool;
+ preferredSelectionTool: {
+ type: "selection" | "lasso";
+ initialized: boolean;
+ };
penMode: boolean;
penDetected: boolean;
exportBackground: boolean;
@@ -364,7 +368,6 @@ export interface AppState {
| { name: "ttd"; tab: "text-to-diagram" | "mermaid" }
| { name: "commandPalette" }
| { name: "elementLinkSelector"; sourceElementId: ExcalidrawElement["id"] };
-
/**
* Reflects user preference for whether the default sidebar should be docked.
*
@@ -448,7 +451,7 @@ export interface AppState {
lockedMultiSelections: { [groupId: string]: true };
/** properties sidebar mode - determines whether to show compact or complete sidebar */
- stylesPanelMode: "compact" | "full";
+ stylesPanelMode: "compact" | "full" | "mobile";
}
export type SearchMatch = {
@@ -571,6 +574,10 @@ export interface ExcalidrawProps {
/** excludes the duplicated elements */
prevElements: readonly ExcalidrawElement[],
) => ExcalidrawElement[] | void;
+ renderTopLeftUI?: (
+ isMobile: boolean,
+ appState: UIAppState,
+ ) => JSX.Element | null;
renderTopRightUI?: (
isMobile: boolean,
appState: UIAppState,
@@ -738,8 +745,7 @@ export type AppClassProperties = {
onPointerUpEmitter: App["onPointerUpEmitter"];
updateEditorAtom: App["updateEditorAtom"];
-
- defaultSelectionTool: "selection" | "lasso";
+ onPointerDownEmitter: App["onPointerDownEmitter"];
};
export type PointerDownState = Readonly<{
diff --git a/packages/utils/tests/__snapshots__/export.test.ts.snap b/packages/utils/tests/__snapshots__/export.test.ts.snap
index 20f3ee28d9..1f799501c9 100644
--- a/packages/utils/tests/__snapshots__/export.test.ts.snap
+++ b/packages/utils/tests/__snapshots__/export.test.ts.snap
@@ -80,6 +80,10 @@ exports[`exportToSvg > with default arguments 1`] = `
},
"penDetected": false,
"penMode": false,
+ "preferredSelectionTool": {
+ "initialized": false,
+ "type": "selection",
+ },
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
From 19b03b4ca9d9dbbd8d3246d99490dadf57e4c507 Mon Sep 17 00:00:00 2001
From: Omar Brikaa
Date: Fri, 10 Oct 2025 19:12:08 +0300
Subject: [PATCH 4/4] fix: remove redundant selectionStart/End resetting that
causes scroll-reset bug on firefox (#8263)
Remove redundant selectionStart/End resetting that causes scroll-reset bug on firefox
---
packages/excalidraw/wysiwyg/textWysiwyg.tsx | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/packages/excalidraw/wysiwyg/textWysiwyg.tsx b/packages/excalidraw/wysiwyg/textWysiwyg.tsx
index adede07f87..149faf8987 100644
--- a/packages/excalidraw/wysiwyg/textWysiwyg.tsx
+++ b/packages/excalidraw/wysiwyg/textWysiwyg.tsx
@@ -226,22 +226,6 @@ export const textWysiwyg = ({
}
}
const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
- const initialSelectionStart = editable.selectionStart;
- const initialSelectionEnd = editable.selectionEnd;
- const initialLength = editable.value.length;
-
- // restore cursor position after value updated so it doesn't
- // go to the end of text when container auto expanded
- if (
- initialSelectionStart === initialSelectionEnd &&
- initialSelectionEnd !== initialLength
- ) {
- // get diff between length and selection end and shift
- // the cursor by "diff" times to position correctly
- const diff = initialLength - initialSelectionEnd;
- editable.selectionStart = editable.value.length - diff;
- editable.selectionEnd = editable.value.length - diff;
- }
if (!container) {
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;