@flowgram-vue/free-lines-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.
Files changed (38) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +1026 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.css +120 -0
  5. package/dist/index.css.map +1 -0
  6. package/dist/index.d.ts +269 -0
  7. package/dist/index.js +1013 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +63 -0
  10. package/src/__tests__/__snapshots__/bezier-controls.spec.ts.snap +20 -0
  11. package/src/__tests__/bezier-controls.spec.ts +25 -0
  12. package/src/components/index.ts +8 -0
  13. package/src/components/workflow-line-render/arrow.ts +60 -0
  14. package/src/components/workflow-line-render/index.css +22 -0
  15. package/src/components/workflow-line-render/index.ts +6 -0
  16. package/src/components/workflow-line-render/line-svg.ts +131 -0
  17. package/src/components/workflow-port-render/cross-hair.ts +16 -0
  18. package/src/components/workflow-port-render/index.css +121 -0
  19. package/src/components/workflow-port-render/index.ts +183 -0
  20. package/src/constants/lines.ts +9 -0
  21. package/src/constants/points.ts +12 -0
  22. package/src/contributions/bezier/bezier-controls.ts +105 -0
  23. package/src/contributions/bezier/index.ts +121 -0
  24. package/src/contributions/fold/fold-line.ts +293 -0
  25. package/src/contributions/fold/index.ts +97 -0
  26. package/src/contributions/index.ts +8 -0
  27. package/src/contributions/straight/index.ts +89 -0
  28. package/src/contributions/straight/point-on-line.ts +37 -0
  29. package/src/contributions/utils.ts +37 -0
  30. package/src/create-free-lines-plugin.ts +44 -0
  31. package/src/css.d.ts +6 -0
  32. package/src/env.d.ts +10 -0
  33. package/src/index.ts +12 -0
  34. package/src/layer/index.ts +8 -0
  35. package/src/layer/workflow-lines-layer.ts +201 -0
  36. package/src/type.ts +40 -0
  37. package/src/types/arrow-renderer.ts +32 -0
  38. package/src/wrap-layer-render.ts +38 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1026 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
4
+ var classNames = require('clsx');
5
+ var freeLayoutCore = require('@flowgram-vue/free-layout-core');
6
+ var core = require('@flowgram-vue/core');
7
+ var inversify = require('inversify');
8
+ var utils = require('@flowgram-vue/utils');
9
+ var renderer = require('@flowgram-vue/renderer');
10
+ var freeStackPlugin = require('@flowgram-vue/free-stack-plugin');
11
+ var bezierJs = require('bezier-js');
12
+
13
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
+
15
+ var classNames__default = /*#__PURE__*/_interopDefault(classNames);
16
+
17
+ var __defProp = Object.defineProperty;
18
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result) __defProp(target, key, result);
25
+ return result;
26
+ };
27
+
28
+ // src/constants/points.ts
29
+ var STROKE_WIDTH_SLECTED = 3;
30
+ var STROKE_WIDTH = 2;
31
+ var PORT_BG_CLASS_NAME = "workflow-port-bg";
32
+ var CrossHair = vue.defineComponent({
33
+ name: "CrossHair",
34
+ setup() {
35
+ return () => vue.h("div", { class: "symbol" }, [vue.h("div", { class: "cross-hair" })]);
36
+ }
37
+ });
38
+ var cross_hair_default = CrossHair;
39
+
40
+ // src/components/workflow-port-render/index.ts
41
+ var WorkflowPortRender = vue.defineComponent({
42
+ name: "WorkflowPortRender",
43
+ props: {
44
+ entity: { type: Object, required: true },
45
+ className: { type: String, default: "" },
46
+ style: { type: Object },
47
+ onClick: {
48
+ type: Function
49
+ },
50
+ primaryColor: { type: String },
51
+ secondaryColor: { type: String },
52
+ errorColor: { type: String },
53
+ backgroundColor: { type: String }
54
+ },
55
+ setup(props) {
56
+ const hoverService = core.useService(freeLayoutCore.WorkflowHoverService);
57
+ const linesManager = core.useService(freeLayoutCore.WorkflowLinesManager);
58
+ const targetElement = vue.shallowRef(props.entity.targetElement);
59
+ const posX = vue.shallowRef(props.entity.relativePosition.x);
60
+ const posY = vue.shallowRef(props.entity.relativePosition.y);
61
+ const hovered = vue.shallowRef(false);
62
+ const linked = vue.shallowRef(Boolean(props.entity?.lines?.length));
63
+ const hasError = vue.shallowRef(props.entity.hasError);
64
+ const readonly = freeLayoutCore.usePlaygroundReadonlyState();
65
+ let disposers = [];
66
+ const bindEntity = () => {
67
+ disposers.forEach((dispose) => dispose.dispose());
68
+ disposers = [];
69
+ const { entity } = props;
70
+ entity.validate();
71
+ hasError.value = entity.hasError;
72
+ disposers.push(
73
+ entity.onEntityChange(() => {
74
+ if (entity.targetElement) {
75
+ if (entity.targetElement !== targetElement.value) {
76
+ targetElement.value = entity.targetElement;
77
+ }
78
+ return;
79
+ }
80
+ const newPos = entity.relativePosition;
81
+ posX.value = Math.round(newPos.x);
82
+ posY.value = Math.round(newPos.y);
83
+ }),
84
+ hoverService.onHoveredChange(() => {
85
+ hovered.value = hoverService.isHovered(entity.id);
86
+ }),
87
+ entity.onErrorChanged(() => {
88
+ hasError.value = entity.hasError;
89
+ }),
90
+ linesManager.onAvailableLinesChange(() => {
91
+ setTimeout(() => {
92
+ if (linesManager.disposed || entity.disposed) return;
93
+ linked.value = Boolean(entity.lines.length);
94
+ }, 0);
95
+ })
96
+ );
97
+ };
98
+ vue.watch(
99
+ () => [props.entity, targetElement.value],
100
+ () => bindEntity(),
101
+ { immediate: true }
102
+ );
103
+ vue.onBeforeUnmount(() => {
104
+ disposers.forEach((dispose) => dispose.dispose());
105
+ });
106
+ return () => {
107
+ const { entity, onClick } = props;
108
+ const { disabled } = entity;
109
+ const className = classNames__default.default("workflow-port-render", props.className || "", {
110
+ hovered: !readonly.value && hovered.value && !disabled,
111
+ linked: linked.value
112
+ });
113
+ const colorStyles = {};
114
+ if (props.primaryColor) {
115
+ colorStyles["--g-workflow-port-color-primary"] = props.primaryColor;
116
+ }
117
+ if (props.secondaryColor) {
118
+ colorStyles["--g-workflow-port-color-secondary"] = props.secondaryColor;
119
+ }
120
+ if (props.errorColor) {
121
+ colorStyles["--g-workflow-port-color-error"] = props.errorColor;
122
+ }
123
+ if (props.backgroundColor) {
124
+ colorStyles["--g-workflow-port-color-background"] = props.backgroundColor;
125
+ }
126
+ const combinedStyle = targetElement.value ? { ...props.style, ...colorStyles } : { ...props.style, ...colorStyles, left: `${posX.value}px`, top: `${posY.value}px` };
127
+ const content = vue.h(
128
+ "div",
129
+ {
130
+ class: className,
131
+ style: combinedStyle,
132
+ onClick: (e) => onClick?.(e, entity),
133
+ onTouchstart: (e) => {
134
+ if (!onClick) {
135
+ return;
136
+ }
137
+ core.MouseTouchEvent.onTouched(e, (mouseEvent) => {
138
+ onClick(mouseEvent, entity);
139
+ });
140
+ },
141
+ "data-port-entity-id": entity.id,
142
+ "data-port-entity-type": entity.portType,
143
+ "data-testid": "sdk.workflow.canvas.node.port"
144
+ },
145
+ [
146
+ vue.h("div", { class: classNames__default.default("bg-circle", "workflow-bg-circle") }),
147
+ vue.h(
148
+ "div",
149
+ {
150
+ class: classNames__default.default({
151
+ bg: true,
152
+ [PORT_BG_CLASS_NAME]: true,
153
+ "workflow-point-bg": true,
154
+ hasError: hasError.value
155
+ })
156
+ },
157
+ [vue.h(cross_hair_default)]
158
+ ),
159
+ vue.h("div", { class: "focus-circle" })
160
+ ]
161
+ );
162
+ if (targetElement.value) {
163
+ return vue.h(vue.Teleport, { to: targetElement.value }, [content]);
164
+ }
165
+ return content;
166
+ };
167
+ }
168
+ });
169
+
170
+ // src/constants/lines.ts
171
+ var LINE_OFFSET = 6;
172
+ var LINE_PADDING = 12;
173
+ var LayerVueProvide = vue.defineComponent({
174
+ name: "LayerVueProvide",
175
+ props: {
176
+ factory: {
177
+ type: Object,
178
+ required: true
179
+ }
180
+ },
181
+ setup(props) {
182
+ const factory = props.factory;
183
+ vue.provide(core.PlaygroundVueContainerKey, factory);
184
+ try {
185
+ vue.provide(core.PlaygroundVueRefKey, factory.get(core.Playground));
186
+ } catch {
187
+ }
188
+ },
189
+ render() {
190
+ return this.$slots.default?.();
191
+ }
192
+ });
193
+ function wrapLayerRender(factory, children) {
194
+ return vue.h(LayerVueProvide, { factory }, () => children);
195
+ }
196
+
197
+ // src/contributions/utils.ts
198
+ function toRelative(p, bbox) {
199
+ return {
200
+ x: p.x - bbox.x + LINE_PADDING,
201
+ y: p.y - bbox.y + LINE_PADDING
202
+ };
203
+ }
204
+ function getShrinkOffset(location, shrink) {
205
+ switch (location) {
206
+ case "left":
207
+ return { x: -shrink, y: 0 };
208
+ case "right":
209
+ return { x: shrink, y: 0 };
210
+ case "bottom":
211
+ return { x: 0, y: shrink };
212
+ case "top":
213
+ return { x: 0, y: -shrink };
214
+ }
215
+ }
216
+ function posWithShrink(pos, location, shrink) {
217
+ const offset = getShrinkOffset(location, shrink);
218
+ return {
219
+ x: pos.x + offset.x,
220
+ y: pos.y + offset.y
221
+ };
222
+ }
223
+ function getArrowPath(pos, location) {
224
+ switch (location) {
225
+ case "left":
226
+ return `M ${pos.x - LINE_OFFSET},${pos.y - LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x - LINE_OFFSET},${pos.y + LINE_OFFSET}`;
227
+ case "right":
228
+ return `M ${pos.x + LINE_OFFSET},${pos.y + LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x + LINE_OFFSET},${pos.y - LINE_OFFSET}`;
229
+ case "bottom":
230
+ return `M ${pos.x - LINE_OFFSET},${pos.y + LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x + LINE_OFFSET},${pos.y + LINE_OFFSET}`;
231
+ case "top":
232
+ return `M ${pos.x - LINE_OFFSET},${pos.y - LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x + LINE_OFFSET},${pos.y - LINE_OFFSET}`;
233
+ }
234
+ }
235
+ var ArrowRenderer = vue.defineComponent({
236
+ name: "ArrowRenderer",
237
+ props: {
238
+ id: { type: String, required: true },
239
+ pos: { type: Object, required: true },
240
+ location: { type: String, required: true },
241
+ strokeWidth: { type: Number, required: true },
242
+ hide: { type: Boolean, default: false },
243
+ line: { type: Object, required: true }
244
+ },
245
+ setup(props) {
246
+ return () => {
247
+ if (props.hide) {
248
+ return null;
249
+ }
250
+ const arrowPath = getArrowPath(props.pos, props.location);
251
+ return vue.h("path", {
252
+ d: arrowPath,
253
+ "stroke-linecap": "round",
254
+ stroke: `url(#${props.id})`,
255
+ fill: "none",
256
+ "stroke-width": props.strokeWidth
257
+ });
258
+ };
259
+ }
260
+ });
261
+
262
+ // src/components/workflow-line-render/line-svg.ts
263
+ var PADDING = 12;
264
+ var LineSVG = vue.defineComponent({
265
+ name: "LineSVG",
266
+ props: {
267
+ color: { type: String },
268
+ selected: { type: Boolean, default: false },
269
+ hovered: { type: Boolean, default: false },
270
+ line: { type: Object, required: true },
271
+ lineType: { type: [String, Number] },
272
+ version: { type: String, required: true },
273
+ strokePrefix: { type: String },
274
+ rendererRegistry: {
275
+ type: Object
276
+ }
277
+ },
278
+ setup(props, { slots }) {
279
+ return () => {
280
+ const { line, color, selected, strokePrefix, rendererRegistry } = props;
281
+ const { position, reverse, hideArrow, vertical } = line;
282
+ const renderData = line.getData(freeLayoutCore.WorkflowLineRenderData);
283
+ const { bounds, path: bezierPath } = renderData;
284
+ const toRelative2 = (p) => ({
285
+ x: p.x - bounds.x + PADDING,
286
+ y: p.y - bounds.y + PADDING
287
+ });
288
+ const fromPos = toRelative2(position.from);
289
+ const toPos = toRelative2(position.to);
290
+ const arrowToPos = posWithShrink(toPos, position.to.location, line.uiState.shrink);
291
+ const arrowFromPos = posWithShrink(
292
+ fromPos,
293
+ position.from.location,
294
+ line.uiState.shrink
295
+ );
296
+ const strokeWidth = selected ? line.uiState.strokeWidthSelected ?? STROKE_WIDTH_SLECTED : line.uiState.strokeWidth ?? STROKE_WIDTH;
297
+ const strokeID = strokePrefix ? `${strokePrefix}-${line.id}` : line.id;
298
+ const CustomArrowRenderer = rendererRegistry?.tryToGetRendererComponent(
299
+ renderer.FlowRendererKey.ARROW_RENDERER
300
+ )?.renderer;
301
+ const ArrowComponent = CustomArrowRenderer || ArrowRenderer;
302
+ const pathNode = vue.h("path", {
303
+ d: bezierPath,
304
+ fill: "none",
305
+ stroke: `url(#${strokeID})`,
306
+ "stroke-width": strokeWidth,
307
+ class: line.processing || line.flowing ? "dashed-line flowing-line" : ""
308
+ });
309
+ return vue.h(
310
+ "div",
311
+ {
312
+ class: classNames__default.default("gedit-flow-activity-edge", line.className),
313
+ style: {
314
+ ...line.uiState.style,
315
+ left: `${bounds.x - PADDING}px`,
316
+ top: `${bounds.y - PADDING}px`,
317
+ position: "absolute"
318
+ }
319
+ },
320
+ [
321
+ slots.default?.(),
322
+ vue.h(
323
+ "svg",
324
+ {
325
+ width: bounds.width + PADDING * 2,
326
+ height: bounds.height + PADDING * 2
327
+ },
328
+ [
329
+ vue.h("defs", null, [
330
+ vue.h(
331
+ "linearGradient",
332
+ {
333
+ x1: vertical ? "100%" : "0%",
334
+ y1: vertical ? "0%" : "100%",
335
+ x2: "100%",
336
+ y2: "100%",
337
+ id: strokeID,
338
+ gradientUnits: "userSpaceOnUse"
339
+ },
340
+ [
341
+ vue.h("stop", { "stop-color": color, offset: "0%" }),
342
+ vue.h("stop", { "stop-color": color, offset: "100%" })
343
+ ]
344
+ )
345
+ ]),
346
+ vue.h("g", null, [
347
+ pathNode,
348
+ vue.h(ArrowComponent, {
349
+ id: strokeID,
350
+ pos: reverse ? arrowFromPos : arrowToPos,
351
+ strokeWidth,
352
+ location: reverse ? position.from.location : position.to.location,
353
+ hide: hideArrow,
354
+ line
355
+ })
356
+ ])
357
+ ]
358
+ )
359
+ ]
360
+ );
361
+ };
362
+ }
363
+ });
364
+
365
+ // src/layer/workflow-lines-layer.ts
366
+ exports.WorkflowLinesLayer = class WorkflowLinesLayer extends core.Layer {
367
+ constructor() {
368
+ super(...arguments);
369
+ this.layerID = freeLayoutCore.nanoid();
370
+ this.mountedLines = /* @__PURE__ */ new Map();
371
+ this._version = 0;
372
+ /**
373
+ * 节点线条
374
+ */
375
+ this.node = utils.domUtils.createDivWithClass("gedit-playground-layer gedit-flow-lines-layer");
376
+ }
377
+ onZoom(scale) {
378
+ this.node.style.transform = `scale(${scale})`;
379
+ }
380
+ onReady() {
381
+ this.pipelineNode.appendChild(this.node);
382
+ this.toDispose.pushAll([
383
+ this.selectService.onSelectionChanged(() => this.render()),
384
+ this.hoverService.onHoveredChange(() => this.render()),
385
+ this.workflowDocument.linesManager.onForceUpdate(() => {
386
+ this.mountedLines.clear();
387
+ this.bumpVersion();
388
+ this.render();
389
+ })
390
+ ]);
391
+ }
392
+ dispose() {
393
+ if (this.rafId != null) {
394
+ cancelAnimationFrame(this.rafId);
395
+ this.rafId = void 0;
396
+ }
397
+ this.mountedLines.clear();
398
+ }
399
+ render() {
400
+ this.scheduleLineRenderUpdate();
401
+ const lines = this.lines.map((line) => this.renderLine(line));
402
+ return wrapLayerRender(this.playgroundContainer, vue.h(vue.Fragment, null, lines));
403
+ }
404
+ scheduleLineRenderUpdate() {
405
+ if (this.rafId != null) {
406
+ cancelAnimationFrame(this.rafId);
407
+ }
408
+ this.rafId = requestAnimationFrame(() => {
409
+ this.rafId = void 0;
410
+ let needsUpdate = false;
411
+ this.lines.forEach((line) => {
412
+ const renderData = line.getData(freeLayoutCore.WorkflowLineRenderData);
413
+ const oldVersion = renderData.renderVersion;
414
+ renderData.update();
415
+ if (renderData.renderVersion !== oldVersion) {
416
+ needsUpdate = true;
417
+ }
418
+ });
419
+ if (needsUpdate) {
420
+ this.render();
421
+ }
422
+ });
423
+ }
424
+ // 用来绕过 memo
425
+ bumpVersion() {
426
+ this._version = this._version + 1;
427
+ if (this._version === Number.MAX_SAFE_INTEGER) {
428
+ this._version = 0;
429
+ }
430
+ }
431
+ lineProps(line) {
432
+ const { lineType } = this.workflowDocument.linesManager;
433
+ const selected = this.selectService.isSelected(line.id);
434
+ const hovered = this.hoverService.isHovered(line.id);
435
+ const version = this.lineVersion(line);
436
+ const oldProps = {
437
+ key: line.id,
438
+ color: line.color,
439
+ selected,
440
+ hovered,
441
+ line,
442
+ lineType,
443
+ version,
444
+ strokePrefix: this.layerID,
445
+ rendererRegistry: this.rendererRegistry
446
+ };
447
+ return this.options.customLineProps ? this.options.customLineProps(line, oldProps) : oldProps;
448
+ }
449
+ lineVersion(line) {
450
+ const renderData = line.getData(freeLayoutCore.WorkflowLineRenderData);
451
+ const { renderVersion } = renderData;
452
+ const selected = this.selectService.isSelected(line.id);
453
+ const hovered = this.hoverService.isHovered(line.id);
454
+ const { version: lineVersion, color } = line;
455
+ const version = `v:${this._version},lv:${lineVersion},rv:${renderVersion},c:${color},s:${selected ? "T" : "F"},h:${hovered ? "T" : "F"}`;
456
+ return version;
457
+ }
458
+ lineComponent(props) {
459
+ const RenderInsideLine = this.options.renderInsideLine;
460
+ const RenderLine = this.options.renderLine ?? LineSVG;
461
+ const inside = RenderInsideLine ? vue.h(RenderInsideLine, props) : null;
462
+ return vue.h(RenderLine, props, () => inside);
463
+ }
464
+ renderLine(line) {
465
+ const lineProps = this.lineProps(line);
466
+ const cache = this.mountedLines.get(line.id);
467
+ const isCached = cache !== void 0;
468
+ const { portal: cachedPortal, version: cachedVersion } = cache ?? {};
469
+ if (isCached && cachedVersion === lineProps.version) {
470
+ return cachedPortal;
471
+ }
472
+ if (!isCached) {
473
+ this.renderElement.appendChild(line.node);
474
+ line.onDispose(() => {
475
+ this.mountedLines.delete(line.id);
476
+ line.node.remove();
477
+ });
478
+ }
479
+ const portal = vue.h(vue.Teleport, { to: line.node, key: line.id }, [this.lineComponent(lineProps)]);
480
+ this.mountedLines.set(line.id, { line, portal, version: lineProps.version });
481
+ return portal;
482
+ }
483
+ get renderElement() {
484
+ return this.stackContext.node;
485
+ }
486
+ };
487
+ exports.WorkflowLinesLayer.type = "WorkflowLinesLayer";
488
+ __decorateClass([
489
+ inversify.inject(freeLayoutCore.WorkflowHoverService)
490
+ ], exports.WorkflowLinesLayer.prototype, "hoverService", 2);
491
+ __decorateClass([
492
+ inversify.inject(freeLayoutCore.WorkflowSelectService)
493
+ ], exports.WorkflowLinesLayer.prototype, "selectService", 2);
494
+ __decorateClass([
495
+ inversify.inject(freeStackPlugin.StackingContextManager)
496
+ ], exports.WorkflowLinesLayer.prototype, "stackContext", 2);
497
+ __decorateClass([
498
+ inversify.inject(renderer.FlowRendererRegistry)
499
+ ], exports.WorkflowLinesLayer.prototype, "rendererRegistry", 2);
500
+ __decorateClass([
501
+ inversify.inject(core.PlaygroundContainerFactory)
502
+ ], exports.WorkflowLinesLayer.prototype, "playgroundContainer", 2);
503
+ __decorateClass([
504
+ core.observeEntities(freeLayoutCore.WorkflowLineEntity)
505
+ ], exports.WorkflowLinesLayer.prototype, "lines", 2);
506
+ __decorateClass([
507
+ core.observeEntities(freeLayoutCore.WorkflowPortEntity)
508
+ ], exports.WorkflowLinesLayer.prototype, "ports", 2);
509
+ __decorateClass([
510
+ core.observeEntityDatas(freeLayoutCore.WorkflowNodeEntity, core.TransformData)
511
+ ], exports.WorkflowLinesLayer.prototype, "trans", 2);
512
+ __decorateClass([
513
+ inversify.inject(freeLayoutCore.WorkflowDocument)
514
+ ], exports.WorkflowLinesLayer.prototype, "workflowDocument", 2);
515
+ exports.WorkflowLinesLayer = __decorateClass([
516
+ inversify.injectable()
517
+ ], exports.WorkflowLinesLayer);
518
+
519
+ // src/contributions/bezier/bezier-controls.ts
520
+ function getBezierEdgeCenter(fromPos, toPos, fromControl, toControl) {
521
+ const x = fromPos.x * 0.125 + fromControl.x * 0.375 + toControl.x * 0.375 + toPos.x * 0.125;
522
+ const y = fromPos.y * 0.125 + fromControl.y * 0.375 + toControl.y * 0.375 + toPos.y * 0.125;
523
+ return {
524
+ x,
525
+ y
526
+ };
527
+ }
528
+ function getControlOffset(distance, curvature) {
529
+ if (distance >= 0) {
530
+ return 0.5 * distance;
531
+ }
532
+ return curvature * 25 * Math.sqrt(-distance);
533
+ }
534
+ function getControlWithCurvature({
535
+ location,
536
+ x1,
537
+ y1,
538
+ x2,
539
+ y2,
540
+ curvature
541
+ }) {
542
+ switch (location) {
543
+ case "left":
544
+ return {
545
+ x: x1 - getControlOffset(x1 - x2, curvature),
546
+ y: y1
547
+ };
548
+ case "right":
549
+ return {
550
+ x: x1 + getControlOffset(x2 - x1, curvature),
551
+ y: y1
552
+ };
553
+ case "top":
554
+ return {
555
+ x: x1,
556
+ y: y1 - getControlOffset(y1 - y2, curvature)
557
+ };
558
+ case "bottom":
559
+ return {
560
+ x: x1,
561
+ y: y1 + getControlOffset(y2 - y1, curvature)
562
+ };
563
+ }
564
+ }
565
+ function getBezierControlPoints(fromPos, toPos, curvature = 0.25) {
566
+ const fromControl = getControlWithCurvature({
567
+ location: fromPos.location,
568
+ x1: fromPos.x,
569
+ y1: fromPos.y,
570
+ x2: toPos.x,
571
+ y2: toPos.y,
572
+ curvature
573
+ });
574
+ const toControl = getControlWithCurvature({
575
+ location: toPos.location,
576
+ x1: toPos.x,
577
+ y1: toPos.y,
578
+ x2: fromPos.x,
579
+ y2: fromPos.y,
580
+ curvature
581
+ });
582
+ const center = getBezierEdgeCenter(fromPos, toPos, fromControl, toControl);
583
+ return {
584
+ controls: [fromControl, toControl],
585
+ center
586
+ };
587
+ }
588
+
589
+ // src/contributions/bezier/index.ts
590
+ var WorkflowBezierLineContribution = class {
591
+ constructor(entity) {
592
+ this.entity = entity;
593
+ }
594
+ get path() {
595
+ return this.data?.path ?? "";
596
+ }
597
+ calcDistance(pos) {
598
+ if (!this.data) {
599
+ return Number.MAX_SAFE_INTEGER;
600
+ }
601
+ return utils.Point.getDistance(pos, this.data.bezier.project(pos));
602
+ }
603
+ get bounds() {
604
+ if (!this.data) {
605
+ return utils.Rectangle.EMPTY;
606
+ }
607
+ return this.data.bbox;
608
+ }
609
+ get center() {
610
+ return this.data?.center;
611
+ }
612
+ update(params) {
613
+ this.data = this.calcBezier(params.fromPos, params.toPos);
614
+ }
615
+ calcBezier(fromPos, toPos) {
616
+ const { controls, center } = getBezierControlPoints(
617
+ fromPos,
618
+ toPos,
619
+ this.entity.uiState.curvature
620
+ );
621
+ const bezier = new bezierJs.Bezier([fromPos, ...controls, toPos]);
622
+ const bbox = bezier.bbox();
623
+ const bboxBounds = new utils.Rectangle(
624
+ bbox.x.min,
625
+ bbox.y.min,
626
+ bbox.x.max - bbox.x.min,
627
+ bbox.y.max - bbox.y.min
628
+ );
629
+ const centerPoint = toRelative(center, bboxBounds);
630
+ const path = this.getPath({ bbox: bboxBounds, fromPos, toPos, controls });
631
+ this.data = {
632
+ fromPos,
633
+ toPos,
634
+ bezier,
635
+ bbox: bboxBounds,
636
+ controls,
637
+ path,
638
+ center: {
639
+ ...center,
640
+ labelX: centerPoint.x,
641
+ labelY: centerPoint.y
642
+ }
643
+ };
644
+ return this.data;
645
+ }
646
+ getPath(params) {
647
+ const { bbox } = params;
648
+ const fromPos = toRelative(params.fromPos, bbox);
649
+ const toPos = toRelative(params.toPos, bbox);
650
+ const controls = params.controls.map((c) => toRelative(c, bbox));
651
+ const shrink = this.entity.uiState.shrink;
652
+ const renderFromPos = posWithShrink(fromPos, params.fromPos.location, shrink);
653
+ const renderToPos = posWithShrink(toPos, params.toPos.location, shrink);
654
+ const controlPoints = controls.map((s) => `${s.x} ${s.y}`).join(",");
655
+ return `M${renderFromPos.x} ${renderFromPos.y} C ${controlPoints}, ${renderToPos.x} ${renderToPos.y}`;
656
+ }
657
+ };
658
+ WorkflowBezierLineContribution.type = freeLayoutCore.LineType.BEZIER;
659
+ var getPointToSegmentDistance = (point, segStart, segEnd) => {
660
+ const { x: px, y: py } = point;
661
+ const { x: x1, y: y1 } = segStart;
662
+ const { x: x2, y: y2 } = segEnd;
663
+ const A = px - x1;
664
+ const B = py - y1;
665
+ const C = x2 - x1;
666
+ const D = y2 - y1;
667
+ const dot = A * C + B * D;
668
+ const lenSq = C * C + D * D;
669
+ const param = lenSq === 0 ? -1 : dot / lenSq;
670
+ let xx;
671
+ let yy;
672
+ if (param < 0) {
673
+ xx = x1;
674
+ yy = y1;
675
+ } else if (param > 1) {
676
+ xx = x2;
677
+ yy = y2;
678
+ } else {
679
+ xx = x1 + param * C;
680
+ yy = y1 + param * D;
681
+ }
682
+ const dx = px - xx;
683
+ const dy = py - yy;
684
+ return Math.sqrt(dx * dx + dy * dy);
685
+ };
686
+ var FoldLine;
687
+ ((FoldLine2) => {
688
+ const EDGE_RADIUS = 5;
689
+ const OFFSET = 20;
690
+ function getEdgeCenter({ source, target }) {
691
+ const xOffset = Math.abs(target.x - source.x) / 2;
692
+ const centerX = target.x < source.x ? target.x + xOffset : target.x - xOffset;
693
+ const yOffset = Math.abs(target.y - source.y) / 2;
694
+ const centerY = target.y < source.y ? target.y + yOffset : target.y - yOffset;
695
+ return [centerX, centerY];
696
+ }
697
+ const getDirection = ({ source, target }) => {
698
+ if (source.location === "left" || source.location === "right") {
699
+ return source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 };
700
+ }
701
+ return source.y < target.y ? { x: 0, y: 1 } : { x: 0, y: -1 };
702
+ };
703
+ const handleDirections = {
704
+ left: { x: -1, y: 0 },
705
+ right: { x: 1, y: 0 },
706
+ top: { x: 0, y: -1 },
707
+ bottom: { x: 0, y: 1 }
708
+ };
709
+ function getPoints({ source, target }) {
710
+ const sourceDir = handleDirections[source.location];
711
+ const targetDir = handleDirections[target.location];
712
+ const sourceGapped = {
713
+ x: source.x + sourceDir.x * OFFSET,
714
+ y: source.y + sourceDir.y * OFFSET,
715
+ location: source.location
716
+ };
717
+ const targetGapped = {
718
+ x: target.x + targetDir.x * OFFSET,
719
+ y: target.y + targetDir.y * OFFSET,
720
+ location: target.location
721
+ };
722
+ const dir = getDirection({
723
+ source: sourceGapped,
724
+ target: targetGapped
725
+ });
726
+ const dirAccessor = dir.x !== 0 ? "x" : "y";
727
+ const currDir = dir[dirAccessor];
728
+ let points = [];
729
+ let centerX, centerY;
730
+ const [defaultCenterX, defaultCenterY] = getEdgeCenter({
731
+ source,
732
+ target
733
+ });
734
+ if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
735
+ centerX = defaultCenterX;
736
+ centerY = defaultCenterY;
737
+ const verticalSplit = [
738
+ { x: centerX, y: sourceGapped.y },
739
+ { x: centerX, y: targetGapped.y }
740
+ ];
741
+ const horizontalSplit = [
742
+ { x: sourceGapped.x, y: centerY },
743
+ { x: targetGapped.x, y: centerY }
744
+ ];
745
+ if (sourceDir[dirAccessor] === currDir) {
746
+ points = dirAccessor === "x" ? verticalSplit : horizontalSplit;
747
+ } else {
748
+ points = dirAccessor === "x" ? horizontalSplit : verticalSplit;
749
+ }
750
+ } else {
751
+ const sourceTarget = [{ x: sourceGapped.x, y: targetGapped.y }];
752
+ const targetSource = [{ x: targetGapped.x, y: sourceGapped.y }];
753
+ if (dirAccessor === "x") {
754
+ points = sourceDir.x === currDir ? targetSource : sourceTarget;
755
+ } else {
756
+ points = sourceDir.y === currDir ? sourceTarget : targetSource;
757
+ }
758
+ const dirAccessorOpposite = dirAccessor === "x" ? "y" : "x";
759
+ const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite];
760
+ const sourceGtTargetOppo = sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite];
761
+ const sourceLtTargetOppo = sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite];
762
+ const flipSourceTarget = sourceDir[dirAccessor] === 1 && (!isSameDir && sourceGtTargetOppo || isSameDir && sourceLtTargetOppo) || sourceDir[dirAccessor] !== 1 && (!isSameDir && sourceLtTargetOppo || isSameDir && sourceGtTargetOppo);
763
+ if (flipSourceTarget) {
764
+ points = dirAccessor === "x" ? sourceTarget : targetSource;
765
+ }
766
+ const sourceGapPoint = { x: sourceGapped.x, y: sourceGapped.y };
767
+ const targetGapPoint = { x: targetGapped.x, y: targetGapped.y };
768
+ const maxXDistance = Math.max(
769
+ Math.abs(sourceGapPoint.x - points[0].x),
770
+ Math.abs(targetGapPoint.x - points[0].x)
771
+ );
772
+ const maxYDistance = Math.max(
773
+ Math.abs(sourceGapPoint.y - points[0].y),
774
+ Math.abs(targetGapPoint.y - points[0].y)
775
+ );
776
+ if (maxXDistance >= maxYDistance) {
777
+ centerX = (sourceGapPoint.x + targetGapPoint.x) / 2;
778
+ centerY = points[0].y;
779
+ } else {
780
+ centerX = points[0].x;
781
+ centerY = (sourceGapPoint.y + targetGapPoint.y) / 2;
782
+ }
783
+ }
784
+ const pathPoints = [
785
+ source,
786
+ { x: sourceGapped.x, y: sourceGapped.y },
787
+ ...points,
788
+ { x: targetGapped.x, y: targetGapped.y },
789
+ target
790
+ ];
791
+ return {
792
+ points: pathPoints,
793
+ center: {
794
+ x: centerX,
795
+ y: centerY
796
+ }
797
+ };
798
+ }
799
+ FoldLine2.getPoints = getPoints;
800
+ function getBend(a, b, c) {
801
+ const bendSize = Math.min(
802
+ utils.Point.getDistance(a, b) / 2,
803
+ utils.Point.getDistance(b, c) / 2,
804
+ EDGE_RADIUS
805
+ );
806
+ const { x, y } = b;
807
+ if (a.x === x && x === c.x || a.y === y && y === c.y) {
808
+ return `L${x} ${y}`;
809
+ }
810
+ if (a.y === y) {
811
+ const xDir2 = a.x < c.x ? -1 : 1;
812
+ const yDir2 = a.y < c.y ? 1 : -1;
813
+ return `L ${x + bendSize * xDir2},${y}Q ${x},${y} ${x},${y + bendSize * yDir2}`;
814
+ }
815
+ const xDir = a.x < c.x ? 1 : -1;
816
+ const yDir = a.y < c.y ? -1 : 1;
817
+ return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
818
+ }
819
+ function getSmoothStepPath(points) {
820
+ const path = points.reduce((res, p, i) => {
821
+ let segment = "";
822
+ if (i > 0 && i < points.length - 1) {
823
+ segment = getBend(points[i - 1], p, points[i + 1]);
824
+ } else {
825
+ segment = `${i === 0 ? "M" : "L"}${p.x} ${p.y}`;
826
+ }
827
+ res += segment;
828
+ return res;
829
+ }, "");
830
+ return path;
831
+ }
832
+ FoldLine2.getSmoothStepPath = getSmoothStepPath;
833
+ function getBounds(points) {
834
+ const xList = points.map((p) => p.x);
835
+ const yList = points.map((p) => p.y);
836
+ const left = Math.min(...xList);
837
+ const right = Math.max(...xList);
838
+ const top = Math.min(...yList);
839
+ const bottom = Math.max(...yList);
840
+ return utils.Rectangle.createRectangleWithTwoPoints(
841
+ {
842
+ x: left,
843
+ y: top
844
+ },
845
+ {
846
+ x: right,
847
+ y: bottom
848
+ }
849
+ );
850
+ }
851
+ FoldLine2.getBounds = getBounds;
852
+ FoldLine2.getFoldLineToPointDistance = (points, pos) => {
853
+ if (points.length === 0) {
854
+ return Infinity;
855
+ }
856
+ if (points.length === 1) {
857
+ return utils.Point.getDistance(points[0], pos);
858
+ }
859
+ const lines = [];
860
+ for (let i = 0; i < points.length - 1; i++) {
861
+ lines.push([points[i], points[i + 1]]);
862
+ }
863
+ const distances = lines.map((line) => {
864
+ const [p1, p2] = line;
865
+ return getPointToSegmentDistance(pos, p1, p2);
866
+ });
867
+ return Math.min(...distances);
868
+ };
869
+ })(FoldLine || (FoldLine = {}));
870
+
871
+ // src/contributions/fold/index.ts
872
+ var WorkflowFoldLineContribution = class {
873
+ constructor(entity) {
874
+ this.entity = entity;
875
+ }
876
+ get path() {
877
+ return this.data?.path ?? "";
878
+ }
879
+ calcDistance(pos) {
880
+ if (!this.data) {
881
+ return Number.MAX_SAFE_INTEGER;
882
+ }
883
+ return FoldLine.getFoldLineToPointDistance(this.data.points, pos);
884
+ }
885
+ get bounds() {
886
+ if (!this.data) {
887
+ return new utils.Rectangle();
888
+ }
889
+ return this.data.bbox;
890
+ }
891
+ get center() {
892
+ return this.data?.center;
893
+ }
894
+ update(params) {
895
+ const { fromPos, toPos } = params;
896
+ const shrink = this.entity.uiState.shrink;
897
+ const source = posWithShrink(fromPos, fromPos.location, shrink);
898
+ const target = posWithShrink(toPos, toPos.location, shrink);
899
+ const { points, center } = FoldLine.getPoints({
900
+ source: {
901
+ ...source,
902
+ location: fromPos.location
903
+ },
904
+ target: {
905
+ ...target,
906
+ location: toPos.location
907
+ }
908
+ });
909
+ const bbox = FoldLine.getBounds(points);
910
+ const adjustedPoints = points.map((p) => toRelative(p, bbox));
911
+ const path = FoldLine.getSmoothStepPath(adjustedPoints);
912
+ const relativeCenter = toRelative(center, bbox);
913
+ this.data = {
914
+ points,
915
+ path,
916
+ bbox,
917
+ center: {
918
+ x: center.x,
919
+ y: center.y,
920
+ labelX: relativeCenter.x,
921
+ labelY: relativeCenter.y
922
+ }
923
+ };
924
+ }
925
+ };
926
+ WorkflowFoldLineContribution.type = freeLayoutCore.LineType.LINE_CHART;
927
+
928
+ // src/contributions/straight/point-on-line.ts
929
+ function projectPointOnLine(point, lineStart, lineEnd) {
930
+ const dx = lineEnd.x - lineStart.x;
931
+ const dy = lineEnd.y - lineStart.y;
932
+ if (dx === 0) {
933
+ return { x: lineStart.x, y: point.y };
934
+ }
935
+ if (dy === 0) {
936
+ return { x: point.x, y: lineStart.y };
937
+ }
938
+ const t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / (dx * dx + dy * dy);
939
+ const clampedT = Math.max(0, Math.min(1, t));
940
+ return {
941
+ x: lineStart.x + clampedT * dx,
942
+ y: lineStart.y + clampedT * dy
943
+ };
944
+ }
945
+
946
+ // src/contributions/straight/index.ts
947
+ var WorkflowStraightLineContribution = class {
948
+ constructor(entity) {
949
+ this.entity = entity;
950
+ }
951
+ get path() {
952
+ return this.data?.path ?? "";
953
+ }
954
+ calcDistance(pos) {
955
+ if (!this.data) {
956
+ return Number.MAX_SAFE_INTEGER;
957
+ }
958
+ const [start, end] = this.data.points;
959
+ return utils.Point.getDistance(pos, projectPointOnLine(pos, start, end));
960
+ }
961
+ get bounds() {
962
+ if (!this.data) {
963
+ return new utils.Rectangle();
964
+ }
965
+ return this.data.bbox;
966
+ }
967
+ get center() {
968
+ return this.data?.center;
969
+ }
970
+ update(params) {
971
+ const { fromPos, toPos } = params;
972
+ const shrink = this.entity.uiState.shrink;
973
+ const source = posWithShrink(fromPos, fromPos.location, shrink);
974
+ const target = posWithShrink(toPos, toPos.location, shrink);
975
+ const points = [source, target];
976
+ const bbox = utils.Rectangle.createRectangleWithTwoPoints(points[0], points[1]);
977
+ const adjustedPoints = points.map((p) => ({
978
+ x: p.x - bbox.x + LINE_PADDING,
979
+ y: p.y - bbox.y + LINE_PADDING
980
+ }));
981
+ const path = `M ${adjustedPoints[0].x} ${adjustedPoints[0].y} L ${adjustedPoints[1].x} ${adjustedPoints[1].y}`;
982
+ this.data = {
983
+ points,
984
+ path,
985
+ bbox,
986
+ center: freeLayoutCore.getLineCenter(fromPos, toPos, bbox, LINE_PADDING)
987
+ };
988
+ }
989
+ };
990
+ WorkflowStraightLineContribution.type = freeLayoutCore.LineType.STRAIGHT;
991
+
992
+ // src/create-free-lines-plugin.ts
993
+ var createFreeLinesPlugin = core.definePluginCreator({
994
+ singleton: true,
995
+ onInit: (ctx, opts) => {
996
+ ctx.playground.registerLayer(exports.WorkflowLinesLayer, {
997
+ ...opts
998
+ });
999
+ if (opts.defaultLineUIState) {
1000
+ ctx.container.get(freeLayoutCore.WorkflowLinesManager).setDefaultUIState(opts.defaultLineUIState);
1001
+ }
1002
+ },
1003
+ onReady: (ctx, opts) => {
1004
+ const linesManager = ctx.container.get(freeLayoutCore.WorkflowLinesManager);
1005
+ linesManager.registerContribution(WorkflowBezierLineContribution).registerContribution(WorkflowFoldLineContribution).registerContribution(WorkflowStraightLineContribution);
1006
+ if (opts.contributions) {
1007
+ opts.contributions.forEach((contribution) => {
1008
+ linesManager.registerContribution(contribution);
1009
+ });
1010
+ }
1011
+ if (opts.defaultLineType) {
1012
+ linesManager.switchLineType(opts.defaultLineType);
1013
+ }
1014
+ }
1015
+ });
1016
+
1017
+ exports.LINE_OFFSET = LINE_OFFSET;
1018
+ exports.LINE_PADDING = LINE_PADDING;
1019
+ exports.LinesLayer = exports.WorkflowLinesLayer;
1020
+ exports.WorkflowBezierLineContribution = WorkflowBezierLineContribution;
1021
+ exports.WorkflowFoldLineContribution = WorkflowFoldLineContribution;
1022
+ exports.WorkflowPortRender = WorkflowPortRender;
1023
+ exports.WorkflowStraightLineContribution = WorkflowStraightLineContribution;
1024
+ exports.createFreeLinesPlugin = createFreeLinesPlugin;
1025
+ //# sourceMappingURL=index.cjs.map
1026
+ //# sourceMappingURL=index.cjs.map