+ {/* Stroke Color */}
+ {canChangeStrokeColor(appState, targetElements) && (
+
+ {renderAction("changeStrokeColor")}
+
+ )}
+
+ {/* Background Color */}
+ {canChangeBackgroundColor(appState, targetElements) && (
+
+ {renderAction("changeBackgroundColor")}
+
+ )}
+
+ {/* Combined Properties (Fill, Stroke, Opacity) */}
+ {(showFillIcons ||
+ hasStrokeWidth(appState.activeTool.type) ||
+ targetElements.some((element) => hasStrokeWidth(element.type)) ||
+ hasStrokeStyle(appState.activeTool.type) ||
+ targetElements.some((element) => hasStrokeStyle(element.type)) ||
+ canChangeRoundness(appState.activeTool.type) ||
+ targetElements.some((element) => canChangeRoundness(element.type))) && (
+
+
{
+ if (open) {
+ setAppState({ openPopup: "compactStrokeStyles" });
+ } else {
+ setAppState({ openPopup: null });
+ }
+ }}
+ >
+
+
+
+ {appState.openPopup === "compactStrokeStyles" && (
+ {}}
+ >
+
+ {showFillIcons && renderAction("changeFillStyle")}
+ {(hasStrokeWidth(appState.activeTool.type) ||
+ targetElements.some((element) =>
+ hasStrokeWidth(element.type),
+ )) &&
+ renderAction("changeStrokeWidth")}
+ {(hasStrokeStyle(appState.activeTool.type) ||
+ targetElements.some((element) =>
+ hasStrokeStyle(element.type),
+ )) && (
+ <>
+ {renderAction("changeStrokeStyle")}
+ {renderAction("changeSloppiness")}
+ >
+ )}
+ {(canChangeRoundness(appState.activeTool.type) ||
+ targetElements.some((element) =>
+ canChangeRoundness(element.type),
+ )) &&
+ renderAction("changeRoundness")}
+ {renderAction("changeOpacity")}
+
+
+ )}
+
+
+ )}
+
+ {/* Combined Arrow Properties */}
+ {(toolIsArrow(appState.activeTool.type) ||
+ targetElements.some((element) => toolIsArrow(element.type))) && (
+
+
{
+ if (open) {
+ setAppState({ openPopup: "compactArrowProperties" });
+ } else {
+ setAppState({ openPopup: null });
+ }
+ }}
+ >
+
+
+
+ {appState.openPopup === "compactArrowProperties" && (
+ {}}
+ >
+ {renderAction("changeArrowProperties")}
+
+ )}
+
+
+ )}
+
+ {/* Linear Editor */}
+ {showLineEditorAction && (
+
+ {renderAction("toggleLinearEditor")}
+
+ )}
+
+ {/* Text Properties */}
+ {(appState.activeTool.type === "text" ||
+ targetElements.some(isTextElement)) && (
+ <>
+
+ {renderAction("changeFontFamily")}
+
+
+
{
+ if (open) {
+ if (appState.editingTextElement) {
+ saveCaretPosition();
+ }
+ setAppState({ openPopup: "compactTextProperties" });
+ } else {
+ setAppState({ openPopup: null });
+ if (appState.editingTextElement) {
+ restoreCaretPosition();
+ }
+ }
+ }}
+ >
+
+
+
+ {appState.openPopup === "compactTextProperties" && (
+ {
+ // Refocus text editor when popover closes with caret restoration
+ if (appState.editingTextElement) {
+ restoreCaretPosition();
+ }
+ }}
+ >
+
+ {(appState.activeTool.type === "text" ||
+ targetElements.some(isTextElement)) &&
+ renderAction("changeFontSize")}
+ {(appState.activeTool.type === "text" ||
+ suppportsHorizontalAlign(targetElements, elementsMap)) &&
+ renderAction("changeTextAlign")}
+ {shouldAllowVerticalAlign(targetElements, elementsMap) &&
+ renderAction("changeVerticalAlign")}
+
+
+ )}
+
+
+ >
+ )}
+
+ {/* Dedicated Copy Button */}
+ {!isEditingTextOrNewElement && targetElements.length > 0 && (
+
+ {renderAction("duplicateSelection")}
+
+ )}
+
+ {/* Dedicated Delete Button */}
+ {!isEditingTextOrNewElement && targetElements.length > 0 && (
+
+ {renderAction("deleteSelectedElements")}
+
+ )}
+
+ {/* Combined Other Actions */}
+ {!isEditingTextOrNewElement && targetElements.length > 0 && (
+
+
{
+ if (open) {
+ setAppState({ openPopup: "compactOtherProperties" });
+ } else {
+ setAppState({ openPopup: null });
+ }
+ }}
+ >
+
+
+
+ {appState.openPopup === "compactOtherProperties" && (
+ {}}
+ >
+
+
+
+ {showAlignActions && !isSingleElementBoundContainer && (
+
+ )}
+
+
+
+ )}
+
+
+ )}
+
+ );
+};
+
export const ShapesSwitcher = ({
activeTool,
appState,
diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx
index 788600749d..3bbfdca6e1 100644
--- a/packages/excalidraw/components/App.tsx
+++ b/packages/excalidraw/components/App.tsx
@@ -41,9 +41,6 @@ import {
LINE_CONFIRM_THRESHOLD,
MAX_ALLOWED_FILE_BYTES,
MIME_TYPES,
- MQ_MAX_HEIGHT_LANDSCAPE,
- MQ_MAX_WIDTH_LANDSCAPE,
- MQ_MAX_WIDTH_PORTRAIT,
MQ_RIGHT_SIDEBAR_MIN_WIDTH,
POINTER_BUTTON,
ROUNDNESS,
@@ -100,9 +97,14 @@ import {
randomInteger,
CLASSES,
Emitter,
- isMobile,
MINIMUM_ARROW_SIZE,
DOUBLE_TAP_POSITION_THRESHOLD,
+ isMobileOrTablet,
+ MQ_MAX_WIDTH_MOBILE,
+ MQ_MAX_HEIGHT_LANDSCAPE,
+ MQ_MAX_WIDTH_LANDSCAPE,
+ MQ_MIN_TABLET,
+ MQ_MAX_TABLET,
} from "@excalidraw/common";
import {
@@ -667,7 +669,7 @@ class App extends React.Component