@cardenelabs/cdl 0.120.0 → 0.122.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.
@@ -118,7 +118,7 @@ declare function resolveLang(value: unknown): CdlLang;
118
118
  declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
119
119
  type Tone = (typeof TONES)[number];
120
120
  /** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
121
- declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
121
+ declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "station", "sequence-board"];
122
122
  type NodeKind = (typeof NODE_KINDS)[number];
123
123
  type Side = "top" | "right" | "bottom" | "left";
124
124
  /**
@@ -133,6 +133,17 @@ type Side = "top" | "right" | "bottom" | "left";
133
133
  * ER の「識別しない」 のように **刻みが意味を持つ関係** はこちらを使う。
134
134
  */
135
135
  type EdgeStyle = "solid" | "dotted-flow" | "dashed";
136
+ /**
137
+ * 線の道筋。
138
+ *
139
+ * - `"default"` ... 自動。 箱を避け、角を丸めた直角の線を引く。 書かない時も同じ
140
+ * - `"back-detour"` ... 箱の上を回って前の箱へ戻る
141
+ * - `"curve"` ... 始点から水平に出て終点へ水平に入る 3 次曲線。 途中の箱は避けず、
142
+ * 2 点が同じ高さなら直線になる。 終点の矢印は水平を向く
143
+ * - `"metro"` ... 横線と傾き ±1 の斜線だけで結び、角を半径 r の円弧で丸める。
144
+ * 途中の箱は避けず、2 点が同じ高さなら横 1 本になる
145
+ */
146
+ type EdgeRouting = "default" | "back-detour" | "curve" | "metro";
136
147
  /** 辺の役目 (#618)。 `main` = 図の主となる道 */
137
148
  type EdgeRole = "main";
138
149
  /**
@@ -393,8 +404,8 @@ type CdlNode = {
393
404
  * 変わるのは kind の既定色を使って描かれる部分で、 枠線 / eyebrow の文字色 / icon /
394
405
  * 強調表示中の行の値。 本文の文字色と背景は変わらない。
395
406
  *
396
- * 効くのは `actor` / `function` / `storage` / `event` / `card` と `person` 以降の汎用 kind の
397
- * 計 26 種。 それ以外は自前の配色を持つため指定しても変わらず、 検査も通す。 効く kind の
407
+ * 効くのは `actor` / `function` / `storage` / `event` / `card`、`person` 以降の汎用 kind、
408
+ * 流れの印、路線図の駅。 それ以外は自前の配色を持つため指定しても変わらず、検査も通す。
398
409
  * 一覧は `test/node-styling.test.tsx` が実装から計算した集合と突き合わせている。
399
410
  *
400
411
  * 既定色と同じ名前を明示指定しても色が変わる kind がある (既定色の hex が `render/tone.ts`
@@ -746,8 +757,12 @@ type CdlEdge = {
746
757
  labelOffsetX?: number;
747
758
  /** label の y 方向 fine offset (default 0、 + で下、 - で上) */
748
759
  labelOffsetY?: number;
749
- /** routing mode (default "default" = 自動 routing、 "back-detour" = back transition 用の上方 detour 強制) */
750
- routing?: "default" | "back-detour";
760
+ /**
761
+ * 線の道筋。 `default` は箱を避ける自動経路、 `back-detour` は箱の上を回る戻り線、
762
+ * `curve` は途中の箱を避けず、両端で水平になる 3 次曲線。 `metro` は途中の箱を避けず、
763
+ * 横線と傾き ±1 の斜線を半径 r の円弧で結ぶ。2 点が同じ高さなら横 1 本。書かなければ `default`。
764
+ */
765
+ routing?: EdgeRouting;
751
766
  /**
752
767
  * CAR-549 SSOT ... label を edge path 上に「被せる」 か「離す」 かの制御 flag。
753
768
  * default (false or undefined) = LABEL_INIT_CLEARANCE (34 world、 CAR-630 で 26 → 34 復帰) 離して配置。
@@ -3035,4 +3050,4 @@ type CdlDiagramViewProps = {
3035
3050
  };
3036
3051
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
3037
3052
 
3038
- export { computeEventBindingKey as $, EDGE_HEADS as A, type BBox as B, type CdlDiagram as C, EDGE_HEAD_DEFAULT as D, type EdgeStyle as E, EDGE_HEAD_FILLS as F, EDGE_HEAD_FILL_DEFAULT as G, EDGE_REVEALS as H, EDGE_REVEAL_DEFAULT as I, type JourneyEmotion as J, EDGE_STYLES as K, type LaidDiagram as L, type EdgeReveal as M, type NodeKind as N, type InteractiveHandlerMap as O, type InteractiveSignalsAccessor as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type LaidLane as U, NODE_KINDS as V, RELATION_FOCUSES as W, RELATION_FOCUS_DEFAULT as X, type RelationFocus as Y, type ScrollProgressHandle as Z, TONES as _, type CdlInput as a, computeFormulaKey as a0, computeInputSignalKey as a1, computeScrollTriggerKey as a2, computed as a3, createInteractiveSignalsAccessor as a4, createScrollProgressHandle as a5, createScrollProgressSignals as a6, hasInteractivePrimitives as a7, resolveLang as a8, sequenceBoardMetrics as a9, sequenceStepId as aa, signal as ab, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type CdlState as h, type CdlInputOption as i, type CdlEventTarget as j, type CdlLang as k, type Computed as l, type CdlDerivedValue as m, type CdlScrollTrigger as n, type CdlDiagramViewProps as o, type LaidEdge as p, type LaidNode as q, type CdlEdge as r, type TreeNodePayload as s, CdlDiagramView as t, type CdlEventBinding as u, type CdlEventKind as v, type CdlFormula as w, type CdlPhase as x, type ChartPieForm as y, type ChartSlopeForm as z };
3053
+ export { TONES as $, type ChartSlopeForm as A, type BBox as B, type CdlDiagram as C, EDGE_HEADS as D, type EdgeStyle as E, EDGE_HEAD_DEFAULT as F, EDGE_HEAD_FILLS as G, EDGE_HEAD_FILL_DEFAULT as H, EDGE_REVEALS as I, type JourneyEmotion as J, EDGE_REVEAL_DEFAULT as K, type LaidDiagram as L, EDGE_STYLES as M, type NodeKind as N, type EdgeReveal as O, type InteractiveHandlerMap as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveSignalsAccessor as U, type LaidLane as V, NODE_KINDS as W, RELATION_FOCUSES as X, RELATION_FOCUS_DEFAULT as Y, type RelationFocus as Z, type ScrollProgressHandle as _, type CdlInput as a, computeEventBindingKey as a0, computeFormulaKey as a1, computeInputSignalKey as a2, computeScrollTriggerKey as a3, computed as a4, createInteractiveSignalsAccessor as a5, createScrollProgressHandle as a6, createScrollProgressSignals as a7, hasInteractivePrimitives as a8, resolveLang as a9, sequenceBoardMetrics as aa, sequenceStepId as ab, signal as ac, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type Computed as m, type CdlDerivedValue as n, type CdlScrollTrigger as o, type CdlDiagramViewProps as p, type LaidEdge as q, type LaidNode as r, type CdlEdge as s, type TreeNodePayload as t, CdlDiagramView as u, type CdlEventBinding as v, type CdlEventKind as w, type CdlFormula as x, type CdlPhase as y, type ChartPieForm as z };
@@ -118,7 +118,7 @@ declare function resolveLang(value: unknown): CdlLang;
118
118
  declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
119
119
  type Tone = (typeof TONES)[number];
120
120
  /** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
121
- declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
121
+ declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "station", "sequence-board"];
122
122
  type NodeKind = (typeof NODE_KINDS)[number];
123
123
  type Side = "top" | "right" | "bottom" | "left";
124
124
  /**
@@ -133,6 +133,17 @@ type Side = "top" | "right" | "bottom" | "left";
133
133
  * ER の「識別しない」 のように **刻みが意味を持つ関係** はこちらを使う。
134
134
  */
135
135
  type EdgeStyle = "solid" | "dotted-flow" | "dashed";
136
+ /**
137
+ * 線の道筋。
138
+ *
139
+ * - `"default"` ... 自動。 箱を避け、角を丸めた直角の線を引く。 書かない時も同じ
140
+ * - `"back-detour"` ... 箱の上を回って前の箱へ戻る
141
+ * - `"curve"` ... 始点から水平に出て終点へ水平に入る 3 次曲線。 途中の箱は避けず、
142
+ * 2 点が同じ高さなら直線になる。 終点の矢印は水平を向く
143
+ * - `"metro"` ... 横線と傾き ±1 の斜線だけで結び、角を半径 r の円弧で丸める。
144
+ * 途中の箱は避けず、2 点が同じ高さなら横 1 本になる
145
+ */
146
+ type EdgeRouting = "default" | "back-detour" | "curve" | "metro";
136
147
  /** 辺の役目 (#618)。 `main` = 図の主となる道 */
137
148
  type EdgeRole = "main";
138
149
  /**
@@ -393,8 +404,8 @@ type CdlNode = {
393
404
  * 変わるのは kind の既定色を使って描かれる部分で、 枠線 / eyebrow の文字色 / icon /
394
405
  * 強調表示中の行の値。 本文の文字色と背景は変わらない。
395
406
  *
396
- * 効くのは `actor` / `function` / `storage` / `event` / `card` と `person` 以降の汎用 kind の
397
- * 計 26 種。 それ以外は自前の配色を持つため指定しても変わらず、 検査も通す。 効く kind の
407
+ * 効くのは `actor` / `function` / `storage` / `event` / `card`、`person` 以降の汎用 kind、
408
+ * 流れの印、路線図の駅。 それ以外は自前の配色を持つため指定しても変わらず、検査も通す。
398
409
  * 一覧は `test/node-styling.test.tsx` が実装から計算した集合と突き合わせている。
399
410
  *
400
411
  * 既定色と同じ名前を明示指定しても色が変わる kind がある (既定色の hex が `render/tone.ts`
@@ -746,8 +757,12 @@ type CdlEdge = {
746
757
  labelOffsetX?: number;
747
758
  /** label の y 方向 fine offset (default 0、 + で下、 - で上) */
748
759
  labelOffsetY?: number;
749
- /** routing mode (default "default" = 自動 routing、 "back-detour" = back transition 用の上方 detour 強制) */
750
- routing?: "default" | "back-detour";
760
+ /**
761
+ * 線の道筋。 `default` は箱を避ける自動経路、 `back-detour` は箱の上を回る戻り線、
762
+ * `curve` は途中の箱を避けず、両端で水平になる 3 次曲線。 `metro` は途中の箱を避けず、
763
+ * 横線と傾き ±1 の斜線を半径 r の円弧で結ぶ。2 点が同じ高さなら横 1 本。書かなければ `default`。
764
+ */
765
+ routing?: EdgeRouting;
751
766
  /**
752
767
  * CAR-549 SSOT ... label を edge path 上に「被せる」 か「離す」 かの制御 flag。
753
768
  * default (false or undefined) = LABEL_INIT_CLEARANCE (34 world、 CAR-630 で 26 → 34 復帰) 離して配置。
@@ -3035,4 +3050,4 @@ type CdlDiagramViewProps = {
3035
3050
  };
3036
3051
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
3037
3052
 
3038
- export { computeEventBindingKey as $, EDGE_HEADS as A, type BBox as B, type CdlDiagram as C, EDGE_HEAD_DEFAULT as D, type EdgeStyle as E, EDGE_HEAD_FILLS as F, EDGE_HEAD_FILL_DEFAULT as G, EDGE_REVEALS as H, EDGE_REVEAL_DEFAULT as I, type JourneyEmotion as J, EDGE_STYLES as K, type LaidDiagram as L, type EdgeReveal as M, type NodeKind as N, type InteractiveHandlerMap as O, type InteractiveSignalsAccessor as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type LaidLane as U, NODE_KINDS as V, RELATION_FOCUSES as W, RELATION_FOCUS_DEFAULT as X, type RelationFocus as Y, type ScrollProgressHandle as Z, TONES as _, type CdlInput as a, computeFormulaKey as a0, computeInputSignalKey as a1, computeScrollTriggerKey as a2, computed as a3, createInteractiveSignalsAccessor as a4, createScrollProgressHandle as a5, createScrollProgressSignals as a6, hasInteractivePrimitives as a7, resolveLang as a8, sequenceBoardMetrics as a9, sequenceStepId as aa, signal as ab, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type CdlState as h, type CdlInputOption as i, type CdlEventTarget as j, type CdlLang as k, type Computed as l, type CdlDerivedValue as m, type CdlScrollTrigger as n, type CdlDiagramViewProps as o, type LaidEdge as p, type LaidNode as q, type CdlEdge as r, type TreeNodePayload as s, CdlDiagramView as t, type CdlEventBinding as u, type CdlEventKind as v, type CdlFormula as w, type CdlPhase as x, type ChartPieForm as y, type ChartSlopeForm as z };
3053
+ export { TONES as $, type ChartSlopeForm as A, type BBox as B, type CdlDiagram as C, EDGE_HEADS as D, type EdgeStyle as E, EDGE_HEAD_DEFAULT as F, EDGE_HEAD_FILLS as G, EDGE_HEAD_FILL_DEFAULT as H, EDGE_REVEALS as I, type JourneyEmotion as J, EDGE_REVEAL_DEFAULT as K, type LaidDiagram as L, EDGE_STYLES as M, type NodeKind as N, type EdgeReveal as O, type InteractiveHandlerMap as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveSignalsAccessor as U, type LaidLane as V, NODE_KINDS as W, RELATION_FOCUSES as X, RELATION_FOCUS_DEFAULT as Y, type RelationFocus as Z, type ScrollProgressHandle as _, type CdlInput as a, computeEventBindingKey as a0, computeFormulaKey as a1, computeInputSignalKey as a2, computeScrollTriggerKey as a3, computed as a4, createInteractiveSignalsAccessor as a5, createScrollProgressHandle as a6, createScrollProgressSignals as a7, hasInteractivePrimitives as a8, resolveLang as a9, sequenceBoardMetrics as aa, sequenceStepId as ab, signal as ac, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type Computed as m, type CdlDerivedValue as n, type CdlScrollTrigger as o, type CdlDiagramViewProps as p, type LaidEdge as q, type LaidNode as r, type CdlEdge as s, type TreeNodePayload as t, CdlDiagramView as u, type CdlEventBinding as v, type CdlEventKind as w, type CdlFormula as x, type CdlPhase as y, type ChartPieForm as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.120.0",
3
+ "version": "0.122.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/builder.ts CHANGED
@@ -17,6 +17,7 @@ import type {
17
17
  CdlState,
18
18
  EdgeHead,
19
19
  EdgeHeadFill,
20
+ EdgeRouting,
20
21
  EdgeStyle,
21
22
  EdgeRole,
22
23
  NodeKind,
@@ -1461,7 +1462,7 @@ export type DiagramBuilder = {
1461
1462
  tailLabel?: string;
1462
1463
  labelOffsetX?: number;
1463
1464
  labelOffsetY?: number;
1464
- routing?: "default" | "back-detour";
1465
+ routing?: EdgeRouting;
1465
1466
  /** CAR-549 SSOT ... true で label を edge path 上に重ねる (flowchart condition label 用) */
1466
1467
  overlay?: boolean;
1467
1468
  /** signal binding: stroke-width template (CAR edge-signal-binding) */
@@ -1573,7 +1574,7 @@ export type DiagramBuilder = {
1573
1574
  tailLabel?: string;
1574
1575
  labelOffsetX?: number;
1575
1576
  labelOffsetY?: number;
1576
- routing?: "default" | "back-detour";
1577
+ routing?: EdgeRouting;
1577
1578
  /** CAR-549 SSOT ... true で label を edge path 上に重ねる (flowchart condition label 用) */
1578
1579
  overlay?: boolean;
1579
1580
  }>,
package/src/index.ts CHANGED
@@ -312,6 +312,7 @@ export type {
312
312
  EdgeHead,
313
313
  EdgeHeadFill,
314
314
  EdgeReveal,
315
+ EdgeRouting,
315
316
  RelationFocus,
316
317
  RowMark,
317
318
  EdgeStyle,
@@ -75,24 +75,37 @@ export function DynShapeNode({
75
75
  }
76
76
  })();
77
77
 
78
- // 名前と値は箱の外に置く (#925)。 中に置くと、内側の塗りが値で動くたびに文字と重なる。
79
- //
80
- // **差し込みはここで通さない**。 `render/nodes.tsx` が `{名前}` を解決した節を渡してくるので、
81
- // ここで通しても何も変わらない (実測 = 通さない形に変えても検査が 1 件も落ちなかった)。
82
- // しかも向こうは値ごとの呼び名の表を持っており、ここには無い = 二重に通すと、
83
- // 呼び名を持つ図だけ結果が変わる余地を作る。
78
+ const 揃え = 箱いっぱいに描く(shape.kind) ? "start" : "middle";
79
+ return renderShapeWithOutsideLabels(形, node, 揃え);
80
+ }
81
+
82
+ /**
83
+ * 名前と値は箱の外に置く (#925)。 中に置くと、内側の塗りが値で動くたびに文字と重なる。
84
+ * 駅は塗りが動かないが、丸が小さく名前が丸より広いので外に置く。
85
+ *
86
+ * **差し込みはここで通さない**。 `render/nodes.tsx` が `{名前}` を解決した節を渡してくるので、
87
+ * ここで通しても何も変わらない (実測 = 通さない形に変えても検査が 1 件も落ちなかった)。
88
+ * しかも向こうは値ごとの呼び名の表を持っており、ここには無い = 二重に通すと、
89
+ * 呼び名を持つ図だけ結果が変わる余地を作る。
90
+ */
91
+ export function renderShapeWithOutsideLabels(
92
+ 形: JSX.Element,
93
+ node: LaidNode,
94
+ 揃え: "start" | "middle",
95
+ ): JSX.Element {
96
+ const cx = node.cx;
97
+ const y = node.cy - node.h / 2;
84
98
  const 名前 = (node.title ?? "").trim();
85
99
  const 値 = (node.subtitle ?? "").trim();
86
100
  // どちらも書かれていない節では包む `<g>` も足さない = 書かない図の DOM を 1 要素も変えない
87
101
  if (名前 === "" && 値 === "") return 形;
88
- const 揃え = 箱いっぱいに描く(shape.kind) ? "start" : "middle";
89
102
  return (
90
103
  <g>
91
104
  {形}
92
105
  {名前 !== "" && (
93
106
  <text
94
107
  data-cdl-role="shape-name"
95
- x={揃え === "start" ? x : cx}
108
+ x={揃え === "start" ? node.cx - node.w / 2 : cx}
96
109
  y={y - NAME_GAP}
97
110
  textAnchor={揃え}
98
111
  fontSize={13}
@@ -106,7 +119,7 @@ export function DynShapeNode({
106
119
  <text
107
120
  data-cdl-role="shape-value"
108
121
  x={cx}
109
- y={y + h + VALUE_GAP}
122
+ y={y + node.h + VALUE_GAP}
110
123
  textAnchor="middle"
111
124
  fontSize={12}
112
125
  fill="var(--cdl-text-dim, #5a6270)"
@@ -0,0 +1,29 @@
1
+ import type { JSX } from "react";
2
+
3
+ import type { LaidNode } from "../types";
4
+ import { renderShapeWithOutsideLabels } from "./dyn-shape";
5
+ import { nodeAccent } from "./node-tone";
6
+
7
+ const STATION_STROKE_ACTIVE = 4;
8
+ const STATION_STROKE_IDLE = 3;
9
+ const STATION_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
10
+
11
+ /** 路線図の線路上に置く駅。名前と説明は丸を隠さないよう外側に置く。 */
12
+ export function StationNode({ node, active }: { node: LaidNode; active: boolean }): JSX.Element {
13
+ const strokeWidth = active ? STATION_STROKE_ACTIVE : STATION_STROKE_IDLE;
14
+ // 線は箱の辺で止まるため、枠線の外縁を箱の辺へ合わせると線路と丸の間が空かない。
15
+ const radius = Math.max(0, Math.min(node.w, node.h) / 2 - strokeWidth / 2);
16
+ const 形 = (
17
+ <circle
18
+ data-cdl-role="node-body"
19
+ data-cdl-mark="station"
20
+ cx={node.cx}
21
+ cy={node.cy}
22
+ r={radius}
23
+ fill="var(--cdl-node-fill, #ffffff)"
24
+ stroke={nodeAccent(node, STATION_DEFAULT)}
25
+ strokeWidth={strokeWidth}
26
+ />
27
+ );
28
+ return renderShapeWithOutsideLabels(形, node, "middle");
29
+ }
@@ -688,7 +688,7 @@ export function detectCollisions(
688
688
  /**
689
689
  * path d 文字列を、 描画する線分の列に分解
690
690
  *
691
- * 曲線 (Q / C) は端点を結ぶ弦に落とす。 交差判定には十分だが、 弦は実際の曲線が通る場所とは
691
+ * 曲線 (Q / C / A) は端点を結ぶ弦に落とす。 交差判定には十分だが、 弦は実際の曲線が通る場所とは
692
692
  * ずれる。 「この線分は本当に真っ直ぐか」 を知る必要がある呼出側のために、 由来の command
693
693
  * (`cmd`) と制御点 (`ctrl`、 曲線のみ) を併せて返す。 弦を直線として扱うと、 端点が同じ高さで
694
694
  * 大きく膨らむ曲線を「水平な直線」 と誤認する。
@@ -700,7 +700,7 @@ export function extractPathSegments(
700
700
  y1: number;
701
701
  x2: number;
702
702
  y2: number;
703
- cmd: "L" | "Q" | "C";
703
+ cmd: "L" | "Q" | "C" | "A";
704
704
  ctrl?: Array<{ x: number; y: number }>;
705
705
  }> {
706
706
  const segs: Array<{
@@ -708,10 +708,10 @@ export function extractPathSegments(
708
708
  y1: number;
709
709
  x2: number;
710
710
  y2: number;
711
- cmd: "L" | "Q" | "C";
711
+ cmd: "L" | "Q" | "C" | "A";
712
712
  ctrl?: Array<{ x: number; y: number }>;
713
713
  }> = [];
714
- const commandRegex = /([MLCQZmlcqz])([^MLCQZmlcqz]*)/g;
714
+ const commandRegex = /([MLCQAZmlcqaz])([^MLCQAZmlcqaz]*)/g;
715
715
  let cur: { x: number; y: number } = { x: 0, y: 0 };
716
716
  let m: RegExpExecArray | null;
717
717
  while ((m = commandRegex.exec(d)) !== null) {
@@ -719,7 +719,7 @@ export function extractPathSegments(
719
719
  const args = (m[2] ?? "").trim().split(/[\s,]+/).filter(Boolean).map(parseFloat);
720
720
  // 引数が足りない command は読み飛ばす。 `args[n]!` で埋めると undefined が座標に入り、
721
721
  // 以降の segment が全て NaN になる = 距離判定が黙って対象外になる (#370)。
722
- const need = cmd === "M" || cmd === "L" ? 2 : cmd === "Q" ? 4 : cmd === "C" ? 6 : 0;
722
+ const need = cmd === "M" || cmd === "L" ? 2 : cmd === "Q" ? 4 : cmd === "C" ? 6 : cmd === "A" ? 7 : 0;
723
723
  if (need > 0 && (args.length < need || !args.slice(0, need).every(Number.isFinite))) continue;
724
724
  if (cmd === "M") {
725
725
  cur = { x: args[0]!, y: args[1]! };
@@ -741,13 +741,17 @@ export function extractPathSegments(
741
741
  ctrl: [{ x: args[0]!, y: args[1]! }, { x: args[2]!, y: args[3]! }],
742
742
  });
743
743
  cur = next;
744
+ } else if (cmd === "A") {
745
+ const next = { x: args[5]!, y: args[6]! };
746
+ segs.push({ x1: cur.x, y1: cur.y, x2: next.x, y2: next.y, cmd: "A" });
747
+ cur = next;
744
748
  }
745
749
  }
746
750
  return segs;
747
751
  }
748
752
 
749
753
  /**
750
- * 曲線 (Q / C) を、 実際に描かれる線に沿った折れ線に開く。
754
+ * 曲線 (Q / C) を、 実際に描かれる線に沿った折れ線に開く。円弧 (A) は弦のまま返す。
751
755
  *
752
756
  * `extractPathSegments` は曲線を端点どうしの弦に落とす。 交差判定はそれで足りるが、
753
757
  * 「線からどれだけ離れているか」 を測る用途では弦は使えない = 弦は描かれていないため、
@@ -788,7 +792,7 @@ export function extractPathSegments(
788
792
  * 無い形を勝手に作ることになる。
789
793
  *
790
794
  * @param segs `extractPathSegments()` の出力
791
- * @returns 直線のみからなる segment 列。 `L` はそのまま。 曲線は `FLATTEN_MIN_DIVISIONS` 以上
795
+ * @returns 直線のみからなる segment 列。 `L` と `A` の弦はそのまま。`Q` / `C` は `FLATTEN_MIN_DIVISIONS` 以上
792
796
  * `FLATTEN_MAX_DIVISIONS` 以下の本数に開く。 誤差が `FLATTEN_TOLERANCE` 以下に収まるのは、
793
797
  * 必要な分割数が上限以下に収まる曲線 (`dev ≤ 4 · 許容差 · 上限²`) に限る。 上限を超える
794
798
  * 曲線では保証されず、 許容差を超えることがある (幅 4e7 の対称な弧では式の上限が 0.596
@@ -804,7 +808,7 @@ export function flattenPathSegments(
804
808
  y1: number;
805
809
  x2: number;
806
810
  y2: number;
807
- cmd: "L" | "Q" | "C";
811
+ cmd: "L" | "Q" | "C" | "A";
808
812
  ctrl?: ReadonlyArray<{ x: number; y: number }>;
809
813
  }>,
810
814
  ): Array<{ x1: number; y1: number; x2: number; y2: number }> {
@@ -2357,16 +2361,17 @@ export function resolveEdgeLabelOverlapsWithChainAndPropagate(
2357
2361
  }
2358
2362
 
2359
2363
  /** 札の逃がし先を測るのに使う、 線の区間と外接矩形。 同じ図の中で使い回す (#810)。 */
2360
- interface 線の測り方 {
2364
+ export interface 線の測り方 {
2361
2365
  readonly 区間: (id: string, d: string) => Seg[];
2362
2366
  readonly 遠い: (box: BBox, edgeId: string, d: string, 余裕: number) => boolean;
2363
2367
  }
2364
2368
 
2365
2369
  /**
2366
- * 線の測り方を作る。 逃がし pass と、 門が「なぜ動かせなかったか」 を説明する側
2367
- * (`札が動けない理由`) の両方が使う = 2 箇所で別に書くと、 片方だけ直してずれる。
2370
+ * 線の測り方を作る。 逃がし pass、 門が「なぜ動かせなかったか」 を説明する側
2371
+ * (`札が動けない理由`)、 終点をずらした線の札の追従 (`followLabelsToFannedTails`、 #1002) が
2372
+ * 同じ測り方を引く = どれかに別の測り方を書くと、 置けるかの判断がずれる。
2368
2373
  */
2369
- function 線の測り方を作る(): 線の測り方 {
2374
+ export function 線の測り方を作る(): 線の測り方 {
2370
2375
  /*
2371
2376
  * 線の区間は **曲線を開いた形** で持つ (`detectCollisions` の札 branch と同じ)。
2372
2377
  *
@@ -2453,7 +2458,7 @@ export interface 札の移動を塞ぐ相手 {
2453
2458
  }
2454
2459
 
2455
2460
  /** 札を動かす判定に使う、 図の中の相手 */
2456
- interface 札の周り {
2461
+ export interface 札の周り {
2457
2462
  readonly 箱たち: readonly BBox[];
2458
2463
  readonly labels: readonly BBox[];
2459
2464
  readonly edges: readonly LaidEdge[];
@@ -2464,10 +2469,11 @@ interface 札の周り {
2464
2469
  /**
2465
2470
  * 札 1 つを動かした先が、 いまより近づく相手を作らないか。 作るなら最初の 1 つを返す。
2466
2471
  *
2467
- * 逃がし pass の `安全か` と、 門の説明 (`札が動けない理由`) が同じものを引く (#810)。
2468
- * 2 箇所で別に書くと、 「pass は動かせないのに説明は動かせると言う」 ずれが生まれる。
2472
+ * 逃がし pass の `安全か`、 門の説明 (`札が動けない理由`)、 終点をずらした線の札の追従
2473
+ * (`followLabelsToFannedTails`、 #1002) が同じものを引く (#810)。 別に書くと、 追従で置いた札を
2474
+ * 逃がし pass が押し直すか、 「pass は動かせないのに説明は動かせると言う」 ずれが生まれる。
2469
2475
  */
2470
- function 札の移動を塞ぐ相手を探す(
2476
+ export function 札の移動を塞ぐ相手を探す(
2471
2477
  周り: 札の周り,
2472
2478
  label: BBox,
2473
2479
  行き先: BBox,
@@ -2649,10 +2655,14 @@ export function 札が動けない理由(
2649
2655
  *
2650
2656
  * ## なぜ最終 pass が要るか
2651
2657
  *
2652
- * `resolveEdgeLabelOverlapsWithChainAndPropagate` は経路を動かす 4 つの pass
2658
+ * `resolveEdgeLabelOverlapsWithChainAndPropagate` の chain 伝搬後に、 経路を動かす 4 つの pass
2653
2659
  * (`bowSharedStraightPaths` / `pullOutgoingFirstCorner` / `fanIncomingEndpoints` /
2654
- * `separateSharedStraightRuns`) の **前** に走る。 札の初期位置は経路から決まるため、
2655
- * 後の pass が経路を動かすと札も一緒に動く。 動いた後を見る者が誰も居なかった。
2660
+ * `separateSharedStraightRuns`) が走る。 4 つとも経路を動かすが、 札も動かすのは
2661
+ * `pullOutgoingFirstCorner` だけで、 動かす区間に一番近い札に限る (`edges.ts` の `labelも動かす`)。
2662
+ *
2663
+ * `bowSharedStraightPaths` と `separateSharedStraightRuns` は札を動かさず、 `fanIncomingEndpoints` が
2664
+ * 残した札は `followLabelsToFannedTails` が追従させる。 追従も `札の移動を塞ぐ相手を探す` で
2665
+ * 置けるかを決める = 別の判定で余白を割る位置へ置くと、 この pass が同じ札を押し直す (#1002)。
2656
2666
  *
2657
2667
  * 0.30.0 で `pullOutgoingFirstCorner` (#651) と `fanIncomingEndpoints` (#649) が経路を
2658
2668
  * 大きく動かすようになり、 消費側の見本帳で札が 11 件動いた (実測 = 最大 217 world)。
@@ -2886,4 +2896,3 @@ function applyEdgeLabelAndPathShifts(
2886
2896
  return next;
2887
2897
  });
2888
2898
  }
2889
-
@@ -0,0 +1,125 @@
1
+ import type { CdlEdge, LaidNode, Side } from "../types";
2
+ import { computeLabelBoxW, EDGE_STUB_OUT, labelPathClearanceHorizontal, labelPillH } from "./spec";
3
+
4
+ type 箱 = Pick<LaidNode, "cx" | "cy" | "w" | "h">;
5
+
6
+ type 曲線の結果 = {
7
+ d: string;
8
+ fromSide: Side;
9
+ toSide: Side;
10
+ labelX: number;
11
+ labelY: number;
12
+ labelAnchor: "middle";
13
+ };
14
+
15
+ /** `routePath` が横の直線を選ぶ値と揃え、既存の直線判定との境を増やさない。 */
16
+ export const SAME_HEIGHT_THRESHOLD = 1;
17
+
18
+ /**
19
+ * 2 つの箱を、両端で水平になる線で結ぶ。
20
+ *
21
+ * 行き先が左右へ離れていれば向かい合う左右の辺を使い、横の範囲が重なる時は両方の右辺を
22
+ * 使う。 2 点が同じ高さなら曲線にしても形が変わらないため、余分な制御点を持たない直線にする。
23
+ * 張り出しには `EDGE_STUB_OUT` の下限を置く。 制御点が端点に重なって接線が縦へ倒れるのを防ぎ、
24
+ * 箱を出てすぐ曲がらないことで、どの箱から出た線かを読めるようにするため。
25
+ *
26
+ * 札は曲線の中点から接線の法線方向へ離す。 線に沿ってずらすより、線と札の間の隙間を直接
27
+ * 保てるため。 この道筋は書き手が形を選ぶためのもので、途中の箱は避けない。
28
+ */
29
+ export function curvePath(
30
+ from: 箱,
31
+ to: 箱,
32
+ edge?: Pick<CdlEdge, "label" | "sub" | "style" | "overlay">,
33
+ labelMarginExtra = 0,
34
+ ): 曲線の結果 {
35
+ const fromLeft = from.cx - from.w / 2;
36
+ const fromRight = from.cx + from.w / 2;
37
+ const toLeft = to.cx - to.w / 2;
38
+ const toRight = to.cx + to.w / 2;
39
+
40
+ let fromSide: Side;
41
+ let toSide: Side;
42
+ let x1: number;
43
+ let x2: number;
44
+
45
+ if (toLeft >= fromRight) {
46
+ fromSide = "right";
47
+ toSide = "left";
48
+ x1 = fromRight;
49
+ x2 = toLeft;
50
+ } else if (toRight <= fromLeft) {
51
+ fromSide = "left";
52
+ toSide = "right";
53
+ x1 = fromLeft;
54
+ x2 = toRight;
55
+ } else {
56
+ fromSide = "right";
57
+ toSide = "right";
58
+ x1 = fromRight;
59
+ x2 = toRight;
60
+ }
61
+
62
+ const y1 = from.cy;
63
+ const y2 = to.cy;
64
+ const sameHeight = Math.abs(y2 - y1) < SAME_HEIGHT_THRESHOLD;
65
+
66
+ let d: string;
67
+ let midX: number;
68
+ let midY: number;
69
+ let tangentX: number;
70
+ let tangentY: number;
71
+
72
+ if (sameHeight) {
73
+ d = `M ${x1} ${y1} L ${x2} ${y2}`;
74
+ midX = (x1 + x2) / 2;
75
+ midY = (y1 + y2) / 2;
76
+ tangentX = x2 - x1;
77
+ tangentY = y2 - y1;
78
+ } else {
79
+ let c1x: number;
80
+ let c2x: number;
81
+ if (fromSide !== toSide) {
82
+ const direction = x2 > x1 ? 1 : -1;
83
+ const bulge = Math.max(Math.abs(x2 - x1) / 2, EDGE_STUB_OUT);
84
+ c1x = x1 + direction * bulge;
85
+ c2x = x2 - direction * bulge;
86
+ } else {
87
+ const bulgeX = Math.max(x1, x2) + EDGE_STUB_OUT;
88
+ c1x = bulgeX;
89
+ c2x = bulgeX;
90
+ }
91
+ d = `M ${x1} ${y1} C ${c1x} ${y1}, ${c2x} ${y2}, ${x2} ${y2}`;
92
+ midX = (x1 + 3 * c1x + 3 * c2x + x2) / 8;
93
+ midY = (y1 + 3 * y1 + 3 * y2 + y2) / 8;
94
+ tangentX = x2 + c2x - c1x - x1;
95
+ tangentY = y2 + y2 - y1 - y1;
96
+ }
97
+
98
+ if (edge?.overlay) {
99
+ return { d, fromSide, toSide, labelX: midX, labelY: midY, labelAnchor: "middle" };
100
+ }
101
+
102
+ const tangentLength = Math.hypot(tangentX, tangentY);
103
+ let normalX = tangentY / tangentLength;
104
+ let normalY = -tangentX / tangentLength;
105
+ if (normalY > Number.EPSILON || (Math.abs(normalY) <= Number.EPSILON && normalX < 0)) {
106
+ normalX *= -1;
107
+ normalY *= -1;
108
+ }
109
+
110
+ const labelW = computeLabelBoxW(edge?.label, edge?.sub);
111
+ const labelH = labelPillH(edge?.sub);
112
+ const distance =
113
+ labelPathClearanceHorizontal(edge) +
114
+ (Math.abs(normalX) * labelW + Math.abs(normalY) * labelH) / 2 +
115
+ labelMarginExtra;
116
+
117
+ return {
118
+ d,
119
+ fromSide,
120
+ toSide,
121
+ labelX: midX + normalX * distance,
122
+ labelY: midY + normalY * distance,
123
+ labelAnchor: "middle",
124
+ };
125
+ }