@rohal12/spindle 0.13.0 → 0.15.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 +3 -4
- package/src/components/macros/Do.tsx +4 -11
- package/src/components/macros/For.tsx +29 -11
- package/src/components/macros/MacroLink.tsx +4 -5
- package/src/components/macros/Set.tsx +3 -4
- package/src/components/macros/VarDisplay.tsx +3 -4
- package/src/components/macros/WidgetInvocation.tsx +25 -11
- package/src/execute-mutation.ts +1 -1
- package/src/hooks/use-merged-locals.ts +6 -23
- package/src/markup/render.tsx +6 -5
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
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 { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
6
5
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
6
|
import { executeMutation } from '../../execute-mutation';
|
|
8
7
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
@@ -19,11 +18,11 @@ export function Button({ rawArgs, children, className, id }: ButtonProps) {
|
|
|
19
18
|
const resolve = useInterpolate();
|
|
20
19
|
className = resolve(className);
|
|
21
20
|
id = resolve(id);
|
|
22
|
-
const
|
|
21
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
23
22
|
|
|
24
23
|
const handleClick = () => {
|
|
25
24
|
try {
|
|
26
|
-
executeMutation(rawArgs,
|
|
25
|
+
executeMutation(rawArgs, getValues(), update);
|
|
27
26
|
} catch (err) {
|
|
28
27
|
console.error(
|
|
29
28
|
`spindle: Error in {button ${rawArgs}}${currentSourceLocation()}:`,
|
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
import { useLayoutEffect, useContext } from 'preact/hooks';
|
|
2
2
|
import type { ASTNode } from '../../markup/ast';
|
|
3
|
-
import {
|
|
4
|
-
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
3
|
+
import { LocalsUpdateContext } from '../../markup/render';
|
|
5
4
|
import { executeMutation } from '../../execute-mutation';
|
|
6
5
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
6
|
+
import { collectText } from '../../utils/extract-text';
|
|
7
7
|
|
|
8
8
|
interface DoProps {
|
|
9
9
|
children: ASTNode[];
|
|
10
10
|
}
|
|
11
11
|
|
|
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
12
|
export function Do({ children }: DoProps) {
|
|
20
13
|
const code = collectText(children);
|
|
21
|
-
const
|
|
14
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
22
15
|
|
|
23
16
|
useLayoutEffect(() => {
|
|
24
17
|
try {
|
|
25
|
-
executeMutation(code,
|
|
18
|
+
executeMutation(code, getValues(), update);
|
|
26
19
|
} catch (err) {
|
|
27
20
|
console.error(`spindle: Error in {do}${currentSourceLocation()}:`, err);
|
|
28
21
|
}
|
|
@@ -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';
|
|
@@ -43,7 +52,11 @@ function parseForArgs(rawArgs: string): {
|
|
|
43
52
|
);
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
itemVar: itemVar.slice(1),
|
|
57
|
+
indexVar: indexVar ? indexVar.slice(1) : null,
|
|
58
|
+
listExpr,
|
|
59
|
+
};
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
function ForIteration({
|
|
@@ -63,16 +76,21 @@ function ForIteration({
|
|
|
63
76
|
...initialValues,
|
|
64
77
|
}));
|
|
65
78
|
|
|
79
|
+
const valuesRef = useRef(localState);
|
|
80
|
+
valuesRef.current = localState;
|
|
81
|
+
|
|
82
|
+
const getValues = useCallback(() => valuesRef.current, []);
|
|
66
83
|
const update = useCallback((key: string, value: unknown) => {
|
|
67
84
|
setLocalState((prev) => ({ ...prev, [key]: value }));
|
|
68
85
|
}, []);
|
|
69
|
-
|
|
70
|
-
const scope: LocalsScope = { values: localState, update };
|
|
86
|
+
const updater = useMemo(() => ({ update, getValues }), [update, getValues]);
|
|
71
87
|
|
|
72
88
|
return (
|
|
73
|
-
<
|
|
74
|
-
{
|
|
75
|
-
|
|
89
|
+
<LocalsUpdateContext.Provider value={updater}>
|
|
90
|
+
<LocalsValuesContext.Provider value={localState}>
|
|
91
|
+
{renderNodes(children)}
|
|
92
|
+
</LocalsValuesContext.Provider>
|
|
93
|
+
</LocalsUpdateContext.Provider>
|
|
76
94
|
);
|
|
77
95
|
}
|
|
78
96
|
|
|
@@ -80,7 +98,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
80
98
|
const resolve = useInterpolate();
|
|
81
99
|
className = resolve(className);
|
|
82
100
|
id = resolve(id);
|
|
83
|
-
const
|
|
101
|
+
const parentValues = useContext(LocalsValuesContext);
|
|
84
102
|
const [mergedVars, mergedTemps, mergedLocals] = useMergedLocals();
|
|
85
103
|
|
|
86
104
|
let parsed: ReturnType<typeof parseForArgs>;
|
|
@@ -130,7 +148,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
130
148
|
return (
|
|
131
149
|
<ForIteration
|
|
132
150
|
key={i}
|
|
133
|
-
parentValues={
|
|
151
|
+
parentValues={parentValues}
|
|
134
152
|
ownKeys={ownKeys}
|
|
135
153
|
initialValues={{}}
|
|
136
154
|
children={children}
|
|
@@ -1,8 +1,7 @@
|
|
|
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 { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
4
|
+
import { LocalsUpdateContext } from '../../markup/render';
|
|
6
5
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
7
6
|
import { executeMutation } from '../../execute-mutation';
|
|
8
7
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
@@ -81,11 +80,11 @@ export function MacroLink({
|
|
|
81
80
|
className = resolve(className);
|
|
82
81
|
id = resolve(id);
|
|
83
82
|
const { display, passage } = parseArgs(rawArgs);
|
|
84
|
-
const
|
|
83
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
85
84
|
|
|
86
85
|
const handleClick = (e: Event) => {
|
|
87
86
|
e.preventDefault();
|
|
88
|
-
executeChildren(children,
|
|
87
|
+
executeChildren(children, getValues(), update);
|
|
89
88
|
if (passage) {
|
|
90
89
|
useStoryStore.getState().navigate(passage);
|
|
91
90
|
}
|
|
@@ -98,7 +97,7 @@ export function MacroLink({
|
|
|
98
97
|
label: display,
|
|
99
98
|
target: passage ?? undefined,
|
|
100
99
|
perform: () => {
|
|
101
|
-
executeChildren(children,
|
|
100
|
+
executeChildren(children, getValues(), update);
|
|
102
101
|
if (passage) {
|
|
103
102
|
useStoryStore.getState().navigate(passage);
|
|
104
103
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useRef, useContext } from 'preact/hooks';
|
|
2
|
-
import {
|
|
3
|
-
import { stripLocalsPrefix } from '../../hooks/use-merged-locals';
|
|
2
|
+
import { LocalsUpdateContext } from '../../markup/render';
|
|
4
3
|
import { executeMutation } from '../../execute-mutation';
|
|
5
4
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
6
5
|
|
|
@@ -9,14 +8,14 @@ interface SetProps {
|
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export function Set({ rawArgs }: SetProps) {
|
|
12
|
-
const
|
|
11
|
+
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
13
12
|
const ran = useRef(false);
|
|
14
13
|
|
|
15
14
|
if (!ran.current) {
|
|
16
15
|
ran.current = true;
|
|
17
16
|
|
|
18
17
|
try {
|
|
19
|
-
executeMutation(rawArgs,
|
|
18
|
+
executeMutation(rawArgs, getValues(), update);
|
|
20
19
|
} catch (err) {
|
|
21
20
|
console.error(
|
|
22
21
|
`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) =>
|
|
@@ -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
|
-
|
|
31
|
-
value = key in localsScope.values ? localsScope.values[key] : undefined;
|
|
30
|
+
value = root in localsValues ? localsValues[root] : undefined;
|
|
32
31
|
} else {
|
|
33
32
|
value = storeValue;
|
|
34
33
|
}
|
|
@@ -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) {
|
|
@@ -114,13 +128,13 @@ export function WidgetInvocation({
|
|
|
114
128
|
value = undefined;
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
|
-
ownKeys[param] = value;
|
|
131
|
+
ownKeys[param.startsWith('@') ? param.slice(1) : param] = value;
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
return (
|
|
121
135
|
<WidgetBody
|
|
122
136
|
body={body}
|
|
123
|
-
parentValues={
|
|
137
|
+
parentValues={parentValues}
|
|
124
138
|
ownKeys={ownKeys}
|
|
125
139
|
/>
|
|
126
140
|
);
|
package/src/execute-mutation.ts
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import { useContext, useMemo } from 'preact/hooks';
|
|
2
2
|
import { useStoryStore } from '../store';
|
|
3
|
-
import {
|
|
3
|
+
import { LocalsValuesContext } from '../markup/render';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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>,
|
|
@@ -30,9 +13,9 @@ export function useMergedLocals(): readonly [
|
|
|
30
13
|
] {
|
|
31
14
|
const variables = useStoryStore((s) => s.variables);
|
|
32
15
|
const temporary = useStoryStore((s) => s.temporary);
|
|
33
|
-
const
|
|
16
|
+
const localsValues = useContext(LocalsValuesContext);
|
|
34
17
|
|
|
35
18
|
return useMemo(() => {
|
|
36
|
-
return [variables, temporary,
|
|
37
|
-
}, [variables, temporary,
|
|
19
|
+
return [variables, temporary, localsValues] as const;
|
|
20
|
+
}, [variables, temporary, localsValues]);
|
|
38
21
|
}
|
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
|
|