@zoneflow/renderer-dom 0.0.6 → 0.0.8
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/engines/debugDrawEngine.js +66 -26
- package/dist/engines/drawEngine.js +85 -31
- package/package.json +2 -2
|
@@ -5,6 +5,21 @@ const DEFAULT_DEBUG_LAYERS = [
|
|
|
5
5
|
"edges",
|
|
6
6
|
"anchors",
|
|
7
7
|
];
|
|
8
|
+
const EDGE_FLOW_CLASS = "zoneflow-debug-edge-flow";
|
|
9
|
+
const EDGE_FLOW_STYLE = `
|
|
10
|
+
@keyframes zoneflow-debug-edge-flow {
|
|
11
|
+
from { stroke-dashoffset: 18; }
|
|
12
|
+
to { stroke-dashoffset: 0; }
|
|
13
|
+
}
|
|
14
|
+
.${EDGE_FLOW_CLASS} {
|
|
15
|
+
animation: zoneflow-debug-edge-flow 900ms linear infinite;
|
|
16
|
+
}
|
|
17
|
+
@media (prefers-reduced-motion: reduce) {
|
|
18
|
+
.${EDGE_FLOW_CLASS} {
|
|
19
|
+
animation: none;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
8
23
|
const drawLayerMap = {
|
|
9
24
|
"graph-layout": drawGraphLayout,
|
|
10
25
|
density: drawDensity,
|
|
@@ -17,6 +32,11 @@ const drawLayerMap = {
|
|
|
17
32
|
function createSvgElement(tag) {
|
|
18
33
|
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
19
34
|
}
|
|
35
|
+
function appendEdgeFlowStyle(svg) {
|
|
36
|
+
const style = createSvgElement("style");
|
|
37
|
+
style.textContent = EDGE_FLOW_STYLE;
|
|
38
|
+
svg.appendChild(style);
|
|
39
|
+
}
|
|
20
40
|
function sortZoneVisualsForRender(pipeline) {
|
|
21
41
|
function getDepth(zoneId) {
|
|
22
42
|
let depth = 0;
|
|
@@ -41,22 +61,39 @@ function getEdgeColor(kind) {
|
|
|
41
61
|
}
|
|
42
62
|
function getBezierCurvePathD(params) {
|
|
43
63
|
const { source, target } = params;
|
|
64
|
+
const distanceX = Math.abs(target.x - source.x);
|
|
65
|
+
const distanceY = Math.abs(target.y - source.y);
|
|
66
|
+
if (distanceX <= 72 && distanceY <= 48) {
|
|
67
|
+
return `M ${source.x} ${source.y} L ${target.x} ${target.y}`;
|
|
68
|
+
}
|
|
44
69
|
const sourceLead = Math.min(Math.max(Math.abs(target.x - source.x) * 0.18, 18), 42);
|
|
45
70
|
const leadSourceX = source.x + sourceLead;
|
|
46
|
-
const
|
|
47
|
-
const
|
|
71
|
+
const targetLead = Math.min(Math.max(Math.abs(target.x - source.x) * 0.16, 18), 42);
|
|
72
|
+
const targetApproachX = target.x - targetLead;
|
|
73
|
+
const shouldRouteAround = targetApproachX - leadSourceX < 36;
|
|
74
|
+
if (shouldRouteAround) {
|
|
75
|
+
const bridgeDistance = Math.abs(leadSourceX - targetApproachX);
|
|
76
|
+
const midX = (leadSourceX + targetApproachX) / 2;
|
|
77
|
+
const sourceBendX = leadSourceX + Math.min(Math.max(bridgeDistance * 0.22, 28), 72);
|
|
78
|
+
const targetBendX = targetApproachX - Math.min(Math.max(bridgeDistance * 0.22, 28), 72);
|
|
79
|
+
const verticalGap = Math.abs(target.y - source.y);
|
|
80
|
+
const verticalDirection = target.y >= source.y ? 1 : -1;
|
|
81
|
+
const laneOffset = Math.min(Math.max(Math.abs(target.x - source.x) * 0.22 + 48, 56), 144);
|
|
82
|
+
const laneY = (source.y + target.y) / 2 +
|
|
83
|
+
(verticalGap < 36 ? verticalDirection * laneOffset : 0);
|
|
84
|
+
return [
|
|
85
|
+
`M ${source.x} ${source.y}`,
|
|
86
|
+
`L ${leadSourceX} ${source.y}`,
|
|
87
|
+
`C ${sourceBendX} ${source.y}, ${sourceBendX} ${laneY}, ${midX} ${laneY}`,
|
|
88
|
+
`C ${targetBendX} ${laneY}, ${targetBendX} ${target.y}, ${targetApproachX} ${target.y}`,
|
|
89
|
+
`L ${target.x} ${target.y}`,
|
|
90
|
+
].join(" ");
|
|
91
|
+
}
|
|
92
|
+
const dx = targetApproachX - leadSourceX;
|
|
48
93
|
const handle = Math.min(Math.max(Math.abs(dx) * 0.45, 28), 104);
|
|
49
|
-
const control1X = leadSourceX + handle
|
|
50
|
-
const control2X =
|
|
51
|
-
return `M ${source.x} ${source.y} L ${leadSourceX} ${source.y} C ${control1X} ${source.y}, ${control2X} ${target.y}, ${target.x} ${target.y}`;
|
|
52
|
-
}
|
|
53
|
-
function getChevronPathD(params) {
|
|
54
|
-
const { target, direction } = params;
|
|
55
|
-
const tipX = target.x - direction * 6;
|
|
56
|
-
const baseX = tipX - direction * 7;
|
|
57
|
-
const topY = target.y - 4;
|
|
58
|
-
const bottomY = target.y + 4;
|
|
59
|
-
return `M ${baseX} ${topY} L ${tipX} ${target.y} L ${baseX} ${bottomY}`;
|
|
94
|
+
const control1X = leadSourceX + handle;
|
|
95
|
+
const control2X = targetApproachX - handle;
|
|
96
|
+
return `M ${source.x} ${source.y} L ${leadSourceX} ${source.y} C ${control1X} ${source.y}, ${control2X} ${target.y}, ${targetApproachX} ${target.y} L ${target.x} ${target.y}`;
|
|
60
97
|
}
|
|
61
98
|
function filterPipelineForExclusion(input) {
|
|
62
99
|
const excludedZoneIds = new Set(input.exclusionState?.excludedZoneIds ?? []);
|
|
@@ -266,32 +303,35 @@ function drawEdges(root, pipeline) {
|
|
|
266
303
|
svg.style.height = "100%";
|
|
267
304
|
svg.style.overflow = "visible";
|
|
268
305
|
svg.style.pointerEvents = "none";
|
|
306
|
+
appendEdgeFlowStyle(svg);
|
|
269
307
|
Object.values(edgesByPathId)
|
|
270
308
|
.flatMap((edges) => edges)
|
|
271
309
|
.forEach((edge) => {
|
|
272
310
|
const path = createSvgElement("path");
|
|
273
311
|
const stroke = getEdgeColor(edge.kind);
|
|
274
|
-
|
|
312
|
+
const pathD = getBezierCurvePathD({
|
|
275
313
|
source: edge.source,
|
|
276
314
|
target: edge.target,
|
|
277
|
-
})
|
|
315
|
+
});
|
|
316
|
+
path.setAttribute("d", pathD);
|
|
278
317
|
path.setAttribute("fill", "none");
|
|
279
318
|
path.setAttribute("stroke", stroke);
|
|
280
319
|
path.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "2" : "2.4");
|
|
281
320
|
path.setAttribute("stroke-linecap", "round");
|
|
282
321
|
path.setAttribute("stroke-linejoin", "round");
|
|
322
|
+
path.setAttribute("opacity", "0.42");
|
|
283
323
|
svg.appendChild(path);
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
svg.appendChild(
|
|
324
|
+
const flow = createSvgElement("path");
|
|
325
|
+
flow.setAttribute("d", pathD);
|
|
326
|
+
flow.setAttribute("fill", "none");
|
|
327
|
+
flow.setAttribute("stroke", stroke);
|
|
328
|
+
flow.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "2.45" : "2.85");
|
|
329
|
+
flow.setAttribute("stroke-linecap", "round");
|
|
330
|
+
flow.setAttribute("stroke-linejoin", "round");
|
|
331
|
+
flow.setAttribute("stroke-dasharray", "1 11");
|
|
332
|
+
flow.setAttribute("stroke-dashoffset", "0");
|
|
333
|
+
flow.setAttribute("class", EDGE_FLOW_CLASS);
|
|
334
|
+
svg.appendChild(flow);
|
|
295
335
|
});
|
|
296
336
|
root.appendChild(svg);
|
|
297
337
|
}
|
|
@@ -9,6 +9,21 @@ const RENDER_Z_INDEX = {
|
|
|
9
9
|
edgeLayer: 20,
|
|
10
10
|
pathLayer: 30,
|
|
11
11
|
};
|
|
12
|
+
const EDGE_FLOW_CLASS = "zoneflow-edge-flow";
|
|
13
|
+
const EDGE_FLOW_STYLE = `
|
|
14
|
+
@keyframes zoneflow-edge-flow {
|
|
15
|
+
from { stroke-dashoffset: 18; }
|
|
16
|
+
to { stroke-dashoffset: 0; }
|
|
17
|
+
}
|
|
18
|
+
.${EDGE_FLOW_CLASS} {
|
|
19
|
+
animation: zoneflow-edge-flow 900ms linear infinite;
|
|
20
|
+
}
|
|
21
|
+
@media (prefers-reduced-motion: reduce) {
|
|
22
|
+
.${EDGE_FLOW_CLASS} {
|
|
23
|
+
animation: none;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
12
27
|
function applyStyles(el, styles) {
|
|
13
28
|
for (const [key, value] of Object.entries(styles)) {
|
|
14
29
|
// @ts-expect-error CSSStyleDeclaration index access
|
|
@@ -34,10 +49,21 @@ function sortZonesForRender(params) {
|
|
|
34
49
|
zone,
|
|
35
50
|
index,
|
|
36
51
|
depth: getZoneDepth(params.input.model, zone.zoneId),
|
|
52
|
+
zOrder: params.input.layoutModel.zoneLayoutsById[zone.zoneId]?.zOrder ?? index,
|
|
37
53
|
}))
|
|
38
|
-
.sort((a, b) => a.depth - b.depth || a.index - b.index)
|
|
54
|
+
.sort((a, b) => a.depth - b.depth || a.zOrder - b.zOrder || a.index - b.index)
|
|
39
55
|
.map((entry) => entry.zone);
|
|
40
56
|
}
|
|
57
|
+
function sortPathsForRender(params) {
|
|
58
|
+
return Object.values(params.pathsById)
|
|
59
|
+
.map((path, index) => ({
|
|
60
|
+
path,
|
|
61
|
+
index,
|
|
62
|
+
zOrder: params.input.layoutModel.pathLayoutsById[path.pathId]?.zOrder ?? index,
|
|
63
|
+
}))
|
|
64
|
+
.sort((a, b) => a.zOrder - b.zOrder || a.index - b.index)
|
|
65
|
+
.map((entry) => entry.path);
|
|
66
|
+
}
|
|
41
67
|
function resolvePathDisplayName(params) {
|
|
42
68
|
const trimmed = params.name.trim();
|
|
43
69
|
if (trimmed)
|
|
@@ -110,6 +136,11 @@ function getOpacity(emphasis) {
|
|
|
110
136
|
function createSvgElement(tag) {
|
|
111
137
|
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
112
138
|
}
|
|
139
|
+
function appendEdgeFlowStyle(svg) {
|
|
140
|
+
const style = createSvgElement("style");
|
|
141
|
+
style.textContent = EDGE_FLOW_STYLE;
|
|
142
|
+
svg.appendChild(style);
|
|
143
|
+
}
|
|
113
144
|
function getEdgeColor(params) {
|
|
114
145
|
return params.kind === "zone-to-path"
|
|
115
146
|
? params.theme.pathEdge
|
|
@@ -117,22 +148,39 @@ function getEdgeColor(params) {
|
|
|
117
148
|
}
|
|
118
149
|
function getBezierCurvePathD(params) {
|
|
119
150
|
const { source, target } = params;
|
|
151
|
+
const distanceX = Math.abs(target.x - source.x);
|
|
152
|
+
const distanceY = Math.abs(target.y - source.y);
|
|
153
|
+
if (distanceX <= 72 && distanceY <= 48) {
|
|
154
|
+
return `M ${source.x} ${source.y} L ${target.x} ${target.y}`;
|
|
155
|
+
}
|
|
120
156
|
const sourceLead = Math.min(Math.max(Math.abs(target.x - source.x) * 0.18, 18), 42);
|
|
121
157
|
const leadSourceX = source.x + sourceLead;
|
|
122
|
-
const
|
|
123
|
-
const
|
|
158
|
+
const targetLead = Math.min(Math.max(Math.abs(target.x - source.x) * 0.16, 18), 42);
|
|
159
|
+
const targetApproachX = target.x - targetLead;
|
|
160
|
+
const shouldRouteAround = targetApproachX - leadSourceX < 36;
|
|
161
|
+
if (shouldRouteAround) {
|
|
162
|
+
const bridgeDistance = Math.abs(leadSourceX - targetApproachX);
|
|
163
|
+
const midX = (leadSourceX + targetApproachX) / 2;
|
|
164
|
+
const sourceBendX = leadSourceX + Math.min(Math.max(bridgeDistance * 0.22, 28), 72);
|
|
165
|
+
const targetBendX = targetApproachX - Math.min(Math.max(bridgeDistance * 0.22, 28), 72);
|
|
166
|
+
const verticalGap = Math.abs(target.y - source.y);
|
|
167
|
+
const verticalDirection = target.y >= source.y ? 1 : -1;
|
|
168
|
+
const laneOffset = Math.min(Math.max(Math.abs(target.x - source.x) * 0.22 + 48, 56), 144);
|
|
169
|
+
const laneY = (source.y + target.y) / 2 +
|
|
170
|
+
(verticalGap < 36 ? verticalDirection * laneOffset : 0);
|
|
171
|
+
return [
|
|
172
|
+
`M ${source.x} ${source.y}`,
|
|
173
|
+
`L ${leadSourceX} ${source.y}`,
|
|
174
|
+
`C ${sourceBendX} ${source.y}, ${sourceBendX} ${laneY}, ${midX} ${laneY}`,
|
|
175
|
+
`C ${targetBendX} ${laneY}, ${targetBendX} ${target.y}, ${targetApproachX} ${target.y}`,
|
|
176
|
+
`L ${target.x} ${target.y}`,
|
|
177
|
+
].join(" ");
|
|
178
|
+
}
|
|
179
|
+
const dx = targetApproachX - leadSourceX;
|
|
124
180
|
const handle = Math.min(Math.max(Math.abs(dx) * 0.45, 28), 104);
|
|
125
|
-
const control1X = leadSourceX + handle
|
|
126
|
-
const control2X =
|
|
127
|
-
return `M ${source.x} ${source.y} L ${leadSourceX} ${source.y} C ${control1X} ${source.y}, ${control2X} ${target.y}, ${target.x} ${target.y}`;
|
|
128
|
-
}
|
|
129
|
-
function getChevronPathD(params) {
|
|
130
|
-
const { target, direction } = params;
|
|
131
|
-
const tipX = target.x - direction * 6;
|
|
132
|
-
const baseX = tipX - direction * 7;
|
|
133
|
-
const topY = target.y - 4;
|
|
134
|
-
const bottomY = target.y + 4;
|
|
135
|
-
return `M ${baseX} ${topY} L ${tipX} ${target.y} L ${baseX} ${bottomY}`;
|
|
181
|
+
const control1X = leadSourceX + handle;
|
|
182
|
+
const control2X = targetApproachX - handle;
|
|
183
|
+
return `M ${source.x} ${source.y} L ${leadSourceX} ${source.y} C ${control1X} ${source.y}, ${control2X} ${target.y}, ${targetApproachX} ${target.y} L ${target.x} ${target.y}`;
|
|
136
184
|
}
|
|
137
185
|
function computeSceneBounds(input) {
|
|
138
186
|
const { pipeline, viewportInfo, } = input;
|
|
@@ -412,6 +460,7 @@ function createPathSlotHost(params) {
|
|
|
412
460
|
}
|
|
413
461
|
function drawEdges(params) {
|
|
414
462
|
const { svg, input } = params;
|
|
463
|
+
appendEdgeFlowStyle(svg);
|
|
415
464
|
for (const [pathId, edges] of Object.entries(input.pipeline.graphLayout.edgesByPathId)) {
|
|
416
465
|
const visibility = input.pipeline.visibility.pathVisibilityById[pathId];
|
|
417
466
|
if (!visibility?.shouldRenderEdge)
|
|
@@ -421,30 +470,32 @@ function drawEdges(params) {
|
|
|
421
470
|
kind: edge.kind,
|
|
422
471
|
theme: input.theme,
|
|
423
472
|
});
|
|
424
|
-
const
|
|
425
|
-
path.setAttribute("d", getBezierCurvePathD({
|
|
473
|
+
const pathD = getBezierCurvePathD({
|
|
426
474
|
source: edge.source,
|
|
427
475
|
target: edge.target,
|
|
428
|
-
})
|
|
476
|
+
});
|
|
477
|
+
const opacity = getOpacity(visibility.emphasis);
|
|
478
|
+
const path = createSvgElement("path");
|
|
479
|
+
path.setAttribute("d", pathD);
|
|
429
480
|
path.setAttribute("fill", "none");
|
|
430
481
|
path.setAttribute("stroke", stroke);
|
|
431
482
|
path.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "2.25" : "1.85");
|
|
432
483
|
path.setAttribute("stroke-linecap", "round");
|
|
433
484
|
path.setAttribute("stroke-linejoin", "round");
|
|
434
|
-
path.setAttribute("opacity", String(
|
|
485
|
+
path.setAttribute("opacity", String(opacity * 0.42));
|
|
435
486
|
svg.appendChild(path);
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
svg.appendChild(
|
|
487
|
+
const flow = createSvgElement("path");
|
|
488
|
+
flow.setAttribute("d", pathD);
|
|
489
|
+
flow.setAttribute("fill", "none");
|
|
490
|
+
flow.setAttribute("stroke", stroke);
|
|
491
|
+
flow.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "2.8" : "2.35");
|
|
492
|
+
flow.setAttribute("stroke-linecap", "round");
|
|
493
|
+
flow.setAttribute("stroke-linejoin", "round");
|
|
494
|
+
flow.setAttribute("stroke-dasharray", "1 11");
|
|
495
|
+
flow.setAttribute("stroke-dashoffset", "0");
|
|
496
|
+
flow.setAttribute("opacity", String(opacity));
|
|
497
|
+
flow.setAttribute("class", EDGE_FLOW_CLASS);
|
|
498
|
+
svg.appendChild(flow);
|
|
448
499
|
}
|
|
449
500
|
}
|
|
450
501
|
}
|
|
@@ -701,7 +752,10 @@ export const domDrawEngine = {
|
|
|
701
752
|
});
|
|
702
753
|
zoneLayer.appendChild(zoneEl);
|
|
703
754
|
}
|
|
704
|
-
for (const pathVisual of
|
|
755
|
+
for (const pathVisual of sortPathsForRender({
|
|
756
|
+
input,
|
|
757
|
+
pathsById: pipeline.graphLayout.pathsById,
|
|
758
|
+
})) {
|
|
705
759
|
const visibility = pipeline.visibility.pathVisibilityById[pathVisual.pathId];
|
|
706
760
|
if (!visibility?.shouldRenderNode ||
|
|
707
761
|
!pathVisual.rect ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoneflow/renderer-dom",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Low-level DOM renderer engines for Zoneflow.",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zoneflow/core": "0.0.
|
|
22
|
+
"@zoneflow/core": "0.0.8"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc -p tsconfig.json",
|