@rohal12/spindle 0.12.0 → 0.14.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.
- package/dist/pkg/format.js +1 -1
- package/package.json +1 -1
- package/src/components/macros/Button.tsx +4 -5
- package/src/components/macros/Do.tsx +5 -12
- package/src/components/macros/For.tsx +24 -10
- package/src/components/macros/MacroLink.tsx +5 -6
- package/src/components/macros/Set.tsx +4 -5
- package/src/components/macros/VarDisplay.tsx +3 -3
- package/src/components/macros/WidgetInvocation.tsx +24 -10
- package/src/hooks/use-merged-locals.ts +20 -10
- package/src/markup/render.tsx +6 -5
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useContext } from 'preact/hooks';
|
|
2
|
-
import { renderInlineNodes,
|
|
2
|
+
import { renderInlineNodes, LocalsUpdateContext } from '../../markup/render';
|
|
3
3
|
import { collectText } from '../../utils/extract-text';
|
|
4
4
|
import { useAction } from '../../hooks/use-action';
|
|
5
|
-
import {
|
|
5
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
6
6
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
7
|
import { executeMutation } from '../../execute-mutation';
|
|
8
8
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
@@ -19,12 +19,11 @@ export function Button({ rawArgs, children, className, id }: ButtonProps) {
|
|
|
19
19
|
const resolve = useInterpolate();
|
|
20
20
|
className = resolve(className);
|
|
21
21
|
id = resolve(id);
|
|
22
|
-
const
|
|
23
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
22
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
24
23
|
|
|
25
24
|
const handleClick = () => {
|
|
26
25
|
try {
|
|
27
|
-
executeMutation(rawArgs,
|
|
26
|
+
executeMutation(rawArgs, stripLocalsPrefix(getValues()), update);
|
|
28
27
|
} catch (err) {
|
|
29
28
|
console.error(
|
|
30
29
|
`spindle: Error in {button ${rawArgs}}${currentSourceLocation()}:`,
|
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
import { useLayoutEffect, useContext } from 'preact/hooks';
|
|
2
2
|
import type { ASTNode } from '../../markup/ast';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { LocalsUpdateContext } from '../../markup/render';
|
|
4
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
5
5
|
import { executeMutation } from '../../execute-mutation';
|
|
6
6
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
7
|
+
import { collectText } from '../../utils/extract-text';
|
|
7
8
|
|
|
8
9
|
interface DoProps {
|
|
9
10
|
children: ASTNode[];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* Concatenate text children into a single JS string for execution.
|
|
14
|
-
*/
|
|
15
|
-
function collectText(nodes: ASTNode[]): string {
|
|
16
|
-
return nodes.map((n) => (n.type === 'text' ? n.value : '')).join('');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
export function Do({ children }: DoProps) {
|
|
20
14
|
const code = collectText(children);
|
|
21
|
-
const
|
|
22
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
15
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
23
16
|
|
|
24
17
|
useLayoutEffect(() => {
|
|
25
18
|
try {
|
|
26
|
-
executeMutation(code,
|
|
19
|
+
executeMutation(code, stripLocalsPrefix(getValues()), update);
|
|
27
20
|
} catch (err) {
|
|
28
21
|
console.error(`spindle: Error in {do}${currentSourceLocation()}:`, err);
|
|
29
22
|
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useContext,
|
|
3
|
+
useState,
|
|
4
|
+
useCallback,
|
|
5
|
+
useRef,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from 'preact/hooks';
|
|
2
8
|
import { evaluate } from '../../expression';
|
|
3
|
-
import {
|
|
4
|
-
|
|
9
|
+
import {
|
|
10
|
+
LocalsValuesContext,
|
|
11
|
+
LocalsUpdateContext,
|
|
12
|
+
renderNodes,
|
|
13
|
+
} from '../../markup/render';
|
|
5
14
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
6
15
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
16
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
@@ -63,16 +72,21 @@ function ForIteration({
|
|
|
63
72
|
...initialValues,
|
|
64
73
|
}));
|
|
65
74
|
|
|
75
|
+
const valuesRef = useRef(localState);
|
|
76
|
+
valuesRef.current = localState;
|
|
77
|
+
|
|
78
|
+
const getValues = useCallback(() => valuesRef.current, []);
|
|
66
79
|
const update = useCallback((key: string, value: unknown) => {
|
|
67
80
|
setLocalState((prev) => ({ ...prev, [key]: value }));
|
|
68
81
|
}, []);
|
|
69
|
-
|
|
70
|
-
const scope: LocalsScope = { values: localState, update };
|
|
82
|
+
const updater = useMemo(() => ({ update, getValues }), [update, getValues]);
|
|
71
83
|
|
|
72
84
|
return (
|
|
73
|
-
<
|
|
74
|
-
{
|
|
75
|
-
|
|
85
|
+
<LocalsUpdateContext.Provider value={updater}>
|
|
86
|
+
<LocalsValuesContext.Provider value={localState}>
|
|
87
|
+
{renderNodes(children)}
|
|
88
|
+
</LocalsValuesContext.Provider>
|
|
89
|
+
</LocalsUpdateContext.Provider>
|
|
76
90
|
);
|
|
77
91
|
}
|
|
78
92
|
|
|
@@ -80,7 +94,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
80
94
|
const resolve = useInterpolate();
|
|
81
95
|
className = resolve(className);
|
|
82
96
|
id = resolve(id);
|
|
83
|
-
const
|
|
97
|
+
const parentValues = useContext(LocalsValuesContext);
|
|
84
98
|
const [mergedVars, mergedTemps, mergedLocals] = useMergedLocals();
|
|
85
99
|
|
|
86
100
|
let parsed: ReturnType<typeof parseForArgs>;
|
|
@@ -130,7 +144,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
130
144
|
return (
|
|
131
145
|
<ForIteration
|
|
132
146
|
key={i}
|
|
133
|
-
parentValues={
|
|
147
|
+
parentValues={parentValues}
|
|
134
148
|
ownKeys={ownKeys}
|
|
135
149
|
initialValues={{}}
|
|
136
150
|
children={children}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useContext } from 'preact/hooks';
|
|
2
2
|
import { useStoryStore } from '../../store';
|
|
3
3
|
import type { ASTNode } from '../../markup/ast';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { LocalsUpdateContext } from '../../markup/render';
|
|
5
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
6
6
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
7
|
import { executeMutation } from '../../execute-mutation';
|
|
8
8
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
@@ -81,12 +81,11 @@ export function MacroLink({
|
|
|
81
81
|
className = resolve(className);
|
|
82
82
|
id = resolve(id);
|
|
83
83
|
const { display, passage } = parseArgs(rawArgs);
|
|
84
|
-
const
|
|
85
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
84
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
86
85
|
|
|
87
86
|
const handleClick = (e: Event) => {
|
|
88
87
|
e.preventDefault();
|
|
89
|
-
executeChildren(children,
|
|
88
|
+
executeChildren(children, stripLocalsPrefix(getValues()), update);
|
|
90
89
|
if (passage) {
|
|
91
90
|
useStoryStore.getState().navigate(passage);
|
|
92
91
|
}
|
|
@@ -99,7 +98,7 @@ export function MacroLink({
|
|
|
99
98
|
label: display,
|
|
100
99
|
target: passage ?? undefined,
|
|
101
100
|
perform: () => {
|
|
102
|
-
executeChildren(children,
|
|
101
|
+
executeChildren(children, stripLocalsPrefix(getValues()), update);
|
|
103
102
|
if (passage) {
|
|
104
103
|
useStoryStore.getState().navigate(passage);
|
|
105
104
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useRef, useContext } from 'preact/hooks';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { LocalsUpdateContext } from '../../markup/render';
|
|
3
|
+
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
4
4
|
import { executeMutation } from '../../execute-mutation';
|
|
5
5
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
6
6
|
|
|
@@ -9,15 +9,14 @@ interface SetProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function Set({ rawArgs }: SetProps) {
|
|
12
|
-
const
|
|
13
|
-
const [, , mergedLocals] = useMergedLocals();
|
|
12
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
14
13
|
const ran = useRef(false);
|
|
15
14
|
|
|
16
15
|
if (!ran.current) {
|
|
17
16
|
ran.current = true;
|
|
18
17
|
|
|
19
18
|
try {
|
|
20
|
-
executeMutation(rawArgs,
|
|
19
|
+
executeMutation(rawArgs, stripLocalsPrefix(getValues()), update);
|
|
21
20
|
} catch (err) {
|
|
22
21
|
console.error(
|
|
23
22
|
`spindle: Error in {set ${rawArgs}}${currentSourceLocation()}:`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useStoryStore } from '../../store';
|
|
2
2
|
import { useContext } from 'preact/hooks';
|
|
3
|
-
import {
|
|
3
|
+
import { LocalsValuesContext } from '../../markup/render';
|
|
4
4
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
5
5
|
|
|
6
6
|
interface VarDisplayProps {
|
|
@@ -14,7 +14,7 @@ export function VarDisplay({ name, scope, className, id }: VarDisplayProps) {
|
|
|
14
14
|
const resolve = useInterpolate();
|
|
15
15
|
className = resolve(className);
|
|
16
16
|
id = resolve(id);
|
|
17
|
-
const
|
|
17
|
+
const localsValues = useContext(LocalsValuesContext);
|
|
18
18
|
const parts = name.split('.');
|
|
19
19
|
const root = parts[0]!;
|
|
20
20
|
const storeValue = useStoryStore((s) =>
|
|
@@ -28,7 +28,7 @@ export function VarDisplay({ name, scope, className, id }: VarDisplayProps) {
|
|
|
28
28
|
let value: unknown;
|
|
29
29
|
if (scope === 'local') {
|
|
30
30
|
const key = `@${root}`;
|
|
31
|
-
value = key in
|
|
31
|
+
value = key in localsValues ? localsValues[key] : undefined;
|
|
32
32
|
} else {
|
|
33
33
|
value = storeValue;
|
|
34
34
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
useContext,
|
|
3
|
+
useState,
|
|
4
|
+
useCallback,
|
|
5
|
+
useRef,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from 'preact/hooks';
|
|
8
|
+
import {
|
|
9
|
+
LocalsValuesContext,
|
|
10
|
+
LocalsUpdateContext,
|
|
11
|
+
renderNodes,
|
|
12
|
+
} from '../../markup/render';
|
|
4
13
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
5
14
|
import { evaluate } from '../../expression';
|
|
6
15
|
import type { ASTNode } from '../../markup/ast';
|
|
@@ -75,16 +84,21 @@ function WidgetBody({
|
|
|
75
84
|
...ownKeys,
|
|
76
85
|
}));
|
|
77
86
|
|
|
87
|
+
const valuesRef = useRef(localState);
|
|
88
|
+
valuesRef.current = localState;
|
|
89
|
+
|
|
90
|
+
const getValues = useCallback(() => valuesRef.current, []);
|
|
78
91
|
const update = useCallback((key: string, value: unknown) => {
|
|
79
92
|
setLocalState((prev) => ({ ...prev, [key]: value }));
|
|
80
93
|
}, []);
|
|
81
|
-
|
|
82
|
-
const scope: LocalsScope = { values: localState, update };
|
|
94
|
+
const updater = useMemo(() => ({ update, getValues }), [update, getValues]);
|
|
83
95
|
|
|
84
96
|
return (
|
|
85
|
-
<
|
|
86
|
-
{
|
|
87
|
-
|
|
97
|
+
<LocalsUpdateContext.Provider value={updater}>
|
|
98
|
+
<LocalsValuesContext.Provider value={localState}>
|
|
99
|
+
{renderNodes(body)}
|
|
100
|
+
</LocalsValuesContext.Provider>
|
|
101
|
+
</LocalsUpdateContext.Provider>
|
|
88
102
|
);
|
|
89
103
|
}
|
|
90
104
|
|
|
@@ -93,7 +107,7 @@ export function WidgetInvocation({
|
|
|
93
107
|
params,
|
|
94
108
|
rawArgs,
|
|
95
109
|
}: WidgetInvocationProps) {
|
|
96
|
-
const
|
|
110
|
+
const parentValues = useContext(LocalsValuesContext);
|
|
97
111
|
const [mergedVars, mergedTemps, mergedLocals] = useMergedLocals();
|
|
98
112
|
|
|
99
113
|
if (params.length === 0 || !rawArgs) {
|
|
@@ -120,7 +134,7 @@ export function WidgetInvocation({
|
|
|
120
134
|
return (
|
|
121
135
|
<WidgetBody
|
|
122
136
|
body={body}
|
|
123
|
-
parentValues={
|
|
137
|
+
parentValues={parentValues}
|
|
124
138
|
ownKeys={ownKeys}
|
|
125
139
|
/>
|
|
126
140
|
);
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { useContext, useMemo } from 'preact/hooks';
|
|
2
2
|
import { useStoryStore } from '../store';
|
|
3
|
-
import {
|
|
3
|
+
import { LocalsValuesContext } from '../markup/render';
|
|
4
|
+
|
|
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
|
+
}
|
|
4
20
|
|
|
5
21
|
/**
|
|
6
22
|
* Return store variables, temporary, and @-prefixed locals from context.
|
|
@@ -14,15 +30,9 @@ export function useMergedLocals(): readonly [
|
|
|
14
30
|
] {
|
|
15
31
|
const variables = useStoryStore((s) => s.variables);
|
|
16
32
|
const temporary = useStoryStore((s) => s.temporary);
|
|
17
|
-
const
|
|
33
|
+
const localsValues = useContext(LocalsValuesContext);
|
|
18
34
|
|
|
19
35
|
return useMemo(() => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (key.startsWith('@')) {
|
|
23
|
-
locals[key.slice(1)] = val;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return [variables, temporary, locals] as const;
|
|
27
|
-
}, [variables, temporary, scope.values]);
|
|
36
|
+
return [variables, temporary, stripLocalsPrefix(localsValues)] as const;
|
|
37
|
+
}, [variables, temporary, localsValues]);
|
|
28
38
|
}
|
package/src/markup/render.tsx
CHANGED
|
@@ -44,17 +44,18 @@ import type { ASTNode, Branch, HtmlNode, MacroNode, VariableNode } from './ast';
|
|
|
44
44
|
import { useStoryStore } from '../store';
|
|
45
45
|
import { useInterpolate } from '../hooks/use-interpolate';
|
|
46
46
|
|
|
47
|
-
export interface
|
|
48
|
-
values: Record<string, unknown>;
|
|
47
|
+
export interface LocalsUpdater {
|
|
49
48
|
update: (key: string, value: unknown) => void;
|
|
49
|
+
getValues: () => Record<string, unknown>;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const
|
|
53
|
-
values: {},
|
|
52
|
+
const defaultUpdater: LocalsUpdater = {
|
|
54
53
|
update: () => {},
|
|
54
|
+
getValues: () => ({}),
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
export const
|
|
57
|
+
export const LocalsValuesContext = createContext<Record<string, unknown>>({});
|
|
58
|
+
export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
|
|
58
59
|
|
|
59
60
|
const EMPTY_BRANCHES: Branch[] = [];
|
|
60
61
|
|