@zoneflow/renderer-dom 0.0.2 → 0.0.4

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/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @zoneflow/renderer-dom
2
+
3
+ `@zoneflow/renderer-dom`은 Zoneflow의 저수준 DOM 렌더러 엔진 패키지입니다.
4
+
5
+ 이 패키지는 주로 다음을 포함합니다.
6
+
7
+ - 그래프 레이아웃 파이프라인
8
+ - DOM draw engine
9
+ - renderer frame / mount registry
10
+ - renderer용 타입 정의
11
+
12
+ 대부분의 앱에서는 이 패키지를 직접 사용할 필요가 없습니다.
13
+ 일반적인 React 앱은 `@zoneflow/react`를 사용하면 충분합니다.
14
+
15
+ ## 설치
16
+
17
+ ```bash
18
+ pnpm add @zoneflow/renderer-dom
19
+ ```
20
+
21
+ ## 언제 직접 쓰나
22
+
23
+ - React 없이 renderer DOM 레이어를 직접 붙일 때
24
+ - low-level draw engine이나 pipeline을 교체/실험할 때
25
+ - renderer frame을 직접 소비하는 커스텀 환경을 만들 때
26
+
27
+ 일반적인 앱 통합은 `@zoneflow/react`를 우선 사용하는 편이 맞습니다.
28
+
29
+ 레포지토리: [github.com/groobee/zoneflow](https://github.com/groobee/zoneflow)
@@ -36,6 +36,56 @@ function resolvePathDisplayName(params) {
36
36
  return trimmed;
37
37
  return params.rule === null ? "Empty" : "Untitled";
38
38
  }
39
+ function resolvePathTargetDisplay(params) {
40
+ const targetZoneId = params.pathVisual.targetZoneId;
41
+ if (!targetZoneId) {
42
+ return {
43
+ label: "—",
44
+ status: "unconfigured",
45
+ };
46
+ }
47
+ const targetZone = params.model.zonesById[targetZoneId];
48
+ if (!targetZone) {
49
+ return {
50
+ label: "—",
51
+ status: "missing",
52
+ };
53
+ }
54
+ return {
55
+ label: targetZone.name,
56
+ status: "resolved",
57
+ };
58
+ }
59
+ function createPathStatusBadge(params) {
60
+ const { owner, status, theme } = params;
61
+ const badge = document.createElement("div");
62
+ const tone = status === "missing" ? theme.status.warning : theme.status.info;
63
+ const isMissing = status === "missing";
64
+ badge.title = isMissing ? "Broken path target" : "Path target not set";
65
+ badge.setAttribute("aria-label", isMissing ? "Broken path target" : "Path target not set");
66
+ badge.textContent = isMissing ? "⚠" : "?";
67
+ applyStyles(badge, {
68
+ position: "absolute",
69
+ right: "10px",
70
+ top: "10px",
71
+ width: "22px",
72
+ height: "22px",
73
+ display: "flex",
74
+ alignItems: "center",
75
+ justifyContent: "center",
76
+ borderRadius: "999px",
77
+ border: tone.border,
78
+ background: tone.background,
79
+ color: tone.color,
80
+ boxShadow: tone.shadow,
81
+ fontSize: "12px",
82
+ lineHeight: "1",
83
+ fontWeight: "700",
84
+ pointerEvents: "none",
85
+ zIndex: 2,
86
+ });
87
+ owner.appendChild(badge);
88
+ }
39
89
  function getOpacity(emphasis) {
40
90
  switch (emphasis) {
41
91
  case "strong":
@@ -52,8 +102,10 @@ function getOpacity(emphasis) {
52
102
  function createSvgElement(tag) {
53
103
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
54
104
  }
55
- function getEdgeColor(kind, themePathEdge) {
56
- return kind === "zone-to-path" ? themePathEdge : "#0f766e";
105
+ function getEdgeColor(params) {
106
+ return params.kind === "zone-to-path"
107
+ ? params.theme.pathEdge
108
+ : params.theme.pathInboundEdge;
57
109
  }
58
110
  function getBezierCurvePathD(params) {
59
111
  const { source, target } = params;
@@ -190,10 +242,11 @@ function renderPathFallback(host, slot, context) {
190
242
  fontFamily: "'IBM Plex Sans', 'Pretendard', sans-serif",
191
243
  };
192
244
  if (slot === "label") {
193
- host.textContent = resolvePathDisplayName({
245
+ const title = resolvePathDisplayName({
194
246
  name: context.path.name,
195
247
  rule: context.path.rule,
196
248
  });
249
+ host.textContent = title;
197
250
  applyStyles(host, {
198
251
  ...base,
199
252
  fontSize: "12px",
@@ -213,11 +266,20 @@ function renderPathFallback(host, slot, context) {
213
266
  return;
214
267
  }
215
268
  if (slot === "target") {
216
- host.textContent = context.pathVisual.targetZoneId ?? "unresolved";
269
+ const targetDisplay = resolvePathTargetDisplay({
270
+ model: context.model,
271
+ pathVisual: context.pathVisual,
272
+ });
273
+ host.textContent = targetDisplay.label;
217
274
  applyStyles(host, {
218
275
  ...base,
219
- color: context.theme.zoneSubtext,
276
+ color: targetDisplay.status === "missing"
277
+ ? context.theme.status.warning.color
278
+ : targetDisplay.status === "unconfigured"
279
+ ? context.theme.status.info.color
280
+ : context.theme.zoneSubtext,
220
281
  fontSize: "11px",
282
+ fontWeight: targetDisplay.status === "resolved" ? 500 : 700,
221
283
  });
222
284
  return;
223
285
  }
@@ -347,7 +409,10 @@ function drawEdges(params) {
347
409
  if (!visibility?.shouldRenderEdge)
348
410
  continue;
349
411
  for (const edge of edges) {
350
- const stroke = getEdgeColor(edge.kind, input.theme.pathEdge);
412
+ const stroke = getEdgeColor({
413
+ kind: edge.kind,
414
+ theme: input.theme,
415
+ });
351
416
  const path = createSvgElement("path");
352
417
  path.setAttribute("d", getBezierCurvePathD({
353
418
  source: edge.source,
@@ -376,7 +441,7 @@ function drawEdges(params) {
376
441
  }
377
442
  }
378
443
  function createSurfaceChrome(params) {
379
- const { owner, accent, radius, topBandOpacity = 0.64 } = params;
444
+ const { owner, accent, radius, theme, topBandOpacity = 0.64 } = params;
380
445
  const chrome = document.createElement("div");
381
446
  const topBand = document.createElement("div");
382
447
  const cornerGlow = document.createElement("div");
@@ -385,7 +450,7 @@ function createSurfaceChrome(params) {
385
450
  inset: "0",
386
451
  borderRadius: radius,
387
452
  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%)",
453
+ background: theme.surface.chrome.overlay,
389
454
  });
390
455
  applyStyles(topBand, {
391
456
  position: "absolute",
@@ -395,7 +460,7 @@ function createSurfaceChrome(params) {
395
460
  height: "44px",
396
461
  borderTopLeftRadius: radius,
397
462
  borderTopRightRadius: radius,
398
- background: `linear-gradient(90deg, ${accent} 0%, rgba(255,255,255,0.04) 72%)`,
463
+ background: `linear-gradient(90deg, ${accent} 0%, ${theme.surface.chrome.accentFade} 72%)`,
399
464
  opacity: topBandOpacity,
400
465
  pointerEvents: "none",
401
466
  });
@@ -406,7 +471,7 @@ function createSurfaceChrome(params) {
406
471
  width: "116px",
407
472
  height: "116px",
408
473
  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%)",
474
+ background: theme.surface.chrome.glow,
410
475
  pointerEvents: "none",
411
476
  });
412
477
  chrome.appendChild(topBand);
@@ -419,8 +484,8 @@ function drawZoneAnchors(params) {
419
484
  ? input.theme.zoneActionBorder
420
485
  : input.theme.zoneContainerBorder;
421
486
  const anchorAccentColor = zone.zone.zoneType === "action"
422
- ? "rgba(245, 158, 11, 0.96)"
423
- : "rgba(37, 99, 235, 0.96)";
487
+ ? input.theme.surface.anchor.actionAccent
488
+ : input.theme.surface.anchor.containerAccent;
424
489
  const shouldRenderAnchor = (kind) => kind === "inlet"
425
490
  ? isZoneInputEnabled(zone.zone)
426
491
  : isZoneOutputEnabled(zone.zone);
@@ -443,11 +508,11 @@ function drawZoneAnchors(params) {
443
508
  width: `${rect.width}px`,
444
509
  height: `${rect.height}px`,
445
510
  borderRadius: "0",
446
- background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(247,250,253,0.98) 100%)",
511
+ background: input.theme.surface.anchor.background,
447
512
  border: `1px solid ${zoneBorderColor}`,
448
513
  borderRight: kind === "inlet" ? "none" : `1px solid ${zoneBorderColor}`,
449
514
  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)",
515
+ boxShadow: input.theme.surface.anchor.shadow,
451
516
  boxSizing: "border-box",
452
517
  overflow: "hidden",
453
518
  pointerEvents: "none",
@@ -457,7 +522,7 @@ function drawZoneAnchors(params) {
457
522
  top: "0",
458
523
  bottom: "0",
459
524
  width: "10px",
460
- background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(247,250,253,0.98) 100%)",
525
+ background: input.theme.surface.anchor.background,
461
526
  right: kind === "inlet" ? "0" : "auto",
462
527
  left: kind === "outlet" ? "0" : "auto",
463
528
  });
@@ -592,9 +657,9 @@ export const domDrawEngine = {
592
657
  border: `1px solid ${zoneVisual.zone.zoneType === "action"
593
658
  ? theme.zoneActionBorder
594
659
  : theme.zoneContainerBorder}`,
595
- background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(248,250,252,0.98) 100%)",
660
+ background: theme.surface.zone.background,
596
661
  boxSizing: "border-box",
597
- boxShadow: "0 18px 34px rgba(15, 23, 42, 0.08), 0 3px 8px rgba(15, 23, 42, 0.05)",
662
+ boxShadow: theme.surface.zone.shadow,
598
663
  overflow: "hidden",
599
664
  });
600
665
  zoneEl.addEventListener("click", (event) => {
@@ -604,9 +669,10 @@ export const domDrawEngine = {
604
669
  createSurfaceChrome({
605
670
  owner: zoneChromeEl,
606
671
  accent: zoneVisual.zone.zoneType === "action"
607
- ? "rgba(245, 158, 11, 0.18)"
608
- : "rgba(37, 99, 235, 0.12)",
672
+ ? theme.surface.zone.actionAccent
673
+ : theme.surface.zone.containerAccent,
609
674
  radius: "0",
675
+ theme,
610
676
  });
611
677
  zoneBodyEl.appendChild(zoneChromeEl);
612
678
  zoneEl.appendChild(zoneBodyEl);
@@ -646,9 +712,9 @@ export const domDrawEngine = {
646
712
  height: `${pathVisual.rect.height}px`,
647
713
  borderRadius: "18px",
648
714
  border: `1px solid ${theme.pathEdge}`,
649
- background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(246,248,252,0.98) 100%)",
715
+ background: theme.surface.path.background,
650
716
  boxSizing: "border-box",
651
- boxShadow: "0 16px 26px rgba(15, 23, 42, 0.08), 0 3px 8px rgba(15, 23, 42, 0.05)",
717
+ boxShadow: theme.surface.path.shadow,
652
718
  opacity: getOpacity(visibility.emphasis),
653
719
  zIndex: 1,
654
720
  overflow: "hidden",
@@ -659,11 +725,23 @@ export const domDrawEngine = {
659
725
  });
660
726
  createSurfaceChrome({
661
727
  owner: pathChromeEl,
662
- accent: "rgba(56, 189, 248, 0.16)",
728
+ accent: theme.surface.path.accent,
663
729
  radius: "18px",
730
+ theme,
664
731
  topBandOpacity: 0.72,
665
732
  });
666
733
  pathEl.appendChild(pathChromeEl);
734
+ const targetDisplay = resolvePathTargetDisplay({
735
+ model: input.model,
736
+ pathVisual,
737
+ });
738
+ if (targetDisplay.status !== "resolved") {
739
+ createPathStatusBadge({
740
+ owner: pathEl,
741
+ status: targetDisplay.status,
742
+ theme,
743
+ });
744
+ }
667
745
  for (const slot of Object.keys(componentLayout?.slots ?? {})) {
668
746
  createPathSlotHost({
669
747
  pathVisual,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./theme";
2
+ export * from "./themes/defaultTheme";
2
3
  export * from "./types";
3
4
  export * from "./anchors";
4
5
  export * from "./pipeline";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./theme";
2
+ export * from "./themes/defaultTheme";
2
3
  export * from "./types";
3
4
  export * from "./anchors";
4
5
  export * from "./pipeline";
package/dist/theme.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ export type ZoneflowStatusTone = {
2
+ border: string;
3
+ background: string;
4
+ color: string;
5
+ shadow: string;
6
+ };
1
7
  export type ZoneflowTheme = {
2
8
  background: string;
3
9
  zoneTitle: string;
@@ -7,7 +13,36 @@ export type ZoneflowTheme = {
7
13
  zoneBadgeBg: string;
8
14
  pathLabel: string;
9
15
  pathEdge: string;
16
+ pathInboundEdge: string;
10
17
  selection: string;
18
+ surface: {
19
+ chrome: {
20
+ overlay: string;
21
+ glow: string;
22
+ accentFade: string;
23
+ };
24
+ zone: {
25
+ background: string;
26
+ shadow: string;
27
+ containerAccent: string;
28
+ actionAccent: string;
29
+ };
30
+ path: {
31
+ background: string;
32
+ shadow: string;
33
+ accent: string;
34
+ };
35
+ anchor: {
36
+ background: string;
37
+ shadow: string;
38
+ containerAccent: string;
39
+ actionAccent: string;
40
+ };
41
+ };
42
+ status: {
43
+ info: ZoneflowStatusTone;
44
+ warning: ZoneflowStatusTone;
45
+ };
11
46
  density: {
12
47
  zone: {
13
48
  detail: number;
@@ -10,7 +10,46 @@ export const defaultTheme = {
10
10
  zoneBadgeBg: "#e0f2fe",
11
11
  pathLabel: "#1e293b",
12
12
  pathEdge: "#7a8aa0",
13
+ pathInboundEdge: "#0f766e",
13
14
  selection: "#2e90fa",
15
+ surface: {
16
+ chrome: {
17
+ overlay: "linear-gradient(180deg, rgba(255,255,255,0.74) 0%, rgba(255,255,255,0.08) 42%, rgba(255,255,255,0) 100%)",
18
+ glow: "radial-gradient(circle, rgba(255,255,255,0.92) 0%, rgba(255,255,255,0.22) 36%, rgba(255,255,255,0) 72%)",
19
+ accentFade: "rgba(255,255,255,0.04)",
20
+ },
21
+ zone: {
22
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(248,250,252,0.98) 100%)",
23
+ shadow: "0 18px 34px rgba(15, 23, 42, 0.08), 0 3px 8px rgba(15, 23, 42, 0.05)",
24
+ containerAccent: "rgba(37, 99, 235, 0.12)",
25
+ actionAccent: "rgba(245, 158, 11, 0.18)",
26
+ },
27
+ path: {
28
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(246,248,252,0.98) 100%)",
29
+ shadow: "0 16px 26px rgba(15, 23, 42, 0.08), 0 3px 8px rgba(15, 23, 42, 0.05)",
30
+ accent: "rgba(56, 189, 248, 0.16)",
31
+ },
32
+ anchor: {
33
+ background: "linear-gradient(180deg, rgba(255,255,255,0.99) 0%, rgba(247,250,253,0.98) 100%)",
34
+ shadow: "0 18px 28px rgba(15, 23, 42, 0.08), inset 0 1px 0 rgba(255,255,255,0.9)",
35
+ containerAccent: "rgba(37, 99, 235, 0.96)",
36
+ actionAccent: "rgba(245, 158, 11, 0.96)",
37
+ },
38
+ },
39
+ status: {
40
+ info: {
41
+ border: "1px solid rgba(217, 119, 6, 0.24)",
42
+ background: "linear-gradient(180deg, rgba(255,251,235,0.98) 0%, rgba(254,243,199,0.98) 100%)",
43
+ color: "#b45309",
44
+ shadow: "0 6px 14px rgba(180, 83, 9, 0.16)",
45
+ },
46
+ warning: {
47
+ border: "1px solid rgba(217, 119, 6, 0.24)",
48
+ background: "linear-gradient(180deg, rgba(255,251,235,0.98) 0%, rgba(254,243,199,0.98) 100%)",
49
+ color: "#b45309",
50
+ shadow: "0 6px 14px rgba(180, 83, 9, 0.16)",
51
+ },
52
+ },
14
53
  density: {
15
54
  zone: {
16
55
  detail: 200,
@@ -32,6 +71,34 @@ export function resolveTheme(theme) {
32
71
  return {
33
72
  ...defaultTheme,
34
73
  ...theme,
74
+ surface: {
75
+ chrome: {
76
+ ...defaultTheme.surface.chrome,
77
+ ...theme.surface?.chrome,
78
+ },
79
+ zone: {
80
+ ...defaultTheme.surface.zone,
81
+ ...theme.surface?.zone,
82
+ },
83
+ path: {
84
+ ...defaultTheme.surface.path,
85
+ ...theme.surface?.path,
86
+ },
87
+ anchor: {
88
+ ...defaultTheme.surface.anchor,
89
+ ...theme.surface?.anchor,
90
+ },
91
+ },
92
+ status: {
93
+ info: {
94
+ ...defaultTheme.status.info,
95
+ ...theme.status?.info,
96
+ },
97
+ warning: {
98
+ ...defaultTheme.status.warning,
99
+ ...theme.status?.warning,
100
+ },
101
+ },
35
102
  density: {
36
103
  zone: {
37
104
  ...defaultTheme.density.zone,
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@zoneflow/renderer-dom",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
+ "description": "Low-level DOM renderer engines for Zoneflow.",
4
5
  "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
8
+ "homepage": "https://github.com/groobee/zoneflow",
7
9
  "repository": {
8
10
  "type": "git",
9
11
  "url": "https://github.com/groobee/zoneflow.git",
@@ -16,7 +18,7 @@
16
18
  "dist"
17
19
  ],
18
20
  "dependencies": {
19
- "@zoneflow/core": "0.0.2"
21
+ "@zoneflow/core": "0.0.4"
20
22
  },
21
23
  "scripts": {
22
24
  "build": "tsc -p tsconfig.json",