diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index 0125f0288..784d6c4b0 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -2,7 +2,6 @@ import { ToolButton } from "../components/ToolButton"; import { DuplicateIcon } from "../components/icons"; import { DEFAULT_GRID_SIZE } from "../constants"; import { duplicateElement, getNonDeletedElements } from "../element"; -import { fixBindingsAfterDuplication } from "../element/binding"; import { bindTextToShapeAfterDuplication, getBoundTextElement, @@ -10,6 +9,8 @@ import { } from "../element/textElement"; import { hasBoundTextElement, + isArrowElement, + isBindableElement, isBoundToContainer, isFrameLikeElement, } from "../element/typeChecks"; @@ -40,11 +41,18 @@ import { invariant, } from "../utils"; +import { type ElementUpdate, mutateElement } from "../element/mutateElement"; + import { register } from "./register"; import type { ActionResult } from "./types"; -import type { ExcalidrawElement } from "../element/types"; +import type { + BoundElement, + ExcalidrawArrowElement, + ExcalidrawElement, +} from "../element/types"; import type { AppState } from "../types"; +import type { Mutable } from "../utility-types"; export const actionDuplicateSelection = register({ name: "duplicateSelection", @@ -325,11 +333,103 @@ const duplicateElements = ( oldElements, oldIdToDuplicatedId, ); - fixBindingsAfterDuplication( - elementsWithClones, - oldElements, - oldIdToDuplicatedId, - ); + // fixBindingsAfterDuplication( + // elementsWithClones, + // oldElements, + // oldIdToDuplicatedId, + // ); + + newElements + .map((element) => { + oldElements.includes(element) && console.error("oldElements", element); + + if (isArrowElement(element)) { + const updates: Mutable> = {}; + + if (element.startBinding) { + const startCloneElementId = oldIdToDuplicatedId.get( + element.startBinding.elementId, + ); + + if (startCloneElementId) { + // The connected element was duplicated, so we need to update the binding + updates.startBinding = { + ...element.startBinding, + elementId: startCloneElementId, + }; + } else { + // The connected element was not duplicated, so we need to remove the binding + updates.startBinding = null; + } + } + + if (element.endBinding) { + const endCloneElementId = oldIdToDuplicatedId.get( + element.endBinding.elementId, + ); + + if (endCloneElementId) { + // The connected element was duplicated, so we need to update the binding + updates.endBinding = { + ...element.endBinding, + elementId: endCloneElementId, + }; + } else { + // The connected element was not duplicated, so we need to remove the binding + updates.endBinding = null; + } + } + + if (Object.keys(updates).length > 0) { + // Only update the element if there are updates to apply + return { + element, + updates, + }; + } + } else if (isBindableElement(element)) { + if (element.boundElements?.length) { + const clonedBoundElements = element.boundElements + ?.map((definition) => { + const clonedBoundElementId = oldIdToDuplicatedId.get( + definition.id, + ); + if (clonedBoundElementId) { + // The connected element was duplicated, so we need to update the binding + return { + ...definition, + id: clonedBoundElementId, + }; + } + + // The connected element was not duplicated, so we need to remove the binding + return null; + }) + .filter( + (definition): definition is BoundElement => definition !== null, + ); + + if (clonedBoundElements?.length) { + return { + element, + updates: { + boundElements: clonedBoundElements, + }, + }; + } + } + } + + return null; + }) + .forEach((change) => { + if (!change) { + return; + } + + mutateElement(change.element, change.updates); + }); + bindElementsToFramesAfterDuplication( elementsWithClones, oldElements, diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index d11c4c20f..deab51bc7 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -658,7 +658,6 @@ export const duplicateElement = ( } copy.id = regenerateId(); - copy.boundElements = null; copy.updated = getUpdatedTimestamp(); copy.seed = randomInteger(); copy.groupIds = getNewGroupIdsForDuplication(