@zoneflow/renderer-dom 0.0.9 → 0.0.10

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.
@@ -1,4 +1,6 @@
1
1
  import { isZoneInputEnabled, isZoneOutputEnabled, } from "@zoneflow/core";
2
+ import { appendEdgeFlowStyle, resolveCollapsedEdgeStroke, resolveDrawableEdgeSegments, resolveEdgeFlowMotion, } from "./edgeFlow";
3
+ import { defaultTheme } from "../themes/defaultTheme";
2
4
  const ANCHOR_SIZE = 8;
3
5
  const DEFAULT_DEBUG_LAYERS = [
4
6
  "graph-layout",
@@ -6,20 +8,6 @@ const DEFAULT_DEBUG_LAYERS = [
6
8
  "anchors",
7
9
  ];
8
10
  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
- `;
23
11
  const drawLayerMap = {
24
12
  "graph-layout": drawGraphLayout,
25
13
  density: drawDensity,
@@ -32,11 +20,6 @@ const drawLayerMap = {
32
20
  function createSvgElement(tag) {
33
21
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
34
22
  }
35
- function appendEdgeFlowStyle(svg) {
36
- const style = createSvgElement("style");
37
- style.textContent = EDGE_FLOW_STYLE;
38
- svg.appendChild(style);
39
- }
40
23
  function sortZoneVisualsForRender(pipeline) {
41
24
  function getDepth(zoneId) {
42
25
  let depth = 0;
@@ -56,8 +39,10 @@ function sortZoneVisualsForRender(pipeline) {
56
39
  .sort((a, b) => a.depth - b.depth || a.index - b.index)
57
40
  .map((entry) => entry.zone);
58
41
  }
59
- function getEdgeColor(kind) {
60
- return kind === "zone-to-path" ? "#2563eb" : "#0f766e";
42
+ function getEdgeColor(params) {
43
+ return params.kind === "zone-to-path"
44
+ ? params.theme.pathEdge
45
+ : params.theme.pathInboundEdge;
61
46
  }
62
47
  function getBezierCurvePathD(params) {
63
48
  const { source, target } = params;
@@ -150,7 +135,7 @@ export const debugDrawEngine = {
150
135
  host.appendChild(worldRoot);
151
136
  host.appendChild(screenRoot);
152
137
  layers.forEach((layer) => {
153
- drawLayerMap[layer]?.(worldRoot, pipeline, camera, pipeline.viewportInfo.world);
138
+ drawLayerMap[layer]?.(worldRoot, pipeline, camera, pipeline.viewportInfo.world, input.theme);
154
139
  });
155
140
  if (layers.includes("viewport")) {
156
141
  drawHostViewport(screenRoot, pipeline.viewportInfo);
@@ -293,7 +278,7 @@ function drawComponentLayout(root, pipeline, camera) {
293
278
  });
294
279
  });
295
280
  }
296
- function drawEdges(root, pipeline) {
281
+ function drawEdges(root, pipeline, _camera, _viewport, theme) {
297
282
  const { edgesByPathId } = pipeline.graphLayout;
298
283
  const svg = createSvgElement("svg");
299
284
  svg.style.position = "absolute";
@@ -303,35 +288,66 @@ function drawEdges(root, pipeline) {
303
288
  svg.style.height = "100%";
304
289
  svg.style.overflow = "visible";
305
290
  svg.style.pointerEvents = "none";
306
- appendEdgeFlowStyle(svg);
307
- Object.values(edgesByPathId)
308
- .flatMap((edges) => edges)
309
- .forEach((edge) => {
310
- const path = createSvgElement("path");
311
- const stroke = getEdgeColor(edge.kind);
312
- const pathD = getBezierCurvePathD({
313
- source: edge.source,
314
- target: edge.target,
291
+ const edgeFlowMotion = resolveEdgeFlowMotion(theme ?? defaultTheme);
292
+ appendEdgeFlowStyle({
293
+ svg,
294
+ animationName: "zoneflow-debug-edge-flow",
295
+ className: EDGE_FLOW_CLASS,
296
+ motion: edgeFlowMotion,
297
+ });
298
+ const effectiveTheme = theme ?? defaultTheme;
299
+ Object.entries(edgesByPathId).forEach(([pathId, edges]) => {
300
+ const visibility = pipeline.visibility?.pathVisibilityById?.[pathId];
301
+ const drawableEdges = resolveDrawableEdgeSegments({
302
+ pathId,
303
+ edges,
304
+ visibility,
305
+ });
306
+ drawableEdges.forEach(({ edge, collapsed }) => {
307
+ const path = createSvgElement("path");
308
+ const stroke = collapsed
309
+ ? resolveCollapsedEdgeStroke(effectiveTheme)
310
+ : getEdgeColor({
311
+ kind: edge.kind,
312
+ theme: effectiveTheme,
313
+ });
314
+ const pathD = getBezierCurvePathD({
315
+ source: edge.source,
316
+ target: edge.target,
317
+ });
318
+ path.setAttribute("d", pathD);
319
+ path.setAttribute("fill", "none");
320
+ path.setAttribute("stroke", stroke);
321
+ path.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "2" : "2.4");
322
+ path.setAttribute("stroke-linecap", "round");
323
+ path.setAttribute("stroke-linejoin", "round");
324
+ path.setAttribute("opacity", "0.42");
325
+ svg.appendChild(path);
326
+ const flowGlow = createSvgElement("path");
327
+ flowGlow.setAttribute("d", pathD);
328
+ flowGlow.setAttribute("fill", "none");
329
+ flowGlow.setAttribute("stroke", stroke);
330
+ flowGlow.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "3.8" : "4.4");
331
+ flowGlow.setAttribute("stroke-linecap", "round");
332
+ flowGlow.setAttribute("stroke-linejoin", "round");
333
+ flowGlow.setAttribute("stroke-dasharray", edgeFlowMotion.dashArray);
334
+ flowGlow.setAttribute("stroke-dashoffset", edgeFlowMotion.dashOffset);
335
+ flowGlow.setAttribute("opacity", "0.18");
336
+ flowGlow.setAttribute("class", EDGE_FLOW_CLASS);
337
+ svg.appendChild(flowGlow);
338
+ const flow = createSvgElement("path");
339
+ flow.setAttribute("d", pathD);
340
+ flow.setAttribute("fill", "none");
341
+ flow.setAttribute("stroke", stroke);
342
+ flow.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "2.15" : "2.55");
343
+ flow.setAttribute("stroke-linecap", "round");
344
+ flow.setAttribute("stroke-linejoin", "round");
345
+ flow.setAttribute("stroke-dasharray", edgeFlowMotion.dashArray);
346
+ flow.setAttribute("stroke-dashoffset", edgeFlowMotion.dashOffset);
347
+ flow.setAttribute("class", EDGE_FLOW_CLASS);
348
+ flow.setAttribute("opacity", "0.94");
349
+ svg.appendChild(flow);
315
350
  });
316
- path.setAttribute("d", pathD);
317
- path.setAttribute("fill", "none");
318
- path.setAttribute("stroke", stroke);
319
- path.setAttribute("stroke-width", edge.kind === "zone-to-path" ? "2" : "2.4");
320
- path.setAttribute("stroke-linecap", "round");
321
- path.setAttribute("stroke-linejoin", "round");
322
- path.setAttribute("opacity", "0.42");
323
- svg.appendChild(path);
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);
335
351
  });
336
352
  root.appendChild(svg);
337
353
  }
@@ -1,5 +1,6 @@
1
1
  import { resolveZoneAnchorRect } from "../anchors";
2
2
  import { getZoneDepth, isZoneInputEnabled, isZoneOutputEnabled, } from "@zoneflow/core";
3
+ import { appendEdgeFlowStyle, resolveCollapsedEdgeStroke, resolveDrawableEdgeSegments, resolveEdgeFlowMotion, } from "./edgeFlow";
3
4
  const SCENE_PADDING = 64;
4
5
  const RENDER_Z_INDEX = {
5
6
  zoneBase: 1,
@@ -10,20 +11,6 @@ const RENDER_Z_INDEX = {
10
11
  pathLayer: 30,
11
12
  };
12
13
  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
- `;
27
14
  function applyStyles(el, styles) {
28
15
  for (const [key, value] of Object.entries(styles)) {
29
16
  // @ts-expect-error CSSStyleDeclaration index access
@@ -136,11 +123,6 @@ function getOpacity(emphasis) {
136
123
  function createSvgElement(tag) {
137
124
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
138
125
  }
139
- function appendEdgeFlowStyle(svg) {
140
- const style = createSvgElement("style");
141
- style.textContent = EDGE_FLOW_STYLE;
142
- svg.appendChild(style);
143
- }
144
126
  function getEdgeColor(params) {
145
127
  return params.kind === "zone-to-path"
146
128
  ? params.theme.pathEdge
@@ -460,16 +442,29 @@ function createPathSlotHost(params) {
460
442
  }
461
443
  function drawEdges(params) {
462
444
  const { svg, input } = params;
463
- appendEdgeFlowStyle(svg);
445
+ const edgeFlowMotion = resolveEdgeFlowMotion(input.theme);
446
+ appendEdgeFlowStyle({
447
+ svg,
448
+ animationName: "zoneflow-edge-flow",
449
+ className: EDGE_FLOW_CLASS,
450
+ motion: edgeFlowMotion,
451
+ });
464
452
  for (const [pathId, edges] of Object.entries(input.pipeline.graphLayout.edgesByPathId)) {
465
453
  const visibility = input.pipeline.visibility.pathVisibilityById[pathId];
466
454
  if (!visibility?.shouldRenderEdge)
467
455
  continue;
468
- for (const edge of edges) {
469
- const stroke = getEdgeColor({
470
- kind: edge.kind,
471
- theme: input.theme,
472
- });
456
+ const drawableEdges = resolveDrawableEdgeSegments({
457
+ pathId,
458
+ edges,
459
+ visibility,
460
+ });
461
+ for (const { edge, collapsed } of drawableEdges) {
462
+ const stroke = collapsed
463
+ ? resolveCollapsedEdgeStroke(input.theme)
464
+ : getEdgeColor({
465
+ kind: edge.kind,
466
+ theme: input.theme,
467
+ });
473
468
  const pathD = getBezierCurvePathD({
474
469
  source: edge.source,
475
470
  target: edge.target,
@@ -484,16 +479,28 @@ function drawEdges(params) {
484
479
  path.setAttribute("stroke-linejoin", "round");
485
480
  path.setAttribute("opacity", String(opacity * 0.42));
486
481
  svg.appendChild(path);
482
+ const flowGlow = createSvgElement("path");
483
+ flowGlow.setAttribute("d", pathD);
484
+ flowGlow.setAttribute("fill", "none");
485
+ flowGlow.setAttribute("stroke", stroke);
486
+ flowGlow.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "4.4" : "3.8");
487
+ flowGlow.setAttribute("stroke-linecap", "round");
488
+ flowGlow.setAttribute("stroke-linejoin", "round");
489
+ flowGlow.setAttribute("stroke-dasharray", edgeFlowMotion.dashArray);
490
+ flowGlow.setAttribute("stroke-dashoffset", edgeFlowMotion.dashOffset);
491
+ flowGlow.setAttribute("opacity", String(opacity * 0.18));
492
+ flowGlow.setAttribute("class", EDGE_FLOW_CLASS);
493
+ svg.appendChild(flowGlow);
487
494
  const flow = createSvgElement("path");
488
495
  flow.setAttribute("d", pathD);
489
496
  flow.setAttribute("fill", "none");
490
497
  flow.setAttribute("stroke", stroke);
491
- flow.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "2.8" : "2.35");
498
+ flow.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "2.55" : "2.15");
492
499
  flow.setAttribute("stroke-linecap", "round");
493
500
  flow.setAttribute("stroke-linejoin", "round");
494
- flow.setAttribute("stroke-dasharray", "1 11");
495
- flow.setAttribute("stroke-dashoffset", "0");
496
- flow.setAttribute("opacity", String(opacity));
501
+ flow.setAttribute("stroke-dasharray", edgeFlowMotion.dashArray);
502
+ flow.setAttribute("stroke-dashoffset", edgeFlowMotion.dashOffset);
503
+ flow.setAttribute("opacity", String(opacity * 0.94));
497
504
  flow.setAttribute("class", EDGE_FLOW_CLASS);
498
505
  svg.appendChild(flow);
499
506
  }
@@ -0,0 +1,26 @@
1
+ import type { EdgeVisual, PathVisibility } from "../types";
2
+ import type { ZoneflowTheme } from "../theme";
3
+ export type EdgeFlowMotion = {
4
+ durationMs: number;
5
+ segmentLength: number;
6
+ gapLength: number;
7
+ dashArray: string;
8
+ dashOffset: string;
9
+ };
10
+ export declare function resolveEdgeFlowMotion(theme: ZoneflowTheme): EdgeFlowMotion;
11
+ export declare function appendEdgeFlowStyle(params: {
12
+ svg: SVGSVGElement;
13
+ animationName: string;
14
+ className: string;
15
+ motion: EdgeFlowMotion;
16
+ }): void;
17
+ export type DrawableEdgeSegment = {
18
+ edge: EdgeVisual;
19
+ collapsed: boolean;
20
+ };
21
+ export declare function resolveDrawableEdgeSegments(params: {
22
+ pathId: string;
23
+ edges: EdgeVisual[];
24
+ visibility?: PathVisibility;
25
+ }): DrawableEdgeSegment[];
26
+ export declare function resolveCollapsedEdgeStroke(theme: ZoneflowTheme): string;
@@ -0,0 +1,64 @@
1
+ function sanitizeNumber(value, fallback, min) {
2
+ if (!Number.isFinite(value))
3
+ return fallback;
4
+ return Math.max(min, value);
5
+ }
6
+ export function resolveEdgeFlowMotion(theme) {
7
+ const durationMs = sanitizeNumber(theme.edgeFlow.durationMs, 1320, 60);
8
+ const segmentLength = sanitizeNumber(theme.edgeFlow.segmentLength, 18, 1);
9
+ const gapLength = sanitizeNumber(theme.edgeFlow.gapLength, 28, 1);
10
+ const cycleLength = segmentLength + gapLength;
11
+ return {
12
+ durationMs,
13
+ segmentLength,
14
+ gapLength,
15
+ dashArray: `${segmentLength} ${gapLength}`,
16
+ dashOffset: String(cycleLength),
17
+ };
18
+ }
19
+ export function appendEdgeFlowStyle(params) {
20
+ const { svg, animationName, className, motion } = params;
21
+ const style = document.createElementNS("http://www.w3.org/2000/svg", "style");
22
+ style.textContent = `
23
+ @keyframes ${animationName} {
24
+ from { stroke-dashoffset: ${motion.dashOffset}; }
25
+ to { stroke-dashoffset: 0; }
26
+ }
27
+ .${className} {
28
+ animation: ${animationName} ${motion.durationMs}ms linear infinite;
29
+ }
30
+ @media (prefers-reduced-motion: reduce) {
31
+ .${className} {
32
+ animation: none;
33
+ }
34
+ }
35
+ `;
36
+ svg.appendChild(style);
37
+ }
38
+ export function resolveDrawableEdgeSegments(params) {
39
+ const { pathId, edges, visibility } = params;
40
+ if (edges.length === 0)
41
+ return [];
42
+ if (visibility?.shouldRenderNode !== false) {
43
+ return edges.map((edge) => ({
44
+ edge,
45
+ collapsed: false,
46
+ }));
47
+ }
48
+ const first = edges[0];
49
+ const last = edges[edges.length - 1];
50
+ return [
51
+ {
52
+ edge: {
53
+ ...first,
54
+ id: `${pathId}:collapsed`,
55
+ source: first.source,
56
+ target: last.target,
57
+ },
58
+ collapsed: true,
59
+ },
60
+ ];
61
+ }
62
+ export function resolveCollapsedEdgeStroke(theme) {
63
+ return `color-mix(in srgb, ${theme.pathEdge} 52%, ${theme.pathInboundEdge} 48%)`;
64
+ }
package/dist/theme.d.ts CHANGED
@@ -4,6 +4,11 @@ export type ZoneflowStatusTone = {
4
4
  color: string;
5
5
  shadow: string;
6
6
  };
7
+ export type ZoneflowEdgeFlowTheme = {
8
+ durationMs: number;
9
+ segmentLength: number;
10
+ gapLength: number;
11
+ };
7
12
  export type ZoneflowTheme = {
8
13
  background: string;
9
14
  zoneTitle: string;
@@ -43,6 +48,7 @@ export type ZoneflowTheme = {
43
48
  info: ZoneflowStatusTone;
44
49
  warning: ZoneflowStatusTone;
45
50
  };
51
+ edgeFlow: ZoneflowEdgeFlowTheme;
46
52
  density: {
47
53
  zone: {
48
54
  detail: number;
@@ -50,6 +50,11 @@ export const defaultTheme = {
50
50
  shadow: "0 6px 14px rgba(180, 83, 9, 0.16)",
51
51
  },
52
52
  },
53
+ edgeFlow: {
54
+ durationMs: 1320,
55
+ segmentLength: 18,
56
+ gapLength: 28,
57
+ },
53
58
  density: {
54
59
  zone: {
55
60
  detail: 200,
@@ -99,6 +104,10 @@ export function resolveTheme(theme) {
99
104
  ...theme.status?.warning,
100
105
  },
101
106
  },
107
+ edgeFlow: {
108
+ ...defaultTheme.edgeFlow,
109
+ ...theme.edgeFlow,
110
+ },
102
111
  density: {
103
112
  zone: {
104
113
  ...defaultTheme.density.zone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoneflow/renderer-dom",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
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.9"
22
+ "@zoneflow/core": "0.0.10"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc -p tsconfig.json",