@rohal12/spindle 0.43.0 → 0.43.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohal12/spindle",
3
- "version": "0.43.0",
3
+ "version": "0.43.1",
4
4
  "type": "module",
5
5
  "description": "A Preact-based story format for Twine 2.",
6
6
  "license": "Unlicense",
@@ -63,6 +63,7 @@ function computeAndApply(
63
63
  locals: Record<string, unknown>,
64
64
  transient: Record<string, unknown>,
65
65
  rawArgs: string,
66
+ prevRef: { current: unknown },
66
67
  ): void {
67
68
  let newValue: unknown;
68
69
  try {
@@ -75,8 +76,8 @@ function computeAndApply(
75
76
  return;
76
77
  }
77
78
 
78
- const current = isTemp ? temporary[name] : variables[name];
79
- if (!valuesEqual(current, newValue)) {
79
+ if (!valuesEqual(prevRef.current, newValue)) {
80
+ prevRef.current = newValue;
80
81
  const state = useStoryStore.getState();
81
82
  if (isTemp) state.setTemporary(name, newValue);
82
83
  else state.setVariable(name, newValue);
@@ -104,6 +105,8 @@ defineMacro({
104
105
  const isTemp = target.startsWith('_');
105
106
  const name = target.slice(1);
106
107
 
108
+ const prevOutput = ctx.hooks.useRef<unknown>(undefined);
109
+
107
110
  const ran = ctx.hooks.useRef(false);
108
111
  if (!ran.current) {
109
112
  ran.current = true;
@@ -116,6 +119,7 @@ defineMacro({
116
119
  mergedLocals,
117
120
  mergedTrans,
118
121
  rawArgs,
122
+ prevOutput,
119
123
  );
120
124
  }
121
125
 
@@ -129,6 +133,7 @@ defineMacro({
129
133
  mergedLocals,
130
134
  mergedTrans,
131
135
  rawArgs,
136
+ prevOutput,
132
137
  );
133
138
  }, [mergedVars, mergedTemps, mergedLocals, mergedTrans]);
134
139
 
@@ -53,21 +53,35 @@ function parseForArgs(rawArgs: string): {
53
53
 
54
54
  function ForIteration({
55
55
  parentValues,
56
- ownKeys,
57
- initialValues,
56
+ itemVar,
57
+ itemValue,
58
+ indexVar,
59
+ indexValue,
58
60
  children,
59
61
  }: {
60
62
  parentValues: Record<string, unknown>;
61
- ownKeys: Record<string, unknown>;
62
- initialValues: Record<string, unknown>;
63
+ itemVar: string;
64
+ itemValue: unknown;
65
+ indexVar: string | null;
66
+ indexValue: number;
63
67
  children: ASTNode[];
64
68
  }) {
65
69
  const [localMutations, setLocalMutations] = useState<Record<string, unknown>>(
66
- () => ({ ...initialValues }),
70
+ () => ({}),
67
71
  );
68
72
 
69
- // Recomputed every render — picks up new parentValues/ownKeys from parent
70
- const localState = { ...parentValues, ...ownKeys, ...localMutations };
73
+ const ownKeys = useMemo(
74
+ () => ({
75
+ [itemVar]: itemValue,
76
+ ...(indexVar ? { [indexVar]: indexValue } : undefined),
77
+ }),
78
+ [itemVar, itemValue, indexVar, indexValue],
79
+ );
80
+
81
+ const localState = useMemo(
82
+ () => ({ ...parentValues, ...ownKeys, ...localMutations }),
83
+ [parentValues, ownKeys, localMutations],
84
+ );
71
85
 
72
86
  const valuesRef = useRef(localState);
73
87
  valuesRef.current = localState;
@@ -131,22 +145,17 @@ defineMacro({
131
145
  );
132
146
  }
133
147
 
134
- const content = list.map((item, i) => {
135
- const ownKeys: Record<string, unknown> = {
136
- [itemVar]: item,
137
- ...(indexVar ? { [indexVar]: i } : undefined),
138
- };
139
-
140
- return (
141
- <ForIteration
142
- key={`${i}-${JSON.stringify(item)}`}
143
- parentValues={parentValues}
144
- ownKeys={ownKeys}
145
- initialValues={{}}
146
- children={children}
147
- />
148
- );
149
- });
148
+ const content = list.map((item, i) => (
149
+ <ForIteration
150
+ key={`${i}-${JSON.stringify(item)}`}
151
+ parentValues={parentValues}
152
+ itemVar={itemVar}
153
+ itemValue={item}
154
+ indexVar={indexVar}
155
+ indexValue={i}
156
+ children={children}
157
+ />
158
+ ));
150
159
 
151
160
  return ctx.wrap(content);
152
161
  },
@@ -190,8 +190,10 @@ function WidgetBody({
190
190
  {},
191
191
  );
192
192
 
193
- // Recomputed every render — picks up new ownKeys from parent
194
- const localState = { ...parentValues, ...ownKeys, ...localMutations };
193
+ const localState = useMemo(
194
+ () => ({ ...parentValues, ...ownKeys, ...localMutations }),
195
+ [parentValues, ownKeys, localMutations],
196
+ );
195
197
 
196
198
  const valuesRef = useRef(localState);
197
199
  valuesRef.current = localState;
@@ -233,10 +235,9 @@ export function WidgetInvocation({
233
235
  }
234
236
 
235
237
  const argExprs = splitArgs(rawArgs);
236
- const ownKeys: Record<string, unknown> = {};
238
+ const values: unknown[] = [];
237
239
 
238
240
  for (let i = 0; i < params.length; i++) {
239
- const param = params[i]!;
240
241
  const expr = argExprs[i];
241
242
  let value: unknown;
242
243
  if (expr !== undefined) {
@@ -252,9 +253,20 @@ export function WidgetInvocation({
252
253
  value = undefined;
253
254
  }
254
255
  }
255
- ownKeys[param.startsWith('@') ? param.slice(1) : param] = value;
256
+ values.push(value);
256
257
  }
257
258
 
259
+ const ownKeys = useMemo(() => {
260
+ const keys: Record<string, unknown> = {};
261
+ for (let i = 0; i < params.length; i++) {
262
+ keys[params[i]!.startsWith('@') ? params[i]!.slice(1) : params[i]!] =
263
+ values[i];
264
+ }
265
+ return keys;
266
+ // params is stable per widget instance; values tracks evaluated args
267
+ // eslint-disable-next-line react-hooks/exhaustive-deps
268
+ }, values);
269
+
258
270
  return (
259
271
  <WidgetChildrenContext.Provider value={childrenValue}>
260
272
  <WidgetBody