@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.
@@ -0,0 +1,237 @@
1
+ import { getPathLayout, setPathComponentLayout, updatePathLayout, } from "@zoneflow/core";
2
+ import { roundCoordinate, snapCoordinate, } from "./moveEditorShared";
3
+ export function resolvePathNodeLayoutComponentId(layoutModel, pathId) {
4
+ const componentLayoutsById = getPathLayout(layoutModel, pathId)?.componentLayoutsById;
5
+ if (componentLayoutsById?.body)
6
+ return "body";
7
+ if (componentLayoutsById?.label)
8
+ return "label";
9
+ return null;
10
+ }
11
+ export function resolvePathMoveOriginSnapshot(params) {
12
+ const { layoutModel, pathId, frame } = params;
13
+ const componentId = resolvePathNodeLayoutComponentId(layoutModel, pathId);
14
+ const pathLayout = getPathLayout(layoutModel, pathId);
15
+ const pathVisual = frame?.pipeline.graphLayout.pathsById[pathId];
16
+ if (componentId) {
17
+ const componentRect = pathLayout?.componentLayoutsById?.[componentId];
18
+ if (componentRect) {
19
+ return {
20
+ x: componentRect.x,
21
+ y: componentRect.y,
22
+ width: componentRect.width ?? pathVisual?.rect?.width ?? 0,
23
+ height: componentRect.height ?? pathVisual?.rect?.height ?? 0,
24
+ componentId,
25
+ coordinateSpace: "component-layout",
26
+ };
27
+ }
28
+ }
29
+ const routeOffset = pathLayout?.routeOffset;
30
+ return {
31
+ x: routeOffset?.x ?? 0,
32
+ y: routeOffset?.y ?? 0,
33
+ width: pathVisual?.rect?.width ?? 0,
34
+ height: pathVisual?.rect?.height ?? 0,
35
+ componentId: null,
36
+ coordinateSpace: "route-offset",
37
+ };
38
+ }
39
+ export function resolvePathNodeSnapshot(params) {
40
+ const { frame, layoutModel, pathId } = params;
41
+ const pathVisual = frame.pipeline.graphLayout.pathsById[pathId];
42
+ if (!pathVisual?.rect)
43
+ return undefined;
44
+ const origin = resolvePathMoveOriginSnapshot({
45
+ frame,
46
+ layoutModel,
47
+ pathId,
48
+ });
49
+ return {
50
+ pathId,
51
+ origin,
52
+ rect: origin.coordinateSpace === "component-layout"
53
+ ? {
54
+ x: origin.x,
55
+ y: origin.y,
56
+ width: origin.width,
57
+ height: origin.height,
58
+ }
59
+ : pathVisual.rect,
60
+ };
61
+ }
62
+ export function applyPathMovePosition(params) {
63
+ const { layoutModel, pathId, origin, x, y } = params;
64
+ if (origin.coordinateSpace === "route-offset") {
65
+ return updatePathLayout(layoutModel, pathId, {
66
+ routeOffset: {
67
+ x,
68
+ y,
69
+ },
70
+ });
71
+ }
72
+ return setPathComponentLayout(layoutModel, pathId, origin.componentId ?? "body", {
73
+ x,
74
+ y,
75
+ width: origin.width,
76
+ height: origin.height,
77
+ });
78
+ }
79
+ export function applyPathNodeSnapshotRect(params) {
80
+ const { layoutModel, snapshot, rect } = params;
81
+ if (snapshot.origin.coordinateSpace === "route-offset" &&
82
+ rect.width === snapshot.rect.width &&
83
+ rect.height === snapshot.rect.height) {
84
+ const deltaX = rect.x - snapshot.rect.x;
85
+ const deltaY = rect.y - snapshot.rect.y;
86
+ return updatePathLayout(layoutModel, snapshot.pathId, {
87
+ routeOffset: {
88
+ x: roundCoordinate(snapshot.origin.x + deltaX),
89
+ y: roundCoordinate(snapshot.origin.y + deltaY),
90
+ },
91
+ });
92
+ }
93
+ return setPathComponentLayout(layoutModel, snapshot.pathId, snapshot.origin.componentId ?? "body", {
94
+ x: roundCoordinate(rect.x),
95
+ y: roundCoordinate(rect.y),
96
+ width: roundCoordinate(rect.width),
97
+ height: roundCoordinate(rect.height),
98
+ });
99
+ }
100
+ export function resolveGroupPathDragOrigin(params) {
101
+ const { frame, layoutModel, pathIds, primaryPathId } = params;
102
+ const originsByPathId = {};
103
+ for (const pathId of pathIds) {
104
+ const resolved = resolvePathNodeSnapshot({
105
+ frame,
106
+ layoutModel,
107
+ pathId,
108
+ });
109
+ if (!resolved)
110
+ continue;
111
+ originsByPathId[pathId] = resolved.origin;
112
+ }
113
+ if (!originsByPathId[primaryPathId]) {
114
+ return undefined;
115
+ }
116
+ return {
117
+ kind: "path-group",
118
+ primaryPathId,
119
+ originsByPathId,
120
+ };
121
+ }
122
+ export function alignPathsByMode(params) {
123
+ const { frame, layoutModel, pathIds, mode, gridSnap } = params;
124
+ const entries = pathIds
125
+ .map((pathId) => resolvePathNodeSnapshot({
126
+ frame,
127
+ layoutModel,
128
+ pathId,
129
+ }))
130
+ .filter((entry) => Boolean(entry));
131
+ if (entries.length < 2)
132
+ return layoutModel;
133
+ const reference = mode === "left"
134
+ ? Math.min(...entries.map((entry) => entry.rect.x))
135
+ : mode === "right"
136
+ ? Math.max(...entries.map((entry) => entry.rect.x + entry.rect.width))
137
+ : mode === "top"
138
+ ? Math.min(...entries.map((entry) => entry.rect.y))
139
+ : mode === "bottom"
140
+ ? Math.max(...entries.map((entry) => entry.rect.y + entry.rect.height))
141
+ : mode === "center-horizontal"
142
+ ? entries.reduce((sum, entry) => sum + entry.rect.x + entry.rect.width / 2, 0) / entries.length
143
+ : entries.reduce((sum, entry) => sum + entry.rect.y + entry.rect.height / 2, 0) / entries.length;
144
+ const snappedReference = snapCoordinate(reference, gridSnap);
145
+ let nextLayoutModel = layoutModel;
146
+ for (const entry of entries) {
147
+ nextLayoutModel = applyPathNodeSnapshotRect({
148
+ layoutModel: nextLayoutModel,
149
+ snapshot: entry,
150
+ rect: {
151
+ x: mode === "left"
152
+ ? snappedReference
153
+ : mode === "right"
154
+ ? snapCoordinate(snappedReference - entry.rect.width, gridSnap)
155
+ : mode === "center-horizontal"
156
+ ? snapCoordinate(snappedReference - entry.rect.width / 2, gridSnap)
157
+ : entry.rect.x,
158
+ y: mode === "top"
159
+ ? snappedReference
160
+ : mode === "bottom"
161
+ ? snapCoordinate(snappedReference - entry.rect.height, gridSnap)
162
+ : mode === "center-vertical"
163
+ ? snapCoordinate(snappedReference - entry.rect.height / 2, gridSnap)
164
+ : entry.rect.y,
165
+ width: entry.rect.width,
166
+ height: entry.rect.height,
167
+ },
168
+ });
169
+ }
170
+ return nextLayoutModel;
171
+ }
172
+ export function distributePathsByMode(params) {
173
+ const { frame, layoutModel, pathIds, mode, gridSnap } = params;
174
+ const axis = mode === "horizontal" ? "x" : "y";
175
+ const sizeKey = mode === "horizontal" ? "width" : "height";
176
+ const entries = pathIds
177
+ .map((pathId) => resolvePathNodeSnapshot({
178
+ frame,
179
+ layoutModel,
180
+ pathId,
181
+ }))
182
+ .filter((entry) => Boolean(entry))
183
+ .sort((a, b) => a.rect[axis] - b.rect[axis]);
184
+ if (entries.length < 3)
185
+ return layoutModel;
186
+ const first = entries[0];
187
+ const last = entries[entries.length - 1];
188
+ const span = last.rect[axis] + last.rect[sizeKey] - first.rect[axis];
189
+ const occupied = entries.reduce((sum, entry) => sum + entry.rect[sizeKey], 0);
190
+ const gap = (span - occupied) / (entries.length - 1);
191
+ let cursor = first.rect[axis] + first.rect[sizeKey] + gap;
192
+ let nextLayoutModel = layoutModel;
193
+ for (const entry of entries.slice(1, -1)) {
194
+ const snapped = snapCoordinate(cursor, gridSnap);
195
+ nextLayoutModel = applyPathNodeSnapshotRect({
196
+ layoutModel: nextLayoutModel,
197
+ snapshot: entry,
198
+ rect: {
199
+ x: mode === "horizontal" ? snapped : entry.rect.x,
200
+ y: mode === "vertical" ? snapped : entry.rect.y,
201
+ width: entry.rect.width,
202
+ height: entry.rect.height,
203
+ },
204
+ });
205
+ cursor += entry.rect[sizeKey] + gap;
206
+ }
207
+ return nextLayoutModel;
208
+ }
209
+ export function resolvePathResizeOrigin(params) {
210
+ const { frame, layoutModel, pathId } = params;
211
+ const snapshot = resolvePathNodeSnapshot({
212
+ frame,
213
+ layoutModel,
214
+ pathId,
215
+ });
216
+ if (!snapshot)
217
+ return undefined;
218
+ return {
219
+ pathId,
220
+ componentId: snapshot.origin.componentId ?? "body",
221
+ originX: snapshot.rect.x,
222
+ originY: snapshot.rect.y,
223
+ originWidth: snapshot.rect.width,
224
+ originHeight: snapshot.rect.height,
225
+ };
226
+ }
227
+ export function resizePathNodeByScreenDelta(params) {
228
+ const { layoutModel, camera, origin, deltaX, deltaY, minWidth = 140, minHeight = 32, gridSnap, } = params;
229
+ const nextWidth = Math.max(minWidth, snapCoordinate(origin.originWidth + deltaX / camera.zoom, gridSnap));
230
+ const nextHeight = Math.max(minHeight, snapCoordinate(origin.originHeight + deltaY / camera.zoom, gridSnap));
231
+ return setPathComponentLayout(layoutModel, origin.pathId, origin.componentId, {
232
+ x: origin.originX,
233
+ y: origin.originY,
234
+ width: nextWidth,
235
+ height: nextHeight,
236
+ });
237
+ }
@@ -0,0 +1,35 @@
1
+ import { type Point, type UniverseLayoutModel, type UniverseModel, type ZoneId } from "@zoneflow/core";
2
+ import type { Rect } from "@zoneflow/renderer-dom";
3
+ export declare const ROOT_WORLD_ORIGIN: Point;
4
+ export declare function resolveWorldZoneOrigin(params: {
5
+ model: UniverseModel;
6
+ layoutModel: UniverseLayoutModel;
7
+ zoneId: ZoneId;
8
+ cache?: Map<ZoneId, Point>;
9
+ }): Point;
10
+ export declare function resolveWorldZoneRect(params: {
11
+ model: UniverseModel;
12
+ layoutModel: UniverseLayoutModel;
13
+ zoneId: ZoneId;
14
+ cache?: Map<ZoneId, Point>;
15
+ }): Rect | undefined;
16
+ export declare function resolveTopSelectedZoneId(params: {
17
+ model: UniverseModel;
18
+ selectedZoneIds: Set<ZoneId>;
19
+ zoneId: ZoneId;
20
+ }): ZoneId;
21
+ export declare function resolveZoneGroupOrigins(params: {
22
+ model: UniverseModel;
23
+ layoutModel: UniverseLayoutModel;
24
+ zoneIds: ZoneId[];
25
+ primaryZoneId: ZoneId;
26
+ }): {
27
+ primaryZoneId: ZoneId;
28
+ originsByZoneId: Record<ZoneId, Point>;
29
+ } | undefined;
30
+ export declare function applyZoneOriginsDelta(params: {
31
+ layoutModel: UniverseLayoutModel;
32
+ originsByZoneId: Record<ZoneId, Point>;
33
+ deltaX: number;
34
+ deltaY: number;
35
+ }): UniverseLayoutModel;
@@ -0,0 +1,106 @@
1
+ import { getZoneLayout, updateZoneLayout, } from "@zoneflow/core";
2
+ import { roundCoordinate } from "./moveEditorShared";
3
+ export const ROOT_WORLD_ORIGIN = { x: 0, y: 0 };
4
+ export function resolveWorldZoneOrigin(params) {
5
+ const { model, layoutModel, zoneId, cache = new Map(), } = params;
6
+ const cached = cache.get(zoneId);
7
+ if (cached) {
8
+ return cached;
9
+ }
10
+ const zone = model.zonesById[zoneId];
11
+ const layout = layoutModel.zoneLayoutsById[zoneId];
12
+ if (!zone || !layout) {
13
+ cache.set(zoneId, ROOT_WORLD_ORIGIN);
14
+ return ROOT_WORLD_ORIGIN;
15
+ }
16
+ if (!zone.parentZoneId) {
17
+ const point = {
18
+ x: layout.x,
19
+ y: layout.y,
20
+ };
21
+ cache.set(zoneId, point);
22
+ return point;
23
+ }
24
+ const parentOrigin = resolveWorldZoneOrigin({
25
+ model,
26
+ layoutModel,
27
+ zoneId: zone.parentZoneId,
28
+ cache,
29
+ });
30
+ const point = {
31
+ x: parentOrigin.x + layout.x,
32
+ y: parentOrigin.y + layout.y,
33
+ };
34
+ cache.set(zoneId, point);
35
+ return point;
36
+ }
37
+ export function resolveWorldZoneRect(params) {
38
+ const { model, layoutModel, zoneId, cache } = params;
39
+ const layout = getZoneLayout(layoutModel, zoneId);
40
+ if (!layout)
41
+ return undefined;
42
+ const origin = resolveWorldZoneOrigin({
43
+ model,
44
+ layoutModel,
45
+ zoneId,
46
+ cache,
47
+ });
48
+ return {
49
+ x: origin.x,
50
+ y: origin.y,
51
+ width: layout.width ?? 0,
52
+ height: layout.height ?? 0,
53
+ };
54
+ }
55
+ export function resolveTopSelectedZoneId(params) {
56
+ const { model, selectedZoneIds, zoneId } = params;
57
+ let currentZoneId = zoneId;
58
+ let currentZone = model.zonesById[currentZoneId];
59
+ while (currentZone?.parentZoneId && selectedZoneIds.has(currentZone.parentZoneId)) {
60
+ currentZoneId = currentZone.parentZoneId;
61
+ currentZone = model.zonesById[currentZoneId];
62
+ }
63
+ return currentZoneId;
64
+ }
65
+ export function resolveZoneGroupOrigins(params) {
66
+ const { model, layoutModel, zoneIds, primaryZoneId } = params;
67
+ const selectedZoneIds = new Set(zoneIds);
68
+ const effectiveZoneIds = zoneIds.filter((zoneId) => resolveTopSelectedZoneId({
69
+ model,
70
+ selectedZoneIds,
71
+ zoneId,
72
+ }) === zoneId);
73
+ const effectivePrimaryZoneId = resolveTopSelectedZoneId({
74
+ model,
75
+ selectedZoneIds,
76
+ zoneId: primaryZoneId,
77
+ });
78
+ const originsByZoneId = {};
79
+ for (const zoneId of effectiveZoneIds) {
80
+ const zoneLayout = getZoneLayout(layoutModel, zoneId);
81
+ if (!zoneLayout)
82
+ continue;
83
+ originsByZoneId[zoneId] = {
84
+ x: zoneLayout.x,
85
+ y: zoneLayout.y,
86
+ };
87
+ }
88
+ if (!originsByZoneId[effectivePrimaryZoneId]) {
89
+ return undefined;
90
+ }
91
+ return {
92
+ primaryZoneId: effectivePrimaryZoneId,
93
+ originsByZoneId,
94
+ };
95
+ }
96
+ export function applyZoneOriginsDelta(params) {
97
+ const { layoutModel, originsByZoneId, deltaX, deltaY } = params;
98
+ let nextLayoutModel = layoutModel;
99
+ for (const [zoneId, point] of Object.entries(originsByZoneId)) {
100
+ nextLayoutModel = updateZoneLayout(nextLayoutModel, zoneId, {
101
+ x: roundCoordinate(point.x + deltaX),
102
+ y: roundCoordinate(point.y + deltaY),
103
+ });
104
+ }
105
+ return nextLayoutModel;
106
+ }
@@ -0,0 +1,50 @@
1
+ import { type UniverseLayoutModel, type UniverseModel, type ZoneId } from "@zoneflow/core";
2
+ import type { CameraState, RendererFrame } from "@zoneflow/renderer-dom";
3
+ import { type GridSnapOptions, type MoveEditorDragOrigin, type MoveEditorTarget, type MoveEditorTargetOptions, type ZoneAlignMode, type ZoneDistributeMode, type ZoneResizeOrigin } from "./moveEditorShared";
4
+ export type { GridSnapOptions, MoveEditorDragOrigin, MoveEditorTarget, MoveEditorTargetOptions, PathAlignMode, PathDistributeMode, PathResizeOrigin, ZoneAlignMode, ZoneDistributeMode, ZoneResizeOrigin, } from "./moveEditorShared";
5
+ export { alignPathsByMode, distributePathsByMode, resolveGroupPathDragOrigin, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./pathMoveEditor";
6
+ export { commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPosition, reparentZoneAtCurrentPosition, resolveZonePlacementAtWorldRect, resolveZoneReparentCandidate, } from "./zoneReparent";
7
+ export declare function getMoveEditorTargets(params: {
8
+ model: UniverseModel;
9
+ frame: RendererFrame;
10
+ camera: CameraState;
11
+ options?: MoveEditorTargetOptions;
12
+ }): MoveEditorTarget[];
13
+ export declare function resolveMoveEditorDragOrigin(layoutModel: UniverseLayoutModel, target: MoveEditorTarget, frame?: RendererFrame): MoveEditorDragOrigin | undefined;
14
+ export declare function resolveGroupZoneDragOrigin(params: {
15
+ model: UniverseModel;
16
+ layoutModel: UniverseLayoutModel;
17
+ zoneIds: ZoneId[];
18
+ primaryZoneId: ZoneId;
19
+ }): MoveEditorDragOrigin | undefined;
20
+ export declare function moveEditorTargetByScreenDelta(params: {
21
+ layoutModel: UniverseLayoutModel;
22
+ camera: CameraState;
23
+ origin: MoveEditorDragOrigin;
24
+ deltaX: number;
25
+ deltaY: number;
26
+ gridSnap?: GridSnapOptions;
27
+ }): UniverseLayoutModel;
28
+ export declare function resolveZoneResizeOrigin(layoutModel: UniverseLayoutModel, zoneId: ZoneId): ZoneResizeOrigin | undefined;
29
+ export declare function resizeZoneByScreenDelta(params: {
30
+ layoutModel: UniverseLayoutModel;
31
+ camera: CameraState;
32
+ origin: ZoneResizeOrigin;
33
+ deltaX: number;
34
+ deltaY: number;
35
+ minWidth?: number;
36
+ minHeight?: number;
37
+ gridSnap?: GridSnapOptions;
38
+ }): UniverseLayoutModel;
39
+ export declare function alignZonesByMode(params: {
40
+ layoutModel: UniverseLayoutModel;
41
+ zoneIds: ZoneId[];
42
+ mode: ZoneAlignMode;
43
+ gridSnap?: GridSnapOptions;
44
+ }): UniverseLayoutModel;
45
+ export declare function distributeZonesByMode(params: {
46
+ layoutModel: UniverseLayoutModel;
47
+ zoneIds: ZoneId[];
48
+ mode: ZoneDistributeMode;
49
+ gridSnap?: GridSnapOptions;
50
+ }): UniverseLayoutModel;