@cardenelabs/dragon 0.18.2 → 0.19.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.2",
3
+ "version": "0.19.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.21.2"
60
+ "@cardenelabs/cdl": "^0.25.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "react": "^19.0.0",
package/src/compile.ts CHANGED
@@ -544,9 +544,75 @@ 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
+ /**
587
+ * 図の配色と、表の箱の行の縞を当てる (#1553)。
588
+ *
589
+ * ## 配色
590
+ *
591
+ * cdl は色を持たない。 名前だけを `data-cdl-palette` として markup に出し、消費側
592
+ * (`cdl-theme.css`) が名前を見て 7 つの口 (台 / 行の面 / 縞 / 枠 / 字 / 型名 / 線) に色を当てる。
593
+ *
594
+ * ER 図は書かなくても `kinari` (生成りに茶) になる。 ER 図は小さい字が密に並ぶので、他の図種と
595
+ * 同じ色みだと行を追えない = 既定を持たせて「作れば必ずその色みになる」 形にする。
596
+ * 書き手が `palette:` を書いた時はそちらが勝つ。
597
+ *
598
+ * ## 行の縞
599
+ *
600
+ * cdl の `er()` 組み立て器は縞を既定で敷くが、**動きを持つ ER 図はその経路を通らない**
601
+ * (`compileGenericWithAnimate` が箱を直に組む)。 同じ図が動きの有無で縞を持ったり持たなかったり
602
+ * しないよう、出口で揃える。
603
+ *
604
+ * 縞を描くのは表の箱 (`storage`) だけ。 他の種別の箱に書いても描画側が読まないので、
605
+ * ここで対象を絞って「書いたのに出ない」 欄を残さない。
606
+ */
607
+ function 配色と縞を当てる(diagram: CdlDiagram, doc: DslDocument): void {
608
+ const 配色 = doc.palette ?? (doc.type === "er" ? "kinari" : undefined);
609
+ if (配色 !== undefined) diagram.palette = 配色;
610
+ if (doc.type !== "er") return;
611
+ for (const node of diagram.nodes) {
612
+ if (node.kind === "storage") node.rowStripe = true;
613
+ }
614
+ }
615
+
550
616
  /**
551
617
  * readout の配色配列から外部参照を落とす (#1374)。
552
618
  *
@@ -599,11 +665,15 @@ function materializeStates(diagram: CdlDiagram, doc: DslDocument): void {
599
665
  * 描画側は段が 1 件以上あることを要求する。 一方で段を作るかどうかは種類ごとにばらけており、
600
666
  * `animation:` を書かない図は 12 種のうち 6 種だけが描かれ、 残り 6 種は弾かれていた。
601
667
  *
602
- * ## 何も光らせない段にはしない
668
+ * ## 空の段にはしない
669
+ *
670
+ * 描画側の既定 (`edgeReveal: "phase"`) は「段が名指しする矢印は、その段が来るまで描かない」。
671
+ * 空の段を入れると、静止した図から線が 1 本も出ない。
603
672
  *
604
- * 段には「この段で何が主役か」 を示す役割がある。 空の段を入れると図は描かれるが、 全ての
605
- * 要素が主役でない状態 (薄い表示) になり、 動かない図として読めない。 全部を光らせる段なら、
606
- * 動かない図が「常に全部が主役」 として自然に読める。
673
+ * **箱は段に載せた後で外す** (#1557)。 ここで載せるのは矢印を描かせるためで、箱まで
674
+ * 「いま」 のままにすると静止した図の全ての箱が主役色の枠になる。 外すのは出口の
675
+ * `静止した図の焦点を外す` が 1 か所で行う = 段を作る経路は cdl の組み立て器にもあり、
676
+ * ここだけ直しても図種によって残る。
607
677
  *
608
678
  * ## 既に段がある図には触らない
609
679
  *
@@ -3436,6 +3506,10 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
3436
3506
  if (s.tailHead !== undefined) target.tailHead = s.tailHead;
3437
3507
  if (s.headFill !== undefined) target.headFill = s.headFill;
3438
3508
  if (s.tailHeadFill !== undefined) target.tailHeadFill = s.tailHeadFill;
3509
+ // 辺の役目と名前の下地 (cdl#618)。 主となる道を朱で引き、丸い下地を外せる。
3510
+ // 書かない辺には値を入れない = 既存の図が変わらない
3511
+ if (s.role !== undefined) target.role = s.role;
3512
+ if (s.labelPlate !== undefined) target.labelPlate = s.labelPlate;
3439
3513
  if (s.labelOffsetX !== undefined) target.labelOffsetX = s.labelOffsetX;
3440
3514
  if (s.labelOffsetY !== undefined) target.labelOffsetY = s.labelOffsetY;
3441
3515
  if (s.overlay !== undefined) target.overlay = s.overlay;
@@ -5443,9 +5517,13 @@ function 行を組み立て器が持つ(type: DslDocument["type"]): boolean {
5443
5517
  /**
5444
5518
  * 行と印を組む (#1466)。 群の分け方は図の種類が決める。
5445
5519
  *
5446
- * ER は **鍵の群を上にまとめ、間を空の行 1 つで開ける** (組み立て器 `er()` と同じ形)。
5447
- * 記法に空の行を書かせないのは、`rows` が空の要素を捨てるため = 書いても消える。
5520
+ * ER は **鍵の群を上にまとめる** (組み立て器 `er()` と同じ形)。
5448
5521
  * 状態遷移は群を分けないので、書いた並びのまま。
5522
+ *
5523
+ * **空の行では開けない** (cdl `#606`)。 群の区切りは行頭の印が持っている = ER は鍵の名前に
5524
+ * 下線が付く。 空の行はその 2 つ目の手掛かりで、代わりに行の間隔を不揃いにしていた
5525
+ * (鍵と値を持つ箱だけ境目が広がる)。 組み立て器が cdl 0.23.0 で空の行をやめたので、
5526
+ * こちらも同時にやめる = 片方だけ残ると記法と図の照合が落ちる。
5449
5527
  */
5450
5528
  function 行と印を組む(
5451
5529
  type: DslDocument["type"],
@@ -5460,10 +5538,10 @@ function 行と印を組む(
5460
5538
  const 鍵: number[] = [];
5461
5539
  const 値: number[] = [];
5462
5540
  rows.forEach((_, i) => ((印[i]?.underline === true ? 鍵 : 値).push(i)));
5463
- const 並び = 鍵.length > 0 && 値.length > 0 ? [...鍵, -1, ...値] : [...鍵, ...値];
5541
+ const 並び = [...鍵, ...値];
5464
5542
  return {
5465
- rows: 並び.map((i) => (i < 0 ? "" : (rows[i] ?? ""))),
5466
- rowMarks: 並び.map((i) => (i < 0 ? null : (印[i] ?? null))),
5543
+ rows: 並び.map((i) => rows[i] ?? ""),
5544
+ rowMarks: 並び.map((i) => 印[i] ?? null),
5467
5545
  };
5468
5546
  }
5469
5547
 
@@ -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,13 @@ 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
+ palette?: DslPalette;
192
203
  }
193
204
 
194
205
  export interface JsonActor {
@@ -347,6 +358,14 @@ export interface JsonStep {
347
358
  tailHead?: EdgeHead;
348
359
  headFill?: EdgeHeadFill;
349
360
  tailHeadFill?: EdgeHeadFill;
361
+ /**
362
+ * 辺の役目 (cdl#618)。 記法の `{ role: main }` と同じ。
363
+ *
364
+ * `main` を書いた辺だけ「いま」 の色で引く。 主となる 1 本 (または 1 続き) にだけ書く。
365
+ */
366
+ role?: "main";
367
+ /** 名前の下地を敷くか (cdl#618)。 記法の `{ labelPlate: false }` と同じ。 既定は敷く */
368
+ labelPlate?: boolean;
350
369
  /** クラス図の関係の語 (#1466)。 書くと端の形 / 塗り / 線種がまとめて決まる */
351
370
  relation?: ClassRelationType;
352
371
  /** 順序図の言づての種類 (#1466)。 `call` / `return` / `fire` */
@@ -491,6 +510,8 @@ export const ACCEPTED_KEYS = {
491
510
  "reveal",
492
511
  // 図の並ぶ向き (#1494)
493
512
  "direction",
513
+ // 図の配色 (#1553)
514
+ "palette",
494
515
  ],
495
516
  actor: [
496
517
  "name",
@@ -548,6 +569,9 @@ export const ACCEPTED_KEYS = {
548
569
  "headFill",
549
570
  "tailHeadFill",
550
571
  "relation",
572
+ // 辺の役目と名前の下地 (cdl#618)
573
+ "role",
574
+ "labelPlate",
551
575
  "kind",
552
576
  "tone",
553
577
  "style",
@@ -649,6 +673,8 @@ export const 欄の型表 = {
649
673
  reveal: "非空の文字列",
650
674
  // 図の並ぶ向き (#1494)
651
675
  direction: "非空の文字列",
676
+ // 図の配色 (#1553)
677
+ palette: "非空の文字列",
652
678
  },
653
679
  actor: {
654
680
  name: "必須の非空文字列",
@@ -704,6 +730,9 @@ export const 欄の型表 = {
704
730
  headFill: "非空の文字列",
705
731
  tailHeadFill: "非空の文字列",
706
732
  relation: "非空の文字列",
733
+ // 辺の役目と名前の下地 (cdl#618)
734
+ role: "非空の文字列",
735
+ labelPlate: "真偽",
707
736
  kind: "非空の文字列",
708
737
  tone: "色",
709
738
  style: "線種",
@@ -2348,6 +2377,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2348
2377
  pos: p0,
2349
2378
  };
2350
2379
  });
2380
+ // 図の配色 (#1553)。 解くのは 1 度だけにする
2381
+ const 配色 = json.palette === undefined ? null : resolvePalette(json.palette);
2382
+
2351
2383
  const flow: DslStep[] = json.flow.map((s, i) => ({
2352
2384
  no: i + 1,
2353
2385
  from: s.from,
@@ -2362,6 +2394,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2362
2394
  headFill: s.headFill,
2363
2395
  tailHeadFill: s.tailHeadFill,
2364
2396
  relation: s.relation,
2397
+ // 辺の役目と名前の下地 (cdl#618)
2398
+ role: s.role,
2399
+ labelPlate: s.labelPlate,
2365
2400
  msgKind: s.kind,
2366
2401
  // 箱と同じ読み替えを通す (#1304)。 通さないと `tone: "成功"` が色名として解決されないまま
2367
2402
  // 図に届き、同じ値が箱では色になり矢印では色にならない
@@ -2502,6 +2537,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2502
2537
  reveal: json.reveal,
2503
2538
  // 図の並ぶ向き (#1494)。 JSON は英語で書くので、記法と同じ語に直してから渡す
2504
2539
  ...(json.direction !== undefined ? { direction: json.direction === "horizontal" ? ("横" as const) : ("縦" as const) } : {}),
2540
+ // 図の配色 (#1553)。 記法と同じ解決を通す = 別名 (`生成り` / `青磁`) の受け方がずれない。
2541
+ // 読めない語は渡さない = 上流の型検査が語を絞っているので、ここに来るのは書き間違いだけ
2542
+ ...(配色 !== null ? { palette: 配色 } : {}),
2505
2543
  pos: p0,
2506
2544
  };
2507
2545
  }
package/src/keywords.ts CHANGED
@@ -45,6 +45,39 @@ 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` = 生成りに茶 (ER 図の既定)、 `celadon` = 青磁に墨。
65
+ */
66
+ export const PALETTE_ALIAS: Record<string, DslPalette> = {
67
+ kinari: "kinari",
68
+ celadon: "celadon",
69
+ 生成り: "kinari",
70
+ 生成りに茶: "kinari",
71
+ 青磁: "celadon",
72
+ 青磁に墨: "celadon",
73
+ };
74
+
75
+ /** 書いた配色を正規の語に直す。 読めない語は `null`。 */
76
+ export function resolvePalette(s: string): DslPalette | null {
77
+ const k = s.trim().toLowerCase();
78
+ return Object.hasOwn(PALETTE_ALIAS, k) ? PALETTE_ALIAS[k]! : null;
79
+ }
80
+
48
81
  /** NodeKind 別名 (日本語 → English) */
49
82
  export const NODE_KIND_ALIAS: Record<string, NodeKind> = {
50
83
  // 日本語