@likec4/diagram 1.27.0 → 1.27.1
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.
- package/bundle/index.js +327 -281
- package/dist/LikeC4Diagram.js +2 -8
- package/dist/base/primitives/compound/CompoundNodeContainer.js +1 -1
- package/dist/base/primitives/edge/EdgeLabel.css.js +1 -4
- package/dist/base/primitives/edge/EdgeLabelContainer.js +1 -1
- package/dist/base/primitives/edge/EdgePath.js +0 -3
- package/dist/base/primitives/edge/edge.css.js +2 -0
- package/dist/base/primitives/element/ElementNodeContainer.css.js +1 -0
- package/dist/base/primitives/element/ElementShape.css.js +1 -12
- package/dist/base/primitives/element/ElementTitle.css.js +3 -2
- package/dist/base/types.js +2 -2
- package/dist/components/Link.js +1 -1
- package/dist/hooks/useDiagram.d.ts +320 -490
- package/dist/likec4diagram/DiagramXYFlow.js +10 -9
- package/dist/likec4diagram/custom/edges/RelationshipEdge.js +30 -41
- package/dist/likec4diagram/custom/nodes/toolbar/Toolbar.js +1 -1
- package/dist/likec4diagram/custom/nodes/toolbar/styles.css.js +6 -1
- package/dist/likec4diagram/ui/controls/LayoutDriftNotification.js +13 -5
- package/dist/likec4diagram/useLayoutConstraints.d.ts +11 -10
- package/dist/likec4diagram/useLayoutConstraints.js +64 -61
- package/dist/overlays/Overlays.js +2 -2
- package/dist/overlays/overlay/Overlay.css.js +31 -12
- package/dist/overlays/overlay/Overlay.js +30 -27
- package/dist/overlays/relationship-details/useLayoutedDetails.js +1 -1
- package/dist/overlays/relationships-browser/SelectElement.js +4 -4
- package/dist/state/DiagramActorProvider.d.ts +4 -2
- package/dist/state/DiagramActorProvider.js +30 -37
- package/dist/state/assign.js +2 -2
- package/dist/state/diagram-machine.d.ts +450 -608
- package/dist/state/diagram-machine.js +87 -57
- package/dist/styles.css.js +1 -1
- package/package.json +5 -5
- package/src/LikeC4Diagram.tsx +1 -7
- package/src/base/primitives/compound/CompoundNodeContainer.tsx +1 -1
- package/src/base/primitives/edge/EdgeLabel.css.ts +1 -4
- package/src/base/primitives/edge/EdgeLabelContainer.tsx +1 -1
- package/src/base/primitives/edge/EdgePath.tsx +0 -3
- package/src/base/primitives/edge/edge.css.ts +2 -0
- package/src/base/primitives/element/ElementNodeContainer.css.ts +1 -0
- package/src/base/primitives/element/ElementShape.css.ts +1 -12
- package/src/base/primitives/element/ElementTitle.css.ts +3 -2
- package/src/base/types.ts +2 -7
- package/src/components/Link.tsx +3 -1
- package/src/likec4diagram/DiagramXYFlow.tsx +11 -9
- package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +39 -48
- package/src/likec4diagram/custom/nodes/toolbar/Toolbar.tsx +2 -2
- package/src/likec4diagram/custom/nodes/toolbar/styles.css.ts +5 -0
- package/src/likec4diagram/ui/controls/LayoutDriftNotification.tsx +8 -4
- package/src/likec4diagram/useLayoutConstraints.ts +84 -89
- package/src/overlays/Overlays.tsx +2 -2
- package/src/overlays/overlay/Overlay.css.ts +30 -12
- package/src/overlays/overlay/Overlay.tsx +35 -29
- package/src/overlays/relationship-details/useLayoutedDetails.ts +1 -1
- package/src/overlays/relationships-browser/SelectElement.tsx +5 -5
- package/src/state/DiagramActorProvider.tsx +40 -50
- package/src/state/assign.ts +2 -2
- package/src/state/diagram-machine.ts +100 -69
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { nonNullable } from '@likec4/core';
|
|
3
3
|
import { cx } from '@likec4/styles/css';
|
|
4
|
-
import { useTimeout, useCallbackRef } from '@mantine/hooks';
|
|
4
|
+
import { useTimeout, useDebouncedCallback, useCallbackRef } from '@mantine/hooks';
|
|
5
5
|
import { deepEqual, shallowEqual } from 'fast-equals';
|
|
6
6
|
import { memo } from 'react';
|
|
7
7
|
import { BaseXYFlow } from '../base/BaseXYFlow.js';
|
|
8
|
-
import { usePanningAtom } from '../context/ReduceGraphics.js';
|
|
8
|
+
import { useIsReducedGraphics, usePanningAtom } from '../context/ReduceGraphics.js';
|
|
9
9
|
import { useDiagram, useDiagramContext } from '../hooks/useDiagram.js';
|
|
10
10
|
import { edgeTypes, nodeTypes } from './custom/index.js';
|
|
11
11
|
import { DiagramUI } from './DiagramUI.js';
|
|
@@ -13,7 +13,7 @@ import { useLayoutConstraints } from './useLayoutConstraints.js';
|
|
|
13
13
|
import { useDiagramEventHandlers } from '../context/DiagramEventHandlers.js';
|
|
14
14
|
|
|
15
15
|
const selectXYProps = (ctx) => ({
|
|
16
|
-
initialized: ctx.initialized,
|
|
16
|
+
initialized: ctx.initialized.xydata && ctx.initialized.xyflow,
|
|
17
17
|
nodes: ctx.xynodes,
|
|
18
18
|
edges: ctx.xyedges,
|
|
19
19
|
pannable: ctx.pannable,
|
|
@@ -56,9 +56,11 @@ const LikeC4DiagramXYFlow = memo(({
|
|
|
56
56
|
onCanvasClick,
|
|
57
57
|
onCanvasDblClick
|
|
58
58
|
} = useDiagramEventHandlers();
|
|
59
|
-
const notReadOnly = !enableReadOnly, layoutConstraints = useLayoutConstraints(), $isPanning = usePanningAtom(), isPanning = useTimeout(() => {
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const notReadOnly = !enableReadOnly, isReducedGraphics = useIsReducedGraphics(), layoutConstraints = useLayoutConstraints(), $isPanning = usePanningAtom(), isPanning = useTimeout(() => {
|
|
60
|
+
if (!$isPanning.get()) {
|
|
61
|
+
$isPanning.set(true);
|
|
62
|
+
}
|
|
63
|
+
}, isReducedGraphics ? 150 : 350), notPanning = useDebouncedCallback(() => {
|
|
62
64
|
isPanning.clear();
|
|
63
65
|
if ($isPanning.get()) {
|
|
64
66
|
$isPanning.set(false);
|
|
@@ -70,10 +72,9 @@ const LikeC4DiagramXYFlow = memo(({
|
|
|
70
72
|
if (!$isPanning.get()) {
|
|
71
73
|
isPanning.start();
|
|
72
74
|
}
|
|
73
|
-
notPanning
|
|
74
|
-
notPanning.start();
|
|
75
|
+
notPanning();
|
|
75
76
|
}), onMoveEnd = useCallbackRef((event, viewport) => {
|
|
76
|
-
notPanning.
|
|
77
|
+
notPanning.flush();
|
|
77
78
|
diagram.send({ type: "xyflow.viewportMoved", viewport, manually: !!event });
|
|
78
79
|
}), onViewportResize = useCallbackRef(() => {
|
|
79
80
|
diagram.send({ type: "xyflow.resized" });
|
|
@@ -103,7 +103,7 @@ const RelationshipEdge = customEdge((props) => {
|
|
|
103
103
|
if (!labelBBox || labelBBox.x === labelPos.x && labelBBox.y === labelPos.y) {
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
diagram.updateEdgeData(id, {
|
|
107
107
|
labelXY: {
|
|
108
108
|
x: labelPos.x,
|
|
109
109
|
y: labelPos.y
|
|
@@ -135,15 +135,13 @@ const RelationshipEdge = customEdge((props) => {
|
|
|
135
135
|
hasMoved = true;
|
|
136
136
|
pointer = clientPoint;
|
|
137
137
|
const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: false });
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
controlPoints: cp
|
|
146
|
-
};
|
|
138
|
+
const cp = controlPoints.slice();
|
|
139
|
+
cp[index] = {
|
|
140
|
+
x: Math.round(x),
|
|
141
|
+
y: Math.round(y)
|
|
142
|
+
};
|
|
143
|
+
diagram.updateEdgeData(id, {
|
|
144
|
+
controlPoints: cp
|
|
147
145
|
});
|
|
148
146
|
}
|
|
149
147
|
e2.stopPropagation();
|
|
@@ -177,13 +175,9 @@ const RelationshipEdge = customEdge((props) => {
|
|
|
177
175
|
newControlPoints.splice(index, 1);
|
|
178
176
|
e2.stopPropagation();
|
|
179
177
|
setTimeout(() => {
|
|
180
|
-
|
|
178
|
+
diagram.updateEdgeData(id, { controlPoints: newControlPoints });
|
|
181
179
|
diagram.scheduleSaveManualLayout();
|
|
182
180
|
}, 10);
|
|
183
|
-
domNode.removeEventListener("pointerup", onPointerUp, {
|
|
184
|
-
capture: true
|
|
185
|
-
});
|
|
186
|
-
e2.stopPropagation();
|
|
187
181
|
};
|
|
188
182
|
domNode.addEventListener("pointerup", onPointerUp, {
|
|
189
183
|
once: true,
|
|
@@ -213,33 +207,28 @@ const RelationshipEdge = customEdge((props) => {
|
|
|
213
207
|
if (e.button !== 2) {
|
|
214
208
|
return;
|
|
215
209
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const distanceToEdge = Math.abs(fromCurrentToNext.cross(fromCurrentToNew).abs() / fromCurrentToNext.abs());
|
|
233
|
-
if (distanceToEdge < minDistance) {
|
|
234
|
-
minDistance = distanceToEdge;
|
|
235
|
-
insertionIndex = i;
|
|
236
|
-
}
|
|
210
|
+
const points2 = [
|
|
211
|
+
new VectorImpl(sourceX, sourceY),
|
|
212
|
+
...controlPoints.map(vector) || [],
|
|
213
|
+
new VectorImpl(targetX, targetY)
|
|
214
|
+
];
|
|
215
|
+
let pointer = { x: e.clientX, y: e.clientY };
|
|
216
|
+
const newPoint = vector(xyflow.screenToFlowPosition(pointer, { snapToGrid: false }));
|
|
217
|
+
let insertionIndex = 0;
|
|
218
|
+
let minDistance = Infinity;
|
|
219
|
+
for (let i = 0; i < points2.length - 1; i++) {
|
|
220
|
+
const a = points2[i], b = points2[i + 1], fromCurrentToNext = b.sub(a), fromCurrentToNew = newPoint.sub(a), fromNextToNew = newPoint.sub(b);
|
|
221
|
+
if (fromCurrentToNext.dot(fromCurrentToNew) * fromCurrentToNext.dot(fromNextToNew) < 0) {
|
|
222
|
+
const distanceToEdge = Math.abs(fromCurrentToNext.cross(fromCurrentToNew).abs() / fromCurrentToNext.abs());
|
|
223
|
+
if (distanceToEdge < minDistance) {
|
|
224
|
+
minDistance = distanceToEdge;
|
|
225
|
+
insertionIndex = i;
|
|
237
226
|
}
|
|
238
227
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
});
|
|
228
|
+
}
|
|
229
|
+
const newControlPoints = controlPoints.slice() || [];
|
|
230
|
+
newControlPoints.splice(insertionIndex, 0, newPoint);
|
|
231
|
+
diagram.updateEdgeData(id, { controlPoints: newControlPoints });
|
|
243
232
|
diagram.scheduleSaveManualLayout();
|
|
244
233
|
e.stopPropagation();
|
|
245
234
|
};
|
|
@@ -290,7 +279,7 @@ const RelationshipEdge = customEdge((props) => {
|
|
|
290
279
|
}
|
|
291
280
|
)
|
|
292
281
|
] }),
|
|
293
|
-
enableEdgeEditing && controlPoints.length > 0 && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
|
|
282
|
+
enableEdgeEditing && controlPoints.length > 0 && (selected || hovered || isControlPointDragging) && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
|
|
294
283
|
EdgeContainer,
|
|
295
284
|
{
|
|
296
285
|
component: "svg",
|
|
@@ -57,7 +57,7 @@ function Toolbar({ title, children, nodeProps, ...props }) {
|
|
|
57
57
|
onDoubleClick: stopPropagation,
|
|
58
58
|
withBorder: true,
|
|
59
59
|
children: /* @__PURE__ */ jsxs(Stack, { gap: "6px", children: [
|
|
60
|
-
/* @__PURE__ */ jsx(Box, { px: "4px", children: /* @__PURE__ */ jsx(Text, {
|
|
60
|
+
/* @__PURE__ */ jsx(Box, { px: "4px", children: /* @__PURE__ */ jsx(Text, { className: toolbarTitle, children: title }) }),
|
|
61
61
|
/* @__PURE__ */ jsx(Group, { gap: 4, children })
|
|
62
62
|
] })
|
|
63
63
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { css } from '@likec4/styles/css';
|
|
2
2
|
|
|
3
3
|
const toolbarTitle = css({
|
|
4
|
+
color: "mantine.colors.dimmed",
|
|
4
5
|
fontSize: "10px",
|
|
5
6
|
fontWeight: 600,
|
|
7
|
+
maxWidth: "220px",
|
|
6
8
|
cursor: "default",
|
|
7
|
-
userSelect: "all"
|
|
9
|
+
userSelect: "all",
|
|
10
|
+
textOverflow: "ellipsis",
|
|
11
|
+
overflow: "hidden",
|
|
12
|
+
whiteSpace: "nowrap"
|
|
8
13
|
});
|
|
9
14
|
|
|
10
15
|
export { toolbarTitle };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { HoverCard, HoverCardTarget,
|
|
2
|
+
import { HoverCard, HoverCardTarget, HoverCardDropdown, Notification, Text, Code } from '@mantine/core';
|
|
3
3
|
import { IconAlertTriangle } from '@tabler/icons-react';
|
|
4
|
+
import { css } from '@likec4/styles/css';
|
|
4
5
|
import { ActionIcon } from './_shared.js';
|
|
5
6
|
|
|
6
7
|
const LayoutDriftNotification = (props) => /* @__PURE__ */ jsxs(
|
|
@@ -11,10 +12,17 @@ const LayoutDriftNotification = (props) => /* @__PURE__ */ jsxs(
|
|
|
11
12
|
closeDelay: 100,
|
|
12
13
|
...props,
|
|
13
14
|
children: [
|
|
14
|
-
/* @__PURE__ */ jsx(HoverCardTarget, { children: /* @__PURE__ */
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/* @__PURE__ */ jsx(HoverCardTarget, { children: /* @__PURE__ */ jsx(
|
|
16
|
+
ActionIcon,
|
|
17
|
+
{
|
|
18
|
+
color: "orange",
|
|
19
|
+
c: "orange",
|
|
20
|
+
className: css({
|
|
21
|
+
bg: "mantine.colors.orange.light"
|
|
22
|
+
}),
|
|
23
|
+
children: /* @__PURE__ */ jsx(IconAlertTriangle, {})
|
|
24
|
+
}
|
|
25
|
+
) }),
|
|
18
26
|
/* @__PURE__ */ jsx(HoverCardDropdown, { p: "0", children: /* @__PURE__ */ jsxs(
|
|
19
27
|
Notification,
|
|
20
28
|
{
|
|
@@ -20,21 +20,22 @@ declare abstract class Rect {
|
|
|
20
20
|
height: number;
|
|
21
21
|
};
|
|
22
22
|
get position(): XYPosition;
|
|
23
|
-
protected abstract parent:
|
|
24
|
-
constructor(xynode: InternalNode, parent?:
|
|
23
|
+
protected abstract parent: CompoundRect | null;
|
|
24
|
+
constructor(xynode: InternalNode, parent?: CompoundRect | null);
|
|
25
25
|
}
|
|
26
|
-
declare class
|
|
27
|
-
readonly parent:
|
|
26
|
+
declare class CompoundRect extends Rect {
|
|
27
|
+
readonly parent: CompoundRect | null;
|
|
28
28
|
readonly children: Rect[];
|
|
29
|
-
constructor(xynode: InternalNode, parent?:
|
|
29
|
+
constructor(xynode: InternalNode, parent?: CompoundRect | null);
|
|
30
|
+
}
|
|
31
|
+
declare class Leaf extends Rect {
|
|
32
|
+
readonly parent: CompoundRect | null;
|
|
33
|
+
constructor(xynode: InternalNode, parent?: CompoundRect | null);
|
|
30
34
|
}
|
|
31
|
-
type NodePositionUpdater = (nodes: Array<{
|
|
32
|
-
rect: Rect | Compound;
|
|
33
|
-
node: InternalNode;
|
|
34
|
-
}>) => void;
|
|
35
35
|
export declare function createLayoutConstraints(xyflowApi: XYStoreApi, editingNodeIds: NonEmptyArray<string>): {
|
|
36
|
+
rects: ReadonlyMap<string, Leaf | CompoundRect>;
|
|
36
37
|
updateXYFlowNodes: () => void;
|
|
37
|
-
onMove: (
|
|
38
|
+
onMove: () => void;
|
|
38
39
|
};
|
|
39
40
|
type LayoutConstraints = {
|
|
40
41
|
onNodeDragStart: OnNodeDrag<Types.Node>;
|
|
@@ -2,7 +2,7 @@ import { nonNullable } from '@likec4/core';
|
|
|
2
2
|
import '@xyflow/react';
|
|
3
3
|
import { getNodeDimensions } from '@xyflow/system';
|
|
4
4
|
import { useRef, useMemo } from 'react';
|
|
5
|
-
import { pipe, filter,
|
|
5
|
+
import { pipe, filter, hasAtLeast, map } from 'remeda';
|
|
6
6
|
import { useDiagram } from '../hooks/useDiagram.js';
|
|
7
7
|
import { useXYStoreApi } from '../hooks/useXYFlow.js';
|
|
8
8
|
|
|
@@ -62,7 +62,7 @@ class Rect {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
class
|
|
65
|
+
class CompoundRect extends Rect {
|
|
66
66
|
constructor(xynode, parent = null) {
|
|
67
67
|
super(xynode, parent);
|
|
68
68
|
this.parent = parent;
|
|
@@ -92,51 +92,51 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
|
|
|
92
92
|
const ancestorsOfDraggingNodes = new Set(
|
|
93
93
|
editingNodeIds.flatMap(ancestorsOf)
|
|
94
94
|
);
|
|
95
|
-
const traverse =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
traverse.push({
|
|
99
|
-
xynode,
|
|
100
|
-
parent: null
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
95
|
+
const traverse = [...nodeLookup.values()].flatMap(
|
|
96
|
+
(x) => !x.parentId ? { xynode: x, parent: null } : []
|
|
97
|
+
);
|
|
104
98
|
while (traverse.length > 0) {
|
|
105
99
|
const { xynode, parent } = traverse.shift();
|
|
106
100
|
const isEditing = editingNodeIds.includes(xynode.id);
|
|
107
|
-
const shouldTraverse = !isEditing &&
|
|
108
|
-
const rect = shouldTraverse ? new Compound(xynode, parent) : new Leaf(xynode, parent);
|
|
109
|
-
rects.set(xynode.id, rect);
|
|
101
|
+
const shouldTraverse = !isEditing && xynode.type !== "element" && xynode.type !== "deployment" && ancestorsOfDraggingNodes.has(xynode.id);
|
|
110
102
|
if (shouldTraverse) {
|
|
103
|
+
const rect = new CompoundRect(xynode, parent);
|
|
104
|
+
rects.set(xynode.id, rect);
|
|
111
105
|
parentLookup.get(xynode.id)?.forEach((child) => {
|
|
112
106
|
traverse.push({
|
|
113
107
|
xynode: child,
|
|
114
108
|
parent: rect
|
|
115
109
|
});
|
|
116
110
|
});
|
|
111
|
+
} else {
|
|
112
|
+
rects.set(xynode.id, new Leaf(xynode, parent));
|
|
117
113
|
}
|
|
118
114
|
}
|
|
119
115
|
const rectsToUpdate = [...rects.values()];
|
|
120
116
|
applyConstraints(rectsToUpdate);
|
|
121
117
|
function applyConstraints(targets) {
|
|
122
|
-
|
|
118
|
+
for (const r of targets) {
|
|
119
|
+
if (!(r instanceof CompoundRect)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
123
122
|
applyConstraints(r.children);
|
|
124
|
-
const childrenBB =
|
|
125
|
-
minX: Math.min(acc.minX, r2.minX),
|
|
126
|
-
minY: Math.min(acc.minY, r2.minY),
|
|
127
|
-
maxX: Math.max(acc.maxX, r2.maxX),
|
|
128
|
-
maxY: Math.max(acc.maxY, r2.maxY)
|
|
129
|
-
}), {
|
|
123
|
+
const childrenBB = {
|
|
130
124
|
minX: Infinity,
|
|
131
125
|
minY: Infinity,
|
|
132
126
|
maxX: -Infinity,
|
|
133
127
|
maxY: -Infinity
|
|
134
|
-
}
|
|
128
|
+
};
|
|
129
|
+
for (const child of r.children) {
|
|
130
|
+
childrenBB.minX = Math.min(childrenBB.minX, child.minX);
|
|
131
|
+
childrenBB.minY = Math.min(childrenBB.minY, child.minY);
|
|
132
|
+
childrenBB.maxX = Math.max(childrenBB.maxX, child.maxX);
|
|
133
|
+
childrenBB.maxY = Math.max(childrenBB.maxY, child.maxY);
|
|
134
|
+
}
|
|
135
135
|
r.minX = childrenBB.minX - Rect.LeftPadding;
|
|
136
136
|
r.minY = childrenBB.minY - Rect.TopPadding;
|
|
137
137
|
r.maxX = childrenBB.maxX + Rect.RightPadding;
|
|
138
138
|
r.maxY = childrenBB.maxY + Rect.BottomPadding;
|
|
139
|
-
}
|
|
139
|
+
}
|
|
140
140
|
}
|
|
141
141
|
function updateXYFlowNodes() {
|
|
142
142
|
applyConstraints(rectsToUpdate);
|
|
@@ -149,7 +149,7 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
|
|
|
149
149
|
position: r.position,
|
|
150
150
|
positionAbsolute: r.positionAbsolute
|
|
151
151
|
});
|
|
152
|
-
if (r instanceof
|
|
152
|
+
if (r instanceof CompoundRect) {
|
|
153
153
|
acc.push({
|
|
154
154
|
id: r.id,
|
|
155
155
|
type: "dimensions",
|
|
@@ -162,26 +162,23 @@ function createLayoutConstraints(xyflowApi, editingNodeIds) {
|
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
164
|
let animationFrameId = null;
|
|
165
|
-
function onMove(
|
|
165
|
+
function onMove() {
|
|
166
166
|
if (rectsToUpdate.length === 0) {
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
169
|
animationFrameId ??= requestAnimationFrame(() => {
|
|
170
170
|
animationFrameId = null;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
node: nonNullable(nodeLookup.get(id))
|
|
178
|
-
}))
|
|
179
|
-
)
|
|
180
|
-
);
|
|
171
|
+
const { nodeLookup: nodeLookup2 } = xyflowApi.getState();
|
|
172
|
+
for (const id of editingNodeIds) {
|
|
173
|
+
const rect = nonNullable(rects.get(id));
|
|
174
|
+
const node = nonNullable(nodeLookup2.get(id));
|
|
175
|
+
rect.positionAbsolute = node.internals.positionAbsolute;
|
|
176
|
+
}
|
|
181
177
|
updateXYFlowNodes();
|
|
182
178
|
});
|
|
183
179
|
}
|
|
184
180
|
return {
|
|
181
|
+
rects,
|
|
185
182
|
updateXYFlowNodes,
|
|
186
183
|
onMove
|
|
187
184
|
};
|
|
@@ -190,33 +187,39 @@ function useLayoutConstraints() {
|
|
|
190
187
|
const xystore = useXYStoreApi();
|
|
191
188
|
const diagram = useDiagram();
|
|
192
189
|
const solverRef = useRef(void 0);
|
|
193
|
-
return useMemo(() =>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
190
|
+
return useMemo(() => {
|
|
191
|
+
let wasPending = false;
|
|
192
|
+
const initial = { x: 0, y: 0 };
|
|
193
|
+
let moved = false;
|
|
194
|
+
return {
|
|
195
|
+
onNodeDragStart: (_event, xynode) => {
|
|
196
|
+
wasPending = diagram.cancelSaveManualLayout();
|
|
197
|
+
const { nodeLookup } = xystore.getState();
|
|
198
|
+
const draggingNodes = pipe(
|
|
199
|
+
Array.from(nodeLookup.values()),
|
|
200
|
+
filter((n) => n.draggable !== false && (n.dragging === true || n.id === xynode.id || n.selected === true))
|
|
201
|
+
);
|
|
202
|
+
if (hasAtLeast(draggingNodes, 1)) {
|
|
203
|
+
solverRef.current = createLayoutConstraints(xystore, map(draggingNodes, (x) => x.id));
|
|
204
|
+
}
|
|
205
|
+
initial.x = _event.clientX;
|
|
206
|
+
initial.y = _event.clientY;
|
|
207
|
+
moved = false;
|
|
208
|
+
},
|
|
209
|
+
onNodeDrag: (_event) => {
|
|
210
|
+
moved = Math.abs(_event.clientX - initial.x) > 4 || Math.abs(_event.clientY - initial.y) > 4;
|
|
211
|
+
solverRef.current?.onMove();
|
|
212
|
+
},
|
|
213
|
+
onNodeDragStop: (_event) => {
|
|
214
|
+
moved = Math.abs(_event.clientX - initial.x) > 4 || Math.abs(_event.clientY - initial.y) > 4;
|
|
215
|
+
console.log("moved", moved);
|
|
216
|
+
if (wasPending || moved) {
|
|
217
|
+
diagram.scheduleSaveManualLayout();
|
|
218
|
+
}
|
|
219
|
+
solverRef.current = void 0;
|
|
205
220
|
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
solverRef.current?.onMove((nodes) => {
|
|
209
|
-
nodes.forEach(({ rect, node }) => {
|
|
210
|
-
rect.positionAbsolute = node.internals.positionAbsolute;
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
},
|
|
214
|
-
onNodeDragStop: () => {
|
|
215
|
-
solverRef.current?.updateXYFlowNodes();
|
|
216
|
-
diagram.scheduleSaveManualLayout();
|
|
217
|
-
solverRef.current = void 0;
|
|
218
|
-
}
|
|
219
|
-
}), [xystore, diagram]);
|
|
221
|
+
};
|
|
222
|
+
}, [xystore, diagram]);
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
export { createLayoutConstraints, useLayoutConstraints };
|
|
@@ -3,7 +3,7 @@ import { nonexhaustive } from '@likec4/core';
|
|
|
3
3
|
import { Box, Notification, ScrollAreaAutosize, Code, Group, Button } from '@mantine/core';
|
|
4
4
|
import { IconX } from '@tabler/icons-react';
|
|
5
5
|
import { useSelector } from '@xstate/react';
|
|
6
|
-
import {
|
|
6
|
+
import { useReducedMotionConfig, LayoutGroup, AnimatePresence } from 'framer-motion';
|
|
7
7
|
import { animate } from 'framer-motion/dom';
|
|
8
8
|
import { memo, useMemo, useEffect } from 'react';
|
|
9
9
|
import { ErrorBoundary } from 'react-error-boundary';
|
|
@@ -71,7 +71,7 @@ const Overlays = memo(({ overlaysActorRef }) => {
|
|
|
71
71
|
xyflowDomNode
|
|
72
72
|
]);
|
|
73
73
|
const overlays = useSelector(overlaysActorRef, selectOverlays, compareSelectOverlays);
|
|
74
|
-
const isMotionReduced =
|
|
74
|
+
const isMotionReduced = useReducedMotionConfig() ?? false;
|
|
75
75
|
const isActiveOverlay = overlays.length > 0;
|
|
76
76
|
useEffect(() => {
|
|
77
77
|
if (!xyflowRendererDom || isMotionReduced) return;
|
|
@@ -2,12 +2,11 @@ import { css } from '@likec4/styles/css';
|
|
|
2
2
|
|
|
3
3
|
const backdropBlur = "--_blur";
|
|
4
4
|
const backdropOpacity = "--_opacity";
|
|
5
|
-
const borderRadius = "
|
|
5
|
+
const borderRadius = "--_border-radius";
|
|
6
6
|
const dialog = css({
|
|
7
7
|
boxSizing: "border-box",
|
|
8
8
|
margin: 0,
|
|
9
9
|
position: "fixed",
|
|
10
|
-
inset: "3rem",
|
|
11
10
|
width: "auto",
|
|
12
11
|
height: "auto",
|
|
13
12
|
maxWidth: "100vw",
|
|
@@ -16,8 +15,7 @@ const dialog = css({
|
|
|
16
15
|
shadow: "xl",
|
|
17
16
|
border: "transparent",
|
|
18
17
|
outline: "none",
|
|
19
|
-
borderRadius
|
|
20
|
-
padding: "6px",
|
|
18
|
+
borderRadius: `var(${borderRadius})`,
|
|
21
19
|
[backdropBlur]: "0px",
|
|
22
20
|
[backdropOpacity]: "0%",
|
|
23
21
|
_backdrop: {
|
|
@@ -28,18 +26,37 @@ const dialog = css({
|
|
|
28
26
|
_light: `[rgb(15 15 15/ var(${backdropOpacity}))]`
|
|
29
27
|
}
|
|
30
28
|
},
|
|
31
|
-
mdDown: {
|
|
32
|
-
borderRadius: "sm",
|
|
33
|
-
inset: "2rem",
|
|
34
|
-
width: "calc(100vw - 4rem)",
|
|
35
|
-
height: "calc(100vh - 4rem)"
|
|
36
|
-
},
|
|
37
29
|
smDown: {
|
|
30
|
+
[borderRadius]: "0px",
|
|
38
31
|
border: "none",
|
|
39
32
|
inset: 0,
|
|
40
33
|
padding: 0,
|
|
41
34
|
width: "100vw",
|
|
42
35
|
height: "100vh"
|
|
36
|
+
},
|
|
37
|
+
sm: {
|
|
38
|
+
[borderRadius]: "6px",
|
|
39
|
+
padding: "6px",
|
|
40
|
+
inset: "1rem",
|
|
41
|
+
width: "calc(100vw - 2rem)",
|
|
42
|
+
height: "calc(100vh - 2rem)"
|
|
43
|
+
},
|
|
44
|
+
md: {
|
|
45
|
+
inset: "2rem",
|
|
46
|
+
width: "calc(100vw - 4rem)",
|
|
47
|
+
height: "calc(100vh - 4rem)"
|
|
48
|
+
},
|
|
49
|
+
lg: {
|
|
50
|
+
inset: "4rem",
|
|
51
|
+
width: "calc(100vw - 8rem)",
|
|
52
|
+
height: "calc(100vh - 8rem)"
|
|
53
|
+
},
|
|
54
|
+
xl: {
|
|
55
|
+
[borderRadius]: "8px",
|
|
56
|
+
padding: "8px",
|
|
57
|
+
inset: "5rem",
|
|
58
|
+
width: "calc(100vw - 10rem)",
|
|
59
|
+
height: "calc(100vh - 10rem)"
|
|
43
60
|
}
|
|
44
61
|
});
|
|
45
62
|
const body = css({
|
|
@@ -47,11 +64,13 @@ const body = css({
|
|
|
47
64
|
containerName: "overlay-dialog",
|
|
48
65
|
containerType: "size",
|
|
49
66
|
border: `0 solid transparent`,
|
|
50
|
-
borderRadius: `calc(${borderRadius} - 2px)`,
|
|
51
67
|
background: "mantine.colors.body",
|
|
52
68
|
overflow: "hidden",
|
|
53
69
|
width: "100%",
|
|
54
|
-
height: "100%"
|
|
70
|
+
height: "100%",
|
|
71
|
+
sm: {
|
|
72
|
+
borderRadius: `calc(var(${borderRadius}) - 2px)`
|
|
73
|
+
}
|
|
55
74
|
});
|
|
56
75
|
|
|
57
76
|
export { backdropBlur, backdropOpacity, body, dialog };
|
|
@@ -3,7 +3,7 @@ import { cx } from '@likec4/styles/css';
|
|
|
3
3
|
import { RemoveScroll, Box } from '@mantine/core';
|
|
4
4
|
import { useMergedRef } from '@mantine/hooks';
|
|
5
5
|
import { useSyncedRef, useDebouncedCallback, useTimeoutEffect } from '@react-hookz/web';
|
|
6
|
-
import { m } from 'framer-motion';
|
|
6
|
+
import { useReducedMotionConfig, m } from 'framer-motion';
|
|
7
7
|
import { forwardRef, useState, useRef, useLayoutEffect } from 'react';
|
|
8
8
|
import { body, backdropOpacity, backdropBlur, dialog } from './Overlay.css.js';
|
|
9
9
|
import { stopPropagation } from '../../utils/xyflow.js';
|
|
@@ -13,6 +13,7 @@ const Overlay = forwardRef(
|
|
|
13
13
|
const [opened, setOpened] = useState(false);
|
|
14
14
|
const dialogRef = useRef(null);
|
|
15
15
|
const isClosingRef = useRef(false);
|
|
16
|
+
const motionNotReduced = useReducedMotionConfig() !== true;
|
|
16
17
|
const onCloseRef = useSyncedRef(onClose);
|
|
17
18
|
const close = useDebouncedCallback(
|
|
18
19
|
() => {
|
|
@@ -46,33 +47,35 @@ const Overlay = forwardRef(
|
|
|
46
47
|
{
|
|
47
48
|
ref: useMergedRef(dialogRef, ref),
|
|
48
49
|
className: cx(RemoveScroll.classNames.fullWidth, dialog, classes?.dialog, className),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
layout: true,
|
|
51
|
+
...motionNotReduced ? {
|
|
52
|
+
initial: {
|
|
53
|
+
[backdropBlur]: "0px",
|
|
54
|
+
[backdropOpacity]: "0%",
|
|
55
|
+
scale: 1.075,
|
|
56
|
+
opacity: 0
|
|
57
|
+
},
|
|
58
|
+
animate: {
|
|
59
|
+
[backdropBlur]: "8px",
|
|
60
|
+
[backdropOpacity]: "60%",
|
|
61
|
+
scale: 1,
|
|
62
|
+
opacity: 1,
|
|
63
|
+
transition: {
|
|
64
|
+
delay: 0.075
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
exit: {
|
|
68
|
+
scale: 1.3,
|
|
69
|
+
opacity: 0,
|
|
70
|
+
translateY: -10,
|
|
71
|
+
[backdropBlur]: "0px",
|
|
72
|
+
[backdropOpacity]: "0%"
|
|
73
|
+
}
|
|
74
|
+
} : {
|
|
75
|
+
initial: {
|
|
76
|
+
[backdropBlur]: "8px",
|
|
77
|
+
[backdropOpacity]: "60%"
|
|
66
78
|
}
|
|
67
|
-
},
|
|
68
|
-
exit: {
|
|
69
|
-
scale: 1.3,
|
|
70
|
-
opacity: 0,
|
|
71
|
-
[backdropBlur]: "0px",
|
|
72
|
-
[backdropOpacity]: "0%"
|
|
73
|
-
// transition: {
|
|
74
|
-
// duration: 0.2,
|
|
75
|
-
// },
|
|
76
79
|
},
|
|
77
80
|
onClick: (e) => {
|
|
78
81
|
e.stopPropagation();
|
|
@@ -50,7 +50,7 @@ const graphId = (node) => ({
|
|
|
50
50
|
function nodeData(element, ctx) {
|
|
51
51
|
let diagramNode = ctx.diagramNodes.get(element.id);
|
|
52
52
|
const ancestor = diagramNode ?? pipe(
|
|
53
|
-
element.ancestors()
|
|
53
|
+
[...element.ancestors()],
|
|
54
54
|
map((ancestor2) => ctx.diagramNodes.get(ancestor2.id)),
|
|
55
55
|
filter(isTruthy),
|
|
56
56
|
first()
|