This is an automated email from the ASF dual-hosted git repository.

thiagoelg pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new 9d43f3cc24d kie-issues#2312: BPMN Editor: Cannot nest Sub-processes 
within Sub-processes (#3580)
9d43f3cc24d is described below

commit 9d43f3cc24d58cdbdbc59b9891a6799f0f936df2
Author: Rajalakshmy S <[email protected]>
AuthorDate: Sat May 30 08:02:05 2026 +0530

    kie-issues#2312: BPMN Editor: Cannot nest Sub-processes within 
Sub-processes (#3580)
    
    Co-authored-by: Rajalakshmy S <[email protected]>
---
 packages/bpmn-editor/src/diagram/BpmnDiagram.tsx   |  94 +++++++---
 .../bpmn-editor/src/diagram/BpmnDiagramDomain.tsx  |   1 +
 .../bpmn-editor/src/mutations/addConnectedNode.ts  |  22 ++-
 packages/bpmn-editor/src/mutations/addEdge.ts      |  22 ++-
 .../src/mutations/moveNodesInsideSubProcess.ts     |  72 +++----
 .../src/mutations/moveNodesOutOfSubProcess.ts      | 206 +++++++++++++++++----
 .../bpmn-editor/src/store/computeDiagramData.ts    | 115 ++++++++----
 7 files changed, 384 insertions(+), 148 deletions(-)

diff --git a/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx 
b/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
index a4bcd38248d..d3dc93f6d55 100644
--- a/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
+++ b/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
@@ -54,6 +54,7 @@ import { addEdge } from "../mutations/addEdge";
 import { addEdgeWaypoint } from "../mutations/addEdgeWaypoint";
 import { moveNodesInsideLane } from "../mutations/moveNodesInsideLane";
 import { moveNodesInsideSubProcess } from 
"../mutations/moveNodesInsideSubProcess";
+import { findParentFlowElements, findParentSubProcess } from 
"../mutations/moveNodesOutOfSubProcess";
 import { addStandaloneNode } from "../mutations/addStandaloneNode";
 import { deleteEdge } from "../mutations/deleteEdge";
 import { deleteEdgeWaypoint } from "../mutations/deleteEdgeWaypoint";
@@ -276,10 +277,15 @@ export function BpmnDiagram({
       console.log("BPMN EDITOR DIAGRAM: onNodeUnparented");
       if (exParentNode.type === NODE_TYPES.subProcess) {
         // ContainmentMode was INSIDE
+
+        const targetSubProcessId =
+          dropTarget?.node?.type === NODE_TYPES.subProcess ? 
dropTarget.node.data.bpmnElement?.["@_id"] : null;
+
         moveNodesOutOfSubProcess({
           definitions: state.bpmn.model.definitions,
           __readonly_subProcessId: exParentNode.data.bpmnElement?.["@_id"],
           __readonly_nodeIds: selectedNodes.flatMap((s) => 
s.data.bpmnElement?.["@_id"] ?? []),
+          __readonly_targetSubProcessId: targetSubProcessId,
         });
       }
 
@@ -382,40 +388,72 @@ export function BpmnDiagram({
 
   // edges
 
+  const findCommonParentContainer = useCallback((state: State, sourceNodeId: 
string, targetNodeId: string) => {
+    const process = state.bpmn.model.definitions.rootElement?.find((e) => 
e.__$$element === "process");
+    if (!process) {
+      return { parentFlowElements: undefined, parentArtifacts: undefined };
+    }
+
+    const sourceParentFlowElements = 
findParentFlowElements(process.flowElement ?? [], sourceNodeId);
+    const targetParentFlowElements = 
findParentFlowElements(process.flowElement ?? [], targetNodeId);
+
+    const parentFlowElements =
+      sourceParentFlowElements === targetParentFlowElements ? 
sourceParentFlowElements : undefined;
+
+    const sourceParentSubProcess = findParentSubProcess(process.flowElement ?? 
[], sourceNodeId);
+    const targetParentSubProcess = findParentSubProcess(process.flowElement ?? 
[], targetNodeId);
+
+    const parentArtifacts =
+      sourceParentSubProcess === targetParentSubProcess ? 
sourceParentSubProcess?.artifact : undefined;
+
+    return { parentFlowElements, parentArtifacts };
+  }, []);
+
   const onEdgeAdded = useCallback<
     OnEdgeAdded<State, BpmnNodeType, BpmnEdgeType, BpmnDiagramNodeData, 
BpmnDiagramEdgeData>
-  >(({ state, edgeType, sourceNode, targetNode, targetHandle }) => {
-    console.log("BPMN EDITOR DIAGRAM: onEdgeAdded");
-    addEdge({
-      definitions: state.bpmn.model.definitions,
-      __readonly_edge: {
-        type: edgeType as BpmnEdgeType,
-        targetHandle: targetHandle,
-        sourceHandle: PositionalNodeHandleId.Center,
-        autoPositionedEdgeMarker: undefined,
-        name: undefined,
-        documentation: undefined,
-      },
-      __readonly_sourceNode: {
-        type: sourceNode.type as BpmnNodeType,
-        href: sourceNode.id,
-        bounds: sourceNode.data.shape["dc:Bounds"],
-        shapeId: sourceNode.data.shape["@_id"],
-      },
-      __readonly_targetNode: {
-        type: targetNode.type as BpmnNodeType,
-        href: targetNode.id,
-        bounds: targetNode.data.shape["dc:Bounds"],
-        shapeId: targetNode.data.shape["@_id"],
-      },
-      __readonly_keepWaypoints: false,
-    });
-  }, []);
+  >(
+    ({ state, edgeType, sourceNode, targetNode, targetHandle }) => {
+      console.log("BPMN EDITOR DIAGRAM: onEdgeAdded");
+
+      const { parentFlowElements, parentArtifacts } = 
findCommonParentContainer(state, sourceNode.id, targetNode.id);
+
+      addEdge({
+        definitions: state.bpmn.model.definitions,
+        __readonly_edge: {
+          type: edgeType as BpmnEdgeType,
+          targetHandle: targetHandle,
+          sourceHandle: PositionalNodeHandleId.Center,
+          autoPositionedEdgeMarker: undefined,
+          name: undefined,
+          documentation: undefined,
+        },
+        __readonly_sourceNode: {
+          type: sourceNode.type as BpmnNodeType,
+          href: sourceNode.id,
+          bounds: sourceNode.data.shape["dc:Bounds"],
+          shapeId: sourceNode.data.shape["@_id"],
+        },
+        __readonly_targetNode: {
+          type: targetNode.type as BpmnNodeType,
+          href: targetNode.id,
+          bounds: targetNode.data.shape["dc:Bounds"],
+          shapeId: targetNode.data.shape["@_id"],
+        },
+        __readonly_keepWaypoints: false,
+        __readonly_parentFlowElements: parentFlowElements,
+        __readonly_parentArtifacts: parentArtifacts,
+      });
+    },
+    [findCommonParentContainer]
+  );
 
   const onEdgeUpdated = useCallback<
     OnEdgeUpdated<State, BpmnNodeType, BpmnEdgeType, BpmnDiagramNodeData, 
BpmnDiagramEdgeData>
   >(({ state, edge, targetNode, sourceNode, targetHandle, sourceHandle, 
firstWaypoint, lastWaypoint }) => {
     console.log("BPMN EDITOR DIAGRAM: onEdgeUpdated");
+
+    const { parentFlowElements, parentArtifacts } = 
findCommonParentContainer(state, sourceNode.id, targetNode.id);
+
     const { newBpmnEdge } = addEdge({
       definitions: state.bpmn.model.definitions,
       __readonly_edge: {
@@ -444,6 +482,8 @@ export function BpmnDiagram({
         shapeId: targetNode.data.shape["@_id"],
       },
       __readonly_keepWaypoints: true,
+      __readonly_parentFlowElements: parentFlowElements,
+      __readonly_parentArtifacts: parentArtifacts,
     });
 
     // The BPMN Edge changed nodes, so we need to delete the old one, but keep 
the waypoints.
diff --git a/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx 
b/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
index 7a76d2d1be9..0ab98cd8443 100644
--- a/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
+++ b/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
@@ -294,6 +294,7 @@ export const BPMN_CONTAINMENT_MAP: 
ContainmentMap<BpmnNodeType> = new Map<
           NODE_TYPES.intermediateCatchEvent,
           NODE_TYPES.intermediateThrowEvent,
           NODE_TYPES.gateway,
+          NODE_TYPES.subProcess,
           NODE_TYPES.endEvent,
           NODE_TYPES.textAnnotation,
           NODE_TYPES.group,
diff --git a/packages/bpmn-editor/src/mutations/addConnectedNode.ts 
b/packages/bpmn-editor/src/mutations/addConnectedNode.ts
index d8f4db3e40e..8a1ddd9ece8 100644
--- a/packages/bpmn-editor/src/mutations/addConnectedNode.ts
+++ b/packages/bpmn-editor/src/mutations/addConnectedNode.ts
@@ -29,6 +29,7 @@ import { addOrGetProcessAndDiagramElements } from 
"./addOrGetProcessAndDiagramEl
 import { NodeNature, nodeNatures } from "./_NodeNature";
 import { addEdge } from "./addEdge";
 import { PositionalNodeHandleId } from 
"@kie-tools/xyflow-react-kie-diagram/dist/nodes/PositionalNodeHandles";
+import { findParentFlowElements, findParentSubProcess } from 
"./moveNodesOutOfSubProcess";
 
 export function addConnectedNode({
   definitions,
@@ -51,10 +52,15 @@ export function addConnectedNode({
 
   const { process, diagramElements } = addOrGetProcessAndDiagramElements({ 
definitions });
 
-  if (newNodeNature === NodeNature.PROCESS_FLOW_ELEMENT || newNodeNature === 
NodeNature.CONTAINER) {
-    process.flowElement ??= [];
+  const parentFlowElements = findParentFlowElements(
+    process.flowElement ?? [],
+    __readonly_sourceNode.bpmnElement["@_id"]
+  );
+
+  const targetFlowElements = parentFlowElements ?? process.flowElement ?? [];
 
-    process.flowElement?.push(
+  if (newNodeNature === NodeNature.PROCESS_FLOW_ELEMENT || newNodeNature === 
NodeNature.CONTAINER) {
+    targetFlowElements.push(
       switchExpression(
         __readonly_newNode.type as Exclude<
           BpmnNodeType,
@@ -107,8 +113,9 @@ export function addConnectedNode({
       )
     );
   } else if (newNodeNature === NodeNature.ARTIFACT) {
-    process.artifact ??= [];
-    process.artifact.push(
+    const parentSubProcess = findParentSubProcess(process.flowElement ?? [], 
__readonly_sourceNode.bpmnElement["@_id"]);
+    const targetArtifacts = parentSubProcess?.artifact ?? process.artifact ?? 
[];
+    targetArtifacts.push(
       ...switchExpression(__readonly_newNode.type as Extract<BpmnNodeType, 
"node_group" | "node_textAnnotation">, {
         [NODE_TYPES.group]: [
           {
@@ -175,6 +182,11 @@ export function addConnectedNode({
       href: newBpmnElementId,
       shapeId: newShapeId,
     },
+    __readonly_parentFlowElements: parentFlowElements,
+    __readonly_parentArtifacts:
+      newNodeNature === NodeNature.ARTIFACT
+        ? findParentSubProcess(process.flowElement ?? [], 
__readonly_sourceNode.bpmnElement["@_id"])?.artifact
+        : undefined,
   });
 
   return { id: newBpmnElementId, href: newBpmnElementId };
diff --git a/packages/bpmn-editor/src/mutations/addEdge.ts 
b/packages/bpmn-editor/src/mutations/addEdge.ts
index 2cf7b6b41e4..213ccfad102 100644
--- a/packages/bpmn-editor/src/mutations/addEdge.ts
+++ b/packages/bpmn-editor/src/mutations/addEdge.ts
@@ -48,6 +48,8 @@ export function addEdge({
   __readonly_targetNode,
   __readonly_edge,
   __readonly_keepWaypoints,
+  __readonly_parentFlowElements,
+  __readonly_parentArtifacts,
 }: {
   definitions: Normalized<BPMN20__tDefinitions>;
   __readonly_sourceNode: {
@@ -71,6 +73,8 @@ export function addEdge({
     autoPositionedEdgeMarker: AutoPositionedEdgeMarker | undefined;
   };
   __readonly_keepWaypoints: boolean;
+  __readonly_parentFlowElements?: Normalized<BPMN20__tProcess>["flowElement"];
+  __readonly_parentArtifacts?: Normalized<BPMN20__tProcess>["artifact"];
 }) {
   if (
     !_checkIsValidConnection(BPMN_GRAPH_STRUCTURE, __readonly_sourceNode, 
__readonly_targetNode, __readonly_edge.type)
@@ -88,7 +92,10 @@ export function addEdge({
 
   // Associations (incl. Compensation Associations)
   if (__readonly_edge.type === EDGE_TYPES.association || __readonly_edge.type 
=== EDGE_TYPES.compensationAssociation) {
-    process.artifact ??= [];
+    const targetArtifacts = __readonly_parentArtifacts ?? process.artifact ?? 
[];
+    if (!process.artifact) {
+      process.artifact = targetArtifacts;
+    }
 
     const newAssociation: Normalized<BPMN20__tAssociation> = {
       "@_id": newEdgeId,
@@ -99,13 +106,13 @@ export function addEdge({
 
     // Remove previously existing association
     const removed = removeFirstMatchIfPresent(
-      process.artifact,
+      targetArtifacts,
       (a) => a.__$$element === "association" && areEdgesEquivalent(a, 
newAssociation)
     );
     existingEdgeId = removed?.["@_id"];
 
     // Replace with the new one.
-    process.artifact?.push({
+    targetArtifacts.push({
       __$$element: "association",
       ...newAssociation,
       "@_id": tryKeepingEdgeId(existingEdgeId, newEdgeId),
@@ -114,7 +121,10 @@ export function addEdge({
 
   // Sequence Flows
   else {
-    process.flowElement ??= [];
+    const targetFlowElements = __readonly_parentFlowElements ?? 
process.flowElement ?? [];
+    if (!process.flowElement) {
+      process.flowElement = targetFlowElements;
+    }
 
     const newSequenceFlow: Normalized<BPMN20__tSequenceFlow> = {
       "@_id": newEdgeId,
@@ -126,14 +136,14 @@ export function addEdge({
 
     // Remove previously existing association
     const removed = removeFirstMatchIfPresent(
-      process.flowElement,
+      targetFlowElements,
       (a) => a.__$$element === "sequenceFlow" && areEdgesEquivalent(a, 
newSequenceFlow)
     );
     existingEdgeId = removed?.["@_id"];
     newEdgeId = tryKeepingEdgeId(existingEdgeId, newEdgeId);
 
     // Replace with the new one.
-    process.flowElement?.push({
+    targetFlowElements.push({
       __$$element: "sequenceFlow",
       ...newSequenceFlow,
       "@_id": newEdgeId,
diff --git a/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts 
b/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
index 63974bcfa35..8cd564b6426 100644
--- a/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
+++ b/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
@@ -23,6 +23,7 @@ import { State } from "../store/Store";
 import { addOrGetProcessAndDiagramElements } from 
"./addOrGetProcessAndDiagramElements";
 import { ElementExclusion } from "@kie-tools/xml-parser-ts/dist/elementFilter";
 import { BPMN20__tProcess } from 
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { isSubProcessElement, findSubProcessRecursively, 
shouldMoveSequenceFlow } from "./moveNodesOutOfSubProcess";
 
 export function moveNodesInsideSubProcess({
   definitions,
@@ -34,55 +35,58 @@ export function moveNodesInsideSubProcess({
   __readonly_nodeIds: string[];
 }) {
   const { process } = addOrGetProcessAndDiagramElements({ definitions });
-  const subProcess = process.flowElement?.find((s) => s["@_id"] === 
__readonly_subProcessId);
-  if (
-    !(
-      subProcess?.__$$element === "subProcess" ||
-      subProcess?.__$$element === "adHocSubProcess" ||
-      subProcess?.__$$element === "transaction"
-    )
-  ) {
-    throw new Error(`BPMN Element with id ${__readonly_subProcessId} is not a 
subProcess.`);
+
+  const subProcess = findSubProcessRecursively(process.flowElement ?? [], 
__readonly_subProcessId ?? "");
+  if (!subProcess) {
+    throw new Error(`Cannot find subprocess with ID: 
${__readonly_subProcessId}`);
   }
 
-  const flowElementsToMove: 
Normalized<Unpacked<NonNullable<BPMN20__tProcess["flowElement"]>>>[] = [];
+  const flowElementsToMove: 
Normalized<Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>>[] = [];
   const artifactsToMove: Normalized<
-    ElementExclusion<Unpacked<NonNullable<BPMN20__tProcess["artifact"]>>, 
"association">
+    ElementExclusion<Unpacked<Normalized<BPMN20__tProcess>["artifact"]>, 
"association">
   >[] = [];
 
   const nodeIdsToMoveInside = new Set(__readonly_nodeIds);
-  const subProcessNodes = new Set();
-  subProcess.flowElement?.forEach((flowElement) => {
+  const subProcessNodes = new Set<string>();
+  subProcess.flowElement?.forEach((flowElement: Normalized<Unpacked<typeof 
subProcess.flowElement>>) => {
     if (flowElement.__$$element !== "sequenceFlow") {
       subProcessNodes.add(flowElement["@_id"]);
     }
   });
 
-  for (let i = 0; i < (process.flowElement ?? []).length; i++) {
-    const flowElement = (process.flowElement ?? [])[i];
-    if (
-      nodeIdsToMoveInside.has(flowElement["@_id"]) ||
-      (flowElement.__$$element === "boundaryEvent" && 
nodeIdsToMoveInside.has(flowElement["@_attachedToRef"]))
-    ) {
-      flowElementsToMove.push(...((process.flowElement?.splice(i, 1) ?? []) as 
typeof flowElementsToMove));
-      i--; // repeat one index because we just altered the array we're 
iterating over.
-    } else if (
-      flowElement.__$$element === "sequenceFlow" &&
-      ((nodeIdsToMoveInside.has(flowElement["@_sourceRef"]) && 
nodeIdsToMoveInside.has(flowElement["@_targetRef"])) ||
-        (subProcessNodes.has(flowElement["@_sourceRef"]) && 
nodeIdsToMoveInside.has(flowElement["@_targetRef"])) ||
-        (nodeIdsToMoveInside.has(flowElement["@_sourceRef"]) && 
subProcessNodes.has(flowElement["@_targetRef"])))
-    ) {
-      // If the source and target are both outside of the sub-process
-      // or if the source and target is already in the sub process the 
sequenceFlow must be copied
-      flowElementsToMove.push(...((process.flowElement?.splice(i, 1) ?? []) as 
typeof flowElementsToMove));
-      i--; // repeat one index because we just altered the array we're 
iterating over.
+  const toMove: 
Normalized<Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>>[] = [];
+
+  const collectElements = (flowElements: 
Normalized<BPMN20__tProcess>["flowElement"]): void => {
+    if (!flowElements) {
+      return;
     }
-  }
+    for (let i = flowElements.length - 1; i >= 0; i--) {
+      const flowElement = flowElements[i];
+      if (
+        nodeIdsToMoveInside.has(flowElement["@_id"]) ||
+        (flowElement.__$$element === "boundaryEvent" &&
+          flowElement["@_attachedToRef"] &&
+          nodeIdsToMoveInside.has(flowElement["@_attachedToRef"]))
+      ) {
+        toMove.push(...flowElements.splice(i, 1));
+      } else if (shouldMoveSequenceFlow(flowElement, nodeIdsToMoveInside, 
subProcessNodes)) {
+        // If the source and target are both outside of the sub-process
+        // or if the source and target is already in the sub process the 
sequenceFlow must be copied
+        toMove.push(...flowElements.splice(i, 1));
+      } else if (isSubProcessElement(flowElement) && flowElement.flowElement) {
+        collectElements(flowElement.flowElement);
+      }
+    }
+  };
+
+  collectElements(process.flowElement ?? []);
+  flowElementsToMove.push(...toMove);
 
   for (let i = 0; i < (process.artifact ?? []).length; i++) {
     const artifact = (process.artifact ?? [])[i];
-    if (nodeIdsToMoveInside.has(artifact["@_id"])) {
-      artifactsToMove.push(...((process.artifact?.splice(i, 1) ?? []) as 
typeof artifactsToMove));
+    if (artifact.__$$element !== "association" && 
nodeIdsToMoveInside.has(artifact["@_id"])) {
+      const spliced = process.artifact?.splice(i, 1) ?? [];
+      artifactsToMove.push(...spliced.filter((a) => a.__$$element !== 
"association"));
       i--; // repeat one index because we just altered the array we're 
iterating over.
     }
   }
diff --git a/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts 
b/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
index a22d61326f8..e7576d888fd 100644
--- a/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
+++ b/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
@@ -24,61 +24,178 @@ import { Normalized } from "../normalization/normalize";
 import { State } from "../store/Store";
 import { addOrGetProcessAndDiagramElements } from 
"./addOrGetProcessAndDiagramElements";
 
+export type SubProcessElement = Normalized<
+  Extract<
+    Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>,
+    { __$$element: "subProcess" | "adHocSubProcess" | "transaction" }
+  >
+>;
+
+export function isSubProcessElement(element: { __$$element?: string }): 
element is SubProcessElement {
+  return (
+    element.__$$element === "subProcess" ||
+    element.__$$element === "adHocSubProcess" ||
+    element.__$$element === "transaction"
+  );
+}
+
+export function findSubProcessRecursively(
+  flowElements: Normalized<BPMN20__tProcess>["flowElement"],
+  subProcessId: string
+): SubProcessElement | undefined {
+  if (!flowElements) {
+    return undefined;
+  }
+  for (const element of flowElements) {
+    if (element["@_id"] === subProcessId && isSubProcessElement(element)) {
+      return element;
+    }
+    if (isSubProcessElement(element) && element.flowElement) {
+      const found = findSubProcessRecursively(element.flowElement, 
subProcessId);
+      if (found) {
+        return found;
+      }
+    }
+  }
+  return undefined;
+}
+
+export function findParentFlowElements(
+  flowElements: Normalized<BPMN20__tProcess>["flowElement"],
+  elementId: string
+): Normalized<BPMN20__tProcess>["flowElement"] | undefined {
+  if (!flowElements) {
+    return undefined;
+  }
+  for (const element of flowElements) {
+    if (element["@_id"] === elementId) {
+      return flowElements;
+    }
+    if (isSubProcessElement(element) && element.flowElement) {
+      const found = findParentFlowElements(element.flowElement, elementId);
+      if (found) {
+        return found;
+      }
+    }
+  }
+  return undefined;
+}
+
+export function findParentSubProcess(
+  flowElements: Normalized<BPMN20__tProcess>["flowElement"],
+  elementId: string
+): SubProcessElement | undefined {
+  if (!flowElements) {
+    return undefined;
+  }
+  for (const element of flowElements) {
+    if (isSubProcessElement(element) && element.flowElement) {
+      // Check if this subprocess directly contains the target
+      for (const child of element.flowElement) {
+        if (child["@_id"] === elementId) {
+          return element;
+        }
+      }
+      // Recurse into nested subprocesses
+      const found = findParentSubProcess(element.flowElement, elementId);
+      if (found) {
+        return found;
+      }
+    }
+  }
+  return undefined;
+}
+
+export function shouldMoveSequenceFlow(
+  flowElement: Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>,
+  nodeIds: Set<string>,
+  existingNodesIds: Set<string>
+): boolean {
+  return (
+    flowElement.__$$element === "sequenceFlow" &&
+    !!flowElement["@_sourceRef"] &&
+    !!flowElement["@_targetRef"] &&
+    ((nodeIds.has(flowElement["@_sourceRef"]) && 
nodeIds.has(flowElement["@_targetRef"])) ||
+      (existingNodesIds.has(flowElement["@_sourceRef"]) && 
nodeIds.has(flowElement["@_targetRef"])) ||
+      (nodeIds.has(flowElement["@_sourceRef"]) && 
existingNodesIds.has(flowElement["@_targetRef"])))
+  );
+}
+
 export function moveNodesOutOfSubProcess({
   definitions,
   __readonly_subProcessId,
   __readonly_nodeIds,
+  __readonly_targetSubProcessId,
 }: {
   definitions: State["bpmn"]["model"]["definitions"];
   __readonly_subProcessId: string | undefined;
   __readonly_nodeIds: string[];
+  __readonly_targetSubProcessId?: string | null;
 }) {
   const { process } = addOrGetProcessAndDiagramElements({ definitions });
-  const subProcess = process.flowElement?.find((s) => s["@_id"] === 
__readonly_subProcessId);
-  if (
-    !(
-      subProcess?.__$$element === "subProcess" ||
-      subProcess?.__$$element === "adHocSubProcess" ||
-      subProcess?.__$$element === "transaction"
-    )
-  ) {
-    throw new Error(`Can't find subProcess with ID 
${__readonly_subProcessId}`);
+
+  const subProcess = findSubProcessRecursively(process.flowElement ?? [], 
__readonly_subProcessId ?? "");
+  if (!subProcess) {
+    throw new Error(`Cannot find subprocess with ID: 
${__readonly_subProcessId}`);
   }
 
-  const flowElementsToMove: 
Normalized<Unpacked<NonNullable<BPMN20__tProcess["flowElement"]>>>[] = [];
+  let parentFlowElements: Normalized<BPMN20__tProcess>["flowElement"];
+  let targetSubProcess: SubProcessElement | undefined;
+
+  if (__readonly_targetSubProcessId) {
+    targetSubProcess = findSubProcessRecursively(process.flowElement ?? [], 
__readonly_targetSubProcessId);
+    if (!targetSubProcess) {
+      throw new Error(`Cannot find target subprocess with ID: 
${__readonly_targetSubProcessId}`);
+    }
+    targetSubProcess.flowElement ??= [];
+    parentFlowElements = targetSubProcess.flowElement;
+  } else if (__readonly_targetSubProcessId === null) {
+    process.flowElement ??= [];
+    parentFlowElements = process.flowElement;
+  } else {
+    parentFlowElements =
+      findParentFlowElements(process.flowElement ?? [], 
__readonly_subProcessId ?? "") ?? process.flowElement ?? [];
+  }
+
+  const flowElementsToMove: 
Normalized<Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>>[] = [];
   const artifactsToMove: Normalized<
-    ElementExclusion<Unpacked<NonNullable<BPMN20__tProcess["artifact"]>>, 
"association">
+    ElementExclusion<Unpacked<Normalized<BPMN20__tProcess>["artifact"]>, 
"association">
   >[] = [];
 
   const nodeIdsToMoveOut = new Set(__readonly_nodeIds);
-  const subProcessNodes = new Set();
-  subProcess.flowElement?.forEach((flowElement) => {
-    if (flowElement.__$$element !== "sequenceFlow") {
-      subProcessNodes.add(flowElement["@_id"]);
+
+  const subProcessNodes = new Set<string>();
+  const collectSubProcessNodeIds = (flowElements: 
Normalized<BPMN20__tProcess>["flowElement"]): void => {
+    if (!flowElements) {
+      return;
     }
-  });
+    for (const flowElement of flowElements) {
+      if (flowElement.__$$element !== "sequenceFlow" && flowElement["@_id"]) {
+        subProcessNodes.add(flowElement["@_id"]);
+      }
+      if (isSubProcessElement(flowElement) && flowElement.flowElement) {
+        collectSubProcessNodeIds(flowElement.flowElement);
+      }
+    }
+  };
+  collectSubProcessNodeIds(subProcess.flowElement ?? []);
 
-  // Check if we're moving out of an Event Sub-Process
   const isEventSubProcess = subProcess.__$$element === "subProcess" && 
(subProcess["@_triggeredByEvent"] ?? false);
 
-  for (let i = 0; i < (subProcess.flowElement ?? []).length; i++) {
-    const flowElement = (subProcess.flowElement ?? [])[i];
+  for (let i = (subProcess.flowElement ?? []).length - 1; i >= 0; i--) {
+    const flowElement = subProcess.flowElement![i];
     if (
-      nodeIdsToMoveOut.has(flowElement["@_id"]) ||
-      (flowElement.__$$element === "boundaryEvent" && 
nodeIdsToMoveOut.has(flowElement["@_attachedToRef"]))
-    ) {
-      flowElementsToMove.push(...((subProcess.flowElement?.splice(i, 1) ?? []) 
as typeof flowElementsToMove));
-      i--; // repeat one index because we just altered the array we're 
iterating over.
-    } else if (
-      flowElement.__$$element === "sequenceFlow" &&
-      ((nodeIdsToMoveOut.has(flowElement["@_sourceRef"]) && 
nodeIdsToMoveOut.has(flowElement["@_targetRef"])) ||
-        (subProcessNodes.has(flowElement["@_sourceRef"]) && 
nodeIdsToMoveOut.has(flowElement["@_targetRef"])) ||
-        (nodeIdsToMoveOut.has(flowElement["@_sourceRef"]) && 
subProcessNodes.has(flowElement["@_targetRef"])))
+      (flowElement["@_id"] && nodeIdsToMoveOut.has(flowElement["@_id"])) ||
+      (flowElement.__$$element === "boundaryEvent" &&
+        flowElement["@_attachedToRef"] &&
+        nodeIdsToMoveOut.has(flowElement["@_attachedToRef"]))
     ) {
+      flowElementsToMove.push(...subProcess.flowElement!.splice(i, 1));
+    } else if (shouldMoveSequenceFlow(flowElement, nodeIdsToMoveOut, 
subProcessNodes)) {
       // If the source and target are both outside of the sub-process
       // or if the source and target is already in the sub process the 
sequenceFlow must be copied
-      flowElementsToMove.push(...((subProcess.flowElement?.splice(i, 1) ?? []) 
as typeof flowElementsToMove));
-      i--; // repeat one index because we just altered the array we're 
iterating over.
+
+      flowElementsToMove.push(...subProcess.flowElement!.splice(i, 1));
     }
   }
 
@@ -112,14 +229,29 @@ export function moveNodesOutOfSubProcess({
 
   for (let i = 0; i < (subProcess.artifact ?? []).length; i++) {
     const artifact = (subProcess.artifact ?? [])[i];
-    if (nodeIdsToMoveOut.has(artifact["@_id"])) {
-      artifactsToMove.push(...((subProcess.artifact?.splice(i, 1) ?? []) as 
typeof artifactsToMove));
+    if (artifact.__$$element !== "association" && 
nodeIdsToMoveOut.has(artifact["@_id"])) {
+      const spliced = subProcess.artifact?.splice(i, 1) ?? [];
+      artifactsToMove.push(...spliced.filter((a) => a.__$$element !== 
"association"));
       i--; // repeat one index because we just altered the array we're 
iterating over.
     }
   }
 
-  process.flowElement ??= [];
-  process.flowElement.push(...flowElementsToMove);
-  process.artifact ??= [];
-  process.artifact.push(...artifactsToMove);
+  parentFlowElements.push(...flowElementsToMove);
+
+  if (__readonly_targetSubProcessId && targetSubProcess) {
+    targetSubProcess.artifact ??= [];
+    targetSubProcess.artifact.push(...artifactsToMove);
+  } else if (__readonly_targetSubProcessId === null) {
+    process.artifact ??= [];
+    process.artifact.push(...artifactsToMove);
+  } else {
+    const parentSubProcess = findParentSubProcess(process.flowElement ?? [], 
__readonly_subProcessId ?? "");
+    if (parentSubProcess) {
+      parentSubProcess.artifact ??= [];
+      parentSubProcess.artifact.push(...artifactsToMove);
+    } else {
+      process.artifact ??= [];
+      process.artifact.push(...artifactsToMove);
+    }
+  }
 }
diff --git a/packages/bpmn-editor/src/store/computeDiagramData.ts 
b/packages/bpmn-editor/src/store/computeDiagramData.ts
index bf670c3687b..d9729f2293d 100644
--- a/packages/bpmn-editor/src/store/computeDiagramData.ts
+++ b/packages/bpmn-editor/src/store/computeDiagramData.ts
@@ -37,6 +37,7 @@ import { BpmnXyFlowDiagramState, State } from "./Store";
 import { NODE_LAYERS } from 
"@kie-tools/xyflow-react-kie-diagram/dist/nodes/Hooks";
 import { ContainmentMode } from 
"@kie-tools/xyflow-react-kie-diagram/dist/graph/graphStructure";
 import { BPMN20__tLane } from 
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { isSubProcessElement } from "../mutations/moveNodesOutOfSubProcess";
 
 export function computeDiagramData(
   definitions: State["bpmn"]["model"]["definitions"],
@@ -102,46 +103,48 @@ export function computeDiagramData(
         nodeBpmnElementsById.set(bpmnElement["@_id"], bpmnElement);
 
         // sub-processes
-        if (
-          bpmnElement?.__$$element === "subProcess" ||
-          bpmnElement?.__$$element === "adHocSubProcess" ||
-          bpmnElement?.__$$element === "transaction"
-        ) {
-          for (const flowElement of bpmnElement.flowElement ?? []) {
-            if (flowElement.__$$element === "boundaryEvent") {
-              parentIdsById.set(flowElement["@_id"], 
flowElement["@_attachedToRef"]);
-            } else {
-              parentIdsById.set(flowElement["@_id"], bpmnElement["@_id"]);
+        if (isSubProcessElement(bpmnElement)) {
+          const processSubProcessElements = (subProcess: typeof bpmnElement, 
parentId: string): void => {
+            for (const flowElement of subProcess.flowElement ?? []) {
+              if (flowElement.__$$element === "boundaryEvent") {
+                parentIdsById.set(flowElement["@_id"], 
flowElement["@_attachedToRef"]);
+              } else {
+                parentIdsById.set(flowElement["@_id"], parentId);
+              }
+              if (flowElement.__$$element !== "sequenceFlow") {
+                if (
+                  flowElement.__$$element !== "callChoreography" &&
+                  flowElement.__$$element !== "choreographyTask" &&
+                  flowElement.__$$element !== "dataObjectReference" &&
+                  flowElement.__$$element !== "dataStoreReference" &&
+                  flowElement.__$$element !== "implicitThrowEvent" &&
+                  flowElement.__$$element !== "manualTask" &&
+                  flowElement.__$$element !== "receiveTask" &&
+                  flowElement.__$$element !== "sendTask" &&
+                  flowElement.__$$element !== "subChoreography"
+                ) {
+                  nodeBpmnElementsById.set(flowElement["@_id"], flowElement);
+                  if (isSubProcessElement(flowElement)) {
+                    processSubProcessElements(flowElement, 
flowElement["@_id"]);
+                  }
+                } else {
+                  // ignore on purpose. those flowElements are not nodes.
+                }
+              } else {
+                edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
+              }
             }
-            if (flowElement.__$$element !== "sequenceFlow") {
-              if (
-                flowElement.__$$element !== "callChoreography" &&
-                flowElement.__$$element !== "choreographyTask" &&
-                flowElement.__$$element !== "dataObjectReference" &&
-                flowElement.__$$element !== "dataStoreReference" &&
-                flowElement.__$$element !== "implicitThrowEvent" &&
-                flowElement.__$$element !== "manualTask" &&
-                flowElement.__$$element !== "receiveTask" &&
-                flowElement.__$$element !== "sendTask" &&
-                flowElement.__$$element !== "subChoreography"
-              ) {
+
+            for (const flowElement of subProcess.artifact ?? []) {
+              parentIdsById.set(flowElement["@_id"], parentId);
+              if (flowElement.__$$element !== "association") {
                 nodeBpmnElementsById.set(flowElement["@_id"], flowElement);
               } else {
-                // ignore on purpose. those flowElements are not nodes.
+                edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
               }
-            } else {
-              edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
             }
-          }
-
-          for (const flowElement of bpmnElement.artifact ?? []) {
-            parentIdsById.set(flowElement["@_id"], bpmnElement["@_id"]);
-            if (flowElement.__$$element !== "association") {
-              nodeBpmnElementsById.set(flowElement["@_id"], flowElement);
-            } else {
-              edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
-            }
-          }
+          };
+          processSubProcessElements(bpmnElement, bpmnElement["@_id"]);
         }
 
         // lanes
@@ -336,10 +339,44 @@ export function computeDiagramData(
     new Map<string, RF.Edge<BpmnDiagramEdgeData>>()
   );
 
-  const sortedNodes = [...nodes]
-    .sort((a, b) => Number(b.type === NODE_TYPES.subProcess) - Number(a.type 
=== NODE_TYPES.subProcess))
-    .sort((a, b) => Number(b.type === NODE_TYPES.lane) - Number(a.type === 
NODE_TYPES.lane))
-    .sort((a, b) => Number(b.type === NODE_TYPES.group) - Number(a.type === 
NODE_TYPES.group));
+  const depthCache = new Map<string, number>();
+
+  const getDepth = (nodeId: string, visiting = new Set<string>()): number => {
+    if (depthCache.has(nodeId)) {
+      return depthCache.get(nodeId)!;
+    }
+
+    if (visiting.has(nodeId)) {
+      console.warn(`Cycle detected in BPMN containment hierarchy at node: 
${nodeId}`);
+      return 0;
+    }
+
+    visiting.add(nodeId);
+    const parentId = parentIdsById.get(nodeId);
+    const depth = parentId ? getDepth(parentId, visiting) + 1 : 0;
+    visiting.delete(nodeId);
+    depthCache.set(nodeId, depth);
+    return depth;
+  };
+
+  const typePriority = (type: BpmnNodeType): number => {
+    switch (type) {
+      case NODE_TYPES.group:
+        return 0;
+      case NODE_TYPES.lane:
+        return 1;
+      case NODE_TYPES.subProcess:
+        return 2;
+      default:
+        return 3;
+    }
+  };
+
+  const sortedNodes = [...nodes].sort((a, b) => {
+    const depthDiff = getDepth(a.id) - getDepth(b.id);
+    if (depthDiff !== 0) return depthDiff;
+    return typePriority(a.type!) - typePriority(b.type!);
+  });
 
   const finalNodes = newNodeProjection ? [...sortedNodes, newNodeProjection] : 
sortedNodes;
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to