@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,14 +1,11 @@
1
1
  import { useRef, useContext } from 'preact/hooks';
2
2
  import { LocalsUpdateContext } from '../../markup/render';
3
- import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
4
3
  import { executeMutation } from '../../execute-mutation';
5
4
  import { currentSourceLocation } from '../../utils/source-location';
5
+ import { registerMacro } from '../../registry';
6
+ import type { MacroProps } from '../../registry';
6
7
 
7
- interface SetProps {
8
- rawArgs: string;
9
- }
10
-
11
- export function Set({ rawArgs }: SetProps) {
8
+ export function Set({ rawArgs }: MacroProps) {
12
9
  const { update, getValues } = useContext(LocalsUpdateContext);
13
10
  const ran = useRef(false);
14
11
 
@@ -16,7 +13,7 @@ export function Set({ rawArgs }: SetProps) {
16
13
  ran.current = true;
17
14
 
18
15
  try {
19
- executeMutation(rawArgs, stripLocalsPrefix(getValues()), update);
16
+ executeMutation(rawArgs, getValues(), update);
20
17
  } catch (err) {
21
18
  console.error(
22
19
  `spindle: Error in {set ${rawArgs}}${currentSourceLocation()}:`,
@@ -28,3 +25,5 @@ export function Set({ rawArgs }: SetProps) {
28
25
 
29
26
  return null;
30
27
  }
28
+
29
+ registerMacro('set', Set);
@@ -1,13 +1,10 @@
1
1
  import { useState } from 'preact/hooks';
2
2
  import { settings } from '../../settings';
3
3
  import { SettingsDialog } from '../SettingsDialog';
4
+ import { registerMacro } from '../../registry';
5
+ import type { MacroProps } from '../../registry';
4
6
 
5
- interface SettingsButtonProps {
6
- className?: string;
7
- id?: string;
8
- }
9
-
10
- export function SettingsButton({ className, id }: SettingsButtonProps) {
7
+ export function SettingsButton({ className, id }: MacroProps) {
11
8
  const [open, setOpen] = useState(false);
12
9
 
13
10
  if (!settings.hasAny()) return null;
@@ -27,3 +24,5 @@ export function SettingsButton({ className, id }: SettingsButtonProps) {
27
24
  </>
28
25
  );
29
26
  }
27
+
28
+ registerMacro('settings', SettingsButton);
@@ -1,7 +1,9 @@
1
1
  import { useLayoutEffect, useContext } from 'preact/hooks';
2
2
  import { RepeatContext } from './Repeat';
3
+ import { registerMacro } from '../../registry';
4
+ import type { MacroProps } from '../../registry';
3
5
 
4
- export function Stop() {
6
+ export function Stop(_props: MacroProps) {
5
7
  const { stop } = useContext(RepeatContext);
6
8
 
7
9
  useLayoutEffect(() => {
@@ -10,3 +12,5 @@ export function Stop() {
10
12
 
11
13
  return null;
12
14
  }
15
+
16
+ registerMacro('stop', Stop);
@@ -1,11 +1,8 @@
1
1
  import { useStoryStore } from '../../store';
2
+ import { registerMacro } from '../../registry';
3
+ import type { MacroProps } from '../../registry';
2
4
 
3
- interface StoryTitleProps {
4
- className?: string;
5
- id?: string;
6
- }
7
-
8
- export function StoryTitle({ className, id }: StoryTitleProps) {
5
+ export function StoryTitle({ className, id }: MacroProps) {
9
6
  const name = useStoryStore((s) => s.storyData?.name || '');
10
7
  const cls = className ? `story-title ${className}` : 'story-title';
11
8
 
@@ -18,3 +15,5 @@ export function StoryTitle({ className, id }: StoryTitleProps) {
18
15
  </span>
19
16
  );
20
17
  }
18
+
19
+ registerMacro('story-title', StoryTitle);
@@ -2,14 +2,11 @@ import { evaluate } from '../../expression';
2
2
  import { renderNodes } from '../../markup/render';
3
3
  import { useMergedLocals } from '../../hooks/use-merged-locals';
4
4
  import { currentSourceLocation } from '../../utils/source-location';
5
+ import { registerMacro, registerSubMacro } from '../../registry';
6
+ import type { MacroProps } from '../../registry';
5
7
  import type { Branch } from '../../markup/ast';
6
8
 
7
- interface SwitchProps {
8
- rawArgs: string;
9
- branches: Branch[];
10
- }
11
-
12
- export function Switch({ rawArgs, branches }: SwitchProps) {
9
+ export function Switch({ rawArgs, branches = [] }: MacroProps) {
13
10
  const [mergedVars, mergedTemps, mergedLocals] = useMergedLocals();
14
11
 
15
12
  let switchValue: unknown;
@@ -65,3 +62,7 @@ export function Switch({ rawArgs, branches }: SwitchProps) {
65
62
 
66
63
  return null;
67
64
  }
65
+
66
+ registerMacro('switch', Switch);
67
+ registerSubMacro('case');
68
+ registerSubMacro('default');
@@ -1,11 +1,7 @@
1
1
  import { useStoryStore } from '../../store';
2
2
  import { useAction } from '../../hooks/use-action';
3
-
4
- interface TextareaProps {
5
- rawArgs: string;
6
- className?: string;
7
- id?: string;
8
- }
3
+ import { registerMacro } from '../../registry';
4
+ import type { MacroProps } from '../../registry';
9
5
 
10
6
  function parseArgs(rawArgs: string): { varName: string; placeholder: string } {
11
7
  const match = rawArgs.match(/^\s*(["']?\$\w+["']?)\s*(?:["'](.*)["'])?\s*$/);
@@ -17,7 +13,7 @@ function parseArgs(rawArgs: string): { varName: string; placeholder: string } {
17
13
  return { varName, placeholder };
18
14
  }
19
15
 
20
- export function Textarea({ rawArgs, className, id }: TextareaProps) {
16
+ export function Textarea({ rawArgs, className, id }: MacroProps) {
21
17
  const { varName, placeholder } = parseArgs(rawArgs);
22
18
  const name = varName.startsWith('$') ? varName.slice(1) : varName;
23
19
 
@@ -48,3 +44,5 @@ export function Textarea({ rawArgs, className, id }: TextareaProps) {
48
44
  />
49
45
  );
50
46
  }
47
+
48
+ registerMacro('textarea', Textarea);
@@ -1,11 +1,7 @@
1
1
  import { useStoryStore } from '../../store';
2
2
  import { useAction } from '../../hooks/use-action';
3
-
4
- interface TextboxProps {
5
- rawArgs: string;
6
- className?: string;
7
- id?: string;
8
- }
3
+ import { registerMacro } from '../../registry';
4
+ import type { MacroProps } from '../../registry';
9
5
 
10
6
  function parseArgs(rawArgs: string): { varName: string; placeholder: string } {
11
7
  const match = rawArgs.match(/^\s*(["']?\$\w+["']?)\s*(?:["'](.*)["'])?\s*$/);
@@ -17,7 +13,7 @@ function parseArgs(rawArgs: string): { varName: string; placeholder: string } {
17
13
  return { varName, placeholder };
18
14
  }
19
15
 
20
- export function Textbox({ rawArgs, className, id }: TextboxProps) {
16
+ export function Textbox({ rawArgs, className, id }: MacroProps) {
21
17
  const { varName, placeholder } = parseArgs(rawArgs);
22
18
  const name = varName.startsWith('$') ? varName.slice(1) : varName;
23
19
 
@@ -47,3 +43,5 @@ export function Textbox({ rawArgs, className, id }: TextboxProps) {
47
43
  />
48
44
  );
49
45
  }
46
+
47
+ registerMacro('textbox', Textbox);
@@ -2,26 +2,21 @@ import { useState, useEffect, useMemo } from 'preact/hooks';
2
2
  import { renderNodes } from '../../markup/render';
3
3
  import { parseDelay } from '../../utils/parse-delay';
4
4
  import { useInterpolate } from '../../hooks/use-interpolate';
5
- import type { ASTNode, Branch } from '../../markup/ast';
6
-
7
- interface TimedProps {
8
- rawArgs: string;
9
- children: ASTNode[];
10
- branches: Branch[];
11
- className?: string;
12
- id?: string;
13
- }
5
+ import { registerMacro, registerSubMacro } from '../../registry';
6
+ import type { MacroProps } from '../../registry';
7
+ import type { ASTNode } from '../../markup/ast';
14
8
 
15
9
  export function Timed({
16
10
  rawArgs,
17
- children,
18
- branches,
11
+ children = [],
12
+ branches = [],
19
13
  className,
20
14
  id,
21
- }: TimedProps) {
15
+ }: MacroProps) {
22
16
  const resolve = useInterpolate();
23
- className = resolve(className);
24
- id = resolve(id);
17
+ const firstBranch = branches[0];
18
+ className = resolve(className ?? firstBranch?.className);
19
+ id = resolve(id ?? firstBranch?.id);
25
20
  // Section 0 = initial children, sections 1..N = {next} branches
26
21
  // Each section has its own delay
27
22
  const sections = useMemo(() => {
@@ -64,3 +59,6 @@ export function Timed({
64
59
  );
65
60
  return <>{content}</>;
66
61
  }
62
+
63
+ registerMacro('timed', Timed);
64
+ registerSubMacro('next');
@@ -2,16 +2,10 @@ import { useState, useEffect, useRef } from 'preact/hooks';
2
2
  import { renderInlineNodes } from '../../markup/render';
3
3
  import { parseDelay } from '../../utils/parse-delay';
4
4
  import { useInterpolate } from '../../hooks/use-interpolate';
5
- import type { ASTNode } from '../../markup/ast';
5
+ import { registerMacro } from '../../registry';
6
+ import type { MacroProps } from '../../registry';
6
7
 
7
- interface TypeProps {
8
- rawArgs: string;
9
- children: ASTNode[];
10
- className?: string;
11
- id?: string;
12
- }
13
-
14
- export function Type({ rawArgs, children, className, id }: TypeProps) {
8
+ export function Type({ rawArgs, children = [], className, id }: MacroProps) {
15
9
  const resolve = useInterpolate();
16
10
  className = resolve(className);
17
11
  id = resolve(id);
@@ -78,3 +72,5 @@ export function Type({ rawArgs, children, className, id }: TypeProps) {
78
72
  </span>
79
73
  );
80
74
  }
75
+
76
+ registerMacro('type', Type);
@@ -1,11 +1,9 @@
1
1
  import { useRef } from 'preact/hooks';
2
2
  import { useStoryStore } from '../../store';
3
+ import { registerMacro } from '../../registry';
4
+ import type { MacroProps } from '../../registry';
3
5
 
4
- interface UnsetProps {
5
- rawArgs: string;
6
- }
7
-
8
- export function Unset({ rawArgs }: UnsetProps) {
6
+ export function Unset({ rawArgs }: MacroProps) {
9
7
  const ran = useRef(false);
10
8
 
11
9
  if (!ran.current) {
@@ -26,3 +24,5 @@ export function Unset({ rawArgs }: UnsetProps) {
26
24
 
27
25
  return null;
28
26
  }
27
+
28
+ registerMacro('unset', Unset);
@@ -27,8 +27,7 @@ export function VarDisplay({ name, scope, className, id }: VarDisplayProps) {
27
27
 
28
28
  let value: unknown;
29
29
  if (scope === 'local') {
30
- const key = `@${root}`;
31
- value = key in localsValues ? localsValues[key] : undefined;
30
+ value = root in localsValues ? localsValues[root] : undefined;
32
31
  } else {
33
32
  value = storeValue;
34
33
  }
@@ -1,11 +1,7 @@
1
1
  import { useLayoutEffect } from 'preact/hooks';
2
2
  import { registerWidget } from '../../widgets/widget-registry';
3
- import type { ASTNode } from '../../markup/ast';
4
-
5
- interface WidgetProps {
6
- rawArgs: string;
7
- children: ASTNode[];
8
- }
3
+ import { registerMacro } from '../../registry';
4
+ import type { MacroProps } from '../../registry';
9
5
 
10
6
  /**
11
7
  * Parse widget definition args: "WidgetName" or "WidgetName" $param1 $param2
@@ -17,7 +13,7 @@ function parseWidgetDef(rawArgs: string): { name: string; params: string[] } {
17
13
  return { name, params };
18
14
  }
19
15
 
20
- export function Widget({ rawArgs, children }: WidgetProps) {
16
+ export function Widget({ rawArgs, children = [] }: MacroProps) {
21
17
  const { name, params } = parseWidgetDef(rawArgs);
22
18
 
23
19
  useLayoutEffect(() => {
@@ -26,3 +22,5 @@ export function Widget({ rawArgs, children }: WidgetProps) {
26
22
 
27
23
  return null;
28
24
  }
25
+
26
+ registerMacro('widget', Widget);
@@ -128,7 +128,7 @@ export function WidgetInvocation({
128
128
  value = undefined;
129
129
  }
130
130
  }
131
- ownKeys[param] = value;
131
+ ownKeys[param.startsWith('@') ? param.slice(1) : param] = value;
132
132
  }
133
133
 
134
134
  return (
@@ -26,7 +26,7 @@ export function executeMutation(
26
26
  }
27
27
  for (const key of Object.keys(localsClone)) {
28
28
  if (localsClone[key] !== mergedLocals[key]) {
29
- scopeUpdate(`@${key}`, localsClone[key]);
29
+ scopeUpdate(key, localsClone[key]);
30
30
  }
31
31
  }
32
32
  }
@@ -3,25 +3,8 @@ import { useStoryStore } from '../store';
3
3
  import { LocalsValuesContext } from '../markup/render';
4
4
 
5
5
  /**
6
- * Strip `@` prefix from locals scope values so they can be passed
7
- * directly to evaluate/execute.
8
- */
9
- export function stripLocalsPrefix(
10
- values: Record<string, unknown>,
11
- ): Record<string, unknown> {
12
- const locals: Record<string, unknown> = {};
13
- for (const [key, val] of Object.entries(values)) {
14
- if (key.startsWith('@')) {
15
- locals[key.slice(1)] = val;
16
- }
17
- }
18
- return locals;
19
- }
20
-
21
- /**
22
- * Return store variables, temporary, and @-prefixed locals from context.
23
- * Locals use `@` prefix keys internally; the returned locals dict has
24
- * the prefix stripped so it can be passed directly to evaluate/execute.
6
+ * Return store variables, temporary, and locals from context.
7
+ * All three dicts use unprefixed keys suitable for evaluate/execute.
25
8
  */
26
9
  export function useMergedLocals(): readonly [
27
10
  Record<string, unknown>,
@@ -33,6 +16,6 @@ export function useMergedLocals(): readonly [
33
16
  const localsValues = useContext(LocalsValuesContext);
34
17
 
35
18
  return useMemo(() => {
36
- return [variables, temporary, stripLocalsPrefix(localsValues)] as const;
19
+ return [variables, temporary, localsValues] as const;
37
20
  }, [variables, temporary, localsValues]);
38
21
  }
package/src/index.tsx CHANGED
@@ -14,6 +14,7 @@ import { tokenize } from './markup/tokenizer';
14
14
  import { buildAST } from './markup/ast';
15
15
  import { registerWidget } from './widgets/widget-registry';
16
16
  import type { ASTNode } from './markup/ast';
17
+ import './macros/register-builtins';
17
18
  import './styles.css';
18
19
 
19
20
  function renderErrors(root: HTMLElement, errors: string[]) {
@@ -0,0 +1,35 @@
1
+ // Import all built-in macro components to trigger their self-registration.
2
+ import '../components/macros/Set';
3
+ import '../components/macros/Print';
4
+ import '../components/macros/Computed';
5
+ import '../components/macros/Meter';
6
+ import '../components/macros/If';
7
+ import '../components/macros/For';
8
+ import '../components/macros/Do';
9
+ import '../components/macros/Button';
10
+ import '../components/macros/MacroLink';
11
+ import '../components/macros/Switch';
12
+ import '../components/macros/Timed';
13
+ import '../components/macros/Repeat';
14
+ import '../components/macros/Stop';
15
+ import '../components/macros/Type';
16
+ import '../components/macros/Widget';
17
+ import '../components/macros/StoryTitle';
18
+ import '../components/macros/Back';
19
+ import '../components/macros/Forward';
20
+ import '../components/macros/Restart';
21
+ import '../components/macros/QuickSave';
22
+ import '../components/macros/QuickLoad';
23
+ import '../components/macros/SettingsButton';
24
+ import '../components/macros/Saves';
25
+ import '../components/macros/Include';
26
+ import '../components/macros/Goto';
27
+ import '../components/macros/Unset';
28
+ import '../components/macros/Textbox';
29
+ import '../components/macros/Numberbox';
30
+ import '../components/macros/Textarea';
31
+ import '../components/macros/Checkbox';
32
+ import '../components/macros/Radiobutton';
33
+ import '../components/macros/Listbox';
34
+ import '../components/macros/Cycle';
35
+ import '../components/macros/PassageDisplay';