@cardenelabs/dragon 0.18.3 → 0.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/dragon",
3
- "version": "0.18.3",
3
+ "version": "0.20.0",
4
4
  "description": "Dragon — Mermaid 感覚で animated SVG を生成する Text DSL。 cdl engine を内部利用。",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -57,7 +57,7 @@
57
57
  "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build"
58
58
  },
59
59
  "dependencies": {
60
- "@cardenelabs/cdl": "^0.22.0"
60
+ "@cardenelabs/cdl": "^0.27.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "react": "^19.0.0",
package/src/compile.ts CHANGED
@@ -544,9 +544,81 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
544
544
  // 作り替えた名前を表示だけ戻す (#1220)。 **図への追加を全て終えた後**に戻す = 途中で戻すと、
545
545
  // 後続の処理が名前で引く時に作り替え前と後が混ざる
546
546
  restoreActorNames(merged, 分けた.元の名前, 作り替えた対象);
547
+ // 配色と行の縞は **図への追加を全て終えた後**に当てる (#1553)。 図種ごとの組み立ては
548
+ // 20 か所以上あり、そのどれに足しても残りが取り残される
549
+ 配色と縞を当てる(merged, doc);
550
+ 静止した図の焦点を外す(merged, doc);
547
551
  return merged;
548
552
  }
549
553
 
554
+ /**
555
+ * 段を書かない図では、どの箱も「いま」 にしない (#1557)。
556
+ *
557
+ * ## 何が起きていたか
558
+ *
559
+ * `animation:` を書かない図でも段は 1 つ作られる。 作るのは 2 経路あり、cdl の組み立て器
560
+ * (`er()` / `flow()` / `topology()` 等) が図種ごとに 1 段を作る経路と、どちらも作らない図種に
561
+ * dragon が 1 段を足す経路 (`injectStaticPhase`)。 どちらも **全ての箱と矢印を段に載せる**。
562
+ *
563
+ * 描画側は段に載った箱を「いま」 として描く (枠を主役色にして太くする)。 その結果、静止した図は
564
+ * 全ての箱が「いま」 になり、止まっている箱の枠が 1 度も出ない (実測 = ER 図 3 箱 / 流れ図 2 箱)。
565
+ *
566
+ * 全部を強調するのと何も強調しないのは、どちらも「区別が無い」 状態。 後者の方が落ち着いて読める。
567
+ *
568
+ * ## 矢印は段に載せたまま置く
569
+ *
570
+ * 描画側の既定 (`edgeReveal: "phase"`) は「段が名指しする矢印は、その段が来るまで描かない」。
571
+ * 矢印まで外すと、静止した図から線が 1 本も出なくなる。
572
+ *
573
+ * ## 判定は書いた内容で決める
574
+ *
575
+ * 「段に全ての箱が載っている図」 を目印にしない = 書き手が 1 段だけ書いて全部を焦点にした図と
576
+ * 見分けが付かない。 見るのは `animation:` を書いたかどうかで、これは書き手が段のために書く値。
577
+ */
578
+ function 静止した図の焦点を外す(diagram: CdlDiagram, doc: DslDocument): void {
579
+ if ((doc.animate?.phases.length ?? 0) > 0) return;
580
+ const 箱 = new Set(diagram.nodes.map((n) => n.id));
581
+ for (const phase of diagram.phases) {
582
+ phase.activate = phase.activate.filter((id) => !箱.has(id));
583
+ }
584
+ }
585
+
586
+ const 既定の配色を持つ図種: ReadonlySet<DslDocument["type"]> = new Set(["er", "class"]);
587
+
588
+ /**
589
+ * 図の配色と、表の箱の行の縞を当てる (#1553)。
590
+ *
591
+ * ## 配色
592
+ *
593
+ * cdl は色を持たない。 名前だけを `data-cdl-palette` として markup に出し、消費側
594
+ * (`cdl-theme.css`) が名前を見て 7 つの口 (台 / 行の面 / 縞 / 枠 / 字 / 型名 / 線) に色を当てる。
595
+ *
596
+ * ER 図とクラス図は書かなくても `kinari` (生成りに茶) になる。
597
+ * どちらも箱の作りが同じ (行頭の印 + 左に名前 + 右に型) で、名前と型が離れて並ぶため、
598
+ * 行を横に追う目印 (行の縞) が要る。
599
+ * 縞の色は配色からしか来ないので、既定が無いと縞が箱の面と同じ色に落ちて 1 本も出ない。
600
+ * 書き手が `palette:` を書いた時はそちらが勝つ。
601
+ *
602
+ * クラス図の意匠は `docs/design/class/note.md` が持つ。
603
+ *
604
+ * ## 行の縞
605
+ *
606
+ * cdl の `er()` 組み立て器は縞を既定で敷くが、**動きを持つ ER 図はその経路を通らない**
607
+ * (`compileGenericWithAnimate` が箱を直に組む)。 同じ図が動きの有無で縞を持ったり持たなかったり
608
+ * しないよう、出口で揃える。
609
+ *
610
+ * 縞を描くのは表の箱 (`storage`) だけ。 他の種別の箱に書いても描画側が読まないので、
611
+ * ここで対象を絞って「書いたのに出ない」 欄を残さない。
612
+ */
613
+ function 配色と縞を当てる(diagram: CdlDiagram, doc: DslDocument): void {
614
+ const 配色 = doc.palette ?? (既定の配色を持つ図種.has(doc.type) ? "kinari" : undefined);
615
+ if (配色 !== undefined) diagram.palette = 配色;
616
+ if (doc.type !== "er") return;
617
+ for (const node of diagram.nodes) {
618
+ if (node.kind === "storage") node.rowStripe = true;
619
+ }
620
+ }
621
+
550
622
  /**
551
623
  * readout の配色配列から外部参照を落とす (#1374)。
552
624
  *
@@ -599,11 +671,15 @@ function materializeStates(diagram: CdlDiagram, doc: DslDocument): void {
599
671
  * 描画側は段が 1 件以上あることを要求する。 一方で段を作るかどうかは種類ごとにばらけており、
600
672
  * `animation:` を書かない図は 12 種のうち 6 種だけが描かれ、 残り 6 種は弾かれていた。
601
673
  *
602
- * ## 何も光らせない段にはしない
674
+ * ## 空の段にはしない
603
675
  *
604
- * 段には「この段で何が主役か」 を示す役割がある。 空の段を入れると図は描かれるが、 全ての
605
- * 要素が主役でない状態 (薄い表示) になり、 動かない図として読めない。 全部を光らせる段なら、
606
- * 動かない図が「常に全部が主役」 として自然に読める。
676
+ * 描画側の既定 (`edgeReveal: "phase"`) は「段が名指しする矢印は、その段が来るまで描かない」。
677
+ * 空の段を入れると、静止した図から線が 1 本も出ない。
678
+ *
679
+ * **箱は段に載せた後で外す** (#1557)。 ここで載せるのは矢印を描かせるためで、箱まで
680
+ * 「いま」 のままにすると静止した図の全ての箱が主役色の枠になる。 外すのは出口の
681
+ * `静止した図の焦点を外す` が 1 か所で行う = 段を作る経路は cdl の組み立て器にもあり、
682
+ * ここだけ直しても図種によって残る。
607
683
  *
608
684
  * ## 既に段がある図には触らない
609
685
  *
@@ -3436,6 +3512,10 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
3436
3512
  if (s.tailHead !== undefined) target.tailHead = s.tailHead;
3437
3513
  if (s.headFill !== undefined) target.headFill = s.headFill;
3438
3514
  if (s.tailHeadFill !== undefined) target.tailHeadFill = s.tailHeadFill;
3515
+ // 辺の役目と名前の下地 (cdl#618)。 主となる道を朱で引き、丸い下地を外せる。
3516
+ // 書かない辺には値を入れない = 既存の図が変わらない
3517
+ if (s.role !== undefined) target.role = s.role;
3518
+ if (s.labelPlate !== undefined) target.labelPlate = s.labelPlate;
3439
3519
  if (s.labelOffsetX !== undefined) target.labelOffsetX = s.labelOffsetX;
3440
3520
  if (s.labelOffsetY !== undefined) target.labelOffsetY = s.labelOffsetY;
3441
3521
  if (s.overlay !== undefined) target.overlay = s.overlay;
@@ -5443,9 +5523,13 @@ function 行を組み立て器が持つ(type: DslDocument["type"]): boolean {
5443
5523
  /**
5444
5524
  * 行と印を組む (#1466)。 群の分け方は図の種類が決める。
5445
5525
  *
5446
- * ER は **鍵の群を上にまとめ、間を空の行 1 つで開ける** (組み立て器 `er()` と同じ形)。
5447
- * 記法に空の行を書かせないのは、`rows` が空の要素を捨てるため = 書いても消える。
5526
+ * ER は **鍵の群を上にまとめる** (組み立て器 `er()` と同じ形)。
5448
5527
  * 状態遷移は群を分けないので、書いた並びのまま。
5528
+ *
5529
+ * **空の行では開けない** (cdl `#606`)。 群の区切りは行頭の印が持っている = ER は鍵の名前に
5530
+ * 下線が付く。 空の行はその 2 つ目の手掛かりで、代わりに行の間隔を不揃いにしていた
5531
+ * (鍵と値を持つ箱だけ境目が広がる)。 組み立て器が cdl 0.23.0 で空の行をやめたので、
5532
+ * こちらも同時にやめる = 片方だけ残ると記法と図の照合が落ちる。
5449
5533
  */
5450
5534
  function 行と印を組む(
5451
5535
  type: DslDocument["type"],
@@ -5460,10 +5544,10 @@ function 行と印を組む(
5460
5544
  const 鍵: number[] = [];
5461
5545
  const 値: number[] = [];
5462
5546
  rows.forEach((_, i) => ((印[i]?.underline === true ? 鍵 : 値).push(i)));
5463
- const 並び = 鍵.length > 0 && 値.length > 0 ? [...鍵, -1, ...値] : [...鍵, ...値];
5547
+ const 並び = [...鍵, ...値];
5464
5548
  return {
5465
- rows: 並び.map((i) => (i < 0 ? "" : (rows[i] ?? ""))),
5466
- rowMarks: 並び.map((i) => (i < 0 ? null : (印[i] ?? null))),
5549
+ rows: 並び.map((i) => rows[i] ?? ""),
5550
+ rowMarks: 並び.map((i) => 印[i] ?? null),
5467
5551
  };
5468
5552
  }
5469
5553
 
@@ -6607,6 +6691,17 @@ const 縦列を選べる図種: ReadonlySet<PresetType> = new Set<PresetType>([
6607
6691
  "swimlane",
6608
6692
  "class",
6609
6693
  "state",
6694
+ /*
6695
+ * ER 図 (#1571)。
6696
+ *
6697
+ * `er` の組み立て器は実体 1 つにつき帯を 1 本作り、必ず段 0 に置く = 表が横 1 列にしか
6698
+ * 並ばない。 関係を 4 本持つ実体があると、どう並べ替えても 2 本は隣を飛び越す
6699
+ * (意匠帳 `docs/design/er/note.md` § 記法の制約 が 4 通りを実測しており、1 列は縦横比 7.0 /
6700
+ * 最長の線 4068、格子は 3.5 / 836)。
6701
+ *
6702
+ * **全ての箱が縦列を書いた時だけ** 効くので、書かない図はいままでどおり組み立て器が並べる。
6703
+ */
6704
+ "er",
6610
6705
  ]);
6611
6706
 
6612
6707
  /**
@@ -36,6 +36,10 @@ import {
36
36
  EVENT_KINDS,
37
37
  type 図形の定義,
38
38
  } from "./v05/parser";
39
+ // 図の配色 (#1553)。 記法の読み手と同じ解決を通す = 別名 (`生成り` / `青磁`) の受け方が
40
+ // 記法と JSON でずれない
41
+ import { resolvePalette } from "./keywords";
42
+ import type { DslPalette } from "./keywords";
39
43
  import type { CompileToCdlOpts } from "./compile";
40
44
  import type {
41
45
  CdlDiagram,
@@ -189,6 +193,16 @@ export interface DragonJson {
189
193
  reveal?: EdgeReveal;
190
194
  /** 図の並ぶ向き (#1494)。 記法の最上位 `direction:` と同じ。 JSON は英語の語で書く */
191
195
  direction?: "vertical" | "horizontal";
196
+ /**
197
+ * 図の配色 (#1553)。 記法の最上位 `palette:` と同じ。
198
+ *
199
+ * 名前だけを図に載せる = cdl は色を持たず、値は `cdl-theme.css` が決める。
200
+ * ER 図とクラス図は書かなくても `kinari` (生成りに茶) になる。 どちらも箱の作りが同じ
201
+ * (行頭の印 + 左に名前 + 右に型) で、名前と型が離れて並ぶため、行を横に追う目印
202
+ * (行の縞) が要る。 縞の色は配色からしか来ないので、既定が無いと縞が箱の面と同じ色に
203
+ * 落ちて 1 本も出ない。 書き手が `palette:` を書いた時はそちらが勝つ。
204
+ */
205
+ palette?: DslPalette;
192
206
  }
193
207
 
194
208
  export interface JsonActor {
@@ -347,6 +361,14 @@ export interface JsonStep {
347
361
  tailHead?: EdgeHead;
348
362
  headFill?: EdgeHeadFill;
349
363
  tailHeadFill?: EdgeHeadFill;
364
+ /**
365
+ * 辺の役目 (cdl#618)。 記法の `{ role: main }` と同じ。
366
+ *
367
+ * `main` を書いた辺だけ「いま」 の色で引く。 主となる 1 本 (または 1 続き) にだけ書く。
368
+ */
369
+ role?: "main";
370
+ /** 名前の下地を敷くか (cdl#618)。 記法の `{ labelPlate: false }` と同じ。 既定は敷く */
371
+ labelPlate?: boolean;
350
372
  /** クラス図の関係の語 (#1466)。 書くと端の形 / 塗り / 線種がまとめて決まる */
351
373
  relation?: ClassRelationType;
352
374
  /** 順序図の言づての種類 (#1466)。 `call` / `return` / `fire` */
@@ -491,6 +513,8 @@ export const ACCEPTED_KEYS = {
491
513
  "reveal",
492
514
  // 図の並ぶ向き (#1494)
493
515
  "direction",
516
+ // 図の配色 (#1553)
517
+ "palette",
494
518
  ],
495
519
  actor: [
496
520
  "name",
@@ -548,6 +572,9 @@ export const ACCEPTED_KEYS = {
548
572
  "headFill",
549
573
  "tailHeadFill",
550
574
  "relation",
575
+ // 辺の役目と名前の下地 (cdl#618)
576
+ "role",
577
+ "labelPlate",
551
578
  "kind",
552
579
  "tone",
553
580
  "style",
@@ -649,6 +676,8 @@ export const 欄の型表 = {
649
676
  reveal: "非空の文字列",
650
677
  // 図の並ぶ向き (#1494)
651
678
  direction: "非空の文字列",
679
+ // 図の配色 (#1553)
680
+ palette: "非空の文字列",
652
681
  },
653
682
  actor: {
654
683
  name: "必須の非空文字列",
@@ -704,6 +733,9 @@ export const 欄の型表 = {
704
733
  headFill: "非空の文字列",
705
734
  tailHeadFill: "非空の文字列",
706
735
  relation: "非空の文字列",
736
+ // 辺の役目と名前の下地 (cdl#618)
737
+ role: "非空の文字列",
738
+ labelPlate: "真偽",
707
739
  kind: "非空の文字列",
708
740
  tone: "色",
709
741
  style: "線種",
@@ -2348,6 +2380,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2348
2380
  pos: p0,
2349
2381
  };
2350
2382
  });
2383
+ // 図の配色 (#1553)。 解くのは 1 度だけにする
2384
+ const 配色 = json.palette === undefined ? null : resolvePalette(json.palette);
2385
+
2351
2386
  const flow: DslStep[] = json.flow.map((s, i) => ({
2352
2387
  no: i + 1,
2353
2388
  from: s.from,
@@ -2362,6 +2397,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2362
2397
  headFill: s.headFill,
2363
2398
  tailHeadFill: s.tailHeadFill,
2364
2399
  relation: s.relation,
2400
+ // 辺の役目と名前の下地 (cdl#618)
2401
+ role: s.role,
2402
+ labelPlate: s.labelPlate,
2365
2403
  msgKind: s.kind,
2366
2404
  // 箱と同じ読み替えを通す (#1304)。 通さないと `tone: "成功"` が色名として解決されないまま
2367
2405
  // 図に届き、同じ値が箱では色になり矢印では色にならない
@@ -2502,6 +2540,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2502
2540
  reveal: json.reveal,
2503
2541
  // 図の並ぶ向き (#1494)。 JSON は英語で書くので、記法と同じ語に直してから渡す
2504
2542
  ...(json.direction !== undefined ? { direction: json.direction === "horizontal" ? ("横" as const) : ("縦" as const) } : {}),
2543
+ // 図の配色 (#1553)。 記法と同じ解決を通す = 別名 (`生成り` / `青磁`) の受け方がずれない。
2544
+ // 読めない語は渡さない = 上流の型検査が語を絞っているので、ここに来るのは書き間違いだけ
2545
+ ...(配色 !== null ? { palette: 配色 } : {}),
2505
2546
  pos: p0,
2506
2547
  };
2507
2548
  }
package/src/keywords.ts CHANGED
@@ -45,6 +45,44 @@ export function resolveDirection(s: string): DslDirection | null {
45
45
  return Object.hasOwn(DIRECTION_ALIAS, k) ? DIRECTION_ALIAS[k]! : null;
46
46
  }
47
47
 
48
+ /**
49
+ * 図の配色 (`palette:`、 #1553)。
50
+ *
51
+ * cdl は色を持たない (形だけを描く)。 名前を `data-cdl-palette` として markup に出すので、
52
+ * dragon の `cdl-theme.css` がその名前を見て 7 つの口 (台 / 行の面 / 縞 / 枠 / 字 / 型名 / 線)
53
+ * に色を当てる。
54
+ *
55
+ * **名前を自由文字列にしない**。 書き間違えると既定の色みのまま出るので、書き手には
56
+ * 「効かない」 としか見えない。 語を絞れば読めない語をその場で知らせられる。
57
+ */
58
+ export const PALETTES = ["kinari", "celadon"] as const;
59
+ export type DslPalette = (typeof PALETTES)[number];
60
+
61
+ /**
62
+ * 配色の別名。 向きと同じく日本語と英語の両方で書ける。
63
+ *
64
+ * `kinari` = 生成りに茶、 `celadon` = 青磁に墨。
65
+ *
66
+ * ER 図とクラス図は書かなくても `kinari` (生成りに茶) になる。 どちらも箱の作りが同じ
67
+ * (行頭の印 + 左に名前 + 右に型) で、名前と型が離れて並ぶため、行を横に追う目印
68
+ * (行の縞) が要る。 縞の色は配色からしか来ないので、既定が無いと縞が箱の面と同じ色に
69
+ * 落ちて 1 本も出ない。 書き手が `palette:` を書いた時はそちらが勝つ。
70
+ */
71
+ export const PALETTE_ALIAS: Record<string, DslPalette> = {
72
+ kinari: "kinari",
73
+ celadon: "celadon",
74
+ 生成り: "kinari",
75
+ 生成りに茶: "kinari",
76
+ 青磁: "celadon",
77
+ 青磁に墨: "celadon",
78
+ };
79
+
80
+ /** 書いた配色を正規の語に直す。 読めない語は `null`。 */
81
+ export function resolvePalette(s: string): DslPalette | null {
82
+ const k = s.trim().toLowerCase();
83
+ return Object.hasOwn(PALETTE_ALIAS, k) ? PALETTE_ALIAS[k]! : null;
84
+ }
85
+
48
86
  /** NodeKind 別名 (日本語 → English) */
49
87
  export const NODE_KIND_ALIAS: Record<string, NodeKind> = {
50
88
  // 日本語