yarn-spinner-runner-ts 0.1.4 → 0.1.5
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/README.md +34 -16
- package/dist/compile/compiler.js +2 -2
- package/dist/compile/compiler.js.map +1 -1
- package/dist/compile/ir.d.ts +1 -0
- package/dist/markup/parser.js +12 -2
- package/dist/markup/parser.js.map +1 -1
- package/dist/model/ast.d.ts +1 -0
- package/dist/parse/lexer.js +5 -5
- package/dist/parse/lexer.js.map +1 -1
- package/dist/parse/parser.js +12 -2
- package/dist/parse/parser.js.map +1 -1
- package/dist/react/DialogueExample.js +12 -10
- package/dist/react/DialogueExample.js.map +1 -1
- package/dist/react/DialogueView.d.ts +10 -4
- package/dist/react/DialogueView.js +38 -12
- package/dist/react/DialogueView.js.map +1 -1
- package/dist/react/MarkupRenderer.js +1 -1
- package/dist/react/MarkupRenderer.js.map +1 -1
- package/dist/react/useYarnRunner.js +63 -10
- package/dist/react/useYarnRunner.js.map +1 -1
- package/dist/runtime/evaluator.js +3 -2
- package/dist/runtime/evaluator.js.map +1 -1
- package/dist/runtime/runner.d.ts +2 -0
- package/dist/runtime/runner.js +66 -10
- package/dist/runtime/runner.js.map +1 -1
- package/dist/tests/dialogue_view.test.d.ts +1 -0
- package/dist/tests/dialogue_view.test.js +18 -0
- package/dist/tests/dialogue_view.test.js.map +1 -0
- package/dist/tests/markup.test.js +7 -0
- package/dist/tests/markup.test.js.map +1 -1
- package/dist/tests/options.test.js +164 -9
- package/dist/tests/options.test.js.map +1 -1
- package/dist/tests/variables_flow_cmds.test.js +52 -10
- package/dist/tests/variables_flow_cmds.test.js.map +1 -1
- package/docs/markup.md +33 -33
- package/eslint.config.cjs +39 -39
- package/package.json +6 -6
- package/src/compile/compiler.ts +2 -2
- package/src/compile/ir.ts +1 -1
- package/src/markup/parser.ts +53 -43
- package/src/model/ast.ts +1 -0
- package/src/parse/lexer.ts +18 -18
- package/src/parse/parser.ts +33 -22
- package/src/react/DialogueExample.tsx +16 -14
- package/src/react/DialogueView.tsx +312 -275
- package/src/react/MarkupRenderer.tsx +1 -2
- package/src/react/useYarnRunner.tsx +101 -34
- package/src/runtime/evaluator.ts +15 -14
- package/src/runtime/runner.ts +102 -37
- package/src/tests/dialogue_view.test.tsx +26 -0
- package/src/tests/markup.test.ts +17 -1
- package/src/tests/options.test.ts +206 -36
- package/src/tests/variables_flow_cmds.test.ts +72 -28
|
@@ -30,15 +30,15 @@ Narrator: Choose one
|
|
|
30
30
|
strictEqual(c.text.includes("Picked B"), true, "Expected body of option B");
|
|
31
31
|
});
|
|
32
32
|
test("option markup is exposed", () => {
|
|
33
|
-
const script = `
|
|
34
|
-
title: Start
|
|
35
|
-
---
|
|
36
|
-
Narrator: Choose
|
|
37
|
-
-> [b]Bold[/b]
|
|
38
|
-
Narrator: Bold
|
|
39
|
-
-> [wave intensity=5]Custom[/wave]
|
|
40
|
-
Narrator: Custom
|
|
41
|
-
===
|
|
33
|
+
const script = `
|
|
34
|
+
title: Start
|
|
35
|
+
---
|
|
36
|
+
Narrator: Choose
|
|
37
|
+
-> [b]Bold[/b]
|
|
38
|
+
Narrator: Bold
|
|
39
|
+
-> [wave intensity=5]Custom[/wave]
|
|
40
|
+
Narrator: Custom
|
|
41
|
+
===
|
|
42
42
|
`;
|
|
43
43
|
const doc = parseYarn(script);
|
|
44
44
|
const ir = compile(doc);
|
|
@@ -58,4 +58,159 @@ Narrator: Choose
|
|
|
58
58
|
ok(customWrapper, "Expected custom wrapper on second option");
|
|
59
59
|
strictEqual(customWrapper.properties.intensity, 5);
|
|
60
60
|
});
|
|
61
|
+
test("option text interpolates variables", () => {
|
|
62
|
+
const script = `
|
|
63
|
+
title: Start
|
|
64
|
+
---
|
|
65
|
+
<<set $cost to 150>>
|
|
66
|
+
<<set $bribe to 300>>
|
|
67
|
+
Narrator: Decide
|
|
68
|
+
-> Pay {$cost}
|
|
69
|
+
Narrator: Paid
|
|
70
|
+
-> Haggle {$bribe}
|
|
71
|
+
Narrator: Haggle
|
|
72
|
+
===
|
|
73
|
+
`;
|
|
74
|
+
const doc = parseYarn(script);
|
|
75
|
+
const ir = compile(doc);
|
|
76
|
+
const runner = new YarnRunner(ir, { startAt: "Start" });
|
|
77
|
+
// First result is command for set
|
|
78
|
+
const initial = runner.currentResult;
|
|
79
|
+
strictEqual(initial?.type, "command", "Expected first <<set>> to emit a command result");
|
|
80
|
+
runner.advance(); // second <<set>> command
|
|
81
|
+
runner.advance(); // move to narration
|
|
82
|
+
runner.advance(); // move to options
|
|
83
|
+
const result = runner.currentResult;
|
|
84
|
+
if (!result || result.type !== "options") {
|
|
85
|
+
throw new Error("Expected to land on options");
|
|
86
|
+
}
|
|
87
|
+
const [pay, haggle] = result.options;
|
|
88
|
+
strictEqual(pay.text, "Pay 150", "Should replace placeholder with variable value");
|
|
89
|
+
strictEqual(haggle.text, "Haggle 300", "Should evaluate expressions inside placeholders");
|
|
90
|
+
});
|
|
91
|
+
test("conditional options respect once blocks and if statements", () => {
|
|
92
|
+
const script = `
|
|
93
|
+
title: Start
|
|
94
|
+
---
|
|
95
|
+
<<declare $secret = false>>
|
|
96
|
+
Narrator: Boot
|
|
97
|
+
<<once>>
|
|
98
|
+
<<set $secret = true>>
|
|
99
|
+
<<endonce>>
|
|
100
|
+
Narrator: Menu
|
|
101
|
+
<<if $secret>>
|
|
102
|
+
-> Secret Option
|
|
103
|
+
Narrator: Secret taken
|
|
104
|
+
<<set $secret = false>>
|
|
105
|
+
<<jump Start>>
|
|
106
|
+
<<endif>>
|
|
107
|
+
-> Regular Option
|
|
108
|
+
Narrator: Regular taken
|
|
109
|
+
<<jump Start>>
|
|
110
|
+
===
|
|
111
|
+
`;
|
|
112
|
+
const doc = parseYarn(script);
|
|
113
|
+
const ir = compile(doc);
|
|
114
|
+
const runner = new YarnRunner(ir, { startAt: "Start" });
|
|
115
|
+
const nextOptions = () => {
|
|
116
|
+
let guard = 25;
|
|
117
|
+
while (guard-- > 0) {
|
|
118
|
+
const result = runner.currentResult;
|
|
119
|
+
if (!result)
|
|
120
|
+
throw new Error("Expected runtime result");
|
|
121
|
+
if (result.type === "options") {
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
runner.advance();
|
|
125
|
+
}
|
|
126
|
+
throw new Error("Failed to reach options result");
|
|
127
|
+
};
|
|
128
|
+
const secretMenu = nextOptions();
|
|
129
|
+
strictEqual(secretMenu.options.length, 1, "First pass should expose the conditional secret option");
|
|
130
|
+
strictEqual(secretMenu.options[0].text, "Secret Option");
|
|
131
|
+
// Consume the secret option to flip the flag off
|
|
132
|
+
runner.advance(0);
|
|
133
|
+
const fallbackMenu = nextOptions();
|
|
134
|
+
strictEqual(fallbackMenu.options.length, 1, "After the secret path is used, only the regular option should remain");
|
|
135
|
+
strictEqual(fallbackMenu.options[0].text, "Regular Option");
|
|
136
|
+
});
|
|
137
|
+
test("options allow space-indented bodies", () => {
|
|
138
|
+
const script = `
|
|
139
|
+
title: Start
|
|
140
|
+
---
|
|
141
|
+
-> Pay
|
|
142
|
+
<<jump Pay>>
|
|
143
|
+
-> Run
|
|
144
|
+
<<jump Run>>
|
|
145
|
+
===
|
|
146
|
+
|
|
147
|
+
title: Pay
|
|
148
|
+
---
|
|
149
|
+
Narrator: Pay branch
|
|
150
|
+
===
|
|
151
|
+
|
|
152
|
+
title: Run
|
|
153
|
+
---
|
|
154
|
+
Narrator: Run branch
|
|
155
|
+
===
|
|
156
|
+
`;
|
|
157
|
+
const doc = parseYarn(script);
|
|
158
|
+
const ir = compile(doc);
|
|
159
|
+
const runner = new YarnRunner(ir, { startAt: "Start" });
|
|
160
|
+
const initial = runner.currentResult;
|
|
161
|
+
if (initial?.type !== "options") {
|
|
162
|
+
runner.advance();
|
|
163
|
+
}
|
|
164
|
+
const optionsResult = runner.currentResult;
|
|
165
|
+
strictEqual(optionsResult?.type, "options", "Expected to reach options");
|
|
166
|
+
if (optionsResult?.type !== "options")
|
|
167
|
+
throw new Error("Options not emitted");
|
|
168
|
+
strictEqual(optionsResult.options.length, 2, "Space indents should still group options together");
|
|
169
|
+
strictEqual(optionsResult.options[0].text, "Pay");
|
|
170
|
+
strictEqual(optionsResult.options[1].text, "Run");
|
|
171
|
+
});
|
|
172
|
+
test("inline [if] option condition filters options", () => {
|
|
173
|
+
const script = `
|
|
174
|
+
title: StartFalse
|
|
175
|
+
---
|
|
176
|
+
<<declare $flag = false>>
|
|
177
|
+
-> Hidden [if $flag]
|
|
178
|
+
Narrator: Hidden
|
|
179
|
+
-> Visible
|
|
180
|
+
Narrator: Visible
|
|
181
|
+
===
|
|
182
|
+
|
|
183
|
+
title: StartTrue
|
|
184
|
+
---
|
|
185
|
+
<<declare $flag = true>>
|
|
186
|
+
-> Hidden [if $flag]
|
|
187
|
+
Narrator: Hidden
|
|
188
|
+
-> Visible
|
|
189
|
+
Narrator: Visible
|
|
190
|
+
===
|
|
191
|
+
`;
|
|
192
|
+
const doc = parseYarn(script);
|
|
193
|
+
const ir = compile(doc);
|
|
194
|
+
const getOptions = (startNode) => {
|
|
195
|
+
const runner = new YarnRunner(ir, { startAt: startNode });
|
|
196
|
+
let guard = 25;
|
|
197
|
+
while (guard-- > 0) {
|
|
198
|
+
const result = runner.currentResult;
|
|
199
|
+
if (!result)
|
|
200
|
+
break;
|
|
201
|
+
if (result.type === "options") {
|
|
202
|
+
return { runner, options: result };
|
|
203
|
+
}
|
|
204
|
+
runner.advance();
|
|
205
|
+
}
|
|
206
|
+
throw new Error("Failed to reach options");
|
|
207
|
+
};
|
|
208
|
+
const { options: optionsFalse } = getOptions("StartFalse");
|
|
209
|
+
strictEqual(optionsFalse.options.length, 1, "Hidden option should be filtered out when condition is false");
|
|
210
|
+
strictEqual(optionsFalse.options[0].text, "Visible");
|
|
211
|
+
const { options: optionsTrue } = getOptions("StartTrue");
|
|
212
|
+
strictEqual(optionsTrue.options.length, 2, "Both options should appear when condition is true");
|
|
213
|
+
strictEqual(optionsTrue.options[0].text, "Hidden");
|
|
214
|
+
strictEqual(optionsTrue.options[1].text, "Visible");
|
|
215
|
+
});
|
|
61
216
|
//# sourceMappingURL=options.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.test.js","sourceRoot":"","sources":["../../src/tests/options.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"options.test.js","sourceRoot":"","sources":["../../src/tests/options.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,16 +2,16 @@ import { test } from "node:test";
|
|
|
2
2
|
import { strictEqual } from "node:assert";
|
|
3
3
|
import { parseYarn, compile, YarnRunner } from "../index.js";
|
|
4
4
|
test("variables, flow control, and commands", () => {
|
|
5
|
-
const script = `
|
|
6
|
-
title: Start
|
|
7
|
-
---
|
|
8
|
-
<<set $score to 10>>
|
|
9
|
-
<<if $score >= 10>>
|
|
10
|
-
Narrator: High
|
|
11
|
-
<<else>>
|
|
12
|
-
Narrator: Low
|
|
13
|
-
<<endif>>
|
|
14
|
-
===
|
|
5
|
+
const script = `
|
|
6
|
+
title: Start
|
|
7
|
+
---
|
|
8
|
+
<<set $score to 10>>
|
|
9
|
+
<<if $score >= 10>>
|
|
10
|
+
Narrator: High
|
|
11
|
+
<<else>>
|
|
12
|
+
Narrator: Low
|
|
13
|
+
<<endif>>
|
|
14
|
+
===
|
|
15
15
|
`;
|
|
16
16
|
const doc = parseYarn(script);
|
|
17
17
|
const ir = compile(doc);
|
|
@@ -27,4 +27,46 @@ title: Start
|
|
|
27
27
|
strictEqual(/High/.test(b.text), true, "Expected High branch");
|
|
28
28
|
strictEqual(runner.getVariable("score"), 10, "Variable should be set");
|
|
29
29
|
});
|
|
30
|
+
test("equality operators support ==, !=, and single =", () => {
|
|
31
|
+
const script = `
|
|
32
|
+
title: Start
|
|
33
|
+
---
|
|
34
|
+
<<set $doorOpen to true>>
|
|
35
|
+
<<if $doorOpen = true>>
|
|
36
|
+
Narrator: Single equals ok
|
|
37
|
+
<<endif>>
|
|
38
|
+
<<if $doorOpen == true>>
|
|
39
|
+
Narrator: Double equals ok
|
|
40
|
+
<<endif>>
|
|
41
|
+
<<if $doorOpen != false>>
|
|
42
|
+
Narrator: Not equals ok
|
|
43
|
+
<<endif>>
|
|
44
|
+
===
|
|
45
|
+
`;
|
|
46
|
+
const doc = parseYarn(script);
|
|
47
|
+
const ir = compile(doc);
|
|
48
|
+
const runner = new YarnRunner(ir, { startAt: "Start" });
|
|
49
|
+
const seen = [];
|
|
50
|
+
let guard = 25;
|
|
51
|
+
while (guard-- > 0) {
|
|
52
|
+
const result = runner.currentResult;
|
|
53
|
+
if (!result)
|
|
54
|
+
break;
|
|
55
|
+
if (result.type === "text" && result.text.trim()) {
|
|
56
|
+
seen.push(result.text.trim());
|
|
57
|
+
}
|
|
58
|
+
if (result.isDialogueEnd) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if (result.type === "options") {
|
|
62
|
+
runner.advance(0);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
runner.advance();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
strictEqual(seen.includes("Single equals ok"), true, "Single equals comparison should succeed");
|
|
69
|
+
strictEqual(seen.includes("Double equals ok"), true, "Double equals comparison should succeed");
|
|
70
|
+
strictEqual(seen.includes("Not equals ok"), true, "Not equals comparison should succeed");
|
|
71
|
+
});
|
|
30
72
|
//# sourceMappingURL=variables_flow_cmds.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variables_flow_cmds.test.js","sourceRoot":"","sources":["../../src/tests/variables_flow_cmds.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE7D,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACjD,MAAM,MAAM,GAAG;;;;;;;;;;CAUhB,CAAC;IAEA,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAExD,yCAAyC;IACzC,0CAA0C;IAC1C,MAAM,CAAC,GAAG,MAAM,CAAC,aAAc,CAAC;IAChC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,gCAAgC,CAAC,CAAC;IACjE,MAAM,CAAC,OAAO,EAAE,CAAC;IACjB,MAAM,CAAC,GAAG,MAAM,CAAC,aAAc,CAAC;IAChC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,8BAA8B,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;QAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC;IACtF,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC;AACzE,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"variables_flow_cmds.test.js","sourceRoot":"","sources":["../../src/tests/variables_flow_cmds.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE7D,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACjD,MAAM,MAAM,GAAG;;;;;;;;;;CAUhB,CAAC;IAEA,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAExD,yCAAyC;IACzC,0CAA0C;IAC1C,MAAM,CAAC,GAAG,MAAM,CAAC,aAAc,CAAC;IAChC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,gCAAgC,CAAC,CAAC;IACjE,MAAM,CAAC,OAAO,EAAE,CAAC;IACjB,MAAM,CAAC,GAAG,MAAM,CAAC,aAAc,CAAC;IAChC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,8BAA8B,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;QAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC;IACtF,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC;AACzE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC3D,MAAM,MAAM,GAAG;;;;;;;;;;;;;;CAchB,CAAC;IAEA,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAExD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,OAAO,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC;QACpC,IAAI,CAAC,MAAM;YAAE,MAAM;QACnB,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,MAAM;QACR,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,yCAAyC,CAAC,CAAC;IAChG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,yCAAyC,CAAC,CAAC;IAChG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,sCAAsC,CAAC,CAAC;AAC5F,CAAC,CAAC,CAAC"}
|
package/docs/markup.md
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
## Markup (Yarn Spinner)
|
|
2
|
-
|
|
3
|
-
Source: [docs.yarnspinner.dev � Markup](https://docs.yarnspinner.dev/write-yarn-scripts/advanced-scripting/markup)
|
|
4
|
-
|
|
5
|
-
### Supported formatting
|
|
6
|
-
|
|
7
|
-
The runtime now parses Yarn Spinner markup and surfaces it through `TextResult.markup` and option metadata. The React components (`DialogueView`, `TypingText`, and the option buttons) render this markup automatically.
|
|
8
|
-
|
|
9
|
-
- The following tags map directly to native HTML elements: `b`, `strong`, `em`, `small`, `sub`, `sup`, `ins`, `del`, and `
|
|
10
|
-
- Any other markup tag is rendered as a `<span>` with the class `yd-markup-<tagName>` so you can style or animate it via CSS.
|
|
11
|
-
- Markup attributes are exposed as `data-markup-*` attributes on the rendered element. For example `[wave speed=2]` renders `<span class="yd-markup-wave" data-markup-speed="2">`.
|
|
12
|
-
|
|
13
|
-
### Example
|
|
14
|
-
|
|
15
|
-
```yarn
|
|
16
|
-
title: Start
|
|
17
|
-
---
|
|
18
|
-
Narrator: Plain [b]bold[/b] [wave speed=2]custom[/wave]
|
|
19
|
-
===
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
The React renderer produces:
|
|
23
|
-
|
|
24
|
-
```html
|
|
25
|
-
Plain <b>bold</b> <span class="yd-markup-wave" data-markup-speed="2">custom</span>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Integration notes
|
|
29
|
-
|
|
30
|
-
- Markup data is available on `TextResult.markup` and on each option entry (`result.options[i].markup`).
|
|
31
|
-
- `TypingText` respects markup while animating, so formatting stays intact during the typewriter effect.
|
|
32
|
-
- When a markup tag is not recognised, it remains in the output (as a span) rather than being stripped, so you can add custom CSS in your host application.
|
|
33
|
-
|
|
1
|
+
## Markup (Yarn Spinner)
|
|
2
|
+
|
|
3
|
+
Source: [docs.yarnspinner.dev � Markup](https://docs.yarnspinner.dev/write-yarn-scripts/advanced-scripting/markup)
|
|
4
|
+
|
|
5
|
+
### Supported formatting
|
|
6
|
+
|
|
7
|
+
The runtime now parses Yarn Spinner markup and surfaces it through `TextResult.markup` and option metadata. The React components (`DialogueView`, `TypingText`, and the option buttons) render this markup automatically.
|
|
8
|
+
|
|
9
|
+
- The following tags map directly to native HTML elements: `b`, `strong`, `em`, `small`, `sub`, `sup`, `ins`, `del`, `mark`, and the self-closing line break `br`.
|
|
10
|
+
- Any other markup tag is rendered as a `<span>` with the class `yd-markup-<tagName>` so you can style or animate it via CSS.
|
|
11
|
+
- Markup attributes are exposed as `data-markup-*` attributes on the rendered element. For example `[wave speed=2]` renders `<span class="yd-markup-wave" data-markup-speed="2">`.
|
|
12
|
+
|
|
13
|
+
### Example
|
|
14
|
+
|
|
15
|
+
```yarn
|
|
16
|
+
title: Start
|
|
17
|
+
---
|
|
18
|
+
Narrator: Plain [b]bold[/b] [wave speed=2]custom[/wave]
|
|
19
|
+
===
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The React renderer produces:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
Plain <b>bold</b> <span class="yd-markup-wave" data-markup-speed="2">custom</span>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Integration notes
|
|
29
|
+
|
|
30
|
+
- Markup data is available on `TextResult.markup` and on each option entry (`result.options[i].markup`).
|
|
31
|
+
- `TypingText` respects markup while animating, so formatting stays intact during the typewriter effect.
|
|
32
|
+
- When a markup tag is not recognised, it remains in the output (as a span) rather than being stripped, so you can add custom CSS in your host application.
|
|
33
|
+
|
|
34
34
|
For the full markup vocabulary see the official Yarn Spinner documentation.
|
package/eslint.config.cjs
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
// ESLint v9 flat config
|
|
2
|
-
const js = require("@eslint/js");
|
|
3
|
-
const tsParser = require("@typescript-eslint/parser");
|
|
4
|
-
const tsPlugin = require("@typescript-eslint/eslint-plugin");
|
|
5
|
-
|
|
6
|
-
module.exports = [
|
|
7
|
-
{
|
|
8
|
-
ignores: ["dist/**", "node_modules/**", "examples/**", "src/examples/**", "src/tests/**"],
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
12
|
-
languageOptions: {
|
|
13
|
-
parser: tsParser,
|
|
14
|
-
ecmaVersion: "latest",
|
|
15
|
-
sourceType: "module",
|
|
16
|
-
globals: {
|
|
17
|
-
console: "readonly",
|
|
18
|
-
window: "readonly",
|
|
19
|
-
setTimeout: "readonly",
|
|
20
|
-
clearTimeout: "readonly",
|
|
21
|
-
setInterval: "readonly",
|
|
22
|
-
clearInterval: "readonly",
|
|
23
|
-
requestAnimationFrame: "readonly",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
plugins: {
|
|
27
|
-
"@typescript-eslint": tsPlugin,
|
|
28
|
-
},
|
|
29
|
-
rules: {
|
|
30
|
-
...js.configs.recommended.rules,
|
|
31
|
-
...tsPlugin.configs.recommended.rules,
|
|
32
|
-
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
33
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
34
|
-
"no-console": ["warn", { allow: ["warn", "error", "assert", "log"] }],
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
// ESLint v9 flat config
|
|
2
|
+
const js = require("@eslint/js");
|
|
3
|
+
const tsParser = require("@typescript-eslint/parser");
|
|
4
|
+
const tsPlugin = require("@typescript-eslint/eslint-plugin");
|
|
5
|
+
|
|
6
|
+
module.exports = [
|
|
7
|
+
{
|
|
8
|
+
ignores: ["dist/**", "node_modules/**", "examples/**", "src/examples/**", "src/tests/**"],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
parser: tsParser,
|
|
14
|
+
ecmaVersion: "latest",
|
|
15
|
+
sourceType: "module",
|
|
16
|
+
globals: {
|
|
17
|
+
console: "readonly",
|
|
18
|
+
window: "readonly",
|
|
19
|
+
setTimeout: "readonly",
|
|
20
|
+
clearTimeout: "readonly",
|
|
21
|
+
setInterval: "readonly",
|
|
22
|
+
clearInterval: "readonly",
|
|
23
|
+
requestAnimationFrame: "readonly",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
plugins: {
|
|
27
|
+
"@typescript-eslint": tsPlugin,
|
|
28
|
+
},
|
|
29
|
+
rules: {
|
|
30
|
+
...js.configs.recommended.rules,
|
|
31
|
+
...tsPlugin.configs.recommended.rules,
|
|
32
|
+
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
33
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
34
|
+
"no-console": ["warn", { allow: ["warn", "error", "assert", "log"] }],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yarn-spinner-runner-ts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript parser, compiler, and runtime for Yarn Spinner 3.x with React adapter [NPM package](https://www.npmjs.com/package/yarn-spinner-runner-ts)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
"js-yaml": "^4.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@eslint/js": "^9.12.0",
|
|
41
|
-
"@types/js-yaml": "^4.0.9",
|
|
42
40
|
"@types/node": "^22.7.4",
|
|
41
|
+
"@types/js-yaml": "^4.0.9",
|
|
43
42
|
"@types/react": "^18.3.3",
|
|
44
43
|
"@types/react-dom": "^18.3.0",
|
|
45
|
-
"@
|
|
44
|
+
"@eslint/js": "^9.12.0",
|
|
46
45
|
"@typescript-eslint/parser": "^8.8.1",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
|
47
47
|
"@vitejs/plugin-react": "^4.3.1",
|
|
48
48
|
"eslint": "^9.12.0",
|
|
49
49
|
"react": "^18.3.1",
|
|
50
50
|
"react-dom": "^18.3.1",
|
|
51
51
|
"rimraf": "^6.0.1",
|
|
52
52
|
"typescript": "^5.6.3",
|
|
53
|
-
"vite": "^5.4.3"
|
|
54
|
-
"vitest": "^4.0.7"
|
|
53
|
+
"vite": "^5.4.3"
|
|
55
54
|
}
|
|
56
55
|
}
|
|
56
|
+
|
package/src/compile/compiler.ts
CHANGED
|
@@ -72,7 +72,7 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|
|
72
72
|
}
|
|
73
73
|
block.push({
|
|
74
74
|
op: "options",
|
|
75
|
-
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, block: emitBlock(o.body) })),
|
|
75
|
+
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body) })),
|
|
76
76
|
});
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
@@ -141,7 +141,7 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|
|
141
141
|
}
|
|
142
142
|
block.push({
|
|
143
143
|
op: "options",
|
|
144
|
-
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, block: emitBlock(o.body) })),
|
|
144
|
+
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body) })),
|
|
145
145
|
});
|
|
146
146
|
break;
|
|
147
147
|
}
|
package/src/compile/ir.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type IRInstruction =
|
|
|
22
22
|
| { op: "command"; content: string }
|
|
23
23
|
| { op: "jump"; target: string }
|
|
24
24
|
| { op: "detour"; target: string }
|
|
25
|
-
| { op: "options"; options: Array<{ text: string; tags?: string[]; css?: string; markup?: MarkupParseResult; block: IRInstruction[] }> }
|
|
25
|
+
| { op: "options"; options: Array<{ text: string; tags?: string[]; css?: string; markup?: MarkupParseResult; condition?: string; block: IRInstruction[] }> }
|
|
26
26
|
| { op: "if"; branches: Array<{ condition: string | null; block: IRInstruction[] }> }
|
|
27
27
|
| { op: "once"; id: string; block: IRInstruction[] };
|
|
28
28
|
|
package/src/markup/parser.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { MarkupParseResult, MarkupSegment, MarkupValue, MarkupWrapper } from "./types.js";
|
|
2
|
-
|
|
3
|
-
const DEFAULT_HTML_TAGS = new Set(["b", "em", "small", "strong", "sub", "sup", "ins", "del", "mark"]);
|
|
1
|
+
import type { MarkupParseResult, MarkupSegment, MarkupValue, MarkupWrapper } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_HTML_TAGS = new Set(["b", "em", "small", "strong", "sub", "sup", "ins", "del", "mark", "br"]);
|
|
4
|
+
const SELF_CLOSING_TAGS = new Set(["br"]);
|
|
4
5
|
|
|
5
6
|
interface StackEntry {
|
|
6
7
|
name: string;
|
|
@@ -125,8 +126,9 @@ export function parseMarkup(input: string): MarkupParseResult {
|
|
|
125
126
|
rest = rest.slice(attrMatch[0].length).trim();
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const finalKind: ParsedTag["kind"] = kind === "self" || SELF_CLOSING_TAGS.has(name) ? "self" : kind;
|
|
130
|
+
return { kind: finalKind, name, properties };
|
|
131
|
+
};
|
|
130
132
|
|
|
131
133
|
const parseAttributeValue = (raw: string): MarkupValue => {
|
|
132
134
|
const trimmed = raw.trim();
|
|
@@ -142,20 +144,20 @@ export function parseMarkup(input: string): MarkupParseResult {
|
|
|
142
144
|
return trimmed;
|
|
143
145
|
};
|
|
144
146
|
|
|
145
|
-
const handleSelfClosing = (tag: ParsedTag) => {
|
|
146
|
-
const wrapper: MarkupWrapper = {
|
|
147
|
-
name: tag.name,
|
|
148
|
-
type: DEFAULT_HTML_TAGS.has(tag.name) ? "default" : "custom",
|
|
149
|
-
properties: tag.properties,
|
|
150
|
-
};
|
|
151
|
-
const position = chars.length;
|
|
152
|
-
pushSegment({
|
|
153
|
-
start: position,
|
|
154
|
-
end: position,
|
|
155
|
-
wrappers: [wrapper],
|
|
156
|
-
selfClosing: true,
|
|
157
|
-
});
|
|
158
|
-
};
|
|
147
|
+
const handleSelfClosing = (tag: ParsedTag) => {
|
|
148
|
+
const wrapper: MarkupWrapper = {
|
|
149
|
+
name: tag.name,
|
|
150
|
+
type: DEFAULT_HTML_TAGS.has(tag.name) ? "default" : "custom",
|
|
151
|
+
properties: tag.properties,
|
|
152
|
+
};
|
|
153
|
+
const position = chars.length;
|
|
154
|
+
pushSegment({
|
|
155
|
+
start: position,
|
|
156
|
+
end: position,
|
|
157
|
+
wrappers: [wrapper],
|
|
158
|
+
selfClosing: true,
|
|
159
|
+
});
|
|
160
|
+
};
|
|
159
161
|
|
|
160
162
|
let i = 0;
|
|
161
163
|
while (i < input.length) {
|
|
@@ -215,30 +217,38 @@ export function parseMarkup(input: string): MarkupParseResult {
|
|
|
215
217
|
continue;
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
if (parsed.kind === "self") {
|
|
219
|
-
handleSelfClosing(parsed);
|
|
220
|
-
i = closeIndex + 1;
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// closing tag
|
|
225
|
-
if (stack.length === 0) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
220
|
+
if (parsed.kind === "self") {
|
|
221
|
+
handleSelfClosing(parsed);
|
|
222
|
+
i = closeIndex + 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// closing tag
|
|
227
|
+
if (stack.length === 0) {
|
|
228
|
+
if (SELF_CLOSING_TAGS.has(parsed.name)) {
|
|
229
|
+
i = closeIndex + 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
appendLiteral(originalText);
|
|
233
|
+
i = closeIndex + 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const top = stack[stack.length - 1];
|
|
237
|
+
if (top.name === parsed.name) {
|
|
238
|
+
flushCurrentSegment();
|
|
239
|
+
stack.pop();
|
|
240
|
+
i = closeIndex + 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (SELF_CLOSING_TAGS.has(parsed.name)) {
|
|
244
|
+
i = closeIndex + 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// mismatched closing; treat as literal
|
|
248
|
+
appendLiteral(originalText);
|
|
249
|
+
i = closeIndex + 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
242
252
|
|
|
243
253
|
appendChar(char);
|
|
244
254
|
i += 1;
|