diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index d9471d657..9e8a26eed 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3099,12 +3099,18 @@ class App extends React.Component { }, ); - const nextElements = [ + const allElements = [ ...this.scene.getElementsIncludingDeleted(), ...newElements, ]; - this.scene.replaceAllElements(nextElements); + const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y }); + + if (topLayerFrame) { + addElementsToFrame(allElements, newElements, topLayerFrame); + } + + this.scene.replaceAllElements(allElements); newElements.forEach((newElement) => { if (isTextElement(newElement) && isBoundToContainer(newElement)) { diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index db58bb626..7056574f4 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -398,12 +398,28 @@ export const addElementsToFrame = ( const finalElementsToAdd: ExcalidrawElement[] = []; + const otherFrames = new Set(); + + for (const element of elementsToAdd) { + if (isFrameLikeElement(element) && element.id !== frame.id) { + otherFrames.add(element.id); + } + } + // - add bound text elements if not already in the array // - filter out elements that are already in the frame for (const element of omitGroupsContainingFrameLikes( allElements, elementsToAdd, )) { + // don't add frames or their children + if ( + isFrameLikeElement(element) || + (element.frameId && otherFrames.has(element.frameId)) + ) { + continue; + } + if (!currTargetFrameChildrenMap.has(element.id)) { finalElementsToAdd.push(element); } diff --git a/packages/excalidraw/tests/clipboard.test.tsx b/packages/excalidraw/tests/clipboard.test.tsx index 38d7b49d5..ce00e2da5 100644 --- a/packages/excalidraw/tests/clipboard.test.tsx +++ b/packages/excalidraw/tests/clipboard.test.tsx @@ -263,3 +263,33 @@ describe("Paste bound text container", () => { }); }); }); + +describe("pasting & frames", () => { + it("should add pasted elements to frame under cursor", async () => { + const frame = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 0, + y: 0, + }); + const rect = API.createElement({ type: "rectangle" }); + + h.elements = [frame]; + + const clipboardJSON = await serializeAsClipboardJSON({ + elements: [rect], + files: null, + }); + + mouse.moveTo(50, 50); + + pasteWithCtrlCmdV(clipboardJSON); + + await waitFor(() => { + expect(h.elements.length).toBe(2); + expect(h.elements[1].type).toBe(rect.type); + expect(h.elements[1].frameId).toBe(frame.id); + }); + }); +}); diff --git a/packages/excalidraw/tests/helpers/ui.ts b/packages/excalidraw/tests/helpers/ui.ts index f37ac0019..58579fe93 100644 --- a/packages/excalidraw/tests/helpers/ui.ts +++ b/packages/excalidraw/tests/helpers/ui.ts @@ -206,6 +206,8 @@ export class Pointer { moveTo(x: number = this.clientX, y: number = this.clientY) { this.clientX = x; this.clientY = y; + // fire "mousemove" to update editor cursor position + fireEvent.mouseMove(document, this.getEvent()); fireEvent.pointerMove(GlobalTestState.interactiveCanvas, this.getEvent()); }