@cardenelabs/cdl 0.5.0 → 0.6.0

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.
@@ -87,6 +87,7 @@ import {
87
87
  segmentsCrossRect,
88
88
  } from "./layout/collisions";
89
89
  import { measureTextWidth } from "./layout/text-width";
90
+ import { templateRefIds } from "./render/utils";
90
91
  import {
91
92
  bboxOverlaps,
92
93
  isNearAny,
@@ -164,7 +165,7 @@ export const MERMAID_PARITY_BASIS_VERSION = "11.16";
164
165
  * | classDiagram | classDiagram |
165
166
  * | gantt | gantt |
166
167
  * | quadrant | quadrantChart |
167
- * | mindMap / mindMapRadial | mindmap |
168
+ * | mindMap | mindmap |
168
169
  * | userJourney | journey |
169
170
  * | chart / pie | xyChart / pie |
170
171
  * | topology / infrastructure / network | architecture-beta (v11.1.0、 service / group / edge で構成を描く) |
@@ -190,8 +191,6 @@ export const MERMAID_COMPATIBLE_PRESETS: ReadonlySet<string> = new Set([
190
191
  "quad",
191
192
  "mindmap",
192
193
  "mind",
193
- "mindmapradial",
194
- "mindradial",
195
194
  "userjourney",
196
195
  "journey",
197
196
  "chart",
@@ -418,7 +417,6 @@ const MIN_NODE_W = 80;
418
417
  const MIN_NODE_H = 40;
419
418
  const MIN_FONT_SIZE = 12;
420
419
  const ROW_FORMAT = /^[^:]+:\s*.+$/;
421
- const PLACEHOLDER_RE = /\{(\w+)\}/g;
422
420
 
423
421
  function emptyCounts(): Record<VisualAxis, number> {
424
422
  return {
@@ -1864,7 +1862,11 @@ function runAxes(
1864
1862
  // 行を描かない種別の `rows` は画面に出ないので、 形式を要求しても意味がない。 書かれている
1865
1863
  // こと自体が破綻なのは Axis 68 (`rows-not-rendered`) が別に見る。
1866
1864
  const DIVIDER_ONLY = /^[─━—-]+$/;
1867
- const stateIds = new Set(diag.states.map((s) => s.id));
1865
+ // `{名前}` で読める値。 他の値から自動で決まる値 (`derived`) も読めるので足す
1866
+ const stateIds = new Set([
1867
+ ...diag.states.map((s) => s.id),
1868
+ ...(diag.derived ?? []).map((d) => d.id),
1869
+ ]);
1868
1870
  for (const n of diag.nodes) {
1869
1871
  if (!n.rows) continue;
1870
1872
  if (!rendersRows(n.kind)) continue;
@@ -1874,11 +1876,9 @@ function runAxes(
1874
1876
  if (!ROW_FORMAT.test(row)) {
1875
1877
  push("row-format", `node "${n.id}" row "${row}" が "key: value" 形式でない`);
1876
1878
  }
1877
- let m: RegExpExecArray | null;
1878
- const re = new RegExp(PLACEHOLDER_RE);
1879
- while ((m = re.exec(row)) !== null) {
1880
- const id = m[1];
1881
- if (id && !stateIds.has(id)) {
1879
+ // 名前の切り出しは描画側と同じ式を使う (`templateRefIds`)
1880
+ for (const id of templateRefIds(row)) {
1881
+ if (!stateIds.has(id)) {
1882
1882
  push("row-format", `node "${n.id}" row "${row}" placeholder "{${id}}" が未定義 state`);
1883
1883
  }
1884
1884
  }
@@ -1,209 +0,0 @@
1
- import type { JSX } from "react";
2
- import type { LaidNode, Tone, MindBranchNode } from "../types";
3
- import { TONE } from "../render/tone";
4
-
5
- const MIND_TONES: Tone[] = ["accent", "teal", "success", "warning", "info", "error"];
6
-
7
- /**
8
- * mind-radial kind ... mindMapRadial preset 用、 中心 + 8 方向 (45 度間隔) の
9
- * 放射状 branch 配置。 mind-map との違いは「第 1 階層を 45 度で 8 分割」 固定。
10
- *
11
- * デザイン方針 (batch3):
12
- * - canvas 背景に subtle radial gradient (中心を強調)
13
- * - 中心 circle を 2 重 (外周 halo + 本体) + drop shadow で目立たせる
14
- * - 各 branch は tone-tinted rounded rect + 中心方向へ tapered edge
15
- * - branch 番号 (方位) を dot annotation で示す
16
- */
17
- export function MindRadialNode({ node, active }: { node: LaidNode; active: boolean }): JSX.Element {
18
- const x0 = node.cx - node.w / 2;
19
- const y0 = node.cy - node.h / 2;
20
- const mindData = node.mindData;
21
- if (!mindData) return <g transform={`translate(${x0} ${y0})`} />;
22
-
23
- const canvasCx = node.w / 2;
24
- const canvasCy = node.h / 2;
25
- const maxRadius = Math.min(node.w, node.h) / 2 - 100;
26
- const gradientId = `mind-radial-bg-${node.id}`;
27
- const shadowId = `mind-radial-shadow-${node.id}`;
28
-
29
- const layout = computeRadialLayout(mindData, canvasCx, canvasCy, maxRadius);
30
-
31
- return (
32
- <g transform={`translate(${x0} ${y0})`}>
33
- <defs>
34
- <radialGradient id={gradientId} cx="50%" cy="50%" r="60%">
35
- <stop offset="0%" stopColor="var(--cdl-tone-accent, #2d6a8f)" stopOpacity="0.14" />
36
- <stop offset="55%" stopColor="var(--cdl-tone-accent, #2d6a8f)" stopOpacity="0.04" />
37
- <stop offset="100%" stopColor="var(--cdl-tone-accent, #2d6a8f)" stopOpacity="0" />
38
- </radialGradient>
39
- <filter id={shadowId} x="-50%" y="-50%" width="200%" height="200%">
40
- <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
41
- <feOffset dx="0" dy="2" result="offsetblur" />
42
- <feComponentTransfer>
43
- <feFuncA type="linear" slope="0.4" />
44
- </feComponentTransfer>
45
- <feMerge>
46
- <feMergeNode />
47
- <feMergeNode in="SourceGraphic" />
48
- </feMerge>
49
- </filter>
50
- </defs>
51
-
52
- <rect
53
- data-cdl-role="node-body"
54
- x={0}
55
- y={0}
56
- width={node.w}
57
- height={node.h}
58
- rx={16}
59
- fill="var(--cdl-node-fill, #ffffff)"
60
- stroke={active ? "var(--cdl-tone-accent, #2d6a8f)" : "var(--cdl-divider, #d4d4d8)"}
61
- strokeWidth={active ? 3 : 1.25}
62
- />
63
-
64
- <circle
65
- cx={canvasCx}
66
- cy={canvasCy}
67
- r={maxRadius + 40}
68
- fill={`url(#${gradientId})`}
69
- pointerEvents="none"
70
- />
71
-
72
- {layout.edges.map((e, i) => (
73
- <line
74
- key={`edge-${i}`}
75
- data-cdl-role="mind-radial-edge"
76
- x1={e.x1}
77
- y1={e.y1}
78
- x2={e.x2}
79
- y2={e.y2}
80
- stroke={toneColor(e.tone)}
81
- strokeWidth={2.25}
82
- strokeOpacity={0.7}
83
- strokeLinecap="round"
84
- />
85
- ))}
86
-
87
- {/* 中心の周りに敷く光の輪。 意図して淡く、 読ませる要素ではない。
88
- 役割を付けるのは、 consumer が色を当てたり検査から外したりできるようにするため
89
- (役割の無い要素には CSS も検査も届かない)。 */}
90
- <circle
91
- data-cdl-role="mind-radial-halo"
92
- cx={canvasCx}
93
- cy={canvasCy}
94
- r={56}
95
- fill="var(--cdl-tone-accent, #2d6a8f)"
96
- fillOpacity={0.18}
97
- />
98
- <circle
99
- cx={canvasCx}
100
- cy={canvasCy}
101
- r={42}
102
- fill="var(--cdl-tone-accent, #2d6a8f)"
103
- filter={`url(#${shadowId})`}
104
- />
105
- <text
106
- x={canvasCx}
107
- y={canvasCy + 5}
108
- fontSize={14}
109
- fontWeight={700}
110
- textAnchor="middle"
111
- fill="var(--cdl-text-on-tone, #ffffff)"
112
- >
113
- {mindData.rootTitle}
114
- </text>
115
-
116
- {layout.nodes.filter((n) => !n.isRoot).map((n) => (
117
- <g key={`n-${n.id}`}>
118
- <rect
119
- x={n.x - 62}
120
- y={n.y - 22}
121
- width={124}
122
- height={44}
123
- rx={12}
124
- fill="var(--cdl-chip-fill, #f4f6fb)"
125
- stroke={toneColor(n.tone)}
126
- strokeWidth={2}
127
- />
128
- <circle
129
- cx={n.x - 62 + 12}
130
- cy={n.y}
131
- r={5}
132
- fill={toneColor(n.tone)}
133
- />
134
- <text
135
- x={n.x + 6}
136
- y={n.y + 5}
137
- fontSize={13}
138
- fontWeight={600}
139
- textAnchor="middle"
140
- fill="var(--cdl-chip-text, #1a1f2a)"
141
- >
142
- {n.title}
143
- </text>
144
- </g>
145
- ))}
146
- </g>
147
- );
148
- }
149
-
150
- type LayoutNode = {
151
- id: string;
152
- title: string;
153
- x: number;
154
- y: number;
155
- tone?: Tone;
156
- isRoot: boolean;
157
- };
158
-
159
- type LayoutEdge = {
160
- x1: number;
161
- y1: number;
162
- x2: number;
163
- y2: number;
164
- tone?: Tone;
165
- };
166
-
167
- function computeRadialLayout(
168
- mindData: { rootId: string; rootTitle: string; branches: MindBranchNode[] },
169
- cx: number,
170
- cy: number,
171
- maxRadius: number,
172
- ): { nodes: LayoutNode[]; edges: LayoutEdge[] } {
173
- const nodes: LayoutNode[] = [];
174
- const edges: LayoutEdge[] = [];
175
-
176
- nodes.push({ id: mindData.rootId, title: mindData.rootTitle, x: cx, y: cy, isRoot: true });
177
-
178
- const childrenOf = new Map<string, MindBranchNode[]>();
179
- for (const br of mindData.branches) {
180
- if (!childrenOf.has(br.parent)) childrenOf.set(br.parent, []);
181
- childrenOf.get(br.parent)!.push(br);
182
- }
183
-
184
- const firstLevel = childrenOf.get(mindData.rootId) ?? [];
185
- const angleStep = (Math.PI * 2) / 8;
186
- firstLevel.slice(0, 8).forEach((br, idx) => {
187
- const angle = -Math.PI / 2 + angleStep * idx;
188
- const tone = br.tone ?? MIND_TONES[idx % MIND_TONES.length]!;
189
- const r = maxRadius;
190
- const x = cx + r * Math.cos(angle);
191
- const y = cy + r * Math.sin(angle);
192
- nodes.push({ id: br.id, title: br.title, x, y, tone, isRoot: false });
193
- const rootR = 42;
194
- const branchR = 62;
195
- const x1 = cx + rootR * Math.cos(angle);
196
- const y1 = cy + rootR * Math.sin(angle);
197
- const x2 = x - branchR * Math.cos(angle);
198
- const y2 = y - 22 * Math.sin(angle);
199
- edges.push({ x1, y1, x2, y2, tone });
200
- });
201
-
202
- return { nodes, edges };
203
- }
204
-
205
-
206
- function toneColor(tone: Tone | undefined): string {
207
- if (!tone) return TONE.accent;
208
- return TONE[tone];
209
- }