diff --git a/packages/excalidraw/actions/actionFrame.ts b/packages/excalidraw/actions/actionFrame.ts index cafb68d95d..ffa33b6d7c 100644 --- a/packages/excalidraw/actions/actionFrame.ts +++ b/packages/excalidraw/actions/actionFrame.ts @@ -12,6 +12,8 @@ import { frameToolIcon } from "../components/icons"; import { StoreAction } from "../store"; import { getSelectedElements } from "../scene"; import { newFrameElement } from "../element/newElement"; +import { getElementsInGroup } from "../groups"; +import { mutateElement } from "../element/mutateElement"; const isSingleFrameSelected = ( appState: UIAppState, @@ -174,6 +176,26 @@ export const actionWrapSelectionInFrame = register({ height: y2 - y1 + PADDING * 2, }); + // for a selected partial group, we want to remove it from the remainder of the group + if (appState.editingGroupId) { + const elementsInGroup = getElementsInGroup( + selectedElements, + appState.editingGroupId, + ); + + for (const elementInGroup of elementsInGroup) { + const index = elementInGroup.groupIds.indexOf(appState.editingGroupId); + + mutateElement( + elementInGroup, + { + groupIds: elementInGroup.groupIds.slice(0, index), + }, + false, + ); + } + } + const nextElements = addElementsToFrame( [...app.scene.getElementsIncludingDeleted(), frame], selectedElements, diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index 922e627d0b..7734e88f77 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -369,12 +369,57 @@ export const getElementsInNewFrame = ( frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap, ) => { - return omitGroupsContainingFrameLikes( - elements, - getElementsCompletelyInFrame(elements, frame, elementsMap), + return omitPartialGroups( + omitGroupsContainingFrameLikes( + elements, + getElementsCompletelyInFrame(elements, frame, elementsMap), + ), + frame, + elementsMap, ); }; +export const omitPartialGroups = ( + elements: ExcalidrawElement[], + frame: ExcalidrawFrameLikeElement, + allElementsMap: ElementsMap, +) => { + const elementsToReturn = []; + const checkedGroups = new Map(); + + for (const element of elements) { + let shouldOmit = false; + if (element.groupIds.length > 0) { + // if some partial group should be omitted, then all elements in that group should be omitted + if (element.groupIds.some((gid) => checkedGroups.get(gid))) { + shouldOmit = true; + } else { + const allElementsInGroup = new Set( + element.groupIds.flatMap((gid) => + getElementsInGroup(allElementsMap, gid), + ), + ); + + shouldOmit = !elementsAreInFrameBounds( + Array.from(allElementsInGroup), + frame, + allElementsMap, + ); + } + + element.groupIds.forEach((gid) => { + checkedGroups.set(gid, shouldOmit); + }); + } + + if (!shouldOmit) { + elementsToReturn.push(element); + } + } + + return elementsToReturn; +}; + export const getContainingFrame = ( element: ExcalidrawElement, elementsMap: ElementsMap,