123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741 |
- import {
- ExcalidrawLinearElement,
- ExcalidrawBindableElement,
- NonDeleted,
- NonDeletedExcalidrawElement,
- PointBinding,
- ExcalidrawElement,
- } from "./types";
- import { getElementAtPosition } from "../scene";
- import { AppState } from "../types";
- import {
- isBindableElement,
- isBindingElement,
- isLinearElement,
- } from "./typeChecks";
- import {
- bindingBorderTest,
- distanceToBindableElement,
- maxBindingGap,
- determineFocusDistance,
- intersectElementWithLine,
- determineFocusPoint,
- } from "./collision";
- import { mutateElement } from "./mutateElement";
- import Scene from "../scene/Scene";
- import { LinearElementEditor } from "./linearElementEditor";
- import { arrayToMap, tupleToCoors } from "../utils";
- import { KEYS } from "../keys";
- export type SuggestedBinding =
- | NonDeleted<ExcalidrawBindableElement>
- | SuggestedPointBinding;
- export type SuggestedPointBinding = [
- NonDeleted<ExcalidrawLinearElement>,
- "start" | "end" | "both",
- NonDeleted<ExcalidrawBindableElement>,
- ];
- export const shouldEnableBindingForPointerEvent = (
- event: React.PointerEvent<HTMLCanvasElement>,
- ) => {
- return !event[KEYS.CTRL_OR_CMD];
- };
- export const isBindingEnabled = (appState: AppState): boolean => {
- return appState.isBindingEnabled;
- };
- const getNonDeletedElements = (
- scene: Scene,
- ids: readonly ExcalidrawElement["id"][],
- ): NonDeleted<ExcalidrawElement>[] => {
- const result: NonDeleted<ExcalidrawElement>[] = [];
- ids.forEach((id) => {
- const element = scene.getNonDeletedElement(id);
- if (element != null) {
- result.push(element);
- }
- });
- return result;
- };
- export const bindOrUnbindLinearElement = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- startBindingElement: ExcalidrawBindableElement | null | "keep",
- endBindingElement: ExcalidrawBindableElement | null | "keep",
- ): void => {
- const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
- const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
- bindOrUnbindLinearElementEdge(
- linearElement,
- startBindingElement,
- endBindingElement,
- "start",
- boundToElementIds,
- unboundFromElementIds,
- );
- bindOrUnbindLinearElementEdge(
- linearElement,
- endBindingElement,
- startBindingElement,
- "end",
- boundToElementIds,
- unboundFromElementIds,
- );
- const onlyUnbound = Array.from(unboundFromElementIds).filter(
- (id) => !boundToElementIds.has(id),
- );
- getNonDeletedElements(Scene.getScene(linearElement)!, onlyUnbound).forEach(
- (element) => {
- mutateElement(element, {
- boundElements: element.boundElements?.filter(
- (element) =>
- element.type !== "arrow" || element.id !== linearElement.id,
- ),
- });
- },
- );
- };
- const bindOrUnbindLinearElementEdge = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- bindableElement: ExcalidrawBindableElement | null | "keep",
- otherEdgeBindableElement: ExcalidrawBindableElement | null | "keep",
- startOrEnd: "start" | "end",
- // Is mutated
- boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
- // Is mutated
- unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
- ): void => {
- if (bindableElement !== "keep") {
- if (bindableElement != null) {
- // Don't bind if we're trying to bind or are already bound to the same
- // element on the other edge already ("start" edge takes precedence).
- if (
- otherEdgeBindableElement == null ||
- (otherEdgeBindableElement === "keep"
- ? !isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
- linearElement,
- bindableElement,
- startOrEnd,
- )
- : startOrEnd === "start" ||
- otherEdgeBindableElement.id !== bindableElement.id)
- ) {
- bindLinearElement(linearElement, bindableElement, startOrEnd);
- boundToElementIds.add(bindableElement.id);
- }
- } else {
- const unbound = unbindLinearElement(linearElement, startOrEnd);
- if (unbound != null) {
- unboundFromElementIds.add(unbound);
- }
- }
- }
- };
- export const bindOrUnbindSelectedElements = (
- elements: NonDeleted<ExcalidrawElement>[],
- ): void => {
- elements.forEach((element) => {
- if (isBindingElement(element)) {
- bindOrUnbindLinearElement(
- element,
- getElligibleElementForBindingElement(element, "start"),
- getElligibleElementForBindingElement(element, "end"),
- );
- } else if (isBindableElement(element)) {
- maybeBindBindableElement(element);
- }
- });
- };
- const maybeBindBindableElement = (
- bindableElement: NonDeleted<ExcalidrawBindableElement>,
- ): void => {
- getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
- ([linearElement, where]) =>
- bindOrUnbindLinearElement(
- linearElement,
- where === "end" ? "keep" : bindableElement,
- where === "start" ? "keep" : bindableElement,
- ),
- );
- };
- export const maybeBindLinearElement = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- appState: AppState,
- scene: Scene,
- pointerCoords: { x: number; y: number },
- ): void => {
- if (appState.startBoundElement != null) {
- bindLinearElement(linearElement, appState.startBoundElement, "start");
- }
- const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
- if (
- hoveredElement != null &&
- !isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
- linearElement,
- hoveredElement,
- "end",
- )
- ) {
- bindLinearElement(linearElement, hoveredElement, "end");
- }
- };
- const bindLinearElement = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- hoveredElement: ExcalidrawBindableElement,
- startOrEnd: "start" | "end",
- ): void => {
- mutateElement(linearElement, {
- [startOrEnd === "start" ? "startBinding" : "endBinding"]: {
- elementId: hoveredElement.id,
- ...calculateFocusAndGap(linearElement, hoveredElement, startOrEnd),
- } as PointBinding,
- });
- const boundElementsMap = arrayToMap(hoveredElement.boundElements || []);
- if (!boundElementsMap.has(linearElement.id)) {
- mutateElement(hoveredElement, {
- boundElements: (hoveredElement.boundElements || []).concat({
- id: linearElement.id,
- type: "arrow",
- }),
- });
- }
- };
- // Don't bind both ends of a simple segment
- const isLinearElementSimpleAndAlreadyBoundOnOppositeEdge = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- bindableElement: ExcalidrawBindableElement,
- startOrEnd: "start" | "end",
- ): boolean => {
- const otherBinding =
- linearElement[startOrEnd === "start" ? "endBinding" : "startBinding"];
- return isLinearElementSimpleAndAlreadyBound(
- linearElement,
- otherBinding?.elementId,
- bindableElement,
- );
- };
- export const isLinearElementSimpleAndAlreadyBound = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- alreadyBoundToId: ExcalidrawBindableElement["id"] | undefined,
- bindableElement: ExcalidrawBindableElement,
- ): boolean => {
- return (
- alreadyBoundToId === bindableElement.id && linearElement.points.length < 3
- );
- };
- export const unbindLinearElements = (
- elements: NonDeleted<ExcalidrawElement>[],
- ): void => {
- elements.forEach((element) => {
- if (isBindingElement(element)) {
- bindOrUnbindLinearElement(element, null, null);
- }
- });
- };
- const unbindLinearElement = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- startOrEnd: "start" | "end",
- ): ExcalidrawBindableElement["id"] | null => {
- const field = startOrEnd === "start" ? "startBinding" : "endBinding";
- const binding = linearElement[field];
- if (binding == null) {
- return null;
- }
- mutateElement(linearElement, { [field]: null });
- return binding.elementId;
- };
- export const getHoveredElementForBinding = (
- pointerCoords: {
- x: number;
- y: number;
- },
- scene: Scene,
- ): NonDeleted<ExcalidrawBindableElement> | null => {
- const hoveredElement = getElementAtPosition(
- scene.getNonDeletedElements(),
- (element) =>
- isBindableElement(element, false) &&
- bindingBorderTest(element, pointerCoords),
- );
- return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
- };
- const calculateFocusAndGap = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- hoveredElement: ExcalidrawBindableElement,
- startOrEnd: "start" | "end",
- ): { focus: number; gap: number } => {
- const direction = startOrEnd === "start" ? -1 : 1;
- const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
- const adjacentPointIndex = edgePointIndex - direction;
- const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
- linearElement,
- edgePointIndex,
- );
- const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
- linearElement,
- adjacentPointIndex,
- );
- return {
- focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
- gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
- };
- };
- // Supports translating, rotating and scaling `changedElement` with bound
- // linear elements.
- // Because scaling involves moving the focus points as well, it is
- // done before the `changedElement` is updated, and the `newSize` is passed
- // in explicitly.
- export const updateBoundElements = (
- changedElement: NonDeletedExcalidrawElement,
- options?: {
- simultaneouslyUpdated?: readonly ExcalidrawElement[];
- newSize?: { width: number; height: number };
- },
- ) => {
- const boundLinearElements = (changedElement.boundElements ?? []).filter(
- (el) => el.type === "arrow",
- );
- if (boundLinearElements.length === 0) {
- return;
- }
- const { newSize, simultaneouslyUpdated } = options ?? {};
- const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
- simultaneouslyUpdated,
- );
- getNonDeletedElements(
- Scene.getScene(changedElement)!,
- boundLinearElements.map((el) => el.id),
- ).forEach((element) => {
- if (!isLinearElement(element)) {
- return;
- }
- const bindableElement = changedElement as ExcalidrawBindableElement;
- // In case the boundElements are stale
- if (!doesNeedUpdate(element, bindableElement)) {
- return;
- }
- const startBinding = maybeCalculateNewGapWhenScaling(
- bindableElement,
- element.startBinding,
- newSize,
- );
- const endBinding = maybeCalculateNewGapWhenScaling(
- bindableElement,
- element.endBinding,
- newSize,
- );
- // `linearElement` is being moved/scaled already, just update the binding
- if (simultaneouslyUpdatedElementIds.has(element.id)) {
- mutateElement(element, { startBinding, endBinding });
- return;
- }
- updateBoundPoint(
- element,
- "start",
- startBinding,
- changedElement as ExcalidrawBindableElement,
- );
- updateBoundPoint(
- element,
- "end",
- endBinding,
- changedElement as ExcalidrawBindableElement,
- );
- });
- };
- const doesNeedUpdate = (
- boundElement: NonDeleted<ExcalidrawLinearElement>,
- changedElement: ExcalidrawBindableElement,
- ) => {
- return (
- boundElement.startBinding?.elementId === changedElement.id ||
- boundElement.endBinding?.elementId === changedElement.id
- );
- };
- const getSimultaneouslyUpdatedElementIds = (
- simultaneouslyUpdated: readonly ExcalidrawElement[] | undefined,
- ): Set<ExcalidrawElement["id"]> => {
- return new Set((simultaneouslyUpdated || []).map((element) => element.id));
- };
- const updateBoundPoint = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- startOrEnd: "start" | "end",
- binding: PointBinding | null | undefined,
- changedElement: ExcalidrawBindableElement,
- ): void => {
- if (
- binding == null ||
- // We only need to update the other end if this is a 2 point line element
- (binding.elementId !== changedElement.id && linearElement.points.length > 2)
- ) {
- return;
- }
- const bindingElement = Scene.getScene(linearElement)!.getElement(
- binding.elementId,
- ) as ExcalidrawBindableElement | null;
- if (bindingElement == null) {
- // We're not cleaning up after deleted elements atm., so handle this case
- return;
- }
- const direction = startOrEnd === "start" ? -1 : 1;
- const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
- const adjacentPointIndex = edgePointIndex - direction;
- const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
- linearElement,
- adjacentPointIndex,
- );
- const focusPointAbsolute = determineFocusPoint(
- bindingElement,
- binding.focus,
- adjacentPoint,
- );
- let newEdgePoint;
- // The linear element was not originally pointing inside the bound shape,
- // we can point directly at the focus point
- if (binding.gap === 0) {
- newEdgePoint = focusPointAbsolute;
- } else {
- const intersections = intersectElementWithLine(
- bindingElement,
- adjacentPoint,
- focusPointAbsolute,
- binding.gap,
- );
- if (intersections.length === 0) {
- // This should never happen, since focusPoint should always be
- // inside the element, but just in case, bail out
- newEdgePoint = focusPointAbsolute;
- } else {
- // Guaranteed to intersect because focusPoint is always inside the shape
- newEdgePoint = intersections[0];
- }
- }
- LinearElementEditor.movePoints(
- linearElement,
- [
- {
- index: edgePointIndex,
- point: LinearElementEditor.pointFromAbsoluteCoords(
- linearElement,
- newEdgePoint,
- ),
- },
- ],
- { [startOrEnd === "start" ? "startBinding" : "endBinding"]: binding },
- );
- };
- const maybeCalculateNewGapWhenScaling = (
- changedElement: ExcalidrawBindableElement,
- currentBinding: PointBinding | null | undefined,
- newSize: { width: number; height: number } | undefined,
- ): PointBinding | null | undefined => {
- if (currentBinding == null || newSize == null) {
- return currentBinding;
- }
- const { gap, focus, elementId } = currentBinding;
- const { width: newWidth, height: newHeight } = newSize;
- const { width, height } = changedElement;
- const newGap = Math.max(
- 1,
- Math.min(
- maxBindingGap(changedElement, newWidth, newHeight),
- gap * (newWidth < newHeight ? newWidth / width : newHeight / height),
- ),
- );
- return { elementId, gap: newGap, focus };
- };
- export const getEligibleElementsForBinding = (
- elements: NonDeleted<ExcalidrawElement>[],
- ): SuggestedBinding[] => {
- const includedElementIds = new Set(elements.map(({ id }) => id));
- return elements.flatMap((element) =>
- isBindingElement(element, false)
- ? (getElligibleElementsForBindingElement(
- element as NonDeleted<ExcalidrawLinearElement>,
- ).filter(
- (element) => !includedElementIds.has(element.id),
- ) as SuggestedBinding[])
- : isBindableElement(element, false)
- ? getElligibleElementsForBindableElementAndWhere(element).filter(
- (binding) => !includedElementIds.has(binding[0].id),
- )
- : [],
- );
- };
- const getElligibleElementsForBindingElement = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- ): NonDeleted<ExcalidrawBindableElement>[] => {
- return [
- getElligibleElementForBindingElement(linearElement, "start"),
- getElligibleElementForBindingElement(linearElement, "end"),
- ].filter(
- (element): element is NonDeleted<ExcalidrawBindableElement> =>
- element != null,
- );
- };
- const getElligibleElementForBindingElement = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- startOrEnd: "start" | "end",
- ): NonDeleted<ExcalidrawBindableElement> | null => {
- return getHoveredElementForBinding(
- getLinearElementEdgeCoors(linearElement, startOrEnd),
- Scene.getScene(linearElement)!,
- );
- };
- const getLinearElementEdgeCoors = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- startOrEnd: "start" | "end",
- ): { x: number; y: number } => {
- const index = startOrEnd === "start" ? 0 : -1;
- return tupleToCoors(
- LinearElementEditor.getPointAtIndexGlobalCoordinates(linearElement, index),
- );
- };
- const getElligibleElementsForBindableElementAndWhere = (
- bindableElement: NonDeleted<ExcalidrawBindableElement>,
- ): SuggestedPointBinding[] => {
- return Scene.getScene(bindableElement)!
- .getNonDeletedElements()
- .map((element) => {
- if (!isBindingElement(element, false)) {
- return null;
- }
- const canBindStart = isLinearElementEligibleForNewBindingByBindable(
- element,
- "start",
- bindableElement,
- );
- const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
- element,
- "end",
- bindableElement,
- );
- if (!canBindStart && !canBindEnd) {
- return null;
- }
- return [
- element,
- canBindStart && canBindEnd ? "both" : canBindStart ? "start" : "end",
- bindableElement,
- ];
- })
- .filter((maybeElement) => maybeElement != null) as SuggestedPointBinding[];
- };
- const isLinearElementEligibleForNewBindingByBindable = (
- linearElement: NonDeleted<ExcalidrawLinearElement>,
- startOrEnd: "start" | "end",
- bindableElement: NonDeleted<ExcalidrawBindableElement>,
- ): boolean => {
- const existingBinding =
- linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
- return (
- existingBinding == null &&
- !isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
- linearElement,
- bindableElement,
- startOrEnd,
- ) &&
- bindingBorderTest(
- bindableElement,
- getLinearElementEdgeCoors(linearElement, startOrEnd),
- )
- );
- };
- // We need to:
- // 1: Update elements not selected to point to duplicated elements
- // 2: Update duplicated elements to point to other duplicated elements
- export const fixBindingsAfterDuplication = (
- sceneElements: readonly ExcalidrawElement[],
- oldElements: readonly ExcalidrawElement[],
- oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
- // There are three copying mechanisms: Copy-paste, duplication and alt-drag.
- // Only when alt-dragging the new "duplicates" act as the "old", while
- // the "old" elements act as the "new copy" - essentially working reverse
- // to the other two.
- duplicatesServeAsOld?: "duplicatesServeAsOld" | undefined,
- ): void => {
- // First collect all the binding/bindable elements, so we only update
- // each once, regardless of whether they were duplicated or not.
- const allBoundElementIds: Set<ExcalidrawElement["id"]> = new Set();
- const allBindableElementIds: Set<ExcalidrawElement["id"]> = new Set();
- const shouldReverseRoles = duplicatesServeAsOld === "duplicatesServeAsOld";
- oldElements.forEach((oldElement) => {
- const { boundElements } = oldElement;
- if (boundElements != null && boundElements.length > 0) {
- boundElements.forEach((boundElement) => {
- if (shouldReverseRoles && !oldIdToDuplicatedId.has(boundElement.id)) {
- allBoundElementIds.add(boundElement.id);
- }
- });
- allBindableElementIds.add(oldIdToDuplicatedId.get(oldElement.id)!);
- }
- if (isBindingElement(oldElement)) {
- if (oldElement.startBinding != null) {
- const { elementId } = oldElement.startBinding;
- if (shouldReverseRoles && !oldIdToDuplicatedId.has(elementId)) {
- allBindableElementIds.add(elementId);
- }
- }
- if (oldElement.endBinding != null) {
- const { elementId } = oldElement.endBinding;
- if (shouldReverseRoles && !oldIdToDuplicatedId.has(elementId)) {
- allBindableElementIds.add(elementId);
- }
- }
- if (oldElement.startBinding != null || oldElement.endBinding != null) {
- allBoundElementIds.add(oldIdToDuplicatedId.get(oldElement.id)!);
- }
- }
- });
- // Update the linear elements
- (
- sceneElements.filter(({ id }) =>
- allBoundElementIds.has(id),
- ) as ExcalidrawLinearElement[]
- ).forEach((element) => {
- const { startBinding, endBinding } = element;
- mutateElement(element, {
- startBinding: newBindingAfterDuplication(
- startBinding,
- oldIdToDuplicatedId,
- ),
- endBinding: newBindingAfterDuplication(endBinding, oldIdToDuplicatedId),
- });
- });
- // Update the bindable shapes
- sceneElements
- .filter(({ id }) => allBindableElementIds.has(id))
- .forEach((bindableElement) => {
- const { boundElements } = bindableElement;
- if (boundElements != null && boundElements.length > 0) {
- mutateElement(bindableElement, {
- boundElements: boundElements.map((boundElement) =>
- oldIdToDuplicatedId.has(boundElement.id)
- ? {
- id: oldIdToDuplicatedId.get(boundElement.id)!,
- type: boundElement.type,
- }
- : boundElement,
- ),
- });
- }
- });
- };
- const newBindingAfterDuplication = (
- binding: PointBinding | null,
- oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
- ): PointBinding | null => {
- if (binding == null) {
- return null;
- }
- const { elementId, focus, gap } = binding;
- return {
- focus,
- gap,
- elementId: oldIdToDuplicatedId.get(elementId) ?? elementId,
- };
- };
- export const fixBindingsAfterDeletion = (
- sceneElements: readonly ExcalidrawElement[],
- deletedElements: readonly ExcalidrawElement[],
- ): void => {
- const deletedElementIds = new Set(
- deletedElements.map((element) => element.id),
- );
- // non-deleted which bindings need to be updated
- const affectedElements: Set<ExcalidrawElement["id"]> = new Set();
- deletedElements.forEach((deletedElement) => {
- if (isBindableElement(deletedElement)) {
- deletedElement.boundElements?.forEach((element) => {
- if (!deletedElementIds.has(element.id)) {
- affectedElements.add(element.id);
- }
- });
- } else if (isBindingElement(deletedElement)) {
- if (deletedElement.startBinding) {
- affectedElements.add(deletedElement.startBinding.elementId);
- }
- if (deletedElement.endBinding) {
- affectedElements.add(deletedElement.endBinding.elementId);
- }
- }
- });
- sceneElements
- .filter(({ id }) => affectedElements.has(id))
- .forEach((element) => {
- if (isBindableElement(element)) {
- mutateElement(element, {
- boundElements: newBoundElementsAfterDeletion(
- element.boundElements,
- deletedElementIds,
- ),
- });
- } else if (isBindingElement(element)) {
- mutateElement(element, {
- startBinding: newBindingAfterDeletion(
- element.startBinding,
- deletedElementIds,
- ),
- endBinding: newBindingAfterDeletion(
- element.endBinding,
- deletedElementIds,
- ),
- });
- }
- });
- };
- const newBindingAfterDeletion = (
- binding: PointBinding | null,
- deletedElementIds: Set<ExcalidrawElement["id"]>,
- ): PointBinding | null => {
- if (binding == null || deletedElementIds.has(binding.elementId)) {
- return null;
- }
- return binding;
- };
- const newBoundElementsAfterDeletion = (
- boundElements: ExcalidrawElement["boundElements"],
- deletedElementIds: Set<ExcalidrawElement["id"]>,
- ) => {
- if (!boundElements) {
- return null;
- }
- return boundElements.filter((ele) => !deletedElementIds.has(ele.id));
- };
|