diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 313ae4e0eb..69e854b0b0 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -23,8 +23,6 @@ import { WINDOWS_EMOJI_FALLBACK_FONT, } from "./constants"; -import { isDarwin } from "./editorInterface"; - import type { MaybePromise, ResolutionType } from "./utility-types"; import type { EVENT } from "./constants"; @@ -425,19 +423,6 @@ 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 }, { diff --git a/packages/element/src/transformHandles.ts b/packages/element/src/transformHandles.ts index d562716948..b686cb49f4 100644 --- a/packages/element/src/transformHandles.ts +++ b/packages/element/src/transformHandles.ts @@ -1,5 +1,6 @@ import { DEFAULT_TRANSFORM_HANDLE_SPACING, + isMobileOrTablet, type EditorInterface, } from "@excalidraw/common"; @@ -325,7 +326,7 @@ export const getTransformHandles = ( ); }; -export const shouldShowBoundingBox = ( +export const hasBoundingBox = ( elements: readonly NonDeletedExcalidrawElement[], appState: InteractiveCanvasAppState, ) => { @@ -344,5 +345,7 @@ export const shouldShowBoundingBox = ( return true; } - return element.points.length > 2; + // 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(); }; diff --git a/packages/element/tests/binding.test.tsx b/packages/element/tests/binding.test.tsx index a3da1c66d9..8690439782 100644 --- a/packages/element/tests/binding.test.tsx +++ b/packages/element/tests/binding.test.tsx @@ -10,6 +10,8 @@ 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, @@ -413,16 +415,12 @@ describe("element binding", () => { expect(arrow.endBinding?.elementId).toBe(rectRight.id); // Drag arrow off of bound rectangle range - const handles = getTransformHandles( + const [elX, elY] = LinearElementEditor.getPointAtIndexGlobalCoordinates( arrow, - h.state.zoom, - arrayToMap(h.elements), - "mouse", - ).se!; - + -1, + h.scene.getNonDeletedElementsMap(), + ); 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(); diff --git a/packages/excalidraw/actions/actionAlign.tsx b/packages/excalidraw/actions/actionAlign.tsx index 63a887635b..2bbe4fab97 100644 --- a/packages/excalidraw/actions/actionAlign.tsx +++ b/packages/excalidraw/actions/actionAlign.tsx @@ -4,7 +4,7 @@ import { isFrameLikeElement } from "@excalidraw/element"; import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element"; -import { KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common"; +import { KEYS, arrayToMap } from "@excalidraw/common"; import { alignElements } from "@excalidraw/element"; @@ -30,6 +30,8 @@ import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; +import { getShortcutKey } from "../shortcut"; + import { register } from "./register"; import type { AppClassProperties, AppState, UIAppState } from "../types"; diff --git a/packages/excalidraw/actions/actionBoundText.tsx b/packages/excalidraw/actions/actionBoundText.tsx index 606770dde5..383668c756 100644 --- a/packages/excalidraw/actions/actionBoundText.tsx +++ b/packages/excalidraw/actions/actionBoundText.tsx @@ -8,6 +8,7 @@ import { } from "@excalidraw/common"; import { getOriginalContainerHeightFromCache, + isBoundToContainer, resetOriginalContainerCache, updateOriginalContainerCache, } from "@excalidraw/element"; @@ -225,7 +226,9 @@ export const actionWrapTextInContainer = register({ trackEvent: { category: "element" }, predicate: (elements, appState, _, app) => { const selectedElements = app.scene.getSelectedElements(appState); - const someTextElements = selectedElements.some((el) => isTextElement(el)); + const someTextElements = selectedElements.some( + (el) => isTextElement(el) && !isBoundToContainer(el), + ); return selectedElements.length > 0 && someTextElements; }, perform: (elements, appState, _, app) => { @@ -234,7 +237,7 @@ export const actionWrapTextInContainer = register({ const containerIds: Mutable = {}; for (const textElement of selectedElements) { - if (isTextElement(textElement)) { + if (isTextElement(textElement) && !isBoundToContainer(textElement)) { const container = newElement({ type: "rectangle", backgroundColor: appState.currentItemBackgroundColor, diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx index 4aa22b0bdd..b8f837b402 100644 --- a/packages/excalidraw/actions/actionCanvas.tsx +++ b/packages/excalidraw/actions/actionCanvas.tsx @@ -7,7 +7,6 @@ import { MIN_ZOOM, THEME, ZOOM_STEP, - getShortcutKey, updateActiveTool, CODES, KEYS, @@ -46,6 +45,7 @@ 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"; diff --git a/packages/excalidraw/actions/actionDistribute.tsx b/packages/excalidraw/actions/actionDistribute.tsx index f02906741c..88e085f1de 100644 --- a/packages/excalidraw/actions/actionDistribute.tsx +++ b/packages/excalidraw/actions/actionDistribute.tsx @@ -2,7 +2,7 @@ import { getNonDeletedElements } from "@excalidraw/element"; import { isFrameLikeElement } from "@excalidraw/element"; -import { CODES, KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common"; +import { CODES, KEYS, arrayToMap } from "@excalidraw/common"; import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element"; @@ -26,6 +26,8 @@ import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; +import { getShortcutKey } from "../shortcut"; + import { register } from "./register"; import type { AppClassProperties, AppState } from "../types"; diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index 570faa503b..462803d205 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -3,7 +3,6 @@ import { KEYS, MOBILE_ACTION_BUTTON_BG, arrayToMap, - getShortcutKey, } from "@excalidraw/common"; import { getNonDeletedElements } from "@excalidraw/element"; @@ -26,6 +25,7 @@ import { DuplicateIcon } from "../components/icons"; import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; +import { getShortcutKey } from "../shortcut"; import { useStylesPanelMode } from ".."; diff --git a/packages/excalidraw/actions/actionGroup.tsx b/packages/excalidraw/actions/actionGroup.tsx index dc0c22efdb..c72216b761 100644 --- a/packages/excalidraw/actions/actionGroup.tsx +++ b/packages/excalidraw/actions/actionGroup.tsx @@ -14,7 +14,7 @@ import { replaceAllElementsInFrame, } from "@excalidraw/element"; -import { KEYS, randomId, arrayToMap, getShortcutKey } from "@excalidraw/common"; +import { KEYS, randomId, arrayToMap } from "@excalidraw/common"; import { getSelectedGroupIds, @@ -43,6 +43,8 @@ import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; +import { getShortcutKey } from "../shortcut"; + import { register } from "./register"; import type { AppClassProperties, AppState } from "../types"; diff --git a/packages/excalidraw/actions/actionLink.tsx b/packages/excalidraw/actions/actionLink.tsx index abb78f7f51..34b3326b57 100644 --- a/packages/excalidraw/actions/actionLink.tsx +++ b/packages/excalidraw/actions/actionLink.tsx @@ -1,6 +1,6 @@ import { isEmbeddableElement } from "@excalidraw/element"; -import { KEYS, getShortcutKey } from "@excalidraw/common"; +import { KEYS } 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"; diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 662ab7ae61..c356456ac1 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -17,7 +17,6 @@ import { randomInteger, arrayToMap, getFontFamilyString, - getShortcutKey, getLineHeight, isTransparent, reduceToCommonValue, @@ -141,6 +140,8 @@ import { restoreCaretPosition, } from "../hooks/useTextEditorFocus"; +import { getShortcutKey } from "../shortcut"; + import { register } from "./register"; import type { AppClassProperties, AppState, Primitive } from "../types"; diff --git a/packages/excalidraw/actions/actionZindex.tsx b/packages/excalidraw/actions/actionZindex.tsx index 62a6aa411f..1269ed23f6 100644 --- a/packages/excalidraw/actions/actionZindex.tsx +++ b/packages/excalidraw/actions/actionZindex.tsx @@ -1,4 +1,4 @@ -import { KEYS, CODES, getShortcutKey, isDarwin } from "@excalidraw/common"; +import { KEYS, CODES, isDarwin } from "@excalidraw/common"; import { moveOneLeft, @@ -16,6 +16,7 @@ import { SendToBackIcon, } from "../components/icons"; import { t } from "../i18n"; +import { getShortcutKey } from "../shortcut"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/shortcuts.ts b/packages/excalidraw/actions/shortcuts.ts index 1a13f1703c..ca593c3402 100644 --- a/packages/excalidraw/actions/shortcuts.ts +++ b/packages/excalidraw/actions/shortcuts.ts @@ -1,8 +1,9 @@ -import { isDarwin, getShortcutKey } from "@excalidraw/common"; +import { isDarwin } from "@excalidraw/common"; import type { SubtypeOf } from "@excalidraw/common/utility-types"; import { t } from "../i18n"; +import { getShortcutKey } from "../shortcut"; import type { ActionName } from "./types"; diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 2b4beacdb8..e161b49527 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -77,7 +77,6 @@ import { wrapEvent, updateObject, updateActiveTool, - getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, @@ -106,6 +105,7 @@ import { type StylesPanelMode, loadDesktopUIModePreference, setDesktopUIMode, + isMobileOrTablet, } from "@excalidraw/common"; import { @@ -174,7 +174,7 @@ import { getContainerElement, isValidTextContainer, redrawTextBoundingBox, - shouldShowBoundingBox, + hasBoundingBox, getFrameChildren, isCursorInFrame, addElementsToFrame, @@ -407,6 +407,8 @@ import { LassoTrail } from "../lasso"; import { EraserTrail } from "../eraser"; +import { getShortcutKey } from "../shortcut"; + import ConvertElementTypePopup, { getConversionTypeFromElements, convertElementTypePopupAtom, @@ -5255,7 +5257,7 @@ class App extends React.Component { if ( considerBoundingBox && this.state.selectedElementIds[element.id] && - shouldShowBoundingBox([element], this.state) + hasBoundingBox([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) @@ -6158,7 +6160,13 @@ class App extends React.Component { (!this.state.selectedLinearElement || this.state.selectedLinearElement.hoverPointIndex === -1) && this.state.openDialog?.name !== "elementLinkSelector" && - !(selectedElements.length === 1 && isElbowArrow(selectedElements[0])) + !(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) + ) ) { const elementWithTransformHandleType = getElementWithTransformHandleType( @@ -7281,14 +7289,8 @@ class App extends React.Component { !this.state.selectedLinearElement?.isEditing && !isElbowArrow(selectedElements[0]) && !( - isLineElement(selectedElements[0]) && - LinearElementEditor.getPointIndexUnderCursor( - selectedElements[0], - elementsMap, - this.state.zoom, - pointerDownState.origin.x, - pointerDownState.origin.y, - ) !== -1 + isLinearElement(selectedElements[0]) && + (isMobileOrTablet() || selectedElements[0].points.length === 2) ) && !( this.state.selectedLinearElement && @@ -11203,6 +11205,17 @@ class App extends React.Component { return [actionCopy, ...options]; } + const zIndexActions: ContextMenuItems = + this.editorInterface.formFactor === "desktop" + ? [ + CONTEXT_MENU_SEPARATOR, + actionSendBackward, + actionBringForward, + actionSendToBack, + actionBringToFront, + ] + : []; + return [ CONTEXT_MENU_SEPARATOR, actionCut, @@ -11228,11 +11241,7 @@ class App extends React.Component { actionUngroup, CONTEXT_MENU_SEPARATOR, actionAddToLibrary, - CONTEXT_MENU_SEPARATOR, - actionSendBackward, - actionBringForward, - actionSendToBack, - actionBringToFront, + ...zIndexActions, CONTEXT_MENU_SEPARATOR, actionFlipHorizontal, actionFlipVertical, diff --git a/packages/excalidraw/components/ColorPicker/ColorInput.tsx b/packages/excalidraw/components/ColorPicker/ColorInput.tsx index fc737584b1..7de0af41e4 100644 --- a/packages/excalidraw/components/ColorPicker/ColorInput.tsx +++ b/packages/excalidraw/components/ColorPicker/ColorInput.tsx @@ -1,8 +1,9 @@ import clsx from "clsx"; import { useCallback, useEffect, useRef, useState } from "react"; -import { KEYS, getShortcutKey } from "@excalidraw/common"; +import { KEYS } from "@excalidraw/common"; +import { getShortcutKey } from "../..//shortcut"; import { useAtom } from "../../editor-jotai"; import { t } from "../../i18n"; import { useEditorInterface } from "../App"; diff --git a/packages/excalidraw/components/CommandPalette/CommandPalette.tsx b/packages/excalidraw/components/CommandPalette/CommandPalette.tsx index 35b8ee6fb7..3e922328b8 100644 --- a/packages/excalidraw/components/CommandPalette/CommandPalette.tsx +++ b/packages/excalidraw/components/CommandPalette/CommandPalette.tsx @@ -7,12 +7,13 @@ 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 { diff --git a/packages/excalidraw/components/ContextMenu.scss b/packages/excalidraw/components/ContextMenu.scss index 3bedf2ba7a..0b370d919b 100644 --- a/packages/excalidraw/components/ContextMenu.scss +++ b/packages/excalidraw/components/ContextMenu.scss @@ -1,6 +1,10 @@ @import "../css/variables.module.scss"; .excalidraw { + .context-menu-popover { + z-index: var(--zIndex-ui-context-menu); + } + .context-menu { position: relative; border-radius: 4px; diff --git a/packages/excalidraw/components/ContextMenu.tsx b/packages/excalidraw/components/ContextMenu.tsx index 3295d1d099..4cef2bb177 100644 --- a/packages/excalidraw/components/ContextMenu.tsx +++ b/packages/excalidraw/components/ContextMenu.tsx @@ -64,6 +64,7 @@ export const ContextMenu = React.memo( offsetTop={appState.offsetTop} viewportWidth={appState.width} viewportHeight={appState.height} + className="context-menu-popover" >
    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); + } } } } diff --git a/packages/excalidraw/components/HintViewer.tsx b/packages/excalidraw/components/HintViewer.tsx index 50d0a10bd9..1fd255c8fe 100644 --- a/packages/excalidraw/components/HintViewer.tsx +++ b/packages/excalidraw/components/HintViewer.tsx @@ -9,11 +9,12 @@ import { isTextElement, } from "@excalidraw/element"; -import { getShortcutKey, type EditorInterface } from "@excalidraw/common"; - import { isNodeInFlowchart } from "@excalidraw/element"; +import type { EditorInterface } from "@excalidraw/common"; + import { t } from "../i18n"; +import { getShortcutKey } from "../shortcut"; import { isEraserActive } from "../appState"; import { isGridModeEnabled } from "../snapping"; @@ -28,6 +29,11 @@ interface HintViewerProps { app: AppClassProperties; } +const getTaggedShortcutKey = (key: string | string[]) => + Array.isArray(key) + ? `${key.map(getShortcutKey).join(" + ")}` + : `${getShortcutKey(key)}`; + const getHints = ({ appState, isMobile, @@ -42,7 +48,9 @@ const getHints = ({ appState.openSidebar.tab === CANVAS_SEARCH_TAB && appState.searchMatches?.matches.length ) { - return t("hints.dismissSearch"); + return t("hints.dismissSearch", { + shortcut: getTaggedShortcutKey("Escape"), + }); } if (appState.openSidebar && !editorInterface.canFitSidebar) { @@ -50,14 +58,21 @@ const getHints = ({ } if (isEraserActive(appState)) { - return t("hints.eraserRevert"); + return t("hints.eraserRevert", { + shortcut: getTaggedShortcutKey("Alt"), + }); } if (activeTool.type === "arrow" || activeTool.type === "line") { if (multiMode) { - return t("hints.linearElementMulti"); + return t("hints.linearElementMulti", { + shortcut_1: getTaggedShortcutKey("Escape"), + shortcut_2: getTaggedShortcutKey("Enter"), + }); } if (activeTool.type === "arrow") { - return t("hints.arrowTool", { arrowShortcut: getShortcutKey("A") }); + return t("hints.arrowTool", { + shortcut: getTaggedShortcutKey("A"), + }); } return t("hints.linearElement"); } @@ -83,31 +98,51 @@ const getHints = ({ ) { const targetElement = selectedElements[0]; if (isLinearElement(targetElement) && targetElement.points.length === 2) { - return t("hints.lockAngle"); + return t("hints.lockAngle", { + shortcut: getTaggedShortcutKey("Shift"), + }); } return isImageElement(targetElement) - ? t("hints.resizeImage") - : t("hints.resize"); + ? t("hints.resizeImage", { + shortcut_1: getTaggedShortcutKey("Shift"), + shortcut_2: getTaggedShortcutKey("Alt"), + }) + : t("hints.resize", { + shortcut_1: getTaggedShortcutKey("Shift"), + shortcut_2: getTaggedShortcutKey("Alt"), + }); } if (isRotating && lastPointerDownWith === "mouse") { - return t("hints.rotate"); + return t("hints.rotate", { + shortcut: getTaggedShortcutKey("Shift"), + }); } if (selectedElements.length === 1 && isTextElement(selectedElements[0])) { - return t("hints.text_selected"); + return t("hints.text_selected", { + shortcut: getTaggedShortcutKey("Enter"), + }); } if (appState.editingTextElement) { - return t("hints.text_editing"); + return t("hints.text_editing", { + shortcut_1: getTaggedShortcutKey("Escape"), + shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "Enter"]), + }); } if (appState.croppingElementId) { - return t("hints.leaveCropEditor"); + return t("hints.leaveCropEditor", { + shortcut_1: getTaggedShortcutKey("Enter"), + shortcut_2: getTaggedShortcutKey("Escape"), + }); } if (selectedElements.length === 1 && isImageElement(selectedElements[0])) { - return t("hints.enterCropEditor"); + return t("hints.enterCropEditor", { + shortcut: getTaggedShortcutKey("Enter"), + }); } if (activeTool.type === "selection") { @@ -117,33 +152,57 @@ const getHints = ({ !appState.editingTextElement && !appState.selectedLinearElement?.isEditing ) { - return [t("hints.deepBoxSelect")]; + return t("hints.deepBoxSelect", { + shortcut: getTaggedShortcutKey("CtrlOrCmd"), + }); } if (isGridModeEnabled(app) && appState.selectedElementsAreBeingDragged) { - return t("hints.disableSnapping"); + return t("hints.disableSnapping", { + shortcut: getTaggedShortcutKey("CtrlOrCmd"), + }); } if (!selectedElements.length && !isMobile) { - return [t("hints.canvasPanning")]; + return t("hints.canvasPanning", { + shortcut_1: getTaggedShortcutKey(t("keys.mmb")), + shortcut_2: getTaggedShortcutKey("Space"), + }); } if (selectedElements.length === 1) { if (isLinearElement(selectedElements[0])) { if (appState.selectedLinearElement?.isEditing) { return appState.selectedLinearElement.selectedPointsIndices - ? t("hints.lineEditor_pointSelected") - : t("hints.lineEditor_nothingSelected"); + ? 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"), + }); } return isLineElement(selectedElements[0]) - ? t("hints.lineEditor_line_info") - : t("hints.lineEditor_info"); + ? t("hints.lineEditor_line_info", { + shortcut: getTaggedShortcutKey("Enter"), + }) + : t("hints.lineEditor_info", { + shortcut_1: getTaggedShortcutKey("CtrlOrCmd"), + shortcut_2: getTaggedShortcutKey(["CtrlOrCmd", "Enter"]), + }); } 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( @@ -151,13 +210,13 @@ const getHints = ({ app.scene.getNonDeletedElementsMap(), ) ) { - return [t("hints.bindTextToElement"), t("hints.createFlowchart")]; + return [bindTextToElement, createFlowchart]; } - return [t("hints.bindTextToElement"), t("hints.createFlowchart")]; + return [bindTextToElement, createFlowchart]; } - return t("hints.bindTextToElement"); + return bindTextToElement; } } } @@ -183,16 +242,21 @@ export const HintViewer = ({ } const hint = Array.isArray(hints) - ? hints - .map((hint) => { - return getShortcutKey(hint).replace(/\. ?$/, ""); - }) - .join(". ") - : getShortcutKey(hints); + ? hints.map((hint) => hint.replace(/\. ?$/, "")).join(", ") + : hints; + + const hintJSX = hint.split(/([^<]+<\/kbd>)/g).map((part, index) => { + if (index % 2 === 1) { + const shortcutMatch = + part[0] === "<" && part.match(/^([^<]+)<\/kbd>$/); + return {shortcutMatch ? shortcutMatch[1] : part}; + } + return part; + }); return (
    - {hint} + {hintJSX}
    ); }; diff --git a/packages/excalidraw/components/IconPicker.tsx b/packages/excalidraw/components/IconPicker.tsx index 246160ce9c..fab4f109b8 100644 --- a/packages/excalidraw/components/IconPicker.tsx +++ b/packages/excalidraw/components/IconPicker.tsx @@ -8,7 +8,7 @@ import { atom, useAtom } from "../editor-jotai"; import { getLanguage, t } from "../i18n"; import Collapsible from "./Stats/Collapsible"; -import { useEditorInterface } from "./App"; +import { useEditorInterface, useExcalidrawContainer } from "./App"; import "./IconPicker.scss"; @@ -39,6 +39,7 @@ function Picker({ numberOfOptionsToAlwaysShow?: number; }) { const editorInterface = useEditorInterface(); + const { container } = useExcalidrawContainer(); const handleKeyDown = (event: React.KeyboardEvent) => { const pressedOption = options.find( @@ -161,6 +162,7 @@ function Picker({ sideOffset={isMobile ? 8 : 12} style={{ zIndex: "var(--zIndex-ui-styles-popup)" }} onKeyDown={handleKeyDown} + collisionBoundary={container ?? undefined} >
    { const popoverRef = useRef(null); @@ -146,7 +150,7 @@ export const Popover = ({ }, [onCloseRequest]); return ( -
    +
    {children}
    ); diff --git a/packages/excalidraw/components/PropertiesPopover.tsx b/packages/excalidraw/components/PropertiesPopover.tsx index c59b031a44..151d8eff16 100644 --- a/packages/excalidraw/components/PropertiesPopover.tsx +++ b/packages/excalidraw/components/PropertiesPopover.tsx @@ -40,6 +40,8 @@ export const PropertiesPopover = React.forwardRef< ref, ) => { const editorInterface = useEditorInterface(); + const isMobilePortrait = + editorInterface.formFactor === "phone" && !editorInterface.isLandscape; return ( @@ -47,20 +49,11 @@ export const PropertiesPopover = React.forwardRef< ref={ref} className={clsx("focus-visible-none", className)} data-prevent-outside-click - side={ - editorInterface.formFactor === "phone" && - !editorInterface.isLandscape - ? "bottom" - : "right" - } - align={ - editorInterface.formFactor === "phone" && - !editorInterface.isLandscape - ? "center" - : "start" - } + side={isMobilePortrait ? "bottom" : "right"} + align={isMobilePortrait ? "center" : "start"} alignOffset={-16} sideOffset={20} + collisionBoundary={container ?? undefined} style={{ zIndex: "var(--zIndex-ui-styles-popup)", marginLeft: diff --git a/packages/excalidraw/components/TTDDialog/TTDDialogSubmitShortcut.tsx b/packages/excalidraw/components/TTDDialog/TTDDialogSubmitShortcut.tsx index 05cad640b8..21a6f16948 100644 --- a/packages/excalidraw/components/TTDDialog/TTDDialogSubmitShortcut.tsx +++ b/packages/excalidraw/components/TTDDialog/TTDDialogSubmitShortcut.tsx @@ -1,4 +1,4 @@ -import { getShortcutKey } from "@excalidraw/common"; +import { getShortcutKey } from "@excalidraw/excalidraw/shortcut"; export const TTDDialogSubmitShortcut = () => { return ( diff --git a/packages/excalidraw/components/ToolPopover.tsx b/packages/excalidraw/components/ToolPopover.tsx index 81d5726d5a..b1fed1cbf1 100644 --- a/packages/excalidraw/components/ToolPopover.tsx +++ b/packages/excalidraw/components/ToolPopover.tsx @@ -11,6 +11,8 @@ import { ToolButton } from "./ToolButton"; import "./ToolPopover.scss"; +import { useExcalidrawContainer } from "./App"; + import type { AppClassProperties } from "../types"; type ToolOption = { @@ -50,6 +52,7 @@ 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) { @@ -90,6 +93,7 @@ export const ToolPopover = ({ {options.map(({ type, icon, title }) => ( + 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"));