@cardenelabs/cdl 0.6.1 → 0.7.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.
- package/dist/index.cjs +129 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +129 -67
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +129 -67
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +129 -67
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/kinds/mind-map.tsx +27 -4
- package/src/kinds/tree.tsx +29 -4
- package/src/render/nodes.tsx +6 -5
- package/src/render/payload-binding.ts +65 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "cardene777",
|
package/src/kinds/mind-map.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode, Tone, MindBranchNode } from "../types";
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
|
+
import { resolveMindData } from "../render/payload-binding";
|
|
4
5
|
|
|
5
6
|
const MIND_TONES: Tone[] = ["accent", "teal", "success", "warning", "info", "error"];
|
|
6
7
|
|
|
@@ -25,10 +26,19 @@ export const MIND_BOX_GAP = 24;
|
|
|
25
26
|
* 深い階層は parent 方向へ扇状展開。 depth 別 tone を rotate。
|
|
26
27
|
* curved edge = bezier で親子を接続、 node は rounded rect で読みやすさ優先。
|
|
27
28
|
*/
|
|
28
|
-
export function MindMapNode({
|
|
29
|
+
export function MindMapNode({
|
|
30
|
+
node,
|
|
31
|
+
active,
|
|
32
|
+
stateValues = {},
|
|
33
|
+
}: {
|
|
34
|
+
node: LaidNode;
|
|
35
|
+
active: boolean;
|
|
36
|
+
stateValues?: Record<string, string>;
|
|
37
|
+
}): JSX.Element {
|
|
29
38
|
const x0 = node.cx - node.w / 2;
|
|
30
39
|
const y0 = node.cy - node.h / 2;
|
|
31
|
-
|
|
40
|
+
// 名前は状態を読める (#467)。 中心と枝の両方を解決層に通す
|
|
41
|
+
const mindData = node.mindData ? resolveMindData(node.mindData, stateValues) : undefined;
|
|
32
42
|
if (!mindData) return <g transform={`translate(${x0} ${y0})`} />;
|
|
33
43
|
|
|
34
44
|
const PAD = 40;
|
|
@@ -67,7 +77,7 @@ export function MindMapNode({ node, active }: { node: LaidNode; active: boolean
|
|
|
67
77
|
))}
|
|
68
78
|
|
|
69
79
|
{layout.nodes.map((n) => (
|
|
70
|
-
<g key={`n-${n.id}`}>
|
|
80
|
+
<g key={`n-${n.id}`} data-cdl-unresolved={n.unresolved ? "true" : undefined}>
|
|
71
81
|
<rect
|
|
72
82
|
x={n.x - n.w / 2}
|
|
73
83
|
y={n.y - n.h / 2}
|
|
@@ -103,6 +113,8 @@ type LayoutNode = {
|
|
|
103
113
|
h: number;
|
|
104
114
|
tone?: Tone;
|
|
105
115
|
isRoot: boolean;
|
|
116
|
+
/** 名前を状態から解決できなかった項目に付く印 (#467) */
|
|
117
|
+
unresolved?: true;
|
|
106
118
|
};
|
|
107
119
|
|
|
108
120
|
type LayoutEdge = {
|
|
@@ -130,6 +142,7 @@ export function computeMindMapLayout(
|
|
|
130
142
|
w: rootW,
|
|
131
143
|
h: rootH,
|
|
132
144
|
isRoot: true,
|
|
145
|
+
...((mindData as { unresolved?: true }).unresolved ? { unresolved: true as const } : {}),
|
|
133
146
|
});
|
|
134
147
|
|
|
135
148
|
const childrenOf = new Map<string, MindBranchNode[]>();
|
|
@@ -281,7 +294,17 @@ export function computeMindMapLayout(
|
|
|
281
294
|
y = (ys[0]! + ys[ys.length - 1]!) / 2;
|
|
282
295
|
}
|
|
283
296
|
|
|
284
|
-
nodes.push({
|
|
297
|
+
nodes.push({
|
|
298
|
+
id: node.id,
|
|
299
|
+
title: node.title,
|
|
300
|
+
x,
|
|
301
|
+
y,
|
|
302
|
+
w,
|
|
303
|
+
h,
|
|
304
|
+
tone: myTone,
|
|
305
|
+
isRoot: false,
|
|
306
|
+
...((node as { unresolved?: true }).unresolved ? { unresolved: true as const } : {}),
|
|
307
|
+
});
|
|
285
308
|
nodePos.set(node.id, { x, y });
|
|
286
309
|
nodeSide.set(node.id, isLeft ? "left" : "right");
|
|
287
310
|
nodeHalfW.set(node.id, w / 2);
|
package/src/kinds/tree.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode, TreeNodePayload } from "../types";
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
|
+
import { resolveTreeData } from "../render/payload-binding";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* tree-hierarchy kind ... 1 node に tree node 配列 (treeData) を保持し、
|
|
@@ -11,10 +12,19 @@ import { TONE } from "../render/tone";
|
|
|
11
12
|
* - depth 1+ は tone 別 accent の left-border + 白 fill card
|
|
12
13
|
* - parent-child edge は elbow line + 中間 join dot で「分岐点」 を強調
|
|
13
14
|
*/
|
|
14
|
-
export function TreeNode({
|
|
15
|
+
export function TreeNode({
|
|
16
|
+
node,
|
|
17
|
+
active,
|
|
18
|
+
stateValues = {},
|
|
19
|
+
}: {
|
|
20
|
+
node: LaidNode;
|
|
21
|
+
active: boolean;
|
|
22
|
+
stateValues?: Record<string, string>;
|
|
23
|
+
}): JSX.Element {
|
|
15
24
|
const x0 = node.cx - node.w / 2;
|
|
16
25
|
const y0 = node.cy - node.h / 2;
|
|
17
|
-
|
|
26
|
+
// 名前は状態を読める (#467)。 解決できない名前は書いたまま残して印を付ける
|
|
27
|
+
const treeNodes = resolveTreeData(node.treeData ?? [], stateValues);
|
|
18
28
|
const gradientId = `tree-root-grad-${node.id}`;
|
|
19
29
|
|
|
20
30
|
const PAD_TOP = 40;
|
|
@@ -79,7 +89,11 @@ export function TreeNode({ node, active }: { node: LaidNode; active: boolean }):
|
|
|
79
89
|
const accent = depthColors[Math.min(n.depth, depthColors.length - 1)] ?? depthColors[0]!;
|
|
80
90
|
const isRoot = n.depth === 0;
|
|
81
91
|
return (
|
|
82
|
-
<g
|
|
92
|
+
<g
|
|
93
|
+
key={`n-${n.id}`}
|
|
94
|
+
data-cdl-unresolved={n.unresolved ? "true" : undefined}
|
|
95
|
+
transform={`translate(${PAD_LEFT + n.x - n.w / 2} ${PAD_TOP + n.y - n.h / 2})`}
|
|
96
|
+
>
|
|
83
97
|
{isRoot ? (
|
|
84
98
|
<>
|
|
85
99
|
<rect
|
|
@@ -143,6 +157,8 @@ type LayoutNode = {
|
|
|
143
157
|
w: number;
|
|
144
158
|
h: number;
|
|
145
159
|
depth: number;
|
|
160
|
+
/** 名前を状態から解決できなかった項目に付く印 (#467) */
|
|
161
|
+
unresolved?: true;
|
|
146
162
|
};
|
|
147
163
|
|
|
148
164
|
export function computeTreeLayout(
|
|
@@ -231,7 +247,16 @@ export function computeTreeLayout(
|
|
|
231
247
|
}
|
|
232
248
|
const y = depth * depthGap + depthGap / 2;
|
|
233
249
|
posOf.set(n.id, { x, y });
|
|
234
|
-
nodes.push({
|
|
250
|
+
nodes.push({
|
|
251
|
+
id: n.id,
|
|
252
|
+
title: n.title,
|
|
253
|
+
x,
|
|
254
|
+
y,
|
|
255
|
+
w: nodeW,
|
|
256
|
+
h: nodeH,
|
|
257
|
+
depth,
|
|
258
|
+
...((n as { unresolved?: true }).unresolved ? { unresolved: true as const } : {}),
|
|
259
|
+
});
|
|
235
260
|
return x;
|
|
236
261
|
};
|
|
237
262
|
for (const r of roots) if (!placed.has(r.id)) place(r, 0);
|
package/src/render/nodes.tsx
CHANGED
|
@@ -149,9 +149,10 @@ export function CdlNodeView({
|
|
|
149
149
|
return <EventNode node={resolvedNode} active={active} progress={progress} />;
|
|
150
150
|
case "card":
|
|
151
151
|
return <CardNode node={resolvedNode} active={active} />;
|
|
152
|
-
// 図表
|
|
153
|
-
// (`render/payload-binding.ts` が描画直前に解決する)。
|
|
154
|
-
//
|
|
152
|
+
// 図表 9 種は payload が `{signal}` を取れるので stateValues を渡す
|
|
153
|
+
// (`render/payload-binding.ts` が描画直前に解決する)。 取れる欄は種別で違い、
|
|
154
|
+
// 数と語を取る 7 種に対し、mind-map / tree-hierarchy は **名前だけ** を取る (#467)。
|
|
155
|
+
// 親子の繋がりと、描かれない欄 (補足) は解決しない。
|
|
155
156
|
case "chart-line":
|
|
156
157
|
return <ChartLineNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
157
158
|
case "chart-pie":
|
|
@@ -161,13 +162,13 @@ export function CdlNodeView({
|
|
|
161
162
|
case "gantt-timeline":
|
|
162
163
|
return <GanttNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
163
164
|
case "mind-map":
|
|
164
|
-
return <MindMapNode node={resolvedNode} active={active} />;
|
|
165
|
+
return <MindMapNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
165
166
|
case "funnel-stages":
|
|
166
167
|
return <FunnelNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
167
168
|
case "quadrant-matrix":
|
|
168
169
|
return <QuadrantNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
169
170
|
case "tree-hierarchy":
|
|
170
|
-
return <TreeHierarchyNode node={resolvedNode} active={active} />;
|
|
171
|
+
return <TreeHierarchyNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
171
172
|
case "journey-map":
|
|
172
173
|
return <UserJourneyNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
173
174
|
case "shape-file":
|
|
@@ -6,11 +6,14 @@ import type {
|
|
|
6
6
|
GanttTaskPayload,
|
|
7
7
|
JourneyEmotion,
|
|
8
8
|
JourneyStepPayload,
|
|
9
|
+
MindBranchNode,
|
|
10
|
+
MindBranchPayload,
|
|
9
11
|
QuadrantItemPayload,
|
|
10
12
|
QuadrantKey,
|
|
11
13
|
QuadrantPayload,
|
|
14
|
+
TreeNodePayload,
|
|
12
15
|
} from "../types";
|
|
13
|
-
import { interpolate } from "./utils";
|
|
16
|
+
import { hasUnresolvedRef, interpolate } from "./utils";
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* 図表 payload が状態を読むための解決層 (dragon#1161 段 3)。
|
|
@@ -24,8 +27,15 @@ import { interpolate } from "./utils";
|
|
|
24
27
|
* (spec § 4.2 が輪について同じ判断をしている = 1 箇所の壊れで図が消えると原因が分からない)。
|
|
25
28
|
* 印は各 kind が `data-cdl-unresolved="true"` として DOM に出す。
|
|
26
29
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* `mind-map` / `tree-hierarchy` の 2 種は **名前だけ** を解決する (#467)。 この 2 種が持つのは
|
|
31
|
+
* 数ではなく構造と名前で、動かせるのは名前の側だけ。
|
|
32
|
+
*
|
|
33
|
+
* 解決するのは **描かれる欄だけ** にする。 この 2 種は補足 (`subtitle`) を描かないため
|
|
34
|
+
* (実測で確認)、解決しても画面には出ず、解決できない時だけ印が付く = 見た目は何も変わらない
|
|
35
|
+
* のに印だけ出る状態になる。 描く方を足すかは別 Issue (#471)。
|
|
36
|
+
*
|
|
37
|
+
* 親子の繋がり (`parent`) も解決しない = 状態で繋ぎ替えると木の形そのものが変わり、
|
|
38
|
+
* 配置の計算と印の付け方が別問題になる。
|
|
29
39
|
*/
|
|
30
40
|
|
|
31
41
|
/** 解決の結果。 `ok` が false なら既定値に落ちたことを表す */
|
|
@@ -194,3 +204,55 @@ export function resolveJourneyData(
|
|
|
194
204
|
return e.ok ? { ...s, emotion: e.value } : { ...s, emotion: e.value, unresolved: true as const };
|
|
195
205
|
});
|
|
196
206
|
}
|
|
207
|
+
|
|
208
|
+
/** 名前の欄を解決した結果。 解決できなければ書いたまま残して印を付ける */
|
|
209
|
+
export type ResolvedTreeNode = TreeNodePayload & UnresolvedMark;
|
|
210
|
+
export type ResolvedMindBranch = MindBranchNode & UnresolvedMark;
|
|
211
|
+
export type ResolvedMindData = Omit<MindBranchPayload, "branches"> & {
|
|
212
|
+
branches: ResolvedMindBranch[];
|
|
213
|
+
} & UnresolvedMark;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 名前の欄を状態で埋める (#467)。
|
|
217
|
+
*
|
|
218
|
+
* **解決できなかった時も文字を消さない**。 数の欄は既定値 (0 等) に落とすが、名前は落とす先が
|
|
219
|
+
* 無く、空にすると図から項目の存在まで消えてしまう。 書いた `{名前}` をそのまま残せば、
|
|
220
|
+
* どの名前が解決できなかったかが図の上で読める。 印は他の欄と同じ `data-cdl-unresolved`。
|
|
221
|
+
*/
|
|
222
|
+
function resolveBoundText(raw: string, values: Record<string, string>): BindingResult<string> {
|
|
223
|
+
// 状態を読まない名前は組み直さない (毎 frame の割当てを避ける近道、 他の resolver と同じ)
|
|
224
|
+
if (!raw.includes("{")) return { value: raw, ok: true };
|
|
225
|
+
const resolved = interpolate(raw, values);
|
|
226
|
+
return hasUnresolvedRef(resolved) ? { value: resolved, ok: false } : { value: resolved, ok: true };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** 木の名前を解決する。 描かれない欄 (補足) と親子の繋がりは触らない */
|
|
230
|
+
export function resolveTreeData(
|
|
231
|
+
nodes: TreeNodePayload[],
|
|
232
|
+
values: Record<string, string>,
|
|
233
|
+
): ResolvedTreeNode[] {
|
|
234
|
+
if (!nodes.some((n) => n.title.includes("{"))) return nodes as ResolvedTreeNode[];
|
|
235
|
+
return nodes.map((n) => {
|
|
236
|
+
const title = resolveBoundText(n.title, values);
|
|
237
|
+
const 解決後: ResolvedTreeNode = { ...n, title: title.value };
|
|
238
|
+
return title.ok ? 解決後 : { ...解決後, unresolved: true as const };
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** 放射の名前を解決する。 中心の名前と枝の名前の両方を見る */
|
|
243
|
+
export function resolveMindData(
|
|
244
|
+
data: MindBranchPayload,
|
|
245
|
+
values: Record<string, string>,
|
|
246
|
+
): ResolvedMindData {
|
|
247
|
+
const 読む = data.rootTitle.includes("{") || data.branches.some((b) => b.title.includes("{"));
|
|
248
|
+
if (!読む) return data as ResolvedMindData;
|
|
249
|
+
|
|
250
|
+
const root = resolveBoundText(data.rootTitle, values);
|
|
251
|
+
const branches = data.branches.map((b) => {
|
|
252
|
+
const title = resolveBoundText(b.title, values);
|
|
253
|
+
const 解決後: ResolvedMindBranch = { ...b, title: title.value };
|
|
254
|
+
return title.ok ? 解決後 : { ...解決後, unresolved: true as const };
|
|
255
|
+
});
|
|
256
|
+
const 解決後: ResolvedMindData = { ...data, rootTitle: root.value, branches };
|
|
257
|
+
return root.ok ? 解決後 : { ...解決後, unresolved: true as const };
|
|
258
|
+
}
|