@cardenelabs/cdl 0.60.0 → 0.62.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.
@@ -769,6 +769,17 @@ type CdlState = {
769
769
  id: string;
770
770
  initial: number | string;
771
771
  };
772
+ /**
773
+ * 選択肢を持つ入力欄 (`dropdown` / `radio` / `tabs` / `multi-select`) の 1 つの選択肢 (#855)。
774
+ *
775
+ * 文字列は値と見せる字を兼ねる。 組は `value` を入力欄の値にし、`label` を操作部の選択肢と
776
+ * 箱の字の差し込み (`{id}`) に描く。 値は綴りのまま残るので、綴りで読む読み出し (信号の灯り等) と
777
+ * 表示の出し分け (`visibleIf`) は名前に影響されない。
778
+ */
779
+ type CdlInputOption = string | {
780
+ value: string;
781
+ label: string;
782
+ };
772
783
  /**
773
784
  * interactive input widget spec (v0.5+、 CAR #243)。
774
785
  *
@@ -801,8 +812,8 @@ type CdlInput = {
801
812
  } | {
802
813
  id: string;
803
814
  kind: "dropdown";
804
- /** 選択肢一覧 (非空必須、 defaultValue はこの中の 1 つ) */
805
- options: readonly string[];
815
+ /** 選択肢一覧 (非空必須、 defaultValue はこの中の 1 つの値) */
816
+ options: readonly CdlInputOption[];
806
817
  defaultValue: string;
807
818
  label?: string;
808
819
  } | {
@@ -810,6 +821,10 @@ type CdlInput = {
810
821
  kind: "toggle";
811
822
  defaultValue: boolean;
812
823
  label?: string;
824
+ /** 入の時に箱の字の差し込み (`{id}`) へ描く名前。 省略時は `true` (#855) */
825
+ onLabel?: string;
826
+ /** 切の時に箱の字の差し込み (`{id}`) へ描く名前。 省略時は `false` (#855) */
827
+ offLabel?: string;
813
828
  }
814
829
  /**
815
830
  * XY pad = 2 軸 slider、 `.input.xypad(id)` は 実は 2 個の signal (id_x / id_y) を生成する
@@ -841,7 +856,7 @@ type CdlInput = {
841
856
  | {
842
857
  id: string;
843
858
  kind: "radio";
844
- options: readonly string[];
859
+ options: readonly CdlInputOption[];
845
860
  defaultValue: string;
846
861
  label?: string;
847
862
  }
@@ -867,7 +882,7 @@ type CdlInput = {
867
882
  | {
868
883
  id: string;
869
884
  kind: "multi-select";
870
- options: readonly string[];
885
+ options: readonly CdlInputOption[];
871
886
  defaultValues: readonly string[];
872
887
  label?: string;
873
888
  }
@@ -875,7 +890,7 @@ type CdlInput = {
875
890
  | {
876
891
  id: string;
877
892
  kind: "tabs";
878
- options: readonly string[];
893
+ options: readonly CdlInputOption[];
879
894
  defaultValue: string;
880
895
  label?: string;
881
896
  }
@@ -2999,4 +3014,4 @@ type CdlDiagramViewProps = {
2999
3014
  };
3000
3015
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
3001
3016
 
3002
- export { computeInputSignalKey as $, EDGE_HEAD_FILLS as A, type BBox as B, type CdlDiagram as C, EDGE_HEAD_FILL_DEFAULT as D, type EdgeStyle as E, EDGE_REVEALS as F, EDGE_REVEAL_DEFAULT as G, EDGE_STYLES as H, type EdgeReveal as I, type JourneyEmotion as J, type InteractiveHandlerMap as K, type LaidDiagram as L, type InteractiveSignalsAccessor as M, type NodeKind as N, type LaidLane as O, NODE_KINDS as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, RELATION_FOCUSES as U, RELATION_FOCUS_DEFAULT as V, type RelationFocus as W, type ScrollProgressHandle as X, TONES as Y, computeEventBindingKey as Z, computeFormulaKey as _, type CdlInput as a, computeScrollTriggerKey as a0, computed as a1, createInteractiveSignalsAccessor as a2, createScrollProgressHandle as a3, createScrollProgressSignals as a4, hasInteractivePrimitives as a5, resolveLang as a6, sequenceBoardMetrics as a7, sequenceStepId as a8, signal as a9, 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 CdlEventTarget as i, type CdlLang as j, type Computed as k, type CdlDerivedValue as l, type CdlScrollTrigger as m, type CdlDiagramViewProps as n, type LaidEdge as o, type LaidNode as p, type CdlEdge as q, CdlDiagramView as r, type CdlEventBinding as s, type CdlEventKind as t, type CdlFormula as u, type CdlPhase as v, type ChartPieForm as w, type ChartSlopeForm as x, EDGE_HEADS as y, EDGE_HEAD_DEFAULT as z };
3017
+ export { computeFormulaKey as $, EDGE_HEAD_DEFAULT as A, type BBox as B, type CdlDiagram as C, EDGE_HEAD_FILLS as D, type EdgeStyle as E, EDGE_HEAD_FILL_DEFAULT as F, EDGE_REVEALS as G, EDGE_REVEAL_DEFAULT as H, EDGE_STYLES as I, type JourneyEmotion as J, type EdgeReveal as K, type LaidDiagram as L, type InteractiveHandlerMap as M, type NodeKind as N, type InteractiveSignalsAccessor as O, type LaidLane as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, NODE_KINDS as U, RELATION_FOCUSES as V, RELATION_FOCUS_DEFAULT as W, type RelationFocus as X, type ScrollProgressHandle as Y, TONES as Z, computeEventBindingKey as _, type CdlInput as a, computeInputSignalKey as a0, computeScrollTriggerKey as a1, computed as a2, createInteractiveSignalsAccessor as a3, createScrollProgressHandle as a4, createScrollProgressSignals as a5, hasInteractivePrimitives as a6, resolveLang as a7, sequenceBoardMetrics as a8, sequenceStepId as a9, signal as aa, 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, CdlDiagramView as s, type CdlEventBinding as t, type CdlEventKind as u, type CdlFormula as v, type CdlPhase as w, type ChartPieForm as x, type ChartSlopeForm as y, EDGE_HEADS as z };
@@ -769,6 +769,17 @@ type CdlState = {
769
769
  id: string;
770
770
  initial: number | string;
771
771
  };
772
+ /**
773
+ * 選択肢を持つ入力欄 (`dropdown` / `radio` / `tabs` / `multi-select`) の 1 つの選択肢 (#855)。
774
+ *
775
+ * 文字列は値と見せる字を兼ねる。 組は `value` を入力欄の値にし、`label` を操作部の選択肢と
776
+ * 箱の字の差し込み (`{id}`) に描く。 値は綴りのまま残るので、綴りで読む読み出し (信号の灯り等) と
777
+ * 表示の出し分け (`visibleIf`) は名前に影響されない。
778
+ */
779
+ type CdlInputOption = string | {
780
+ value: string;
781
+ label: string;
782
+ };
772
783
  /**
773
784
  * interactive input widget spec (v0.5+、 CAR #243)。
774
785
  *
@@ -801,8 +812,8 @@ type CdlInput = {
801
812
  } | {
802
813
  id: string;
803
814
  kind: "dropdown";
804
- /** 選択肢一覧 (非空必須、 defaultValue はこの中の 1 つ) */
805
- options: readonly string[];
815
+ /** 選択肢一覧 (非空必須、 defaultValue はこの中の 1 つの値) */
816
+ options: readonly CdlInputOption[];
806
817
  defaultValue: string;
807
818
  label?: string;
808
819
  } | {
@@ -810,6 +821,10 @@ type CdlInput = {
810
821
  kind: "toggle";
811
822
  defaultValue: boolean;
812
823
  label?: string;
824
+ /** 入の時に箱の字の差し込み (`{id}`) へ描く名前。 省略時は `true` (#855) */
825
+ onLabel?: string;
826
+ /** 切の時に箱の字の差し込み (`{id}`) へ描く名前。 省略時は `false` (#855) */
827
+ offLabel?: string;
813
828
  }
814
829
  /**
815
830
  * XY pad = 2 軸 slider、 `.input.xypad(id)` は 実は 2 個の signal (id_x / id_y) を生成する
@@ -841,7 +856,7 @@ type CdlInput = {
841
856
  | {
842
857
  id: string;
843
858
  kind: "radio";
844
- options: readonly string[];
859
+ options: readonly CdlInputOption[];
845
860
  defaultValue: string;
846
861
  label?: string;
847
862
  }
@@ -867,7 +882,7 @@ type CdlInput = {
867
882
  | {
868
883
  id: string;
869
884
  kind: "multi-select";
870
- options: readonly string[];
885
+ options: readonly CdlInputOption[];
871
886
  defaultValues: readonly string[];
872
887
  label?: string;
873
888
  }
@@ -875,7 +890,7 @@ type CdlInput = {
875
890
  | {
876
891
  id: string;
877
892
  kind: "tabs";
878
- options: readonly string[];
893
+ options: readonly CdlInputOption[];
879
894
  defaultValue: string;
880
895
  label?: string;
881
896
  }
@@ -2999,4 +3014,4 @@ type CdlDiagramViewProps = {
2999
3014
  };
3000
3015
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
3001
3016
 
3002
- export { computeInputSignalKey as $, EDGE_HEAD_FILLS as A, type BBox as B, type CdlDiagram as C, EDGE_HEAD_FILL_DEFAULT as D, type EdgeStyle as E, EDGE_REVEALS as F, EDGE_REVEAL_DEFAULT as G, EDGE_STYLES as H, type EdgeReveal as I, type JourneyEmotion as J, type InteractiveHandlerMap as K, type LaidDiagram as L, type InteractiveSignalsAccessor as M, type NodeKind as N, type LaidLane as O, NODE_KINDS as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, RELATION_FOCUSES as U, RELATION_FOCUS_DEFAULT as V, type RelationFocus as W, type ScrollProgressHandle as X, TONES as Y, computeEventBindingKey as Z, computeFormulaKey as _, type CdlInput as a, computeScrollTriggerKey as a0, computed as a1, createInteractiveSignalsAccessor as a2, createScrollProgressHandle as a3, createScrollProgressSignals as a4, hasInteractivePrimitives as a5, resolveLang as a6, sequenceBoardMetrics as a7, sequenceStepId as a8, signal as a9, 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 CdlEventTarget as i, type CdlLang as j, type Computed as k, type CdlDerivedValue as l, type CdlScrollTrigger as m, type CdlDiagramViewProps as n, type LaidEdge as o, type LaidNode as p, type CdlEdge as q, CdlDiagramView as r, type CdlEventBinding as s, type CdlEventKind as t, type CdlFormula as u, type CdlPhase as v, type ChartPieForm as w, type ChartSlopeForm as x, EDGE_HEADS as y, EDGE_HEAD_DEFAULT as z };
3017
+ export { computeFormulaKey as $, EDGE_HEAD_DEFAULT as A, type BBox as B, type CdlDiagram as C, EDGE_HEAD_FILLS as D, type EdgeStyle as E, EDGE_HEAD_FILL_DEFAULT as F, EDGE_REVEALS as G, EDGE_REVEAL_DEFAULT as H, EDGE_STYLES as I, type JourneyEmotion as J, type EdgeReveal as K, type LaidDiagram as L, type InteractiveHandlerMap as M, type NodeKind as N, type InteractiveSignalsAccessor as O, type LaidLane as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, NODE_KINDS as U, RELATION_FOCUSES as V, RELATION_FOCUS_DEFAULT as W, type RelationFocus as X, type ScrollProgressHandle as Y, TONES as Z, computeEventBindingKey as _, type CdlInput as a, computeInputSignalKey as a0, computeScrollTriggerKey as a1, computed as a2, createInteractiveSignalsAccessor as a3, createScrollProgressHandle as a4, createScrollProgressSignals as a5, hasInteractivePrimitives as a6, resolveLang as a7, sequenceBoardMetrics as a8, sequenceStepId as a9, signal as aa, 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, CdlDiagramView as s, type CdlEventBinding as t, type CdlEventKind as u, type CdlFormula as v, type CdlPhase as w, type ChartPieForm as x, type ChartSlopeForm as y, EDGE_HEADS as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.60.0",
3
+ "version": "0.62.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
@@ -1,4 +1,5 @@
1
1
  import { parseFormula } from "./formula";
2
+ import { 選択肢の値 } from "./input-option";
2
3
  import type { CdlLang } from "./lang";
3
4
  import type {
4
5
  CdlDiagram,
@@ -7,6 +8,7 @@ import type {
7
8
  CdlEventTarget,
8
9
  CdlFormula,
9
10
  CdlInput,
11
+ CdlInputOption,
10
12
  CdlLane,
11
13
  CdlNode,
12
14
  CdlPhase,
@@ -1284,11 +1286,11 @@ export type InputChain = {
1284
1286
  ) => DiagramBuilder;
1285
1287
  dropdown: (
1286
1288
  id: string,
1287
- opts: { options: readonly string[]; defaultValue: string; label?: string },
1289
+ opts: { options: readonly CdlInputOption[]; defaultValue: string; label?: string },
1288
1290
  ) => DiagramBuilder;
1289
1291
  toggle: (
1290
1292
  id: string,
1291
- opts: { defaultValue: boolean; label?: string },
1293
+ opts: { defaultValue: boolean; label?: string; onLabel?: string; offLabel?: string },
1292
1294
  ) => DiagramBuilder;
1293
1295
  /** 2 軸 slider = XY pad、 単一 id で pointer position を保持 (x/y は同 signal に格納) */
1294
1296
  xypad: (
@@ -1311,7 +1313,7 @@ export type InputChain = {
1311
1313
  /** 排他選択、 dropdown と同じ semantics だが視覚は横並び button */
1312
1314
  radio: (
1313
1315
  id: string,
1314
- opts: { options: readonly string[]; defaultValue: string; label?: string },
1316
+ opts: { options: readonly CdlInputOption[]; defaultValue: string; label?: string },
1315
1317
  ) => DiagramBuilder;
1316
1318
  /** hex color (#RRGGBB) picker、 SVG stroke / fill に signal 経由で bind */
1317
1319
  color: (
@@ -1339,11 +1341,11 @@ export type InputChain = {
1339
1341
  ) => DiagramBuilder;
1340
1342
  multiSelect: (
1341
1343
  id: string,
1342
- opts: { options: readonly string[]; defaultValues: readonly string[]; label?: string },
1344
+ opts: { options: readonly CdlInputOption[]; defaultValues: readonly string[]; label?: string },
1343
1345
  ) => DiagramBuilder;
1344
1346
  tabs: (
1345
1347
  id: string,
1346
- opts: { options: readonly string[]; defaultValue: string; label?: string },
1348
+ opts: { options: readonly CdlInputOption[]; defaultValue: string; label?: string },
1347
1349
  ) => DiagramBuilder;
1348
1350
  datetime: (
1349
1351
  id: string,
@@ -2008,9 +2010,10 @@ export function diagram(
2008
2010
  if (opts.options.length === 0) {
2009
2011
  throw new Error(`input.dropdown '${inputId}' の options が空です (最低 1 件必要)`);
2010
2012
  }
2011
- if (!opts.options.includes(opts.defaultValue)) {
2013
+ // 既定値は選択肢の値 (綴り) で比べる。 名前を渡すと入力欄に名前が入り、綴りで読む読み出しが読めない (#855)
2014
+ if (!opts.options.some((o) => 選択肢の値(o) === opts.defaultValue)) {
2012
2015
  throw new Error(
2013
- `input.dropdown '${inputId}' の defaultValue '${opts.defaultValue}' が options に含まれません (options: ${opts.options.join(", ")})`,
2016
+ `input.dropdown '${inputId}' の defaultValue '${opts.defaultValue}' が options の値に含まれません (options: ${opts.options.map(選択肢の値).join(", ")})`,
2014
2017
  );
2015
2018
  }
2016
2019
  inputs.push({
@@ -2028,6 +2031,9 @@ export function diagram(
2028
2031
  id: inputId,
2029
2032
  kind: "toggle",
2030
2033
  defaultValue: opts.defaultValue,
2034
+ // 名前を渡さない切り替えは変更前と同じ形に保つ (空の名前も渡さないのと同じ)
2035
+ ...(opts.onLabel ? { onLabel: opts.onLabel } : {}),
2036
+ ...(opts.offLabel ? { offLabel: opts.offLabel } : {}),
2031
2037
  label: opts.label,
2032
2038
  });
2033
2039
  return api;
@@ -2095,9 +2101,9 @@ export function diagram(
2095
2101
  if (opts.options.length === 0) {
2096
2102
  throw new Error(`input.radio '${inputId}' の options が空です`);
2097
2103
  }
2098
- if (!opts.options.includes(opts.defaultValue)) {
2104
+ if (!opts.options.some((o) => 選択肢の値(o) === opts.defaultValue)) {
2099
2105
  throw new Error(
2100
- `input.radio '${inputId}' の defaultValue '${opts.defaultValue}' が options に含まれません`,
2106
+ `input.radio '${inputId}' の defaultValue '${opts.defaultValue}' が options の値に含まれません`,
2101
2107
  );
2102
2108
  }
2103
2109
  inputs.push({
@@ -2183,8 +2189,8 @@ export function diagram(
2183
2189
  if (opts.options.length === 0) {
2184
2190
  throw new Error(`input.tabs '${inputId}' の options が空です`);
2185
2191
  }
2186
- if (!opts.options.includes(opts.defaultValue)) {
2187
- throw new Error(`input.tabs '${inputId}' の defaultValue が options に含まれません`);
2192
+ if (!opts.options.some((o) => 選択肢の値(o) === opts.defaultValue)) {
2193
+ throw new Error(`input.tabs '${inputId}' の defaultValue が options の値に含まれません`);
2188
2194
  }
2189
2195
  inputs.push({
2190
2196
  id: inputId,
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ export type { Signal, Computed } from "./reactive";
8
8
  // 追加、 consumer app 側 HTML widget と signal 経由で bind する。 cdl 自身は widget を描画せず metadata のみ。
9
9
  export { createInputSignals, inputDefaultValue } from "./input-signals";
10
10
  export type { InputChain } from "./builder";
11
- export type { CdlInput } from "./types";
11
+ export type { CdlInput, CdlInputOption } from "./types";
12
12
 
13
13
  // 描画側が書く固定の字の言語 (#847)。 図の `lang` に渡す値と、記法の文字列を言語に直す関数。
14
14
  // 辞書そのものは公開しない = 字の一覧は描画の実装に属し、利用側が辞書の中身に依存すると
@@ -0,0 +1,62 @@
1
+ import type { CdlInput, CdlInputOption } from "./types";
2
+
3
+ /**
4
+ * 入力欄の値と、画面に描く名前を分けて読む (#855)。
5
+ *
6
+ * 選択肢は文字列 (値と名前を兼ねる) か `{ value, label }` の組で書ける。 値は入力欄に入り、
7
+ * 綴りで読む読み出しと表示の出し分けが読む。 名前は操作部の選択肢と箱の字の差し込みにだけ描く。
8
+ */
9
+
10
+ /** 選択肢の値 (入力欄に入る綴り) */
11
+ export function 選択肢の値(option: CdlInputOption): string {
12
+ return typeof option === "string" ? option : option.value;
13
+ }
14
+
15
+ /** 選択肢の名前 (画面に描く字)。 名前が空なら値を描く */
16
+ export function 選択肢の名前(option: CdlInputOption): string {
17
+ return typeof option === "string" || option.label === "" ? 選択肢の値(option) : option.label;
18
+ }
19
+
20
+ /** 1 つの入力欄の値ごとの名前。 `複数` は値を `,` でつないで持つ入力欄 (`multi-select`) */
21
+ export type 入力欄の名前 = { readonly 名前: ReadonlyMap<string, string>; readonly 複数: boolean };
22
+
23
+ /** 入力欄の id → 値ごとの名前の表。 名前を持つ入力欄だけを載せる */
24
+ export type 値の名前の表 = ReadonlyMap<string, 入力欄の名前>;
25
+
26
+ /**
27
+ * 図の入力欄から、値ごとの名前の表を作る。
28
+ *
29
+ * 載せるのは名前を書いた値だけ。 名前を 1 つも持たない入力欄は表に載らないので、
30
+ * 名前を書かない図の差し込みは変更前と同じ字になる。
31
+ */
32
+ export function 値の名前の表を作る(inputs: readonly CdlInput[] | undefined): 値の名前の表 {
33
+ const 表 = new Map<string, 入力欄の名前>();
34
+ for (const input of inputs ?? []) {
35
+ const 名前 = new Map<string, string>();
36
+ if (input.kind === "dropdown" || input.kind === "radio" || input.kind === "tabs" || input.kind === "multi-select") {
37
+ for (const option of input.options) {
38
+ if (typeof option !== "string" && option.label !== "") 名前.set(option.value, option.label);
39
+ }
40
+ } else if (input.kind === "toggle") {
41
+ if (input.onLabel) 名前.set("true", input.onLabel);
42
+ if (input.offLabel) 名前.set("false", input.offLabel);
43
+ }
44
+ if (名前.size > 0) 表.set(input.id, { 名前, 複数: input.kind === "multi-select" });
45
+ }
46
+ return 表;
47
+ }
48
+
49
+ /**
50
+ * 差し込む値を名前で読む。 名前を持たない値は綴りのまま返す。
51
+ *
52
+ * `multi-select` の値は選んだ値を `,` でつないだ文字列 (`"a,b"`) なので、値ごとに名前へ置き換えて
53
+ * つなぎ直す。 つなぎの字は `,` のまま (図の言語を持たない差し込みで、特定の言語の読点を選ばない)。
54
+ * それ以外の入力欄は値全体を 1 つとして引く (値に `,` を含む選択肢を割らない)。
55
+ */
56
+ export function 値を名前で読む(入力欄: 入力欄の名前, 値: string): string {
57
+ if (!入力欄.複数) return 入力欄.名前.get(値) ?? 値;
58
+ return 値
59
+ .split(",")
60
+ .map((v) => 入力欄.名前.get(v) ?? v)
61
+ .join(",");
62
+ }
@@ -3,6 +3,7 @@ import type { JSX } from "react";
3
3
  import type { LaidNode, NodeKind } from "../types";
4
4
  import { toneColor } from "./node-tone";
5
5
  import { interpolate } from "../render/utils";
6
+ import type { 値の名前の表 } from "../input-option";
6
7
  import {
7
8
  genericTextCentered,
8
9
  genericTextShift,
@@ -235,11 +236,14 @@ export function GenericNode({
235
236
  active,
236
237
  value,
237
238
  stateValues,
239
+ 値の名前,
238
240
  }: {
239
241
  node: LaidNode;
240
242
  active: boolean;
241
243
  value: string | undefined;
242
244
  stateValues: Record<string, string>;
245
+ /** 入力欄の値ごとの名前 (#855)。 値と行の差し込みに使う */
246
+ 値の名前?: 値の名前の表;
243
247
  }): JSX.Element {
244
248
  const base = STYLES[node.kind] ?? FALLBACK;
245
249
  // `tone` 指定があれば kind の既定色を置き換える。 色を決める式は `node-tone.ts` に集約。
@@ -357,7 +361,7 @@ export function GenericNode({
357
361
  // 独自に書いていた間、 解決できない参照を空文字に潰していた = 書き間違いが
358
362
  // 「値が無い」 と区別できなかった。
359
363
  // 共通経路は解決できない参照を `{名前}` のまま残すので、 読み手が気付ける。
360
- const resolvedValue = value !== undefined ? interpolate(value, stateValues) : undefined;
364
+ const resolvedValue = value !== undefined ? interpolate(value, stateValues, 値の名前) : undefined;
361
365
 
362
366
  /*
363
367
  * shape 別の text 配置 (diamond / cloud は中心揃え、 rect 等は左上揃え)。
@@ -449,7 +453,7 @@ export function GenericNode({
449
453
  // rightColX (= 左列 max 幅 + gap) で右列開始 x を揃える (DB table / UML class 風)。
450
454
  // 旧実装は split(":") 単一 delimiter + 右 anchor (node 右端寄せ) で行毎にズレていた。
451
455
  // value と同じく engine 共通の経路で解決する (独自に書くと字種と既定値がずれる)
452
- const resolved = node.rows.map((row) => interpolate(row, stateValues));
456
+ const resolved = node.rows.map((row) => interpolate(row, stateValues, 値の名前));
453
457
  const splits = resolved.map(splitRow);
454
458
  // 左列は比例の書体で描く (`font-family` を指定していないので継承する)。 字数に一定を
455
459
  // 掛ける形では実物に届かず、 右列が左列の字に重なる (`#425`)。 字ごとの幅で見積もる。
package/src/lang.ts CHANGED
@@ -236,6 +236,18 @@ const en = {
236
236
  "readout.kanban.inprogress": "In Progress",
237
237
  /** `kanban-board` の列の見出し (値は `done` で読む) */
238
238
  "readout.kanban.done": "Done",
239
+ /** `status-timeline` の区間の字 (値は `active` で読む) */
240
+ "readout.statusTimeline.active": "ACTIVE",
241
+ /** `status-timeline` の区間の字 (値は `idle` で読む) */
242
+ "readout.statusTimeline.idle": "IDLE",
243
+ /** `status-timeline` の区間の字 (値は `error` で読む) */
244
+ "readout.statusTimeline.error": "ERROR",
245
+ /** `user-presence` の状態の字 (値は `online` で読む) */
246
+ "readout.presence.online": "online",
247
+ /** `user-presence` の状態の字 (値は `away` で読む) */
248
+ "readout.presence.away": "away",
249
+ /** `user-presence` の状態の字 (値は `offline` で読む) */
250
+ "readout.presence.offline": "offline",
239
251
  /** `traffic-light` の赤の灯の見出し (値は `red` で読む) */
240
252
  "readout.trafficLight.red": "red",
241
253
  /** `traffic-light` の黄の灯の見出し (値は `yellow` / `amber` で読む) */
@@ -355,6 +367,12 @@ const ja: Record<CdlTextKey, string> = {
355
367
  "readout.kanban.todo": "未着手",
356
368
  "readout.kanban.inprogress": "進行中",
357
369
  "readout.kanban.done": "完了",
370
+ "readout.statusTimeline.active": "稼働",
371
+ "readout.statusTimeline.idle": "待機",
372
+ "readout.statusTimeline.error": "異常",
373
+ "readout.presence.online": "オンライン",
374
+ "readout.presence.away": "離席",
375
+ "readout.presence.offline": "オフライン",
358
376
  "readout.trafficLight.red": "赤",
359
377
  "readout.trafficLight.yellow": "黄",
360
378
  "readout.trafficLight.green": "緑",
@@ -4,6 +4,7 @@ import { CDL_TEXT, resolveLang, 字を差し込む, type CdlLang, type CdlTextKe
4
4
  import { assertNever } from "../assert-never";
5
5
  import type { CdlDiagram, CdlInput, CdlReadout } from "../types";
6
6
  import { createInputSignals, inputDefaultValue } from "../input-signals";
7
+ import { 選択肢の値, 選択肢の名前 } from "../input-option";
7
8
  import { createFormulaComputeds } from "../formula";
8
9
  import { 式を名札で読む } from "../formula/parser";
9
10
  import {
@@ -18,6 +19,7 @@ import type { Signal, Computed } from "../reactive";
18
19
  import { effect } from "../reactive";
19
20
  import { textWidth } from "../kinds/text-width";
20
21
  import { 桁で割る, 桁で省く } from "../kinds/row-align";
22
+ import { 幅で切る } from "../kinds/box-lines";
21
23
 
22
24
  /**
23
25
  * interactive primitive (input widget / formula / scroll trigger / event handler) を SVG diagram に
@@ -781,9 +783,10 @@ function InputWidget({
781
783
  data-cdl-input={input.id}
782
784
  className="cdl-ip-dropdown-select"
783
785
  >
786
+ {/* 値は綴りのまま入力欄に入れ、字だけを選択肢の名前で描く (#855) */}
784
787
  {input.options.map((opt) => (
785
- <option key={opt} value={opt}>
786
- {opt}
788
+ <option key={選択肢の値(opt)} value={選択肢の値(opt)}>
789
+ {選択肢の名前(opt)}
787
790
  </option>
788
791
  ))}
789
792
  </select>
@@ -799,19 +802,19 @@ function InputWidget({
799
802
  <legend className="cdl-ip-label">{label}</legend>
800
803
  <div className="cdl-ip-radio-options">
801
804
  {input.options.map((opt) => (
802
- <label key={opt} className="cdl-ip-radio-option">
805
+ <label key={選択肢の値(opt)} className="cdl-ip-radio-option">
803
806
  <input
804
807
  type="radio"
805
808
  name={input.id}
806
- value={opt}
807
- checked={value === opt}
809
+ value={選択肢の値(opt)}
810
+ checked={value === 選択肢の値(opt)}
808
811
  onChange={() => {
809
- signal.value = opt;
812
+ signal.value = 選択肢の値(opt);
810
813
  }}
811
814
  data-cdl-input={input.id}
812
815
  className="cdl-ip-radio-input"
813
816
  />
814
- <span className="cdl-ip-radio-label">{opt}</span>
817
+ <span className="cdl-ip-radio-label">{選択肢の名前(opt)}</span>
815
818
  </label>
816
819
  ))}
817
820
  </div>
@@ -864,16 +867,16 @@ function InputWidget({
864
867
  <div className="cdl-ip-tabs-bar" role="tablist">
865
868
  {input.options.map((opt) => (
866
869
  <button
867
- key={opt}
870
+ key={選択肢の値(opt)}
868
871
  type="button"
869
872
  role="tab"
870
- className={`cdl-ip-tabs-btn ${value === opt ? "cdl-ip-tabs-active" : ""}`}
873
+ className={`cdl-ip-tabs-btn ${value === 選択肢の値(opt) ? "cdl-ip-tabs-active" : ""}`}
871
874
  onClick={() => {
872
- signal.value = opt;
875
+ signal.value = 選択肢の値(opt);
873
876
  }}
874
877
  data-cdl-input={input.id}
875
878
  >
876
- {opt}
879
+ {選択肢の名前(opt)}
877
880
  </button>
878
881
  ))}
879
882
  </div>
@@ -2983,14 +2986,14 @@ function MultiSelectWidget({
2983
2986
  <div className="cdl-ip-multi-select-chips">
2984
2987
  {input.options.map((opt) => (
2985
2988
  <button
2986
- key={opt}
2989
+ key={選択肢の値(opt)}
2987
2990
  type="button"
2988
- className={`cdl-ip-multi-select-chip ${selected.has(opt) ? "cdl-ip-multi-select-active" : ""}`}
2989
- onClick={() => toggle(opt)}
2991
+ className={`cdl-ip-multi-select-chip ${selected.has(選択肢の値(opt)) ? "cdl-ip-multi-select-active" : ""}`}
2992
+ onClick={() => toggle(選択肢の値(opt))}
2990
2993
  data-cdl-input={input.id}
2991
- data-cdl-multi-value={opt}
2994
+ data-cdl-multi-value={選択肢の値(opt)}
2992
2995
  >
2993
- {opt}
2996
+ {選択肢の名前(opt)}
2994
2997
  </button>
2995
2998
  ))}
2996
2999
  </div>
@@ -7732,12 +7735,24 @@ function StatusTimelineReadout({
7732
7735
  // silent skip
7733
7736
  }
7734
7737
  }
7738
+ const 引く = use字引();
7735
7739
  const colorLookup = new Map(colorMap.map((c) => [c.status.toLowerCase(), c.color]));
7736
7740
  const shown = events.slice(0, max);
7737
7741
  const stripHeight = 28;
7738
7742
  const width = 320;
7739
7743
  const cellW = shown.length > 0 ? (width - 20) / shown.length : 0;
7740
7744
  const height = stripHeight + 40;
7745
+ /*
7746
+ * 区間の字 (#857)。 既定の対応に並ぶ 3 つの値は辞書で引き、それ以外は値を大文字にする。
7747
+ * 色は値の綴りで選ぶので、辞書の字では引かない。
7748
+ *
7749
+ * 字は区間の幅に収まるだけ描く。 4 字で切っていた形は字数で幅を見積もっており、収まる幅があっても
7750
+ * `active` を `ACTI` と描き、和文の字には切る根拠が無かった (#851 と同じ理由)。
7751
+ */
7752
+ const 区間の字 = (status: string): string =>
7753
+ status === "active" || status === "idle" || status === "error"
7754
+ ? 引く(`readout.statusTimeline.${status}`)
7755
+ : status.toUpperCase();
7741
7756
  return (
7742
7757
  <div className="cdl-ip-item cdl-ip-readout-status-timeline" data-cdl-readout={id}>
7743
7758
  <span className="cdl-ip-label">{label}</span>
@@ -7750,8 +7765,11 @@ function StatusTimelineReadout({
7750
7765
  {shown.map((ev, i) => {
7751
7766
  const x = 10 + i * cellW;
7752
7767
  const c = colorLookup.get(ev.status) ?? "#cbd5e1";
7768
+ const 字 = 区間の字(ev.status);
7769
+ // 左右に 3px ずつ余白を取る (区間の枠は幅 - 2 で描く)
7770
+ const 描く字 = 幅で切る(字, 10, cellW - 8);
7753
7771
  return (
7754
- <g key={i} data-cdl-status-event={i}>
7772
+ <g key={i} data-cdl-status-event={i} data-cdl-status={ev.status}>
7755
7773
  <rect x={x} y={10} width={cellW - 2} height={stripHeight} fill={c} rx={2} />
7756
7774
  <text
7757
7775
  x={x + cellW / 2}
@@ -7762,7 +7780,8 @@ function StatusTimelineReadout({
7762
7780
  fontWeight={600}
7763
7781
  style={{ userSelect: "none" }}
7764
7782
  >
7765
- {ev.status.length > 5 ? ev.status.slice(0, 4).toUpperCase() : ev.status.toUpperCase()}
7783
+ {描く字 !== 字 ? <title>{字}</title> : null}
7784
+ {描く字}
7766
7785
  </text>
7767
7786
  <text
7768
7787
  x={x + cellW / 2}
@@ -8094,6 +8113,10 @@ function UserPresenceReadout({
8094
8113
  const width = 260;
8095
8114
  const height = shown.length * rowH + (overflow > 0 ? 20 : 0) + 8;
8096
8115
  const statusColor = (s: string) => (s === "online" ? "#22c55e" : s === "away" ? "#eab308" : "#94a3b8");
8116
+ const 引く = use字引();
8117
+ // 色を選ぶ 3 つの値は辞書で引き、それ以外は値のまま描く (#857)。 色は値の綴りで選ぶ
8118
+ const 状態の字 = (s: string): string =>
8119
+ s === "online" || s === "away" || s === "offline" ? 引く(`readout.presence.${s}`) : s;
8097
8120
  return (
8098
8121
  <div className="cdl-ip-item cdl-ip-readout-user-presence" data-cdl-readout={id}>
8099
8122
  <span className="cdl-ip-label">{label}</span>
@@ -8106,7 +8129,7 @@ function UserPresenceReadout({
8106
8129
  {shown.map((u, i) => {
8107
8130
  const y = 4 + i * rowH;
8108
8131
  return (
8109
- <g key={i} data-cdl-presence-user={i}>
8132
+ <g key={i} data-cdl-presence-user={i} data-cdl-presence-status={u.status}>
8110
8133
  <circle cx={12} cy={y + rowH / 2} r={6} fill={statusColor(u.status)} />
8111
8134
  <text
8112
8135
  x={26}
@@ -8126,7 +8149,7 @@ function UserPresenceReadout({
8126
8149
  fill="#64748b"
8127
8150
  style={{ userSelect: "none" }}
8128
8151
  >
8129
- {u.status}
8152
+ {状態の字(u.status)}
8130
8153
  </text>
8131
8154
  </g>
8132
8155
  );
@@ -89,6 +89,7 @@ import {
89
89
  import type { LaidNode } from "../types";
90
90
  import type { CdlLang } from "../lang";
91
91
  import { hasUnresolvedRef, interpolate, resolveNumericAttr } from "./utils";
92
+ import type { 値の名前の表 } from "../input-option";
92
93
 
93
94
  /**
94
95
  * 触れた箱の関係を光らせる時に、箱へ付ける受け口 (#806)。
@@ -117,9 +118,15 @@ export function CdlNodeView({
117
118
  chartLineScale,
118
119
  関係の受け口,
119
120
  lang,
121
+ 値の名前,
120
122
  }: {
121
123
  node: LaidNode;
122
124
  active: boolean;
125
+ /**
126
+ * 入力欄の値ごとの名前 (#855)。 箱の字の差し込みにだけ使い、表示の出し分けと数の差し込みには使わない。
127
+ * 渡さない呼び方は値を綴りのまま差し込む
128
+ */
129
+ 値の名前?: 値の名前の表;
123
130
  /** 描画側の固定の字の言語 (#847)。 字を持つ形の部品へ渡す。 渡さない呼び方は英語で描く */
124
131
  lang?: CdlLang;
125
132
  /**
@@ -134,8 +141,8 @@ export function CdlNodeView({
134
141
  /** 触れた箱の関係を光らせる時の受け口 (#806)。 書かない図では `undefined` */
135
142
  関係の受け口?: 関係の受け口;
136
143
  }): JSX.Element {
137
- const value = node.value ? interpolate(node.value, stateValues) : undefined;
138
- const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues));
144
+ const value = node.value ? interpolate(node.value, stateValues, 値の名前) : undefined;
145
+ const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues, 値の名前));
139
146
  // visual binding = wBind / hBind の template を stateValues で resolve、 layout の
140
147
  // static w/h を fallback として保持。 signal 値変化で node の描画寸法がリアルタイム変化する
141
148
  // (arc-intro 相当の 「slider で bar が伸びる」 UX の foundation)。
@@ -159,9 +166,9 @@ export function CdlNodeView({
159
166
  }
160
167
  const resolvedNode: LaidNode = {
161
168
  ...node,
162
- title: interpolate(node.title, stateValues),
163
- subtitle: node.subtitle ? interpolate(node.subtitle, stateValues) : node.subtitle,
164
- eyebrow: node.eyebrow ? interpolate(node.eyebrow, stateValues) : node.eyebrow,
169
+ title: interpolate(node.title, stateValues, 値の名前),
170
+ subtitle: node.subtitle ? interpolate(node.subtitle, stateValues, 値の名前) : node.subtitle,
171
+ eyebrow: node.eyebrow ? interpolate(node.eyebrow, stateValues, 値の名前) : node.eyebrow,
165
172
  w: boundW,
166
173
  h: boundH,
167
174
  };
@@ -442,7 +449,7 @@ export function CdlNodeView({
442
449
  return <MarkEndNode node={resolvedNode} active={active} />;
443
450
  default:
444
451
  // 新 NodeKind 25 種は GenericNode で汎用描画 (kind 別 icon / color / shape)
445
- return <GenericNode node={resolvedNode} active={active} value={node.value} stateValues={stateValues} />;
452
+ return <GenericNode node={resolvedNode} active={active} value={node.value} stateValues={stateValues} 値の名前={値の名前} />;
446
453
  }
447
454
  })();
448
455
  // node bounding box を node.w/h に固定するための invisible rect。