@cardenelabs/cdl 0.21.2 → 0.23.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/CHANGELOG.md +93 -1
- package/dist/index.cjs +6995 -6435
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +6994 -6436
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +22782 -22337
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +22782 -22337
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/kinds/actor.tsx +15 -6
- package/src/kinds/card.tsx +15 -4
- package/src/kinds/event.tsx +13 -4
- package/src/kinds/function.tsx +13 -4
- package/src/kinds/generic.tsx +15 -3
- package/src/layout/edges.ts +627 -0
- package/src/layout/order.ts +201 -0
- package/src/layout/spec.ts +27 -0
- package/src/layout.ts +25 -1
- package/src/presets.ts +5 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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/index.ts
CHANGED
|
@@ -65,6 +65,10 @@ export { compile } from "./compile";
|
|
|
65
65
|
export { layout } from "./layout";
|
|
66
66
|
export { validate } from "./validate";
|
|
67
67
|
export { countEdgeCrossings, visualValidate, visualValidateLaid, visualValidateAll } from "./visual-validate";
|
|
68
|
+
// 並べ替え (#599)。 関係から lane / stack を決める純関数。 `compile` からは呼ばない =
|
|
69
|
+
// 書き手が明示した配置を engine が黙って動かすと既存の全図が変わるため、呼ぶ判断は上流に置く
|
|
70
|
+
export { orderNodes, countOrderCrossings } from "./layout/order";
|
|
71
|
+
export type { OrderNode, OrderEdge, OrderResult } from "./layout/order";
|
|
68
72
|
export type {
|
|
69
73
|
Violation,
|
|
70
74
|
VisualAxis,
|
package/src/kinds/actor.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import type { JSX } from "react";
|
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
3
|
import { nodeAccent } from "./node-tone";
|
|
4
4
|
import { titlePlacement } from "./compact-title";
|
|
5
|
-
import { 目印なしの繰り上げ } from "../layout/spec";
|
|
5
|
+
import { 余りを分ける, 目印なしの繰り上げ } from "../layout/spec";
|
|
6
|
+
import { NODE_SIZE } from "../layout/tokens";
|
|
6
7
|
|
|
7
8
|
export function ActorNode({ node, active, value }: { node: LaidNode; active: boolean; value: string | undefined }): JSX.Element {
|
|
8
9
|
const accent = nodeAccent(node, "var(--cdl-tone-accent, #2d6a8f)");
|
|
@@ -11,8 +12,16 @@ export function ActorNode({ node, active, value }: { node: LaidNode; active: boo
|
|
|
11
12
|
// 小見出しを書かない時は、そのために空けた帯のぶん字を上へ繰り上げる (#589)。
|
|
12
13
|
// 繰り上げ量は `layout/spec.ts` が持つ = 高さを出す側と同じ値を引く
|
|
13
14
|
const 繰り上げ = node.eyebrow ? 0 : 目印なしの繰り上げ("actor");
|
|
15
|
+
// 箱が中身より高い時は余りを上下で分ける (#597)
|
|
16
|
+
const 余り = 余りを分ける("actor", NODE_SIZE.actor.h, node.h, {
|
|
17
|
+
hasEyebrow: Boolean(node.eyebrow),
|
|
18
|
+
hasSubtitle: Boolean(node.subtitle),
|
|
19
|
+
hasValue: Boolean(value),
|
|
20
|
+
hasRows: false,
|
|
21
|
+
});
|
|
22
|
+
const ずらし = 繰り上げ - 余り;
|
|
14
23
|
// 小型の箱では名前を中央に置く。 固定の位置のままだと下端をまたぐ (#416)
|
|
15
|
-
const 名前 = titlePlacement(node, { x: 26, y: 86 -
|
|
24
|
+
const 名前 = titlePlacement(node, { x: 26, y: 86 - ずらし, size: 32 }, { hasEyebrow: Boolean(node.eyebrow) });
|
|
16
25
|
return (
|
|
17
26
|
<g transform={`translate(${x} ${y})`}>
|
|
18
27
|
<rect
|
|
@@ -27,7 +36,7 @@ export function ActorNode({ node, active, value }: { node: LaidNode; active: boo
|
|
|
27
36
|
strokeWidth={active ? 3 : 1.25}
|
|
28
37
|
/>
|
|
29
38
|
{node.eyebrow && (
|
|
30
|
-
<text x={26} y={38} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
39
|
+
<text x={26} y={38 - 余り} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
31
40
|
{node.eyebrow}
|
|
32
41
|
</text>
|
|
33
42
|
)}
|
|
@@ -54,14 +63,14 @@ export function ActorNode({ node, active, value }: { node: LaidNode; active: boo
|
|
|
54
63
|
<line
|
|
55
64
|
data-cdl-role="node-row-divider"
|
|
56
65
|
x1={26}
|
|
57
|
-
y1={110 -
|
|
66
|
+
y1={110 - ずらし}
|
|
58
67
|
x2={node.w - 26}
|
|
59
|
-
y2={110 -
|
|
68
|
+
y2={110 - ずらし}
|
|
60
69
|
stroke="var(--cdl-row-divider, #7e848e)"
|
|
61
70
|
/>
|
|
62
71
|
)}
|
|
63
72
|
{node.subtitle && (
|
|
64
|
-
<text x={26} y={140 -
|
|
73
|
+
<text x={26} y={140 - ずらし} fontSize={20} fill="var(--cdl-text-dim, #5a6270)">
|
|
65
74
|
{node.subtitle}
|
|
66
75
|
</text>
|
|
67
76
|
)}
|
package/src/kinds/card.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import type { JSX } from "react";
|
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
3
|
import { nodeAccent } from "./node-tone";
|
|
4
4
|
import { fitTextSize, isCompactBox } from "./compact-title";
|
|
5
|
-
import { 目印なしの繰り上げ } from "../layout/spec";
|
|
5
|
+
import { 余りを分ける, 目印なしの繰り上げ } from "../layout/spec";
|
|
6
|
+
import { NODE_SIZE } from "../layout/tokens";
|
|
6
7
|
|
|
7
8
|
export function CardNode({ node, active }: { node: LaidNode; active: boolean }): JSX.Element {
|
|
8
9
|
const accent = nodeAccent(node, "var(--cdl-tone-accent, #2d6a8f)");
|
|
@@ -19,7 +20,17 @@ export function CardNode({ node, active }: { node: LaidNode; active: boolean }):
|
|
|
19
20
|
const 小型に呼び名 = compact && Boolean(node.subtitle);
|
|
20
21
|
// 小見出しを書かない時の繰り上げ (#589)。 値は `layout/spec.ts` SSOT
|
|
21
22
|
const 繰り上げ = node.eyebrow ? 0 : 目印なしの繰り上げ("card");
|
|
22
|
-
|
|
23
|
+
// 箱が中身より高い時は余りを上下で分ける (#597)。 小型の形は元から中央に置くので対象外
|
|
24
|
+
const 余り = compact
|
|
25
|
+
? 0
|
|
26
|
+
: 余りを分ける("card", NODE_SIZE.card.h, node.h, {
|
|
27
|
+
hasEyebrow: Boolean(node.eyebrow),
|
|
28
|
+
hasSubtitle: Boolean(node.subtitle),
|
|
29
|
+
hasValue: false,
|
|
30
|
+
hasRows: false,
|
|
31
|
+
});
|
|
32
|
+
const ずらし = 繰り上げ - 余り;
|
|
33
|
+
const titleY = compact ? (小型に呼び名 ? node.h / 2 - 2 : node.h / 2 + 8) : 86 - ずらし;
|
|
23
34
|
const titleX = compact ? node.w / 2 : 26;
|
|
24
35
|
const paddingX = compact ? 16 : 52;
|
|
25
36
|
const availableW = node.w - paddingX;
|
|
@@ -41,7 +52,7 @@ export function CardNode({ node, active }: { node: LaidNode; active: boolean }):
|
|
|
41
52
|
strokeWidth={active ? 3 : 1.25}
|
|
42
53
|
/>
|
|
43
54
|
{!compact && node.eyebrow && (
|
|
44
|
-
<text x={26} y={38} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
55
|
+
<text x={26} y={38 - 余り} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
45
56
|
{node.eyebrow}
|
|
46
57
|
</text>
|
|
47
58
|
)}
|
|
@@ -57,7 +68,7 @@ export function CardNode({ node, active }: { node: LaidNode; active: boolean }):
|
|
|
57
68
|
{node.title}
|
|
58
69
|
</text>
|
|
59
70
|
{!compact && node.subtitle && (
|
|
60
|
-
<text x={26} y={124 -
|
|
71
|
+
<text x={26} y={124 - ずらし} fontSize={subtitleSize} fill="var(--cdl-text-dim, #5a6270)">
|
|
61
72
|
{node.subtitle}
|
|
62
73
|
</text>
|
|
63
74
|
)}
|
package/src/kinds/event.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import type { JSX } from "react";
|
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
3
|
import { nodeAccent } from "./node-tone";
|
|
4
4
|
import { titlePlacement } from "./compact-title";
|
|
5
|
-
import { 目印なしの繰り上げ } from "../layout/spec";
|
|
5
|
+
import { 余りを分ける, 目印なしの繰り上げ } from "../layout/spec";
|
|
6
|
+
import { NODE_SIZE } from "../layout/tokens";
|
|
6
7
|
|
|
7
8
|
export function EventNode({
|
|
8
9
|
node,
|
|
@@ -18,8 +19,16 @@ export function EventNode({
|
|
|
18
19
|
const y = node.cy - node.h / 2;
|
|
19
20
|
// 小見出しを書かない時の繰り上げ (#589)。 値は `layout/spec.ts` SSOT
|
|
20
21
|
const 繰り上げ = node.eyebrow ? 0 : 目印なしの繰り上げ("event");
|
|
22
|
+
// 箱が中身より高い時は余りを上下で分ける (#597)
|
|
23
|
+
const 余り = 余りを分ける("event", NODE_SIZE.event.h, node.h, {
|
|
24
|
+
hasEyebrow: Boolean(node.eyebrow),
|
|
25
|
+
hasSubtitle: Boolean(node.subtitle),
|
|
26
|
+
hasValue: false,
|
|
27
|
+
hasRows: false,
|
|
28
|
+
});
|
|
29
|
+
const ずらし = 繰り上げ - 余り;
|
|
21
30
|
// 小型の箱では名前を中央に置く。 固定の位置のままだと下端をまたぐ (#416)
|
|
22
|
-
const 名前 = titlePlacement(node, { x: 26, y: 88 -
|
|
31
|
+
const 名前 = titlePlacement(node, { x: 26, y: 88 - ずらし, size: 26 }, { hasEyebrow: Boolean(node.eyebrow) });
|
|
23
32
|
const scale = active ? 0.78 + 0.22 * progress : 0.92;
|
|
24
33
|
const opacity = active ? 0.5 + 0.5 * progress : 0.65;
|
|
25
34
|
return (
|
|
@@ -40,7 +49,7 @@ export function EventNode({
|
|
|
40
49
|
strokeWidth={active ? 3 : 1.25}
|
|
41
50
|
/>
|
|
42
51
|
{node.eyebrow && (
|
|
43
|
-
<text x={26} y={38} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
52
|
+
<text x={26} y={38 - 余り} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
44
53
|
{node.eyebrow}
|
|
45
54
|
</text>
|
|
46
55
|
)}
|
|
@@ -59,7 +68,7 @@ export function EventNode({
|
|
|
59
68
|
{node.subtitle && (
|
|
60
69
|
<text
|
|
61
70
|
x={26}
|
|
62
|
-
y={128 -
|
|
71
|
+
y={128 - ずらし}
|
|
63
72
|
fontSize={21}
|
|
64
73
|
fill="var(--cdl-text-dim, #5a6270)"
|
|
65
74
|
style={{ fontFamily: "'JetBrains Mono', monospace" }}
|
package/src/kinds/function.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import type { JSX } from "react";
|
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
3
|
import { nodeAccent } from "./node-tone";
|
|
4
4
|
import { titlePlacement } from "./compact-title";
|
|
5
|
-
import { 目印なしの繰り上げ } from "../layout/spec";
|
|
5
|
+
import { 余りを分ける, 目印なしの繰り上げ } from "../layout/spec";
|
|
6
|
+
import { NODE_SIZE } from "../layout/tokens";
|
|
6
7
|
|
|
7
8
|
export function FunctionNode({ node, active }: { node: LaidNode; active: boolean }): JSX.Element {
|
|
8
9
|
const accent = nodeAccent(node, "var(--cdl-tone-teal, #2f8770)");
|
|
@@ -13,8 +14,16 @@ export function FunctionNode({ node, active }: { node: LaidNode; active: boolean
|
|
|
13
14
|
const y = node.cy - node.h / 2;
|
|
14
15
|
// 小見出しを書かない時の繰り上げ (#589)。 値は `layout/spec.ts` SSOT
|
|
15
16
|
const 繰り上げ = node.eyebrow ? 0 : 目印なしの繰り上げ("function");
|
|
17
|
+
// 箱が中身より高い時は余りを上下で分ける (#597)
|
|
18
|
+
const 余り = 余りを分ける("function", NODE_SIZE.function.h, node.h, {
|
|
19
|
+
hasEyebrow: Boolean(node.eyebrow),
|
|
20
|
+
hasSubtitle: Boolean(node.subtitle),
|
|
21
|
+
hasValue: false,
|
|
22
|
+
hasRows: false,
|
|
23
|
+
});
|
|
24
|
+
const ずらし = 繰り上げ - 余り;
|
|
16
25
|
// 小型の箱では名前を中央に置く。 固定の位置のままだと下端をまたぐ (#416)
|
|
17
|
-
const 名前 = titlePlacement(node, { x: 26, y: 86 -
|
|
26
|
+
const 名前 = titlePlacement(node, { x: 26, y: 86 - ずらし, size: 24 }, { hasEyebrow: Boolean(node.eyebrow) });
|
|
18
27
|
return (
|
|
19
28
|
<g transform={`translate(${x} ${y})`}>
|
|
20
29
|
<rect
|
|
@@ -30,7 +39,7 @@ export function FunctionNode({ node, active }: { node: LaidNode; active: boolean
|
|
|
30
39
|
strokeOpacity={active ? 1 : 0.5}
|
|
31
40
|
/>
|
|
32
41
|
{node.eyebrow && (
|
|
33
|
-
<text x={26} y={38} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
42
|
+
<text x={26} y={38 - 余り} fontSize={18} fontWeight={700} letterSpacing={2} fill={accent}>
|
|
34
43
|
{node.eyebrow}
|
|
35
44
|
</text>
|
|
36
45
|
)}
|
|
@@ -47,7 +56,7 @@ export function FunctionNode({ node, active }: { node: LaidNode; active: boolean
|
|
|
47
56
|
{node.title}
|
|
48
57
|
</text>
|
|
49
58
|
{node.subtitle && (
|
|
50
|
-
<text x={26} y={126 -
|
|
59
|
+
<text x={26} y={126 - ずらし} fontSize={20} fill="var(--cdl-text-dim, #5a6270)">
|
|
51
60
|
{node.subtitle}
|
|
52
61
|
</text>
|
|
53
62
|
)}
|
package/src/kinds/generic.tsx
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
rowBaselineY,
|
|
10
10
|
CLOUD_ART_CENTER_RISE,
|
|
11
11
|
CLOUD_CIRCLES,
|
|
12
|
+
余りを分ける,
|
|
12
13
|
目印なしの繰り上げ,
|
|
13
14
|
} from "../layout/spec";
|
|
15
|
+
import { NODE_SIZE } from "../layout/tokens";
|
|
14
16
|
import {
|
|
15
17
|
GENERIC_ROW_LEFT_SIZE_PX,
|
|
16
18
|
rowColumnWidth,
|
|
@@ -302,6 +304,16 @@ export function GenericNode({
|
|
|
302
304
|
// 小見出しを書かない時は、そのために空けた帯のぶん字を上へ繰り上げる (#589)。
|
|
303
305
|
// 中央へ寄せる形と行を持つ形では繰り上げない (前者は塊の置き方が違い、後者は 1 行目と重なる)
|
|
304
306
|
const 繰り上げ = node.eyebrow || centered ? 0 : 目印なしの繰り上げ(node.kind, hasRows);
|
|
307
|
+
// 箱が中身より高い時は余りを上下で分ける (#597)。 中央へ寄せる形は元から中央に置く
|
|
308
|
+
const 余り = centered
|
|
309
|
+
? 0
|
|
310
|
+
: 余りを分ける(node.kind, NODE_SIZE[node.kind]?.h ?? h, h, {
|
|
311
|
+
hasEyebrow: Boolean(node.eyebrow),
|
|
312
|
+
hasSubtitle: Boolean(node.subtitle),
|
|
313
|
+
hasValue: Boolean(resolvedValue),
|
|
314
|
+
hasRows,
|
|
315
|
+
});
|
|
316
|
+
const ずらし = 繰り上げ - 余り;
|
|
305
317
|
/*
|
|
306
318
|
* 中央へ寄せる形では、字を **絵の芯** に合わせる (#588)。
|
|
307
319
|
*
|
|
@@ -310,9 +322,9 @@ export function GenericNode({
|
|
|
310
322
|
* ひし形 (`decision`) は箱と絵の芯が同じなので 0 になる。
|
|
311
323
|
*/
|
|
312
324
|
const 絵の芯 = centered && STYLES[node.kind]?.shape === "cloud" ? h * CLOUD_ART_CENTER_RISE : 0;
|
|
313
|
-
const eyebrowY = centered ? h / 2 - 26 - 絵の芯 : 32 + cylinderShift
|
|
314
|
-
const titleY = genericTitleBaselineY(node.kind, h, hasRows) -
|
|
315
|
-
const subtitleY = centered ? h / 2 + 28 - 絵の芯 : 96 + cylinderShift -
|
|
325
|
+
const eyebrowY = centered ? h / 2 - 26 - 絵の芯 : 32 + cylinderShift + 余り;
|
|
326
|
+
const titleY = genericTitleBaselineY(node.kind, h, hasRows) - ずらし - 絵の芯;
|
|
327
|
+
const subtitleY = centered ? h / 2 + 28 - 絵の芯 : 96 + cylinderShift - ずらし;
|
|
316
328
|
// icon 位置 (diamond / cloud は title の真右の少し上、 中心配置に合わせる)
|
|
317
329
|
const iconX = centered ? w / 2 + 40 : w - 48;
|
|
318
330
|
// 目印も字と同じ芯に合わせる (#588)。 字だけ動かすと目印との間隔が変わる
|