@zoneflow/editor-dom 0.0.5 → 0.0.7

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./zoneMoveEditor";
2
2
  export * from "./pathCreateEditor";
3
+ export * from "./zOrderEditor";
3
4
  export { alignPathsByMode, alignZonesByMode, commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPosition, distributePathsByMode, distributeZonesByMode, resolveGroupPathDragOrigin, resolveGroupZoneDragOrigin, resolveZonePlacementAtWorldRect, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./zoneMoveEditor";
4
5
  export type { PathResizeOrigin } from "./zoneMoveEditor";
5
6
  export { resolvePathOutputAnchorScreenRect, retargetPathFromOutputAnchorDrag, } from "./pathCreateEditor";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./zoneMoveEditor";
2
2
  export * from "./pathCreateEditor";
3
+ export * from "./zOrderEditor";
3
4
  export { alignPathsByMode, alignZonesByMode, commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPosition, distributePathsByMode, distributeZonesByMode, resolveGroupPathDragOrigin, resolveGroupZoneDragOrigin, resolveZonePlacementAtWorldRect, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./zoneMoveEditor";
4
5
  export { resolvePathOutputAnchorScreenRect, retargetPathFromOutputAnchorDrag, } from "./pathCreateEditor";
@@ -26,11 +26,23 @@ export type PathMoveOriginSnapshot = {
26
26
  componentId: "body" | "label" | null;
27
27
  coordinateSpace: PathMoveCoordinateSpace;
28
28
  };
29
+ export type ObjectSnapGuides = {
30
+ x: number[];
31
+ y: number[];
32
+ };
33
+ export type ObjectSnapAxisMatch = {
34
+ guide: number;
35
+ align: "start" | "center" | "end";
36
+ snappedStart: number;
37
+ };
29
38
  export type MoveEditorDragOrigin = {
30
39
  kind: "zone";
31
40
  zoneId: ZoneId;
32
41
  originX: number;
33
42
  originY: number;
43
+ width: number;
44
+ height: number;
45
+ objectSnapGuides?: ObjectSnapGuides;
34
46
  } | {
35
47
  kind: "zone-group";
36
48
  primaryZoneId: ZoneId;
@@ -39,6 +51,7 @@ export type MoveEditorDragOrigin = {
39
51
  kind: "path";
40
52
  pathId: PathId;
41
53
  origin: PathMoveOriginSnapshot;
54
+ objectSnapGuides?: ObjectSnapGuides;
42
55
  } | {
43
56
  kind: "path-group";
44
57
  primaryPathId: PathId;
@@ -61,6 +74,10 @@ export type GridSnapOptions = {
61
74
  enabled?: boolean;
62
75
  size?: number;
63
76
  };
77
+ export type ObjectSnapOptions = {
78
+ enabled?: boolean;
79
+ threshold?: number;
80
+ };
64
81
  export type ZoneAlignMode = "left" | "right" | "top" | "bottom" | "center-horizontal" | "center-vertical";
65
82
  export type ZoneDistributeMode = "horizontal" | "vertical";
66
83
  export type PathAlignMode = ZoneAlignMode;
@@ -83,5 +100,20 @@ export declare function resolveSnappedMove(params: {
83
100
  effectiveDeltaX: number;
84
101
  effectiveDeltaY: number;
85
102
  };
103
+ export declare function collectRectObjectSnapGuides(rects: Rect[]): ObjectSnapGuides;
104
+ export declare function resolveObjectSnappedRectPosition(params: {
105
+ x: number;
106
+ y: number;
107
+ width: number;
108
+ height: number;
109
+ camera: CameraState;
110
+ guides?: ObjectSnapGuides;
111
+ objectSnap?: ObjectSnapOptions;
112
+ }): {
113
+ x: number;
114
+ y: number;
115
+ guideX: number | undefined;
116
+ guideY: number | undefined;
117
+ };
86
118
  export declare function containsPoint(rect: Rect, point: Point): boolean;
87
119
  export declare function getRectArea(rect: Rect): number;
@@ -37,6 +37,85 @@ export function resolveSnappedMove(params) {
37
37
  effectiveDeltaY: nextY - originY,
38
38
  };
39
39
  }
40
+ export function collectRectObjectSnapGuides(rects) {
41
+ const x = new Set();
42
+ const y = new Set();
43
+ for (const rect of rects) {
44
+ x.add(roundCoordinate(rect.x));
45
+ x.add(roundCoordinate(rect.x + rect.width / 2));
46
+ x.add(roundCoordinate(rect.x + rect.width));
47
+ y.add(roundCoordinate(rect.y));
48
+ y.add(roundCoordinate(rect.y + rect.height / 2));
49
+ y.add(roundCoordinate(rect.y + rect.height));
50
+ }
51
+ return {
52
+ x: Array.from(x).sort((a, b) => a - b),
53
+ y: Array.from(y).sort((a, b) => a - b),
54
+ };
55
+ }
56
+ function resolveAxisObjectSnap(params) {
57
+ const { start, size, guides, threshold } = params;
58
+ const candidates = [
59
+ { coordinate: start, align: "start" },
60
+ { coordinate: start + size / 2, align: "center" },
61
+ { coordinate: start + size, align: "end" },
62
+ ];
63
+ let best;
64
+ for (const guide of guides) {
65
+ for (const candidate of candidates) {
66
+ const distance = Math.abs(guide - candidate.coordinate);
67
+ if (distance > threshold)
68
+ continue;
69
+ const snappedStart = candidate.align === "start"
70
+ ? guide
71
+ : candidate.align === "center"
72
+ ? guide - size / 2
73
+ : guide - size;
74
+ if (!best || distance < best.distance) {
75
+ best = {
76
+ distance,
77
+ match: {
78
+ guide: roundCoordinate(guide),
79
+ align: candidate.align,
80
+ snappedStart: roundCoordinate(snappedStart),
81
+ },
82
+ };
83
+ }
84
+ }
85
+ }
86
+ return best?.match;
87
+ }
88
+ export function resolveObjectSnappedRectPosition(params) {
89
+ const { x, y, width, height, camera, guides, objectSnap } = params;
90
+ if (!guides || !objectSnap?.enabled) {
91
+ return {
92
+ x: roundCoordinate(x),
93
+ y: roundCoordinate(y),
94
+ guideX: undefined,
95
+ guideY: undefined,
96
+ };
97
+ }
98
+ const threshold = objectSnap.threshold ?? 8;
99
+ const worldThreshold = threshold / camera.zoom;
100
+ const xMatch = resolveAxisObjectSnap({
101
+ start: x,
102
+ size: width,
103
+ guides: guides.x,
104
+ threshold: worldThreshold,
105
+ });
106
+ const yMatch = resolveAxisObjectSnap({
107
+ start: y,
108
+ size: height,
109
+ guides: guides.y,
110
+ threshold: worldThreshold,
111
+ });
112
+ return {
113
+ x: xMatch?.snappedStart ?? roundCoordinate(x),
114
+ y: yMatch?.snappedStart ?? roundCoordinate(y),
115
+ guideX: xMatch?.guide,
116
+ guideY: yMatch?.guide,
117
+ };
118
+ }
40
119
  export function containsPoint(rect, point) {
41
120
  return (point.x >= rect.x &&
42
121
  point.x <= rect.x + rect.width &&
@@ -0,0 +1,14 @@
1
+ import { type PathId, type UniverseLayoutModel, type UniverseModel, type ZoneId } from "@zoneflow/core";
2
+ export type ZOrderMode = "send-to-back" | "send-backward" | "bring-forward" | "bring-to-front";
3
+ export declare function reorderZonesByZOrderMode(params: {
4
+ model: UniverseModel;
5
+ layoutModel: UniverseLayoutModel;
6
+ zoneIds: ZoneId[];
7
+ mode: ZOrderMode;
8
+ }): UniverseLayoutModel;
9
+ export declare function reorderPathsByZOrderMode(params: {
10
+ model: UniverseModel;
11
+ layoutModel: UniverseLayoutModel;
12
+ pathIds: PathId[];
13
+ mode: ZOrderMode;
14
+ }): UniverseLayoutModel;
@@ -0,0 +1,126 @@
1
+ import { updatePathLayout, updateZoneLayout, } from "@zoneflow/core";
2
+ function reorderIds(params) {
3
+ const { orderedIds, selectedIds, mode } = params;
4
+ if (orderedIds.length < 2 || selectedIds.size === 0)
5
+ return orderedIds;
6
+ if (mode === "send-to-back") {
7
+ return [
8
+ ...orderedIds.filter((id) => selectedIds.has(id)),
9
+ ...orderedIds.filter((id) => !selectedIds.has(id)),
10
+ ];
11
+ }
12
+ if (mode === "bring-to-front") {
13
+ return [
14
+ ...orderedIds.filter((id) => !selectedIds.has(id)),
15
+ ...orderedIds.filter((id) => selectedIds.has(id)),
16
+ ];
17
+ }
18
+ const nextIds = [...orderedIds];
19
+ if (mode === "send-backward") {
20
+ for (let index = 1; index < nextIds.length; index += 1) {
21
+ const currentId = nextIds[index];
22
+ const previousId = nextIds[index - 1];
23
+ if (!currentId || !previousId)
24
+ continue;
25
+ if (!selectedIds.has(currentId) || selectedIds.has(previousId))
26
+ continue;
27
+ nextIds[index - 1] = currentId;
28
+ nextIds[index] = previousId;
29
+ }
30
+ return nextIds;
31
+ }
32
+ for (let index = nextIds.length - 2; index >= 0; index -= 1) {
33
+ const currentId = nextIds[index];
34
+ const nextId = nextIds[index + 1];
35
+ if (!currentId || !nextId)
36
+ continue;
37
+ if (!selectedIds.has(currentId) || selectedIds.has(nextId))
38
+ continue;
39
+ nextIds[index] = nextId;
40
+ nextIds[index + 1] = currentId;
41
+ }
42
+ return nextIds;
43
+ }
44
+ function sortByCurrentZOrder(params) {
45
+ const { ids, getZOrder } = params;
46
+ return [...ids]
47
+ .map((id, index) => ({
48
+ id,
49
+ index,
50
+ zOrder: getZOrder(id) ?? index,
51
+ }))
52
+ .sort((a, b) => a.zOrder - b.zOrder || a.index - b.index)
53
+ .map((entry) => entry.id);
54
+ }
55
+ export function reorderZonesByZOrderMode(params) {
56
+ const { model, layoutModel, zoneIds, mode } = params;
57
+ const selectedIds = new Set(zoneIds);
58
+ if (selectedIds.size === 0)
59
+ return layoutModel;
60
+ const selectedIdsByParent = new Map();
61
+ for (const zoneId of selectedIds) {
62
+ const zone = model.zonesById[zoneId];
63
+ if (!zone)
64
+ continue;
65
+ const parentZoneId = zone.parentZoneId;
66
+ const current = selectedIdsByParent.get(parentZoneId) ?? new Set();
67
+ current.add(zoneId);
68
+ selectedIdsByParent.set(parentZoneId, current);
69
+ }
70
+ let nextLayoutModel = layoutModel;
71
+ for (const [parentZoneId, selectedSiblingIds] of selectedIdsByParent) {
72
+ const siblingIds = parentZoneId === null
73
+ ? model.rootZoneIds
74
+ : model.zonesById[parentZoneId]?.childZoneIds ?? [];
75
+ const orderedSiblingIds = sortByCurrentZOrder({
76
+ ids: siblingIds.filter((zoneId) => Boolean(model.zonesById[zoneId])),
77
+ getZOrder: (zoneId) => layoutModel.zoneLayoutsById[zoneId]?.zOrder,
78
+ });
79
+ const reorderedSiblingIds = reorderIds({
80
+ orderedIds: orderedSiblingIds,
81
+ selectedIds: selectedSiblingIds,
82
+ mode,
83
+ });
84
+ reorderedSiblingIds.forEach((zoneId, zOrder) => {
85
+ if (!nextLayoutModel.zoneLayoutsById[zoneId])
86
+ return;
87
+ nextLayoutModel = updateZoneLayout(nextLayoutModel, zoneId, {
88
+ zOrder,
89
+ });
90
+ });
91
+ }
92
+ return nextLayoutModel;
93
+ }
94
+ function getAllPathIds(model) {
95
+ const pathIds = [];
96
+ const zoneIds = Object.keys(model.zonesById);
97
+ for (const zoneId of zoneIds) {
98
+ const zone = model.zonesById[zoneId];
99
+ if (!zone)
100
+ continue;
101
+ pathIds.push(...zone.pathIds.filter((pathId) => Boolean(zone.pathsById[pathId])));
102
+ }
103
+ return pathIds;
104
+ }
105
+ export function reorderPathsByZOrderMode(params) {
106
+ const { model, layoutModel, pathIds, mode } = params;
107
+ const selectedIds = new Set(pathIds);
108
+ if (selectedIds.size === 0)
109
+ return layoutModel;
110
+ const orderedPathIds = sortByCurrentZOrder({
111
+ ids: getAllPathIds(model),
112
+ getZOrder: (pathId) => layoutModel.pathLayoutsById[pathId]?.zOrder,
113
+ });
114
+ const reorderedPathIds = reorderIds({
115
+ orderedIds: orderedPathIds,
116
+ selectedIds,
117
+ mode,
118
+ });
119
+ let nextLayoutModel = layoutModel;
120
+ reorderedPathIds.forEach((pathId, zOrder) => {
121
+ nextLayoutModel = updatePathLayout(nextLayoutModel, pathId, {
122
+ zOrder,
123
+ });
124
+ });
125
+ return nextLayoutModel;
126
+ }
@@ -1,16 +1,22 @@
1
1
  import { type UniverseLayoutModel, type UniverseModel, type ZoneId } from "@zoneflow/core";
2
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";
3
+ import { type GridSnapOptions, type ObjectSnapOptions, type MoveEditorDragOrigin, type MoveEditorTarget, type MoveEditorTargetOptions, type ZoneAlignMode, type ZoneDistributeMode, type ZoneResizeOrigin } from "./moveEditorShared";
4
+ export type { GridSnapOptions, MoveEditorDragOrigin, MoveEditorTarget, MoveEditorTargetOptions, ObjectSnapOptions, PathAlignMode, PathDistributeMode, PathResizeOrigin, ZoneAlignMode, ZoneDistributeMode, ZoneResizeOrigin, } from "./moveEditorShared";
5
5
  export { alignPathsByMode, distributePathsByMode, resolveGroupPathDragOrigin, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./pathMoveEditor";
6
6
  export { commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPosition, reparentZoneAtCurrentPosition, resolveZonePlacementAtWorldRect, resolveZoneReparentCandidate, } from "./zoneReparent";
7
7
  export declare function getMoveEditorTargets(params: {
8
8
  model: UniverseModel;
9
+ layoutModel?: UniverseLayoutModel;
9
10
  frame: RendererFrame;
10
11
  camera: CameraState;
11
12
  options?: MoveEditorTargetOptions;
12
13
  }): MoveEditorTarget[];
13
- export declare function resolveMoveEditorDragOrigin(layoutModel: UniverseLayoutModel, target: MoveEditorTarget, frame?: RendererFrame): MoveEditorDragOrigin | undefined;
14
+ export declare function resolveMoveEditorDragOrigin(params: {
15
+ model: UniverseModel;
16
+ layoutModel: UniverseLayoutModel;
17
+ target: MoveEditorTarget;
18
+ frame?: RendererFrame;
19
+ }): MoveEditorDragOrigin | undefined;
14
20
  export declare function resolveGroupZoneDragOrigin(params: {
15
21
  model: UniverseModel;
16
22
  layoutModel: UniverseLayoutModel;
@@ -24,7 +30,19 @@ export declare function moveEditorTargetByScreenDelta(params: {
24
30
  deltaX: number;
25
31
  deltaY: number;
26
32
  gridSnap?: GridSnapOptions;
33
+ objectSnap?: ObjectSnapOptions;
27
34
  }): UniverseLayoutModel;
35
+ export declare function resolveMoveEditorObjectSnapGuides(params: {
36
+ camera: CameraState;
37
+ origin: MoveEditorDragOrigin;
38
+ deltaX: number;
39
+ deltaY: number;
40
+ gridSnap?: GridSnapOptions;
41
+ objectSnap?: ObjectSnapOptions;
42
+ }): {
43
+ guideX: number | undefined;
44
+ guideY: number | undefined;
45
+ };
28
46
  export declare function resolveZoneResizeOrigin(layoutModel: UniverseLayoutModel, zoneId: ZoneId): ZoneResizeOrigin | undefined;
29
47
  export declare function resizeZoneByScreenDelta(params: {
30
48
  layoutModel: UniverseLayoutModel;
@@ -1,5 +1,5 @@
1
1
  import { getZoneDepth, getZoneLayout, updateZoneLayout, } from "@zoneflow/core";
2
- import { projectWorldRectToScreenRect, resolveSnappedMove, roundCoordinate, snapCoordinate, typedValues, } from "./moveEditorShared";
2
+ import { collectRectObjectSnapGuides, projectWorldRectToScreenRect, resolveObjectSnappedRectPosition, resolveSnappedMove, roundCoordinate, snapCoordinate, typedValues, } from "./moveEditorShared";
3
3
  import { applyPathMovePosition, resolvePathMoveOriginSnapshot, } from "./pathMoveEditor";
4
4
  import { applyZoneOriginsDelta, resolveZoneGroupOrigins, } from "./zoneGeometry";
5
5
  export { alignPathsByMode, distributePathsByMode, resolveGroupPathDragOrigin, resolvePathResizeOrigin, resizePathNodeByScreenDelta, } from "./pathMoveEditor";
@@ -7,6 +7,48 @@ export { commitZoneGroupReparentAtCurrentPosition, commitZoneReparentAtCurrentPo
7
7
  const DEFAULT_MIN_VISIBLE_SIZE = 18;
8
8
  const DEFAULT_MIN_ZONE_WIDTH = 140;
9
9
  const DEFAULT_MIN_ZONE_HEIGHT = 96;
10
+ function collectDescendantZoneIds(model, zoneId) {
11
+ const descendants = new Set();
12
+ const queue = [...(model.zonesById[zoneId]?.childZoneIds ?? [])];
13
+ while (queue.length > 0) {
14
+ const current = queue.shift();
15
+ if (!current || descendants.has(current))
16
+ continue;
17
+ descendants.add(current);
18
+ queue.push(...(model.zonesById[current]?.childZoneIds ?? []));
19
+ }
20
+ return descendants;
21
+ }
22
+ function resolveObjectSnapGuides(params) {
23
+ const { model, frame, target } = params;
24
+ if (!frame)
25
+ return undefined;
26
+ const excludedZoneIds = target.kind === "zone"
27
+ ? new Set([target.zoneId, ...collectDescendantZoneIds(model, target.zoneId)])
28
+ : new Set();
29
+ const candidateRects = [];
30
+ for (const zoneVisual of typedValues(frame.pipeline.graphLayout.zonesById)) {
31
+ const visibility = frame.pipeline.visibility.zoneVisibilityById[zoneVisual.zoneId];
32
+ if (!visibility?.isVisible)
33
+ continue;
34
+ if (excludedZoneIds.has(zoneVisual.zoneId))
35
+ continue;
36
+ candidateRects.push(zoneVisual.rect);
37
+ }
38
+ for (const pathVisual of typedValues(frame.pipeline.graphLayout.pathsById)) {
39
+ const visibility = frame.pipeline.visibility.pathVisibilityById[pathVisual.pathId];
40
+ if (!visibility?.shouldRenderNode || !pathVisual.rect)
41
+ continue;
42
+ if (target.kind === "path" && pathVisual.pathId === target.pathId)
43
+ continue;
44
+ if (target.kind === "zone" && excludedZoneIds.has(pathVisual.sourceZoneId))
45
+ continue;
46
+ candidateRects.push(pathVisual.rect);
47
+ }
48
+ return candidateRects.length > 0
49
+ ? collectRectObjectSnapGuides(candidateRects)
50
+ : undefined;
51
+ }
10
52
  function resolveResizedAnchor(params) {
11
53
  const { kind, width, height, current } = params;
12
54
  const rectWidth = current?.rect?.width;
@@ -34,7 +76,7 @@ function resolveResizedAnchor(params) {
34
76
  };
35
77
  }
36
78
  export function getMoveEditorTargets(params) {
37
- const { model, frame, camera, options, } = params;
79
+ const { model, layoutModel, frame, camera, options, } = params;
38
80
  const includeRoot = options?.includeRoot ?? true;
39
81
  const minVisibleSize = options?.minVisibleSize ?? DEFAULT_MIN_VISIBLE_SIZE;
40
82
  const zoneTargets = [];
@@ -57,9 +99,10 @@ export function getMoveEditorTargets(params) {
57
99
  label: zone.name,
58
100
  rect,
59
101
  depth: getZoneDepth(model, zoneVisual.zoneId),
102
+ zOrder: layoutModel?.zoneLayoutsById[zoneVisual.zoneId]?.zOrder,
60
103
  });
61
104
  }
62
- for (const pathVisual of typedValues(frame.pipeline.graphLayout.pathsById)) {
105
+ for (const [index, pathVisual] of typedValues(frame.pipeline.graphLayout.pathsById).entries()) {
63
106
  const visibility = frame.pipeline.visibility.pathVisibilityById[pathVisual.pathId];
64
107
  if (!visibility?.shouldRenderNode || !pathVisual.rect)
65
108
  continue;
@@ -73,25 +116,44 @@ export function getMoveEditorTargets(params) {
73
116
  pathId: pathVisual.pathId,
74
117
  label: pathVisual.path.name,
75
118
  rect,
119
+ zOrder: layoutModel?.pathLayoutsById[pathVisual.pathId]?.zOrder ?? index,
76
120
  });
77
121
  }
78
122
  return [
79
123
  ...zoneTargets
80
- .sort((a, b) => a.depth - b.depth)
81
- .map(({ depth: _depth, ...target }) => target),
82
- ...pathTargets,
124
+ .sort((a, b) => {
125
+ const aOrder = a.zOrder ?? 0;
126
+ const bOrder = b.zOrder ?? 0;
127
+ return a.depth - b.depth || aOrder - bOrder;
128
+ })
129
+ .map(({ depth: _depth, zOrder: _zOrder, ...target }) => target),
130
+ ...pathTargets
131
+ .sort((a, b) => (a.zOrder ?? 0) - (b.zOrder ?? 0))
132
+ .map(({ zOrder: _zOrder, ...target }) => target),
83
133
  ];
84
134
  }
85
- export function resolveMoveEditorDragOrigin(layoutModel, target, frame) {
135
+ export function resolveMoveEditorDragOrigin(params) {
136
+ const { model, layoutModel, target, frame } = params;
86
137
  if (target.kind === "zone") {
87
138
  const zoneLayout = getZoneLayout(layoutModel, target.zoneId);
88
139
  if (!zoneLayout)
89
140
  return undefined;
141
+ const zoneRect = frame?.pipeline.graphLayout.zonesById[target.zoneId]?.rect ??
142
+ getZoneLayout(layoutModel, target.zoneId);
143
+ const width = zoneRect?.width ?? 0;
144
+ const height = zoneRect?.height ?? 0;
90
145
  return {
91
146
  kind: "zone",
92
147
  zoneId: target.zoneId,
93
148
  originX: zoneLayout.x,
94
149
  originY: zoneLayout.y,
150
+ width,
151
+ height,
152
+ objectSnapGuides: resolveObjectSnapGuides({
153
+ model,
154
+ frame,
155
+ target,
156
+ }),
95
157
  };
96
158
  }
97
159
  return {
@@ -102,6 +164,11 @@ export function resolveMoveEditorDragOrigin(layoutModel, target, frame) {
102
164
  layoutModel,
103
165
  pathId: target.pathId,
104
166
  }),
167
+ objectSnapGuides: resolveObjectSnapGuides({
168
+ model,
169
+ frame,
170
+ target,
171
+ }),
105
172
  };
106
173
  }
107
174
  export function resolveGroupZoneDragOrigin(params) {
@@ -115,7 +182,7 @@ export function resolveGroupZoneDragOrigin(params) {
115
182
  };
116
183
  }
117
184
  export function moveEditorTargetByScreenDelta(params) {
118
- const { layoutModel, camera, origin, deltaX, deltaY, gridSnap, } = params;
185
+ const { layoutModel, camera, origin, deltaX, deltaY, gridSnap, objectSnap, } = params;
119
186
  if (origin.kind === "zone") {
120
187
  const { nextX, nextY } = resolveSnappedMove({
121
188
  originX: origin.originX,
@@ -125,9 +192,18 @@ export function moveEditorTargetByScreenDelta(params) {
125
192
  camera,
126
193
  gridSnap,
127
194
  });
128
- return updateZoneLayout(layoutModel, origin.zoneId, {
195
+ const snapped = resolveObjectSnappedRectPosition({
129
196
  x: nextX,
130
197
  y: nextY,
198
+ width: origin.width,
199
+ height: origin.height,
200
+ camera,
201
+ guides: origin.objectSnapGuides,
202
+ objectSnap,
203
+ });
204
+ return updateZoneLayout(layoutModel, origin.zoneId, {
205
+ x: snapped.x,
206
+ y: snapped.y,
131
207
  });
132
208
  }
133
209
  if (origin.kind === "zone-group") {
@@ -181,14 +257,76 @@ export function moveEditorTargetByScreenDelta(params) {
181
257
  camera,
182
258
  gridSnap,
183
259
  });
260
+ const snapped = resolveObjectSnappedRectPosition({
261
+ x: nextX,
262
+ y: nextY,
263
+ width: origin.origin.width,
264
+ height: origin.origin.height,
265
+ camera,
266
+ guides: origin.objectSnapGuides,
267
+ objectSnap,
268
+ });
184
269
  return applyPathMovePosition({
185
270
  layoutModel,
186
271
  pathId: origin.pathId,
187
272
  origin: origin.origin,
188
- x: nextX,
189
- y: nextY,
273
+ x: snapped.x,
274
+ y: snapped.y,
190
275
  });
191
276
  }
277
+ export function resolveMoveEditorObjectSnapGuides(params) {
278
+ const { camera, origin, deltaX, deltaY, gridSnap, objectSnap } = params;
279
+ if (origin.kind === "zone") {
280
+ const { nextX, nextY } = resolveSnappedMove({
281
+ originX: origin.originX,
282
+ originY: origin.originY,
283
+ deltaX,
284
+ deltaY,
285
+ camera,
286
+ gridSnap,
287
+ });
288
+ const snapped = resolveObjectSnappedRectPosition({
289
+ x: nextX,
290
+ y: nextY,
291
+ width: origin.width,
292
+ height: origin.height,
293
+ camera,
294
+ guides: origin.objectSnapGuides,
295
+ objectSnap,
296
+ });
297
+ return {
298
+ guideX: snapped.guideX,
299
+ guideY: snapped.guideY,
300
+ };
301
+ }
302
+ if (origin.kind === "path") {
303
+ const { nextX, nextY } = resolveSnappedMove({
304
+ originX: origin.origin.x,
305
+ originY: origin.origin.y,
306
+ deltaX,
307
+ deltaY,
308
+ camera,
309
+ gridSnap,
310
+ });
311
+ const snapped = resolveObjectSnappedRectPosition({
312
+ x: nextX,
313
+ y: nextY,
314
+ width: origin.origin.width,
315
+ height: origin.origin.height,
316
+ camera,
317
+ guides: origin.objectSnapGuides,
318
+ objectSnap,
319
+ });
320
+ return {
321
+ guideX: snapped.guideX,
322
+ guideY: snapped.guideY,
323
+ };
324
+ }
325
+ return {
326
+ guideX: undefined,
327
+ guideY: undefined,
328
+ };
329
+ }
192
330
  export function resolveZoneResizeOrigin(layoutModel, zoneId) {
193
331
  const zoneLayout = getZoneLayout(layoutModel, zoneId);
194
332
  if (!zoneLayout)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoneflow/editor-dom",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "description": "Low-level editor geometry and interaction helpers for Zoneflow.",
6
6
  "type": "module",
@@ -19,8 +19,8 @@
19
19
  "dist"
20
20
  ],
21
21
  "dependencies": {
22
- "@zoneflow/core": "0.0.5",
23
- "@zoneflow/renderer-dom": "0.0.5"
22
+ "@zoneflow/core": "0.0.7",
23
+ "@zoneflow/renderer-dom": "0.0.7"
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsc -p tsconfig.json",