@rohal12/spindle 0.52.5 → 0.52.7

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 (43) hide show
  1. package/dist/pkg/format.js +1 -1
  2. package/dist/pkg/headless.js +705 -576
  3. package/dist/pkg/macro-registry.json +255 -0
  4. package/dist/pkg/story-variables.js +54 -32
  5. package/package.json +1 -1
  6. package/src/components/macros/Button.tsx +2 -4
  7. package/src/components/macros/Checkbox.tsx +6 -13
  8. package/src/components/macros/Computed.tsx +38 -55
  9. package/src/components/macros/Cycle.tsx +1 -0
  10. package/src/components/macros/Dialog.tsx +7 -8
  11. package/src/components/macros/Do.tsx +2 -1
  12. package/src/components/macros/For.tsx +27 -78
  13. package/src/components/macros/Goto.tsx +6 -9
  14. package/src/components/macros/Include.tsx +10 -57
  15. package/src/components/macros/Listbox.tsx +1 -0
  16. package/src/components/macros/MacroError.tsx +37 -0
  17. package/src/components/macros/MacroLink.tsx +9 -24
  18. package/src/components/macros/Meter.tsx +19 -37
  19. package/src/components/macros/Numberbox.tsx +5 -32
  20. package/src/components/macros/PassageDisplay.tsx +37 -47
  21. package/src/components/macros/Print.tsx +1 -0
  22. package/src/components/macros/Radiobutton.tsx +8 -38
  23. package/src/components/macros/Repeat.tsx +3 -3
  24. package/src/components/macros/Set.tsx +3 -29
  25. package/src/components/macros/Switch.tsx +36 -37
  26. package/src/components/macros/Textarea.tsx +2 -28
  27. package/src/components/macros/Textbox.tsx +2 -29
  28. package/src/components/macros/Type.tsx +3 -3
  29. package/src/components/macros/Unset.tsx +17 -40
  30. package/src/components/macros/Watch.tsx +23 -91
  31. package/src/components/macros/WidgetInvocation.tsx +9 -71
  32. package/src/components/macros/detached-body.tsx +2 -2
  33. package/src/components/macros/input-macro.ts +48 -0
  34. package/src/components/macros/locals-scope.tsx +60 -0
  35. package/src/components/macros/macro-args.ts +280 -0
  36. package/src/components/macros/option-utils.ts +7 -12
  37. package/src/define-macro.ts +47 -30
  38. package/src/hooks/use-render-options.ts +26 -0
  39. package/src/js-lexer.ts +80 -45
  40. package/src/markup/render.tsx +2 -4
  41. package/src/registry.ts +48 -0
  42. package/types/index.d.ts +69 -6
  43. package/types/tooling.d.ts +33 -1
@@ -3,6 +3,13 @@
3
3
  "name": "set",
4
4
  "block": false,
5
5
  "subMacros": [],
6
+ "parameters": [
7
+ {
8
+ "name": "code",
9
+ "type": "expression",
10
+ "required": true
11
+ }
12
+ ],
6
13
  "source": "builtin"
7
14
  },
8
15
  {
@@ -11,6 +18,13 @@
11
18
  "subMacros": [],
12
19
  "interpolate": true,
13
20
  "merged": true,
21
+ "parameters": [
22
+ {
23
+ "name": "expression",
24
+ "type": "expression",
25
+ "required": true
26
+ }
27
+ ],
14
28
  "source": "builtin"
15
29
  },
16
30
  {
@@ -18,6 +32,22 @@
18
32
  "block": false,
19
33
  "subMacros": [],
20
34
  "merged": true,
35
+ "parameters": [
36
+ {
37
+ "name": "target",
38
+ "type": "variable",
39
+ "required": true
40
+ },
41
+ {
42
+ "name": "=",
43
+ "type": "separator"
44
+ },
45
+ {
46
+ "name": "expression",
47
+ "type": "expression",
48
+ "required": true
49
+ }
50
+ ],
21
51
  "source": "builtin"
22
52
  },
23
53
  {
@@ -26,6 +56,22 @@
26
56
  "subMacros": [],
27
57
  "interpolate": true,
28
58
  "merged": true,
59
+ "parameters": [
60
+ {
61
+ "name": "current",
62
+ "type": "expression",
63
+ "required": true
64
+ },
65
+ {
66
+ "name": "max",
67
+ "type": "expression",
68
+ "required": true
69
+ },
70
+ {
71
+ "name": "label",
72
+ "type": "string"
73
+ }
74
+ ],
29
75
  "source": "builtin"
30
76
  },
31
77
  {
@@ -42,6 +88,22 @@
42
88
  "subMacros": [],
43
89
  "interpolate": true,
44
90
  "merged": true,
91
+ "parameters": [
92
+ {
93
+ "name": "variables",
94
+ "type": "names",
95
+ "required": true
96
+ },
97
+ {
98
+ "name": "of",
99
+ "type": "separator"
100
+ },
101
+ {
102
+ "name": "list",
103
+ "type": "expression",
104
+ "required": true
105
+ }
106
+ ],
45
107
  "source": "builtin"
46
108
  },
47
109
  {
@@ -55,6 +117,13 @@
55
117
  "block": true,
56
118
  "subMacros": [],
57
119
  "interpolate": true,
120
+ "parameters": [
121
+ {
122
+ "name": "delay",
123
+ "type": "delay",
124
+ "required": true
125
+ }
126
+ ],
58
127
  "source": "builtin"
59
128
  },
60
129
  {
@@ -62,6 +131,13 @@
62
131
  "block": true,
63
132
  "subMacros": [],
64
133
  "interpolate": true,
134
+ "parameters": [
135
+ {
136
+ "name": "label",
137
+ "type": "text",
138
+ "required": true
139
+ }
140
+ ],
65
141
  "source": "builtin"
66
142
  },
67
143
  {
@@ -69,6 +145,17 @@
69
145
  "block": true,
70
146
  "subMacros": [],
71
147
  "interpolate": true,
148
+ "parameters": [
149
+ {
150
+ "name": "text",
151
+ "type": "string",
152
+ "required": true
153
+ },
154
+ {
155
+ "name": "passage",
156
+ "type": "string"
157
+ }
158
+ ],
72
159
  "source": "builtin"
73
160
  },
74
161
  {
@@ -79,6 +166,13 @@
79
166
  "default"
80
167
  ],
81
168
  "merged": true,
169
+ "parameters": [
170
+ {
171
+ "name": "expression",
172
+ "type": "expression",
173
+ "required": true
174
+ }
175
+ ],
82
176
  "source": "builtin"
83
177
  },
84
178
  {
@@ -101,6 +195,13 @@
101
195
  "block": true,
102
196
  "subMacros": [],
103
197
  "interpolate": true,
198
+ "parameters": [
199
+ {
200
+ "name": "delay",
201
+ "type": "delay",
202
+ "required": true
203
+ }
204
+ ],
104
205
  "source": "builtin"
105
206
  },
106
207
  {
@@ -162,6 +263,17 @@
162
263
  "block": true,
163
264
  "subMacros": [],
164
265
  "interpolate": true,
266
+ "parameters": [
267
+ {
268
+ "name": "label",
269
+ "type": "text",
270
+ "required": true
271
+ },
272
+ {
273
+ "name": "noclose",
274
+ "type": "flag"
275
+ }
276
+ ],
165
277
  "source": "builtin"
166
278
  },
167
279
  {
@@ -182,6 +294,17 @@
182
294
  "subMacros": [],
183
295
  "interpolate": true,
184
296
  "merged": true,
297
+ "parameters": [
298
+ {
299
+ "name": "inline",
300
+ "type": "flag"
301
+ },
302
+ {
303
+ "name": "passage",
304
+ "type": "expression",
305
+ "required": true
306
+ }
307
+ ],
185
308
  "source": "builtin"
186
309
  },
187
310
  {
@@ -195,12 +318,26 @@
195
318
  "block": false,
196
319
  "subMacros": [],
197
320
  "merged": true,
321
+ "parameters": [
322
+ {
323
+ "name": "passage",
324
+ "type": "expression",
325
+ "required": true
326
+ }
327
+ ],
198
328
  "source": "builtin"
199
329
  },
200
330
  {
201
331
  "name": "unset",
202
332
  "block": false,
203
333
  "subMacros": [],
334
+ "parameters": [
335
+ {
336
+ "name": "variable",
337
+ "type": "variable",
338
+ "required": true
339
+ }
340
+ ],
204
341
  "source": "builtin"
205
342
  },
206
343
  {
@@ -208,6 +345,17 @@
208
345
  "block": false,
209
346
  "subMacros": [],
210
347
  "storeVar": true,
348
+ "parameters": [
349
+ {
350
+ "name": "variable",
351
+ "type": "variable",
352
+ "required": true
353
+ },
354
+ {
355
+ "name": "placeholder",
356
+ "type": "string"
357
+ }
358
+ ],
211
359
  "source": "builtin"
212
360
  },
213
361
  {
@@ -215,6 +363,17 @@
215
363
  "block": false,
216
364
  "subMacros": [],
217
365
  "storeVar": true,
366
+ "parameters": [
367
+ {
368
+ "name": "variable",
369
+ "type": "variable",
370
+ "required": true
371
+ },
372
+ {
373
+ "name": "placeholder",
374
+ "type": "string"
375
+ }
376
+ ],
218
377
  "source": "builtin"
219
378
  },
220
379
  {
@@ -222,6 +381,17 @@
222
381
  "block": false,
223
382
  "subMacros": [],
224
383
  "storeVar": true,
384
+ "parameters": [
385
+ {
386
+ "name": "variable",
387
+ "type": "variable",
388
+ "required": true
389
+ },
390
+ {
391
+ "name": "placeholder",
392
+ "type": "string"
393
+ }
394
+ ],
225
395
  "source": "builtin"
226
396
  },
227
397
  {
@@ -229,6 +399,17 @@
229
399
  "block": false,
230
400
  "subMacros": [],
231
401
  "storeVar": true,
402
+ "parameters": [
403
+ {
404
+ "name": "variable",
405
+ "type": "variable",
406
+ "required": true
407
+ },
408
+ {
409
+ "name": "label",
410
+ "type": "text"
411
+ }
412
+ ],
232
413
  "source": "builtin"
233
414
  },
234
415
  {
@@ -236,6 +417,22 @@
236
417
  "block": false,
237
418
  "subMacros": [],
238
419
  "storeVar": true,
420
+ "parameters": [
421
+ {
422
+ "name": "variable",
423
+ "type": "variable",
424
+ "required": true
425
+ },
426
+ {
427
+ "name": "value",
428
+ "type": "text",
429
+ "required": true
430
+ },
431
+ {
432
+ "name": "label",
433
+ "type": "text"
434
+ }
435
+ ],
239
436
  "source": "builtin"
240
437
  },
241
438
  {
@@ -245,6 +442,13 @@
245
442
  "option"
246
443
  ],
247
444
  "storeVar": true,
445
+ "parameters": [
446
+ {
447
+ "name": "variable",
448
+ "type": "variable",
449
+ "required": true
450
+ }
451
+ ],
248
452
  "source": "builtin"
249
453
  },
250
454
  {
@@ -252,6 +456,13 @@
252
456
  "block": true,
253
457
  "subMacros": [],
254
458
  "storeVar": true,
459
+ "parameters": [
460
+ {
461
+ "name": "variable",
462
+ "type": "variable",
463
+ "required": true
464
+ }
465
+ ],
255
466
  "source": "builtin"
256
467
  },
257
468
  {
@@ -271,12 +482,56 @@
271
482
  "name": "watch",
272
483
  "block": false,
273
484
  "subMacros": [],
485
+ "parameters": [
486
+ {
487
+ "name": "condition",
488
+ "type": "string",
489
+ "required": true
490
+ },
491
+ {
492
+ "name": "options",
493
+ "type": "options",
494
+ "parameters": [
495
+ {
496
+ "name": "goto",
497
+ "type": "string"
498
+ },
499
+ {
500
+ "name": "dialog",
501
+ "type": "string"
502
+ },
503
+ {
504
+ "name": "run",
505
+ "type": "string"
506
+ },
507
+ {
508
+ "name": "name",
509
+ "type": "string"
510
+ },
511
+ {
512
+ "name": "priority",
513
+ "type": "number"
514
+ },
515
+ {
516
+ "name": "once",
517
+ "type": "flag"
518
+ }
519
+ ]
520
+ }
521
+ ],
274
522
  "source": "builtin"
275
523
  },
276
524
  {
277
525
  "name": "unwatch",
278
526
  "block": false,
279
527
  "subMacros": [],
528
+ "parameters": [
529
+ {
530
+ "name": "name",
531
+ "type": "text",
532
+ "required": true
533
+ }
534
+ ],
280
535
  "source": "builtin"
281
536
  }
282
537
  ]
@@ -248,6 +248,13 @@ var UNCLOSED = -1;
248
248
  var MALFORMED = -2;
249
249
  /** A frame result: the scan stops at index `s` inside it (`STOPPED - s`). */
250
250
  var STOPPED = -3;
251
+ /**
252
+ * The result of a frame that closes at `end`. `bodyStarted`: the code inside
253
+ * started a function or class body. That body replaced the one to come, and
254
+ * is gone once the frame is closed, so after the frame no body is to come —
255
+ * a scan that skips the frame must know, as must the frames around it.
256
+ */
257
+ var frameEnd = (end, bodyStarted) => end * 2 + +bodyStarted;
251
258
  /** The closers whose search for their frame a frame result may depend on. */
252
259
  var CLOSERS = [
253
260
  ")",
@@ -480,14 +487,30 @@ function scan(src, handlers, start, nesting, outer, ctx) {
480
487
  ]] : void 0;
481
488
  /**
482
489
  * How many function or class bodies to come the scan has started, and with
483
- * `parens`, that count when each open frame opened. Code in a `(…)` or
484
- * `[…]` that starts one replaces the one to come, and the new one can't
485
- * follow once the frame is closed: none is to come then.
490
+ * `braces` or `parens`, that count when each open frame opened. Code in a
491
+ * frame that starts one replaces the one to come, and the new one can't
492
+ * follow once the frame is closed: none is to come then. A scan skipping
493
+ * the frame learns this from its result (`frameEnd`, `resume`), and counts
494
+ * the body as started too, for the frames around it to record.
486
495
  */
487
496
  let bodyStarts = 0;
488
- const startsAt = parens ? [0] : void 0;
497
+ const startsAt = braces || parens ? [0] : void 0;
489
498
  /** A `(…)` or `[…]` frame result found in `parens`, to skip to. */
490
499
  let parenTo;
500
+ /** The result of the frame at index `k`, closing at `i`. */
501
+ const closedAt = (k) => frameEnd(i, startsAt !== void 0 && bodyStarts !== startsAt[k]);
502
+ /**
503
+ * Go on after a frame an earlier scan lexed, whose result (`frameEnd`) is
504
+ * `result`: returns the index it closes at. A body started in it is gone
505
+ * after it, with the one it replaced.
506
+ */
507
+ function resume(result) {
508
+ if (result % 2 === 1) {
509
+ pendingBody = void 0;
510
+ bodyStarts++;
511
+ }
512
+ return Math.floor(result / 2);
513
+ }
491
514
  /** The closer `c` looked for its frame and found index `k`. */
492
515
  function lookedFor(c, k) {
493
516
  if (!lowest) return;
@@ -635,8 +658,8 @@ function scan(src, handlers, start, nesting, outer, ctx) {
635
658
  if (lowest) {
636
659
  for (let j = frames.length - 1; j >= k; j--) foldLowest(j);
637
660
  lowest.length = k;
638
- startsAt.length = k;
639
661
  }
662
+ if (startsAt) startsAt.length = k;
640
663
  frames.length = k;
641
664
  if (stackIds) stackIds.length = k;
642
665
  for (const list of Object.values(closers)) while (list.length && list[list.length - 1] >= k) list.pop();
@@ -713,6 +736,18 @@ function scan(src, handlers, start, nesting, outer, ctx) {
713
736
  stmtNext = f.kind === "block";
714
737
  keyNext = f.kind === "object" || f.kind === "class";
715
738
  }
739
+ /**
740
+ * Go past frame `f` if an earlier scan lexed it: to just after it, or to
741
+ * the end of the source when it never closes. Whether it did, or
742
+ * MALFORMED when that scan found the code malformed.
743
+ */
744
+ function skipLexed(f) {
745
+ const end = known(f);
746
+ if (end === void 0) return false;
747
+ if (end === MALFORMED) return MALFORMED;
748
+ i = end === UNCLOSED ? src.length : resume(end) + 1;
749
+ return true;
750
+ }
716
751
  function openBrace() {
717
752
  let f;
718
753
  if (pendingBody?.depth === frames.length) {
@@ -756,10 +791,10 @@ function scan(src, handlers, start, nesting, outer, ctx) {
756
791
  return;
757
792
  }
758
793
  const closed = frames[k];
794
+ const result = closedAt(k);
759
795
  if (lowest && c !== "}") {
760
796
  for (let j = frames.length - 1; j > k; j--) foldLowest(j);
761
- const bodyStarted = bodyStarts !== startsAt[k];
762
- recordParen(k, i * 2 + +bodyStarted);
797
+ recordParen(k, result);
763
798
  }
764
799
  truncate(k);
765
800
  if (c === "]" && !ctx.lookahead) ctx.brackets.set(closed.open, i);
@@ -768,7 +803,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
768
803
  stmtNext = closed.header;
769
804
  } else if (c === "]") endPunct(c, false);
770
805
  else {
771
- record(closed, i);
806
+ record(closed, result);
772
807
  afterBrace(closed);
773
808
  }
774
809
  }
@@ -870,23 +905,16 @@ function scan(src, handlers, start, nesting, outer, ctx) {
870
905
  } else if (ch === "`") {
871
906
  literal(ch, i);
872
907
  if (frames.length === 1) return i + 1;
873
- record(top(), i);
908
+ record(top(), closedAt(frames.length - 1));
874
909
  i++;
875
910
  truncate(frames.length - 1);
876
911
  endOperand();
877
912
  } else if (ch === "$" && src.charAt(i + 1) === "{") {
878
913
  const f = frame("expr", "}", i);
879
914
  f.interpolation = true;
880
- const end = known(f);
881
- if (end === MALFORMED) return malformed();
882
- if (end === UNCLOSED) {
883
- i = src.length;
884
- break;
885
- }
886
- if (end !== void 0) {
887
- i = end + 1;
888
- continue;
889
- }
915
+ const skipped = skipLexed(f);
916
+ if (skipped === MALFORMED) return malformed();
917
+ if (skipped) continue;
890
918
  literal("${", i);
891
919
  i += 2;
892
920
  nesting++;
@@ -913,14 +941,9 @@ function scan(src, handlers, start, nesting, outer, ctx) {
913
941
  if (ch === "`") {
914
942
  endWord();
915
943
  const f = frame("template", "`", i);
916
- const end = known(f);
917
- if (end === MALFORMED) return malformed();
918
- if (end === UNCLOSED) {
919
- i = src.length;
920
- break;
921
- }
922
- if (end !== void 0) {
923
- i = end + 1;
944
+ const skipped = skipLexed(f);
945
+ if (skipped === MALFORMED) return malformed();
946
+ if (skipped) {
924
947
  endOperand();
925
948
  continue;
926
949
  }
@@ -939,7 +962,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
939
962
  lookedFor("}", k);
940
963
  if (frames[k].interpolation) {
941
964
  endWord();
942
- record(frames[k], i);
965
+ record(frames[k], closedAt(k));
943
966
  truncate(k);
944
967
  nesting--;
945
968
  literal(ch, i);
@@ -1021,7 +1044,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1021
1044
  continue;
1022
1045
  }
1023
1046
  }
1024
- if (ch === outer.closer && frames.length === 1) break;
1047
+ if (ch === outer.closer && (frames.length === 1 || ch === "]" && innermost("]") === 0 && innermost("}") === 0)) break;
1025
1048
  trackCode(ch);
1026
1049
  if (skipTo) {
1027
1050
  const { frame: skipped, end } = skipTo;
@@ -1031,7 +1054,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1031
1054
  i = src.length;
1032
1055
  break;
1033
1056
  }
1034
- i = end;
1057
+ i = resume(end);
1035
1058
  afterBrace(skipped);
1036
1059
  i++;
1037
1060
  continue;
@@ -1049,8 +1072,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1049
1072
  strict.stopped = true;
1050
1073
  return STOPPED - result;
1051
1074
  }
1052
- i = Math.floor(result / 2);
1053
- if (result % 2 === 1) pendingBody = void 0;
1075
+ i = resume(result);
1054
1076
  if (skipped.closer === ")") {
1055
1077
  endPunct(")", skipped.header);
1056
1078
  stmtNext = skipped.header;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohal12/spindle",
3
- "version": "0.52.5",
3
+ "version": "0.52.7",
4
4
  "type": "module",
5
5
  "description": "A Preact-based story format for Twine 2.",
6
6
  "license": "Unlicense",
@@ -1,15 +1,13 @@
1
1
  import { defineMacro } from '../../define-macro';
2
- import { readWholeQuoted } from './arg-utils';
3
2
  import { useDetachedBody } from './detached-body';
4
3
 
5
4
  defineMacro({
6
5
  name: 'button',
7
6
  block: true,
8
7
  interpolate: true,
8
+ parameters: [{ name: 'label', type: 'text', required: true }],
9
9
  render({ rawArgs, children = [] }, ctx) {
10
- const text =
11
- readWholeQuoted(rawArgs.trim()) ?? rawArgs.replace(/^["']|["']$/g, '');
12
- const label = ctx.resolve?.(text) ?? rawArgs;
10
+ const label = ctx.resolve?.(ctx.args.label ?? '') ?? rawArgs;
13
11
  const runBody = useDetachedBody();
14
12
 
15
13
  // Run the body outside the passage tree: all macro side effects ({set},
@@ -1,21 +1,14 @@
1
1
  import { defineMacro } from '../../define-macro';
2
- import { readWholeQuoted, stripLooseQuotes } from './arg-utils';
3
-
4
- export function parseCheckboxLabel(rawArgs: string): string {
5
- // `s`: a quoted label may span lines. Its trailing whitespace is trimmed
6
- // here, not by the regex: `(.+?)\s*$` tries every position of a run of
7
- // spaces in the label, taking time quadratic in its length.
8
- const match = rawArgs.match(/^\s*["']?\$?[\w.]+["']?\s+(.+)$/s);
9
- if (!match) return '';
10
- const rest = match[1]!.trimEnd() || match[1]!;
11
- return readWholeQuoted(rest) ?? stripLooseQuotes(rest);
12
- }
13
2
 
14
3
  defineMacro({
15
4
  name: 'checkbox',
16
5
  storeVar: true,
17
- render({ rawArgs }, ctx) {
18
- const label = parseCheckboxLabel(rawArgs);
6
+ parameters: [
7
+ { name: 'variable', type: 'variable', required: true },
8
+ { name: 'label', type: 'text' },
9
+ ],
10
+ render(_props, ctx) {
11
+ const label = ctx.args.label ?? '';
19
12
 
20
13
  ctx.useAction({
21
14
  type: 'checkbox',