Compare commits

..

1 Commits

Author SHA1 Message Date
Mark Tolmacs
314cd356bc fix: Alt-duplication doesn't place the duplicate at the last moment where Alt is pressed
fix: Shift + Alt

fix: Test

chore: Revert to master

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

fix: Arrow re-draw

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-10-15 22:40:36 +02:00
31 changed files with 133 additions and 248 deletions

View File

@@ -23,7 +23,7 @@
]
},
"engines": {
"node": ">=18.0.0"
"node": "18.0.0 - 22.x.x"
},
"dependencies": {
"@excalidraw/random-username": "1.0.0",

View File

@@ -44,7 +44,7 @@
"vitest-canvas-mock": "0.3.3"
},
"engines": {
"node": ">=18.0.0"
"node": "18.0.0 - 22.x.x"
},
"homepage": ".",
"prettier": "@excalidraw/prettier-config",

View File

@@ -20,6 +20,7 @@ import {
ENV,
FONT_FAMILY,
getFontFamilyFallbacks,
isDarwin,
isAndroid,
isIOS,
WINDOWS_EMOJI_FALLBACK_FONT,
@@ -425,6 +426,19 @@ export const allowFullScreen = () =>
export const exitFullScreen = () => document.exitFullscreen();
export const getShortcutKey = (shortcut: string): string => {
shortcut = shortcut
.replace(/\bAlt\b/i, "Alt")
.replace(/\bShift\b/i, "Shift")
.replace(/\b(Enter|Return)\b/i, "Enter");
if (isDarwin) {
return shortcut
.replace(/\bCtrlOrCmd\b/gi, "Cmd")
.replace(/\bAlt\b/i, "Option");
}
return shortcut.replace(/\bCtrlOrCmd\b/gi, "Ctrl");
};
export const viewportCoordsToSceneCoords = (
{ clientX, clientY }: { clientX: number; clientY: number },
{

View File

@@ -2,7 +2,6 @@ import {
DEFAULT_TRANSFORM_HANDLE_SPACING,
isAndroid,
isIOS,
isMobileOrTablet,
} from "@excalidraw/common";
import { pointFrom, pointRotateRads } from "@excalidraw/math";
@@ -327,7 +326,7 @@ export const getTransformHandles = (
);
};
export const hasBoundingBox = (
export const shouldShowBoundingBox = (
elements: readonly NonDeletedExcalidrawElement[],
appState: InteractiveCanvasAppState,
) => {
@@ -346,7 +345,5 @@ export const hasBoundingBox = (
return true;
}
// on mobile/tablet we currently don't show bbox because of resize issues
// (also prob best for simplicity's sake)
return element.points.length > 2 && !isMobileOrTablet();
return element.points.length > 2;
};

View File

@@ -10,8 +10,6 @@ import { API } from "@excalidraw/excalidraw/tests/helpers/api";
import { UI, Pointer, Keyboard } from "@excalidraw/excalidraw/tests/helpers/ui";
import { fireEvent, render } from "@excalidraw/excalidraw/tests/test-utils";
import { LinearElementEditor } from "@excalidraw/element";
import { getTransformHandles } from "../src/transformHandles";
import {
getTextEditor,
@@ -415,12 +413,16 @@ describe("element binding", () => {
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
// Drag arrow off of bound rectangle range
const [elX, elY] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
const handles = getTransformHandles(
arrow,
-1,
h.scene.getNonDeletedElementsMap(),
);
h.state.zoom,
arrayToMap(h.elements),
"mouse",
).se!;
Keyboard.keyDown(KEYS.CTRL_OR_CMD);
const elX = handles[0] + handles[2] / 2;
const elY = handles[1] + handles[3] / 2;
mouse.downAt(elX, elY);
mouse.moveTo(300, 400);
mouse.up();

View File

@@ -4,7 +4,7 @@ import { isFrameLikeElement } from "@excalidraw/element";
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element";
import { KEYS, arrayToMap } from "@excalidraw/common";
import { KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common";
import { alignElements } from "@excalidraw/element";
@@ -30,8 +30,6 @@ import { t } from "../i18n";
import { isSomeElementSelected } from "../scene";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";
import type { AppClassProperties, AppState, UIAppState } from "../types";

View File

@@ -7,6 +7,7 @@ import {
MIN_ZOOM,
THEME,
ZOOM_STEP,
getShortcutKey,
updateActiveTool,
CODES,
KEYS,
@@ -45,7 +46,6 @@ import { t } from "../i18n";
import { getNormalizedZoom } from "../scene";
import { centerScrollOn } from "../scene/scroll";
import { getStateForZoom } from "../scene/zoom";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";

View File

@@ -2,7 +2,7 @@ import { getNonDeletedElements } from "@excalidraw/element";
import { isFrameLikeElement } from "@excalidraw/element";
import { CODES, KEYS, arrayToMap } from "@excalidraw/common";
import { CODES, KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common";
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element";
@@ -26,8 +26,6 @@ import { t } from "../i18n";
import { isSomeElementSelected } from "../scene";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";
import type { AppClassProperties, AppState } from "../types";

View File

@@ -3,6 +3,7 @@ import {
KEYS,
MOBILE_ACTION_BUTTON_BG,
arrayToMap,
getShortcutKey,
} from "@excalidraw/common";
import { getNonDeletedElements } from "@excalidraw/element";
@@ -25,7 +26,6 @@ import { DuplicateIcon } from "../components/icons";
import { t } from "../i18n";
import { isSomeElementSelected } from "../scene";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";

View File

@@ -14,7 +14,7 @@ import {
replaceAllElementsInFrame,
} from "@excalidraw/element";
import { KEYS, randomId, arrayToMap } from "@excalidraw/common";
import { KEYS, randomId, arrayToMap, getShortcutKey } from "@excalidraw/common";
import {
getSelectedGroupIds,
@@ -43,8 +43,6 @@ import { t } from "../i18n";
import { isSomeElementSelected } from "../scene";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";
import type { AppClassProperties, AppState } from "../types";

View File

@@ -1,6 +1,6 @@
import { isEmbeddableElement } from "@excalidraw/element";
import { KEYS } from "@excalidraw/common";
import { KEYS, getShortcutKey } from "@excalidraw/common";
import { CaptureUpdateAction } from "@excalidraw/element";
@@ -8,8 +8,8 @@ import { ToolButton } from "../components/ToolButton";
import { getContextMenuLabel } from "../components/hyperlink/Hyperlink";
import { LinkIcon } from "../components/icons";
import { t } from "../i18n";
import { getSelectedElements } from "../scene";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";

View File

@@ -17,6 +17,7 @@ import {
randomInteger,
arrayToMap,
getFontFamilyString,
getShortcutKey,
getLineHeight,
isTransparent,
reduceToCommonValue,
@@ -141,8 +142,6 @@ import {
restoreCaretPosition,
} from "../hooks/useTextEditorFocus";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";
import type { AppClassProperties, AppState, Primitive } from "../types";

View File

@@ -1,4 +1,4 @@
import { KEYS, CODES, isDarwin } from "@excalidraw/common";
import { KEYS, CODES, getShortcutKey, isDarwin } from "@excalidraw/common";
import {
moveOneLeft,
@@ -16,7 +16,6 @@ import {
SendToBackIcon,
} from "../components/icons";
import { t } from "../i18n";
import { getShortcutKey } from "../shortcut";
import { register } from "./register";

View File

@@ -1,9 +1,8 @@
import { isDarwin } from "@excalidraw/common";
import { isDarwin, getShortcutKey } from "@excalidraw/common";
import type { SubtypeOf } from "@excalidraw/common/utility-types";
import { t } from "../i18n";
import { getShortcutKey } from "../shortcut";
import type { ActionName } from "./types";

View File

@@ -80,6 +80,7 @@ import {
wrapEvent,
updateObject,
updateActiveTool,
getShortcutKey,
isTransparent,
easeToValuesRAF,
muteFSAbortError,
@@ -172,7 +173,7 @@ import {
getContainerElement,
isValidTextContainer,
redrawTextBoundingBox,
hasBoundingBox,
shouldShowBoundingBox,
getFrameChildren,
isCursorInFrame,
addElementsToFrame,
@@ -405,8 +406,6 @@ import { LassoTrail } from "../lasso";
import { EraserTrail } from "../eraser";
import { getShortcutKey } from "../shortcut";
import ConvertElementTypePopup, {
getConversionTypeFromElements,
convertElementTypePopupAtom,
@@ -5263,7 +5262,7 @@ class App extends React.Component<AppProps, AppState> {
if (
considerBoundingBox &&
this.state.selectedElementIds[element.id] &&
hasBoundingBox([element], this.state)
shouldShowBoundingBox([element], this.state)
) {
// if hitting the bounding box, return early
// but if not, we should check for other cases as well (e.g. frame name)
@@ -6166,13 +6165,7 @@ class App extends React.Component<AppProps, AppState> {
(!this.state.selectedLinearElement ||
this.state.selectedLinearElement.hoverPointIndex === -1) &&
this.state.openDialog?.name !== "elementLinkSelector" &&
!(selectedElements.length === 1 && isElbowArrow(selectedElements[0])) &&
// HACK: Disable transform handles for linear elements on mobile until a
// better way of showing them is found
!(
isLinearElement(selectedElements[0]) &&
(isMobileOrTablet() || selectedElements[0].points.length === 2)
)
!(selectedElements.length === 1 && isElbowArrow(selectedElements[0]))
) {
const elementWithTransformHandleType =
getElementWithTransformHandleType(
@@ -7292,8 +7285,14 @@ class App extends React.Component<AppProps, AppState> {
!this.state.selectedLinearElement?.isEditing &&
!isElbowArrow(selectedElements[0]) &&
!(
isLinearElement(selectedElements[0]) &&
(isMobileOrTablet() || selectedElements[0].points.length === 2)
isLineElement(selectedElements[0]) &&
LinearElementEditor.getPointIndexUnderCursor(
selectedElements[0],
elementsMap,
this.state.zoom,
pointerDownState.origin.x,
pointerDownState.origin.y,
) !== -1
) &&
!(
this.state.selectedLinearElement &&
@@ -8866,6 +8865,15 @@ class App extends React.Component<AppProps, AppState> {
}));
this.scene.replaceAllElements(elementsWithIndices);
elementsWithIndices.forEach((element) => {
if (
isBindableElement(element) &&
element.boundElements?.some((other) => other.type === "arrow")
) {
updateBoundElements(element, this.scene);
}
});
this.maybeCacheVisibleGaps(event, selectedElements, true);
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
});
@@ -11205,17 +11213,6 @@ class App extends React.Component<AppProps, AppState> {
return [actionCopy, ...options];
}
const zIndexActions: ContextMenuItems =
this.state.stylesPanelMode === "full"
? [
CONTEXT_MENU_SEPARATOR,
actionSendBackward,
actionBringForward,
actionSendToBack,
actionBringToFront,
]
: [];
return [
CONTEXT_MENU_SEPARATOR,
actionCut,
@@ -11241,7 +11238,11 @@ class App extends React.Component<AppProps, AppState> {
actionUngroup,
CONTEXT_MENU_SEPARATOR,
actionAddToLibrary,
...zIndexActions,
CONTEXT_MENU_SEPARATOR,
actionSendBackward,
actionBringForward,
actionSendToBack,
actionBringToFront,
CONTEXT_MENU_SEPARATOR,
actionFlipHorizontal,
actionFlipVertical,

View File

@@ -1,9 +1,8 @@
import clsx from "clsx";
import { useCallback, useEffect, useRef, useState } from "react";
import { KEYS } from "@excalidraw/common";
import { KEYS, getShortcutKey } from "@excalidraw/common";
import { getShortcutKey } from "../..//shortcut";
import { useAtom } from "../../editor-jotai";
import { t } from "../../i18n";
import { useDevice } from "../App";

View File

@@ -7,13 +7,12 @@ import {
EVENT,
KEYS,
capitalizeString,
getShortcutKey,
isWritableElement,
} from "@excalidraw/common";
import { actionToggleShapeSwitch } from "@excalidraw/excalidraw/actions/actionToggleShapeSwitch";
import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
import type { MarkRequired } from "@excalidraw/common/utility-types";
import {

View File

@@ -1,10 +1,6 @@
@import "../css/variables.module.scss";
.excalidraw {
.context-menu-popover {
z-index: var(--zIndex-ui-context-menu);
}
.context-menu {
position: relative;
border-radius: 4px;

View File

@@ -64,7 +64,6 @@ export const ContextMenu = React.memo(
offsetTop={appState.offsetTop}
viewportWidth={appState.width}
viewportHeight={appState.height}
className="context-menu-popover"
>
<ul
className="context-menu"

View File

@@ -2,12 +2,11 @@ import React from "react";
import { isDarwin, isFirefox, isWindows } from "@excalidraw/common";
import { KEYS } from "@excalidraw/common";
import { KEYS, getShortcutKey } from "@excalidraw/common";
import { getShortcutFromShortcutName } from "../actions/shortcuts";
import { probablySupportsClipboardBlob } from "../clipboard";
import { t } from "../i18n";
import { getShortcutKey } from "../shortcut";
import { Dialog } from "./Dialog";
import { ExternalLinkIcon, GithubIcon, youtubeIcon } from "./icons";

View File

@@ -28,24 +28,11 @@ $wide-viewport-width: 1000px;
> span {
padding: 0.25rem;
}
kbd {
display: inline-block;
margin: 0 1px;
font-family: monospace;
border: 1px solid var(--color-gray-40);
border-radius: 4px;
padding: 1px 3px;
font-size: 10px;
}
}
&.theme--dark {
.HintViewer {
color: var(--color-gray-60);
kbd {
border-color: var(--color-gray-60);
}
}
}
}

View File

@@ -9,10 +9,11 @@ import {
isTextElement,
} from "@excalidraw/element";
import { getShortcutKey } from "@excalidraw/common";
import { isNodeInFlowchart } from "@excalidraw/element";
import { t } from "../i18n";
import { getShortcutKey } from "../shortcut";
import { isEraserActive } from "../appState";
import { isGridModeEnabled } from "../snapping";
@@ -27,11 +28,6 @@ interface HintViewerProps {
app: AppClassProperties;
}
const getTaggedShortcutKey = (key: string | string[]) =>
Array.isArray(key)
? `<kbd>${key.map(getShortcutKey).join(" + ")}</kbd>`
: `<kbd>${getShortcutKey(key)}</kbd>`;
const getHints = ({
appState,
isMobile,
@@ -46,9 +42,7 @@ const getHints = ({
appState.openSidebar.tab === CANVAS_SEARCH_TAB &&
appState.searchMatches?.matches.length
) {
return t("hints.dismissSearch", {
shortcut: getTaggedShortcutKey("Escape"),
});
return t("hints.dismissSearch");
}
if (appState.openSidebar && !device.editor.canFitSidebar) {
@@ -56,21 +50,14 @@ const getHints = ({
}
if (isEraserActive(appState)) {
return t("hints.eraserRevert", {
shortcut: getTaggedShortcutKey("Alt"),
});
return t("hints.eraserRevert");
}
if (activeTool.type === "arrow" || activeTool.type === "line") {
if (multiMode) {
return t("hints.linearElementMulti", {
shortcut_1: getTaggedShortcutKey("Escape"),
shortcut_2: getTaggedShortcutKey("Enter"),
});
return t("hints.linearElementMulti");
}
if (activeTool.type === "arrow") {
return t("hints.arrowTool", {
shortcut: getTaggedShortcutKey("A"),
});
return t("hints.arrowTool", { arrowShortcut: getShortcutKey("A") });
}
return t("hints.linearElement");
}
@@ -96,51 +83,31 @@ const getHints = ({
) {
const targetElement = selectedElements[0];
if (isLinearElement(targetElement) && targetElement.points.length === 2) {
return t("hints.lockAngle", {
shortcut: getTaggedShortcutKey("Shift"),
});
return t("hints.lockAngle");
}
return isImageElement(targetElement)
? t("hints.resizeImage", {
shortcut_1: getTaggedShortcutKey("Shift"),
shortcut_2: getTaggedShortcutKey("Alt"),
})
: t("hints.resize", {
shortcut_1: getTaggedShortcutKey("Shift"),
shortcut_2: getTaggedShortcutKey("Alt"),
});
? t("hints.resizeImage")
: t("hints.resize");
}
if (isRotating && lastPointerDownWith === "mouse") {
return t("hints.rotate", {
shortcut: getTaggedShortcutKey("Shift"),
});
return t("hints.rotate");
}
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
return t("hints.text_selected", {
shortcut: getTaggedShortcutKey("Enter"),
});
return t("hints.text_selected");
}
if (appState.editingTextElement) {
return t("hints.text_editing", {
shortcut_1: getTaggedShortcutKey("Escape"),
shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "Enter"]),
});
return t("hints.text_editing");
}
if (appState.croppingElementId) {
return t("hints.leaveCropEditor", {
shortcut_1: getTaggedShortcutKey("Enter"),
shortcut_2: getTaggedShortcutKey("Escape"),
});
return t("hints.leaveCropEditor");
}
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
return t("hints.enterCropEditor", {
shortcut: getTaggedShortcutKey("Enter"),
});
return t("hints.enterCropEditor");
}
if (activeTool.type === "selection") {
@@ -150,57 +117,33 @@ const getHints = ({
!appState.editingTextElement &&
!appState.selectedLinearElement?.isEditing
) {
return t("hints.deepBoxSelect", {
shortcut: getTaggedShortcutKey("CtrlOrCmd"),
});
return [t("hints.deepBoxSelect")];
}
if (isGridModeEnabled(app) && appState.selectedElementsAreBeingDragged) {
return t("hints.disableSnapping", {
shortcut: getTaggedShortcutKey("CtrlOrCmd"),
});
return t("hints.disableSnapping");
}
if (!selectedElements.length && !isMobile) {
return t("hints.canvasPanning", {
shortcut_1: getTaggedShortcutKey(t("keys.mmb")),
shortcut_2: getTaggedShortcutKey("Space"),
});
return [t("hints.canvasPanning")];
}
if (selectedElements.length === 1) {
if (isLinearElement(selectedElements[0])) {
if (appState.selectedLinearElement?.isEditing) {
return appState.selectedLinearElement.selectedPointsIndices
? t("hints.lineEditor_pointSelected", {
shortcut_1: getTaggedShortcutKey("Delete"),
shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "D"]),
})
: t("hints.lineEditor_nothingSelected", {
shortcut_1: getTaggedShortcutKey("Shift"),
shortcut_2: getTaggedShortcutKey("Alt"),
});
? t("hints.lineEditor_pointSelected")
: t("hints.lineEditor_nothingSelected");
}
return isLineElement(selectedElements[0])
? t("hints.lineEditor_line_info", {
shortcut: getTaggedShortcutKey("Enter"),
})
: t("hints.lineEditor_info", {
shortcut_1: getTaggedShortcutKey("CtrlOrCmd"),
shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "Enter"]),
});
? t("hints.lineEditor_line_info")
: t("hints.lineEditor_info");
}
if (
!appState.newElement &&
!appState.selectedElementsAreBeingDragged &&
isTextBindableContainer(selectedElements[0])
) {
const bindTextToElement = t("hints.bindTextToElement", {
shortcut: getTaggedShortcutKey("Enter"),
});
const createFlowchart = t("hints.createFlowchart", {
shortcut: getTaggedShortcutKey(["CtrlOrCmd", "↑↓"]),
});
if (isFlowchartNodeElement(selectedElements[0])) {
if (
isNodeInFlowchart(
@@ -208,13 +151,13 @@ const getHints = ({
app.scene.getNonDeletedElementsMap(),
)
) {
return [bindTextToElement, createFlowchart];
return [t("hints.bindTextToElement"), t("hints.createFlowchart")];
}
return [bindTextToElement, createFlowchart];
return [t("hints.bindTextToElement"), t("hints.createFlowchart")];
}
return bindTextToElement;
return t("hints.bindTextToElement");
}
}
}
@@ -240,21 +183,16 @@ export const HintViewer = ({
}
const hint = Array.isArray(hints)
? hints.map((hint) => hint.replace(/\. ?$/, "")).join(", ")
: hints;
const hintJSX = hint.split(/(<kbd>[^<]+<\/kbd>)/g).map((part, index) => {
if (index % 2 === 1) {
const shortcutMatch =
part[0] === "<" && part.match(/^<kbd>([^<]+)<\/kbd>$/);
return <kbd key={index}>{shortcutMatch ? shortcutMatch[1] : part}</kbd>;
}
return part;
});
? hints
.map((hint) => {
return getShortcutKey(hint).replace(/\. ?$/, "");
})
.join(". ")
: getShortcutKey(hints);
return (
<div className="HintViewer">
<span>{hintJSX}</span>
<span>{hint}</span>
</div>
);
};

View File

@@ -8,7 +8,7 @@ import { atom, useAtom } from "../editor-jotai";
import { getLanguage, t } from "../i18n";
import Collapsible from "./Stats/Collapsible";
import { useDevice, useExcalidrawContainer } from "./App";
import { useDevice } from "./App";
import "./IconPicker.scss";
@@ -39,7 +39,6 @@ function Picker<T>({
numberOfOptionsToAlwaysShow?: number;
}) {
const device = useDevice();
const { container } = useExcalidrawContainer();
const handleKeyDown = (event: React.KeyboardEvent) => {
const pressedOption = options.find(
@@ -162,7 +161,6 @@ function Picker<T>({
sideOffset={isMobile ? 8 : 12}
style={{ zIndex: "var(--zIndex-ui-styles-popup)" }}
onKeyDown={handleKeyDown}
collisionBoundary={container ?? undefined}
>
<div
className={`picker`}

View File

@@ -3,8 +3,6 @@ import { unstable_batchedUpdates } from "react-dom";
import { KEYS, queryFocusableElements } from "@excalidraw/common";
import clsx from "clsx";
import "./Popover.scss";
type Props = {
@@ -17,7 +15,6 @@ type Props = {
offsetTop?: number;
viewportWidth?: number;
viewportHeight?: number;
className?: string;
};
export const Popover = ({
@@ -30,7 +27,6 @@ export const Popover = ({
offsetTop = 0,
viewportWidth = window.innerWidth,
viewportHeight = window.innerHeight,
className,
}: Props) => {
const popoverRef = useRef<HTMLDivElement>(null);
@@ -150,7 +146,7 @@ export const Popover = ({
}, [onCloseRequest]);
return (
<div className={clsx("popover", className)} ref={popoverRef} tabIndex={-1}>
<div className="popover" ref={popoverRef} tabIndex={-1}>
{children}
</div>
);

View File

@@ -40,8 +40,6 @@ export const PropertiesPopover = React.forwardRef<
ref,
) => {
const device = useDevice();
const isMobilePortrait =
device.editor.isMobile && !device.viewport.isLandscape;
return (
<Popover.Portal container={container}>
@@ -49,11 +47,18 @@ export const PropertiesPopover = React.forwardRef<
ref={ref}
className={clsx("focus-visible-none", className)}
data-prevent-outside-click
side={isMobilePortrait ? "bottom" : "right"}
align={isMobilePortrait ? "center" : "start"}
side={
device.editor.isMobile && !device.viewport.isLandscape
? "bottom"
: "right"
}
align={
device.editor.isMobile && !device.viewport.isLandscape
? "center"
: "start"
}
alignOffset={-16}
sideOffset={20}
collisionBoundary={container ?? undefined}
style={{
zIndex: "var(--zIndex-ui-styles-popup)",
marginLeft: device.editor.isMobile ? "0.5rem" : undefined,

View File

@@ -1,4 +1,4 @@
import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
import { getShortcutKey } from "@excalidraw/common";
export const TTDDialogSubmitShortcut = () => {
return (

View File

@@ -11,8 +11,6 @@ import { ToolButton } from "./ToolButton";
import "./ToolPopover.scss";
import { useExcalidrawContainer } from "./App";
import type { AppClassProperties } from "../types";
type ToolOption = {
@@ -52,7 +50,6 @@ export const ToolPopover = ({
const currentType = activeTool.type;
const isActive = displayedOption.type === currentType;
const SIDE_OFFSET = 32 / 2 + 10;
const { container } = useExcalidrawContainer();
// if currentType is not in options, close popup
if (!options.some((o) => o.type === currentType) && isPopupOpen) {
@@ -93,7 +90,6 @@ export const ToolPopover = ({
<Popover.Content
className="tool-popover-content"
sideOffset={SIDE_OFFSET}
collisionBoundary={container ?? undefined}
>
{options.map(({ type, icon, title }) => (
<ToolButton

View File

@@ -12,10 +12,9 @@
--zIndex-eyeDropperPreview: 6;
--zIndex-hyperlinkContainer: 7;
--zIndex-ui-styles-popup: 40;
--zIndex-ui-bottom: 60;
--zIndex-ui-library: 80;
--zIndex-ui-context-menu: 90;
--zIndex-ui-styles-popup: 100;
--zIndex-ui-top: 100;
--zIndex-modal: 1000;

View File

@@ -337,33 +337,33 @@
"shapes": "Shapes"
},
"hints": {
"dismissSearch": "{{shortcut}} to dismiss search",
"canvasPanning": "To move canvas, hold {{shortcut_1}} or {{shortcut_2}} while dragging, or use the hand tool",
"dismissSearch": "Escape to dismiss search",
"canvasPanning": "To move canvas, hold mouse wheel or spacebar while dragging, or use the hand tool",
"linearElement": "Click to start multiple points, drag for single line",
"arrowTool": "Click to start multiple points, drag for single line. Press {{shortcut}} again to change arrow type.",
"arrowTool": "Click to start multiple points, drag for single line. Press {{arrowShortcut}} again to change arrow type.",
"freeDraw": "Click and drag, release when you're finished",
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
"embeddable": "Click-drag to create a website embed",
"text_selected": "Double-click or press {{shortcut}} to edit text",
"text_editing": "Press {{shortcut_1}} or {{shortcut_2}} to finish editing",
"linearElementMulti": "Click on last point or press {{shortcut_1}} or {{shortcut_2}} to finish",
"lockAngle": "You can constrain angle by holding {{shortcut}}",
"resize": "You can constrain proportions by holding {{shortcut_1}} while resizing,\nhold {{shortcut_2}} to resize from the center",
"resizeImage": "You can resize freely by holding {{shortcut_1}},\nhold {{shortcut_2}} to resize from the center",
"rotate": "You can constrain angles by holding {{shortcut}} while rotating",
"lineEditor_info": "Hold {{shortcut_1}} and Double-click or press {{shortcut_2}} to edit points",
"lineEditor_line_info": "Double-click or press {{shortcut}} to edit points",
"lineEditor_pointSelected": "Press {{shortcut_1}} to remove point(s),\n{{shortcut_2}} to duplicate, or drag to move",
"lineEditor_nothingSelected": "Select a point to edit (hold {{shortcut_1}} to select multiple),\nor hold {{shortcut_2}} and click to add new points",
"text_selected": "Double-click or press ENTER to edit text",
"text_editing": "Press Escape or CtrlOrCmd+ENTER to finish editing",
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
"lockAngle": "You can constrain angle by holding SHIFT",
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
"resizeImage": "You can resize freely by holding SHIFT,\nhold ALT to resize from the center",
"rotate": "You can constrain angles by holding SHIFT while rotating",
"lineEditor_info": "Hold CtrlOrCmd and Double-click or press CtrlOrCmd + Enter to edit points",
"lineEditor_line_info": "Double-click or press Enter to edit points",
"lineEditor_pointSelected": "Press Delete to remove point(s),\nCtrlOrCmd+D to duplicate, or drag to move",
"lineEditor_nothingSelected": "Select a point to edit (hold SHIFT to select multiple),\nor hold Alt and click to add new points",
"publishLibrary": "Publish your own library",
"bindTextToElement": "{{shortcut}} to add text",
"createFlowchart": "{{shortcut}} to create a flowchart",
"deepBoxSelect": "Hold {{shortcut}} to deep select, and to prevent dragging",
"eraserRevert": "Hold {{shortcut}} to revert the elements marked for deletion",
"bindTextToElement": "Press enter to add text",
"createFlowchart": "Hold CtrlOrCmd and Arrow key to create a flowchart",
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
"disableSnapping": "Hold {{shortcut}} to disable snapping",
"enterCropEditor": "Double click the image or press {{shortcut}} to crop the image",
"leaveCropEditor": "Click outside the image or press {{shortcut_1}} or {{shortcut_2}} to finish cropping"
"disableSnapping": "Hold CtrlOrCmd to disable snapping",
"enterCropEditor": "Double click the image or press ENTER to crop the image",
"leaveCropEditor": "Click outside the image or press ENTER or ESCAPE to finish cropping"
},
"canvasError": {
"cannotShowPreview": "Cannot show preview",
@@ -648,17 +648,5 @@
},
"itemNotAvailable": "Command is not available...",
"shortcutHint": "For Command palette, use {{shortcut}}"
},
"keys": {
"ctrl": "Ctrl",
"option": "Option",
"cmd": "Cmd",
"alt": "Alt",
"escape": "Esc",
"enter": "Enter",
"shift": "Shift",
"spacebar": "Space",
"delete": "Delete",
"mmb": "Scroll wheel"
}
}

View File

@@ -22,7 +22,7 @@ import {
getOmitSidesForDevice,
getTransformHandles,
getTransformHandlesFromCoords,
hasBoundingBox,
shouldShowBoundingBox,
} from "@excalidraw/element";
import {
isElbowArrow,
@@ -892,7 +892,7 @@ const _renderInteractiveScene = ({
// Paint selected elements
if (!appState.multiElement && !appState.selectedLinearElement?.isEditing) {
const showBoundingBox = hasBoundingBox(selectedElements, appState);
const showBoundingBox = shouldShowBoundingBox(selectedElements, appState);
const isSingleLinearElementSelected =
selectedElements.length === 1 && isLinearElement(selectedElements[0]);

View File

@@ -1,19 +0,0 @@
import { isDarwin } from "@excalidraw/common";
import { t } from "./i18n";
export const getShortcutKey = (shortcut: string): string =>
shortcut
.replace(
/\b(Opt(?:ion)?|Alt)\b/i,
isDarwin ? t("keys.option") : t("keys.alt"),
)
.replace(/\bShift\b/i, t("keys.shift"))
.replace(/\b(Enter|Return)\b/i, t("keys.enter"))
.replace(
/\b(Ctrl|Cmd|Command|CtrlOrCmd)\b/gi,
isDarwin ? t("keys.cmd") : t("keys.ctrl"),
)
.replace(/\b(Esc(?:ape)?)\b/i, t("keys.escape"))
.replace(/\b(Space(?:bar)?)\b/i, t("keys.spacebar"))
.replace(/\b(Del(?:ete)?)\b/i, t("keys.delete"));