@rohal12/spindle 0.43.6 → 0.44.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 +14 -13
- package/src/story-api.ts +59 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rohal12/spindle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Preact-based story format for Twine 2.",
|
|
6
6
|
"license": "Unlicense",
|
|
@@ -61,28 +61,29 @@
|
|
|
61
61
|
"micromark": "^4.0.2",
|
|
62
62
|
"micromark-extension-gfm-strikethrough": "^2.1.0",
|
|
63
63
|
"micromark-extension-gfm-table": "^2.1.1",
|
|
64
|
-
"preact": "^10.
|
|
65
|
-
"zustand": "^5.0.
|
|
64
|
+
"preact": "^10.29.0",
|
|
65
|
+
"zustand": "^5.0.12"
|
|
66
66
|
},
|
|
67
67
|
"lint-staged": {
|
|
68
68
|
"*": "prettier --write --ignore-unknown"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@preact/preset-vite": "^2.10.
|
|
72
|
-
"@rohal12/twee-ts": "^1.
|
|
71
|
+
"@preact/preset-vite": "^2.10.5",
|
|
72
|
+
"@rohal12/twee-ts": "^1.14.0",
|
|
73
73
|
"@types/js-yaml": "^4.0.9",
|
|
74
|
-
"@types/
|
|
75
|
-
"@
|
|
76
|
-
"
|
|
74
|
+
"@types/node": "^25.5.0",
|
|
75
|
+
"@types/react": "^19.2.14",
|
|
76
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
77
|
+
"happy-dom": "^20.8.9",
|
|
77
78
|
"husky": "^9.1.7",
|
|
78
79
|
"js-yaml": "^4.1.1",
|
|
79
|
-
"lint-staged": "^16.
|
|
80
|
+
"lint-staged": "^16.4.0",
|
|
80
81
|
"playwright": "^1.58.2",
|
|
81
82
|
"prettier": "^3.8.1",
|
|
82
|
-
"typescript": "^
|
|
83
|
-
"vite": "^
|
|
84
|
-
"vite-plugin-singlefile": "^2.3.
|
|
83
|
+
"typescript": "^6.0.2",
|
|
84
|
+
"vite": "^8.0.3",
|
|
85
|
+
"vite-plugin-singlefile": "^2.3.2",
|
|
85
86
|
"vitepress": "^1.6.4",
|
|
86
|
-
"vitest": "^4.
|
|
87
|
+
"vitest": "^4.1.2"
|
|
87
88
|
}
|
|
88
89
|
}
|
package/src/story-api.ts
CHANGED
|
@@ -113,6 +113,37 @@ function ensureVariableChangedSubscription(): void {
|
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** Traverse a dot-delimited path on an object and return the value. */
|
|
117
|
+
function getByPath(obj: Record<string, unknown>, path: string): unknown {
|
|
118
|
+
const segments = path.split('.');
|
|
119
|
+
let current: unknown = obj[segments[0]!];
|
|
120
|
+
for (let i = 1; i < segments.length; i++) {
|
|
121
|
+
if (current == null) return undefined;
|
|
122
|
+
current = (current as Record<string, unknown>)[segments[i]!];
|
|
123
|
+
}
|
|
124
|
+
return current;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Set a value at a dot-delimited path on an object (must be an Immer draft for mutation). */
|
|
128
|
+
function setByPath(
|
|
129
|
+
obj: Record<string, unknown>,
|
|
130
|
+
path: string,
|
|
131
|
+
value: unknown,
|
|
132
|
+
): void {
|
|
133
|
+
const segments = path.split('.');
|
|
134
|
+
let current: Record<string, unknown> = obj;
|
|
135
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
136
|
+
const next = current[segments[i]!];
|
|
137
|
+
if (next == null || typeof next !== 'object') {
|
|
138
|
+
throw new TypeError(
|
|
139
|
+
`spindle: Cannot set property "${segments[i + 1]}" on ${typeof next} (at "${segments.slice(0, i + 1).join('.')}")`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
current = next as Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
current[segments[segments.length - 1]!] = value;
|
|
145
|
+
}
|
|
146
|
+
|
|
116
147
|
export interface StoryAPI {
|
|
117
148
|
get(name: string): unknown;
|
|
118
149
|
set(name: string, value: unknown): void;
|
|
@@ -192,30 +223,43 @@ export interface StoryAPI {
|
|
|
192
223
|
};
|
|
193
224
|
}
|
|
194
225
|
|
|
226
|
+
/** Set a single variable, resolving dot-paths if present. */
|
|
227
|
+
function setOne(name: string, value: unknown): void {
|
|
228
|
+
const isTransient = name.startsWith('%');
|
|
229
|
+
const key = isTransient ? name.slice(1) : name;
|
|
230
|
+
const namespace = isTransient ? 'transient' : 'variables';
|
|
231
|
+
|
|
232
|
+
if (key.includes('.')) {
|
|
233
|
+
useStoryStore.setState((state) => {
|
|
234
|
+
setByPath(state[namespace] as Record<string, unknown>, key, value);
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
const state = useStoryStore.getState();
|
|
238
|
+
if (isTransient) {
|
|
239
|
+
state.setTransient(key, value);
|
|
240
|
+
} else {
|
|
241
|
+
state.setVariable(key, value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
195
246
|
function createStoryAPI(): StoryAPI {
|
|
196
247
|
return {
|
|
197
248
|
get(name: string): unknown {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
249
|
+
const isTransient = name.startsWith('%');
|
|
250
|
+
const key = isTransient ? name.slice(1) : name;
|
|
251
|
+
const store = isTransient
|
|
252
|
+
? useStoryStore.getState().transient
|
|
253
|
+
: useStoryStore.getState().variables;
|
|
254
|
+
return key.includes('.') ? getByPath(store, key) : store[key];
|
|
202
255
|
},
|
|
203
256
|
|
|
204
257
|
set(nameOrVars: string | Record<string, unknown>, value?: unknown): void {
|
|
205
|
-
const state = useStoryStore.getState();
|
|
206
258
|
if (typeof nameOrVars === 'string') {
|
|
207
|
-
|
|
208
|
-
state.setTransient(nameOrVars.slice(1), value);
|
|
209
|
-
} else {
|
|
210
|
-
state.setVariable(nameOrVars, value);
|
|
211
|
-
}
|
|
259
|
+
setOne(nameOrVars, value);
|
|
212
260
|
} else {
|
|
213
261
|
for (const [k, v] of Object.entries(nameOrVars)) {
|
|
214
|
-
|
|
215
|
-
state.setTransient(k.slice(1), v);
|
|
216
|
-
} else {
|
|
217
|
-
state.setVariable(k, v);
|
|
218
|
-
}
|
|
262
|
+
setOne(k, v);
|
|
219
263
|
}
|
|
220
264
|
}
|
|
221
265
|
},
|