@rohal12/spindle 0.52.6 → 0.52.8

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 (48) hide show
  1. package/README.md +26 -0
  2. package/dist/pkg/format.js +1 -1
  3. package/dist/pkg/headless.js +764 -735
  4. package/dist/pkg/macro-registry.json +18 -0
  5. package/dist/pkg/story-variables.js +54 -32
  6. package/package.json +1 -1
  7. package/src/action-registry.ts +9 -0
  8. package/src/class-registry.ts +1 -5
  9. package/src/components/Passage.tsx +35 -39
  10. package/src/components/PassageDialog.tsx +2 -2
  11. package/src/components/StoryInterface.tsx +2 -2
  12. package/src/components/macros/Checkbox.tsx +3 -8
  13. package/src/components/macros/Computed.tsx +1 -1
  14. package/src/components/macros/Cycle.tsx +3 -6
  15. package/src/components/macros/ExprDisplay.tsx +12 -29
  16. package/src/components/macros/If.tsx +35 -41
  17. package/src/components/macros/Include.tsx +2 -1
  18. package/src/components/macros/Listbox.tsx +5 -7
  19. package/src/components/macros/Print.tsx +3 -5
  20. package/src/components/macros/Radiobutton.tsx +3 -5
  21. package/src/components/macros/Repeat.tsx +2 -1
  22. package/src/components/macros/Switch.tsx +6 -13
  23. package/src/components/macros/Timed.tsx +14 -18
  24. package/src/components/macros/Type.tsx +2 -1
  25. package/src/components/macros/VarDisplay.tsx +12 -32
  26. package/src/components/macros/Widget.tsx +49 -20
  27. package/src/components/macros/branches.ts +18 -0
  28. package/src/components/macros/display.ts +19 -0
  29. package/src/components/macros/input-macro.ts +33 -11
  30. package/src/components/macros/macro-args.ts +7 -0
  31. package/src/components/macros/option-utils.ts +10 -7
  32. package/src/define-macro.ts +4 -6
  33. package/src/expression.ts +58 -40
  34. package/src/hooks/use-action.ts +7 -15
  35. package/src/index.tsx +5 -15
  36. package/src/interpolation.ts +34 -41
  37. package/src/js-lexer.ts +80 -45
  38. package/src/markup/ast.ts +4 -3
  39. package/src/markup/render.tsx +4 -12
  40. package/src/registry.ts +13 -12
  41. package/src/saves/save-manager.ts +18 -13
  42. package/src/saves/storage.ts +19 -20
  43. package/src/story-api.ts +30 -73
  44. package/src/structural.ts +1 -18
  45. package/src/types-drift-check.ts +37 -50
  46. package/src/utils/macro-names.ts +40 -0
  47. package/src/utils/namespace.ts +17 -0
  48. package/src/widgets/widget-registry.ts +5 -4
@@ -1,5 +1,6 @@
1
1
  import { defineMacro } from '../../define-macro';
2
2
  import { MacroError } from './MacroError';
3
+ import { display } from './display';
3
4
 
4
5
  defineMacro({
5
6
  name: 'print',
@@ -8,9 +9,7 @@ defineMacro({
8
9
  merged: true,
9
10
  render({ rawArgs }, ctx) {
10
11
  try {
11
- const result = ctx.evaluate!(rawArgs);
12
- const display = result == null ? '' : String(result);
13
- return ctx.wrap(display);
12
+ return ctx.wrap(display(ctx.evaluate!(rawArgs)));
14
13
  } catch (err) {
15
14
  return (
16
15
  <MacroError
@@ -21,7 +20,6 @@ defineMacro({
21
20
  }
22
21
  },
23
22
  text({ rawArgs }, ctx) {
24
- const result = ctx.evaluate(rawArgs);
25
- return result == null ? '' : String(result);
23
+ return display(ctx.evaluate(rawArgs));
26
24
  },
27
25
  });
@@ -1,10 +1,11 @@
1
1
  import { defineMacro } from '../../define-macro';
2
+ import { VARIABLE_PARAMETER, useVariableAction } from './input-macro';
2
3
 
3
4
  defineMacro({
4
5
  name: 'radiobutton',
5
6
  storeVar: true,
6
7
  parameters: [
7
- { name: 'variable', type: 'variable', required: true },
8
+ VARIABLE_PARAMETER,
8
9
  { name: 'value', type: 'text', required: true },
9
10
  { name: 'label', type: 'text' },
10
11
  ],
@@ -12,13 +13,10 @@ defineMacro({
12
13
  const radioValue = ctx.args.value ?? '';
13
14
  const label = ctx.args.label ?? '';
14
15
 
15
- ctx.useAction({
16
+ useVariableAction(ctx, {
16
17
  type: 'radiobutton',
17
18
  key: `$${ctx.varName}:${radioValue}`,
18
- authorId: ctx.id,
19
19
  label: label || radioValue,
20
- variable: ctx.varName,
21
- value: ctx.value,
22
20
  perform: () => ctx.setValue!(radioValue),
23
21
  });
24
22
 
@@ -1,5 +1,6 @@
1
1
  import { createContext } from 'preact';
2
2
  import { defineMacro } from '../../define-macro';
3
+ import { DELAY_PARAMETER } from './macro-args';
3
4
 
4
5
  export const RepeatContext = createContext<{ stop: () => void }>({
5
6
  stop: () => {},
@@ -9,7 +10,7 @@ defineMacro({
9
10
  name: 'repeat',
10
11
  block: true,
11
12
  interpolate: true,
12
- parameters: [{ name: 'delay', type: 'delay', required: true }],
13
+ parameters: [DELAY_PARAMETER],
13
14
  render({ children = [] }, ctx) {
14
15
  const { useState, useEffect, useCallback } = ctx.hooks;
15
16
 
@@ -1,26 +1,19 @@
1
1
  import { defineMacro } from '../../define-macro';
2
2
  import { MacroError } from './MacroError';
3
3
  import type { Branch } from '../../markup/ast';
4
+ import { selectBranch } from './branches';
4
5
 
5
6
  /**
6
7
  * The first {case} branch whose value equals `value`, else the {default}
7
8
  * branch, else null. Throws what a case expression throws.
8
9
  */
9
- function selectBranch(
10
+ function selectCase(
10
11
  value: unknown,
11
12
  branches: Branch[],
12
13
  evaluate: (expr: string) => unknown,
13
14
  ): Branch | null {
14
- let defaultBranch: Branch | null = null;
15
- for (let i = 1; i < branches.length; i++) {
16
- const branch = branches[i]!;
17
- if (branch.rawArgs === '') {
18
- defaultBranch = branch;
19
- } else if (value === evaluate(branch.rawArgs)) {
20
- return branch;
21
- }
22
- }
23
- return defaultBranch;
15
+ // The first branch holds the content before the first {case}
16
+ return selectBranch(branches.slice(1), (expr) => value === evaluate(expr));
24
17
  }
25
18
 
26
19
  defineMacro({
@@ -43,7 +36,7 @@ defineMacro({
43
36
 
44
37
  let branch: Branch | null;
45
38
  try {
46
- branch = selectBranch(switchValue, branches, ctx.evaluate!);
39
+ branch = selectCase(switchValue, branches, ctx.evaluate!);
47
40
  } catch (err) {
48
41
  return (
49
42
  <MacroError
@@ -56,7 +49,7 @@ defineMacro({
56
49
  return branch && <>{ctx.renderNodes(branch.children)}</>;
57
50
  },
58
51
  text({ rawArgs, branches = [] }, ctx) {
59
- const branch = selectBranch(ctx.evaluate(rawArgs), branches, ctx.evaluate);
52
+ const branch = selectCase(ctx.evaluate(rawArgs), branches, ctx.evaluate);
60
53
  return branch ? ctx.renderText(branch.children) : '';
61
54
  },
62
55
  });
@@ -1,16 +1,21 @@
1
- import { parseDelay } from '../../utils/parse-delay';
2
1
  import { defineMacro } from '../../define-macro';
2
+ import { DELAY_PARAMETER, parseMacroArgs } from './macro-args';
3
+ import { wrapContent } from './display';
4
+
5
+ /** The parameters of {timed} and of each {next}: the delay before it shows. */
6
+ const TIMED_PARAMETERS = [DELAY_PARAMETER] as const;
3
7
 
4
8
  defineMacro({
5
9
  name: 'timed',
6
10
  subMacros: ['next'],
7
11
  interpolate: true,
12
+ parameters: TIMED_PARAMETERS,
8
13
  render({ branches = [] }, ctx) {
9
14
  const { useState, useEffect, useMemo } = ctx.hooks;
10
15
 
11
16
  const sections = useMemo(() => {
12
17
  return branches.map((branch) => ({
13
- delay: branch.rawArgs ? parseDelay(branch.rawArgs) : 0,
18
+ delay: parseMacroArgs(branch.rawArgs, TIMED_PARAMETERS).delay ?? 0,
14
19
  nodes: branch.children,
15
20
  className: branch.className,
16
21
  id: branch.id,
@@ -35,21 +40,12 @@ defineMacro({
35
40
  if (visibleIndex < 0) return ctx.wrap(null);
36
41
 
37
42
  const section = sections[visibleIndex]!;
38
- const content = ctx.renderNodes(section.nodes);
39
- const sectionClass = ctx.resolve!(section.className);
40
- const sectionId = ctx.resolve!(section.id);
41
-
42
- const inner =
43
- sectionClass || sectionId ? (
44
- <span
45
- id={sectionId}
46
- class={sectionClass}
47
- >
48
- {content}
49
- </span>
50
- ) : (
51
- content
52
- );
53
- return ctx.wrap(inner);
43
+ return ctx.wrap(
44
+ wrapContent(
45
+ ctx.resolve!(section.className),
46
+ ctx.resolve!(section.id),
47
+ ctx.renderNodes(section.nodes),
48
+ ),
49
+ );
54
50
  },
55
51
  });
@@ -1,10 +1,11 @@
1
1
  import { defineMacro } from '../../define-macro';
2
+ import { DELAY_PARAMETER } from './macro-args';
2
3
 
3
4
  defineMacro({
4
5
  name: 'type',
5
6
  block: true,
6
7
  interpolate: true,
7
- parameters: [{ name: 'delay', type: 'delay', required: true }],
8
+ parameters: [DELAY_PARAMETER],
8
9
  render({ children = [] }, ctx) {
9
10
  const { useState, useEffect, useRef } = ctx.hooks;
10
11
  const speed = ctx.args.delay ?? 0;
@@ -3,13 +3,9 @@ import { useContext } from 'preact/hooks';
3
3
  import { LocalsValuesContext } from '../../markup/render';
4
4
  import { useInterpolate } from '../../hooks/use-interpolate';
5
5
  import { ownValue, variableNameError } from '../../utils/namespace';
6
-
7
- const SIGILS = {
8
- variable: '$',
9
- temporary: '_',
10
- local: '@',
11
- transient: '%',
12
- } as const;
6
+ import { SCOPE_SIGILS } from '../../markup/tokenizer';
7
+ import type { VariableNode } from '../../markup/ast';
8
+ import { display, wrapContent } from './display';
13
9
 
14
10
  const STORE_NAMESPACES = {
15
11
  variable: 'variables',
@@ -17,17 +13,12 @@ const STORE_NAMESPACES = {
17
13
  transient: 'transient',
18
14
  } as const;
19
15
 
20
- interface VarDisplayProps {
21
- name: string;
22
- scope: 'variable' | 'temporary' | 'local' | 'transient';
23
- className?: string;
24
- id?: string;
25
- }
26
-
27
- export function VarDisplay({ name, scope, className, id }: VarDisplayProps) {
16
+ /** A `{$name}` (`{_name}`, `{@name}`, `{%name}`) variable display. */
17
+ export function VarDisplay({ node }: { node: VariableNode }) {
18
+ const { name, scope } = node;
28
19
  const resolve = useInterpolate();
29
- className = resolve(className);
30
- id = resolve(id);
20
+ const className = resolve(node.className);
21
+ const id = resolve(node.id);
31
22
  const localsValues = useContext(LocalsValuesContext);
32
23
  const parts = name.split('.');
33
24
  const root = parts[0]!;
@@ -36,11 +27,10 @@ export function VarDisplay({ name, scope, className, id }: VarDisplayProps) {
36
27
  scope === 'local' ? undefined : ownValue(s[STORE_NAMESPACES[scope]], root),
37
28
  );
38
29
 
39
- const nameError = variableNameError(root, SIGILS[scope] + root);
30
+ const sigil = SCOPE_SIGILS[scope];
31
+ const nameError = variableNameError(root, sigil + root);
40
32
  if (nameError) {
41
- return (
42
- <span class="error">{`{${SIGILS[scope]}${name} error: ${nameError}}`}</span>
43
- );
33
+ return <span class="error">{`{${sigil}${name} error: ${nameError}}`}</span>;
44
34
  }
45
35
 
46
36
  let value: unknown;
@@ -61,15 +51,5 @@ export function VarDisplay({ name, scope, className, id }: VarDisplayProps) {
61
51
  value = (value as Record<string, unknown>)[part];
62
52
  }
63
53
 
64
- const display = value == null ? '' : String(value);
65
- if (className || id)
66
- return (
67
- <span
68
- id={id}
69
- class={className}
70
- >
71
- {display}
72
- </span>
73
- );
74
- return <>{display}</>;
54
+ return wrapContent(className, id, display(value));
75
55
  }
@@ -1,35 +1,69 @@
1
1
  import { registerWidget } from '../../widgets/widget-registry';
2
2
  import { astContainsChildren } from '../../widgets/ast-scanner';
3
- import { registerBlockMacro } from '../../markup/ast';
3
+ import { registerBlockMacro, type ASTNode } from '../../markup/ast';
4
4
  import { defineMacro } from '../../define-macro';
5
5
  import { checkVariableName } from '../../utils/namespace';
6
6
  import { MacroError } from './MacroError';
7
+ import { parseMacroArgs } from './macro-args';
8
+ import type { MacroArgs } from '../../registry';
9
+
10
+ /** A {widget} definition's name, then its `@` parameters. */
11
+ const WIDGET_PARAMETERS = [
12
+ { name: 'name', type: 'text', required: true },
13
+ { name: 'parameters', type: 'expression' },
14
+ ] as const;
15
+
16
+ interface WidgetDef {
17
+ name: string;
18
+ params: string[];
19
+ }
7
20
 
8
21
  /**
9
- * Read the arguments of a {widget} definition: its name (quotes dropped)
10
- * and its parameters, the words after it that start with one of the
11
- * characters of `sigils`.
22
+ * The widget a {widget} definition's arguments declare: its name and its
23
+ * parameters, the words after it that start with `@` (docs/widgets.md).
24
+ * Other words, such as `$name`, are not parameters.
12
25
  */
13
- export function parseWidgetDef(
14
- rawArgs: string,
15
- sigils = '@',
16
- ): { name: string; params: string[] } {
17
- const [first, ...rest] = rawArgs.trim().split(/\s+/);
26
+ function widgetDef({
27
+ name = '',
28
+ parameters = '',
29
+ }: MacroArgs<typeof WIDGET_PARAMETERS>): WidgetDef {
18
30
  return {
19
- name: first!.replace(/["']/g, ''),
20
- params: rest.filter((word) => sigils.includes(word[0]!)),
31
+ name,
32
+ params: parameters.split(/\s+/).filter((word) => word.startsWith('@')),
21
33
  };
22
34
  }
23
35
 
36
+ /** Read the arguments of a {widget} definition (see widgetDef). */
37
+ export function parseWidgetDef(rawArgs: string): WidgetDef {
38
+ return widgetDef(parseMacroArgs(rawArgs, WIDGET_PARAMETERS));
39
+ }
40
+
41
+ /**
42
+ * Register the widget a definition declares, with its body. Widgets whose
43
+ * body renders {@children} take a closing tag, so they are registered as
44
+ * block macros too: passages parsed later nest their content. A parameter
45
+ * that no namespace can hold throws (see registerWidget), registering
46
+ * nothing.
47
+ */
48
+ export function registerWidgetDef(
49
+ { name, params }: WidgetDef,
50
+ body: ASTNode[],
51
+ ): void {
52
+ const isBlock = astContainsChildren(body);
53
+ registerWidget(name, body, params, isBlock);
54
+ if (isBlock) registerBlockMacro(name);
55
+ }
56
+
24
57
  defineMacro({
25
58
  name: 'widget',
26
59
  block: true,
27
- render({ rawArgs, children = [] }, ctx) {
28
- let parsed: ReturnType<typeof parseWidgetDef> | undefined;
60
+ parameters: WIDGET_PARAMETERS,
61
+ render({ children = [] }, ctx) {
62
+ let parsed: WidgetDef | undefined;
29
63
  let error: unknown;
30
64
  try {
31
65
  // Refuse `@` parameters that no namespace can hold
32
- const def = parseWidgetDef(rawArgs);
66
+ const def = widgetDef(ctx.args);
33
67
  for (const param of def.params) checkVariableName(param.slice(1), param);
34
68
  parsed = def;
35
69
  } catch (err) {
@@ -41,12 +75,7 @@ defineMacro({
41
75
  const paramsKey = params.join(',');
42
76
 
43
77
  ctx.hooks.useLayoutEffect(() => {
44
- if (!parsed) return;
45
- // Widgets whose body renders {@children} take a closing tag; register
46
- // them as block macros so passages parsed later nest their content.
47
- const isBlock = astContainsChildren(children);
48
- registerWidget(name, children, params, isBlock);
49
- if (isBlock) registerBlockMacro(name);
78
+ if (parsed) registerWidgetDef(parsed, children);
50
79
  }, [name, childrenKey, paramsKey]);
51
80
 
52
81
  if (error) {
@@ -0,0 +1,18 @@
1
+ import type { Branch } from '../../markup/ast';
2
+
3
+ /**
4
+ * The branch a conditional block ({if}, {switch}) shows: the first of
5
+ * `branches` whose argument `matches`, else the last bare one ({else},
6
+ * {default}), else null. Throws what `matches` throws.
7
+ */
8
+ export function selectBranch(
9
+ branches: Branch[],
10
+ matches: (rawArgs: string) => boolean,
11
+ ): Branch | null {
12
+ let fallback: Branch | null = null;
13
+ for (const branch of branches) {
14
+ if (branch.rawArgs === '') fallback = branch;
15
+ else if (matches(branch.rawArgs)) return branch;
16
+ }
17
+ return fallback;
18
+ }
@@ -0,0 +1,19 @@
1
+ import { h, Fragment, type ComponentChildren, type VNode } from 'preact';
2
+
3
+ /** The text a value is shown as: nothing for null and undefined. */
4
+ export function display(value: unknown): string {
5
+ return value == null ? '' : String(value);
6
+ }
7
+
8
+ /**
9
+ * `content` in a `<span>` carrying `className` and `id`, or in a fragment if
10
+ * it has neither.
11
+ */
12
+ export function wrapContent(
13
+ className: string | undefined,
14
+ id: string | undefined,
15
+ content: ComponentChildren,
16
+ ): VNode<any> {
17
+ if (className || id) return h('span', { id, class: className }, content);
18
+ return h(Fragment, null, content);
19
+ }
@@ -1,5 +1,34 @@
1
1
  import { h } from 'preact';
2
- import { defineMacro } from '../../define-macro';
2
+ import { defineMacro, type MacroContext } from '../../define-macro';
3
+ import type { UseActionOptions } from '../../hooks/use-action';
4
+ import { display } from './display';
5
+
6
+ /** The story variable a `storeVar` macro binds: its first parameter. */
7
+ export const VARIABLE_PARAMETER = {
8
+ name: 'variable',
9
+ type: 'variable',
10
+ required: true,
11
+ } as const;
12
+
13
+ /**
14
+ * Register the automation action of a macro bound to a story variable: keyed
15
+ * by the variable (or `key`), with the variable's value unless `action`
16
+ * gives another.
17
+ */
18
+ export function useVariableAction(
19
+ ctx: Pick<MacroContext, 'useAction' | 'id' | 'varName' | 'value'>,
20
+ action: Omit<UseActionOptions, 'key' | 'authorId' | 'variable'> & {
21
+ key?: string;
22
+ },
23
+ ): string {
24
+ return ctx.useAction({
25
+ key: `$${ctx.varName}`,
26
+ authorId: ctx.id,
27
+ variable: ctx.varName,
28
+ value: ctx.value,
29
+ ...action,
30
+ });
31
+ }
3
32
 
4
33
  /**
5
34
  * Define a text input macro, `{name $var "placeholder"}`: an `<input>` of
@@ -17,20 +46,13 @@ export function defineInputMacro(
17
46
  defineMacro({
18
47
  name,
19
48
  storeVar: true,
20
- parameters: [
21
- { name: 'variable', type: 'variable', required: true },
22
- { name: 'placeholder', type: 'string' },
23
- ],
49
+ parameters: [VARIABLE_PARAMETER, { name: 'placeholder', type: 'string' }],
24
50
  render(_props, ctx) {
25
51
  const placeholder = ctx.args.placeholder ?? '';
26
52
 
27
- ctx.useAction({
53
+ useVariableAction(ctx, {
28
54
  type: name,
29
- key: `$${ctx.varName}`,
30
- authorId: ctx.id,
31
55
  label: placeholder || ctx.varName!,
32
- variable: ctx.varName,
33
- value: ctx.value,
34
56
  perform: (v) => ctx.setValue!(toValue(v)),
35
57
  });
36
58
 
@@ -38,7 +60,7 @@ export function defineInputMacro(
38
60
  type,
39
61
  id: ctx.id,
40
62
  class: ctx.cls,
41
- value: ctx.value == null ? '' : String(ctx.value),
63
+ value: display(ctx.value),
42
64
  placeholder,
43
65
  onInput: (e: Event) =>
44
66
  ctx.setValue!(toValue((e.target as HTMLInputElement).value)),
@@ -232,6 +232,13 @@ function readGroup(
232
232
  : unset(rest);
233
233
  }
234
234
 
235
+ /** The duration a timing macro ({repeat}, {type}, {timed}/{next}) waits. */
236
+ export const DELAY_PARAMETER = {
237
+ name: 'delay',
238
+ type: 'delay',
239
+ required: true,
240
+ } as const;
241
+
235
242
  /** Read `rawArgs` into the declared `parameters` (see above). */
236
243
  export function parseMacroArgs<const P extends readonly ParameterDef[]>(
237
244
  rawArgs: string,
@@ -1,5 +1,4 @@
1
1
  import type { ASTNode } from '../../markup/ast';
2
- import { readWholeQuoted } from './arg-utils';
3
2
  import { parseMacroArgs } from './macro-args';
4
3
 
5
4
  /** `$var "placeholder"`, the arguments of the input macros. */
@@ -14,18 +13,22 @@ export function parseVarArgs(rawArgs: string): {
14
13
  return { varName: variable.replace(/["']/g, ''), placeholder };
15
14
  }
16
15
 
16
+ /** The parameters of an `{option}` sub-macro: its value. */
17
+ const OPTION_PARAMETERS = [{ name: 'value', type: 'string' }] as const;
18
+
17
19
  /**
18
- * Walk AST children to find {option} macro nodes, returning their rawArgs as values.
20
+ * Walk AST children to find {option} macro nodes, returning their values.
19
21
  */
20
22
  export function extractOptions(children: ASTNode[]): string[] {
21
23
  const options: string[] = [];
22
24
  for (const node of children) {
23
25
  if (node.type === 'macro' && node.name === 'option') {
24
- const raw = node.rawArgs.trim();
25
- // Strip surrounding quotes so {option "Long Sword"} gives "Long Sword"
26
- const stripped =
27
- readWholeQuoted(raw) ?? raw.replace(/^(["'])(.+)\1$/s, '$2');
28
- options.push(stripped);
26
+ // {option "Long Sword"} gives `Long Sword`. A value that isn't one
27
+ // quoted string loses a matching pair of quotes around it, if any.
28
+ const { value } = parseMacroArgs(node.rawArgs, OPTION_PARAMETERS);
29
+ options.push(
30
+ value ?? node.rawArgs.trim().replace(/^(["'])(.+)\1$/s, '$2'),
31
+ );
29
32
  }
30
33
  }
31
34
  return options;
@@ -1,4 +1,4 @@
1
- import { h, Fragment } from 'preact';
1
+ import { h } from 'preact';
2
2
  import type { VNode, ComponentChildren } from 'preact';
3
3
  import {
4
4
  useContext,
@@ -28,6 +28,7 @@ import type { UseActionOptions } from './hooks/use-action';
28
28
  import { collectText } from './utils/extract-text';
29
29
  import { currentSourceLocation } from './utils/source-location';
30
30
  import { parseVarArgs, extractOptions } from './components/macros/option-utils';
31
+ import { wrapContent } from './components/macros/display';
31
32
  import {
32
33
  parseMacroArgs,
33
34
  readBoundVariable,
@@ -177,11 +178,8 @@ export function defineMacro<const P extends readonly ParameterDef[] = []>(
177
178
  mutate: (code: string) => executeMutation(code, getValues(), update),
178
179
  update,
179
180
  getValues,
180
- wrap: (content: ComponentChildren): VNode<any> => {
181
- if (className || id)
182
- return h('span', { id, class: className }, content);
183
- return h(Fragment, null, content);
184
- },
181
+ wrap: (content: ComponentChildren): VNode<any> =>
182
+ wrapContent(className, id, content),
185
183
  };
186
184
 
187
185
  if (config.merged) {