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

tiagobento 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 b108e14b3dd kie-issues#831: Make the new DMN Editor actually generate 
an SVG representing the currently open DRD (#2124)
b108e14b3dd is described below

commit b108e14b3dd3dfe9352b0e792f735ecd7b2ec0a7
Author: Tiago Bento <[email protected]>
AuthorDate: Fri Jan 19 09:55:01 2024 -0500

    kie-issues#831: Make the new DMN Editor actually generate an SVG 
representing the currently open DRD (#2124)
---
 .../dmn-editor-envelope/src/DmnEditorFactory.tsx   |   23 +-
 packages/dmn-editor-envelope/src/DmnEditorRoot.tsx |    8 +
 packages/dmn-editor/package.json                   |    1 +
 packages/dmn-editor/src/DmnEditor.css              |    6 +-
 packages/dmn-editor/src/DmnEditor.tsx              |   74 +-
 packages/dmn-editor/src/diagram/Diagram.tsx        | 1588 ++++++++++----------
 .../dmn-editor/src/diagram/edges/EdgeMarkers.tsx   |  102 +-
 packages/dmn-editor/src/diagram/maths/DmnMaths.ts  |    3 -
 .../dmn-editor/src/diagram/nodes/DefaultSizes.ts   |    2 +-
 .../src/diagram/nodes/EditableNodeLabel.tsx        |   11 +-
 packages/dmn-editor/src/diagram/nodes/NodeStyle.ts |  194 ++-
 packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx |   34 +-
 packages/dmn-editor/src/diagram/nodes/Nodes.tsx    |   54 +-
 packages/dmn-editor/src/store/useDiagramData.tsx   |    4 +
 packages/dmn-editor/src/svg/DmnDiagramSvg.tsx      |  290 ++++
 pnpm-lock.yaml                                     |   49 +
 16 files changed, 1472 insertions(+), 971 deletions(-)

diff --git a/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx 
b/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
index bc35756f9a9..b5012e4cb2f 100644
--- a/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
+++ b/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
@@ -55,28 +55,7 @@ export class DmnEditorInterface implements Editor {
   // Not in-editor
 
   public getPreview(): Promise<string | undefined> {
-    return Promise.resolve(`
-<svg
-  xmlns="http://www.w3.org/2000/svg";
-  xmlns:xlink="http://www.w3.org/1999/xlink"; version="1.1"
-  width="540" height="540" viewBox="0 0 540 540" xml:space="preserve">
-  <g transform="matrix(1 0 0 1 270 270)" 
id="ee1530d3-d469-49de-b8ad-62ffb6e5db7a"></g>
-  <g transform="matrix(1 0 0 1 270 270)" 
id="b6eca5e2-94e1-4e3f-a04e-16bc0ada9ea4">
-    <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; 
stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; 
stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; 
visibility: hidden;" vector-effect="non-scaling-stroke"  x="-540" y="-540" 
rx="0" ry="0" width="1080" height="1080" />
-  </g>
-  <g transform="matrix(0.68 0 0 0.68 270 192.07)" style="" 
id="12bd02ec-b291-4d62-acdc-3fdd30cc84d7"  >
-    <text xml:space="preserve" font-family="Raleway" font-size="105" 
font-style="normal" font-weight="900" style="stroke: none; stroke-width: 1; 
stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; 
stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: 
nonzero; opacity: 1; white-space: pre;" >
-      <tspan x="-187.11" y="-26.34" >Not yet</tspan>
-      <tspan x="-321.65" y="92.31" >implemented</tspan>
-    </text>
-  </g>
-  <g transform="matrix(1 0 0 1 200 354.97)" style="" 
id="b2ea8c5b-9fc6-43c0-9e3f-5837adab8b51"  >
-    <text xml:space="preserve" font-family="Alegreya" font-size="38" 
font-style="normal" font-weight="700" style="stroke: none; stroke-width: 1; 
stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; 
stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: 
nonzero; opacity: 1; white-space: pre;" >
-      <tspan x="-190" y="-11.04" style="white-space: pre; ">Use the legacy DMN 
Editor to </tspan>
-      <tspan x="-170" y="38.68" >generate SVGs temporarily.</tspan>
-    </text>
-  </g>
-</svg>`);
+    return this.self.getDiagramSvg();
   }
 
   public async validate(): Promise<Notification[]> {
diff --git a/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx 
b/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
index 367febf5be6..958e86cb072 100644
--- a/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
+++ b/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
@@ -75,9 +75,12 @@ export type DmnEditorRootState = {
 export class DmnEditorRoot extends React.Component<DmnEditorRootProps, 
DmnEditorRootState> {
   private readonly externalModelsManagerDoneBootstraping = 
imperativePromiseHandle<void>();
 
+  private readonly dmnEditorRef: React.RefObject<DmnEditor.DmnEditorRef>;
+
   constructor(props: DmnEditorRootProps) {
     super(props);
     props.exposing(this);
+    this.dmnEditorRef = React.createRef();
     this.state = {
       externalModelsByNamespace: {},
       marshaller: undefined,
@@ -99,6 +102,10 @@ export class DmnEditorRoot extends 
React.Component<DmnEditorRootProps, DmnEditor
     this.setState((prev) => ({ ...prev, pointer: Math.min(prev.stack.length - 
1, prev.pointer + 1) }));
   }
 
+  public async getDiagramSvg(): Promise<string | undefined> {
+    return this.dmnEditorRef.current?.getDiagramSvg();
+  }
+
   public async getContent(): Promise<string> {
     if (!this.state.marshaller || !this.model) {
       throw new Error(
@@ -270,6 +277,7 @@ export class DmnEditorRoot extends 
React.Component<DmnEditorRootProps, DmnEditor
         {this.model && (
           <>
             <DmnEditor.DmnEditor
+              ref={this.dmnEditorRef}
               originalVersion={this.state.marshaller?.originalVersion}
               model={this.model}
               externalModelsByNamespace={this.state.externalModelsByNamespace}
diff --git a/packages/dmn-editor/package.json b/packages/dmn-editor/package.json
index db8f700bcdc..8a25ed3216e 100644
--- a/packages/dmn-editor/package.json
+++ b/packages/dmn-editor/package.json
@@ -43,6 +43,7 @@
     "@patternfly/react-core": "^4.276.6",
     "@patternfly/react-icons": "^4.93.6",
     "@patternfly/react-styles": "^4.92.6",
+    "@visx/text": "^3.3.0",
     "d3-drag": "^3.0.0",
     "d3-selection": "^3.0.0",
     "immer": "^10.0.3",
diff --git a/packages/dmn-editor/src/DmnEditor.css 
b/packages/dmn-editor/src/DmnEditor.css
index 58f840323ae..861f8f78c2b 100644
--- a/packages/dmn-editor/src/DmnEditor.css
+++ b/packages/dmn-editor/src/DmnEditor.css
@@ -886,7 +886,7 @@ th {
 .react-flow__node > .kie-dmn-editor--node-shape.drop-target-invalid {
   border-color: red !important;
 }
-.react-flow__node > .kie-dmn-editor--node-shape.drop-target-invalid > g * {
+.react-flow__node > .kie-dmn-editor--node-shape.drop-target-invalid > * {
   stroke: rgba(255, 0, 0, 0.2) !important;
   fill: rgba(164, 0, 0, 0.1) !important;
 }
@@ -898,12 +898,12 @@ th {
   border-color: #006ba4 !important;
   filter: drop-shadow(2px 2px 2px #006ba477);
 }
-.react-flow__node > .kie-dmn-editor--node-shape.drop-target > g * {
+.react-flow__node > .kie-dmn-editor--node-shape.drop-target > * {
   stroke: #006ba4 !important;
   fill: rgba(0, 107, 164, 0.1) !important;
 }
 
-.react-flow__node.selected:not(.react-flow__node-node_unknown) > 
.kie-dmn-editor--node-shape > g * {
+.react-flow__node.selected:not(.react-flow__node-node_unknown) > 
.kie-dmn-editor--node-shape > * {
   stroke: #006ba4 !important;
 }
 
diff --git a/packages/dmn-editor/src/DmnEditor.tsx 
b/packages/dmn-editor/src/DmnEditor.tsx
index 520bc1fdeef..522838bb0f6 100644
--- a/packages/dmn-editor/src/DmnEditor.tsx
+++ b/packages/dmn-editor/src/DmnEditor.tsx
@@ -1,7 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 import "@patternfly/react-core/dist/styles/base.css";
 import "reactflow/dist/style.css";
 
 import * as React from "react";
+import * as ReactDOM from "react-dom";
+import * as RF from "reactflow";
 import { useCallback, useEffect, useImperativeHandle, useRef, useState, 
useMemo } from "react";
 import { Drawer, DrawerContent, DrawerContentBody } from 
"@patternfly/react-core/dist/js/components/Drawer";
 import { Tab, TabTitleIcon, TabTitleText, Tabs } from 
"@patternfly/react-core/dist/js/components/Tabs";
@@ -10,7 +31,7 @@ import { InfrastructureIcon } from 
"@patternfly/react-icons/dist/js/icons/infras
 import { PficonTemplateIcon } from 
"@patternfly/react-icons/dist/js/icons/pficon-template-icon";
 import { BoxedExpression } from "./boxedExpressions/BoxedExpression";
 import { DataTypes } from "./dataTypes/DataTypes";
-import { Diagram } from "./diagram/Diagram";
+import { Diagram, DiagramRef } from "./diagram/Diagram";
 import { DmnVersionLabel } from "./diagram/DmnVersionLabel";
 import { IncludedModels } from "./includedModels/IncludedModels";
 import { DiagramPropertiesPanel } from 
"./propertiesPanel/DiagramPropertiesPanel";
@@ -38,10 +59,15 @@ import { original } from "immer";
 import "@kie-tools/dmn-marshaller/dist/kie-extensions"; // This is here 
because of the KIE Extension for DMN.
 import "./DmnEditor.css"; // Leave it for last, as this overrides some of the 
PF and RF styles.
 
+import { DmnDiagramSvg } from "./svg/DmnDiagramSvg";
+
 const ON_MODEL_CHANGE_DEBOUNCE_TIME_IN_MS = 500;
 
+const SVG_PADDING = 20;
+
 export type DmnEditorRef = {
   reset: (mode: DmnLatestModel) => void;
+  getDiagramSvg: () => Promise<string | undefined>;
 };
 
 export type EvaluationResults = Record<string, any>;
@@ -140,16 +166,53 @@ export const DmnEditorInternal = ({
   const { boxedExpressionEditor, dmn, navigation, dispatch, diagram } = 
useDmnEditorStore((s) => s);
 
   const dmnEditorStoreApi = useDmnEditorStoreApi();
-  const { isDiagramEditingInProgress } = useDmnEditorDerivedStore();
+  const { isDiagramEditingInProgress, importsByNamespace } = 
useDmnEditorDerivedStore();
   const { dmnModelBeforeEditingRef, dmnEditorRootElementRef } = useDmnEditor();
 
+  // Refs
+
+  const diagramRef = useRef<DiagramRef>(null);
+  const diagramContainerRef = useRef<HTMLDivElement>(null);
+  const beeContainerRef = useRef<HTMLDivElement>(null);
+
   // Allow imperativelly controlling the Editor.
   useImperativeHandle(
     forwardRef,
     () => ({
       reset: (model) => dispatch.dmn.reset(model),
+      getDiagramSvg: async () => {
+        const nodes = diagramRef.current?.getReactFlowInstance()?.getNodes();
+        const edges = diagramRef.current?.getReactFlowInstance()?.getEdges();
+        if (!nodes || !edges) {
+          return undefined;
+        }
+
+        const bounds = RF.getRectOfNodes(nodes);
+
+        const svg = document.createElementNS("http://www.w3.org/2000/svg";, 
"svg");
+        svg.setAttribute("width", bounds.width + SVG_PADDING * 2 + "");
+        svg.setAttribute("height", bounds.height + SVG_PADDING * 2 + "");
+
+        // We're still on React 17.
+        // eslint-disable-next-line react/no-deprecated
+        ReactDOM.render(
+          // Indepdent of where the nodes are located, they'll always be 
rendered at the top-left corner of the SVG
+          <g transform={`translate(${-bounds.x + SVG_PADDING} ${-bounds.y + 
SVG_PADDING})`}>
+            <DmnDiagramSvg
+              nodes={nodes}
+              edges={edges}
+              snapGrid={diagram.snapGrid}
+              importsByNamespace={importsByNamespace}
+              thisDmn={dmnEditorStoreApi.getState().dmn}
+            />
+          </g>,
+          svg
+        );
+
+        return new XMLSerializer().serializeToString(svg);
+      },
     }),
-    [dispatch.dmn]
+    [diagram.snapGrid, dispatch.dmn, dmnEditorStoreApi, importsByNamespace]
   );
 
   // Make sure the DMN Editor reacts to props changing.
@@ -206,9 +269,6 @@ export const DmnEditorInternal = ({
     [dmnEditorStoreApi]
   );
 
-  const diagramContainerRef = useRef<HTMLDivElement>(null);
-  const beeContainerRef = useRef<HTMLDivElement>(null);
-
   const tabTitle = useMemo(() => {
     return {
       editor: (
@@ -265,7 +325,7 @@ export const DmnEditorInternal = ({
                     <DrawerContentBody>
                       <div className={"kie-dmn-editor--diagram-container"} 
ref={diagramContainerRef}>
                         {originalVersion && <DmnVersionLabel 
version={originalVersion} />}
-                        <Diagram container={diagramContainerRef} />
+                        <Diagram ref={diagramRef} 
container={diagramContainerRef} />
                       </div>
                     </DrawerContentBody>
                   </DrawerContent>
diff --git a/packages/dmn-editor/src/diagram/Diagram.tsx 
b/packages/dmn-editor/src/diagram/Diagram.tsx
index eb1c5ebd87b..13757d1cf4f 100644
--- a/packages/dmn-editor/src/diagram/Diagram.tsx
+++ b/packages/dmn-editor/src/diagram/Diagram.tsx
@@ -147,422 +147,471 @@ const edgeTypes: Record<EdgeType, any> = {
   [EDGE_TYPES.association]: AssociationEdge,
 };
 
-export function Diagram({ container }: { container: 
React.RefObject<HTMLElement> }) {
-  // Contexts
-
-  const dmnEditorStoreApi = useDmnEditorStoreApi();
-  const diagram = useDmnEditorStore((s) => s.diagram);
-  const thisDmn = useDmnEditorStore((s) => s.dmn);
-
-  const { dmnModelBeforeEditingRef } = useDmnEditor();
-
-  const {
-    dmnShapesByHref,
-    nodesById,
-    selectedNodesById,
-    selectedEdgesById,
-    edgesById,
-    nodes,
-    edges,
-    isDropTargetNodeValidForSelection,
-    isDiagramEditingInProgress,
-    selectedNodeTypes,
-    externalDmnsByNamespace,
-    drgElementsWithoutVisualRepresentationOnCurrentDrd,
-  } = useDmnEditorDerivedStore();
-
-  // State
-
-  const [reactFlowInstance, setReactFlowInstance] = useState<
-    RF.ReactFlowInstance<DmnDiagramNodeData, DmnDiagramEdgeData> | undefined
-  >(undefined);
-
-  // Refs
-
-  const nodeIdBeingDraggedRef = useRef<string | null>(null);
+export type DiagramRef = {
+  getReactFlowInstance: () => RF.ReactFlowInstance | undefined;
+};
 
-  // Memos
+export const Diagram = React.forwardRef<DiagramRef, { container: 
React.RefObject<HTMLElement> }>(
+  ({ container }, ref) => {
+    // Contexts
 
-  const rfSnapGrid = useMemo<[number, number]>(
-    () => (diagram.snapGrid.isEnabled ? [diagram.snapGrid.x, 
diagram.snapGrid.y] : [1, 1]),
-    [diagram.snapGrid.isEnabled, diagram.snapGrid.x, diagram.snapGrid.y]
-  );
+    const dmnEditorStoreApi = useDmnEditorStoreApi();
+    const diagram = useDmnEditorStore((s) => s.diagram);
+    const thisDmn = useDmnEditorStore((s) => s.dmn);
 
-  // Callbacks
+    const { dmnModelBeforeEditingRef } = useDmnEditor();
 
-  const onConnect = useCallback<RF.OnConnect>(
-    (connection) => {
-      console.debug("DMN DIAGRAM: `onConnect`: ", connection);
+    const {
+      dmnShapesByHref,
+      nodesById,
+      selectedNodesById,
+      selectedEdgesById,
+      edgesById,
+      nodes,
+      edges,
+      isDropTargetNodeValidForSelection,
+      isDiagramEditingInProgress,
+      selectedNodeTypes,
+      externalDmnsByNamespace,
+      drgElementsWithoutVisualRepresentationOnCurrentDrd,
+    } = useDmnEditorDerivedStore();
 
-      const sourceNode = nodesById.get(connection.source!);
-      const targetNode = nodesById.get(connection.target!);
-      if (!sourceNode || !targetNode) {
-        throw new Error("Cannot create connection without target and source 
nodes!");
-      }
+    // State
 
-      const sourceBounds = sourceNode.data.shape["dc:Bounds"];
-      const targetBounds = targetNode.data.shape["dc:Bounds"];
-      if (!sourceBounds || !targetBounds) {
-        throw new Error("Cannot create connection without target bounds!");
-      }
+    const [reactFlowInstance, setReactFlowInstance] = useState<
+      RF.ReactFlowInstance<DmnDiagramNodeData, DmnDiagramEdgeData> | undefined
+    >(undefined);
 
-      // --------- This is where we draw the line between the diagram and the 
model.
+    // Refs
 
-      dmnEditorStoreApi.setState((state) => {
-        addEdge({
-          definitions: state.dmn.model.definitions,
-          drdIndex: state.diagram.drdIndex,
-          edge: {
-            type: connection.sourceHandle as EdgeType,
-            targetHandle: connection.targetHandle as PositionalNodeHandleId,
-            sourceHandle: PositionalNodeHandleId.Center,
-          },
-          sourceNode: {
-            type: sourceNode.type as NodeType,
-            data: sourceNode.data,
-            href: sourceNode.id,
-            bounds: sourceBounds,
-            shapeId: sourceNode.data.shape["@_id"],
-          },
-          targetNode: {
-            type: targetNode.type as NodeType,
-            href: targetNode.id,
-            data: targetNode.data,
-            bounds: targetBounds,
-            index: targetNode.data.index,
-            shapeId: targetNode.data.shape["@_id"],
-          },
-          keepWaypoints: false,
-        });
-      });
-    },
-    [dmnEditorStoreApi, nodesById]
-  );
+    React.useImperativeHandle(
+      ref,
+      () => ({
+        getReactFlowInstance: () => {
+          return reactFlowInstance;
+        },
+      }),
+      [reactFlowInstance]
+    );
 
-  const getFirstNodeFittingBounds = useCallback(
-    (nodeIdToIgnore: string, bounds: DC__Bounds, minSizes: (snapGrid: 
SnapGrid) => DC__Dimension, snapGrid: SnapGrid) =>
-      reactFlowInstance
-        ?.getNodes()
-        .reverse() // Respect the nodes z-index.
-        .find(
-          (node) =>
-            node.id !== nodeIdToIgnore && // don't ever use the node being 
dragged
-            getContainmentRelationship({
-              bounds: bounds!,
-              container: node.data.shape["dc:Bounds"]!,
-              snapGrid,
-              containerMinSizes: MIN_NODE_SIZES[node.type as NodeType],
-              boundsMinSizes: minSizes,
-            }).isInside
-        ),
-    [reactFlowInstance]
-  );
+    const nodeIdBeingDraggedRef = useRef<string | null>(null);
 
-  const onDragOver = useCallback((e: React.DragEvent) => {
-    if (
-      !e.dataTransfer.types.find(
-        (t) =>
-          t === MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE ||
-          t === MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS ||
-          t === MIME_TYPE_FOR_DMN_EDITOR_DRG_NODE
-      )
-    ) {
-      return;
-    }
+    // Memos
 
-    e.preventDefault();
-    e.dataTransfer.dropEffect = "move";
-  }, []);
+    const rfSnapGrid = useMemo<[number, number]>(
+      () => (diagram.snapGrid.isEnabled ? [diagram.snapGrid.x, 
diagram.snapGrid.y] : [1, 1]),
+      [diagram.snapGrid.isEnabled, diagram.snapGrid.x, diagram.snapGrid.y]
+    );
 
-  const onDrop = useCallback(
-    (e: React.DragEvent) => {
-      e.preventDefault();
+    // Callbacks
 
-      if (!container.current || !reactFlowInstance) {
-        return;
-      }
+    const onConnect = useCallback<RF.OnConnect>(
+      (connection) => {
+        console.debug("DMN DIAGRAM: `onConnect`: ", connection);
 
-      // we need to remove the wrapper bounds, in order to get the correct 
position
-      const dropPoint = reactFlowInstance.screenToFlowPosition({
-        x: e.clientX,
-        y: e.clientY,
-      });
+        const sourceNode = nodesById.get(connection.source!);
+        const targetNode = nodesById.get(connection.target!);
+        if (!sourceNode || !targetNode) {
+          throw new Error("Cannot create connection without target and source 
nodes!");
+        }
 
-      if 
(e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE)) {
-        const typeOfNewNodeFromPalette = e.dataTransfer.getData(
-          MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE
-        ) as NodeType;
-        e.stopPropagation();
+        const sourceBounds = sourceNode.data.shape["dc:Bounds"];
+        const targetBounds = targetNode.data.shape["dc:Bounds"];
+        if (!sourceBounds || !targetBounds) {
+          throw new Error("Cannot create connection without target bounds!");
+        }
 
         // --------- This is where we draw the line between the diagram and 
the model.
 
         dmnEditorStoreApi.setState((state) => {
-          const { id, href: newNodeId } = addStandaloneNode({
+          addEdge({
             definitions: state.dmn.model.definitions,
             drdIndex: state.diagram.drdIndex,
-            newNode: {
-              type: typeOfNewNodeFromPalette,
-              bounds: {
-                "@_x": dropPoint.x,
-                "@_y": dropPoint.y,
-                "@_width": 
DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette](state.diagram.snapGrid)["@_width"],
-                "@_height": 
DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette](state.diagram.snapGrid)["@_height"],
-              },
+            edge: {
+              type: connection.sourceHandle as EdgeType,
+              targetHandle: connection.targetHandle as PositionalNodeHandleId,
+              sourceHandle: PositionalNodeHandleId.Center,
             },
+            sourceNode: {
+              type: sourceNode.type as NodeType,
+              data: sourceNode.data,
+              href: sourceNode.id,
+              bounds: sourceBounds,
+              shapeId: sourceNode.data.shape["@_id"],
+            },
+            targetNode: {
+              type: targetNode.type as NodeType,
+              href: targetNode.id,
+              data: targetNode.data,
+              bounds: targetBounds,
+              index: targetNode.data.index,
+              shapeId: targetNode.data.shape["@_id"],
+            },
+            keepWaypoints: false,
           });
-          state.diagram._selectedNodes = [newNodeId];
-          state.focus.consumableId = id;
         });
-      } else if 
(e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS))
 {
-        e.stopPropagation();
-        const externalNode = JSON.parse(
-          
e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS)
-        ) as ExternalNode;
+      },
+      [dmnEditorStoreApi, nodesById]
+    );
 
-        // --------- This is where we draw the line between the diagram and 
the model.
+    const getFirstNodeFittingBounds = useCallback(
+      (
+        nodeIdToIgnore: string,
+        bounds: DC__Bounds,
+        minSizes: (snapGrid: SnapGrid) => DC__Dimension,
+        snapGrid: SnapGrid
+      ) =>
+        reactFlowInstance
+          ?.getNodes()
+          .reverse() // Respect the nodes z-index.
+          .find(
+            (node) =>
+              node.id !== nodeIdToIgnore && // don't ever use the node being 
dragged
+              getContainmentRelationship({
+                bounds: bounds!,
+                container: node.data.shape["dc:Bounds"]!,
+                snapGrid,
+                containerMinSizes: MIN_NODE_SIZES[node.type as NodeType],
+                boundsMinSizes: minSizes,
+              }).isInside
+          ),
+      [reactFlowInstance]
+    );
+
+    const onDragOver = useCallback((e: React.DragEvent) => {
+      if (
+        !e.dataTransfer.types.find(
+          (t) =>
+            t === MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE ||
+            t === MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS 
||
+            t === MIME_TYPE_FOR_DMN_EDITOR_DRG_NODE
+        )
+      ) {
+        return;
+      }
 
-        const externalDrgElement = (
-          
externalDmnsByNamespace.get(externalNode.externalDrgElementNamespace)?.model.definitions.drgElement
 ?? []
-        ).find((s) => s["@_id"] === externalNode.externalDrgElementId);
-        if (!externalDrgElement) {
-          throw new Error(`Can't find DRG element with id 
'${externalNode.externalDrgElementId}'.`);
+      e.preventDefault();
+      e.dataTransfer.dropEffect = "move";
+    }, []);
+
+    const onDrop = useCallback(
+      (e: React.DragEvent) => {
+        e.preventDefault();
+
+        if (!container.current || !reactFlowInstance) {
+          return;
         }
 
-        const externalNodeType = getNodeTypeFromDmnObject(externalDrgElement)!;
+        // we need to remove the wrapper bounds, in order to get the correct 
position
+        const dropPoint = reactFlowInstance.screenToFlowPosition({
+          x: e.clientX,
+          y: e.clientY,
+        });
 
-        dmnEditorStoreApi.setState((state) => {
-          const defaultExternalNodeDimensions = 
DEFAULT_NODE_SIZES[externalNodeType](state.diagram.snapGrid);
+        if 
(e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE)) {
+          const typeOfNewNodeFromPalette = e.dataTransfer.getData(
+            MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE
+          ) as NodeType;
+          e.stopPropagation();
 
-          const namespaceName = getXmlNamespaceDeclarationName({
-            model: state.dmn.model.definitions,
-            namespace: externalNode.externalDrgElementNamespace,
+          // --------- This is where we draw the line between the diagram and 
the model.
+
+          dmnEditorStoreApi.setState((state) => {
+            const { id, href: newNodeId } = addStandaloneNode({
+              definitions: state.dmn.model.definitions,
+              drdIndex: state.diagram.drdIndex,
+              newNode: {
+                type: typeOfNewNodeFromPalette,
+                bounds: {
+                  "@_x": dropPoint.x,
+                  "@_y": dropPoint.y,
+                  "@_width": 
DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette](state.diagram.snapGrid)["@_width"],
+                  "@_height": 
DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette](state.diagram.snapGrid)["@_height"],
+                },
+              },
+            });
+            state.diagram._selectedNodes = [newNodeId];
+            state.focus.consumableId = id;
           });
+        } else if 
(e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS))
 {
+          e.stopPropagation();
+          const externalNode = JSON.parse(
+            
e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS)
+          ) as ExternalNode;
+
+          // --------- This is where we draw the line between the diagram and 
the model.
 
-          if (!namespaceName) {
-            throw new Error(`Can't find namespace name for 
'${externalNode.externalDrgElementNamespace}'.`);
+          const externalDrgElement = (
+            
externalDmnsByNamespace.get(externalNode.externalDrgElementNamespace)?.model.definitions.drgElement
 ?? []
+          ).find((s) => s["@_id"] === externalNode.externalDrgElementId);
+          if (!externalDrgElement) {
+            throw new Error(`Can't find DRG element with id 
'${externalNode.externalDrgElementId}'.`);
           }
 
-          addShape({
-            definitions: state.dmn.model.definitions,
-            drdIndex: state.diagram.drdIndex,
-            nodeType: externalNodeType,
-            shape: {
-              "@_dmnElementRef": buildXmlQName({
-                type: "xml-qname",
-                prefix: namespaceName,
-                localPart: externalDrgElement["@_id"]!,
-              }),
-              "@_isCollapsed": true,
-              "dc:Bounds": {
-                "@_x": dropPoint.x,
-                "@_y": dropPoint.y,
-                "@_width": defaultExternalNodeDimensions["@_width"],
-                "@_height": defaultExternalNodeDimensions["@_height"],
+          const externalNodeType = 
getNodeTypeFromDmnObject(externalDrgElement)!;
+
+          dmnEditorStoreApi.setState((state) => {
+            const defaultExternalNodeDimensions = 
DEFAULT_NODE_SIZES[externalNodeType](state.diagram.snapGrid);
+
+            const namespaceName = getXmlNamespaceDeclarationName({
+              model: state.dmn.model.definitions,
+              namespace: externalNode.externalDrgElementNamespace,
+            });
+
+            if (!namespaceName) {
+              throw new Error(`Can't find namespace name for 
'${externalNode.externalDrgElementNamespace}'.`);
+            }
+
+            addShape({
+              definitions: state.dmn.model.definitions,
+              drdIndex: state.diagram.drdIndex,
+              nodeType: externalNodeType,
+              shape: {
+                "@_dmnElementRef": buildXmlQName({
+                  type: "xml-qname",
+                  prefix: namespaceName,
+                  localPart: externalDrgElement["@_id"]!,
+                }),
+                "@_isCollapsed": true,
+                "dc:Bounds": {
+                  "@_x": dropPoint.x,
+                  "@_y": dropPoint.y,
+                  "@_width": defaultExternalNodeDimensions["@_width"],
+                  "@_height": defaultExternalNodeDimensions["@_height"],
+                },
               },
-            },
+            });
+            state.diagram._selectedNodes = [
+              buildXmlHref({
+                namespace: externalNode.externalDrgElementNamespace,
+                id: externalNode.externalDrgElementId,
+              }),
+            ];
           });
-          state.diagram._selectedNodes = [
-            buildXmlHref({
-              namespace: externalNode.externalDrgElementNamespace,
-              id: externalNode.externalDrgElementId,
-            }),
-          ];
-        });
 
-        console.debug(`DMN DIAGRAM: Adding external node`, 
JSON.stringify(externalNode));
-      } else if (e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_DRG_NODE)) {
-        const drgElement = 
JSON.parse(e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_DRG_NODE)) as 
Unpacked<
-          DMN15__tDefinitions["drgElement"]
-        >;
+          console.debug(`DMN DIAGRAM: Adding external node`, 
JSON.stringify(externalNode));
+        } else if (e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_DRG_NODE)) {
+          const drgElement = 
JSON.parse(e.dataTransfer.getData(MIME_TYPE_FOR_DMN_EDITOR_DRG_NODE)) as 
Unpacked<
+            DMN15__tDefinitions["drgElement"]
+          >;
 
-        const nodeType = getNodeTypeFromDmnObject(drgElement)!;
+          const nodeType = getNodeTypeFromDmnObject(drgElement)!;
 
-        dmnEditorStoreApi.setState((state) => {
-          const defaultNodeDimensions = 
DEFAULT_NODE_SIZES[nodeType](state.diagram.snapGrid);
-          addShape({
-            definitions: state.dmn.model.definitions,
-            drdIndex: state.diagram.drdIndex,
-            nodeType,
-            shape: {
-              "@_dmnElementRef": buildXmlQName({ type: "xml-qname", localPart: 
drgElement["@_id"]! }),
-              "@_isCollapsed": false,
-              "dc:Bounds": {
-                "@_x": dropPoint.x,
-                "@_y": dropPoint.y,
-                "@_width": defaultNodeDimensions["@_width"],
-                "@_height": defaultNodeDimensions["@_height"],
+          dmnEditorStoreApi.setState((state) => {
+            const defaultNodeDimensions = 
DEFAULT_NODE_SIZES[nodeType](state.diagram.snapGrid);
+            addShape({
+              definitions: state.dmn.model.definitions,
+              drdIndex: state.diagram.drdIndex,
+              nodeType,
+              shape: {
+                "@_dmnElementRef": buildXmlQName({ type: "xml-qname", 
localPart: drgElement["@_id"]! }),
+                "@_isCollapsed": false,
+                "dc:Bounds": {
+                  "@_x": dropPoint.x,
+                  "@_y": dropPoint.y,
+                  "@_width": defaultNodeDimensions["@_width"],
+                  "@_height": defaultNodeDimensions["@_height"],
+                },
               },
-            },
+            });
           });
-        });
 
-        console.debug(`DMN DIAGRAM: Adding DRG node`, 
JSON.stringify(drgElement));
+          console.debug(`DMN DIAGRAM: Adding DRG node`, 
JSON.stringify(drgElement));
+        }
+      },
+      [container, reactFlowInstance, dmnEditorStoreApi, 
externalDmnsByNamespace]
+    );
+
+    useEffect(() => {
+      const edgeUpdaterSource = document.querySelectorAll(
+        ".react-flow__edgeupdater-source, .react-flow__edgeupdater-target"
+      );
+      if (diagram.ongoingConnection) {
+        edgeUpdaterSource.forEach((e) => e.classList.add("hidden"));
+      } else {
+        edgeUpdaterSource.forEach((e) => e.classList.remove("hidden"));
       }
-    },
-    [container, reactFlowInstance, dmnEditorStoreApi, externalDmnsByNamespace]
-  );
+    }, [diagram.ongoingConnection]);
 
-  useEffect(() => {
-    const edgeUpdaterSource = document.querySelectorAll(
-      ".react-flow__edgeupdater-source, .react-flow__edgeupdater-target"
+    const onConnectStart = useCallback<RF.OnConnectStart>(
+      (e, newConnection) => {
+        console.debug("DMN DIAGRAM: `onConnectStart`");
+        dmnEditorStoreApi.setState((state) => {
+          state.diagram.ongoingConnection = newConnection;
+        });
+      },
+      [dmnEditorStoreApi]
     );
-    if (diagram.ongoingConnection) {
-      edgeUpdaterSource.forEach((e) => e.classList.add("hidden"));
-    } else {
-      edgeUpdaterSource.forEach((e) => e.classList.remove("hidden"));
-    }
-  }, [diagram.ongoingConnection]);
-
-  const onConnectStart = useCallback<RF.OnConnectStart>(
-    (e, newConnection) => {
-      console.debug("DMN DIAGRAM: `onConnectStart`");
-      dmnEditorStoreApi.setState((state) => {
-        state.diagram.ongoingConnection = newConnection;
-      });
-    },
-    [dmnEditorStoreApi]
-  );
 
-  const onConnectEnd = useCallback(
-    (e: MouseEvent) => {
-      console.debug("DMN DIAGRAM: `onConnectEnd`");
-      dmnEditorStoreApi.setState((state) => {
-        state.diagram.ongoingConnection = undefined;
-      });
+    const onConnectEnd = useCallback(
+      (e: MouseEvent) => {
+        console.debug("DMN DIAGRAM: `onConnectEnd`");
+        dmnEditorStoreApi.setState((state) => {
+          state.diagram.ongoingConnection = undefined;
+        });
 
-      const targetIsPane = (e.target as Element | 
null)?.classList?.contains("react-flow__pane");
-      if (!targetIsPane || !container.current || !diagram.ongoingConnection || 
!reactFlowInstance) {
-        return;
-      }
+        const targetIsPane = (e.target as Element | 
null)?.classList?.contains("react-flow__pane");
+        if (!targetIsPane || !container.current || !diagram.ongoingConnection 
|| !reactFlowInstance) {
+          return;
+        }
 
-      const dropPoint = reactFlowInstance.screenToFlowPosition({
-        x: e.clientX,
-        y: e.clientY,
-      });
+        const dropPoint = reactFlowInstance.screenToFlowPosition({
+          x: e.clientX,
+          y: e.clientY,
+        });
 
-      // only try to create node if source handle is compatible
-      if (!Object.values(NODE_TYPES).find((n) => n === 
diagram.ongoingConnection!.handleId)) {
-        return;
-      }
+        // only try to create node if source handle is compatible
+        if (!Object.values(NODE_TYPES).find((n) => n === 
diagram.ongoingConnection!.handleId)) {
+          return;
+        }
 
-      if (!diagram.ongoingConnection.nodeId) {
-        return;
-      }
+        if (!diagram.ongoingConnection.nodeId) {
+          return;
+        }
 
-      const sourceNode = nodesById.get(diagram.ongoingConnection.nodeId);
-      if (!sourceNode) {
-        return;
-      }
+        const sourceNode = nodesById.get(diagram.ongoingConnection.nodeId);
+        if (!sourceNode) {
+          return;
+        }
 
-      const sourceNodeBounds = 
dmnShapesByHref.get(sourceNode.id)?.["dc:Bounds"];
-      if (!sourceNodeBounds) {
-        return;
-      }
+        const sourceNodeBounds = 
dmnShapesByHref.get(sourceNode.id)?.["dc:Bounds"];
+        if (!sourceNodeBounds) {
+          return;
+        }
 
-      const newNodeType = diagram.ongoingConnection.handleId as NodeType;
-      const sourceNodeType = sourceNode.type as NodeType;
+        const newNodeType = diagram.ongoingConnection.handleId as NodeType;
+        const sourceNodeType = sourceNode.type as NodeType;
 
-      const edge = getDefaultEdgeTypeBetween(sourceNodeType as NodeType, 
newNodeType);
-      if (!edge) {
-        throw new Error(`DMN DIAGRAM: Invalid structure: ${sourceNodeType} 
--(any)--> ${newNodeType}`);
-      }
+        const edge = getDefaultEdgeTypeBetween(sourceNodeType as NodeType, 
newNodeType);
+        if (!edge) {
+          throw new Error(`DMN DIAGRAM: Invalid structure: ${sourceNodeType} 
--(any)--> ${newNodeType}`);
+        }
 
-      // --------- This is where we draw the line between the diagram and the 
model.
+        // --------- This is where we draw the line between the diagram and 
the model.
 
-      dmnEditorStoreApi.setState((state) => {
-        const { id, href: newDmnObejctHref } = addConnectedNode({
-          definitions: state.dmn.model.definitions,
-          drdIndex: state.diagram.drdIndex,
-          edge,
-          sourceNode: {
-            href: sourceNode.id,
-            type: sourceNodeType as NodeType,
-            bounds: sourceNodeBounds,
-            shapeId: sourceNode.data.shape["@_id"],
-          },
-          newNode: {
-            type: newNodeType,
-            bounds: {
-              "@_x": dropPoint.x,
-              "@_y": dropPoint.y,
-              "@_width": 
DEFAULT_NODE_SIZES[newNodeType](state.diagram.snapGrid)["@_width"],
-              "@_height": 
DEFAULT_NODE_SIZES[newNodeType](state.diagram.snapGrid)["@_height"],
+        dmnEditorStoreApi.setState((state) => {
+          const { id, href: newDmnObejctHref } = addConnectedNode({
+            definitions: state.dmn.model.definitions,
+            drdIndex: state.diagram.drdIndex,
+            edge,
+            sourceNode: {
+              href: sourceNode.id,
+              type: sourceNodeType as NodeType,
+              bounds: sourceNodeBounds,
+              shapeId: sourceNode.data.shape["@_id"],
             },
-          },
-        });
+            newNode: {
+              type: newNodeType,
+              bounds: {
+                "@_x": dropPoint.x,
+                "@_y": dropPoint.y,
+                "@_width": 
DEFAULT_NODE_SIZES[newNodeType](state.diagram.snapGrid)["@_width"],
+                "@_height": 
DEFAULT_NODE_SIZES[newNodeType](state.diagram.snapGrid)["@_height"],
+              },
+            },
+          });
 
-        state.diagram._selectedNodes = [newDmnObejctHref];
-        state.focus.consumableId = id;
-      });
-    },
-    [dmnEditorStoreApi, container, diagram.ongoingConnection, 
reactFlowInstance, nodesById, dmnShapesByHref]
-  );
+          state.diagram._selectedNodes = [newDmnObejctHref];
+          state.focus.consumableId = id;
+        });
+      },
+      [dmnEditorStoreApi, container, diagram.ongoingConnection, 
reactFlowInstance, nodesById, dmnShapesByHref]
+    );
 
-  const isValidConnection = useCallback<RF.IsValidConnection>(
-    (edgeOrConnection) => {
-      const state = dmnEditorStoreApi.getState();
-      const edgeId = state.diagram.edgeIdBeingUpdated;
-      const edgeType = edgeId ? (reactFlowInstance?.getEdge(edgeId)?.type as 
EdgeType) : undefined;
+    const isValidConnection = useCallback<RF.IsValidConnection>(
+      (edgeOrConnection) => {
+        const state = dmnEditorStoreApi.getState();
+        const edgeId = state.diagram.edgeIdBeingUpdated;
+        const edgeType = edgeId ? (reactFlowInstance?.getEdge(edgeId)?.type as 
EdgeType) : undefined;
 
-      const ongoingConnectionHierarchy = buildHierarchy({
-        nodeId: state.diagram.ongoingConnection?.nodeId,
-        edges: reactFlowInstance?.getEdges() ?? [],
-      });
+        const ongoingConnectionHierarchy = buildHierarchy({
+          nodeId: state.diagram.ongoingConnection?.nodeId,
+          edges: reactFlowInstance?.getEdges() ?? [],
+        });
 
-      return (
-        // Reflexive edges are not allowed for DMN
-        edgeOrConnection.source !== edgeOrConnection.target &&
-        // Matches DMNs structure.
-        checkIsValidConnection(nodesById, edgeOrConnection, edgeType) &&
-        // Does not form cycles.
-        !!edgeOrConnection.target &&
-        !ongoingConnectionHierarchy.dependencies.has(edgeOrConnection.target) 
&&
-        !!edgeOrConnection.source &&
-        !ongoingConnectionHierarchy.dependents.has(edgeOrConnection.source)
-      );
-    },
-    [dmnEditorStoreApi, reactFlowInstance, nodesById]
-  );
+        return (
+          // Reflexive edges are not allowed for DMN
+          edgeOrConnection.source !== edgeOrConnection.target &&
+          // Matches DMNs structure.
+          checkIsValidConnection(nodesById, edgeOrConnection, edgeType) &&
+          // Does not form cycles.
+          !!edgeOrConnection.target &&
+          
!ongoingConnectionHierarchy.dependencies.has(edgeOrConnection.target) &&
+          !!edgeOrConnection.source &&
+          !ongoingConnectionHierarchy.dependents.has(edgeOrConnection.source)
+        );
+      },
+      [dmnEditorStoreApi, reactFlowInstance, nodesById]
+    );
 
-  const onNodesChange = useCallback<RF.OnNodesChange>(
-    (changes) => {
-      if (!reactFlowInstance) {
-        return;
-      }
+    const onNodesChange = useCallback<RF.OnNodesChange>(
+      (changes) => {
+        if (!reactFlowInstance) {
+          return;
+        }
 
-      dmnEditorStoreApi.setState((state) => {
-        const controlWaypointsByEdge = new Map<number, Set<number>>();
-
-        for (const change of changes) {
-          switch (change.type) {
-            case "add":
-              console.debug(`DMN DIAGRAM: 'onNodesChange' --> add 
'${change.item.id}'`);
-              state.dispatch.diagram.setNodeStatus(state, change.item.id, { 
selected: true });
-              break;
-            case "dimensions":
-              console.debug(`DMN DIAGRAM: 'onNodesChange' --> dimensions 
'${change.id}'`);
-              state.dispatch.diagram.setNodeStatus(state, change.id, { 
resizing: change.resizing });
-              if (change.dimensions) {
-                const node = nodesById.get(change.id)!;
-                // We only need to resize the node if its snapped dimensions 
change, as snapping is non-destructive.
-                const snappedShape = snapShapeDimensions(
-                  state.diagram.snapGrid,
-                  node.data.shape,
-                  MIN_NODE_SIZES[node.type as NodeType](state.diagram.snapGrid)
-                );
-                if (
-                  snappedShape.width !== change.dimensions.width ||
-                  snappedShape.height !== change.dimensions.height
-                ) {
-                  resizeNode({
+        dmnEditorStoreApi.setState((state) => {
+          const controlWaypointsByEdge = new Map<number, Set<number>>();
+
+          for (const change of changes) {
+            switch (change.type) {
+              case "add":
+                console.debug(`DMN DIAGRAM: 'onNodesChange' --> add 
'${change.item.id}'`);
+                state.dispatch.diagram.setNodeStatus(state, change.item.id, { 
selected: true });
+                break;
+              case "dimensions":
+                console.debug(`DMN DIAGRAM: 'onNodesChange' --> dimensions 
'${change.id}'`);
+                state.dispatch.diagram.setNodeStatus(state, change.id, { 
resizing: change.resizing });
+                if (change.dimensions) {
+                  const node = nodesById.get(change.id)!;
+                  // We only need to resize the node if its snapped dimensions 
change, as snapping is non-destructive.
+                  const snappedShape = snapShapeDimensions(
+                    state.diagram.snapGrid,
+                    node.data.shape,
+                    MIN_NODE_SIZES[node.type as 
NodeType](state.diagram.snapGrid)
+                  );
+                  if (
+                    snappedShape.width !== change.dimensions.width ||
+                    snappedShape.height !== change.dimensions.height
+                  ) {
+                    resizeNode({
+                      definitions: state.dmn.model.definitions,
+                      drdIndex: state.diagram.drdIndex,
+                      dmnShapesByHref,
+                      snapGrid: state.diagram.snapGrid,
+                      change: {
+                        isExternal: !!node.data.dmnObjectQName.prefix,
+                        nodeType: node.type as NodeType,
+                        index: node.data.index,
+                        shapeIndex: node.data.shape.index,
+                        sourceEdgeIndexes: edges.flatMap((e) =>
+                          e.source === change.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
+                        ),
+                        targetEdgeIndexes: edges.flatMap((e) =>
+                          e.target === change.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
+                        ),
+                        dimension: {
+                          "@_width": change.dimensions?.width ?? 0,
+                          "@_height": change.dimensions?.height ?? 0,
+                        },
+                      },
+                    });
+                  }
+                }
+                break;
+              case "position":
+                console.debug(`DMN DIAGRAM: 'onNodesChange' --> position 
'${change.id}'`);
+                state.dispatch.diagram.setNodeStatus(state, change.id, { 
dragging: change.dragging });
+                if (change.positionAbsolute) {
+                  const node = nodesById.get(change.id)!;
+                  const { delta } = repositionNode({
                     definitions: state.dmn.model.definitions,
                     drdIndex: state.diagram.drdIndex,
-                    dmnShapesByHref,
-                    snapGrid: state.diagram.snapGrid,
+                    controlWaypointsByEdge,
                     change: {
-                      isExternal: !!node.data.dmnObjectQName.prefix,
+                      type: "absolute",
                       nodeType: node.type as NodeType,
-                      index: node.data.index,
+                      selectedEdges: [...selectedEdgesById.keys()],
                       shapeIndex: node.data.shape.index,
                       sourceEdgeIndexes: edges.flatMap((e) =>
                         e.source === change.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
@@ -570,492 +619,467 @@ export function Diagram({ container }: { container: 
React.RefObject<HTMLElement>
                       targetEdgeIndexes: edges.flatMap((e) =>
                         e.target === change.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
                       ),
-                      dimension: {
-                        "@_width": change.dimensions?.width ?? 0,
-                        "@_height": change.dimensions?.height ?? 0,
-                      },
+                      position: change.positionAbsolute,
                     },
                   });
+
+                  // FIXME: This should be inside `repositionNode` I guess?
+
+                  // Update nested
+                  // External Decision Services will have encapsulated and 
output decisions, but they aren't depicted in the graph.
+                  if (node.type === NODE_TYPES.decisionService && 
!node.data.dmnObjectQName.prefix) {
+                    const decisionService = node.data.dmnObject as 
DMN15__tDecisionService;
+                    const nested = [
+                      ...(decisionService.outputDecision ?? []),
+                      ...(decisionService.encapsulatedDecision ?? []),
+                    ];
+
+                    for (let i = 0; i < nested.length; i++) {
+                      const nestedNode = nodesById.get(nested[i]["@_href"])!;
+                      const snappedNestedNodeShapeWithAppliedDelta = 
snapShapePosition(
+                        state.diagram.snapGrid,
+                        offsetShapePosition(nestedNode.data.shape, delta)
+                      );
+                      repositionNode({
+                        definitions: state.dmn.model.definitions,
+                        drdIndex: state.diagram.drdIndex,
+                        controlWaypointsByEdge,
+                        change: {
+                          type: "absolute",
+                          nodeType: nestedNode.type as NodeType,
+                          selectedEdges: edges.map((e) => e.id),
+                          shapeIndex: nestedNode.data.shape.index,
+                          sourceEdgeIndexes: edges.flatMap((e) =>
+                            e.source === nestedNode.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
+                          ),
+                          targetEdgeIndexes: edges.flatMap((e) =>
+                            e.target === nestedNode.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
+                          ),
+                          position: snappedNestedNodeShapeWithAppliedDelta,
+                        },
+                      });
+                    }
+                  }
                 }
-              }
-              break;
-            case "position":
-              console.debug(`DMN DIAGRAM: 'onNodesChange' --> position 
'${change.id}'`);
-              state.dispatch.diagram.setNodeStatus(state, change.id, { 
dragging: change.dragging });
-              if (change.positionAbsolute) {
+                break;
+              case "remove":
+                console.debug(`DMN DIAGRAM: 'onNodesChange' --> remove 
'${change.id}'`);
                 const node = nodesById.get(change.id)!;
-                const { delta } = repositionNode({
+                deleteNode({
                   definitions: state.dmn.model.definitions,
                   drdIndex: state.diagram.drdIndex,
-                  controlWaypointsByEdge,
-                  change: {
-                    type: "absolute",
-                    nodeType: node.type as NodeType,
-                    selectedEdges: [...selectedEdgesById.keys()],
-                    shapeIndex: node.data.shape.index,
-                    sourceEdgeIndexes: edges.flatMap((e) =>
-                      e.source === change.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
-                    ),
-                    targetEdgeIndexes: edges.flatMap((e) =>
-                      e.target === change.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
-                    ),
-                    position: change.positionAbsolute,
-                  },
+                  dmnObjectQName: node.data.dmnObjectQName,
+                  dmnObjectId: node.data.dmnObject?.["@_id"],
+                  nodeNature: nodeNatures[node.type as NodeType],
                 });
-
-                // FIXME: This should be inside `repositionNode` I guess?
-
-                // Update nested
-                // External Decision Services will have encapsulated and 
output decisions, but they aren't depicted in the graph.
-                if (node.type === NODE_TYPES.decisionService && 
!node.data.dmnObjectQName.prefix) {
-                  const decisionService = node.data.dmnObject as 
DMN15__tDecisionService;
-                  const nested = [
-                    ...(decisionService.outputDecision ?? []),
-                    ...(decisionService.encapsulatedDecision ?? []),
-                  ];
-
-                  for (let i = 0; i < nested.length; i++) {
-                    const nestedNode = nodesById.get(nested[i]["@_href"])!;
-                    const snappedNestedNodeShapeWithAppliedDelta = 
snapShapePosition(
-                      state.diagram.snapGrid,
-                      offsetShapePosition(nestedNode.data.shape, delta)
-                    );
-                    repositionNode({
-                      definitions: state.dmn.model.definitions,
-                      drdIndex: state.diagram.drdIndex,
-                      controlWaypointsByEdge,
-                      change: {
-                        type: "absolute",
-                        nodeType: nestedNode.type as NodeType,
-                        selectedEdges: edges.map((e) => e.id),
-                        shapeIndex: nestedNode.data.shape.index,
-                        sourceEdgeIndexes: edges.flatMap((e) =>
-                          e.source === nestedNode.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
-                        ),
-                        targetEdgeIndexes: edges.flatMap((e) =>
-                          e.target === nestedNode.id && e.data?.dmnEdge ? 
[e.data.dmnEdge.index] : []
-                        ),
-                        position: snappedNestedNodeShapeWithAppliedDelta,
-                      },
-                    });
-                  }
-                }
-              }
-              break;
-            case "remove":
-              console.debug(`DMN DIAGRAM: 'onNodesChange' --> remove 
'${change.id}'`);
-              const node = nodesById.get(change.id)!;
-              deleteNode({
-                definitions: state.dmn.model.definitions,
-                drdIndex: state.diagram.drdIndex,
-                dmnObjectQName: node.data.dmnObjectQName,
-                dmnObjectId: node.data.dmnObject?.["@_id"],
-                nodeNature: nodeNatures[node.type as NodeType],
-              });
-              state.dispatch.diagram.setNodeStatus(state, node.id, {
-                selected: false,
-                dragging: false,
-                resizing: false,
-              });
-              break;
-            case "reset":
-              state.dispatch.diagram.setNodeStatus(state, change.item.id, {
-                selected: false,
-                dragging: false,
-                resizing: false,
-              });
-              break;
-            case "select":
-              state.dispatch.diagram.setNodeStatus(state, change.id, { 
selected: change.selected });
-              break;
+                state.dispatch.diagram.setNodeStatus(state, node.id, {
+                  selected: false,
+                  dragging: false,
+                  resizing: false,
+                });
+                break;
+              case "reset":
+                state.dispatch.diagram.setNodeStatus(state, change.item.id, {
+                  selected: false,
+                  dragging: false,
+                  resizing: false,
+                });
+                break;
+              case "select":
+                state.dispatch.diagram.setNodeStatus(state, change.id, { 
selected: change.selected });
+                break;
+            }
           }
-        }
-      });
-    },
-    [reactFlowInstance, dmnEditorStoreApi, nodesById, dmnShapesByHref, edges, 
selectedEdgesById]
-  );
-
-  const resetToBeforeEditingBegan = useCallback(() => {
-    dmnEditorStoreApi.setState((state) => {
-      state.dmn.model = dmnModelBeforeEditingRef.current;
-      state.diagram.draggingNodes = [];
-      state.diagram.draggingWaypoints = [];
-      state.diagram.resizingNodes = [];
-      state.diagram.dropTargetNode = undefined;
-      state.diagram.edgeIdBeingUpdated = undefined;
-    });
-  }, [dmnEditorStoreApi, dmnModelBeforeEditingRef]);
+        });
+      },
+      [reactFlowInstance, dmnEditorStoreApi, nodesById, dmnShapesByHref, 
edges, selectedEdgesById]
+    );
 
-  const onNodeDrag = useCallback<RF.NodeDragHandler>(
-    (e, node: RF.Node<DmnDiagramNodeData>) => {
-      nodeIdBeingDraggedRef.current = node.id;
+    const resetToBeforeEditingBegan = useCallback(() => {
       dmnEditorStoreApi.setState((state) => {
-        state.diagram.dropTargetNode = getFirstNodeFittingBounds(
-          node.id,
-          {
-            // We can't use node.data.dmnObject because it hasn't been updated 
at this point yet.
-            "@_x": node.positionAbsolute?.x ?? 0,
-            "@_y": node.positionAbsolute?.y ?? 0,
-            "@_width": node.width ?? 0,
-            "@_height": node.height ?? 0,
-          },
-          MIN_NODE_SIZES[node.type as NodeType],
-          state.diagram.snapGrid
-        );
+        state.dmn.model = dmnModelBeforeEditingRef.current;
+        state.diagram.draggingNodes = [];
+        state.diagram.draggingWaypoints = [];
+        state.diagram.resizingNodes = [];
+        state.diagram.dropTargetNode = undefined;
+        state.diagram.edgeIdBeingUpdated = undefined;
       });
-    },
-    [dmnEditorStoreApi, getFirstNodeFittingBounds]
-  );
+    }, [dmnEditorStoreApi, dmnModelBeforeEditingRef]);
 
-  const onNodeDragStart = useCallback<RF.NodeDragHandler>(
-    (e, node: RF.Node<DmnDiagramNodeData>, nodes) => {
-      dmnModelBeforeEditingRef.current = thisDmn.model;
-      onNodeDrag(e, node, nodes);
-    },
-    [thisDmn.model, dmnModelBeforeEditingRef, onNodeDrag]
-  );
+    const onNodeDrag = useCallback<RF.NodeDragHandler>(
+      (e, node: RF.Node<DmnDiagramNodeData>) => {
+        nodeIdBeingDraggedRef.current = node.id;
+        dmnEditorStoreApi.setState((state) => {
+          state.diagram.dropTargetNode = getFirstNodeFittingBounds(
+            node.id,
+            {
+              // We can't use node.data.dmnObject because it hasn't been 
updated at this point yet.
+              "@_x": node.positionAbsolute?.x ?? 0,
+              "@_y": node.positionAbsolute?.y ?? 0,
+              "@_width": node.width ?? 0,
+              "@_height": node.height ?? 0,
+            },
+            MIN_NODE_SIZES[node.type as NodeType],
+            state.diagram.snapGrid
+          );
+        });
+      },
+      [dmnEditorStoreApi, getFirstNodeFittingBounds]
+    );
 
-  const onNodeDragStop = useCallback<RF.NodeDragHandler>(
-    (e, node: RF.Node<DmnDiagramNodeData>) => {
-      console.debug("DMN DIAGRAM: `onNodeDragStop`");
-      const nodeBeingDragged = nodesById.get(nodeIdBeingDraggedRef.current!);
-      nodeIdBeingDraggedRef.current = null;
-      if (!nodeBeingDragged) {
-        return;
-      }
+    const onNodeDragStart = useCallback<RF.NodeDragHandler>(
+      (e, node: RF.Node<DmnDiagramNodeData>, nodes) => {
+        dmnModelBeforeEditingRef.current = thisDmn.model;
+        onNodeDrag(e, node, nodes);
+      },
+      [thisDmn.model, dmnModelBeforeEditingRef, onNodeDrag]
+    );
 
-      // Validate
-      const dropTargetNode = 
dmnEditorStoreApi.getState().diagram.dropTargetNode;
-      if (dropTargetNode && containment.has(dropTargetNode.type as NodeType) 
&& !isDropTargetNodeValidForSelection) {
-        console.debug(
-          `DMN DIAGRAM: Invalid containment: 
'${[...selectedNodeTypes].join("', '")}' inside '${
-            dropTargetNode.type
-          }'. Ignoring nodes dropped.`
-        );
-        resetToBeforeEditingBegan();
-        return;
-      }
+    const onNodeDragStop = useCallback<RF.NodeDragHandler>(
+      (e, node: RF.Node<DmnDiagramNodeData>) => {
+        console.debug("DMN DIAGRAM: `onNodeDragStop`");
+        const nodeBeingDragged = nodesById.get(nodeIdBeingDraggedRef.current!);
+        nodeIdBeingDraggedRef.current = null;
+        if (!nodeBeingDragged) {
+          return;
+        }
+
+        // Validate
+        const dropTargetNode = 
dmnEditorStoreApi.getState().diagram.dropTargetNode;
+        if (dropTargetNode && containment.has(dropTargetNode.type as NodeType) 
&& !isDropTargetNodeValidForSelection) {
+          console.debug(
+            `DMN DIAGRAM: Invalid containment: 
'${[...selectedNodeTypes].join("', '")}' inside '${
+              dropTargetNode.type
+            }'. Ignoring nodes dropped.`
+          );
+          resetToBeforeEditingBegan();
+          return;
+        }
 
-      const selectedNodes = [...selectedNodesById.values()];
+        const selectedNodes = [...selectedNodesById.values()];
 
-      try {
-        dmnEditorStoreApi.setState((state) => {
-          state.diagram.dropTargetNode = undefined;
+        try {
+          dmnEditorStoreApi.setState((state) => {
+            state.diagram.dropTargetNode = undefined;
 
-          if (!node.dragging) {
-            return;
-          }
+            if (!node.dragging) {
+              return;
+            }
 
-          // Un-parent
-          if (nodeBeingDragged.data.parentRfNode) {
-            const p = nodesById.get(nodeBeingDragged.data.parentRfNode.id);
-            if (p?.type === NODE_TYPES.decisionService && 
nodeBeingDragged.type === NODE_TYPES.decision) {
+            // Un-parent
+            if (nodeBeingDragged.data.parentRfNode) {
+              const p = nodesById.get(nodeBeingDragged.data.parentRfNode.id);
+              if (p?.type === NODE_TYPES.decisionService && 
nodeBeingDragged.type === NODE_TYPES.decision) {
+                for (let i = 0; i < selectedNodes.length; i++) {
+                  deleteDecisionFromDecisionService({
+                    definitions: state.dmn.model.definitions,
+                    decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // 
We can assume that all selected nodes are Decisions because the contaiment was 
validated above.
+                    decisionServiceId: p.data.dmnObject!["@_id"]!,
+                  });
+                }
+              } else {
+                console.debug(
+                  `DMN DIAGRAM: Ignoring '${nodeBeingDragged.type}' with 
parent '${dropTargetNode?.type}' dropping somewhere..`
+                );
+              }
+            }
+
+            // Parent
+            if (dropTargetNode?.type === NODE_TYPES.decisionService) {
               for (let i = 0; i < selectedNodes.length; i++) {
-                deleteDecisionFromDecisionService({
+                addDecisionToDecisionService({
                   definitions: state.dmn.model.definitions,
+                  drdIndex: state.diagram.drdIndex,
                   decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // We 
can assume that all selected nodes are Decisions because the contaiment was 
validated above.
-                  decisionServiceId: p.data.dmnObject!["@_id"]!,
+                  decisionServiceId: 
nodesById.get(dropTargetNode.id)!.data.dmnObject!["@_id"]!,
+                  snapGrid: state.diagram.snapGrid,
                 });
               }
             } else {
               console.debug(
-                `DMN DIAGRAM: Ignoring '${nodeBeingDragged.type}' with parent 
'${dropTargetNode?.type}' dropping somewhere..`
+                `DMN DIAGRAM: Ignoring '${nodeBeingDragged.type}' dropped on 
top of '${dropTargetNode?.type}'`
               );
             }
-          }
+          });
+        } catch (e) {
+          console.error(e);
+          resetToBeforeEditingBegan();
+        }
+      },
+      [
+        dmnEditorStoreApi,
+        isDropTargetNodeValidForSelection,
+        nodesById,
+        resetToBeforeEditingBegan,
+        selectedNodeTypes,
+        selectedNodesById,
+      ]
+    );
 
-          // Parent
-          if (dropTargetNode?.type === NODE_TYPES.decisionService) {
-            for (let i = 0; i < selectedNodes.length; i++) {
-              addDecisionToDecisionService({
-                definitions: state.dmn.model.definitions,
-                drdIndex: state.diagram.drdIndex,
-                decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // We 
can assume that all selected nodes are Decisions because the contaiment was 
validated above.
-                decisionServiceId: 
nodesById.get(dropTargetNode.id)!.data.dmnObject!["@_id"]!,
-                snapGrid: state.diagram.snapGrid,
-              });
+    const onEdgesChange = useCallback<RF.OnEdgesChange>(
+      (changes) => {
+        dmnEditorStoreApi.setState((state) => {
+          for (const change of changes) {
+            switch (change.type) {
+              case "select":
+                console.debug(`DMN DIAGRAM: 'onEdgesChange' --> select 
'${change.id}'`);
+                state.dispatch.diagram.setEdgeStatus(state, change.id, { 
selected: change.selected });
+                break;
+              case "remove":
+                console.debug(`DMN DIAGRAM: 'onEdgesChange' --> remove 
'${change.id}'`);
+                const edge = edgesById.get(change.id);
+                if (edge?.data) {
+                  deleteEdge({
+                    definitions: state.dmn.model.definitions,
+                    drdIndex: state.diagram.drdIndex,
+                    edge: { id: change.id, dmnObject: edge.data.dmnObject },
+                  });
+                  state.dispatch.diagram.setEdgeStatus(state, change.id, { 
selected: false, draggingWaypoint: false });
+                }
+                break;
+              case "add":
+              case "reset":
+                console.debug(`DMN DIAGRAM: 'onEdgesChange' --> add/reset 
'${change.item.id}'. Ignoring`);
             }
-          } else {
-            console.debug(
-              `DMN DIAGRAM: Ignoring '${nodeBeingDragged.type}' dropped on top 
of '${dropTargetNode?.type}'`
-            );
           }
         });
-      } catch (e) {
-        console.error(e);
-        resetToBeforeEditingBegan();
-      }
-    },
-    [
-      dmnEditorStoreApi,
-      isDropTargetNodeValidForSelection,
-      nodesById,
-      resetToBeforeEditingBegan,
-      selectedNodeTypes,
-      selectedNodesById,
-    ]
-  );
-
-  const onEdgesChange = useCallback<RF.OnEdgesChange>(
-    (changes) => {
-      dmnEditorStoreApi.setState((state) => {
-        for (const change of changes) {
-          switch (change.type) {
-            case "select":
-              console.debug(`DMN DIAGRAM: 'onEdgesChange' --> select 
'${change.id}'`);
-              state.dispatch.diagram.setEdgeStatus(state, change.id, { 
selected: change.selected });
-              break;
-            case "remove":
-              console.debug(`DMN DIAGRAM: 'onEdgesChange' --> remove 
'${change.id}'`);
-              const edge = edgesById.get(change.id);
-              if (edge?.data) {
-                deleteEdge({
-                  definitions: state.dmn.model.definitions,
-                  drdIndex: state.diagram.drdIndex,
-                  edge: { id: change.id, dmnObject: edge.data.dmnObject },
-                });
-                state.dispatch.diagram.setEdgeStatus(state, change.id, { 
selected: false, draggingWaypoint: false });
-              }
-              break;
-            case "add":
-            case "reset":
-              console.debug(`DMN DIAGRAM: 'onEdgesChange' --> add/reset 
'${change.item.id}'. Ignoring`);
-          }
-        }
-      });
-    },
-    [dmnEditorStoreApi, edgesById]
-  );
+      },
+      [dmnEditorStoreApi, edgesById]
+    );
 
-  const onEdgeUpdate = useCallback<RF.OnEdgeUpdateFunc<DmnDiagramEdgeData>>(
-    (oldEdge, newConnection) => {
-      console.debug("DMN DIAGRAM: `onEdgeUpdate`", oldEdge, newConnection);
+    const onEdgeUpdate = useCallback<RF.OnEdgeUpdateFunc<DmnDiagramEdgeData>>(
+      (oldEdge, newConnection) => {
+        console.debug("DMN DIAGRAM: `onEdgeUpdate`", oldEdge, newConnection);
 
-      const sourceNode = nodesById.get(newConnection.source!);
-      const targetNode = nodesById.get(newConnection.target!);
-      if (!sourceNode || !targetNode) {
-        throw new Error("Cannot create connection without target and source 
nodes!");
-      }
+        const sourceNode = nodesById.get(newConnection.source!);
+        const targetNode = nodesById.get(newConnection.target!);
+        if (!sourceNode || !targetNode) {
+          throw new Error("Cannot create connection without target and source 
nodes!");
+        }
 
-      const sourceBounds = sourceNode.data.shape["dc:Bounds"];
-      const targetBounds = targetNode.data.shape["dc:Bounds"];
-      if (!sourceBounds || !targetBounds) {
-        throw new Error("Cannot create connection without target bounds!");
-      }
+        const sourceBounds = sourceNode.data.shape["dc:Bounds"];
+        const targetBounds = targetNode.data.shape["dc:Bounds"];
+        if (!sourceBounds || !targetBounds) {
+          throw new Error("Cannot create connection without target bounds!");
+        }
 
-      // --------- This is where we draw the line between the diagram and the 
model.
+        // --------- This is where we draw the line between the diagram and 
the model.
 
-      const lastWaypoint = oldEdge.data?.dmnEdge
-        ? 
oldEdge.data!.dmnEdge!["di:waypoint"]![oldEdge.data!.dmnEdge!["di:waypoint"]!.length
 - 1]!
-        : getBoundsCenterPoint(targetBounds);
-      const firstWaypoint = oldEdge.data?.dmnEdge
-        ? oldEdge.data!.dmnEdge!["di:waypoint"]![0]!
-        : getBoundsCenterPoint(sourceBounds);
+        const lastWaypoint = oldEdge.data?.dmnEdge
+          ? 
oldEdge.data!.dmnEdge!["di:waypoint"]![oldEdge.data!.dmnEdge!["di:waypoint"]!.length
 - 1]!
+          : getBoundsCenterPoint(targetBounds);
+        const firstWaypoint = oldEdge.data?.dmnEdge
+          ? oldEdge.data!.dmnEdge!["di:waypoint"]![0]!
+          : getBoundsCenterPoint(sourceBounds);
 
-      dmnEditorStoreApi.setState((state) => {
-        const { newDmnEdge } = addEdge({
-          definitions: state.dmn.model.definitions,
-          drdIndex: state.diagram.drdIndex,
-          edge: {
-            type: oldEdge.type as EdgeType,
-            targetHandle: ((newConnection.targetHandle as 
PositionalNodeHandleId) ??
-              getHandlePosition({ shapeBounds: targetBounds, waypoint: 
lastWaypoint })
-                .handlePosition) as PositionalNodeHandleId,
-            sourceHandle: ((newConnection.sourceHandle as 
PositionalNodeHandleId) ??
-              getHandlePosition({ shapeBounds: sourceBounds, waypoint: 
firstWaypoint })
-                .handlePosition) as PositionalNodeHandleId,
-          },
-          sourceNode: {
-            type: sourceNode.type as NodeType,
-            href: sourceNode.id,
-            data: sourceNode.data,
-            bounds: sourceBounds,
-            shapeId: sourceNode.data.shape["@_id"],
-          },
-          targetNode: {
-            type: targetNode.type as NodeType,
-            href: targetNode.id,
-            data: targetNode.data,
-            bounds: targetBounds,
-            index: targetNode.data.index,
-            shapeId: targetNode.data.shape["@_id"],
-          },
-          keepWaypoints: true,
-        });
-
-        // The DMN Edge changed nodes, so we need to delete the old one, but 
keep the waypoints!
-        if (newDmnEdge["@_dmnElementRef"] !== oldEdge.id) {
-          const { dmnEdge: deletedDmnEdge } = deleteEdge({
+        dmnEditorStoreApi.setState((state) => {
+          const { newDmnEdge } = addEdge({
             definitions: state.dmn.model.definitions,
             drdIndex: state.diagram.drdIndex,
-            edge: { id: oldEdge.id, dmnObject: oldEdge.data!.dmnObject },
+            edge: {
+              type: oldEdge.type as EdgeType,
+              targetHandle: ((newConnection.targetHandle as 
PositionalNodeHandleId) ??
+                getHandlePosition({ shapeBounds: targetBounds, waypoint: 
lastWaypoint })
+                  .handlePosition) as PositionalNodeHandleId,
+              sourceHandle: ((newConnection.sourceHandle as 
PositionalNodeHandleId) ??
+                getHandlePosition({ shapeBounds: sourceBounds, waypoint: 
firstWaypoint })
+                  .handlePosition) as PositionalNodeHandleId,
+            },
+            sourceNode: {
+              type: sourceNode.type as NodeType,
+              href: sourceNode.id,
+              data: sourceNode.data,
+              bounds: sourceBounds,
+              shapeId: sourceNode.data.shape["@_id"],
+            },
+            targetNode: {
+              type: targetNode.type as NodeType,
+              href: targetNode.id,
+              data: targetNode.data,
+              bounds: targetBounds,
+              index: targetNode.data.index,
+              shapeId: targetNode.data.shape["@_id"],
+            },
+            keepWaypoints: true,
           });
 
-          const deletedWaypoints = deletedDmnEdge?.["di:waypoint"];
+          // The DMN Edge changed nodes, so we need to delete the old one, but 
keep the waypoints!
+          if (newDmnEdge["@_dmnElementRef"] !== oldEdge.id) {
+            const { dmnEdge: deletedDmnEdge } = deleteEdge({
+              definitions: state.dmn.model.definitions,
+              drdIndex: state.diagram.drdIndex,
+              edge: { id: oldEdge.id, dmnObject: oldEdge.data!.dmnObject },
+            });
 
-          if (oldEdge.source !== newConnection.source && deletedWaypoints) {
-            newDmnEdge["di:waypoint"] = [newDmnEdge["di:waypoint"]![0], 
...deletedWaypoints.slice(1)];
-          }
+            const deletedWaypoints = deletedDmnEdge?.["di:waypoint"];
 
-          if (oldEdge.target !== newConnection.target && deletedWaypoints) {
-            newDmnEdge["di:waypoint"] = [
-              ...deletedWaypoints.slice(0, deletedWaypoints.length - 1),
-              newDmnEdge["di:waypoint"]![newDmnEdge["di:waypoint"]!.length - 
1],
-            ];
-          }
-        }
+            if (oldEdge.source !== newConnection.source && deletedWaypoints) {
+              newDmnEdge["di:waypoint"] = [newDmnEdge["di:waypoint"]![0], 
...deletedWaypoints.slice(1)];
+            }
 
-        // Keep the updated edge selected
-        state.diagram._selectedEdges = [newDmnEdge["@_dmnElementRef"]!];
+            if (oldEdge.target !== newConnection.target && deletedWaypoints) {
+              newDmnEdge["di:waypoint"] = [
+                ...deletedWaypoints.slice(0, deletedWaypoints.length - 1),
+                newDmnEdge["di:waypoint"]![newDmnEdge["di:waypoint"]!.length - 
1],
+              ];
+            }
+          }
 
-        // Finish edge update atomically.
-        state.diagram.ongoingConnection = undefined;
-        state.diagram.edgeIdBeingUpdated = undefined;
-      });
-    },
-    [dmnEditorStoreApi, nodesById]
-  );
+          // Keep the updated edge selected
+          state.diagram._selectedEdges = [newDmnEdge["@_dmnElementRef"]!];
 
-  const onEdgeUpdateStart = useCallback(
-    (e: React.MouseEvent | React.TouchEvent, edge: RF.Edge, handleType: 
RF.HandleType) => {
-      console.debug("DMN DIAGRAM: `onEdgeUpdateStart`");
-      dmnEditorStoreApi.setState((state) => {
-        state.diagram.edgeIdBeingUpdated = edge.id;
-      });
-    },
-    [dmnEditorStoreApi]
-  );
+          // Finish edge update atomically.
+          state.diagram.ongoingConnection = undefined;
+          state.diagram.edgeIdBeingUpdated = undefined;
+        });
+      },
+      [dmnEditorStoreApi, nodesById]
+    );
 
-  const onEdgeUpdateEnd = useCallback(
-    (e: MouseEvent | TouchEvent, edge: RF.Edge, handleType: RF.HandleType) => {
-      console.debug("DMN DIAGRAM: `onEdgeUpdateEnd`");
+    const onEdgeUpdateStart = useCallback(
+      (e: React.MouseEvent | React.TouchEvent, edge: RF.Edge, handleType: 
RF.HandleType) => {
+        console.debug("DMN DIAGRAM: `onEdgeUpdateStart`");
+        dmnEditorStoreApi.setState((state) => {
+          state.diagram.edgeIdBeingUpdated = edge.id;
+        });
+      },
+      [dmnEditorStoreApi]
+    );
 
-      // Needed for when the edge update operation doesn't change anything.
-      dmnEditorStoreApi.setState((state) => {
-        state.diagram.ongoingConnection = undefined;
-        state.diagram.edgeIdBeingUpdated = undefined;
-      });
-    },
-    [dmnEditorStoreApi]
-  );
+    const onEdgeUpdateEnd = useCallback(
+      (e: MouseEvent | TouchEvent, edge: RF.Edge, handleType: RF.HandleType) 
=> {
+        console.debug("DMN DIAGRAM: `onEdgeUpdateEnd`");
 
-  // Override Reactflow's behavior by intercepting the keydown event using its 
`capture` variant.
-  const handleRfKeyDownCapture = useCallback(
-    (e: React.KeyboardEvent) => {
-      if (e.key === "Escape") {
-        if (isDiagramEditingInProgress && dmnModelBeforeEditingRef.current) {
-          console.debug(
-            "DMN DIAGRAM: Intercepting Escape pressed and preventing 
propagation. Reverting DMN model to what it was before editing began."
-          );
+        // Needed for when the edge update operation doesn't change anything.
+        dmnEditorStoreApi.setState((state) => {
+          state.diagram.ongoingConnection = undefined;
+          state.diagram.edgeIdBeingUpdated = undefined;
+        });
+      },
+      [dmnEditorStoreApi]
+    );
 
-          e.stopPropagation();
-          e.preventDefault();
+    // Override Reactflow's behavior by intercepting the keydown event using 
its `capture` variant.
+    const handleRfKeyDownCapture = useCallback(
+      (e: React.KeyboardEvent) => {
+        if (e.key === "Escape") {
+          if (isDiagramEditingInProgress && dmnModelBeforeEditingRef.current) {
+            console.debug(
+              "DMN DIAGRAM: Intercepting Escape pressed and preventing 
propagation. Reverting DMN model to what it was before editing began."
+            );
 
-          resetToBeforeEditingBegan();
-        } else if (!diagram.ongoingConnection) {
-          dmnEditorStoreApi.setState((state) => {
-            if (selectedNodesById.size > 0 || selectedEdgesById.size > 0) {
-              console.debug("DMN DIAGRAM: Esc pressed. Desselecting 
everything.");
-              state.diagram._selectedNodes = [];
-              state.diagram._selectedEdges = [];
-              e.preventDefault();
-            } else if (selectedNodesById.size <= 0 && selectedEdgesById.size 
<= 0) {
-              console.debug("DMN DIAGRAM: Esc pressed. Closing all open 
panels.");
-              state.diagram.propertiesPanel.isOpen = false;
-              state.diagram.overlaysPanel.isOpen = false;
-              state.diagram.openNodesPanel = DiagramNodesPanel.NONE;
-              e.preventDefault();
-            } else {
-              // Let the
-            }
-          });
-        } else {
-          // Let the KeyboardShortcuts handle it.
+            e.stopPropagation();
+            e.preventDefault();
+
+            resetToBeforeEditingBegan();
+          } else if (!diagram.ongoingConnection) {
+            dmnEditorStoreApi.setState((state) => {
+              if (selectedNodesById.size > 0 || selectedEdgesById.size > 0) {
+                console.debug("DMN DIAGRAM: Esc pressed. Desselecting 
everything.");
+                state.diagram._selectedNodes = [];
+                state.diagram._selectedEdges = [];
+                e.preventDefault();
+              } else if (selectedNodesById.size <= 0 && selectedEdgesById.size 
<= 0) {
+                console.debug("DMN DIAGRAM: Esc pressed. Closing all open 
panels.");
+                state.diagram.propertiesPanel.isOpen = false;
+                state.diagram.overlaysPanel.isOpen = false;
+                state.diagram.openNodesPanel = DiagramNodesPanel.NONE;
+                e.preventDefault();
+              } else {
+                // Let the
+              }
+            });
+          } else {
+            // Let the KeyboardShortcuts handle it.
+          }
         }
-      }
-    },
-    [
-      diagram.ongoingConnection,
-      dmnEditorStoreApi,
-      dmnModelBeforeEditingRef,
-      isDiagramEditingInProgress,
-      resetToBeforeEditingBegan,
-      selectedEdgesById.size,
-      selectedNodesById.size,
-    ]
-  );
-
-  const [showEmptyState, setShowEmptyState] = useState(true);
-
-  const isEmptyStateShowing =
-    showEmptyState && nodes.length === 0 && 
drgElementsWithoutVisualRepresentationOnCurrentDrd.length === 0;
+      },
+      [
+        diagram.ongoingConnection,
+        dmnEditorStoreApi,
+        dmnModelBeforeEditingRef,
+        isDiagramEditingInProgress,
+        resetToBeforeEditingBegan,
+        selectedEdgesById.size,
+        selectedNodesById.size,
+      ]
+    );
 
-  return (
-    <>
-      {isEmptyStateShowing && <DmnDiagramEmptyState 
setShowEmptyState={setShowEmptyState} />}
-      <DiagramContainerContextProvider container={container}>
-        <EdgeMarkers />
-        <RF.ReactFlow
-          connectionMode={RF.ConnectionMode.Loose} // Allow target handles to 
be used as source. This is very important for allowing the positional handles 
to be updated for the base of an edge.
-          onKeyDownCapture={handleRfKeyDownCapture} // Override Reactflow's 
keyboard listeners.
-          nodes={nodes}
-          edges={edges}
-          onNodesChange={onNodesChange}
-          onEdgesChange={onEdgesChange}
-          onEdgeUpdateStart={onEdgeUpdateStart}
-          onEdgeUpdateEnd={onEdgeUpdateEnd}
-          onEdgeUpdate={onEdgeUpdate}
-          onlyRenderVisibleElements={true}
-          zoomOnDoubleClick={false}
-          elementsSelectable={true}
-          panOnScroll={true}
-          zoomOnScroll={false}
-          preventScrolling={true}
-          selectionOnDrag={true}
-          panOnDrag={PAN_ON_DRAG}
-          panActivationKeyCode={"Alt"}
-          selectionMode={RF.SelectionMode.Full} // For selections happening 
inside Groups/DecisionServices it's better to leave it as "Full"
-          isValidConnection={isValidConnection}
-          connectionLineComponent={ConnectionLine}
-          onConnect={onConnect}
-          onConnectStart={onConnectStart}
-          onConnectEnd={onConnectEnd}
-          // (begin)
-          // 'Starting to drag' and 'dragging' should have the same behavior. 
Otherwise,
-          // clicking a node and letting it go, without moving, won't work 
properly, and
-          // Decisions will be removed from Decision Services.
-          onNodeDragStart={onNodeDragStart}
-          onNodeDrag={onNodeDrag}
-          // (end)
-          onNodeDragStop={onNodeDragStop}
-          nodeTypes={nodeTypes}
-          edgeTypes={edgeTypes}
-          snapToGrid={true}
-          snapGrid={rfSnapGrid}
-          defaultViewport={DEFAULT_VIEWPORT}
-          fitView={false}
-          fitViewOptions={FIT_VIEW_OPTIONS}
-          attributionPosition={"bottom-right"}
-          onInit={setReactFlowInstance}
-          // (begin)
-          // Used to make the Palette work by dropping nodes on the Reactflow 
Canvas
-          onDrop={onDrop}
-          onDragOver={onDragOver}
-          // (end)
-        >
-          <SelectionStatus />
-          <Palette pulse={isEmptyStateShowing} />
-          <TopRightCornerPanels />
-          <PanWhenAltPressed />
-          <KeyboardShortcuts />
-          {!isFirefox && <RF.Background />}
-          <RF.Controls fitViewOptions={FIT_VIEW_OPTIONS} 
position={"bottom-right"} />
-          <SetConnectionToReactFlowStore />
-        </RF.ReactFlow>
-      </DiagramContainerContextProvider>
-    </>
-  );
-}
+    const [showEmptyState, setShowEmptyState] = useState(true);
+
+    const isEmptyStateShowing =
+      showEmptyState && nodes.length === 0 && 
drgElementsWithoutVisualRepresentationOnCurrentDrd.length === 0;
+
+    return (
+      <>
+        {isEmptyStateShowing && <DmnDiagramEmptyState 
setShowEmptyState={setShowEmptyState} />}
+        <DiagramContainerContextProvider container={container}>
+          <svg style={{ position: "absolute", top: 0, left: 0 }}>
+            <EdgeMarkers />
+          </svg>
+
+          <RF.ReactFlow
+            connectionMode={RF.ConnectionMode.Loose} // Allow target handles 
to be used as source. This is very important for allowing the positional 
handles to be updated for the base of an edge.
+            onKeyDownCapture={handleRfKeyDownCapture} // Override Reactflow's 
keyboard listeners.
+            nodes={nodes}
+            edges={edges}
+            onNodesChange={onNodesChange}
+            onEdgesChange={onEdgesChange}
+            onEdgeUpdateStart={onEdgeUpdateStart}
+            onEdgeUpdateEnd={onEdgeUpdateEnd}
+            onEdgeUpdate={onEdgeUpdate}
+            onlyRenderVisibleElements={true}
+            zoomOnDoubleClick={false}
+            elementsSelectable={true}
+            panOnScroll={true}
+            zoomOnScroll={false}
+            preventScrolling={true}
+            selectionOnDrag={true}
+            panOnDrag={PAN_ON_DRAG}
+            panActivationKeyCode={"Alt"}
+            selectionMode={RF.SelectionMode.Full} // For selections happening 
inside Groups/DecisionServices it's better to leave it as "Full"
+            isValidConnection={isValidConnection}
+            connectionLineComponent={ConnectionLine}
+            onConnect={onConnect}
+            onConnectStart={onConnectStart}
+            onConnectEnd={onConnectEnd}
+            // (begin)
+            // 'Starting to drag' and 'dragging' should have the same 
behavior. Otherwise,
+            // clicking a node and letting it go, without moving, won't work 
properly, and
+            // Decisions will be removed from Decision Services.
+            onNodeDragStart={onNodeDragStart}
+            onNodeDrag={onNodeDrag}
+            // (end)
+            onNodeDragStop={onNodeDragStop}
+            nodeTypes={nodeTypes}
+            edgeTypes={edgeTypes}
+            snapToGrid={true}
+            snapGrid={rfSnapGrid}
+            defaultViewport={DEFAULT_VIEWPORT}
+            fitView={false}
+            fitViewOptions={FIT_VIEW_OPTIONS}
+            attributionPosition={"bottom-right"}
+            onInit={setReactFlowInstance}
+            // (begin)
+            // Used to make the Palette work by dropping nodes on the 
Reactflow Canvas
+            onDrop={onDrop}
+            onDragOver={onDragOver}
+            // (end)
+          >
+            <SelectionStatus />
+            <Palette pulse={isEmptyStateShowing} />
+            <TopRightCornerPanels />
+            <PanWhenAltPressed />
+            <KeyboardShortcuts />
+            {!isFirefox && <RF.Background />}
+            <RF.Controls fitViewOptions={FIT_VIEW_OPTIONS} 
position={"bottom-right"} />
+            <SetConnectionToReactFlowStore />
+          </RF.ReactFlow>
+        </DiagramContainerContextProvider>
+      </>
+    );
+  }
+);
 
 function DmnDiagramEmptyState({
   setShowEmptyState,
diff --git a/packages/dmn-editor/src/diagram/edges/EdgeMarkers.tsx 
b/packages/dmn-editor/src/diagram/edges/EdgeMarkers.tsx
index 3baec6d9e09..f36ffde8165 100644
--- a/packages/dmn-editor/src/diagram/edges/EdgeMarkers.tsx
+++ b/packages/dmn-editor/src/diagram/edges/EdgeMarkers.tsx
@@ -21,57 +21,55 @@ import * as React from "react";
 
 export function EdgeMarkers() {
   return (
-    <svg style={{ position: "absolute", top: 0, left: 0 }}>
-      <defs>
-        <marker
-          id="closed-circle-at-center"
-          viewBox="0 0 10 10"
-          refX={5}
-          refY={5}
-          markerUnits="userSpaceOnUse"
-          markerWidth="8"
-          markerHeight="8"
-          orient="auto-start-reverse"
-        >
-          <circle cx="5" cy="5" r="5" fill="context-fill" 
stroke="context-stroke" />
-        </marker>
-        <marker
-          id="closed-circle-at-border"
-          viewBox="0 0 10 10"
-          refX={10}
-          refY={5}
-          markerUnits="userSpaceOnUse"
-          markerWidth="8"
-          markerHeight="8"
-          orient="auto-start-reverse"
-        >
-          <circle cx="5" cy="5" r="5" fill="context-fill" 
stroke="context-stroke" />
-        </marker>
-        <marker
-          id="closed-arrow"
-          viewBox="0 0 10 10"
-          refX={10}
-          refY={5}
-          markerUnits="userSpaceOnUse"
-          markerWidth="8"
-          markerHeight="8"
-          orient="auto-start-reverse"
-        >
-          <path d="M 0 0 L 10 5 L 0 10 z" fill="context-fill" 
stroke="context-stroke" />
-        </marker>
-        <marker
-          id="open-arrow"
-          viewBox="0 0 10 10"
-          refX={10}
-          refY={5}
-          markerUnits="userSpaceOnUse"
-          markerWidth="8"
-          markerHeight="8"
-          orient="auto-start-reverse"
-        >
-          <path d="M 0,0 L 10,5 M 10,5 L 0,10" stroke="black" />
-        </marker>
-      </defs>
-    </svg>
+    <defs>
+      <marker
+        id="closed-circle-at-center"
+        viewBox="0 0 10 10"
+        refX={5}
+        refY={5}
+        markerUnits="userSpaceOnUse"
+        markerWidth="8"
+        markerHeight="8"
+        orient="auto-start-reverse"
+      >
+        <circle cx="5" cy="5" r="5" fill="context-fill" 
stroke="context-stroke" />
+      </marker>
+      <marker
+        id="closed-circle-at-border"
+        viewBox="0 0 10 10"
+        refX={10}
+        refY={5}
+        markerUnits="userSpaceOnUse"
+        markerWidth="8"
+        markerHeight="8"
+        orient="auto-start-reverse"
+      >
+        <circle cx="5" cy="5" r="5" fill="context-fill" 
stroke="context-stroke" />
+      </marker>
+      <marker
+        id="closed-arrow"
+        viewBox="0 0 10 10"
+        refX={10}
+        refY={5}
+        markerUnits="userSpaceOnUse"
+        markerWidth="8"
+        markerHeight="8"
+        orient="auto-start-reverse"
+      >
+        <path d="M 0 0 L 10 5 L 0 10 z" fill="context-fill" 
stroke="context-stroke" />
+      </marker>
+      <marker
+        id="open-arrow"
+        viewBox="0 0 10 10"
+        refX={10}
+        refY={5}
+        markerUnits="userSpaceOnUse"
+        markerWidth="8"
+        markerHeight="8"
+        orient="auto-start-reverse"
+      >
+        <path d="M 0,0 L 10,5 M 10,5 L 0,10" stroke="black" />
+      </marker>
+    </defs>
   );
 }
diff --git a/packages/dmn-editor/src/diagram/maths/DmnMaths.ts 
b/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
index a070f818944..5e965c0a7db 100644
--- a/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
+++ b/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
@@ -302,7 +302,6 @@ export function getNodeTypeFromDmnObject(dmnObject: 
NodeDmnObjects) {
   }
 
   const type = switchExpression(dmnObject.__$$element, {
-    // Normal nodes
     inputData: NODE_TYPES.inputData,
     decision: NODE_TYPES.decision,
     businessKnowledgeModel: NODE_TYPES.bkm,
@@ -310,8 +309,6 @@ export function getNodeTypeFromDmnObject(dmnObject: 
NodeDmnObjects) {
     decisionService: NODE_TYPES.decisionService,
     group: NODE_TYPES.group,
     textAnnotation: NODE_TYPES.textAnnotation,
-    // No nodes associated with
-    association: undefined,
     default: undefined,
   });
 
diff --git a/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts 
b/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
index 88fd8ee0a5b..c4c3a1b45c2 100644
--- a/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
+++ b/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
@@ -65,7 +65,7 @@ export const MIN_NODE_SIZES: Record<NodeType, (snapGrid: 
SnapGrid) => DC__Dimens
     };
   },
   [NODE_TYPES.textAnnotation]: (snapGrid) => {
-    const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, 200, 200);
+    const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, 200, 60);
     return {
       "@_width": snappedMinSize.width,
       "@_height": snappedMinSize.height,
diff --git a/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx 
b/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
index a5dd9c7fc90..ffd5a9d4663 100644
--- a/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
@@ -33,6 +33,7 @@ import { generateUuid } from 
"@kie-tools/boxed-expression-component/dist/api";
 import "./EditableNodeLabel.css";
 import { useFocusableElement } from "../../focus/useFocusableElement";
 import { flushSync } from "react-dom";
+import { NodeLabelPosition } from "./NodeSvgs";
 
 export type OnEditableNodeLabelChange = (value: string | undefined) => void;
 
@@ -50,7 +51,7 @@ export function EditableNodeLabel({
   shouldCommitOnBlur,
   skipValidation,
   allUniqueNames,
-  fontStyle,
+  fontCssProperties: fontStyle,
 }: {
   id?: string;
   shouldCommitOnBlur?: boolean;
@@ -58,14 +59,14 @@ export function EditableNodeLabel({
   truncate?: boolean;
   namedElement?: DMN15__tNamedElement;
   namedElementQName?: XmlQName;
-  position?: "center-center" | "top-center" | "center-left" | "top-left";
+  position: NodeLabelPosition;
   isEditing: boolean;
   value: string | undefined;
   setEditing: React.Dispatch<React.SetStateAction<boolean>>;
   onChange: OnEditableNodeLabelChange;
   skipValidation?: boolean;
   allUniqueNames: UniqueNameIndex;
-  fontStyle?: React.CSSProperties;
+  fontCssProperties?: React.CSSProperties;
 }) {
   const thisDmn = useDmnEditorStore((s) => s.dmn);
   const { importsByNamespace } = useDmnEditorDerivedStore();
@@ -211,10 +212,8 @@ export function EditableNodeLabel({
     )
   );
 
-  const positionClass = position ?? "center-center";
-
   return (
-    <div className={`kie-dmn-editor--editable-node-name-input ${positionClass} 
${grow ? "grow" : ""}`}>
+    <div className={`kie-dmn-editor--editable-node-name-input ${position} 
${grow ? "grow" : ""}`}>
       {(isEditing && (
         <input
           spellCheck={"false"} // Let's not confuse FEEL name validation with 
the browser's grammar check.
diff --git a/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts 
b/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
index 8df30f6e224..9374cc576e8 100644
--- a/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
+++ b/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
@@ -20,9 +20,11 @@
 import React, { useMemo } from "react";
 import { DMNDI15__DMNStyle } from 
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
 import { NodeType } from "../connections/graphStructure";
+import { NODE_TYPES } from "./NodeTypes";
+import { NodeLabelPosition } from "./NodeSvgs";
 
 export interface NodeStyle {
-  fontStyle: React.CSSProperties;
+  fontCssProperties: React.CSSProperties;
   shapeStyle: ShapeStyle;
 }
 
@@ -63,57 +65,43 @@ export function useNodeStyle(args: {
   nodeType?: NodeType;
   isEnabled?: boolean;
 }): NodeStyle {
-  const fillColor = useMemo(() => {
-    const blue = args.dmnStyle?.["dmndi:FillColor"]?.["@_blue"];
-    const green = args.dmnStyle?.["dmndi:FillColor"]?.["@_green"];
-    const red = args.dmnStyle?.["dmndi:FillColor"]?.["@_red"];
-
-    const opacity =
-      args.nodeType === "node_decisionService" ||
-      args.nodeType === "node_group" ||
-      args.nodeType === "node_textAnnotation"
-        ? 0.1
-        : DEFAULT_NODE_OPACITY;
-    if (!args.isEnabled || blue === undefined || green === undefined || red 
=== undefined) {
-      return `rgba(${DEFAULT_NODE_RED_FILL}, ${DEFAULT_NODE_GREEN_FILL}, 
${DEFAULT_NODE_BLUE_FILL}, ${opacity})`;
-    }
-
-    return `rgba(${red}, ${green}, ${blue}, ${opacity})`;
-  }, [args.dmnStyle, args.nodeType, args.isEnabled]);
-  const strokeColor = useMemo(() => {
-    const blue = args.dmnStyle?.["dmndi:StrokeColor"]?.["@_blue"];
-    const green = args.dmnStyle?.["dmndi:StrokeColor"]?.["@_green"];
-    const red = args.dmnStyle?.["dmndi:StrokeColor"]?.["@_red"];
-
-    if (!args.isEnabled || blue === undefined || green === undefined || red 
=== undefined) {
-      return DEFAULT_NODE_STROKE_COLOR;
-    }
-    return `rgba(${red}, ${green}, ${blue}, 1)`;
-  }, [args.dmnStyle, args.isEnabled]);
-
-  const fontProperties = useMemo(() => {
-    const blue = args.dmnStyle?.["dmndi:FontColor"]?.["@_blue"];
-    const green = args.dmnStyle?.["dmndi:FontColor"]?.["@_green"];
-    const red = args.dmnStyle?.["dmndi:FontColor"]?.["@_red"];
-
-    const fontColor =
-      !args.isEnabled || blue === undefined || green === undefined || red === 
undefined
-        ? DEFAULT_FONT_COLOR
-        : `rgba(${red}, ${green}, ${blue}, 1)`;
-
-    return {
-      bold: args.isEnabled ? args.dmnStyle?.["@_fontBold"] ?? false : false,
-      italic: args.isEnabled ? args.dmnStyle?.["@_fontItalic"] ?? false : 
false,
-      underline: args.isEnabled ? args.dmnStyle?.["@_fontUnderline"] ?? false 
: false,
-      strikeThrough: args.isEnabled ? args.dmnStyle?.["@_fontStrikeThrough"] 
?? false : false,
-      family: args.isEnabled ? args.dmnStyle?.["@_fontFamily"] : undefined,
-      size: args.isEnabled ? args.dmnStyle?.["@_fontSize"] : undefined,
-      color: fontColor,
-    };
-  }, [args.dmnStyle, args.isEnabled]);
+  const fillColor = useMemo(
+    () => getNodeShapeFillColor({ dmnStyle: args.dmnStyle, nodeType: 
args.nodeType, isEnabled: args.isEnabled }),
+    [args.dmnStyle, args.isEnabled, args.nodeType]
+  );
+
+  const strokeColor = useMemo(
+    () => getNodeShapeStrokeColor({ dmnStyle: args.dmnStyle, isEnabled: 
args.isEnabled }),
+    [args.dmnStyle, args.isEnabled]
+  );
+
+  const dmnFontStyle = useMemo(
+    () => getDmnFontStyle({ dmnStyle: args.dmnStyle, isEnabled: args.isEnabled 
}),
+    [args.dmnStyle, args.isEnabled]
+  );
+
+  return useMemo(
+    () =>
+      getNodeStyle({
+        fillColor,
+        strokeColor,
+        dmnFontStyle,
+      }),
+    [fillColor, dmnFontStyle, strokeColor]
+  );
+}
 
+export function getNodeStyle({
+  fillColor,
+  strokeColor,
+  dmnFontStyle,
+}: {
+  fillColor: string;
+  strokeColor: string;
+  dmnFontStyle: DmnFontStyle;
+}): NodeStyle {
   return {
-    fontStyle: getFonteStyle(fontProperties),
+    fontCssProperties: getFontCssProperties(dmnFontStyle),
     shapeStyle: {
       fillColor,
       strokeColor,
@@ -122,21 +110,111 @@ export function useNodeStyle(args: {
   };
 }
 
-export function getFonteStyle(fontProperties?: DmnFontStyle): 
React.CSSProperties {
+export function getNodeShapeFillColor(args: {
+  dmnStyle?: DMNDI15__DMNStyle | undefined;
+  nodeType?: NodeType | undefined;
+  isEnabled?: boolean | undefined;
+}) {
+  const blue = args.dmnStyle?.["dmndi:FillColor"]?.["@_blue"];
+  const green = args.dmnStyle?.["dmndi:FillColor"]?.["@_green"];
+  const red = args.dmnStyle?.["dmndi:FillColor"]?.["@_red"];
+
+  const opacity =
+    args.nodeType === NODE_TYPES.decisionService ||
+    args.nodeType === NODE_TYPES.group ||
+    args.nodeType === NODE_TYPES.textAnnotation
+      ? 0.1
+      : DEFAULT_NODE_OPACITY;
+
+  if (!args.isEnabled || blue === undefined || green === undefined || red === 
undefined) {
+    return `rgba(${DEFAULT_NODE_RED_FILL}, ${DEFAULT_NODE_GREEN_FILL}, 
${DEFAULT_NODE_BLUE_FILL}, ${opacity})`;
+  }
+
+  return `rgba(${red}, ${green}, ${blue}, ${opacity})`;
+}
+
+export function getNodeShapeStrokeColor(args: {
+  dmnStyle?: DMNDI15__DMNStyle | undefined;
+  isEnabled?: boolean | undefined;
+}) {
+  const blue = args.dmnStyle?.["dmndi:StrokeColor"]?.["@_blue"];
+  const green = args.dmnStyle?.["dmndi:StrokeColor"]?.["@_green"];
+  const red = args.dmnStyle?.["dmndi:StrokeColor"]?.["@_red"];
+
+  if (!args.isEnabled || blue === undefined || green === undefined || red === 
undefined) {
+    return DEFAULT_NODE_STROKE_COLOR;
+  }
+  return `rgba(${red}, ${green}, ${blue}, 1)`;
+}
+
+export function getDmnFontStyle(args: {
+  dmnStyle?: DMNDI15__DMNStyle | undefined;
+  isEnabled?: boolean | undefined;
+}): DmnFontStyle {
+  const blue = args.dmnStyle?.["dmndi:FontColor"]?.["@_blue"];
+  const green = args.dmnStyle?.["dmndi:FontColor"]?.["@_green"];
+  const red = args.dmnStyle?.["dmndi:FontColor"]?.["@_red"];
+
+  const fontColor =
+    !args.isEnabled || blue === undefined || green === undefined || red === 
undefined
+      ? DEFAULT_FONT_COLOR
+      : `rgba(${red}, ${green}, ${blue}, 1)`;
+
+  return {
+    bold: args.isEnabled ? args.dmnStyle?.["@_fontBold"] ?? false : false,
+    italic: args.isEnabled ? args.dmnStyle?.["@_fontItalic"] ?? false : false,
+    underline: args.isEnabled ? args.dmnStyle?.["@_fontUnderline"] ?? false : 
false,
+    strikeThrough: args.isEnabled ? args.dmnStyle?.["@_fontStrikeThrough"] ?? 
false : false,
+    family: args.isEnabled ? args.dmnStyle?.["@_fontFamily"] : undefined,
+    size: args.isEnabled ? args.dmnStyle?.["@_fontSize"] : undefined,
+    color: fontColor,
+  };
+}
+
+export function getFontCssProperties(dmnFontStyle?: DmnFontStyle): 
React.CSSProperties {
   let textDecoration = "";
-  if (fontProperties?.underline) {
+  if (dmnFontStyle?.underline) {
     textDecoration += "underline ";
   }
-  if (fontProperties?.strikeThrough) {
+  if (dmnFontStyle?.strikeThrough) {
     textDecoration += "line-through";
   }
 
+  // Using default values here ensures that the editable Diagram rendered by 
ReactFlow and the SVG generated are the closest possible.
   return {
-    fontWeight: fontProperties?.bold ? "bold" : "",
-    fontStyle: fontProperties?.italic ? "italic" : "",
-    fontFamily: fontProperties?.family,
+    fontWeight: dmnFontStyle?.bold ? "bold" : "",
+    fontStyle: dmnFontStyle?.italic ? "italic" : "",
+    fontFamily: dmnFontStyle?.family ?? "arial",
     textDecoration,
-    fontSize: fontProperties?.size,
-    color: fontProperties?.color,
+    fontSize: dmnFontStyle?.size ?? "16px",
+    color: dmnFontStyle?.color ?? "black",
+    lineHeight: "1.5em", // This needs to be em `em` otherwise `@visx/text` 
breaks when generating the SVG.
   };
 }
+
+export function getNodeLabelPosition(nodeType: NodeType): NodeLabelPosition {
+  switch (nodeType) {
+    case NODE_TYPES.inputData:
+      return "center-center";
+    case NODE_TYPES.decision:
+      return "center-center";
+    case NODE_TYPES.bkm:
+      return "center-center";
+    case NODE_TYPES.decisionService:
+      return "top-center";
+    case NODE_TYPES.knowledgeSource:
+      return "center-left";
+    case NODE_TYPES.textAnnotation:
+      return "top-left";
+    case NODE_TYPES.group:
+      return "top-left";
+    case NODE_TYPES.unknown:
+      return "center-center";
+    default:
+      assertUnreachable(nodeType);
+  }
+}
+
+export function assertUnreachable(_x: never): never {
+  throw new Error("Didn't expect to get here: " + _x);
+}
diff --git a/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx 
b/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
index e4fdbfa9934..03db3ededc7 100644
--- a/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
@@ -22,6 +22,8 @@ import * as RF from "reactflow";
 import { DEFAULT_INTRACTION_WIDTH } from "../maths/DmnMaths";
 import { DEFAULT_NODE_FILL, DEFAULT_NODE_STROKE_COLOR, 
DEFAULT_NODE_STROKE_WIDTH } from "./NodeStyle";
 
+export type NodeLabelPosition = "center-center" | "top-center" | "center-left" 
| "top-left";
+
 export type NodeSvgProps = RF.Dimensions &
   RF.XYPosition & {
     fillColor?: string;
@@ -81,7 +83,7 @@ export function InputDataNodeSvg(__props: NodeSvgProps) {
         })();
 
   return (
-    <g>
+    <>
       <rect
         {...props}
         x={x}
@@ -95,7 +97,7 @@ export function InputDataNodeSvg(__props: NodeSvgProps) {
         rx={rx}
         ry={ry}
       />
-    </g>
+    </>
   );
 }
 
@@ -103,7 +105,7 @@ export function DecisionNodeSvg(__props: NodeSvgProps) {
   const { strokeWidth, x, y, width, height, fillColor, strokeColor, props } = 
normalize(__props);
 
   return (
-    <g>
+    <>
       <rect
         x={x}
         y={y}
@@ -115,7 +117,7 @@ export function DecisionNodeSvg(__props: NodeSvgProps) {
         strokeLinejoin={"round"}
         {...props}
       />
-    </g>
+    </>
   );
 }
 
@@ -123,7 +125,7 @@ export function BkmNodeSvg(__props: NodeSvgProps) {
   const { strokeWidth, x, y, width, height, fillColor, strokeColor, props } = 
normalize(__props);
   const bevel = 25;
   return (
-    <g>
+    <>
       <polygon
         {...props}
         points={`${bevel},0 0,${bevel} 0,${height} ${width - bevel},${height} 
${width},${height - bevel}, ${width},0`}
@@ -133,7 +135,7 @@ export function BkmNodeSvg(__props: NodeSvgProps) {
         strokeLinejoin={"round"}
         transform={`translate(${x},${y})`}
       />
-    </g>
+    </>
   );
 }
 
@@ -145,7 +147,7 @@ export function KnowledgeSourceNodeSvg(__props: 
NodeSvgProps) {
   const straightLines = `M${width},${height} L${width},0 L0,0 L0,${height}`;
   const bottomWave = `Q${width / 4},${height + amplitude} ${width / 
2},${height} T${width},${height}`;
   return (
-    <g>
+    <>
       <path
         {...props}
         d={`${straightLines} ${bottomWave} Z`}
@@ -155,7 +157,7 @@ export function KnowledgeSourceNodeSvg(__props: 
NodeSvgProps) {
         strokeLinejoin={"round"}
         transform={`translate(${x},${y})`}
       />
-    </g>
+    </>
   );
 }
 
@@ -197,7 +199,7 @@ export const DecisionServiceNodeSvg = React.forwardRef<
   } = _interactionRectProps;
 
   return (
-    <g>
+    <>
       {!isCollapsed && (
         <>
           <path
@@ -267,7 +269,7 @@ export const DecisionServiceNodeSvg = React.forwardRef<
           </text>
         </>
       )}
-    </g>
+    </>
   );
 });
 
@@ -275,7 +277,7 @@ export function TextAnnotationNodeSvg(__props: NodeSvgProps 
& { showPlaceholder?
   const { strokeWidth, x, y, width, height, fillColor, strokeColor, props: 
_props } = normalize(__props);
   const { showPlaceholder, ...props } = _props;
   return (
-    <g>
+    <>
       <rect
         x={x}
         y={y}
@@ -302,7 +304,7 @@ export function TextAnnotationNodeSvg(__props: NodeSvgProps 
& { showPlaceholder?
           Text
         </text>
       )}
-    </g>
+    </>
   );
 }
 
@@ -322,7 +324,7 @@ export const GroupNodeSvg = 
React.forwardRef<SVGRectElement, NodeSvgProps & { st
 
     const strokeDasharray = props.strokeDasharray ?? "14,10,3,10";
     return (
-      <g>
+      <>
         <rect
           {...props}
           x={x}
@@ -351,7 +353,7 @@ export const GroupNodeSvg = 
React.forwardRef<SVGRectElement, NodeSvgProps & { st
           ry={"30"}
           className={containerNodeInteractionRectCssClassName}
         />
-      </g>
+      </>
     );
   }
 );
@@ -360,7 +362,7 @@ export const UnknownNodeSvg = (_props: NodeSvgProps & { 
strokeDasharray?: string
   const { strokeWidth, x, y, width, height, props } = normalize(_props);
   const strokeDasharray = props.strokeDasharray ?? "2,4";
   return (
-    <g>
+    <>
       <rect
         {...props}
         x={x}
@@ -373,6 +375,6 @@ export const UnknownNodeSvg = (_props: NodeSvgProps & { 
strokeDasharray?: string
         strokeWidth={strokeWidth}
         strokeDasharray={strokeDasharray}
       />
-    </g>
+    </>
   );
 };
diff --git a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx 
b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
index 5727dcd5a3e..068a395eaed 100644
--- a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
@@ -67,9 +67,18 @@ import { drag } from "d3-drag";
 import { updateDecisionServiceDividerLine } from 
"../../mutations/updateDecisionServiceDividerLine";
 import { addTopLevelItemDefinition } from 
"../../mutations/addTopLevelItemDefinition";
 import { DmnBuiltInDataType } from 
"@kie-tools/boxed-expression-component/dist/api";
-import { useNodeStyle } from "./NodeStyle";
+import { getNodeLabelPosition, useNodeStyle } from "./NodeStyle";
 
-export type NodeDmnObjects = Unpacked<DMN15__tDefinitions["drgElement"] | 
DMN15__tDefinitions["artifact"]> | null;
+export type ElementFilter<E extends { __$$element: string }, Filter extends 
string> = E extends any
+  ? E["__$$element"] extends Filter
+    ? E
+    : never
+  : never;
+
+export type NodeDmnObjects =
+  | null
+  | Unpacked<DMN15__tDefinitions["drgElement"]>
+  | ElementFilter<Unpacked<DMN15__tDefinitions["artifact"]>, "textAnnotation" 
| "group">;
 
 export type DmnDiagramNodeData<T extends NodeDmnObjects = NodeDmnObjects> = {
   dmnObjectNamespace: string | undefined;
@@ -136,7 +145,7 @@ export const InputDataNode = React.memo(
 
     const onCreateDataType = useDataTypeCreationCallbackForNodes(index, 
inputData["@_name"]);
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -176,11 +185,12 @@ export const InputDataNode = React.memo(
             namedElementQName={dmnObjectQName}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
+            position={getNodeLabelPosition(type as NodeType)}
             value={inputData["@_label"] ?? inputData["@_name"]}
             onChange={setName}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {isHovered && (
             <NodeResizerHandle
@@ -254,7 +264,7 @@ export const DecisionNode = React.memo(
 
     const onCreateDataType = useDataTypeCreationCallbackForNodes(index, 
decision["@_name"]);
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -297,11 +307,12 @@ export const DecisionNode = React.memo(
             namedElementQName={dmnObjectQName}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
+            position={getNodeLabelPosition(type as NodeType)}
             value={decision["@_label"] ?? decision["@_name"]}
             onChange={setName}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {isHovered && (
             <NodeResizerHandle
@@ -375,7 +386,7 @@ export const BkmNode = React.memo(
 
     const onCreateDataType = useDataTypeCreationCallbackForNodes(index, 
bkm["@_name"]);
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -418,11 +429,12 @@ export const BkmNode = React.memo(
             namedElementQName={dmnObjectQName}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
+            position={getNodeLabelPosition(type as NodeType)}
             value={bkm["@_label"] ?? bkm["@_name"]}
             onChange={setName}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {isHovered && (
             <NodeResizerHandle
@@ -483,7 +495,7 @@ export const KnowledgeSourceNode = React.memo(
 
     const { allFeelVariableUniqueNames } = useDmnEditorDerivedStore();
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -522,7 +534,7 @@ export const KnowledgeSourceNode = React.memo(
           <EditableNodeLabel
             namedElement={knowledgeSource}
             namedElementQName={dmnObjectQName}
-            position={"center-left"}
+            position={getNodeLabelPosition(type as NodeType)}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
             value={knowledgeSource["@_label"] ?? knowledgeSource["@_name"]}
@@ -530,7 +542,7 @@ export const KnowledgeSourceNode = React.memo(
             skipValidation={true}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {isHovered && (
             <NodeResizerHandle
@@ -583,7 +595,7 @@ export const TextAnnotationNode = React.memo(
 
     const { allFeelVariableUniqueNames } = useDmnEditorDerivedStore();
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -623,7 +635,7 @@ export const TextAnnotationNode = React.memo(
             id={textAnnotation["@_id"]}
             namedElement={undefined}
             namedElementQName={undefined}
-            position={"top-left"}
+            position={getNodeLabelPosition(type as NodeType)}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
             value={textAnnotation["@_label"] ?? textAnnotation.text?.__$$text}
@@ -631,7 +643,7 @@ export const TextAnnotationNode = React.memo(
             skipValidation={true}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {isHovered && (
             <NodeResizerHandle
@@ -768,7 +780,7 @@ export const DecisionServiceNode = React.memo(
       shape.index,
     ]);
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -813,14 +825,14 @@ export const DecisionServiceNode = React.memo(
           <EditableNodeLabel
             namedElement={decisionService}
             namedElementQName={dmnObjectQName}
-            position={"top-center"}
+            position={getNodeLabelPosition(type as NodeType)}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
             value={decisionService["@_label"] ?? decisionService["@_name"]}
             onChange={setName}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {selected && !dragging && !isCollapsed && (
             <NodeResizerHandle
@@ -906,7 +918,7 @@ export const GroupNode = React.memo(
 
     const { allFeelVariableUniqueNames } = useDmnEditorDerivedStore();
 
-    const { fontStyle, shapeStyle } = useNodeStyle({
+    const { fontCssProperties, shapeStyle } = useNodeStyle({
       dmnStyle: shape["di:Style"],
       nodeType: type as NodeType,
       isEnabled: diagram.overlays.enableStyles,
@@ -943,7 +955,7 @@ export const GroupNode = React.memo(
             id={group["@_id"]}
             namedElement={undefined}
             namedElementQName={undefined}
-            position={"top-left"}
+            position={getNodeLabelPosition(type as NodeType)}
             isEditing={isEditingLabel}
             setEditing={setEditingLabel}
             value={group["@_label"] ?? group["@_name"]}
@@ -951,7 +963,7 @@ export const GroupNode = React.memo(
             skipValidation={true}
             allUniqueNames={allFeelVariableUniqueNames}
             shouldCommitOnBlur={true}
-            fontStyle={fontStyle}
+            fontCssProperties={fontCssProperties}
           />
           {selected && !dragging && (
             <NodeResizerHandle
@@ -1001,7 +1013,7 @@ export const UnknownNode = React.memo(
           <EditableNodeLabel
             namedElement={undefined}
             namedElementQName={undefined}
-            position={"center-center"}
+            position={getNodeLabelPosition(type as NodeType)}
             isEditing={false}
             setEditing={() => {}}
             value={`? `}
diff --git a/packages/dmn-editor/src/store/useDiagramData.tsx 
b/packages/dmn-editor/src/store/useDiagramData.tsx
index 9789f1c4f59..3ca1beaf800 100644
--- a/packages/dmn-editor/src/store/useDiagramData.tsx
+++ b/packages/dmn-editor/src/store/useDiagramData.tsx
@@ -326,6 +326,10 @@ export function useDiagramData(externalDmnsByNamespace: 
ExternalDmnsIndex) {
         return newNode ? [newNode] : [];
       }),
       ...(thisDmn.model.definitions.artifact ?? []).flatMap((dmnObject, index) 
=> {
+        if (dmnObject.__$$element === "association") {
+          return [];
+        }
+
         const newNode = ackNode({ type: "xml-qname", localPart: 
dmnObject["@_id"]! }, dmnObject, index);
         return newNode ? [newNode] : [];
       }),
diff --git a/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx 
b/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx
new file mode 100644
index 00000000000..7dc91246484
--- /dev/null
+++ b/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as React from "react";
+import * as RF from "reactflow";
+import {
+  AssociationPath,
+  AuthorityRequirementPath,
+  DmnDiagramEdgeData,
+  InformationRequirementPath,
+  KnowledgeRequirementPath,
+} from "../diagram/edges/Edges";
+import { DmnDiagramNodeData } from "../diagram/nodes/Nodes";
+import { SnapGrid, State } from "../store/Store";
+import { EdgeMarkers } from "../diagram/edges/EdgeMarkers";
+import { EDGE_TYPES } from "../diagram/edges/EdgeTypes";
+import { getSnappedMultiPointAnchoredEdgePath } from 
"../diagram/edges/getSnappedMultiPointAnchoredEdgePath";
+import {
+  InputDataNodeSvg,
+  DecisionNodeSvg,
+  BkmNodeSvg,
+  KnowledgeSourceNodeSvg,
+  DecisionServiceNodeSvg,
+  GroupNodeSvg,
+  TextAnnotationNodeSvg,
+  UnknownNodeSvg,
+  NodeLabelPosition,
+} from "../diagram/nodes/NodeSvgs";
+import { NODE_TYPES } from "../diagram/nodes/NodeTypes";
+import { useMemo } from "react";
+import {
+  assertUnreachable,
+  getDmnFontStyle,
+  getNodeLabelPosition,
+  getNodeShapeFillColor,
+  getNodeShapeStrokeColor,
+  getNodeStyle,
+} from "../diagram/nodes/NodeStyle";
+import { NodeType } from "../diagram/connections/graphStructure";
+import { buildFeelQNameFromXmlQName } from "../feel/buildFeelQName";
+import { DerivedStore } from "../store/DerivedStore";
+import { Text } from "@visx/text";
+
+export function DmnDiagramSvg({
+  nodes,
+  edges,
+  snapGrid,
+  thisDmn,
+  importsByNamespace,
+}: {
+  nodes: RF.Node<DmnDiagramNodeData>[];
+  edges: RF.Edge<DmnDiagramEdgeData>[];
+  snapGrid: SnapGrid;
+  thisDmn: State["dmn"];
+  importsByNamespace: DerivedStore["importsByNamespace"];
+}) {
+  const { nodesSvg, nodesById } = useMemo(() => {
+    const nodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
+
+    const nodesSvg = nodes.map((node) => {
+      const { fontCssProperties: fontStyle, shapeStyle } = getNodeStyle({
+        fillColor: getNodeShapeFillColor({
+          dmnStyle: node.data.shape["di:Style"],
+          nodeType: node.type as NodeType,
+          isEnabled: true,
+        }),
+        strokeColor: getNodeShapeStrokeColor({ dmnStyle: 
node.data.shape["di:Style"], isEnabled: true }),
+        dmnFontStyle: getDmnFontStyle({ dmnStyle: node.data.shape["di:Style"], 
isEnabled: true }),
+      });
+
+      nodesById.set(node.id, node);
+
+      const { height, width, ...style } = node.style!;
+
+      const label =
+        node.data?.dmnObject?.__$$element === "group"
+          ? node.data.dmnObject?.["@_label"] ?? 
node.data?.dmnObject?.["@_name"] ?? "<Empty>"
+          : node.data?.dmnObject?.__$$element === "textAnnotation"
+          ? node.data.dmnObject?.["@_label"] ?? 
node.data?.dmnObject?.text?.__$$text ?? "<Empty>"
+          : buildFeelQNameFromXmlQName({
+              namedElement: node.data!.dmnObject!,
+              importsByNamespace,
+              model: thisDmn.model.definitions,
+              namedElementQName: node.data!.dmnObjectQName,
+              relativeToNamespace: thisDmn.model.definitions["@_namespace"],
+            }).full;
+
+      return (
+        <>
+          <g data-kie-dmn-node-id={node.id}>
+            {node.type === NODE_TYPES.inputData && (
+              <InputDataNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...shapeStyle}
+              />
+            )}
+            {node.type === NODE_TYPES.decision && (
+              <DecisionNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...shapeStyle}
+              />
+            )}
+            {node.type === NODE_TYPES.bkm && (
+              <BkmNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...shapeStyle}
+              />
+            )}
+            {node.type === NODE_TYPES.knowledgeSource && (
+              <KnowledgeSourceNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...shapeStyle}
+              />
+            )}
+            {node.type === NODE_TYPES.decisionService && (
+              <DecisionServiceNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                showSectionLabels={false}
+                isReadonly={true}
+                {...style}
+                {...shapeStyle}
+              />
+            )}
+            {node.type === NODE_TYPES.group && (
+              <GroupNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...(shapeStyle as any)}
+              />
+            )}
+            {node.type === NODE_TYPES.textAnnotation && (
+              <TextAnnotationNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...shapeStyle}
+              />
+            )}
+            {node.type === NODE_TYPES.unknown && (
+              <UnknownNodeSvg
+                width={node.width!}
+                height={node.height!}
+                x={node.positionAbsolute!.x}
+                y={node.positionAbsolute!.y}
+                {...style}
+                {...(shapeStyle as any)}
+              />
+            )}
+            <>
+              {label.split("\n").map((labelLine, i) => (
+                <Text
+                  key={i}
+                  lineHeight={fontStyle.lineHeight}
+                  style={{ ...fontStyle, fill: fontStyle.color }}
+                  dy={`calc(1.5em * ${i})`}
+                  {...getNodeLabelSvgTextAlignmentProps(node, 
getNodeLabelPosition(node.type as NodeType))}
+                >
+                  {labelLine}
+                </Text>
+              ))}
+            </>
+          </g>
+        </>
+      );
+    });
+
+    return { nodesSvg, nodesById };
+  }, [importsByNamespace, nodes, thisDmn.model.definitions]);
+
+  return (
+    <>
+      <EdgeMarkers />
+      {edges.map((e) => {
+        const { path } = getSnappedMultiPointAnchoredEdgePath({
+          snapGrid,
+          dmnEdge: e.data?.dmnEdge,
+          dmnShapeSource: e.data?.dmnShapeSource,
+          dmnShapeTarget: e.data?.dmnShapeTarget,
+          sourceNode: nodesById?.get(e.source),
+          targetNode: nodesById?.get(e.target),
+        });
+        return (
+          <>
+            {e.type === EDGE_TYPES.informationRequirement && 
<InformationRequirementPath d={path} />}
+            {e.type === EDGE_TYPES.knowledgeRequirement && 
<KnowledgeRequirementPath d={path} />}
+            {e.type === EDGE_TYPES.authorityRequirement && (
+              <AuthorityRequirementPath d={path} 
centerToConnectionPoint={true} />
+            )}
+            {e.type === EDGE_TYPES.association && <AssociationPath d={path} />}
+          </>
+        );
+      })}
+      {nodesSvg}
+    </>
+  );
+}
+
+const SVG_NODE_LABEL_TEXT_PADDING_ALL = 10;
+const SVG_NODE_LABEL_TEXT_ADDITIONAL_PADDING_TOP_LEFT = 8;
+
+export function getNodeLabelSvgTextAlignmentProps(n: 
RF.Node<DmnDiagramNodeData>, labelPosition: NodeLabelPosition) {
+  switch (labelPosition) {
+    case "center-center":
+      const ccTx = n.position.x! + n.width! / 2;
+      const ccTy = n.position.y! + n.height! / 2;
+      const ccWidth = n.width! - 2 * SVG_NODE_LABEL_TEXT_PADDING_ALL;
+      return {
+        verticalAnchor: "middle",
+        textAnchor: "middle",
+        transform: `translate(${ccTx},${ccTy})`,
+        width: ccWidth,
+      } as const;
+
+    case "top-center":
+      const tcTx = n.position.x! + n.width! / 2;
+      const tcTy = n.position.y! + SVG_NODE_LABEL_TEXT_PADDING_ALL;
+      const tcWidth = n.width! - 2 * SVG_NODE_LABEL_TEXT_PADDING_ALL;
+      return {
+        verticalAnchor: "start",
+        textAnchor: "middle",
+        transform: `translate(${tcTx},${tcTy})`,
+        width: tcWidth,
+      } as const;
+
+    case "center-left":
+      const clTx = n.position.x! + SVG_NODE_LABEL_TEXT_PADDING_ALL;
+      const clTy = n.position.y! + n.height! / 2;
+      const clWidth = n.width! - 2 * SVG_NODE_LABEL_TEXT_PADDING_ALL;
+      return {
+        verticalAnchor: "middle",
+        textAnchor: "start",
+        transform: `translate(${clTx},${clTy})`,
+        width: clWidth,
+      } as const;
+
+    case "top-left":
+      const tlTx = n.position.x! + SVG_NODE_LABEL_TEXT_PADDING_ALL + 
SVG_NODE_LABEL_TEXT_ADDITIONAL_PADDING_TOP_LEFT;
+      const tlTy = n.position.y! + SVG_NODE_LABEL_TEXT_PADDING_ALL + 
SVG_NODE_LABEL_TEXT_ADDITIONAL_PADDING_TOP_LEFT;
+      const tlWidth =
+        n.width! - 2 * SVG_NODE_LABEL_TEXT_PADDING_ALL - 2 * 
SVG_NODE_LABEL_TEXT_ADDITIONAL_PADDING_TOP_LEFT;
+      return {
+        verticalAnchor: "start",
+        textAnchor: "start",
+        transform: `translate(${tlTx},${tlTy})`,
+        width: tlWidth,
+      } as const;
+    default:
+      assertUnreachable(labelPosition);
+  }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 536e627202e..6f2694a9d90 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3300,6 +3300,9 @@ importers:
       "@patternfly/react-styles":
         specifier: ^4.92.6
         version: 4.92.6
+      "@visx/text":
+        specifier: ^3.3.0
+        version: 3.3.0([email protected])
       d3-drag:
         specifier: ^3.0.0
         version: 3.0.0
@@ -23185,6 +23188,11 @@ packages:
     resolution:
       { integrity: 
sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw==
 }
 
+  /@types/[email protected]:
+    resolution:
+      { integrity: 
sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==
 }
+    dev: false
+
   /@types/[email protected]:
     resolution:
       { integrity: 
sha512-r7/zWe+f9x+zjXqGxf821qz++ld8tp6Z4jUS6qmPZUXH6tfh4riXOhAqb12tWGWAevCFtMt1goLWkQMqIJKpsA==
 }
@@ -23769,6 +23777,21 @@ packages:
       { integrity: 
sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
 }
     dev: true
 
+  /@visx/[email protected]([email protected]):
+    resolution:
+      { integrity: 
sha512-fOimcsf0GtQE9whM5MdA/xIkHMaV29z7qNqNXysUDE8znSMKsN+ott7kSg2ljAEE89CQo3WKHkPNettoVsa84w==
 }
+    peerDependencies:
+      react: ^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0
+    dependencies:
+      "@types/lodash": 4.14.202
+      "@types/react": 17.0.21
+      classnames: 2.3.2
+      lodash: 4.17.21
+      prop-types: 15.8.1
+      react: 17.0.2
+      reduce-css-calc: 1.3.0
+    dev: false
+
   /@vscode/[email protected]:
     resolution:
       { integrity: 
sha512-M31xGH0RgqNU6CZ4/9g39oUMJ99nLzfjA+4UbtIQ6TcXQ6+2qkjOOxedmPBDDCg26/3Al5ubjY80hIoaMwKYSw==
 }
@@ -25889,6 +25912,11 @@ packages:
       babel-preset-current-node-syntax: 1.0.1(@babel/[email protected])
     dev: true
 
+  /[email protected]:
+    resolution:
+      { integrity: 
sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==
 }
+    dev: false
+
   /[email protected]:
     resolution:
       { integrity: 
sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 }
@@ -35566,6 +35594,11 @@ packages:
       react: 17.0.2
     dev: true
 
+  /[email protected]:
+    resolution:
+      { integrity: 
sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==
 }
+    dev: false
+
   /[email protected]:
     resolution:
       { integrity: 
sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
 }
@@ -39840,6 +39873,22 @@ packages:
       strip-indent: 3.0.0
     dev: true
 
+  /[email protected]:
+    resolution:
+      { integrity: 
sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==
 }
+    dependencies:
+      balanced-match: 0.4.2
+      math-expression-evaluator: 1.4.0
+      reduce-function-call: 1.0.3
+    dev: false
+
+  /[email protected]:
+    resolution:
+      { integrity: 
sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==
 }
+    dependencies:
+      balanced-match: 1.0.2
+    dev: false
+
   /[email protected]:
     resolution:
       { integrity: 
sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
 }


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

Reply via email to