@zoneflow/renderer-dom 0.0.7 → 0.0.9

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.
@@ -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 dx = target.x - leadSourceX;
47
- const direction = dx >= 0 ? 1 : -1;
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 * direction;
50
- const control2X = target.x - handle * direction;
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
- path.setAttribute("d", getBezierCurvePathD({
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 chevron = createSvgElement("path");
285
- chevron.setAttribute("d", getChevronPathD({
286
- target: edge.target,
287
- direction: edge.kind === "path-to-zone" ? 1 : edge.target.x >= edge.source.x ? 1 : -1,
288
- }));
289
- chevron.setAttribute("fill", "none");
290
- chevron.setAttribute("stroke", stroke);
291
- chevron.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "1.7" : "1.95");
292
- chevron.setAttribute("stroke-linecap", "round");
293
- chevron.setAttribute("stroke-linejoin", "round");
294
- svg.appendChild(chevron);
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
@@ -121,6 +136,11 @@ function getOpacity(emphasis) {
121
136
  function createSvgElement(tag) {
122
137
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
123
138
  }
139
+ function appendEdgeFlowStyle(svg) {
140
+ const style = createSvgElement("style");
141
+ style.textContent = EDGE_FLOW_STYLE;
142
+ svg.appendChild(style);
143
+ }
124
144
  function getEdgeColor(params) {
125
145
  return params.kind === "zone-to-path"
126
146
  ? params.theme.pathEdge
@@ -128,22 +148,39 @@ function getEdgeColor(params) {
128
148
  }
129
149
  function getBezierCurvePathD(params) {
130
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
+ }
131
156
  const sourceLead = Math.min(Math.max(Math.abs(target.x - source.x) * 0.18, 18), 42);
132
157
  const leadSourceX = source.x + sourceLead;
133
- const dx = target.x - leadSourceX;
134
- const direction = dx >= 0 ? 1 : -1;
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;
135
180
  const handle = Math.min(Math.max(Math.abs(dx) * 0.45, 28), 104);
136
- const control1X = leadSourceX + handle * direction;
137
- const control2X = target.x - handle * direction;
138
- return `M ${source.x} ${source.y} L ${leadSourceX} ${source.y} C ${control1X} ${source.y}, ${control2X} ${target.y}, ${target.x} ${target.y}`;
139
- }
140
- function getChevronPathD(params) {
141
- const { target, direction } = params;
142
- const tipX = target.x - direction * 6;
143
- const baseX = tipX - direction * 7;
144
- const topY = target.y - 4;
145
- const bottomY = target.y + 4;
146
- 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}`;
147
184
  }
148
185
  function computeSceneBounds(input) {
149
186
  const { pipeline, viewportInfo, } = input;
@@ -423,6 +460,7 @@ function createPathSlotHost(params) {
423
460
  }
424
461
  function drawEdges(params) {
425
462
  const { svg, input } = params;
463
+ appendEdgeFlowStyle(svg);
426
464
  for (const [pathId, edges] of Object.entries(input.pipeline.graphLayout.edgesByPathId)) {
427
465
  const visibility = input.pipeline.visibility.pathVisibilityById[pathId];
428
466
  if (!visibility?.shouldRenderEdge)
@@ -432,30 +470,32 @@ function drawEdges(params) {
432
470
  kind: edge.kind,
433
471
  theme: input.theme,
434
472
  });
435
- const path = createSvgElement("path");
436
- path.setAttribute("d", getBezierCurvePathD({
473
+ const pathD = getBezierCurvePathD({
437
474
  source: edge.source,
438
475
  target: edge.target,
439
- }));
476
+ });
477
+ const opacity = getOpacity(visibility.emphasis);
478
+ const path = createSvgElement("path");
479
+ path.setAttribute("d", pathD);
440
480
  path.setAttribute("fill", "none");
441
481
  path.setAttribute("stroke", stroke);
442
482
  path.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "2.25" : "1.85");
443
483
  path.setAttribute("stroke-linecap", "round");
444
484
  path.setAttribute("stroke-linejoin", "round");
445
- path.setAttribute("opacity", String(getOpacity(visibility.emphasis)));
485
+ path.setAttribute("opacity", String(opacity * 0.42));
446
486
  svg.appendChild(path);
447
- const chevron = createSvgElement("path");
448
- chevron.setAttribute("d", getChevronPathD({
449
- target: edge.target,
450
- direction: edge.kind === "path-to-zone" ? 1 : edge.target.x >= edge.source.x ? 1 : -1,
451
- }));
452
- chevron.setAttribute("fill", "none");
453
- chevron.setAttribute("stroke", stroke);
454
- chevron.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "1.95" : "1.7");
455
- chevron.setAttribute("stroke-linecap", "round");
456
- chevron.setAttribute("stroke-linejoin", "round");
457
- chevron.setAttribute("opacity", String(getOpacity(visibility.emphasis)));
458
- svg.appendChild(chevron);
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);
459
499
  }
460
500
  }
461
501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoneflow/renderer-dom",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
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.7"
22
+ "@zoneflow/core": "0.0.9"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc -p tsconfig.json",