@likec4/diagram 1.44.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/bundle/index.js +18160 -17671
  2. package/dist/LikeC4Diagram.js +7 -5
  3. package/dist/LikeC4Diagram.props.d.ts +9 -8
  4. package/dist/LikeC4Styles.js +0 -3
  5. package/dist/LikeC4View.d.ts +3 -3
  6. package/dist/LikeC4View.js +7 -3
  7. package/dist/ReactLikeC4.d.ts +2 -2
  8. package/dist/base/Base.d.ts +1 -1
  9. package/dist/base/updateEdges.js +25 -15
  10. package/dist/base/updateNodes.d.ts +14 -0
  11. package/dist/base/updateNodes.js +51 -18
  12. package/dist/base-primitives/Markdown.d.ts +30 -4
  13. package/dist/base-primitives/Markdown.js +3 -2
  14. package/dist/base-primitives/compound/CompoundActionButton.d.ts +3 -2
  15. package/dist/base-primitives/compound/CompoundDetailsButton.d.ts +3 -2
  16. package/dist/base-primitives/edge/EdgeActionButton.d.ts +3 -2
  17. package/dist/base-primitives/edge/EdgeContainer.d.ts +1 -1
  18. package/dist/base-primitives/edge/EdgeContainer.js +4 -1
  19. package/dist/base-primitives/edge/EdgeLabel.d.ts +2 -2
  20. package/dist/base-primitives/edge/EdgePath.js +24 -4
  21. package/dist/base-primitives/element/ElementActionButtons.d.ts +3 -2
  22. package/dist/base-primitives/element/ElementActionButtons.js +2 -2
  23. package/dist/base-primitives/element/ElementData.d.ts +3 -3
  24. package/dist/base-primitives/element/ElementData.js +13 -7
  25. package/dist/base-primitives/element/ElementDetailsButton.d.ts +3 -2
  26. package/dist/base-primitives/element/ElementDetailsButton.js +3 -0
  27. package/dist/base-primitives/element/ElementShape.js +5 -5
  28. package/dist/base-primitives/element/ElementTags.d.ts +6 -4
  29. package/dist/context/DiagramFeatures.d.ts +1 -0
  30. package/dist/context/TagStylesContext.js +1 -1
  31. package/dist/custom/hooks.d.ts +2 -2
  32. package/dist/custom/index.js +1 -1
  33. package/dist/hooks/index.d.ts +2 -1
  34. package/dist/hooks/useDiagramCompareLayout.js +7 -2
  35. package/dist/hooks/useLikeC4Project.d.ts +6 -1
  36. package/dist/hooks/useLikeC4Project.js +14 -2
  37. package/dist/hooks/useMediaPrint.d.ts +6 -0
  38. package/dist/likec4diagram/DiagramXYFlow.js +11 -6
  39. package/dist/likec4diagram/custom/edges/EdgeDrifts.d.ts +6 -0
  40. package/dist/likec4diagram/custom/edges/EdgeDrifts.js +38 -0
  41. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +24 -14
  42. package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +3 -1
  43. package/dist/likec4diagram/custom/edges/useControlPoints.js +3 -3
  44. package/dist/likec4diagram/custom/edges/useRelationshipEdgePath.js +5 -5
  45. package/dist/likec4diagram/custom/nodes/NodeDrifts.d.ts +6 -0
  46. package/dist/likec4diagram/custom/nodes/{ElementNodeDrifts.js → NodeDrifts.js} +8 -4
  47. package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +4 -2
  48. package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
  49. package/dist/likec4diagram/custom/nodes/nodes.js +64 -23
  50. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +3 -3
  51. package/dist/likec4diagram/relationship-popover/actor.d.ts +1 -1
  52. package/dist/likec4diagram/state/DiagramActorProvider.d.ts +2 -2
  53. package/dist/likec4diagram/state/DiagramActorProvider.js +0 -1
  54. package/dist/likec4diagram/state/assign.d.ts +10 -6
  55. package/dist/likec4diagram/state/assign.js +17 -192
  56. package/dist/likec4diagram/state/createViewChange.js +15 -26
  57. package/dist/likec4diagram/state/diagram-api.d.ts +2 -6
  58. package/dist/likec4diagram/state/diagram-api.js +2 -10
  59. package/dist/likec4diagram/state/hotkeyActor.d.ts +0 -7
  60. package/dist/likec4diagram/state/hotkeyActor.js +1 -17
  61. package/dist/likec4diagram/state/machine.actions.d.ts +430 -746
  62. package/dist/likec4diagram/state/machine.actions.js +193 -284
  63. package/dist/likec4diagram/state/machine.actions.layout.d.ts +579 -0
  64. package/dist/likec4diagram/state/machine.actions.layout.js +129 -0
  65. package/dist/likec4diagram/state/machine.d.ts +17 -3712
  66. package/dist/likec4diagram/state/machine.js +59 -73
  67. package/dist/likec4diagram/state/machine.setup.d.ts +61 -18
  68. package/dist/likec4diagram/state/machine.setup.js +27 -5
  69. package/dist/likec4diagram/state/machine.state.initializing.d.ts +25 -0
  70. package/dist/likec4diagram/state/machine.state.initializing.js +5 -5
  71. package/dist/likec4diagram/state/machine.state.navigating.d.ts +5 -108
  72. package/dist/likec4diagram/state/machine.state.navigating.js +167 -34
  73. package/dist/likec4diagram/state/machine.state.ready.d.ts +2316 -2851
  74. package/dist/likec4diagram/state/machine.state.ready.focused.d.ts +386 -0
  75. package/dist/likec4diagram/state/machine.state.ready.focused.js +85 -0
  76. package/dist/likec4diagram/state/machine.state.ready.idle.d.ts +474 -0
  77. package/dist/likec4diagram/state/machine.state.ready.idle.js +96 -0
  78. package/dist/likec4diagram/state/machine.state.ready.js +144 -319
  79. package/dist/likec4diagram/state/machine.state.ready.printing.d.ts +128 -0
  80. package/dist/likec4diagram/state/machine.state.ready.printing.js +40 -0
  81. package/dist/likec4diagram/state/machine.state.ready.walkthrough.d.ts +511 -0
  82. package/dist/likec4diagram/state/machine.state.ready.walkthrough.js +177 -0
  83. package/dist/likec4diagram/state/mediaPrintActor.d.ts +12 -0
  84. package/dist/likec4diagram/state/mediaPrintActor.js +18 -0
  85. package/dist/likec4diagram/state/syncManualLayoutActor.d.ts +6 -240
  86. package/dist/likec4diagram/state/syncManualLayoutActor.js +150 -89
  87. package/dist/likec4diagram/state/types.d.ts +5 -7
  88. package/dist/likec4diagram/state/utils.d.ts +15 -2
  89. package/dist/likec4diagram/state/utils.js +43 -3
  90. package/dist/likec4diagram/types.d.ts +8 -13
  91. package/dist/likec4diagram/ui/notation/NotationPanel.css.js +5 -2
  92. package/dist/likec4diagram/useLayoutConstraints.js +107 -102
  93. package/dist/likec4diagram/xyflow-diagram/diagram-view.js +12 -15
  94. package/dist/likec4diagram/xyflow-sequence/sequence-layout.js +11 -8
  95. package/dist/navigationpanel/NavigationPanel.js +5 -1
  96. package/dist/navigationpanel/actor.d.ts +4 -204
  97. package/dist/navigationpanel/actor.js +1 -1
  98. package/dist/navigationpanel/controls/ToggleReadonly.js +8 -3
  99. package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +66 -66
  100. package/dist/navigationpanel/walkthrough/WalkthroughPanel.js +4 -3
  101. package/dist/overlays/element-details/ElementDetailsCard.css.js +3 -3
  102. package/dist/overlays/element-details/MetadataValue.d.ts +2 -1
  103. package/dist/overlays/element-details/actor.d.ts +8 -83
  104. package/dist/overlays/element-details/actor.js +0 -8
  105. package/dist/overlays/overlay/Overlay.js +10 -10
  106. package/dist/overlays/overlaysActor.d.ts +7 -392
  107. package/dist/overlays/overlaysActor.js +67 -46
  108. package/dist/overlays/relationship-details/_types.d.ts +3 -4
  109. package/dist/overlays/relationship-details/actor.d.ts +6 -562
  110. package/dist/overlays/relationship-details/actor.js +1 -0
  111. package/dist/overlays/relationship-details/layout-to-xyflow.js +3 -5
  112. package/dist/overlays/relationship-details/layout.d.ts +3 -3
  113. package/dist/overlays/relationship-details/layout.js +4 -4
  114. package/dist/overlays/relationships-browser/_types.d.ts +2 -3
  115. package/dist/overlays/relationships-browser/actor.d.ts +7 -739
  116. package/dist/overlays/relationships-browser/layout.d.ts +2 -2
  117. package/dist/overlays/relationships-browser/layout.js +3 -3
  118. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +0 -3
  119. package/dist/overlays/types.d.ts +12 -13
  120. package/dist/search/components/_shared.css.d.ts +1 -1
  121. package/dist/search/searchActor.d.ts +3 -66
  122. package/dist/shadowroot/ShadowRoot.d.ts +5 -7
  123. package/dist/shadowroot/ShadowRoot.js +7 -5
  124. package/dist/styles.css2.js +1 -1
  125. package/dist/utils/vector.d.ts +5 -0
  126. package/dist/utils/vector.js +7 -0
  127. package/dist/utils/view-bounds.js +7 -6
  128. package/dist/utils/xyflow.d.ts +4 -2
  129. package/dist/utils/xyflow.js +18 -50
  130. package/package.json +19 -18
  131. package/panda.buildinfo.json +17 -5
  132. package/styles-min.css +1 -1
  133. package/styles.css +1 -1
  134. package/dist/likec4diagram/custom/nodes/ElementNodeDrifts.d.ts +0 -4
@@ -1,6 +1,7 @@
1
1
  import { BBox } from '@likec4/core/types';
2
- import { nonNullable } from '@likec4/core/utils';
3
- import { getNodesBounds, getNodeDimensions, getEdgePosition } from '@xyflow/system';
2
+ import { nonNullable, nonexhaustive } from '@likec4/core/utils';
3
+ import { getViewportForBounds, getNodesBounds, getNodeDimensions, getEdgePosition } from '@xyflow/system';
4
+ import { MinZoom } from '../../base/const.js';
4
5
 
5
6
  function typedSystem(system) {
6
7
  return {
@@ -124,5 +125,44 @@ function getEdgeBounds(edge, store) {
124
125
  [edgePosition.targetX, edgePosition.targetY]
125
126
  ]);
126
127
  }
128
+ function nodeRef(node) {
129
+ switch (node.type) {
130
+ case "element":
131
+ case "compound-element":
132
+ case "seq-actor":
133
+ return node.data.modelFqn;
134
+ case "deployment":
135
+ case "compound-deployment":
136
+ return node.data.modelFqn ?? node.data.deploymentFqn;
137
+ case "seq-parallel":
138
+ case "view-group":
139
+ return null;
140
+ default:
141
+ nonexhaustive(node);
142
+ }
143
+ }
144
+ function findCorrespondingNode(context, event) {
145
+ const fromNodeId = context.lastOnNavigate?.fromNode;
146
+ const fromNode = fromNodeId && context.xynodes.find((n) => n.id === fromNodeId);
147
+ const fromRef = fromNode && nodeRef(fromNode);
148
+ const toNode = fromRef && event.xynodes.find((n) => nodeRef(n) === fromRef);
149
+ return { fromNode, toNode };
150
+ }
151
+ function calcViewportForBounds(context, bounds) {
152
+ let currentZoom = context.xyflow.getZoom();
153
+ let {
154
+ width,
155
+ height
156
+ } = context.xystore.getState();
157
+ const maxZoom = Math.max(currentZoom, 1);
158
+ return getViewportForBounds(
159
+ bounds,
160
+ width,
161
+ height,
162
+ MinZoom,
163
+ maxZoom,
164
+ context.fitViewPadding
165
+ );
166
+ }
127
167
 
128
- export { activeSequenceBounds, findDiagramEdge, findDiagramNode, focusedBounds, typedSystem };
168
+ export { activeSequenceBounds, calcViewportForBounds, findCorrespondingNode, findDiagramEdge, findDiagramNode, focusedBounds, nodeRef, typedSystem };
@@ -1,18 +1,17 @@
1
- import { BBox, DeploymentFqn, DiagramEdge, DiagramEdgeDriftReason, DiagramNode, DiagramNodeDriftReason, ExclusiveUnion, Fqn, IconUrl, NonEmptyReadonlyArray, RichTextOrEmpty, ViewId } from '@likec4/core/types';
1
+ import { BBox, DeploymentFqn, DiagramEdge, DiagramNode, DiagramNodeDriftReason, ExclusiveUnion, Fqn, IconUrl, MarkdownOrString, NonEmptyReadonlyArray, ViewId } from '@likec4/core/types';
2
2
  import { XYPosition } from '@xyflow/system';
3
3
  import { FunctionComponent } from 'react';
4
4
  import { Simplify } from 'type-fest';
5
5
  import { BaseEdge, BaseEdgeData, BaseEdgeProps, BaseNode, BaseNodeData, BaseNodeProps, NonOptional } from '../base/types';
6
6
  export declare namespace Types {
7
- type LeafNodeData = Simplify<BaseNodeData & NonOptional<Pick<DiagramNode, 'id' | 'title' | 'color' | 'shape' | 'width' | 'level' | 'height' | 'style' | 'tags' | 'x' | 'y'>> & {
8
- technology: string | null;
9
- description: RichTextOrEmpty;
7
+ type LeafNodeData = Simplify<BaseNodeData & NonOptional<Pick<DiagramNode, 'id' | 'title' | 'technology' | 'description' | 'color' | 'shape' | 'width' | 'level' | 'height' | 'style' | 'tags' | 'x' | 'y'>> & {
10
8
  /**
11
9
  * View this node belongs to
12
10
  */
13
11
  viewId: ViewId;
14
12
  isMultiple?: boolean | undefined;
15
13
  icon: string | null;
14
+ drifts: NonEmptyReadonlyArray<DiagramNodeDriftReason> | null;
16
15
  }>;
17
16
  /**
18
17
  * Represents element from logical model
@@ -24,7 +23,6 @@ export declare namespace Types {
24
23
  * If set - this node has navigation to another view and diagram has handler for this
25
24
  */
26
25
  navigateTo: ViewId | null;
27
- drifts: NonEmptyReadonlyArray<DiagramNodeDriftReason> | null;
28
26
  }>;
29
27
  /**
30
28
  * Represents element from deployment model
@@ -33,7 +31,6 @@ export declare namespace Types {
33
31
  navigateTo: ViewId | null;
34
32
  deploymentFqn: DeploymentFqn;
35
33
  modelFqn: Fqn | null;
36
- drifts: NonEmptyReadonlyArray<DiagramNodeDriftReason> | null;
37
34
  }>;
38
35
  type SequenceActorNodePort = {
39
36
  id: string;
@@ -59,6 +56,7 @@ export declare namespace Types {
59
56
  viewId: ViewId;
60
57
  depth: number;
61
58
  icon?: IconUrl;
59
+ drifts: NonEmptyReadonlyArray<DiagramNodeDriftReason> | null;
62
60
  }>;
63
61
  type CompoundElementNodeData = Simplify<CompoundNodeData & {
64
62
  modelFqn: Fqn;
@@ -67,7 +65,6 @@ export declare namespace Types {
67
65
  * If set - this node has navigation to another view and diagram has handler for this
68
66
  */
69
67
  navigateTo: ViewId | null;
70
- drifts: NonEmptyReadonlyArray<DiagramNodeDriftReason> | null;
71
68
  }>;
72
69
  type CompoundDeploymentNodeData = Simplify<CompoundNodeData & {
73
70
  deploymentFqn: DeploymentFqn;
@@ -79,7 +76,6 @@ export declare namespace Types {
79
76
  * If set - this node has navigation to another view and diagram has handler for this
80
77
  */
81
78
  navigateTo: ViewId | null;
82
- drifts: NonEmptyReadonlyArray<DiagramNodeDriftReason> | null;
83
79
  }>;
84
80
  type ViewGroupNodeData = Simplify<CompoundNodeData & {
85
81
  isViewGroup: true;
@@ -106,14 +102,13 @@ export declare namespace Types {
106
102
  type: Type;
107
103
  }>;
108
104
  type NodeProps<Type extends NodeType = NodeType> = BaseNodeProps<Node<Type>>;
109
- type RelationshipEdgeData = Simplify<BaseEdgeData & NonOptional<Pick<DiagramEdge, 'id' | 'label' | 'labelBBox' | 'technology' | 'points' | 'dir' | 'color' | 'line' | 'head' | 'tail' | 'navigateTo' | 'astPath'>> & {
110
- notes: RichTextOrEmpty;
105
+ type RelationshipEdgeData = Simplify<BaseEdgeData & NonOptional<Pick<DiagramEdge, 'id' | 'label' | 'labelBBox' | 'technology' | 'points' | 'dir' | 'color' | 'line' | 'head' | 'tail' | 'navigateTo' | 'astPath' | 'drifts'>> & {
106
+ notes: MarkdownOrString | null;
111
107
  labelXY: XYPosition | null;
112
108
  controlPoints: XYPosition[] | undefined | null;
113
- drifts: NonEmptyReadonlyArray<DiagramEdgeDriftReason> | null;
114
109
  }>;
115
- type SequenceStepEdgeData = Simplify<BaseEdgeData & NonOptional<Pick<DiagramEdge, 'id' | 'label' | 'technology' | 'points' | 'dir' | 'color' | 'line' | 'head' | 'tail' | 'navigateTo' | 'astPath'>> & {
116
- notes: RichTextOrEmpty;
110
+ type SequenceStepEdgeData = Simplify<BaseEdgeData & NonOptional<Pick<DiagramEdge, 'id' | 'label' | 'technology' | 'points' | 'dir' | 'color' | 'line' | 'head' | 'tail' | 'navigateTo' | 'astPath' | 'drifts'>> & {
111
+ notes: MarkdownOrString | null;
117
112
  labelXY: XYPosition | null;
118
113
  labelBBox: BBox;
119
114
  controlPoints: XYPosition[] | undefined | null;
@@ -7,7 +7,10 @@ const container = css({
7
7
  padding: "2",
8
8
  margin: "0",
9
9
  width: "min-content",
10
- height: "min-content"
10
+ height: "min-content",
11
+ _print: {
12
+ display: "none"
13
+ }
11
14
  });
12
15
  const icon = css({
13
16
  ["--ai-radius"]: "0px",
@@ -43,7 +46,7 @@ const elementNotation = css({
43
46
  // WebkitBackdropFilter: fallbackVar(vars.safariAnimationHook, 'blur(8px)'),
44
47
  // backdropFilter: fallbackVar(vars.safariAnimationHook, 'blur(8px)'),
45
48
  // vars: {
46
- // // [stokeFillMix]: `color-mix(in srgb, ${vars.element.stroke} 90%, ${vars.element.fill})`
49
+ // // [stokeFillMix]: `color-mix(in oklab, ${vars.element.stroke} 90%, ${vars.element.fill})`
47
50
  // },
48
51
  _hover: {
49
52
  transition: "all 120ms ease-out",
@@ -1,13 +1,13 @@
1
- import { DefaultMap, nonNullable } from '@likec4/core';
2
- import { isome } from '@likec4/core/utils';
1
+ import { DefaultMap, nonNullable, BBox } from '@likec4/core';
2
+ import { isome, invariant } from '@likec4/core/utils';
3
3
  import { getNodeDimensions } from '@xyflow/system';
4
4
  import { produce } from 'immer';
5
5
  import { useRef, useMemo } from 'react';
6
- import { pipe, filter, hasAtLeast, map, forEach } from 'remeda';
6
+ import { pipe, filter, map, hasAtLeast, flatMap, unique, difference, clamp } from 'remeda';
7
7
  import '@xstate/react';
8
8
  import 'fast-equals';
9
9
  import { useDiagram } from '../hooks/safeContext.js';
10
- import { bezierControlPoints, getNodeIntersection } from '../utils/xyflow.js';
10
+ import { nodeToRect, bezierControlPoints } from '../utils/xyflow.js';
11
11
  import { useXYStoreApi } from '../hooks/useXYFlow.js';
12
12
  import { vector } from '../utils/vector.js';
13
13
 
@@ -45,14 +45,14 @@ class Rect {
45
45
  }
46
46
  get dimensions() {
47
47
  return {
48
- width: this.maxX - this.minX,
49
- height: this.maxY - this.minY
48
+ width: Math.round(this.maxX - this.minX),
49
+ height: Math.round(this.maxY - this.minY)
50
50
  };
51
51
  }
52
52
  get diff() {
53
53
  return {
54
- x: this.positionAbsolute.x - this.initialX,
55
- y: this.positionAbsolute.y - this.initialY
54
+ x: Math.round(this.positionAbsolute.x - this.initialX),
55
+ y: Math.round(this.positionAbsolute.y - this.initialY)
56
56
  };
57
57
  }
58
58
  get isMoved() {
@@ -100,6 +100,7 @@ class Leaf extends Rect {
100
100
  }
101
101
  }
102
102
  function makeEdgeModifier(edge, anchor) {
103
+ const controlPoints = edge.data.controlPoints ?? null;
103
104
  return (edgeLookup) => {
104
105
  const current = nonNullable(edgeLookup.get(edge.id), `Edge ${edge.id} not found`);
105
106
  const { x: dx, y: dy } = anchor.diff;
@@ -109,8 +110,7 @@ function makeEdgeModifier(edge, anchor) {
109
110
  type: "replace",
110
111
  item: produce(current, (draft) => {
111
112
  draft.data.points = edge.data.points;
112
- draft.data.controlPoints = edge.data.controlPoints;
113
- draft.data.labelXY = edge.data.labelXY;
113
+ draft.data.controlPoints = controlPoints;
114
114
  draft.data.labelBBox = edge.data.labelBBox;
115
115
  })
116
116
  };
@@ -120,18 +120,12 @@ function makeEdgeModifier(edge, anchor) {
120
120
  type: "replace",
121
121
  item: produce(current, (draft) => {
122
122
  draft.data.points = map(edge.data.points, (pt) => [pt[0] + dx, pt[1] + dy]);
123
- if (edge.data.controlPoints) {
124
- draft.data.controlPoints = (edge.data.controlPoints ?? []).map((pt) => ({
123
+ if (controlPoints) {
124
+ draft.data.controlPoints = controlPoints.map((pt) => ({
125
125
  x: pt.x + dx,
126
126
  y: pt.y + dy
127
127
  }));
128
128
  }
129
- if (edge.data.labelXY) {
130
- draft.data.labelXY = {
131
- x: edge.data.labelXY.x + dx,
132
- y: edge.data.labelXY.y + dy
133
- };
134
- }
135
129
  if (edge.data.labelBBox) {
136
130
  draft.data.labelBBox = {
137
131
  x: edge.data.labelBBox.x + dx,
@@ -144,6 +138,56 @@ function makeEdgeModifier(edge, anchor) {
144
138
  };
145
139
  };
146
140
  }
141
+ function makeRelativeEdgeModifier(edge, movingRect, anchorNode, staticNode) {
142
+ const controlPoints = edge.data.controlPoints ?? bezierControlPoints(edge.data.points);
143
+ const anchorV = vector(BBox.center(anchorNode));
144
+ const staticV = vector(BBox.center(staticNode));
145
+ const staticToAnchor = anchorV.subtract(staticV);
146
+ const staticToAnchorLength = staticToAnchor.length();
147
+ return (edgeLookup) => {
148
+ const current = nonNullable(edgeLookup.get(edge.id), `Edge ${edge.id} not found`);
149
+ const { x: dx, y: dy } = movingRect.diff;
150
+ if (dx === 0 && dy === 0) {
151
+ return {
152
+ id: edge.id,
153
+ type: "replace",
154
+ item: produce(current, (draft) => {
155
+ draft.data.points = edge.data.points;
156
+ draft.data.controlPoints = edge.data.controlPoints;
157
+ draft.data.labelBBox = edge.data.labelBBox;
158
+ })
159
+ };
160
+ }
161
+ const d = vector(dx, dy);
162
+ const relativePoint = (pt) => {
163
+ const p = vector(pt);
164
+ const staticToP = p.subtract(staticV);
165
+ const projLength = staticToP.dot(staticToAnchor);
166
+ const coeff = clamp(projLength / staticToAnchorLength ** 2, {
167
+ min: -1,
168
+ max: 1
169
+ });
170
+ const newP = p.add(d.multiply(coeff)).round();
171
+ return {
172
+ x: newP.x,
173
+ y: newP.y
174
+ };
175
+ };
176
+ return {
177
+ id: edge.id,
178
+ type: "replace",
179
+ item: produce(current, (draft) => {
180
+ draft.data.controlPoints = controlPoints.map(relativePoint);
181
+ if (edge.data.labelBBox) {
182
+ draft.data.labelBBox ??= edge.data.labelBBox;
183
+ const { x, y } = relativePoint(edge.data.labelBBox);
184
+ draft.data.labelBBox.x = x;
185
+ draft.data.labelBBox.y = y;
186
+ }
187
+ })
188
+ };
189
+ };
190
+ }
147
191
  function createLayoutConstraints(xyflowApi, editingNodeIds) {
148
192
  const { parentLookup, nodeLookup, edges, edgeLookup } = xyflowApi.getState();
149
193
  const rects = /* @__PURE__ */ new Map();
@@ -169,6 +213,16 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
169
213
  }
170
214
  return nested;
171
215
  });
216
+ if (hasAtLeast(editingNodeIds, 2)) {
217
+ const leafsOnly = pipe(
218
+ editingNodeIds,
219
+ flatMap((id) => [...nestedOf.get(id)]),
220
+ unique(),
221
+ (exclude) => difference(editingNodeIds, exclude)
222
+ );
223
+ invariant(hasAtLeast(leafsOnly, 1), "All editing nodes are nested within each other");
224
+ editingNodeIds = leafsOnly;
225
+ }
172
226
  const ancestorsOfDraggingNodes = new Set(
173
227
  editingNodeIds.flatMap((i) => ancestorsOf.get(i))
174
228
  );
@@ -188,40 +242,21 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
188
242
  parent: rect
189
243
  });
190
244
  });
191
- } else {
192
- rects.set(xynode.id, new Leaf(xynode, parent));
245
+ continue;
193
246
  }
247
+ rects.set(xynode.id, new Leaf(xynode, parent));
194
248
  }
195
249
  const rectsToUpdate = [...rects.values()];
196
250
  const edgeModifiers = /* @__PURE__ */ new Map();
197
- for (const r of rectsToUpdate) {
198
- if (r instanceof CompoundRect) {
199
- continue;
200
- }
201
- const nested = nestedOf.get(r.id);
202
- if (nested.size === 0) {
203
- continue;
204
- }
205
- pipe(
206
- edges,
207
- filter((e) => !edgeModifiers.has(e) && nested.has(e.source) && nested.has(e.target)),
208
- forEach((e) => {
209
- edgeModifiers.set(e, makeEdgeModifier(e, r));
210
- })
211
- );
212
- }
213
251
  const findMovingAncestor = (nodeId) => {
214
252
  return rects.get(nodeId) ?? ancestorsOf.get(nodeId).map((id) => rects.get(id)).find((s) => !!s) ?? null;
215
253
  };
216
254
  const movingNodes = new Set(editingNodeIds.flatMap((id) => [id, ...nestedOf.get(id)]));
217
255
  for (const edge of edges) {
218
- if (edgeModifiers.has(edge)) {
219
- continue;
220
- }
221
256
  const isSourceMoving = movingNodes.has(edge.source);
222
257
  const isTargetMoving = movingNodes.has(edge.target);
223
258
  if (isSourceMoving && isTargetMoving) {
224
- let r = rects.get(edge.source) ?? rects.get(edge.target) ?? ancestorsOf.get(edge.source).map((id) => rects.get(id)).find((s) => !!s) ?? ancestorsOf.get(edge.target).map((id) => rects.get(id)).find((s) => !!s);
259
+ let r = rects.get(edge.source) ?? rects.get(edge.target) ?? findMovingAncestor(edge.source) ?? findMovingAncestor(edge.target);
225
260
  if (r) {
226
261
  edgeModifiers.set(edge, makeEdgeModifier(edge, r));
227
262
  }
@@ -232,61 +267,21 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
232
267
  if (!movingRect) {
233
268
  continue;
234
269
  }
235
- const controlPoints = edge.data.controlPoints ?? bezierControlPoints(edge.data.points);
236
- const sourceNode = nonNullable(nodeLookup.get(edge.source), `Source node ${edge.source} not found`);
237
- const targetNode = nonNullable(nodeLookup.get(edge.target), `Target node ${edge.target} not found`);
238
- const sourceIntersectionPoint = vector(getNodeIntersection(sourceNode, targetNode));
239
- const targetIntersectionPoint = vector(getNodeIntersection(targetNode, sourceNode));
240
- const [anchorV, staticV] = isSourceMoving ? [sourceIntersectionPoint, targetIntersectionPoint] : [targetIntersectionPoint, sourceIntersectionPoint];
241
- const staticToAnchor = anchorV.subtract(staticV);
242
- const staticToAnchorLength = staticToAnchor.length();
243
- edgeModifiers.set(edge, (edgeLookup2) => {
244
- const current = nonNullable(edgeLookup2.get(edge.id), `Edge ${edge.id} not found`);
245
- const { x: dx, y: dy } = movingRect.diff;
246
- if (dx === 0 && dy === 0) {
247
- return {
248
- id: edge.id,
249
- type: "replace",
250
- item: produce(current, (draft) => {
251
- draft.data.points = edge.data.points;
252
- draft.data.controlPoints = edge.data.controlPoints;
253
- draft.data.labelXY = edge.data.labelXY;
254
- draft.data.labelBBox = edge.data.labelBBox;
255
- })
256
- };
257
- }
258
- const d = vector(dx, dy);
259
- const relativePoint = (pt) => {
260
- const p = vector(pt);
261
- const staticToP = p.subtract(staticV);
262
- const projLength = staticToP.dot(staticToAnchor);
263
- const coeff = Math.min(projLength / staticToAnchorLength ** 2, 1);
264
- const newP = p.add(d.multiply(coeff));
265
- return {
266
- x: Math.round(newP.x),
267
- y: Math.round(newP.y)
268
- };
269
- };
270
- return {
271
- id: edge.id,
272
- type: "replace",
273
- item: produce(current, (draft) => {
274
- draft.data.controlPoints = controlPoints.map(relativePoint);
275
- if (edge.data.labelBBox) {
276
- const { x, y } = relativePoint(edge.data.labelBBox);
277
- draft.data.labelBBox = {
278
- x,
279
- y,
280
- width: edge.data.labelBBox.width,
281
- height: edge.data.labelBBox.height
282
- };
283
- }
284
- if (edge.data.labelXY) {
285
- draft.data.labelXY = relativePoint(edge.data.labelXY);
286
- }
287
- })
288
- };
289
- });
270
+ const [sourceNode, targetNode] = pipe(
271
+ [edge.source, edge.target],
272
+ map((id) => nonNullable(nodeLookup.get(id), `Node ${id} not found`)),
273
+ map(nodeToRect)
274
+ );
275
+ const [anchorNode, staticNode] = isSourceMoving ? [sourceNode, targetNode] : [targetNode, sourceNode];
276
+ edgeModifiers.set(
277
+ edge,
278
+ makeRelativeEdgeModifier(
279
+ edge,
280
+ movingRect,
281
+ anchorNode,
282
+ staticNode
283
+ )
284
+ );
290
285
  continue;
291
286
  }
292
287
  }
@@ -335,10 +330,11 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
335
330
  }
336
331
  return acc;
337
332
  }, []);
338
- xyflowApi.getState().triggerNodeChanges(nodeUpdates);
339
- const changes = _edgeModifiers.map((fm) => fm(edgeLookup));
333
+ const { edgeLookup: edgeLookup2, triggerNodeChanges, triggerEdgeChanges } = xyflowApi.getState();
334
+ triggerNodeChanges(nodeUpdates);
335
+ const changes = _edgeModifiers.map((fm) => fm(edgeLookup2));
340
336
  if (changes.length > 0) {
341
- xyflowApi.getState().triggerEdgeChanges(changes);
337
+ triggerEdgeChanges(changes);
342
338
  }
343
339
  }
344
340
  let animationFrameId = null;
@@ -349,8 +345,16 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
349
345
  animationFrameId ??= requestAnimationFrame(() => {
350
346
  animationFrameId = null;
351
347
  for (const id of editingNodeIds) {
352
- const rect = nonNullable(rects.get(id));
353
- const node = nonNullable(nodeLookup.get(id));
348
+ const rect = rects.get(id);
349
+ if (!rect) {
350
+ console.warn(`Rect not found for id ${id}`);
351
+ continue;
352
+ }
353
+ const node = nodeLookup.get(id);
354
+ if (!node) {
355
+ console.warn(`Node not found for id ${id}`);
356
+ continue;
357
+ }
354
358
  rect.positionAbsolute = node.internals.positionAbsolute;
355
359
  }
356
360
  updateXYFlow();
@@ -373,10 +377,11 @@ function useLayoutConstraints() {
373
377
  const { nodeLookup } = xystore.getState();
374
378
  const draggingNodes = pipe(
375
379
  Array.from(nodeLookup.values()),
376
- filter((n) => n.draggable !== false && (n.dragging === true || n.id === xynode.id || n.selected === true))
380
+ filter((n) => n.dragging === true || n.id === xynode.id || n.selected === true),
381
+ map((n) => n.id)
377
382
  );
378
383
  if (hasAtLeast(draggingNodes, 1)) {
379
- solverRef.current = createLayoutConstraints(xystore, map(draggingNodes, (x) => x.id));
384
+ solverRef.current = createLayoutConstraints(xystore, draggingNodes);
380
385
  }
381
386
  },
382
387
  onNodeDrag: (_event) => {
@@ -1,4 +1,4 @@
1
- import { Queue, whereOperatorAsPredicate, nonNullable, GroupElementKind, RichText, invariant } from '@likec4/core';
1
+ import { Queue, whereOperatorAsPredicate, nonNullable, GroupElementKind, invariant } from '@likec4/core';
2
2
  import { pick, hasAtLeast } from 'remeda';
3
3
  import { ZIndexes } from '../../base/const.js';
4
4
 
@@ -50,7 +50,6 @@ function diagramToXY(opts) {
50
50
  position.y -= parent.y;
51
51
  }
52
52
  const id = ns + node.id;
53
- const drifts = node.drifts ?? null;
54
53
  const base = {
55
54
  id,
56
55
  deletable: false,
@@ -78,14 +77,15 @@ function diagramToXY(opts) {
78
77
  icon: node.icon ?? "none",
79
78
  tags: node.tags ?? null,
80
79
  x: node.x,
81
- y: node.y
80
+ y: node.y,
81
+ drifts: node.drifts ?? null
82
82
  };
83
83
  const leafNodeData = {
84
84
  viewId: view.id,
85
85
  id: node.id,
86
86
  title: node.title,
87
87
  technology: node.technology ?? null,
88
- description: RichText.from(node.description),
88
+ description: node.description ?? null,
89
89
  height: node.height,
90
90
  width: node.width,
91
91
  level: node.level,
@@ -96,7 +96,8 @@ function diagramToXY(opts) {
96
96
  tags: node.tags,
97
97
  x: node.x,
98
98
  y: node.y,
99
- isMultiple: node.style?.multiple ?? false
99
+ isMultiple: node.style?.multiple ?? false,
100
+ drifts: node.drifts ?? null
100
101
  };
101
102
  if (node.kind === GroupElementKind) {
102
103
  xynodes.push({
@@ -127,8 +128,7 @@ function diagramToXY(opts) {
127
128
  ...compoundData,
128
129
  ...navigateTo,
129
130
  deploymentFqn,
130
- modelFqn,
131
- drifts
131
+ modelFqn
132
132
  }
133
133
  }
134
134
  );
@@ -143,8 +143,7 @@ function diagramToXY(opts) {
143
143
  data: {
144
144
  ...compoundData,
145
145
  ...navigateTo,
146
- modelFqn,
147
- drifts
146
+ modelFqn
148
147
  }
149
148
  }
150
149
  );
@@ -159,8 +158,7 @@ function diagramToXY(opts) {
159
158
  ...leafNodeData,
160
159
  ...navigateTo,
161
160
  deploymentFqn,
162
- modelFqn,
163
- drifts
161
+ modelFqn
164
162
  }
165
163
  }
166
164
  );
@@ -175,8 +173,7 @@ function diagramToXY(opts) {
175
173
  data: {
176
174
  ...leafNodeData,
177
175
  ...navigateTo,
178
- modelFqn,
179
- drifts
176
+ modelFqn
180
177
  }
181
178
  }
182
179
  );
@@ -203,11 +200,11 @@ function diagramToXY(opts) {
203
200
  id: edge.id,
204
201
  label: edge.label,
205
202
  technology: edge.technology,
206
- notes: RichText.from(edge.notes),
203
+ notes: edge.notes ?? null,
207
204
  navigateTo: edge.navigateTo,
208
205
  controlPoints: edge.controlPoints ?? null,
209
206
  labelBBox: edge.labelBBox ?? null,
210
- labelXY: edge.labelBBox ? { x: edge.labelBBox.x, y: edge.labelBBox.y } : null,
207
+ labelXY: null,
211
208
  points: edge.points,
212
209
  color: edge.color ?? "gray",
213
210
  line: edge.line ?? "dashed",
@@ -1,5 +1,4 @@
1
1
  import { nonNullable } from '@likec4/core';
2
- import { RichText } from '@likec4/core/types';
3
2
  import { SeqZIndex, SeqParallelAreaColor } from './const.js';
4
3
 
5
4
  function sequenceLayoutToXY(view) {
@@ -46,7 +45,8 @@ function toCompoundArea({ id, x, y, width, height, depth }, node, view) {
46
45
  y,
47
46
  viewId: view.id,
48
47
  depth,
49
- isViewGroup: true
48
+ isViewGroup: true,
49
+ drifts: node.drifts ?? null
50
50
  },
51
51
  // zIndex: SeqZIndex.compound,
52
52
  position: {
@@ -83,9 +83,10 @@ function toSeqParallelArea({ parallelPrefix, x, y, width, height }, view) {
83
83
  icon: null,
84
84
  width,
85
85
  height,
86
- description: RichText.EMPTY,
86
+ description: null,
87
87
  viewId: view.id,
88
- parallelPrefix
88
+ parallelPrefix,
89
+ drifts: null
89
90
  },
90
91
  zIndex: SeqZIndex.parallel,
91
92
  position: {
@@ -126,10 +127,11 @@ function toSeqActorNode({ id, x, y, width, height, ports }, actor, bounds, view)
126
127
  tags: actor.tags,
127
128
  modelFqn: actor.modelRef ?? null,
128
129
  technology: actor.technology ?? null,
129
- description: RichText.from(actor.description),
130
+ description: actor.description ?? null,
130
131
  viewHeight: bounds.height,
131
132
  viewId: view.id,
132
- ports
133
+ ports,
134
+ drifts: actor.drifts ?? null
133
135
  },
134
136
  deletable: false,
135
137
  selectable: true,
@@ -149,7 +151,7 @@ function toSeqStepEdge({ id, labelBBox, sourceHandle, targetHandle }, edge) {
149
151
  id,
150
152
  label: edge.label,
151
153
  technology: edge.technology,
152
- notes: RichText.from(edge.notes),
154
+ notes: edge.notes ?? null,
153
155
  navigateTo: edge.navigateTo,
154
156
  controlPoints: null,
155
157
  labelBBox: {
@@ -165,7 +167,8 @@ function toSeqStepEdge({ id, labelBBox, sourceHandle, targetHandle }, edge) {
165
167
  dir: "forward",
166
168
  head: edge.head ?? "normal",
167
169
  tail: edge.tail ?? "none",
168
- astPath: edge.astPath
170
+ astPath: edge.astPath,
171
+ drifts: edge.drifts ?? null
169
172
  },
170
173
  selectable: true,
171
174
  focusable: false,
@@ -69,6 +69,9 @@ const NavigationPanel = memo(() => {
69
69
  "calc(100vw - 2 * {spacing.md})",
70
70
  "calc(100cqw - 2 * {spacing.md})"
71
71
  ]
72
+ },
73
+ _print: {
74
+ display: "none"
72
75
  }
73
76
  },
74
77
  children: /* @__PURE__ */ jsxs(NavigationPanelActorContextProvider, { value: actorRef, children: [
@@ -109,6 +112,7 @@ const NavigationPanelPopoverTarget = ({ actor }) => {
109
112
  m.div,
110
113
  {
111
114
  layout: true,
115
+ layoutDependency: isActiveWalkthrough,
112
116
  className: hstack({
113
117
  layerStyle: "likec4.panel",
114
118
  position: "relative",
@@ -118,7 +122,7 @@ const NavigationPanelPopoverTarget = ({ actor }) => {
118
122
  width: "100%"
119
123
  }),
120
124
  onMouseLeave: () => actor.send({ type: "breadcrumbs.mouseLeave" }),
121
- children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: isActiveWalkthrough ? /* @__PURE__ */ jsx(ActiveWalkthroughControls, {}) : /* @__PURE__ */ jsx(NavigationPanelControls, {}) })
125
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: isActiveWalkthrough ? /* @__PURE__ */ jsx(ActiveWalkthroughControls, {}) : /* @__PURE__ */ jsx(NavigationPanelControls, {}) })
122
126
  }
123
127
  ) }) });
124
128
  };