@zoneflow/renderer-dom 0.0.1

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.
@@ -0,0 +1,681 @@
1
+ import { resolveZoneAnchorRect } from "../anchors";
2
+ import { getZoneDepth, isZoneInputEnabled, isZoneOutputEnabled, } from "@zoneflow/core";
3
+ const SCENE_PADDING = 64;
4
+ function applyStyles(el, styles) {
5
+ for (const [key, value] of Object.entries(styles)) {
6
+ // @ts-expect-error CSSStyleDeclaration index access
7
+ el.style[key] = String(value);
8
+ }
9
+ }
10
+ function createEmptyMountRegistry() {
11
+ return {
12
+ zones: [],
13
+ paths: [],
14
+ };
15
+ }
16
+ function clearHost(host) {
17
+ host.innerHTML = "";
18
+ }
19
+ function createIdSet(ids) {
20
+ return new Set(ids ?? []);
21
+ }
22
+ function sortZonesForRender(params) {
23
+ const zones = Object.values(params.zonesById);
24
+ return zones
25
+ .map((zone, index) => ({
26
+ zone,
27
+ index,
28
+ depth: getZoneDepth(params.input.model, zone.zoneId),
29
+ }))
30
+ .sort((a, b) => a.depth - b.depth || a.index - b.index)
31
+ .map((entry) => entry.zone);
32
+ }
33
+ function resolvePathDisplayName(params) {
34
+ const trimmed = params.name.trim();
35
+ if (trimmed)
36
+ return trimmed;
37
+ return params.rule === null ? "Empty" : "Untitled";
38
+ }
39
+ function getOpacity(emphasis) {
40
+ switch (emphasis) {
41
+ case "strong":
42
+ return 1;
43
+ case "normal":
44
+ return 0.92;
45
+ case "dim":
46
+ return 0.52;
47
+ case "hidden":
48
+ default:
49
+ return 0.18;
50
+ }
51
+ }
52
+ function createSvgElement(tag) {
53
+ return document.createElementNS("http://www.w3.org/2000/svg", tag);
54
+ }
55
+ function getEdgeColor(kind, themePathEdge) {
56
+ return kind === "zone-to-path" ? themePathEdge : "#0f766e";
57
+ }
58
+ function getBezierCurvePathD(params) {
59
+ const { source, target } = params;
60
+ const sourceLead = Math.min(Math.max(Math.abs(target.x - source.x) * 0.18, 18), 42);
61
+ const leadSourceX = source.x + sourceLead;
62
+ const dx = target.x - leadSourceX;
63
+ const direction = dx >= 0 ? 1 : -1;
64
+ const handle = Math.min(Math.max(Math.abs(dx) * 0.45, 28), 104);
65
+ const control1X = leadSourceX + handle * direction;
66
+ const control2X = target.x - handle * direction;
67
+ return `M ${source.x} ${source.y} L ${leadSourceX} ${source.y} C ${control1X} ${source.y}, ${control2X} ${target.y}, ${target.x} ${target.y}`;
68
+ }
69
+ function getChevronPathD(params) {
70
+ const { target, direction } = params;
71
+ const tipX = target.x - direction * 6;
72
+ const baseX = tipX - direction * 7;
73
+ const topY = target.y - 4;
74
+ const bottomY = target.y + 4;
75
+ return `M ${baseX} ${topY} L ${tipX} ${target.y} L ${baseX} ${bottomY}`;
76
+ }
77
+ function computeSceneBounds(input) {
78
+ const { pipeline, viewportInfo, } = input;
79
+ let maxX = viewportInfo.world.x + viewportInfo.world.width;
80
+ let maxY = viewportInfo.world.y + viewportInfo.world.height;
81
+ for (const zone of Object.values(pipeline.graphLayout.zonesById)) {
82
+ maxX = Math.max(maxX, zone.rect.x + zone.rect.width);
83
+ maxY = Math.max(maxY, zone.rect.y + zone.rect.height);
84
+ }
85
+ for (const path of Object.values(pipeline.graphLayout.pathsById)) {
86
+ if (!path.rect)
87
+ continue;
88
+ maxX = Math.max(maxX, path.rect.x + path.rect.width);
89
+ maxY = Math.max(maxY, path.rect.y + path.rect.height);
90
+ }
91
+ for (const edges of Object.values(pipeline.graphLayout.edgesByPathId)) {
92
+ for (const edge of edges) {
93
+ maxX = Math.max(maxX, edge.source.x, edge.target.x);
94
+ maxY = Math.max(maxY, edge.source.y, edge.target.y);
95
+ }
96
+ }
97
+ return {
98
+ x: 0,
99
+ y: 0,
100
+ width: Math.max(1, maxX + SCENE_PADDING),
101
+ height: Math.max(1, maxY + SCENE_PADDING),
102
+ };
103
+ }
104
+ function renderZoneFallback(host, slot, context) {
105
+ const base = {
106
+ width: "100%",
107
+ height: "100%",
108
+ overflow: "hidden",
109
+ textOverflow: "ellipsis",
110
+ whiteSpace: "nowrap",
111
+ color: context.theme.zoneTitle,
112
+ boxSizing: "border-box",
113
+ fontFamily: "'IBM Plex Sans', 'Pretendard', sans-serif",
114
+ };
115
+ if (slot === "title") {
116
+ host.textContent = context.zone.name;
117
+ applyStyles(host, {
118
+ ...base,
119
+ fontSize: context.textScale === "lg" ? "15px" : context.textScale === "sm" ? "12px" : "13px",
120
+ fontWeight: 700,
121
+ });
122
+ return;
123
+ }
124
+ if (slot === "type") {
125
+ host.textContent = context.zone.zoneType;
126
+ applyStyles(host, {
127
+ ...base,
128
+ color: context.theme.zoneSubtext,
129
+ fontSize: "11px",
130
+ textTransform: "uppercase",
131
+ letterSpacing: "0.04em",
132
+ });
133
+ return;
134
+ }
135
+ if (slot === "badge") {
136
+ const badge = document.createElement("div");
137
+ badge.textContent = context.zone.action?.type ?? "zone";
138
+ applyStyles(badge, {
139
+ display: "inline-flex",
140
+ alignItems: "center",
141
+ height: "100%",
142
+ maxWidth: "100%",
143
+ padding: "0 8px",
144
+ borderRadius: "999px",
145
+ background: context.theme.zoneBadgeBg,
146
+ color: context.theme.zoneTitle,
147
+ fontSize: "11px",
148
+ fontWeight: 600,
149
+ boxSizing: "border-box",
150
+ overflow: "hidden",
151
+ textOverflow: "ellipsis",
152
+ whiteSpace: "nowrap",
153
+ });
154
+ host.appendChild(badge);
155
+ return;
156
+ }
157
+ if (slot === "body") {
158
+ host.textContent = context.zone.action?.type
159
+ ? `Action: ${context.zone.action.type}`
160
+ : `${context.zone.childZoneIds.length} child zones`;
161
+ applyStyles(host, {
162
+ ...base,
163
+ whiteSpace: "normal",
164
+ color: context.theme.zoneSubtext,
165
+ fontSize: "12px",
166
+ lineHeight: "1.4",
167
+ });
168
+ return;
169
+ }
170
+ if (slot === "footer") {
171
+ host.textContent = context.zone.zoneType === "action"
172
+ ? "action node"
173
+ : `${context.zone.pathIds.length} conditions`;
174
+ applyStyles(host, {
175
+ ...base,
176
+ color: context.theme.zoneSubtext,
177
+ fontSize: "11px",
178
+ });
179
+ }
180
+ }
181
+ function renderPathFallback(host, slot, context) {
182
+ const base = {
183
+ width: "100%",
184
+ height: "100%",
185
+ overflow: "hidden",
186
+ textOverflow: "ellipsis",
187
+ whiteSpace: "nowrap",
188
+ color: context.theme.pathLabel,
189
+ boxSizing: "border-box",
190
+ fontFamily: "'IBM Plex Sans', 'Pretendard', sans-serif",
191
+ };
192
+ if (slot === "label") {
193
+ host.textContent = resolvePathDisplayName({
194
+ name: context.path.name,
195
+ rule: context.path.rule,
196
+ });
197
+ applyStyles(host, {
198
+ ...base,
199
+ fontSize: "12px",
200
+ fontWeight: 700,
201
+ });
202
+ return;
203
+ }
204
+ if (slot === "rule") {
205
+ host.textContent = context.path.rule?.type ?? "empty";
206
+ applyStyles(host, {
207
+ ...base,
208
+ color: context.theme.zoneSubtext,
209
+ fontSize: "10px",
210
+ textTransform: "uppercase",
211
+ letterSpacing: "0.04em",
212
+ });
213
+ return;
214
+ }
215
+ if (slot === "target") {
216
+ host.textContent = context.pathVisual.targetZoneId ?? "unresolved";
217
+ applyStyles(host, {
218
+ ...base,
219
+ color: context.theme.zoneSubtext,
220
+ fontSize: "11px",
221
+ });
222
+ return;
223
+ }
224
+ if (slot === "body") {
225
+ host.textContent = context.path.rule
226
+ ? context.path.rule.payload
227
+ ? JSON.stringify(context.path.rule.payload)
228
+ : "No payload"
229
+ : "No rule configured";
230
+ applyStyles(host, {
231
+ ...base,
232
+ whiteSpace: "normal",
233
+ color: context.theme.zoneSubtext,
234
+ fontSize: "11px",
235
+ lineHeight: "1.4",
236
+ });
237
+ }
238
+ }
239
+ function toLocalRect(ownerRect, slotRect) {
240
+ return {
241
+ x: slotRect.x - ownerRect.x,
242
+ y: slotRect.y - ownerRect.y,
243
+ width: slotRect.width,
244
+ height: slotRect.height,
245
+ };
246
+ }
247
+ function createZoneSlotHost(params) {
248
+ const { zoneVisual, componentLayout, slot, input, mounts, owner, } = params;
249
+ const rect = componentLayout.slots[slot];
250
+ if (!rect)
251
+ return;
252
+ const localRect = toLocalRect(zoneVisual.rect, rect);
253
+ const host = document.createElement("div");
254
+ host.dataset.zoneflowZoneId = zoneVisual.zoneId;
255
+ host.dataset.zoneflowSlot = slot;
256
+ applyStyles(host, {
257
+ position: "absolute",
258
+ left: `${localRect.x}px`,
259
+ top: `${localRect.y}px`,
260
+ width: `${localRect.width}px`,
261
+ height: `${localRect.height}px`,
262
+ pointerEvents: "auto",
263
+ });
264
+ const visibility = input.pipeline.visibility.zoneVisibilityById[zoneVisual.zoneId];
265
+ const density = input.pipeline.density.zoneDensityById[zoneVisual.zoneId];
266
+ const context = {
267
+ model: input.model,
268
+ layoutModel: input.layoutModel,
269
+ zone: zoneVisual.zone,
270
+ zoneVisual,
271
+ density,
272
+ visibility,
273
+ componentLayout,
274
+ camera: input.camera,
275
+ theme: input.theme,
276
+ textScale: input.textScale,
277
+ };
278
+ const renderer = input.zoneComponentRenderers?.[slot];
279
+ if (renderer) {
280
+ renderer(host, context);
281
+ }
282
+ else {
283
+ renderZoneFallback(host, slot, context);
284
+ }
285
+ owner.appendChild(host);
286
+ mounts.zones.push({
287
+ key: `${zoneVisual.zoneId}:${slot}`,
288
+ zoneId: zoneVisual.zoneId,
289
+ slot,
290
+ host,
291
+ rect,
292
+ context,
293
+ });
294
+ }
295
+ function createPathSlotHost(params) {
296
+ const { pathVisual, componentLayout, slot, input, mounts, owner, } = params;
297
+ const rect = componentLayout.slots[slot];
298
+ if (!rect || !pathVisual.rect)
299
+ return;
300
+ const localRect = toLocalRect(pathVisual.rect, rect);
301
+ const host = document.createElement("div");
302
+ host.dataset.zoneflowPathId = pathVisual.pathId;
303
+ host.dataset.zoneflowSlot = slot;
304
+ applyStyles(host, {
305
+ position: "absolute",
306
+ left: `${localRect.x}px`,
307
+ top: `${localRect.y}px`,
308
+ width: `${localRect.width}px`,
309
+ height: `${localRect.height}px`,
310
+ pointerEvents: "auto",
311
+ });
312
+ const visibility = input.pipeline.visibility.pathVisibilityById[pathVisual.pathId];
313
+ const density = input.pipeline.density.pathDensityById[pathVisual.pathId];
314
+ const context = {
315
+ model: input.model,
316
+ layoutModel: input.layoutModel,
317
+ path: pathVisual.path,
318
+ pathVisual,
319
+ density,
320
+ visibility,
321
+ componentLayout,
322
+ camera: input.camera,
323
+ theme: input.theme,
324
+ textScale: input.textScale,
325
+ };
326
+ const renderer = input.pathComponentRenderers?.[slot];
327
+ if (renderer) {
328
+ renderer(host, context);
329
+ }
330
+ else {
331
+ renderPathFallback(host, slot, context);
332
+ }
333
+ owner.appendChild(host);
334
+ mounts.paths.push({
335
+ key: `${pathVisual.pathId}:${slot}`,
336
+ pathId: pathVisual.pathId,
337
+ slot,
338
+ host,
339
+ rect,
340
+ context,
341
+ });
342
+ }
343
+ function drawEdges(params) {
344
+ const { svg, input } = params;
345
+ for (const [pathId, edges] of Object.entries(input.pipeline.graphLayout.edgesByPathId)) {
346
+ const visibility = input.pipeline.visibility.pathVisibilityById[pathId];
347
+ if (!visibility?.shouldRenderEdge)
348
+ continue;
349
+ for (const edge of edges) {
350
+ const stroke = getEdgeColor(edge.kind, input.theme.pathEdge);
351
+ const path = createSvgElement("path");
352
+ path.setAttribute("d", getBezierCurvePathD({
353
+ source: edge.source,
354
+ target: edge.target,
355
+ }));
356
+ path.setAttribute("fill", "none");
357
+ path.setAttribute("stroke", stroke);
358
+ path.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "2.25" : "1.85");
359
+ path.setAttribute("stroke-linecap", "round");
360
+ path.setAttribute("stroke-linejoin", "round");
361
+ path.setAttribute("opacity", String(getOpacity(visibility.emphasis)));
362
+ svg.appendChild(path);
363
+ const chevron = createSvgElement("path");
364
+ chevron.setAttribute("d", getChevronPathD({
365
+ target: edge.target,
366
+ direction: edge.kind === "path-to-zone" ? 1 : edge.target.x >= edge.source.x ? 1 : -1,
367
+ }));
368
+ chevron.setAttribute("fill", "none");
369
+ chevron.setAttribute("stroke", stroke);
370
+ chevron.setAttribute("stroke-width", edge.kind === "path-to-zone" ? "1.95" : "1.7");
371
+ chevron.setAttribute("stroke-linecap", "round");
372
+ chevron.setAttribute("stroke-linejoin", "round");
373
+ chevron.setAttribute("opacity", String(getOpacity(visibility.emphasis)));
374
+ svg.appendChild(chevron);
375
+ }
376
+ }
377
+ }
378
+ function createSurfaceChrome(params) {
379
+ const { owner, accent, radius, topBandOpacity = 0.64 } = params;
380
+ const chrome = document.createElement("div");
381
+ const topBand = document.createElement("div");
382
+ const cornerGlow = document.createElement("div");
383
+ applyStyles(chrome, {
384
+ position: "absolute",
385
+ inset: "0",
386
+ borderRadius: radius,
387
+ pointerEvents: "none",
388
+ background: "linear-gradient(180deg, rgba(255,255,255,0.74) 0%, rgba(255,255,255,0.08) 42%, rgba(255,255,255,0) 100%)",
389
+ });
390
+ applyStyles(topBand, {
391
+ position: "absolute",
392
+ left: "0",
393
+ top: "0",
394
+ right: "0",
395
+ height: "44px",
396
+ borderTopLeftRadius: radius,
397
+ borderTopRightRadius: radius,
398
+ background: `linear-gradient(90deg, ${accent} 0%, rgba(255,255,255,0.04) 72%)`,
399
+ opacity: topBandOpacity,
400
+ pointerEvents: "none",
401
+ });
402
+ applyStyles(cornerGlow, {
403
+ position: "absolute",
404
+ right: "-20px",
405
+ top: "-24px",
406
+ width: "116px",
407
+ height: "116px",
408
+ borderRadius: "999px",
409
+ background: "radial-gradient(circle, rgba(255,255,255,0.92) 0%, rgba(255,255,255,0.22) 36%, rgba(255,255,255,0) 72%)",
410
+ pointerEvents: "none",
411
+ });
412
+ chrome.appendChild(topBand);
413
+ chrome.appendChild(cornerGlow);
414
+ owner.appendChild(chrome);
415
+ }
416
+ function drawZoneAnchors(params) {
417
+ const { owner, zone, input } = params;
418
+ const zoneBorderColor = zone.zone.zoneType === "action"
419
+ ? input.theme.zoneActionBorder
420
+ : input.theme.zoneContainerBorder;
421
+ const anchorAccentColor = zone.zone.zoneType === "action"
422
+ ? "rgba(245, 158, 11, 0.96)"
423
+ : "rgba(37, 99, 235, 0.96)";
424
+ const shouldRenderAnchor = (kind) => kind === "inlet"
425
+ ? isZoneInputEnabled(zone.zone)
426
+ : isZoneOutputEnabled(zone.zone);
427
+ for (const kind of ["inlet", "outlet"]) {
428
+ if (!shouldRenderAnchor(kind))
429
+ continue;
430
+ const anchor = zone.anchors[kind];
431
+ const rect = resolveZoneAnchorRect({
432
+ zoneRect: zone.rect,
433
+ anchor,
434
+ kind,
435
+ });
436
+ const el = document.createElement("div");
437
+ const seam = document.createElement("div");
438
+ const accent = document.createElement("div");
439
+ applyStyles(el, {
440
+ position: "absolute",
441
+ left: `${rect.x - zone.rect.x}px`,
442
+ top: `${rect.y - zone.rect.y}px`,
443
+ width: `${rect.width}px`,
444
+ height: `${rect.height}px`,
445
+ borderRadius: "0",
446
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(247,250,253,0.98) 100%)",
447
+ border: `1px solid ${zoneBorderColor}`,
448
+ borderRight: kind === "inlet" ? "none" : `1px solid ${zoneBorderColor}`,
449
+ borderLeft: kind === "outlet" ? "none" : `1px solid ${zoneBorderColor}`,
450
+ boxShadow: "0 18px 28px rgba(15, 23, 42, 0.08), inset 0 1px 0 rgba(255,255,255,0.9)",
451
+ boxSizing: "border-box",
452
+ overflow: "hidden",
453
+ pointerEvents: "none",
454
+ });
455
+ applyStyles(seam, {
456
+ position: "absolute",
457
+ top: "0",
458
+ bottom: "0",
459
+ width: "10px",
460
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(247,250,253,0.98) 100%)",
461
+ right: kind === "inlet" ? "0" : "auto",
462
+ left: kind === "outlet" ? "0" : "auto",
463
+ });
464
+ applyStyles(accent, {
465
+ position: "absolute",
466
+ top: "50%",
467
+ width: "4px",
468
+ height: `${Math.max(22, rect.height * 0.34)}px`,
469
+ transform: "translateY(-50%)",
470
+ borderRadius: "2px",
471
+ background: anchorAccentColor,
472
+ left: kind === "inlet" ? "8px" : "auto",
473
+ right: kind === "outlet" ? "8px" : "auto",
474
+ boxShadow: `0 0 0 4px ${anchorAccentColor.replace("0.96", "0.12")}`,
475
+ });
476
+ el.appendChild(seam);
477
+ el.appendChild(accent);
478
+ owner.appendChild(el);
479
+ }
480
+ }
481
+ export const domDrawEngine = {
482
+ draw(input) {
483
+ const mounts = createEmptyMountRegistry();
484
+ const { host, viewportInfo, camera, theme, pipeline, interactionHandlers, } = input;
485
+ const excludedZoneIds = createIdSet(input.exclusionState?.excludedZoneIds);
486
+ const excludedPathIds = createIdSet(input.exclusionState?.excludedPathIds);
487
+ clearHost(host);
488
+ applyStyles(host, {
489
+ position: "relative",
490
+ overflow: "hidden",
491
+ background: theme.background,
492
+ });
493
+ const sceneBounds = computeSceneBounds(input);
494
+ const viewportRoot = document.createElement("div");
495
+ const worldRoot = document.createElement("div");
496
+ const edgeSvg = createSvgElement("svg");
497
+ const zoneLayer = document.createElement("div");
498
+ const pathLayer = document.createElement("div");
499
+ applyStyles(viewportRoot, {
500
+ position: "absolute",
501
+ left: `${viewportInfo.effective.x}px`,
502
+ top: `${viewportInfo.effective.y}px`,
503
+ width: `${viewportInfo.effective.width}px`,
504
+ height: `${viewportInfo.effective.height}px`,
505
+ overflow: "hidden",
506
+ background: theme.background,
507
+ });
508
+ viewportRoot.addEventListener("click", (event) => {
509
+ if (event.target === viewportRoot) {
510
+ interactionHandlers?.onBackgroundClick?.();
511
+ }
512
+ });
513
+ applyStyles(worldRoot, {
514
+ position: "absolute",
515
+ left: "0",
516
+ top: "0",
517
+ width: `${sceneBounds.width}px`,
518
+ height: `${sceneBounds.height}px`,
519
+ transform: `translate(${camera.x - viewportInfo.effective.x}px, ${camera.y - viewportInfo.effective.y}px) scale(${camera.zoom})`,
520
+ transformOrigin: "0 0",
521
+ willChange: "transform",
522
+ });
523
+ edgeSvg.setAttribute("width", String(sceneBounds.width));
524
+ edgeSvg.setAttribute("height", String(sceneBounds.height));
525
+ edgeSvg.setAttribute("viewBox", `0 0 ${sceneBounds.width} ${sceneBounds.height}`);
526
+ applyStyles(edgeSvg, {
527
+ position: "absolute",
528
+ left: "0",
529
+ top: "0",
530
+ width: `${sceneBounds.width}px`,
531
+ height: `${sceneBounds.height}px`,
532
+ overflow: "visible",
533
+ pointerEvents: "none",
534
+ zIndex: 20,
535
+ });
536
+ applyStyles(zoneLayer, {
537
+ position: "absolute",
538
+ left: "0",
539
+ top: "0",
540
+ width: `${sceneBounds.width}px`,
541
+ height: `${sceneBounds.height}px`,
542
+ zIndex: 10,
543
+ });
544
+ applyStyles(pathLayer, {
545
+ position: "absolute",
546
+ left: "0",
547
+ top: "0",
548
+ width: `${sceneBounds.width}px`,
549
+ height: `${sceneBounds.height}px`,
550
+ zIndex: 30,
551
+ });
552
+ worldRoot.appendChild(edgeSvg);
553
+ worldRoot.appendChild(zoneLayer);
554
+ worldRoot.appendChild(pathLayer);
555
+ viewportRoot.appendChild(worldRoot);
556
+ host.appendChild(viewportRoot);
557
+ drawEdges({
558
+ svg: edgeSvg,
559
+ input,
560
+ });
561
+ for (const zoneVisual of sortZonesForRender({
562
+ input,
563
+ zonesById: pipeline.graphLayout.zonesById,
564
+ })) {
565
+ const visibility = pipeline.visibility.zoneVisibilityById[zoneVisual.zoneId];
566
+ if (!visibility?.isVisible || excludedZoneIds.has(zoneVisual.zoneId))
567
+ continue;
568
+ const componentLayout = pipeline.componentLayout.zonesById[zoneVisual.zoneId];
569
+ const zoneDepth = getZoneDepth(input.model, zoneVisual.zoneId);
570
+ const zoneEl = document.createElement("div");
571
+ const zoneBodyEl = document.createElement("div");
572
+ const zoneChromeEl = document.createElement("div");
573
+ zoneEl.dataset.zoneflowZoneId = zoneVisual.zoneId;
574
+ zoneBodyEl.dataset.zoneflowZoneBody = zoneVisual.zoneId;
575
+ applyStyles(zoneEl, {
576
+ position: "absolute",
577
+ left: `${zoneVisual.rect.x}px`,
578
+ top: `${zoneVisual.rect.y}px`,
579
+ width: `${zoneVisual.rect.width}px`,
580
+ height: `${zoneVisual.rect.height}px`,
581
+ opacity: getOpacity(visibility.emphasis),
582
+ overflow: "visible",
583
+ zIndex: zoneDepth + 1,
584
+ });
585
+ applyStyles(zoneBodyEl, {
586
+ position: "absolute",
587
+ left: "0",
588
+ top: "0",
589
+ width: "100%",
590
+ height: "100%",
591
+ borderRadius: "0",
592
+ border: `1px solid ${zoneVisual.zone.zoneType === "action"
593
+ ? theme.zoneActionBorder
594
+ : theme.zoneContainerBorder}`,
595
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(248,250,252,0.98) 100%)",
596
+ boxSizing: "border-box",
597
+ boxShadow: "0 18px 34px rgba(15, 23, 42, 0.08), 0 3px 8px rgba(15, 23, 42, 0.05)",
598
+ overflow: "hidden",
599
+ });
600
+ zoneEl.addEventListener("click", (event) => {
601
+ event.stopPropagation();
602
+ interactionHandlers?.onZoneClick?.(zoneVisual.zoneId);
603
+ });
604
+ createSurfaceChrome({
605
+ owner: zoneChromeEl,
606
+ accent: zoneVisual.zone.zoneType === "action"
607
+ ? "rgba(245, 158, 11, 0.18)"
608
+ : "rgba(37, 99, 235, 0.12)",
609
+ radius: "0",
610
+ });
611
+ zoneBodyEl.appendChild(zoneChromeEl);
612
+ zoneEl.appendChild(zoneBodyEl);
613
+ for (const slot of Object.keys(componentLayout?.slots ?? {})) {
614
+ createZoneSlotHost({
615
+ zoneVisual,
616
+ componentLayout,
617
+ slot,
618
+ input,
619
+ mounts,
620
+ owner: zoneBodyEl,
621
+ });
622
+ }
623
+ drawZoneAnchors({
624
+ owner: zoneEl,
625
+ zone: zoneVisual,
626
+ input,
627
+ });
628
+ zoneLayer.appendChild(zoneEl);
629
+ }
630
+ for (const pathVisual of Object.values(pipeline.graphLayout.pathsById)) {
631
+ const visibility = pipeline.visibility.pathVisibilityById[pathVisual.pathId];
632
+ if (!visibility?.shouldRenderNode ||
633
+ !pathVisual.rect ||
634
+ excludedPathIds.has(pathVisual.pathId)) {
635
+ continue;
636
+ }
637
+ const componentLayout = pipeline.componentLayout.pathsById[pathVisual.pathId];
638
+ const pathEl = document.createElement("div");
639
+ const pathChromeEl = document.createElement("div");
640
+ pathEl.dataset.zoneflowPathId = pathVisual.pathId;
641
+ applyStyles(pathEl, {
642
+ position: "absolute",
643
+ left: `${pathVisual.rect.x}px`,
644
+ top: `${pathVisual.rect.y}px`,
645
+ width: `${pathVisual.rect.width}px`,
646
+ height: `${pathVisual.rect.height}px`,
647
+ borderRadius: "18px",
648
+ border: `1px solid ${theme.pathEdge}`,
649
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(246,248,252,0.98) 100%)",
650
+ boxSizing: "border-box",
651
+ boxShadow: "0 16px 26px rgba(15, 23, 42, 0.08), 0 3px 8px rgba(15, 23, 42, 0.05)",
652
+ opacity: getOpacity(visibility.emphasis),
653
+ zIndex: 1,
654
+ overflow: "hidden",
655
+ });
656
+ pathEl.addEventListener("click", (event) => {
657
+ event.stopPropagation();
658
+ interactionHandlers?.onPathClick?.(pathVisual.pathId);
659
+ });
660
+ createSurfaceChrome({
661
+ owner: pathChromeEl,
662
+ accent: "rgba(56, 189, 248, 0.16)",
663
+ radius: "18px",
664
+ topBandOpacity: 0.72,
665
+ });
666
+ pathEl.appendChild(pathChromeEl);
667
+ for (const slot of Object.keys(componentLayout?.slots ?? {})) {
668
+ createPathSlotHost({
669
+ pathVisual,
670
+ componentLayout,
671
+ slot,
672
+ input,
673
+ mounts,
674
+ owner: pathEl,
675
+ });
676
+ }
677
+ pathLayer.appendChild(pathEl);
678
+ }
679
+ return mounts;
680
+ },
681
+ };
@@ -0,0 +1,6 @@
1
+ import type { GraphLayoutEngine } from "../types";
2
+ export declare const DEFAULT_PATH_NODE_WIDTH = 120;
3
+ export declare const DEFAULT_PATH_NODE_HEIGHT = 32;
4
+ export declare const DEFAULT_PATH_NODE_OFFSET_X = 32;
5
+ export declare const DEFAULT_PATH_NODE_GAP_Y = 40;
6
+ export declare const defaultGraphLayoutEngine: GraphLayoutEngine;