@flowgram-vue/free-snap-plugin 0.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,1034 @@
1
+ import { EntityManager, PlaygroundConfigEntity, definePluginCreator, Layer, TransformData } from '@flowgram-vue/core';
2
+ import { inject, injectable } from 'inversify';
3
+ import { domUtils, Emitter, Rectangle } from '@flowgram-vue/utils';
4
+ import { WorkflowDocument, WorkflowDragService, WorkflowNodeEntity } from '@flowgram-vue/free-layout-core';
5
+ import { FlowNodeTransformData, FlowNodeBaseType } from '@flowgram-vue/document';
6
+ import { h, Fragment } from 'vue';
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __decorateClass = (decorators, target, key, kind) => {
11
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
12
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
13
+ if (decorator = decorators[i])
14
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
15
+ if (kind && result) __defProp(target, key, result);
16
+ return result;
17
+ };
18
+
19
+ // src/constant.ts
20
+ var SnapDefaultOptions = {
21
+ enableEdgeSnapping: true,
22
+ edgeThreshold: 7,
23
+ enableGridSnapping: false,
24
+ gridSize: 20,
25
+ enableMultiSnapping: true,
26
+ enableOnlyViewportSnapping: true,
27
+ edgeColor: "#4E40E5",
28
+ alignColor: "#4E40E5",
29
+ edgeLineWidth: 2,
30
+ alignLineWidth: 2,
31
+ alignCrossWidth: 16
32
+ };
33
+ var Epsilon = 1e-5;
34
+
35
+ // src/utils.ts
36
+ var isEqual = (a, b) => {
37
+ if (a === void 0 || b === void 0) {
38
+ return false;
39
+ }
40
+ return Math.abs(a - b) < Epsilon;
41
+ };
42
+ var isLessThan = (a, b) => {
43
+ if (a === void 0 || b === void 0) {
44
+ return false;
45
+ }
46
+ return b - a > Epsilon;
47
+ };
48
+ var isGreaterThan = (a, b) => {
49
+ if (a === void 0 || b === void 0) {
50
+ return false;
51
+ }
52
+ return a - b > Epsilon;
53
+ };
54
+ var isLessThanOrEqual = (a, b) => isEqual(a, b) || isLessThan(a, b);
55
+ var isNumber = (value) => typeof value === "number" && !isNaN(value);
56
+
57
+ // src/service.ts
58
+ var WorkflowSnapService = class {
59
+ constructor() {
60
+ this.disposers = [];
61
+ this.snapEmitter = new Emitter();
62
+ this.onSnap = this.snapEmitter.event;
63
+ this._disabled = false;
64
+ }
65
+ init(params = {}) {
66
+ this.options = {
67
+ ...SnapDefaultOptions,
68
+ ...params
69
+ };
70
+ this.mountListener();
71
+ }
72
+ dispose() {
73
+ this.disposers.forEach((disposer) => disposer.dispose());
74
+ }
75
+ get disabled() {
76
+ return this._disabled;
77
+ }
78
+ disable() {
79
+ if (this._disabled) {
80
+ return;
81
+ }
82
+ this._disabled = true;
83
+ this.clear();
84
+ }
85
+ enable() {
86
+ if (!this._disabled) {
87
+ return;
88
+ }
89
+ this._disabled = false;
90
+ this.clear();
91
+ }
92
+ mountListener() {
93
+ const dragAdjusterDisposer = this.dragService.registerPosAdjuster((params) => {
94
+ const { selectedNodes: targetNodes, position } = params;
95
+ const isMultiSnapping = this.options.enableMultiSnapping ? false : targetNodes.length !== 1;
96
+ if (this._disabled || !this.options.enableEdgeSnapping || isMultiSnapping) {
97
+ return {
98
+ x: 0,
99
+ y: 0
100
+ };
101
+ }
102
+ return this.snapping({
103
+ targetNodes,
104
+ position
105
+ });
106
+ });
107
+ const dragEndDisposer = this.dragService.onNodesDrag((event) => {
108
+ if (event.type !== "onDragEnd" || this._disabled) {
109
+ return;
110
+ }
111
+ if (this.options.enableGridSnapping) {
112
+ this.gridSnapping({
113
+ targetNodes: event.nodes,
114
+ gridSize: this.options.gridSize
115
+ });
116
+ }
117
+ if (this.options.enableEdgeSnapping) {
118
+ this.clear();
119
+ }
120
+ });
121
+ this.disposers.push(dragAdjusterDisposer, dragEndDisposer);
122
+ }
123
+ snapping(params) {
124
+ const { targetNodes, position } = params;
125
+ const targetBounds = this.getBounds(targetNodes);
126
+ const targetRect = new Rectangle(
127
+ position.x,
128
+ position.y,
129
+ targetBounds.width,
130
+ targetBounds.height
131
+ );
132
+ const snapNodeRects = this.getSnapNodeRects({
133
+ targetNodes,
134
+ targetRect
135
+ });
136
+ const { alignOffset, alignRects, alignSpacing } = this.calcAlignOffset({
137
+ targetRect,
138
+ alignThreshold: this.options.edgeThreshold,
139
+ snapNodeRects
140
+ });
141
+ const { snapOffset, snapEdgeLines } = this.calcSnapOffset({
142
+ targetRect,
143
+ edgeThreshold: this.options.edgeThreshold,
144
+ snapNodeRects
145
+ });
146
+ const offset = {
147
+ x: snapOffset.x || alignOffset.x,
148
+ y: snapOffset.y || alignOffset.y
149
+ };
150
+ const snapRect = new Rectangle(
151
+ position.x + offset.x,
152
+ position.y + offset.y,
153
+ targetRect.width,
154
+ targetRect.height
155
+ );
156
+ this.snapEmitter.fire({
157
+ snapRect,
158
+ snapEdgeLines,
159
+ alignRects,
160
+ alignSpacing
161
+ });
162
+ return offset;
163
+ }
164
+ calcSnapOffset(params) {
165
+ const { snapNodeRects, edgeThreshold, targetRect } = params;
166
+ const snapLines = this.getSnapLines({
167
+ snapNodeRects
168
+ });
169
+ const topYClosestLine = snapLines.horizontal.find(
170
+ (line) => isLessThanOrEqual(Math.abs(line.y - targetRect.top), edgeThreshold)
171
+ );
172
+ const bottomYClosestLine = snapLines.horizontal.find(
173
+ (line) => isLessThanOrEqual(Math.abs(line.y - targetRect.bottom), edgeThreshold)
174
+ );
175
+ const leftXClosestLine = snapLines.vertical.find(
176
+ (line) => isLessThanOrEqual(Math.abs(line.x - targetRect.left), edgeThreshold)
177
+ );
178
+ const rightXClosestLine = snapLines.vertical.find(
179
+ (line) => isLessThanOrEqual(Math.abs(line.x - targetRect.right), edgeThreshold)
180
+ );
181
+ const midYClosestLine = snapLines.midHorizontal.find(
182
+ (line) => isLessThanOrEqual(Math.abs(line.y - targetRect.center.y), edgeThreshold)
183
+ );
184
+ const midXClosestLine = snapLines.midVertical.find(
185
+ (line) => isLessThanOrEqual(Math.abs(line.x - targetRect.center.x), edgeThreshold)
186
+ );
187
+ const topYClosest = topYClosestLine?.y;
188
+ const bottomYClosest = isNumber(bottomYClosestLine?.y) ? bottomYClosestLine.y - targetRect.height : void 0;
189
+ const leftXClosest = leftXClosestLine?.x;
190
+ const rightXClosest = isNumber(rightXClosestLine?.x) ? rightXClosestLine.x - targetRect.width : void 0;
191
+ const midYClosest = isNumber(midYClosestLine?.y) ? midYClosestLine.y - targetRect.height / 2 : void 0;
192
+ const midXClosest = isNumber(midXClosestLine?.x) ? midXClosestLine.x - targetRect.width / 2 : void 0;
193
+ const snappingPosition = {
194
+ x: midXClosest ?? leftXClosest ?? rightXClosest ?? targetRect.x,
195
+ y: midYClosest ?? topYClosest ?? bottomYClosest ?? targetRect.y
196
+ };
197
+ const snapOffset = {
198
+ x: snappingPosition.x - targetRect.x,
199
+ y: snappingPosition.y - targetRect.y
200
+ };
201
+ const snapEdgeLines = {
202
+ top: isEqual(topYClosest, snappingPosition.y) ? topYClosestLine : void 0,
203
+ bottom: isEqual(bottomYClosest, snappingPosition.y) ? bottomYClosestLine : void 0,
204
+ left: isEqual(leftXClosest, snappingPosition.x) ? leftXClosestLine : void 0,
205
+ right: isEqual(rightXClosest, snappingPosition.x) ? rightXClosestLine : void 0,
206
+ midVertical: isEqual(midXClosest, snappingPosition.x) ? midXClosestLine : void 0,
207
+ midHorizontal: isEqual(midYClosest, snappingPosition.y) ? midYClosestLine : void 0
208
+ };
209
+ return { snapOffset, snapEdgeLines };
210
+ }
211
+ gridSnapping(params) {
212
+ const { gridSize, targetNodes } = params;
213
+ const rect = this.getBounds(targetNodes);
214
+ const snap = (value) => Math.round(value / gridSize) * gridSize;
215
+ const snappedPosition = {
216
+ x: snap(rect.x),
217
+ y: snap(rect.y)
218
+ };
219
+ const offset = {
220
+ x: snappedPosition.x - rect.x,
221
+ y: snappedPosition.y - rect.y
222
+ };
223
+ targetNodes.forEach(
224
+ (node) => this.updateNodePositionWithOffset({
225
+ node,
226
+ offset
227
+ })
228
+ );
229
+ }
230
+ clear() {
231
+ this.snapEmitter.fire({
232
+ snapEdgeLines: {},
233
+ snapRect: Rectangle.EMPTY,
234
+ alignRects: {
235
+ top: [],
236
+ bottom: [],
237
+ left: [],
238
+ right: []
239
+ },
240
+ alignSpacing: {}
241
+ });
242
+ }
243
+ getSnapLines(params) {
244
+ const { snapNodeRects } = params;
245
+ const horizontalLines = [];
246
+ const verticalLines = [];
247
+ const midHorizontalLines = [];
248
+ const midVerticalLines = [];
249
+ snapNodeRects.forEach((snapNodeRect) => {
250
+ const nodeBounds = snapNodeRect.rect;
251
+ const nodeCenter = nodeBounds.center;
252
+ const top = {
253
+ y: nodeBounds.top,
254
+ sourceNodeId: snapNodeRect.id
255
+ };
256
+ const bottom = {
257
+ y: nodeBounds.bottom,
258
+ sourceNodeId: snapNodeRect.id
259
+ };
260
+ const left = {
261
+ x: nodeBounds.left,
262
+ sourceNodeId: snapNodeRect.id
263
+ };
264
+ const right = {
265
+ x: nodeBounds.right,
266
+ sourceNodeId: snapNodeRect.id
267
+ };
268
+ const midHorizontal = {
269
+ y: nodeCenter.y,
270
+ sourceNodeId: snapNodeRect.id
271
+ };
272
+ const midVertical = {
273
+ x: nodeCenter.x,
274
+ sourceNodeId: snapNodeRect.id
275
+ };
276
+ horizontalLines.push(top, bottom);
277
+ verticalLines.push(left, right);
278
+ midHorizontalLines.push(midHorizontal);
279
+ midVerticalLines.push(midVertical);
280
+ });
281
+ return {
282
+ horizontal: horizontalLines,
283
+ vertical: verticalLines,
284
+ midHorizontal: midHorizontalLines,
285
+ midVertical: midVerticalLines
286
+ };
287
+ }
288
+ getAvailableNodes(params) {
289
+ const { targetNodes, targetRect } = params;
290
+ const targetCenter = targetRect.center;
291
+ const targetContainerId = targetNodes[0].parent?.id ?? this.document.root.id;
292
+ const disabledNodeIds = targetNodes.map((n) => n.id);
293
+ disabledNodeIds.push(FlowNodeBaseType.ROOT);
294
+ const availableNodes = this.nodes.filter((n) => n.parent?.id === targetContainerId).filter((n) => !disabledNodeIds.includes(n.id)).sort((nodeA, nodeB) => {
295
+ const nodeCenterA = nodeA.getData(FlowNodeTransformData).bounds.center;
296
+ const nodeCenterB = nodeB.getData(FlowNodeTransformData).bounds.center;
297
+ const distanceA = Math.abs(nodeCenterA.x - targetCenter.x) + Math.abs(nodeCenterA.y - targetCenter.y);
298
+ const distanceB = Math.abs(nodeCenterB.x - targetCenter.x) + Math.abs(nodeCenterB.y - targetCenter.y);
299
+ return distanceA - distanceB;
300
+ });
301
+ return availableNodes;
302
+ }
303
+ viewRect() {
304
+ const { width, height, scrollX, scrollY, zoom } = this.playgroundConfig.config;
305
+ return new Rectangle(scrollX / zoom, scrollY / zoom, width / zoom, height / zoom);
306
+ }
307
+ getSnapNodeRects(params) {
308
+ const availableNodes = this.getAvailableNodes(params);
309
+ const viewRect = this.viewRect();
310
+ return availableNodes.map((node) => {
311
+ const snapNodeRect = {
312
+ id: node.id,
313
+ rect: node.getData(FlowNodeTransformData).bounds,
314
+ entity: node
315
+ };
316
+ if (this.options.enableOnlyViewportSnapping && node.parent?.flowNodeType === FlowNodeBaseType.ROOT && !Rectangle.intersects(viewRect, snapNodeRect.rect)) {
317
+ return;
318
+ }
319
+ return snapNodeRect;
320
+ }).filter(Boolean);
321
+ }
322
+ get nodes() {
323
+ return this.entityManager.getEntities(WorkflowNodeEntity);
324
+ }
325
+ getBounds(nodes) {
326
+ if (nodes.length === 0) {
327
+ return Rectangle.EMPTY;
328
+ }
329
+ return Rectangle.enlarge(nodes.map((n) => n.getData(FlowNodeTransformData).bounds));
330
+ }
331
+ updateNodePositionWithOffset(params) {
332
+ const { node, offset } = params;
333
+ const transform = node.getData(TransformData);
334
+ const positionWithOffset = {
335
+ x: transform.position.x + offset.x,
336
+ y: transform.position.y + offset.y
337
+ };
338
+ transform.update({
339
+ position: positionWithOffset
340
+ });
341
+ this.document.layout.updateAffectedTransform(node);
342
+ }
343
+ calcAlignOffset(params) {
344
+ const { snapNodeRects, targetRect, alignThreshold } = params;
345
+ const alignRects = this.getAlignRects({
346
+ targetRect,
347
+ snapNodeRects
348
+ });
349
+ const alignSpacing = this.calcAlignSpacing({
350
+ targetRect,
351
+ alignRects
352
+ });
353
+ let topY;
354
+ let bottomY;
355
+ let leftX;
356
+ let rightX;
357
+ let midY;
358
+ let midX;
359
+ if (alignSpacing.top) {
360
+ const topAlignY = alignRects.top[0].rect.bottom + alignSpacing.top;
361
+ const isAlignTop = isLessThanOrEqual(Math.abs(targetRect.top - topAlignY), alignThreshold);
362
+ if (isAlignTop) {
363
+ topY = topAlignY;
364
+ } else {
365
+ alignSpacing.top = void 0;
366
+ }
367
+ }
368
+ if (alignSpacing.bottom) {
369
+ const bottomAlignY = alignRects.bottom[0].rect.top - alignSpacing.bottom;
370
+ const isAlignBottom = isLessThan(Math.abs(targetRect.bottom - bottomAlignY), alignThreshold);
371
+ if (isAlignBottom) {
372
+ bottomY = bottomAlignY - targetRect.height;
373
+ } else {
374
+ alignSpacing.bottom = void 0;
375
+ }
376
+ }
377
+ if (alignSpacing.left) {
378
+ const leftAlignX = alignRects.left[0].rect.right + alignSpacing.left;
379
+ const isAlignLeft = isLessThanOrEqual(Math.abs(targetRect.left - leftAlignX), alignThreshold);
380
+ if (isAlignLeft) {
381
+ leftX = leftAlignX;
382
+ } else {
383
+ alignSpacing.left = void 0;
384
+ }
385
+ }
386
+ if (alignSpacing.right) {
387
+ const rightAlignX = alignRects.right[0].rect.left - alignSpacing.right;
388
+ const isAlignRight = isLessThanOrEqual(
389
+ Math.abs(targetRect.right - rightAlignX),
390
+ alignThreshold
391
+ );
392
+ if (isAlignRight) {
393
+ rightX = rightAlignX - targetRect.width;
394
+ } else {
395
+ alignSpacing.right = void 0;
396
+ }
397
+ }
398
+ if (alignSpacing.midHorizontal) {
399
+ const leftAlignX = alignRects.left[0].rect.right + alignSpacing.midHorizontal;
400
+ const isAlignMidHorizontal = isLessThanOrEqual(
401
+ Math.abs(targetRect.left - leftAlignX),
402
+ alignThreshold
403
+ );
404
+ if (isAlignMidHorizontal) {
405
+ midX = leftAlignX;
406
+ } else {
407
+ alignSpacing.midHorizontal = void 0;
408
+ }
409
+ }
410
+ if (alignSpacing.midVertical) {
411
+ const topAlignY = alignRects.top[0].rect.bottom + alignSpacing.midVertical;
412
+ const isAlignMidVertical = isLessThanOrEqual(
413
+ Math.abs(targetRect.top - topAlignY),
414
+ alignThreshold
415
+ );
416
+ if (isAlignMidVertical) {
417
+ midY = topAlignY;
418
+ } else {
419
+ alignSpacing.midVertical = void 0;
420
+ }
421
+ }
422
+ const alignPosition = {
423
+ x: midX ?? leftX ?? rightX ?? targetRect.x,
424
+ y: midY ?? topY ?? bottomY ?? targetRect.y
425
+ };
426
+ const alignOffset = {
427
+ x: alignPosition.x - targetRect.x,
428
+ y: alignPosition.y - targetRect.y
429
+ };
430
+ return { alignOffset, alignRects, alignSpacing };
431
+ }
432
+ calcAlignSpacing(params) {
433
+ const { targetRect, alignRects } = params;
434
+ const topSpacing = this.getDirectionAlignSpacing({
435
+ rects: alignRects.top,
436
+ isHorizontal: false
437
+ });
438
+ const bottomSpacing = this.getDirectionAlignSpacing({
439
+ rects: alignRects.bottom,
440
+ isHorizontal: false
441
+ });
442
+ const leftSpacing = this.getDirectionAlignSpacing({
443
+ rects: alignRects.left,
444
+ isHorizontal: true
445
+ });
446
+ const rightSpacing = this.getDirectionAlignSpacing({
447
+ rects: alignRects.right,
448
+ isHorizontal: true
449
+ });
450
+ const midHorizontalSpacing = this.getMidAlignSpacing({
451
+ rectA: alignRects.left[0]?.rect,
452
+ rectB: alignRects.right[0]?.rect,
453
+ targetRect,
454
+ isHorizontal: true
455
+ });
456
+ const midVerticalSpacing = this.getMidAlignSpacing({
457
+ rectA: alignRects.top[0]?.rect,
458
+ rectB: alignRects.bottom[0]?.rect,
459
+ targetRect,
460
+ isHorizontal: false
461
+ });
462
+ return {
463
+ top: topSpacing,
464
+ bottom: bottomSpacing,
465
+ left: leftSpacing,
466
+ right: rightSpacing,
467
+ midHorizontal: midHorizontalSpacing,
468
+ midVertical: midVerticalSpacing
469
+ };
470
+ }
471
+ getAlignRects(params) {
472
+ const { targetRect, snapNodeRects } = params;
473
+ const topVerticalRects = [];
474
+ const bottomVerticalRects = [];
475
+ const leftHorizontalRects = [];
476
+ const rightHorizontalRects = [];
477
+ snapNodeRects.forEach((snapNodeRect) => {
478
+ const nodeRect = snapNodeRect.rect;
479
+ const { isVerticalIntersection, isHorizontalIntersection, isIntersection } = this.intersection(nodeRect, targetRect);
480
+ if (isIntersection) {
481
+ return;
482
+ } else if (isVerticalIntersection) {
483
+ if (isGreaterThan(nodeRect.center.y, targetRect.center.y)) {
484
+ bottomVerticalRects.push({
485
+ rect: nodeRect,
486
+ sourceNodeId: snapNodeRect.id
487
+ });
488
+ } else {
489
+ topVerticalRects.push({
490
+ rect: nodeRect,
491
+ sourceNodeId: snapNodeRect.id
492
+ });
493
+ }
494
+ } else if (isHorizontalIntersection) {
495
+ if (isGreaterThan(nodeRect.center.x, targetRect.center.x)) {
496
+ rightHorizontalRects.push({
497
+ rect: nodeRect,
498
+ sourceNodeId: snapNodeRect.id
499
+ });
500
+ } else {
501
+ leftHorizontalRects.push({
502
+ rect: nodeRect,
503
+ sourceNodeId: snapNodeRect.id
504
+ });
505
+ }
506
+ }
507
+ });
508
+ return {
509
+ top: topVerticalRects,
510
+ bottom: bottomVerticalRects,
511
+ left: leftHorizontalRects,
512
+ right: rightHorizontalRects
513
+ };
514
+ }
515
+ getMidAlignSpacing(params) {
516
+ const { rectA, rectB, targetRect, isHorizontal } = params;
517
+ if (!rectA || !rectB) {
518
+ return;
519
+ }
520
+ const { isVerticalIntersection, isHorizontalIntersection, isIntersection } = this.intersection(
521
+ rectA,
522
+ rectB
523
+ );
524
+ if (isIntersection) {
525
+ return;
526
+ }
527
+ if (isHorizontal && isHorizontalIntersection && !isVerticalIntersection) {
528
+ const betweenSpacing = Math.min(
529
+ Math.abs(rectA.left - rectB.right),
530
+ Math.abs(rectA.right - rectB.left)
531
+ );
532
+ return (betweenSpacing - targetRect.width) / 2;
533
+ } else if (!isHorizontal && isVerticalIntersection && !isHorizontalIntersection) {
534
+ const betweenSpacing = Math.min(
535
+ Math.abs(rectA.top - rectB.bottom),
536
+ Math.abs(rectA.bottom - rectB.top)
537
+ );
538
+ return (betweenSpacing - targetRect.height) / 2;
539
+ }
540
+ }
541
+ getDirectionAlignSpacing(params) {
542
+ const { rects, isHorizontal } = params;
543
+ if (rects.length < 2) {
544
+ return;
545
+ }
546
+ const rectA = rects[0].rect;
547
+ const rectB = rects[1].rect;
548
+ const { isVerticalIntersection, isHorizontalIntersection, isIntersection } = this.intersection(
549
+ rectA,
550
+ rectB
551
+ );
552
+ if (isIntersection) {
553
+ return;
554
+ }
555
+ if (isHorizontal && isHorizontalIntersection && !isVerticalIntersection) {
556
+ return Math.min(Math.abs(rectA.left - rectB.right), Math.abs(rectA.right - rectB.left));
557
+ } else if (!isHorizontal && isVerticalIntersection && !isHorizontalIntersection) {
558
+ return Math.min(Math.abs(rectA.top - rectB.bottom), Math.abs(rectA.bottom - rectB.top));
559
+ }
560
+ return;
561
+ }
562
+ intersection(rectA, rectB) {
563
+ const isVerticalIntersection = isLessThan(rectA.left, rectB.right) && isGreaterThan(rectA.right, rectB.left);
564
+ const isHorizontalIntersection = isLessThan(rectA.top, rectB.bottom) && isGreaterThan(rectA.bottom, rectB.top);
565
+ const isIntersection = isHorizontalIntersection && isVerticalIntersection;
566
+ return {
567
+ isHorizontalIntersection,
568
+ isVerticalIntersection,
569
+ isIntersection
570
+ };
571
+ }
572
+ };
573
+ __decorateClass([
574
+ inject(WorkflowDocument)
575
+ ], WorkflowSnapService.prototype, "document", 2);
576
+ __decorateClass([
577
+ inject(EntityManager)
578
+ ], WorkflowSnapService.prototype, "entityManager", 2);
579
+ __decorateClass([
580
+ inject(WorkflowDragService)
581
+ ], WorkflowSnapService.prototype, "dragService", 2);
582
+ __decorateClass([
583
+ inject(PlaygroundConfigEntity)
584
+ ], WorkflowSnapService.prototype, "playgroundConfig", 2);
585
+ WorkflowSnapService = __decorateClass([
586
+ injectable()
587
+ ], WorkflowSnapService);
588
+ var WorkflowSnapLayer = class extends Layer {
589
+ constructor() {
590
+ super(...arguments);
591
+ this.node = domUtils.createDivWithClass(
592
+ "gedit-playground-layer gedit-flow-snap-layer"
593
+ );
594
+ this.edgeLines = [];
595
+ this.alignLines = [];
596
+ }
597
+ onReady() {
598
+ this.node.style.zIndex = "9999";
599
+ this.toDispose.pushAll([
600
+ this.service.onSnap((event) => {
601
+ this.edgeLines = this.calcEdgeLines(event);
602
+ this.alignLines = this.calcAlignLines(event);
603
+ this.render();
604
+ })
605
+ ]);
606
+ }
607
+ render() {
608
+ const children = [];
609
+ if (this.alignLines.length > 0) {
610
+ children.push(
611
+ h("div", { class: "workflow-snap-align-lines" }, this.renderAlignLines())
612
+ );
613
+ }
614
+ if (this.edgeLines.length > 0) {
615
+ children.push(h("div", { class: "workflow-snap-edge-lines" }, this.renderEdgeLines()));
616
+ }
617
+ return h(Fragment, null, children);
618
+ }
619
+ onZoom(scale) {
620
+ this.node.style.transform = `scale(${scale})`;
621
+ }
622
+ renderEdgeLines() {
623
+ return this.edgeLines.map((renderLine) => {
624
+ const { className, sourceNode, top, left, width, height, dashed } = renderLine;
625
+ const id = `${className}-${sourceNode}-${top}-${left}-${width}-${height}`;
626
+ const isHorizontal = width < height;
627
+ const border = `${this.options.edgeLineWidth}px ${dashed ? "dashed" : "solid"} ${this.options.edgeColor}`;
628
+ return h("div", {
629
+ class: `workflow-snap-edge-line ${className}`,
630
+ "data-testid": "sdk.workflow.canvas.snap.edgeLine",
631
+ "data-snap-line-id": id,
632
+ "data-snap-line-source-node": sourceNode,
633
+ key: id,
634
+ style: {
635
+ top: `${top}px`,
636
+ left: `${left}px`,
637
+ width: `${width}px`,
638
+ height: `${height}px`,
639
+ position: "absolute",
640
+ borderLeft: isHorizontal ? border : "none",
641
+ borderTop: !isHorizontal ? border : "none"
642
+ }
643
+ });
644
+ });
645
+ }
646
+ renderAlignLines() {
647
+ return this.alignLines.map((renderLine) => {
648
+ const id = `${renderLine.className}-${renderLine.sourceNode}-${renderLine.top}-${renderLine.left}-${renderLine.width}-${renderLine.height}`;
649
+ const isHorizontal = isGreaterThan(renderLine.width, renderLine.height);
650
+ const alignLineWidth = this.options.alignLineWidth;
651
+ const alignCrossWidth = this.options.alignCrossWidth;
652
+ const adjustedTop = isHorizontal ? renderLine.top - alignLineWidth / 2 : renderLine.top;
653
+ const adjustedLeft = isHorizontal ? renderLine.left : renderLine.left - alignLineWidth / 2;
654
+ return h(
655
+ "div",
656
+ {
657
+ class: `workflow-snap-align-line ${renderLine.className}`,
658
+ "data-testid": "sdk.workflow.canvas.snap.alignLine",
659
+ "data-snap-line-id": id,
660
+ "data-snap-line-source-node": renderLine.sourceNode,
661
+ key: id,
662
+ style: {
663
+ position: "absolute"
664
+ }
665
+ },
666
+ [
667
+ h("div", {
668
+ style: {
669
+ position: "absolute",
670
+ top: `${adjustedTop}px`,
671
+ left: `${adjustedLeft}px`,
672
+ width: `${isHorizontal ? renderLine.width : alignLineWidth}px`,
673
+ height: `${isHorizontal ? alignLineWidth : renderLine.height}px`,
674
+ backgroundColor: this.options.alignColor
675
+ }
676
+ }),
677
+ h("div", {
678
+ style: {
679
+ position: "absolute",
680
+ top: `${isHorizontal ? adjustedTop - (alignCrossWidth - alignLineWidth) / 2 : adjustedTop}px`,
681
+ left: `${isHorizontal ? adjustedLeft : adjustedLeft - (alignCrossWidth - alignLineWidth) / 2}px`,
682
+ width: `${isHorizontal ? alignLineWidth : alignCrossWidth}px`,
683
+ height: `${isHorizontal ? alignCrossWidth : alignLineWidth}px`,
684
+ backgroundColor: this.options.alignColor
685
+ }
686
+ }),
687
+ h("div", {
688
+ style: {
689
+ position: "absolute",
690
+ top: `${isHorizontal ? adjustedTop - (alignCrossWidth - alignLineWidth) / 2 : adjustedTop + renderLine.height - alignLineWidth}px`,
691
+ left: `${isHorizontal ? adjustedLeft + renderLine.width - alignLineWidth : adjustedLeft - (alignCrossWidth - alignLineWidth) / 2}px`,
692
+ width: `${isHorizontal ? alignLineWidth : alignCrossWidth}px`,
693
+ height: `${isHorizontal ? alignCrossWidth : alignLineWidth}px`,
694
+ backgroundColor: this.options.alignColor
695
+ }
696
+ })
697
+ ]
698
+ );
699
+ });
700
+ }
701
+ calcEdgeLines(event) {
702
+ const { alignRects, snapRect, snapEdgeLines } = event;
703
+ const edgeLines = [];
704
+ const topFullAlign = this.directionFullAlign({
705
+ alignRects: alignRects.top,
706
+ targetRect: snapRect,
707
+ isVertical: true
708
+ });
709
+ const bottomFullAlign = this.directionFullAlign({
710
+ alignRects: alignRects.bottom,
711
+ targetRect: snapRect,
712
+ isVertical: true
713
+ });
714
+ const leftFullAlign = this.directionFullAlign({
715
+ alignRects: alignRects.left,
716
+ targetRect: snapRect,
717
+ isVertical: false
718
+ });
719
+ const rightFullAlign = this.directionFullAlign({
720
+ alignRects: alignRects.right,
721
+ targetRect: snapRect,
722
+ isVertical: false
723
+ });
724
+ if (topFullAlign) {
725
+ const top = topFullAlign.rect.top;
726
+ const height = bottomFullAlign ? snapRect.bottom - snapRect.height / 2 - top : snapRect.bottom - top;
727
+ const width = this.options.edgeLineWidth;
728
+ const lineData = {
729
+ top,
730
+ width,
731
+ height
732
+ };
733
+ edgeLines.push({
734
+ className: "edge-full-top-left",
735
+ sourceNode: topFullAlign.sourceNodeId,
736
+ left: snapRect.left,
737
+ ...lineData
738
+ });
739
+ edgeLines.push({
740
+ className: "edge-full-top-right",
741
+ sourceNode: topFullAlign.sourceNodeId,
742
+ left: snapRect.right - 1,
743
+ ...lineData
744
+ });
745
+ edgeLines.push({
746
+ className: "edge-full-top-mid",
747
+ sourceNode: topFullAlign.sourceNodeId,
748
+ left: snapRect.left + snapRect.width / 2,
749
+ dashed: true,
750
+ ...lineData
751
+ });
752
+ }
753
+ if (bottomFullAlign) {
754
+ const top = topFullAlign ? snapRect.top + snapRect.height / 2 : snapRect.top;
755
+ const height = bottomFullAlign.rect.bottom - top;
756
+ const width = this.options.edgeLineWidth;
757
+ const lineData = {
758
+ top,
759
+ width,
760
+ height
761
+ };
762
+ edgeLines.push({
763
+ className: "edge-full-bottom-left",
764
+ sourceNode: bottomFullAlign.sourceNodeId,
765
+ left: snapRect.left,
766
+ ...lineData
767
+ });
768
+ edgeLines.push({
769
+ className: "edge-full-bottom-right",
770
+ sourceNode: bottomFullAlign.sourceNodeId,
771
+ left: snapRect.right - 1,
772
+ ...lineData
773
+ });
774
+ edgeLines.push({
775
+ className: "edge-full-bottom-mid",
776
+ sourceNode: bottomFullAlign.sourceNodeId,
777
+ left: snapRect.left + snapRect.width / 2,
778
+ dashed: true,
779
+ ...lineData
780
+ });
781
+ }
782
+ if (leftFullAlign) {
783
+ const left = leftFullAlign.rect.left;
784
+ const width = rightFullAlign ? snapRect.right - snapRect.width / 2 - left : snapRect.right - left;
785
+ const height = this.options.edgeLineWidth;
786
+ const lineData = {
787
+ left,
788
+ width,
789
+ height
790
+ };
791
+ edgeLines.push({
792
+ className: "edge-full-left-top",
793
+ sourceNode: leftFullAlign.sourceNodeId,
794
+ top: snapRect.top,
795
+ ...lineData
796
+ });
797
+ edgeLines.push({
798
+ className: "edge-full-left-bottom",
799
+ sourceNode: leftFullAlign.sourceNodeId,
800
+ top: snapRect.bottom - 1,
801
+ ...lineData
802
+ });
803
+ edgeLines.push({
804
+ className: "edge-full-left-mid",
805
+ sourceNode: leftFullAlign.sourceNodeId,
806
+ top: snapRect.top + snapRect.height / 2,
807
+ dashed: true,
808
+ ...lineData
809
+ });
810
+ }
811
+ if (rightFullAlign) {
812
+ const left = leftFullAlign ? snapRect.left + snapRect.width / 2 : snapRect.left;
813
+ const width = rightFullAlign.rect.right - left;
814
+ const height = this.options.edgeLineWidth;
815
+ const lineData = {
816
+ left,
817
+ width,
818
+ height
819
+ };
820
+ edgeLines.push({
821
+ className: "edge-full-right-top",
822
+ sourceNode: rightFullAlign.sourceNodeId,
823
+ top: snapRect.top,
824
+ ...lineData
825
+ });
826
+ edgeLines.push({
827
+ className: "edge-full-right-bottom",
828
+ sourceNode: rightFullAlign.sourceNodeId,
829
+ top: snapRect.bottom - 1,
830
+ ...lineData
831
+ });
832
+ edgeLines.push({
833
+ className: "edge-full-right-mid",
834
+ sourceNode: rightFullAlign.sourceNodeId,
835
+ top: snapRect.top + snapRect.height / 2,
836
+ dashed: true,
837
+ ...lineData
838
+ });
839
+ }
840
+ const snappedEdgeLines = Object.entries(snapEdgeLines).map(([direction, snapLine]) => {
841
+ if (!snapLine) {
842
+ return;
843
+ }
844
+ const sourceNode = this.document.getNode(snapLine.sourceNodeId);
845
+ if (!sourceNode) {
846
+ return;
847
+ }
848
+ const nodeRect = sourceNode.getData(FlowNodeTransformData).bounds;
849
+ if (isNumber(snapLine.x)) {
850
+ const top = Math.min(nodeRect.top, snapRect.top);
851
+ const bottom = Math.max(nodeRect.bottom, snapRect.bottom);
852
+ const height = bottom - top;
853
+ const left = direction === "right" ? snapLine.x - 1 : snapLine.x;
854
+ const width = this.options.edgeLineWidth;
855
+ const isMidX = direction === "midVertical";
856
+ const lineData = {
857
+ className: `edge-snapped-${direction}`,
858
+ sourceNode: snapLine.sourceNodeId,
859
+ top,
860
+ left,
861
+ width,
862
+ height,
863
+ dashed: isMidX
864
+ };
865
+ const onTop = top === nodeRect.top;
866
+ if (onTop && topFullAlign) {
867
+ return;
868
+ }
869
+ if (!onTop && bottomFullAlign) {
870
+ return;
871
+ }
872
+ return lineData;
873
+ } else if (isNumber(snapLine.y)) {
874
+ const left = Math.min(nodeRect.left, snapRect.left);
875
+ const right = Math.max(nodeRect.right, snapRect.right);
876
+ const width = right - left;
877
+ const top = direction === "bottom" ? snapLine.y - 1 : snapLine.y;
878
+ const height = this.options.edgeLineWidth;
879
+ const isMidY = direction === "midHorizontal";
880
+ const lineData = {
881
+ className: `edge-snapped-${direction}`,
882
+ sourceNode: snapLine.sourceNodeId,
883
+ top,
884
+ left,
885
+ width,
886
+ height,
887
+ dashed: isMidY
888
+ };
889
+ const onLeft = left === nodeRect.left;
890
+ if (onLeft && leftFullAlign) {
891
+ return;
892
+ }
893
+ if (!onLeft && rightFullAlign) {
894
+ return;
895
+ }
896
+ return lineData;
897
+ }
898
+ }).filter(Boolean);
899
+ edgeLines.push(...snappedEdgeLines);
900
+ return edgeLines;
901
+ }
902
+ directionFullAlign(params) {
903
+ const { alignRects, targetRect, isVertical } = params;
904
+ let fullAlignIndex = -1;
905
+ for (let i = 0; i < alignRects.length; i++) {
906
+ const alignRect = alignRects[i];
907
+ const prevRect = alignRects[i - 1]?.rect ?? targetRect;
908
+ const isFullAlign = this.rectFullAlign(alignRect.rect, prevRect, isVertical);
909
+ if (!isFullAlign) {
910
+ break;
911
+ }
912
+ fullAlignIndex = i;
913
+ }
914
+ const fullAlignRect = alignRects[fullAlignIndex];
915
+ return fullAlignRect;
916
+ }
917
+ rectFullAlign(rectA, rectB, isVertical) {
918
+ if (isVertical) {
919
+ return isEqual(rectA.left, rectB.left) && isEqual(rectA.right, rectB.right);
920
+ } else {
921
+ return isEqual(rectA.top, rectB.top) && isEqual(rectA.bottom, rectB.bottom);
922
+ }
923
+ }
924
+ calcAlignLines(event) {
925
+ const { alignRects, alignSpacing, snapRect } = event;
926
+ const topAlignLines = this.calcDirectionAlignLines({
927
+ alignRects: alignRects.top,
928
+ targetRect: snapRect,
929
+ isVertical: true,
930
+ spacing: alignSpacing.midVertical ?? alignSpacing.top
931
+ });
932
+ const bottomAlignLines = this.calcDirectionAlignLines({
933
+ alignRects: alignRects.bottom,
934
+ targetRect: snapRect,
935
+ isVertical: true,
936
+ spacing: alignSpacing.midVertical ?? alignSpacing.bottom
937
+ });
938
+ const leftAlignLines = this.calcDirectionAlignLines({
939
+ alignRects: alignRects.left,
940
+ targetRect: snapRect,
941
+ isVertical: false,
942
+ spacing: alignSpacing.midHorizontal ?? alignSpacing.left
943
+ });
944
+ const rightAlignLines = this.calcDirectionAlignLines({
945
+ alignRects: alignRects.right,
946
+ targetRect: snapRect,
947
+ isVertical: false,
948
+ spacing: alignSpacing.midHorizontal ?? alignSpacing.right
949
+ });
950
+ return [...topAlignLines, ...bottomAlignLines, ...leftAlignLines, ...rightAlignLines];
951
+ }
952
+ calcDirectionAlignLines(params) {
953
+ const { alignRects, targetRect, isVertical, spacing } = params;
954
+ const alignLines = [];
955
+ if (!spacing) {
956
+ return alignLines;
957
+ }
958
+ for (let i = 0; i < alignRects.length; i++) {
959
+ const alignRect = alignRects[i];
960
+ const rect = alignRect.rect;
961
+ const prevRect = alignRects[i - 1]?.rect ?? targetRect;
962
+ const betweenSpacing = isVertical ? Math.min(Math.abs(prevRect.top - rect.bottom), Math.abs(prevRect.bottom - rect.top)) : Math.min(Math.abs(prevRect.left - rect.right), Math.abs(prevRect.right - rect.left));
963
+ if (!isEqual(betweenSpacing, spacing)) {
964
+ break;
965
+ }
966
+ if (isVertical) {
967
+ const centerX = this.calcHorizontalIntersectionCenter(rect, targetRect);
968
+ alignLines.push({
969
+ className: "align-vertical",
970
+ sourceNode: alignRect.sourceNodeId,
971
+ top: Math.min(rect.bottom, prevRect.bottom),
972
+ left: centerX,
973
+ width: 1,
974
+ height: spacing
975
+ });
976
+ } else {
977
+ const centerY = this.calcVerticalIntersectionCenter(rect, targetRect);
978
+ alignLines.push({
979
+ className: "align-horizontal",
980
+ sourceNode: alignRect.sourceNodeId,
981
+ top: centerY,
982
+ left: Math.min(rect.right, prevRect.right),
983
+ width: spacing,
984
+ height: 1
985
+ });
986
+ }
987
+ }
988
+ return alignLines;
989
+ }
990
+ calcVerticalIntersectionCenter(rectA, rectB) {
991
+ const top = Math.max(rectA.top, rectB.top);
992
+ const bottom = Math.min(rectA.bottom, rectB.bottom);
993
+ return (top + bottom) / 2;
994
+ }
995
+ calcHorizontalIntersectionCenter(rectA, rectB) {
996
+ const left = Math.max(rectA.left, rectB.left);
997
+ const right = Math.min(rectA.right, rectB.right);
998
+ return (left + right) / 2;
999
+ }
1000
+ };
1001
+ WorkflowSnapLayer.type = "WorkflowSnapLayer";
1002
+ __decorateClass([
1003
+ inject(WorkflowDocument)
1004
+ ], WorkflowSnapLayer.prototype, "document", 2);
1005
+ __decorateClass([
1006
+ inject(WorkflowSnapService)
1007
+ ], WorkflowSnapLayer.prototype, "service", 2);
1008
+ WorkflowSnapLayer = __decorateClass([
1009
+ injectable()
1010
+ ], WorkflowSnapLayer);
1011
+
1012
+ // src/create-plugin.ts
1013
+ var createFreeSnapPlugin = definePluginCreator({
1014
+ onBind({ bind }) {
1015
+ bind(WorkflowSnapService).toSelf().inSingletonScope();
1016
+ },
1017
+ onInit(ctx, opts) {
1018
+ const options = {
1019
+ ...SnapDefaultOptions,
1020
+ ...opts
1021
+ };
1022
+ ctx.playground.registerLayer(WorkflowSnapLayer, options);
1023
+ const snapService = ctx.get(WorkflowSnapService);
1024
+ snapService.init(options);
1025
+ },
1026
+ onDispose(ctx) {
1027
+ const snapService = ctx.get(WorkflowSnapService);
1028
+ snapService.dispose();
1029
+ }
1030
+ });
1031
+
1032
+ export { WorkflowSnapService, createFreeSnapPlugin };
1033
+ //# sourceMappingURL=index.js.map
1034
+ //# sourceMappingURL=index.js.map