diff --git a/src/actions/actionClipboard.tsx b/src/actions/actionClipboard.tsx index fdbe41dc..93dc36ba 100644 --- a/src/actions/actionClipboard.tsx +++ b/src/actions/actionClipboard.tsx @@ -17,7 +17,8 @@ export const actionCopy = register({ }; }, contextItemLabel: "labels.copy", - keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.C, + // don't supply a shortcut since we handle this conditionally via onCopy event + keyTest: undefined, }); export const actionCut = register({ diff --git a/src/components/App.tsx b/src/components/App.tsx index 7455e416..9ce43b7a 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1047,6 +1047,21 @@ class App extends React.Component { }); private onCopy = withBatchedUpdates((event: ClipboardEvent) => { + const activeSelection = document.getSelection(); + // if there's a selected text is outside the component, prevent our copy + // action + if ( + activeSelection?.anchorNode && + // it can happen that certain interactions will create a selection + // outside (or potentially inside) the component without actually + // selecting anything (i.e. the selection range is collapsed). Copying + // in such case wouldn't copy anything to the clipboard anyway, so prevent + // our copy handler only if the selection isn't collapsed + !activeSelection.isCollapsed && + !this.excalidrawContainerRef.current!.contains(activeSelection.anchorNode) + ) { + return; + } if (isWritableElement(event.target)) { return; } @@ -2118,6 +2133,14 @@ class App extends React.Component { ) => { event.persist(); + // remove any active selection when we start to interact with canvas + // (mainly, we care about removing selection outside the component which + // would prevent our copy handling otherwise) + const selection = document.getSelection(); + if (selection?.anchorNode) { + selection.removeAllRanges(); + } + this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event); this.maybeCleanupAfterMissingPointerUp(event);