@zoneflow/editor-dom 0.0.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/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/moveEditorShared.d.ts +87 -0
- package/dist/moveEditorShared.js +48 -0
- package/dist/pathCreateEditor.d.ts +42 -0
- package/dist/pathCreateEditor.js +220 -0
- package/dist/pathMoveEditor.d.ts +66 -0
- package/dist/pathMoveEditor.js +237 -0
- package/dist/zoneGeometry.d.ts +35 -0
- package/dist/zoneGeometry.js +106 -0
- package/dist/zoneMoveEditor.d.ts +50 -0
- package/dist/zoneMoveEditor.js +300 -0
- package/dist/zoneReparent.d.ts +46 -0
- package/dist/zoneReparent.js +173 -0
- package/package.json +24 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from "./zoneMoveEditor";
|
|
2
|
+
export * from "./pathCreateEditor";
|
|
3
|
+
export { alignPathsByMode, alignZonesByMode, commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPosition, distributePathsByMode, distributeZonesByMode, resolveGroupPathDragOrigin, resolveGroupZoneDragOrigin, resolveZonePlacementAtWorldRect, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./zoneMoveEditor";
|
|
4
|
+
export type { PathResizeOrigin } from "./zoneMoveEditor";
|
|
5
|
+
export { resolvePathOutputAnchorScreenRect, retargetPathFromOutputAnchorDrag, } from "./pathCreateEditor";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from "./zoneMoveEditor";
|
|
2
|
+
export * from "./pathCreateEditor";
|
|
3
|
+
export { alignPathsByMode, alignZonesByMode, commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPosition, distributePathsByMode, distributeZonesByMode, resolveGroupPathDragOrigin, resolveGroupZoneDragOrigin, resolveZonePlacementAtWorldRect, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./zoneMoveEditor";
|
|
4
|
+
export { resolvePathOutputAnchorScreenRect, retargetPathFromOutputAnchorDrag, } from "./pathCreateEditor";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { PathId, Point, ZoneId } from "@zoneflow/core";
|
|
2
|
+
import type { CameraState, Rect } from "@zoneflow/renderer-dom";
|
|
3
|
+
export type MoveEditorTarget = {
|
|
4
|
+
key: string;
|
|
5
|
+
kind: "zone";
|
|
6
|
+
zoneId: ZoneId;
|
|
7
|
+
label: string;
|
|
8
|
+
rect: Rect;
|
|
9
|
+
} | {
|
|
10
|
+
key: string;
|
|
11
|
+
kind: "path";
|
|
12
|
+
pathId: PathId;
|
|
13
|
+
label: string;
|
|
14
|
+
rect: Rect;
|
|
15
|
+
};
|
|
16
|
+
export type MoveEditorTargetOptions = {
|
|
17
|
+
includeRoot?: boolean;
|
|
18
|
+
minVisibleSize?: number;
|
|
19
|
+
};
|
|
20
|
+
export type PathMoveCoordinateSpace = "route-offset" | "component-layout";
|
|
21
|
+
export type PathMoveOriginSnapshot = {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
componentId: "body" | "label" | null;
|
|
27
|
+
coordinateSpace: PathMoveCoordinateSpace;
|
|
28
|
+
};
|
|
29
|
+
export type MoveEditorDragOrigin = {
|
|
30
|
+
kind: "zone";
|
|
31
|
+
zoneId: ZoneId;
|
|
32
|
+
originX: number;
|
|
33
|
+
originY: number;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "zone-group";
|
|
36
|
+
primaryZoneId: ZoneId;
|
|
37
|
+
originsByZoneId: Record<ZoneId, Point>;
|
|
38
|
+
} | {
|
|
39
|
+
kind: "path";
|
|
40
|
+
pathId: PathId;
|
|
41
|
+
origin: PathMoveOriginSnapshot;
|
|
42
|
+
} | {
|
|
43
|
+
kind: "path-group";
|
|
44
|
+
primaryPathId: PathId;
|
|
45
|
+
originsByPathId: Record<PathId, PathMoveOriginSnapshot>;
|
|
46
|
+
};
|
|
47
|
+
export type ZoneResizeOrigin = {
|
|
48
|
+
zoneId: ZoneId;
|
|
49
|
+
originWidth: number;
|
|
50
|
+
originHeight: number;
|
|
51
|
+
};
|
|
52
|
+
export type PathResizeOrigin = {
|
|
53
|
+
pathId: PathId;
|
|
54
|
+
componentId: "body" | "label";
|
|
55
|
+
originX: number;
|
|
56
|
+
originY: number;
|
|
57
|
+
originWidth: number;
|
|
58
|
+
originHeight: number;
|
|
59
|
+
};
|
|
60
|
+
export type GridSnapOptions = {
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
size?: number;
|
|
63
|
+
};
|
|
64
|
+
export type ZoneAlignMode = "left" | "right" | "top" | "bottom" | "center-horizontal" | "center-vertical";
|
|
65
|
+
export type ZoneDistributeMode = "horizontal" | "vertical";
|
|
66
|
+
export type PathAlignMode = ZoneAlignMode;
|
|
67
|
+
export type PathDistributeMode = ZoneDistributeMode;
|
|
68
|
+
export declare function typedValues<TKey extends string, TValue>(record: Record<TKey, TValue>): TValue[];
|
|
69
|
+
export declare function projectWorldRectToScreenRect(rect: Rect, camera: CameraState): Rect;
|
|
70
|
+
export declare function roundCoordinate(value: number): number;
|
|
71
|
+
export declare function resolveSnapSize(options?: GridSnapOptions): number | null;
|
|
72
|
+
export declare function snapCoordinate(value: number, options?: GridSnapOptions): number;
|
|
73
|
+
export declare function resolveSnappedMove(params: {
|
|
74
|
+
originX: number;
|
|
75
|
+
originY: number;
|
|
76
|
+
deltaX: number;
|
|
77
|
+
deltaY: number;
|
|
78
|
+
camera: CameraState;
|
|
79
|
+
gridSnap?: GridSnapOptions;
|
|
80
|
+
}): {
|
|
81
|
+
nextX: number;
|
|
82
|
+
nextY: number;
|
|
83
|
+
effectiveDeltaX: number;
|
|
84
|
+
effectiveDeltaY: number;
|
|
85
|
+
};
|
|
86
|
+
export declare function containsPoint(rect: Rect, point: Point): boolean;
|
|
87
|
+
export declare function getRectArea(rect: Rect): number;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function typedValues(record) {
|
|
2
|
+
return Object.values(record);
|
|
3
|
+
}
|
|
4
|
+
export function projectWorldRectToScreenRect(rect, camera) {
|
|
5
|
+
return {
|
|
6
|
+
x: camera.x + rect.x * camera.zoom,
|
|
7
|
+
y: camera.y + rect.y * camera.zoom,
|
|
8
|
+
width: rect.width * camera.zoom,
|
|
9
|
+
height: rect.height * camera.zoom,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function roundCoordinate(value) {
|
|
13
|
+
return Math.round(value * 100) / 100;
|
|
14
|
+
}
|
|
15
|
+
export function resolveSnapSize(options) {
|
|
16
|
+
if (!options?.enabled)
|
|
17
|
+
return null;
|
|
18
|
+
const size = options.size ?? 16;
|
|
19
|
+
if (!Number.isFinite(size) || size <= 0)
|
|
20
|
+
return null;
|
|
21
|
+
return size;
|
|
22
|
+
}
|
|
23
|
+
export function snapCoordinate(value, options) {
|
|
24
|
+
const size = resolveSnapSize(options);
|
|
25
|
+
if (!size)
|
|
26
|
+
return roundCoordinate(value);
|
|
27
|
+
return roundCoordinate(Math.round(value / size) * size);
|
|
28
|
+
}
|
|
29
|
+
export function resolveSnappedMove(params) {
|
|
30
|
+
const { originX, originY, deltaX, deltaY, camera, gridSnap } = params;
|
|
31
|
+
const nextX = snapCoordinate(originX + deltaX / camera.zoom, gridSnap);
|
|
32
|
+
const nextY = snapCoordinate(originY + deltaY / camera.zoom, gridSnap);
|
|
33
|
+
return {
|
|
34
|
+
nextX,
|
|
35
|
+
nextY,
|
|
36
|
+
effectiveDeltaX: nextX - originX,
|
|
37
|
+
effectiveDeltaY: nextY - originY,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function containsPoint(rect, point) {
|
|
41
|
+
return (point.x >= rect.x &&
|
|
42
|
+
point.x <= rect.x + rect.width &&
|
|
43
|
+
point.y >= rect.y &&
|
|
44
|
+
point.y <= rect.y + rect.height);
|
|
45
|
+
}
|
|
46
|
+
export function getRectArea(rect) {
|
|
47
|
+
return rect.width * rect.height;
|
|
48
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type PathId, type Point, type UniverseLayoutModel, type UniverseModel, type ZoneId } from "@zoneflow/core";
|
|
2
|
+
import { type CameraState, type Rect, type RendererFrame } from "@zoneflow/renderer-dom";
|
|
3
|
+
import type { GridSnapOptions } from "./zoneMoveEditor";
|
|
4
|
+
export declare function screenPointToWorldPoint(point: Point, camera: CameraState): Point;
|
|
5
|
+
export declare function resolveZoneAnchorScreenRect(params: {
|
|
6
|
+
frame: RendererFrame;
|
|
7
|
+
camera: CameraState;
|
|
8
|
+
zoneId: ZoneId;
|
|
9
|
+
kind: "inlet" | "outlet";
|
|
10
|
+
}): Rect | undefined;
|
|
11
|
+
export declare function resolveInputAnchorTargetZoneId(params: {
|
|
12
|
+
model: UniverseModel;
|
|
13
|
+
frame: RendererFrame;
|
|
14
|
+
camera: CameraState;
|
|
15
|
+
point: Point;
|
|
16
|
+
excludeZoneIds?: ZoneId[];
|
|
17
|
+
}): ZoneId | null;
|
|
18
|
+
export declare function resolvePathOutputAnchorScreenRect(params: {
|
|
19
|
+
frame: RendererFrame;
|
|
20
|
+
camera: CameraState;
|
|
21
|
+
pathId: PathId;
|
|
22
|
+
}): Rect | undefined;
|
|
23
|
+
export type CreatePathFromAnchorDragResult = {
|
|
24
|
+
model: UniverseModel;
|
|
25
|
+
layoutModel: UniverseLayoutModel;
|
|
26
|
+
pathId: PathId;
|
|
27
|
+
};
|
|
28
|
+
export declare function retargetPathFromOutputAnchorDrag(params: {
|
|
29
|
+
model: UniverseModel;
|
|
30
|
+
sourceZoneId: ZoneId;
|
|
31
|
+
pathId: PathId;
|
|
32
|
+
targetZoneId?: ZoneId | null;
|
|
33
|
+
}): UniverseModel | undefined;
|
|
34
|
+
export declare function createPathFromOutputAnchorDrag(params: {
|
|
35
|
+
model: UniverseModel;
|
|
36
|
+
layoutModel: UniverseLayoutModel;
|
|
37
|
+
frame: RendererFrame;
|
|
38
|
+
sourceZoneId: ZoneId;
|
|
39
|
+
dropWorldPoint: Point;
|
|
40
|
+
targetZoneId?: ZoneId | null;
|
|
41
|
+
gridSnap?: GridSnapOptions;
|
|
42
|
+
}): CreatePathFromAnchorDragResult | undefined;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { addPath, createPathId, isZoneInputEnabled, isZoneOutputEnabled, setPathTarget, updatePathLayout, } from "@zoneflow/core";
|
|
2
|
+
function typedValues(record) {
|
|
3
|
+
return Object.values(record);
|
|
4
|
+
}
|
|
5
|
+
const DEFAULT_PATH_NODE_WIDTH = 120;
|
|
6
|
+
const DEFAULT_PATH_NODE_HEIGHT = 32;
|
|
7
|
+
const DEFAULT_PATH_NODE_OFFSET_X = 32;
|
|
8
|
+
const DEFAULT_PATH_NODE_GAP_Y = 40;
|
|
9
|
+
const DEFAULT_ANCHOR_WIDTH = 24;
|
|
10
|
+
const DEFAULT_ANCHOR_ATTACH_DEPTH = 10;
|
|
11
|
+
const DEFAULT_PATH_OUTPUT_HANDLE_WIDTH = 18;
|
|
12
|
+
const DEFAULT_PATH_OUTPUT_HANDLE_MIN_HEIGHT = 22;
|
|
13
|
+
const DEFAULT_PATH_OUTPUT_HANDLE_MAX_HEIGHT = 40;
|
|
14
|
+
const DEFAULT_PATH_OUTPUT_HANDLE_MARGIN_Y = 4;
|
|
15
|
+
const DEFAULT_PATH_OUTPUT_HANDLE_OVERHANG = 9;
|
|
16
|
+
function roundCoordinate(value) {
|
|
17
|
+
return Math.round(value * 100) / 100;
|
|
18
|
+
}
|
|
19
|
+
function resolveSnapSize(options) {
|
|
20
|
+
if (!options?.enabled)
|
|
21
|
+
return null;
|
|
22
|
+
const size = options.size ?? 16;
|
|
23
|
+
if (!Number.isFinite(size) || size <= 0)
|
|
24
|
+
return null;
|
|
25
|
+
return size;
|
|
26
|
+
}
|
|
27
|
+
function snapCoordinate(value, options) {
|
|
28
|
+
const size = resolveSnapSize(options);
|
|
29
|
+
if (!size)
|
|
30
|
+
return roundCoordinate(value);
|
|
31
|
+
return roundCoordinate(Math.round(value / size) * size);
|
|
32
|
+
}
|
|
33
|
+
function clamp(value, min, max) {
|
|
34
|
+
return Math.min(Math.max(value, min), max);
|
|
35
|
+
}
|
|
36
|
+
function midpoint(a, b) {
|
|
37
|
+
return {
|
|
38
|
+
x: roundCoordinate((a.x + b.x) / 2),
|
|
39
|
+
y: roundCoordinate((a.y + b.y) / 2),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function containsPoint(rect, point) {
|
|
43
|
+
return (point.x >= rect.x &&
|
|
44
|
+
point.x <= rect.x + rect.width &&
|
|
45
|
+
point.y >= rect.y &&
|
|
46
|
+
point.y <= rect.y + rect.height);
|
|
47
|
+
}
|
|
48
|
+
function getRectArea(rect) {
|
|
49
|
+
return rect.width * rect.height;
|
|
50
|
+
}
|
|
51
|
+
function projectWorldRectToScreenRect(rect, camera) {
|
|
52
|
+
return {
|
|
53
|
+
x: camera.x + rect.x * camera.zoom,
|
|
54
|
+
y: camera.y + rect.y * camera.zoom,
|
|
55
|
+
width: rect.width * camera.zoom,
|
|
56
|
+
height: rect.height * camera.zoom,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function resolveZoneAnchorRect(params) {
|
|
60
|
+
const { zoneRect, anchor, kind } = params;
|
|
61
|
+
if (anchor.rect) {
|
|
62
|
+
return {
|
|
63
|
+
x: anchor.rect.x,
|
|
64
|
+
y: anchor.rect.y,
|
|
65
|
+
width: anchor.rect.width ?? DEFAULT_ANCHOR_WIDTH,
|
|
66
|
+
height: anchor.rect.height ?? zoneRect.height,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
x: kind === "inlet"
|
|
71
|
+
? zoneRect.x - (DEFAULT_ANCHOR_WIDTH - DEFAULT_ANCHOR_ATTACH_DEPTH)
|
|
72
|
+
: zoneRect.x + zoneRect.width - DEFAULT_ANCHOR_ATTACH_DEPTH,
|
|
73
|
+
y: zoneRect.y,
|
|
74
|
+
width: DEFAULT_ANCHOR_WIDTH,
|
|
75
|
+
height: zoneRect.height,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function screenPointToWorldPoint(point, camera) {
|
|
79
|
+
return {
|
|
80
|
+
x: roundCoordinate((point.x - camera.x) / camera.zoom),
|
|
81
|
+
y: roundCoordinate((point.y - camera.y) / camera.zoom),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function resolveZoneAnchorScreenRect(params) {
|
|
85
|
+
const { frame, camera, zoneId, kind } = params;
|
|
86
|
+
const zoneVisual = frame.pipeline.graphLayout.zonesById[zoneId];
|
|
87
|
+
if (!zoneVisual)
|
|
88
|
+
return undefined;
|
|
89
|
+
if (kind === "inlet" && !isZoneInputEnabled(zoneVisual.zone))
|
|
90
|
+
return undefined;
|
|
91
|
+
if (kind === "outlet" && !isZoneOutputEnabled(zoneVisual.zone))
|
|
92
|
+
return undefined;
|
|
93
|
+
const anchorRect = resolveZoneAnchorRect({
|
|
94
|
+
zoneRect: zoneVisual.rect,
|
|
95
|
+
anchor: zoneVisual.anchors[kind],
|
|
96
|
+
kind,
|
|
97
|
+
});
|
|
98
|
+
return projectWorldRectToScreenRect(anchorRect, camera);
|
|
99
|
+
}
|
|
100
|
+
export function resolveInputAnchorTargetZoneId(params) {
|
|
101
|
+
const { model, frame, camera, point, excludeZoneIds } = params;
|
|
102
|
+
const excluded = new Set(excludeZoneIds ?? []);
|
|
103
|
+
let bestZoneId = null;
|
|
104
|
+
let bestArea = Number.POSITIVE_INFINITY;
|
|
105
|
+
for (const zoneVisual of typedValues(frame.pipeline.graphLayout.zonesById)) {
|
|
106
|
+
if (excluded.has(zoneVisual.zoneId))
|
|
107
|
+
continue;
|
|
108
|
+
if (!model.zonesById[zoneVisual.zoneId])
|
|
109
|
+
continue;
|
|
110
|
+
if (!isZoneInputEnabled(zoneVisual.zone))
|
|
111
|
+
continue;
|
|
112
|
+
const visibility = frame.pipeline.visibility.zoneVisibilityById[zoneVisual.zoneId];
|
|
113
|
+
if (!visibility?.isVisible)
|
|
114
|
+
continue;
|
|
115
|
+
const rect = resolveZoneAnchorScreenRect({
|
|
116
|
+
frame,
|
|
117
|
+
camera,
|
|
118
|
+
zoneId: zoneVisual.zoneId,
|
|
119
|
+
kind: "inlet",
|
|
120
|
+
});
|
|
121
|
+
if (!rect || !containsPoint(rect, point))
|
|
122
|
+
continue;
|
|
123
|
+
const area = getRectArea(rect);
|
|
124
|
+
if (area < bestArea) {
|
|
125
|
+
bestZoneId = zoneVisual.zoneId;
|
|
126
|
+
bestArea = area;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return bestZoneId;
|
|
130
|
+
}
|
|
131
|
+
export function resolvePathOutputAnchorScreenRect(params) {
|
|
132
|
+
const { frame, camera, pathId } = params;
|
|
133
|
+
const pathVisual = frame.pipeline.graphLayout.pathsById[pathId];
|
|
134
|
+
if (!pathVisual?.rect)
|
|
135
|
+
return undefined;
|
|
136
|
+
const outlet = pathVisual.outlet ?? {
|
|
137
|
+
x: pathVisual.rect.x + pathVisual.rect.width,
|
|
138
|
+
y: pathVisual.rect.y + pathVisual.rect.height / 2,
|
|
139
|
+
};
|
|
140
|
+
const height = clamp(pathVisual.rect.height * 0.72, DEFAULT_PATH_OUTPUT_HANDLE_MIN_HEIGHT, Math.min(DEFAULT_PATH_OUTPUT_HANDLE_MAX_HEIGHT, Math.max(DEFAULT_PATH_OUTPUT_HANDLE_MIN_HEIGHT, pathVisual.rect.height - DEFAULT_PATH_OUTPUT_HANDLE_MARGIN_Y * 2)));
|
|
141
|
+
const minY = pathVisual.rect.y + DEFAULT_PATH_OUTPUT_HANDLE_MARGIN_Y;
|
|
142
|
+
const maxY = pathVisual.rect.y +
|
|
143
|
+
pathVisual.rect.height -
|
|
144
|
+
DEFAULT_PATH_OUTPUT_HANDLE_MARGIN_Y -
|
|
145
|
+
height;
|
|
146
|
+
return projectWorldRectToScreenRect({
|
|
147
|
+
x: outlet.x -
|
|
148
|
+
(DEFAULT_PATH_OUTPUT_HANDLE_WIDTH - DEFAULT_PATH_OUTPUT_HANDLE_OVERHANG),
|
|
149
|
+
y: clamp(outlet.y - height / 2, minY, maxY),
|
|
150
|
+
width: DEFAULT_PATH_OUTPUT_HANDLE_WIDTH,
|
|
151
|
+
height,
|
|
152
|
+
}, camera);
|
|
153
|
+
}
|
|
154
|
+
export function retargetPathFromOutputAnchorDrag(params) {
|
|
155
|
+
const { model, sourceZoneId, pathId, targetZoneId, } = params;
|
|
156
|
+
const sourceZone = model.zonesById[sourceZoneId];
|
|
157
|
+
if (!sourceZone?.pathsById[pathId])
|
|
158
|
+
return undefined;
|
|
159
|
+
if (targetZoneId && !isZoneInputEnabled(model.zonesById[targetZoneId])) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
return setPathTarget(model, sourceZoneId, pathId, targetZoneId
|
|
163
|
+
? {
|
|
164
|
+
universeId: model.universeId,
|
|
165
|
+
zoneId: targetZoneId,
|
|
166
|
+
}
|
|
167
|
+
: null);
|
|
168
|
+
}
|
|
169
|
+
export function createPathFromOutputAnchorDrag(params) {
|
|
170
|
+
const { model, layoutModel, frame, sourceZoneId, dropWorldPoint, targetZoneId, gridSnap, } = params;
|
|
171
|
+
const sourceZone = model.zonesById[sourceZoneId];
|
|
172
|
+
const sourceVisual = frame.pipeline.graphLayout.zonesById[sourceZoneId];
|
|
173
|
+
const targetVisual = targetZoneId
|
|
174
|
+
? frame.pipeline.graphLayout.zonesById[targetZoneId]
|
|
175
|
+
: undefined;
|
|
176
|
+
if (!sourceZone || !sourceVisual)
|
|
177
|
+
return undefined;
|
|
178
|
+
if (!isZoneOutputEnabled(sourceZone))
|
|
179
|
+
return undefined;
|
|
180
|
+
if (targetZoneId && !isZoneInputEnabled(model.zonesById[targetZoneId])) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const pathId = createPathId();
|
|
184
|
+
const nextPathIndex = sourceZone.pathIds.length;
|
|
185
|
+
const sourceOutlet = sourceVisual.anchors.outlet.point;
|
|
186
|
+
const targetInlet = targetVisual?.anchors.inlet?.point;
|
|
187
|
+
const desiredCenter = targetInlet
|
|
188
|
+
? midpoint(sourceOutlet, targetInlet)
|
|
189
|
+
: dropWorldPoint;
|
|
190
|
+
const desiredRect = {
|
|
191
|
+
x: snapCoordinate(desiredCenter.x - DEFAULT_PATH_NODE_WIDTH / 2, gridSnap),
|
|
192
|
+
y: snapCoordinate(desiredCenter.y - DEFAULT_PATH_NODE_HEIGHT / 2, gridSnap),
|
|
193
|
+
};
|
|
194
|
+
const routeOffset = {
|
|
195
|
+
x: roundCoordinate(desiredRect.x - (sourceOutlet.x + DEFAULT_PATH_NODE_OFFSET_X)),
|
|
196
|
+
y: roundCoordinate(desiredRect.y -
|
|
197
|
+
(sourceOutlet.y - DEFAULT_PATH_NODE_HEIGHT / 2 + nextPathIndex * DEFAULT_PATH_NODE_GAP_Y)),
|
|
198
|
+
};
|
|
199
|
+
const ordinal = nextPathIndex + 1;
|
|
200
|
+
const nextModel = addPath(model, sourceZoneId, {
|
|
201
|
+
id: pathId,
|
|
202
|
+
key: `condition_${ordinal}`,
|
|
203
|
+
name: "Empty",
|
|
204
|
+
target: targetZoneId
|
|
205
|
+
? {
|
|
206
|
+
universeId: model.universeId,
|
|
207
|
+
zoneId: targetZoneId,
|
|
208
|
+
}
|
|
209
|
+
: null,
|
|
210
|
+
rule: null,
|
|
211
|
+
});
|
|
212
|
+
const nextLayoutModel = updatePathLayout(layoutModel, pathId, {
|
|
213
|
+
routeOffset,
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
model: nextModel,
|
|
217
|
+
layoutModel: nextLayoutModel,
|
|
218
|
+
pathId,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type PathId, type UniverseLayoutModel } from "@zoneflow/core";
|
|
2
|
+
import type { CameraState, Rect, RendererFrame } from "@zoneflow/renderer-dom";
|
|
3
|
+
import { type GridSnapOptions, type MoveEditorDragOrigin, type PathAlignMode, type PathDistributeMode, type PathMoveOriginSnapshot, type PathResizeOrigin } from "./moveEditorShared";
|
|
4
|
+
export type PathNodeSnapshot = {
|
|
5
|
+
pathId: PathId;
|
|
6
|
+
origin: PathMoveOriginSnapshot;
|
|
7
|
+
rect: Rect;
|
|
8
|
+
};
|
|
9
|
+
export declare function resolvePathNodeLayoutComponentId(layoutModel: UniverseLayoutModel, pathId: PathId): "body" | "label" | null;
|
|
10
|
+
export declare function resolvePathMoveOriginSnapshot(params: {
|
|
11
|
+
layoutModel: UniverseLayoutModel;
|
|
12
|
+
pathId: PathId;
|
|
13
|
+
frame?: RendererFrame;
|
|
14
|
+
}): PathMoveOriginSnapshot;
|
|
15
|
+
export declare function resolvePathNodeSnapshot(params: {
|
|
16
|
+
frame: RendererFrame;
|
|
17
|
+
layoutModel: UniverseLayoutModel;
|
|
18
|
+
pathId: PathId;
|
|
19
|
+
}): PathNodeSnapshot | undefined;
|
|
20
|
+
export declare function applyPathMovePosition(params: {
|
|
21
|
+
layoutModel: UniverseLayoutModel;
|
|
22
|
+
pathId: PathId;
|
|
23
|
+
origin: PathMoveOriginSnapshot;
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
}): UniverseLayoutModel;
|
|
27
|
+
export declare function applyPathNodeSnapshotRect(params: {
|
|
28
|
+
layoutModel: UniverseLayoutModel;
|
|
29
|
+
snapshot: PathNodeSnapshot;
|
|
30
|
+
rect: Rect;
|
|
31
|
+
}): UniverseLayoutModel;
|
|
32
|
+
export declare function resolveGroupPathDragOrigin(params: {
|
|
33
|
+
frame: RendererFrame;
|
|
34
|
+
layoutModel: UniverseLayoutModel;
|
|
35
|
+
pathIds: PathId[];
|
|
36
|
+
primaryPathId: PathId;
|
|
37
|
+
}): MoveEditorDragOrigin | undefined;
|
|
38
|
+
export declare function alignPathsByMode(params: {
|
|
39
|
+
frame: RendererFrame;
|
|
40
|
+
layoutModel: UniverseLayoutModel;
|
|
41
|
+
pathIds: PathId[];
|
|
42
|
+
mode: PathAlignMode;
|
|
43
|
+
gridSnap?: GridSnapOptions;
|
|
44
|
+
}): UniverseLayoutModel;
|
|
45
|
+
export declare function distributePathsByMode(params: {
|
|
46
|
+
frame: RendererFrame;
|
|
47
|
+
layoutModel: UniverseLayoutModel;
|
|
48
|
+
pathIds: PathId[];
|
|
49
|
+
mode: PathDistributeMode;
|
|
50
|
+
gridSnap?: GridSnapOptions;
|
|
51
|
+
}): UniverseLayoutModel;
|
|
52
|
+
export declare function resolvePathResizeOrigin(params: {
|
|
53
|
+
frame: RendererFrame;
|
|
54
|
+
layoutModel: UniverseLayoutModel;
|
|
55
|
+
pathId: PathId;
|
|
56
|
+
}): PathResizeOrigin | undefined;
|
|
57
|
+
export declare function resizePathNodeByScreenDelta(params: {
|
|
58
|
+
layoutModel: UniverseLayoutModel;
|
|
59
|
+
camera: CameraState;
|
|
60
|
+
origin: PathResizeOrigin;
|
|
61
|
+
deltaX: number;
|
|
62
|
+
deltaY: number;
|
|
63
|
+
minWidth?: number;
|
|
64
|
+
minHeight?: number;
|
|
65
|
+
gridSnap?: GridSnapOptions;
|
|
66
|
+
}): UniverseLayoutModel;
|