@rohal12/spindle 0.14.0 → 0.16.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.
Files changed (44) hide show
  1. package/dist/pkg/format.js +1 -1
  2. package/package.json +1 -1
  3. package/src/components/macros/Back.tsx +5 -6
  4. package/src/components/macros/Button.tsx +6 -11
  5. package/src/components/macros/Checkbox.tsx +5 -7
  6. package/src/components/macros/Computed.tsx +5 -5
  7. package/src/components/macros/Cycle.tsx +5 -9
  8. package/src/components/macros/Do.tsx +6 -8
  9. package/src/components/macros/For.tsx +10 -9
  10. package/src/components/macros/Forward.tsx +5 -6
  11. package/src/components/macros/Goto.tsx +5 -5
  12. package/src/components/macros/If.tsx +5 -5
  13. package/src/components/macros/Include.tsx +5 -7
  14. package/src/components/macros/Listbox.tsx +6 -9
  15. package/src/components/macros/MacroLink.tsx +8 -12
  16. package/src/components/macros/Meter.tsx +5 -7
  17. package/src/components/macros/Numberbox.tsx +5 -7
  18. package/src/components/macros/PassageDisplay.tsx +5 -6
  19. package/src/components/macros/Print.tsx +5 -7
  20. package/src/components/macros/QuickLoad.tsx +5 -6
  21. package/src/components/macros/QuickSave.tsx +5 -6
  22. package/src/components/macros/Radiobutton.tsx +5 -7
  23. package/src/components/macros/Repeat.tsx +6 -10
  24. package/src/components/macros/Restart.tsx +5 -6
  25. package/src/components/macros/Saves.tsx +5 -6
  26. package/src/components/macros/Set.tsx +6 -7
  27. package/src/components/macros/SettingsButton.tsx +5 -6
  28. package/src/components/macros/Stop.tsx +5 -1
  29. package/src/components/macros/StoryTitle.tsx +5 -6
  30. package/src/components/macros/Switch.tsx +7 -6
  31. package/src/components/macros/Textarea.tsx +5 -7
  32. package/src/components/macros/Textbox.tsx +5 -7
  33. package/src/components/macros/Timed.tsx +12 -14
  34. package/src/components/macros/Type.tsx +5 -9
  35. package/src/components/macros/Unset.tsx +5 -5
  36. package/src/components/macros/VarDisplay.tsx +1 -2
  37. package/src/components/macros/Widget.tsx +5 -7
  38. package/src/components/macros/WidgetInvocation.tsx +1 -1
  39. package/src/execute-mutation.ts +1 -1
  40. package/src/hooks/use-merged-locals.ts +3 -20
  41. package/src/index.tsx +1 -0
  42. package/src/macros/register-builtins.ts +35 -0
  43. package/src/markup/render.tsx +36 -404
  44. package/src/registry.ts +13 -1
@@ -1,46 +1,12 @@
1
1
  import { createContext } from 'preact';
2
2
  import { PassageLink } from '../components/PassageLink';
3
3
  import { VarDisplay } from '../components/macros/VarDisplay';
4
- import { Set } from '../components/macros/Set';
5
- import { Print } from '../components/macros/Print';
6
- import { If } from '../components/macros/If';
7
- import { For } from '../components/macros/For';
8
- import { Do } from '../components/macros/Do';
9
- import { Button } from '../components/macros/Button';
10
- import { StoryTitle } from '../components/macros/StoryTitle';
11
- import { Restart } from '../components/macros/Restart';
12
- import { Back } from '../components/macros/Back';
13
- import { Forward } from '../components/macros/Forward';
14
- import { QuickSave } from '../components/macros/QuickSave';
15
- import { QuickLoad } from '../components/macros/QuickLoad';
16
- import { SettingsButton } from '../components/macros/SettingsButton';
17
- import { Saves } from '../components/macros/Saves';
18
- import { Include } from '../components/macros/Include';
19
- import { Goto } from '../components/macros/Goto';
20
- import { Unset } from '../components/macros/Unset';
21
- import { Textbox } from '../components/macros/Textbox';
22
- import { Numberbox } from '../components/macros/Numberbox';
23
- import { Textarea } from '../components/macros/Textarea';
24
- import { Checkbox } from '../components/macros/Checkbox';
25
- import { Radiobutton } from '../components/macros/Radiobutton';
26
- import { Listbox } from '../components/macros/Listbox';
27
- import { Cycle } from '../components/macros/Cycle';
28
- import { MacroLink } from '../components/macros/MacroLink';
29
- import { Switch } from '../components/macros/Switch';
30
- import { Timed } from '../components/macros/Timed';
31
- import { Repeat } from '../components/macros/Repeat';
32
- import { Stop } from '../components/macros/Stop';
33
- import { Type } from '../components/macros/Type';
34
- import { Widget } from '../components/macros/Widget';
35
4
  import { WidgetInvocation } from '../components/macros/WidgetInvocation';
36
- import { Computed } from '../components/macros/Computed';
37
- import { Meter } from '../components/macros/Meter';
38
- import { PassageDisplay } from '../components/macros/PassageDisplay';
39
5
  import { getWidget } from '../widgets/widget-registry';
40
- import { getMacro } from '../registry';
6
+ import { getMacro, isSubMacro } from '../registry';
41
7
  import { markdownToHtml } from './markdown';
42
8
  import { h } from 'preact';
43
- import type { ASTNode, Branch, HtmlNode, MacroNode, VariableNode } from './ast';
9
+ import type { ASTNode, HtmlNode, MacroNode, VariableNode } from './ast';
44
10
  import { useStoryStore } from '../store';
45
11
  import { useInterpolate } from '../hooks/use-interpolate';
46
12
 
@@ -57,8 +23,6 @@ const defaultUpdater: LocalsUpdater = {
57
23
  export const LocalsValuesContext = createContext<Record<string, unknown>>({});
58
24
  export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
59
25
 
60
- const EMPTY_BRANCHES: Branch[] = [];
61
-
62
26
  /**
63
27
  * Convert an HTML string (from micromark) to Preact VNodes,
64
28
  * replacing <span data-tw="N"> placeholder elements with pre-rendered components.
@@ -123,374 +87,42 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
123
87
  }
124
88
 
125
89
  function renderMacro(node: MacroNode, key: number) {
126
- switch (node.name) {
127
- case 'set':
128
- return (
129
- <Set
130
- key={key}
131
- rawArgs={node.rawArgs}
132
- />
133
- );
134
-
135
- case 'computed':
136
- return (
137
- <Computed
138
- key={key}
139
- rawArgs={node.rawArgs}
140
- />
141
- );
142
-
143
- case 'print':
144
- return (
145
- <Print
146
- key={key}
147
- rawArgs={node.rawArgs}
148
- className={node.className}
149
- id={node.id}
150
- />
151
- );
152
-
153
- case 'meter':
154
- return (
155
- <Meter
156
- key={key}
157
- rawArgs={node.rawArgs}
158
- className={node.className}
159
- id={node.id}
160
- />
161
- );
162
-
163
- case 'passage':
164
- return (
165
- <PassageDisplay
166
- key={key}
167
- className={node.className}
168
- id={node.id}
169
- />
170
- );
171
-
172
- case 'if':
173
- return (
174
- <If
175
- key={key}
176
- branches={node.branches ?? EMPTY_BRANCHES}
177
- />
178
- );
179
-
180
- case 'for':
181
- return (
182
- <For
183
- key={key}
184
- rawArgs={node.rawArgs}
185
- children={node.children}
186
- className={node.className}
187
- id={node.id}
188
- />
189
- );
190
-
191
- case 'do':
192
- return (
193
- <Do
194
- key={key}
195
- children={node.children}
196
- />
197
- );
198
-
199
- case 'button':
200
- return (
201
- <Button
202
- key={key}
203
- rawArgs={node.rawArgs}
204
- children={node.children}
205
- className={node.className}
206
- id={node.id}
207
- />
208
- );
209
-
210
- case 'story-title':
211
- return (
212
- <StoryTitle
213
- key={key}
214
- className={node.className}
215
- id={node.id}
216
- />
217
- );
218
-
219
- case 'back':
220
- return (
221
- <Back
222
- key={key}
223
- className={node.className}
224
- id={node.id}
225
- />
226
- );
227
-
228
- case 'forward':
229
- return (
230
- <Forward
231
- key={key}
232
- className={node.className}
233
- id={node.id}
234
- />
235
- );
236
-
237
- case 'restart':
238
- return (
239
- <Restart
240
- key={key}
241
- className={node.className}
242
- id={node.id}
243
- />
244
- );
245
-
246
- case 'quicksave':
247
- return (
248
- <QuickSave
249
- key={key}
250
- className={node.className}
251
- id={node.id}
252
- />
253
- );
254
-
255
- case 'quickload':
256
- return (
257
- <QuickLoad
258
- key={key}
259
- className={node.className}
260
- id={node.id}
261
- />
262
- );
263
-
264
- case 'settings':
265
- return (
266
- <SettingsButton
267
- key={key}
268
- className={node.className}
269
- id={node.id}
270
- />
271
- );
272
-
273
- case 'saves':
274
- return (
275
- <Saves
276
- key={key}
277
- className={node.className}
278
- id={node.id}
279
- />
280
- );
281
-
282
- case 'include':
283
- return (
284
- <Include
285
- key={key}
286
- rawArgs={node.rawArgs}
287
- className={node.className}
288
- id={node.id}
289
- />
290
- );
291
-
292
- case 'goto':
293
- return (
294
- <Goto
295
- key={key}
296
- rawArgs={node.rawArgs}
297
- />
298
- );
299
-
300
- case 'unset':
301
- return (
302
- <Unset
303
- key={key}
304
- rawArgs={node.rawArgs}
305
- />
306
- );
307
-
308
- case 'textbox':
309
- return (
310
- <Textbox
311
- key={key}
312
- rawArgs={node.rawArgs}
313
- className={node.className}
314
- id={node.id}
315
- />
316
- );
317
-
318
- case 'numberbox':
319
- return (
320
- <Numberbox
321
- key={key}
322
- rawArgs={node.rawArgs}
323
- className={node.className}
324
- id={node.id}
325
- />
326
- );
327
-
328
- case 'textarea':
329
- return (
330
- <Textarea
331
- key={key}
332
- rawArgs={node.rawArgs}
333
- className={node.className}
334
- id={node.id}
335
- />
336
- );
337
-
338
- case 'checkbox':
339
- return (
340
- <Checkbox
341
- key={key}
342
- rawArgs={node.rawArgs}
343
- className={node.className}
344
- id={node.id}
345
- />
346
- );
347
-
348
- case 'radiobutton':
349
- return (
350
- <Radiobutton
351
- key={key}
352
- rawArgs={node.rawArgs}
353
- className={node.className}
354
- id={node.id}
355
- />
356
- );
357
-
358
- case 'listbox':
359
- return (
360
- <Listbox
361
- key={key}
362
- rawArgs={node.rawArgs}
363
- children={node.children}
364
- className={node.className}
365
- id={node.id}
366
- />
367
- );
368
-
369
- case 'cycle':
370
- return (
371
- <Cycle
372
- key={key}
373
- rawArgs={node.rawArgs}
374
- children={node.children}
375
- className={node.className}
376
- id={node.id}
377
- />
378
- );
379
-
380
- case 'link':
381
- return (
382
- <MacroLink
383
- key={key}
384
- rawArgs={node.rawArgs}
385
- children={node.children}
386
- className={node.className}
387
- id={node.id}
388
- />
389
- );
390
-
391
- case 'switch':
392
- return (
393
- <Switch
394
- key={key}
395
- rawArgs={node.rawArgs}
396
- branches={node.branches ?? EMPTY_BRANCHES}
397
- />
398
- );
399
-
400
- case 'timed': {
401
- const firstBranch = node.branches?.[0];
402
- return (
403
- <Timed
404
- key={key}
405
- rawArgs={node.rawArgs}
406
- children={node.children}
407
- branches={node.branches ?? EMPTY_BRANCHES}
408
- className={node.className ?? firstBranch?.className}
409
- id={node.id ?? firstBranch?.id}
410
- />
411
- );
412
- }
413
-
414
- case 'repeat':
415
- return (
416
- <Repeat
417
- key={key}
418
- rawArgs={node.rawArgs}
419
- children={node.children}
420
- className={node.className}
421
- id={node.id}
422
- />
423
- );
424
-
425
- case 'stop':
426
- return <Stop key={key} />;
427
-
428
- case 'type':
429
- return (
430
- <Type
431
- key={key}
432
- rawArgs={node.rawArgs}
433
- children={node.children}
434
- className={node.className}
435
- id={node.id}
436
- />
437
- );
438
-
439
- case 'widget':
440
- return (
441
- <Widget
442
- key={key}
443
- rawArgs={node.rawArgs}
444
- children={node.children}
445
- />
446
- );
447
-
448
- // {option}, {case}, {default}, {next} are handled by parent components
449
- case 'option':
450
- case 'case':
451
- case 'default':
452
- case 'next':
453
- return null;
454
-
455
- default: {
456
- // Check widget registry for user-defined widgets
457
- const widget = getWidget(node.name);
458
- if (widget) {
459
- return (
460
- <WidgetInvocation
461
- key={key}
462
- body={widget.body}
463
- params={widget.params}
464
- rawArgs={node.rawArgs}
465
- />
466
- );
467
- }
468
-
469
- // Check component registry for custom macros
470
- const Component = getMacro(node.name);
471
- if (Component) {
472
- return (
473
- <Component
474
- key={key}
475
- rawArgs={node.rawArgs}
476
- className={node.className}
477
- id={node.id}
478
- >
479
- {renderNodes(node.children)}
480
- </Component>
481
- );
482
- }
90
+ if (isSubMacro(node.name)) return null;
91
+
92
+ const widget = getWidget(node.name);
93
+ if (widget) {
94
+ return (
95
+ <WidgetInvocation
96
+ key={key}
97
+ body={widget.body}
98
+ params={widget.params}
99
+ rawArgs={node.rawArgs}
100
+ />
101
+ );
102
+ }
483
103
 
484
- return (
485
- <span
486
- key={key}
487
- class="error"
488
- >
489
- {`{unknown macro: ${node.name}}`}
490
- </span>
491
- );
492
- }
104
+ const Component = getMacro(node.name);
105
+ if (Component) {
106
+ return (
107
+ <Component
108
+ key={key}
109
+ rawArgs={node.rawArgs}
110
+ className={node.className}
111
+ id={node.id}
112
+ children={node.children}
113
+ branches={node.branches}
114
+ />
115
+ );
493
116
  }
117
+
118
+ return (
119
+ <span
120
+ key={key}
121
+ class="error"
122
+ >
123
+ {`{unknown macro: ${node.name}}`}
124
+ </span>
125
+ );
494
126
  }
495
127
 
496
128
  /**
package/src/registry.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import type { ComponentType } from 'preact';
2
+ import type { ASTNode, Branch } from './markup/ast';
2
3
 
3
4
  export interface MacroProps {
4
5
  rawArgs: string;
5
6
  className?: string;
6
7
  id?: string;
7
- children: preact.ComponentChildren;
8
+ children?: ASTNode[];
9
+ branches?: Branch[];
8
10
  }
9
11
 
10
12
  const registry = new Map<string, ComponentType<MacroProps>>();
@@ -19,3 +21,13 @@ export function registerMacro(
19
21
  export function getMacro(name: string): ComponentType<MacroProps> | undefined {
20
22
  return registry.get(name.toLowerCase());
21
23
  }
24
+
25
+ const subMacros = new Set<string>();
26
+
27
+ export function registerSubMacro(name: string): void {
28
+ subMacros.add(name.toLowerCase());
29
+ }
30
+
31
+ export function isSubMacro(name: string): boolean {
32
+ return subMacros.has(name.toLowerCase());
33
+ }