@cardenelabs/cdl 0.59.1 → 0.61.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 +48 -1
- package/dist/index.cjs +108 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -10
- package/dist/index.d.ts +32 -10
- package/dist/index.js +108 -46
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +94 -36
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +94 -36
- package/dist/react.js.map +1 -1
- package/dist/{render-DpwY2hzM.d.cts → render-Dk8_6ua6.d.cts} +28 -6
- package/dist/{render-DpwY2hzM.d.ts → render-Dk8_6ua6.d.ts} +28 -6
- package/package.json +1 -1
- package/src/builder.ts +30 -16
- package/src/formula/parser.ts +32 -3
- package/src/index.ts +1 -1
- package/src/input-option.ts +62 -0
- package/src/kinds/generic.tsx +6 -2
- package/src/render/interactive-panel.tsx +36 -21
- package/src/render/nodes.tsx +13 -6
- package/src/render/stage.tsx +5 -0
- package/src/render/utils.ts +14 -2
- package/src/render.tsx +6 -1
- package/src/types.ts +25 -5
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
893
|
+
options: readonly CdlInputOption[];
|
|
879
894
|
defaultValue: string;
|
|
880
895
|
label?: string;
|
|
881
896
|
}
|
|
@@ -2463,6 +2478,13 @@ type CdlFormula = {
|
|
|
2463
2478
|
id: string;
|
|
2464
2479
|
/** safe subset expression 文字列。 例 `"gasBase * (1 + delta/8)"` */
|
|
2465
2480
|
expression: string;
|
|
2481
|
+
/**
|
|
2482
|
+
* 操作部の式の行に描く名札 (#853)。 無ければ id を描く。
|
|
2483
|
+
*
|
|
2484
|
+
* 名札があると、式の中身の名前のうち名札を持つ入力欄と式の id も名札に置き換えて描く。
|
|
2485
|
+
* 置き換えは表示だけで、計算は `expression` を読む。
|
|
2486
|
+
*/
|
|
2487
|
+
label?: string;
|
|
2466
2488
|
};
|
|
2467
2489
|
/**
|
|
2468
2490
|
* event handler の bind target (CAR #246)。 node / edge / lane の id 指定 or diagram 全体 (root)。
|
|
@@ -2992,4 +3014,4 @@ type CdlDiagramViewProps = {
|
|
|
2992
3014
|
};
|
|
2993
3015
|
declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
|
|
2994
3016
|
|
|
2995
|
-
export {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
893
|
+
options: readonly CdlInputOption[];
|
|
879
894
|
defaultValue: string;
|
|
880
895
|
label?: string;
|
|
881
896
|
}
|
|
@@ -2463,6 +2478,13 @@ type CdlFormula = {
|
|
|
2463
2478
|
id: string;
|
|
2464
2479
|
/** safe subset expression 文字列。 例 `"gasBase * (1 + delta/8)"` */
|
|
2465
2480
|
expression: string;
|
|
2481
|
+
/**
|
|
2482
|
+
* 操作部の式の行に描く名札 (#853)。 無ければ id を描く。
|
|
2483
|
+
*
|
|
2484
|
+
* 名札があると、式の中身の名前のうち名札を持つ入力欄と式の id も名札に置き換えて描く。
|
|
2485
|
+
* 置き換えは表示だけで、計算は `expression` を読む。
|
|
2486
|
+
*/
|
|
2487
|
+
label?: string;
|
|
2466
2488
|
};
|
|
2467
2489
|
/**
|
|
2468
2490
|
* event handler の bind target (CAR #246)。 node / edge / lane の id 指定 or diagram 全体 (root)。
|
|
@@ -2992,4 +3014,4 @@ type CdlDiagramViewProps = {
|
|
|
2992
3014
|
};
|
|
2993
3015
|
declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
|
|
2994
3016
|
|
|
2995
|
-
export {
|
|
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.
|
|
3
|
+
"version": "0.61.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
|
|
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
|
|
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
|
|
1344
|
+
opts: { options: readonly CdlInputOption[]; defaultValues: readonly string[]; label?: string },
|
|
1343
1345
|
) => DiagramBuilder;
|
|
1344
1346
|
tabs: (
|
|
1345
1347
|
id: string,
|
|
1346
|
-
opts: { options: readonly
|
|
1348
|
+
opts: { options: readonly CdlInputOption[]; defaultValue: string; label?: string },
|
|
1347
1349
|
) => DiagramBuilder;
|
|
1348
1350
|
datetime: (
|
|
1349
1351
|
id: string,
|
|
@@ -1596,8 +1598,10 @@ export type DiagramBuilder = {
|
|
|
1596
1598
|
* consumer が createFormulaComputeds() で computed 化して signal と reactive bind する。
|
|
1597
1599
|
* expression syntax error は本 method 内で throw (fail-fast)。
|
|
1598
1600
|
* 例 `.formula("gasFee", "gasBase * (1 + delta/8)")`
|
|
1601
|
+
*
|
|
1602
|
+
* `opts.label` は操作部の式の行に描く名札 (#853)。 例 `.formula("total", "baseFee + tip", { label: "合計" })`
|
|
1599
1603
|
*/
|
|
1600
|
-
formula: (id: string, expression: string) => DiagramBuilder;
|
|
1604
|
+
formula: (id: string, expression: string, opts?: { label?: string }) => DiagramBuilder;
|
|
1601
1605
|
/**
|
|
1602
1606
|
* derive chain = 「連鎖形 formula」 を N 個宣言的に生成 (CAR: derive chain)。
|
|
1603
1607
|
* 各 iteration で `{i}` 置換と、 直前 iteration の id (`prev`) を参照可能。
|
|
@@ -1607,11 +1611,14 @@ export type DiagramBuilder = {
|
|
|
1607
1611
|
*
|
|
1608
1612
|
* baseId は「gas1 / gas2 / gas3」 のように `gas${i+1}` として使われる、
|
|
1609
1613
|
* expr fn は index (0..count-1) と prev id (前 iteration の完全 id) を受け取る。
|
|
1614
|
+
*
|
|
1615
|
+
* `opts.label` は index を受け取り、その式の名札を返す (#853)。 例 `{ label: (i) => \`${i + 1} 回目\` }`
|
|
1610
1616
|
*/
|
|
1611
1617
|
deriveChain: (
|
|
1612
1618
|
baseId: string,
|
|
1613
1619
|
count: number,
|
|
1614
1620
|
exprFn: (i: number, prev: string | null) => string,
|
|
1621
|
+
opts?: { label?: (i: number) => string },
|
|
1615
1622
|
) => DiagramBuilder;
|
|
1616
1623
|
/**
|
|
1617
1624
|
* animation primitive (CAR #245)。 `.animation.scroll(id, opts)` で scroll-driven trigger を追加。
|
|
@@ -2003,9 +2010,10 @@ export function diagram(
|
|
|
2003
2010
|
if (opts.options.length === 0) {
|
|
2004
2011
|
throw new Error(`input.dropdown '${inputId}' の options が空です (最低 1 件必要)`);
|
|
2005
2012
|
}
|
|
2006
|
-
|
|
2013
|
+
// 既定値は選択肢の値 (綴り) で比べる。 名前を渡すと入力欄に名前が入り、綴りで読む読み出しが読めない (#855)
|
|
2014
|
+
if (!opts.options.some((o) => 選択肢の値(o) === opts.defaultValue)) {
|
|
2007
2015
|
throw new Error(
|
|
2008
|
-
`input.dropdown '${inputId}' の defaultValue '${opts.defaultValue}' が options
|
|
2016
|
+
`input.dropdown '${inputId}' の defaultValue '${opts.defaultValue}' が options の値に含まれません (options: ${opts.options.map(選択肢の値).join(", ")})`,
|
|
2009
2017
|
);
|
|
2010
2018
|
}
|
|
2011
2019
|
inputs.push({
|
|
@@ -2023,6 +2031,9 @@ export function diagram(
|
|
|
2023
2031
|
id: inputId,
|
|
2024
2032
|
kind: "toggle",
|
|
2025
2033
|
defaultValue: opts.defaultValue,
|
|
2034
|
+
// 名前を渡さない切り替えは変更前と同じ形に保つ (空の名前も渡さないのと同じ)
|
|
2035
|
+
...(opts.onLabel ? { onLabel: opts.onLabel } : {}),
|
|
2036
|
+
...(opts.offLabel ? { offLabel: opts.offLabel } : {}),
|
|
2026
2037
|
label: opts.label,
|
|
2027
2038
|
});
|
|
2028
2039
|
return api;
|
|
@@ -2090,9 +2101,9 @@ export function diagram(
|
|
|
2090
2101
|
if (opts.options.length === 0) {
|
|
2091
2102
|
throw new Error(`input.radio '${inputId}' の options が空です`);
|
|
2092
2103
|
}
|
|
2093
|
-
if (!opts.options.
|
|
2104
|
+
if (!opts.options.some((o) => 選択肢の値(o) === opts.defaultValue)) {
|
|
2094
2105
|
throw new Error(
|
|
2095
|
-
`input.radio '${inputId}' の defaultValue '${opts.defaultValue}' が options
|
|
2106
|
+
`input.radio '${inputId}' の defaultValue '${opts.defaultValue}' が options の値に含まれません`,
|
|
2096
2107
|
);
|
|
2097
2108
|
}
|
|
2098
2109
|
inputs.push({
|
|
@@ -2178,8 +2189,8 @@ export function diagram(
|
|
|
2178
2189
|
if (opts.options.length === 0) {
|
|
2179
2190
|
throw new Error(`input.tabs '${inputId}' の options が空です`);
|
|
2180
2191
|
}
|
|
2181
|
-
if (!opts.options.
|
|
2182
|
-
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 の値に含まれません`);
|
|
2183
2194
|
}
|
|
2184
2195
|
inputs.push({
|
|
2185
2196
|
id: inputId,
|
|
@@ -2213,7 +2224,7 @@ export function diagram(
|
|
|
2213
2224
|
return api;
|
|
2214
2225
|
},
|
|
2215
2226
|
},
|
|
2216
|
-
formula(formulaId, expression) {
|
|
2227
|
+
formula(formulaId, expression, opts) {
|
|
2217
2228
|
assertUniqueFormulaId(formulaId);
|
|
2218
2229
|
// 入口で parse を試み、 syntax error は formula id 付きで throw (fail-fast)
|
|
2219
2230
|
try {
|
|
@@ -2222,10 +2233,11 @@ export function diagram(
|
|
|
2222
2233
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2223
2234
|
throw new Error(`formula '${formulaId}' の expression syntax error: ${msg}`);
|
|
2224
2235
|
}
|
|
2225
|
-
|
|
2236
|
+
// 名札の無い式は `label` を持たせない (空の文字列も持たせない = 名札の無い図の形を変えない)
|
|
2237
|
+
formulas.push({ id: formulaId, expression, ...(opts?.label ? { label: opts.label } : {}) });
|
|
2226
2238
|
return api;
|
|
2227
2239
|
},
|
|
2228
|
-
deriveChain(baseId, count, exprFn) {
|
|
2240
|
+
deriveChain(baseId, count, exprFn, opts) {
|
|
2229
2241
|
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
2230
2242
|
throw new Error(`deriveChain: count (${count}) は 1..100 の整数である必要があります`);
|
|
2231
2243
|
}
|
|
@@ -2242,7 +2254,9 @@ export function diagram(
|
|
|
2242
2254
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2243
2255
|
throw new Error(`deriveChain '${id}' の expression syntax error: ${msg}`);
|
|
2244
2256
|
}
|
|
2245
|
-
|
|
2257
|
+
const label = opts?.label?.(i);
|
|
2258
|
+
// 名札の無い式は `label` を持たせない (空の文字列も持たせない = 名札の無い図の形を変えない)
|
|
2259
|
+
formulas.push({ id, expression: expr, ...(label ? { label } : {}) });
|
|
2246
2260
|
prev = id;
|
|
2247
2261
|
}
|
|
2248
2262
|
return api;
|
package/src/formula/parser.ts
CHANGED
|
@@ -21,7 +21,8 @@ import { isValidTemplateName } from "../template-name";
|
|
|
21
21
|
|
|
22
22
|
type Token =
|
|
23
23
|
| { kind: "num"; value: number; pos: number }
|
|
24
|
-
|
|
24
|
+
/** `end` は字句の直後の位置。 `{name}` の形は閉じ括弧の直後 (表示用の置き換えが字句ごと差し替える) */
|
|
25
|
+
| { kind: "id"; name: string; pos: number; end: number }
|
|
25
26
|
| { kind: "math"; fn: MathFnName; pos: number }
|
|
26
27
|
| { kind: "op"; op: BinaryOp; pos: number }
|
|
27
28
|
| { kind: "lparen"; pos: number }
|
|
@@ -92,7 +93,7 @@ function tokenize(src: string): Token[] {
|
|
|
92
93
|
`invalid name "{${name}}" at position ${start} (空白 / { } / . / [ ] は名前に使えない)`,
|
|
93
94
|
);
|
|
94
95
|
}
|
|
95
|
-
tokens.push({ kind: "id", name, pos: start });
|
|
96
|
+
tokens.push({ kind: "id", name, pos: start, end: close + 1 });
|
|
96
97
|
i = close + 1;
|
|
97
98
|
continue;
|
|
98
99
|
}
|
|
@@ -156,7 +157,7 @@ function tokenize(src: string): Token[] {
|
|
|
156
157
|
`property access "${name}.${src[i + 1] ?? ""}..." at position ${start} is not allowed (only Math.* namespace is accessible)`,
|
|
157
158
|
);
|
|
158
159
|
}
|
|
159
|
-
tokens.push({ kind: "id", name, pos: start });
|
|
160
|
+
tokens.push({ kind: "id", name, pos: start, end: i });
|
|
160
161
|
continue;
|
|
161
162
|
}
|
|
162
163
|
// operators
|
|
@@ -401,6 +402,34 @@ export function parseFormula(expression: string): FormulaAst {
|
|
|
401
402
|
return new Parser(tokens).parse();
|
|
402
403
|
}
|
|
403
404
|
|
|
405
|
+
/**
|
|
406
|
+
* 式の中身の名前を、`名札` に載る名前だけ名札に置き換えた表示用の字を返す (#853)。 **計算には使わない**。
|
|
407
|
+
*
|
|
408
|
+
* 置き換えは字句の位置で行う。 文字列の検索で探すと、`gas1` の置き換えが `gas10` の先頭に当たり、
|
|
409
|
+
* 値の `min` の置き換えが `Math.min` の中に当たる。 関数の名前 (`Math.min` / 直後が `(` の `min`) と数は
|
|
410
|
+
* 名前の字句にならないので置き換わらない。 名札が空の文字列の名前は置き換えない。
|
|
411
|
+
*
|
|
412
|
+
* 字句に切れない式 (構文の誤り) は、元の字をそのまま返す。
|
|
413
|
+
*/
|
|
414
|
+
export function 式を名札で読む(expression: string, 名札: ReadonlyMap<string, string>): string {
|
|
415
|
+
let tokens: Token[];
|
|
416
|
+
try {
|
|
417
|
+
tokens = tokenize(expression);
|
|
418
|
+
} catch {
|
|
419
|
+
return expression;
|
|
420
|
+
}
|
|
421
|
+
let 読み = "";
|
|
422
|
+
let 前 = 0;
|
|
423
|
+
for (const t of tokens) {
|
|
424
|
+
if (t.kind !== "id") continue;
|
|
425
|
+
const 札 = 名札.get(t.name);
|
|
426
|
+
if (札 === undefined || 札 === "") continue;
|
|
427
|
+
読み += expression.slice(前, t.pos) + 札;
|
|
428
|
+
前 = t.end;
|
|
429
|
+
}
|
|
430
|
+
return 読み + expression.slice(前);
|
|
431
|
+
}
|
|
432
|
+
|
|
404
433
|
/**
|
|
405
434
|
* AST 内で参照される identifier (signal 名) を全て収集する。 evaluator が signal 経由で
|
|
406
435
|
* runtime value を lookup する時の識別子リスト作成に使う。
|
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
|
+
}
|
package/src/kinds/generic.tsx
CHANGED
|
@@ -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`)。 字ごとの幅で見積もる。
|