@rohal12/spindle 0.41.0 → 0.43.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/Computed.tsx +6 -3
- package/src/components/macros/ExprDisplay.tsx +8 -1
- package/src/components/macros/Unset.tsx +3 -1
- package/src/components/macros/VarDisplay.tsx +4 -2
- package/src/components/macros/WidgetInvocation.tsx +11 -3
- package/src/define-macro.ts +11 -1
- package/src/execute-mutation.ts +12 -1
- package/src/expression.ts +14 -7
- package/src/hooks/use-interpolate.ts +3 -3
- package/src/hooks/use-merged-locals.ts +6 -4
- package/src/index.tsx +22 -1
- package/src/interpolation.ts +18 -7
- package/src/markup/ast.ts +1 -1
- package/src/markup/render.tsx +1 -0
- package/src/markup/tokenizer.ts +86 -1
- package/src/store.ts +26 -1
- package/src/story-api.ts +32 -4
- package/src/story-variables.ts +22 -9
- package/src/triggers.ts +7 -1
- package/src/types-drift-check.ts +15 -0
- package/types/index.d.ts +344 -7
package/src/story-api.ts
CHANGED
|
@@ -76,20 +76,37 @@ function ensureVariableChangedSubscription(): void {
|
|
|
76
76
|
if (variableChangedSubActive) return;
|
|
77
77
|
variableChangedSubActive = true;
|
|
78
78
|
let prevVars = { ...useStoryStore.getState().variables };
|
|
79
|
+
let prevTrans = { ...useStoryStore.getState().transient };
|
|
79
80
|
useStoryStore.subscribe((state) => {
|
|
80
81
|
const changed: Record<string, { from: unknown; to: unknown }> = {};
|
|
81
82
|
let hasChanges = false;
|
|
82
|
-
|
|
83
|
+
|
|
84
|
+
// Check $variables
|
|
85
|
+
const allVarKeys = new Set([
|
|
83
86
|
...Object.keys(prevVars),
|
|
84
87
|
...Object.keys(state.variables),
|
|
85
88
|
]);
|
|
86
|
-
for (const key of
|
|
89
|
+
for (const key of allVarKeys) {
|
|
87
90
|
if (state.variables[key] !== prevVars[key]) {
|
|
88
91
|
changed[key] = { from: prevVars[key], to: state.variables[key] };
|
|
89
92
|
hasChanges = true;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
95
|
+
|
|
96
|
+
// Check %transient
|
|
97
|
+
const allTransKeys = new Set([
|
|
98
|
+
...Object.keys(prevTrans),
|
|
99
|
+
...Object.keys(state.transient),
|
|
100
|
+
]);
|
|
101
|
+
for (const key of allTransKeys) {
|
|
102
|
+
if (state.transient[key] !== prevTrans[key]) {
|
|
103
|
+
changed[`%${key}`] = { from: prevTrans[key], to: state.transient[key] };
|
|
104
|
+
hasChanges = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
prevVars = { ...state.variables };
|
|
109
|
+
prevTrans = { ...state.transient };
|
|
93
110
|
if (hasChanges) {
|
|
94
111
|
emit('variableChanged', changed);
|
|
95
112
|
}
|
|
@@ -178,16 +195,27 @@ export interface StoryAPI {
|
|
|
178
195
|
function createStoryAPI(): StoryAPI {
|
|
179
196
|
return {
|
|
180
197
|
get(name: string): unknown {
|
|
198
|
+
if (name.startsWith('%')) {
|
|
199
|
+
return useStoryStore.getState().transient[name.slice(1)];
|
|
200
|
+
}
|
|
181
201
|
return useStoryStore.getState().variables[name];
|
|
182
202
|
},
|
|
183
203
|
|
|
184
204
|
set(nameOrVars: string | Record<string, unknown>, value?: unknown): void {
|
|
185
205
|
const state = useStoryStore.getState();
|
|
186
206
|
if (typeof nameOrVars === 'string') {
|
|
187
|
-
|
|
207
|
+
if (nameOrVars.startsWith('%')) {
|
|
208
|
+
state.setTransient(nameOrVars.slice(1), value);
|
|
209
|
+
} else {
|
|
210
|
+
state.setVariable(nameOrVars, value);
|
|
211
|
+
}
|
|
188
212
|
} else {
|
|
189
213
|
for (const [k, v] of Object.entries(nameOrVars)) {
|
|
190
|
-
|
|
214
|
+
if (k.startsWith('%')) {
|
|
215
|
+
state.setTransient(k.slice(1), v);
|
|
216
|
+
} else {
|
|
217
|
+
state.setVariable(k, v);
|
|
218
|
+
}
|
|
191
219
|
}
|
|
192
220
|
}
|
|
193
221
|
},
|
package/src/story-variables.ts
CHANGED
|
@@ -12,7 +12,10 @@ export interface VariableSchema extends FieldSchema {
|
|
|
12
12
|
default: unknown;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
function declarationRegex(sigil: string): RegExp {
|
|
16
|
+
const escaped = sigil.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
17
|
+
return new RegExp(`^${escaped}(\\w+)\\s*=\\s*(.+)$`);
|
|
18
|
+
}
|
|
16
19
|
const VAR_REF_RE = /\$(\w+(?:\.\w+)*)/g;
|
|
17
20
|
const FOR_LOCAL_RE = /\{for\s+@(\w+)(?:\s*,\s*@(\w+))?\s+of\b/g;
|
|
18
21
|
|
|
@@ -32,20 +35,23 @@ function inferSchema(value: unknown): FieldSchema {
|
|
|
32
35
|
const jsType = typeof value;
|
|
33
36
|
if (!VALID_VAR_TYPES.has(jsType)) {
|
|
34
37
|
throw new Error(
|
|
35
|
-
`
|
|
38
|
+
`Unsupported type "${jsType}" for value ${String(value)}. Expected number, string, boolean, array, or object.`,
|
|
36
39
|
);
|
|
37
40
|
}
|
|
38
41
|
return { type: jsType as VarType };
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
|
-
* Parse a StoryVariables passage content into a schema map.
|
|
43
|
-
* Each line: `$varName = expression`
|
|
45
|
+
* Parse a StoryVariables or StoryTransients passage content into a schema map.
|
|
46
|
+
* Each line: `$varName = expression` (or `%varName = expression` for transients)
|
|
44
47
|
*/
|
|
45
48
|
export function parseStoryVariables(
|
|
46
49
|
content: string,
|
|
50
|
+
sigil: '$' | '%' = '$',
|
|
47
51
|
): Map<string, VariableSchema> {
|
|
48
52
|
const schema = new Map<string, VariableSchema>();
|
|
53
|
+
const DECLARATION_RE = declarationRegex(sigil);
|
|
54
|
+
const passageName = sigil === '%' ? 'StoryTransients' : 'StoryVariables';
|
|
49
55
|
|
|
50
56
|
for (const rawLine of content.split('\n')) {
|
|
51
57
|
const line = rawLine.trim();
|
|
@@ -54,7 +60,7 @@ export function parseStoryVariables(
|
|
|
54
60
|
const match = line.match(DECLARATION_RE);
|
|
55
61
|
if (!match) {
|
|
56
62
|
throw new Error(
|
|
57
|
-
|
|
63
|
+
`${passageName}: Invalid declaration: "${line}". Expected: ${sigil}name = value`,
|
|
58
64
|
);
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -64,11 +70,18 @@ export function parseStoryVariables(
|
|
|
64
70
|
value = new Function('return (' + expr + ')')();
|
|
65
71
|
} catch (err) {
|
|
66
72
|
throw new Error(
|
|
67
|
-
|
|
73
|
+
`${passageName}: Failed to evaluate "${sigil}${name} = ${expr}": ${err instanceof Error ? err.message : err}`,
|
|
68
74
|
);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
let fieldSchema: FieldSchema;
|
|
78
|
+
try {
|
|
79
|
+
fieldSchema = inferSchema(value);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`${passageName}: ${err instanceof Error ? err.message : err}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
72
85
|
schema.set(name, { ...fieldSchema, name, default: value });
|
|
73
86
|
}
|
|
74
87
|
|
|
@@ -146,8 +159,8 @@ export function validatePassages(
|
|
|
146
159
|
const errors: string[] = [];
|
|
147
160
|
|
|
148
161
|
for (const [name, passage] of passages) {
|
|
149
|
-
// Don't validate the StoryVariables
|
|
150
|
-
if (name === 'StoryVariables') continue;
|
|
162
|
+
// Don't validate the StoryVariables/StoryTransients passages themselves
|
|
163
|
+
if (name === 'StoryVariables' || name === 'StoryTransients') continue;
|
|
151
164
|
|
|
152
165
|
const forLocals = extractForLocals(passage.content);
|
|
153
166
|
|
package/src/triggers.ts
CHANGED
|
@@ -45,7 +45,13 @@ let dialogHostCallbacks: DialogHostCallbacks | null = null;
|
|
|
45
45
|
function evalCondition(condition: string): boolean {
|
|
46
46
|
const state = useStoryStore.getState();
|
|
47
47
|
try {
|
|
48
|
-
return !!evaluate(
|
|
48
|
+
return !!evaluate(
|
|
49
|
+
condition,
|
|
50
|
+
state.variables,
|
|
51
|
+
state.temporary,
|
|
52
|
+
{},
|
|
53
|
+
state.transient,
|
|
54
|
+
);
|
|
49
55
|
} catch {
|
|
50
56
|
return false;
|
|
51
57
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time check: the hand-written types/index.d.ts must stay in sync
|
|
3
|
+
* with the source StoryAPI interface. If this file fails to compile,
|
|
4
|
+
* the published types have drifted from the implementation.
|
|
5
|
+
*
|
|
6
|
+
* Run: npx tsc --noEmit
|
|
7
|
+
*/
|
|
8
|
+
import type { StoryAPI as SourceAPI } from './story-api';
|
|
9
|
+
import type { StoryAPI as PublishedAPI } from '../types/index';
|
|
10
|
+
|
|
11
|
+
// Both directions — if either fails, the types have drifted.
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
13
|
+
const _sourceToPublished: PublishedAPI = {} as SourceAPI;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
const _publishedToSource: SourceAPI = {} as PublishedAPI;
|
package/types/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface HistoryMoment {
|
|
|
14
14
|
passage: string;
|
|
15
15
|
variables: Record<string, unknown>;
|
|
16
16
|
timestamp: number;
|
|
17
|
+
prng?: { seed: string; pull: number } | null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -27,6 +28,7 @@ export interface SavePayload {
|
|
|
27
28
|
historyIndex: number;
|
|
28
29
|
visitCounts?: Record<string, number>;
|
|
29
30
|
renderCounts?: Record<string, number>;
|
|
31
|
+
prng?: { seed: string; pull: number } | null;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -79,6 +81,9 @@ export interface SettingsAPI {
|
|
|
79
81
|
addList(name: string, config: ListConfig): void;
|
|
80
82
|
addRange(name: string, config: RangeConfig): void;
|
|
81
83
|
get(name: string): unknown;
|
|
84
|
+
getToggle(name: string): boolean;
|
|
85
|
+
getList(name: string): string;
|
|
86
|
+
getRange(name: string): number;
|
|
82
87
|
set(name: string, value: unknown): void;
|
|
83
88
|
getAll(): Record<string, unknown>;
|
|
84
89
|
getDefinitions(): Map<string, SettingDef>;
|
|
@@ -102,6 +107,239 @@ export interface Passage {
|
|
|
102
107
|
content: string;
|
|
103
108
|
}
|
|
104
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Map of story event names to their callback signatures.
|
|
112
|
+
* @see {@link ../../src/event-emitter.ts} for the implementation.
|
|
113
|
+
*/
|
|
114
|
+
export interface StoryEventMap {
|
|
115
|
+
storyinit: () => void;
|
|
116
|
+
beforerestart: () => void;
|
|
117
|
+
actionsChanged: () => void;
|
|
118
|
+
variableChanged: (
|
|
119
|
+
changed: Record<string, { from: unknown; to: unknown }>,
|
|
120
|
+
) => void;
|
|
121
|
+
beforesave: (
|
|
122
|
+
slot: string | undefined,
|
|
123
|
+
custom: Record<string, unknown> | undefined,
|
|
124
|
+
) => void;
|
|
125
|
+
aftersave: (slot: string | undefined) => void;
|
|
126
|
+
beforeload: (slot: string | undefined) => void;
|
|
127
|
+
afterload: (slot: string | undefined) => void;
|
|
128
|
+
beforenavigate: (passageName: string) => void;
|
|
129
|
+
afternavigate: (to: string, from: string) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Event name that can be passed to `Story.on()`. */
|
|
133
|
+
export type StoryEvent = keyof StoryEventMap;
|
|
134
|
+
|
|
135
|
+
/** Callback type for a given story event. */
|
|
136
|
+
export type StoryEventCallback<E extends StoryEvent> = StoryEventMap[E];
|
|
137
|
+
|
|
138
|
+
/** Transition animation type. */
|
|
139
|
+
export type TransitionType = 'none' | 'fade' | 'fade-through' | 'crossfade';
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Configuration for passage transitions.
|
|
143
|
+
* @see {@link ../../src/transition.ts} for the implementation.
|
|
144
|
+
*/
|
|
145
|
+
export interface TransitionConfig {
|
|
146
|
+
type: TransitionType;
|
|
147
|
+
duration?: number;
|
|
148
|
+
pause?: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Options for `Story.watch()` trigger registration.
|
|
153
|
+
* @see {@link ../../src/triggers.ts} for the implementation.
|
|
154
|
+
*/
|
|
155
|
+
export interface WatchOptions {
|
|
156
|
+
goto?: string;
|
|
157
|
+
dialog?: string;
|
|
158
|
+
run?: string;
|
|
159
|
+
once?: boolean;
|
|
160
|
+
name?: string;
|
|
161
|
+
priority?: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Type of interactive action registered by a macro. */
|
|
165
|
+
export type ActionType =
|
|
166
|
+
| 'link'
|
|
167
|
+
| 'button'
|
|
168
|
+
| 'cycle'
|
|
169
|
+
| 'textbox'
|
|
170
|
+
| 'numberbox'
|
|
171
|
+
| 'textarea'
|
|
172
|
+
| 'checkbox'
|
|
173
|
+
| 'radiobutton'
|
|
174
|
+
| 'listbox'
|
|
175
|
+
| 'back'
|
|
176
|
+
| 'forward'
|
|
177
|
+
| 'restart'
|
|
178
|
+
| 'save'
|
|
179
|
+
| 'load'
|
|
180
|
+
| 'dialog';
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A registered interactive action (link, button, input, etc.).
|
|
184
|
+
* @see {@link ../../src/action-registry.ts} for the implementation.
|
|
185
|
+
*/
|
|
186
|
+
export interface StoryAction {
|
|
187
|
+
id: string;
|
|
188
|
+
type: ActionType;
|
|
189
|
+
label: string;
|
|
190
|
+
target?: string;
|
|
191
|
+
variable?: string;
|
|
192
|
+
options?: string[];
|
|
193
|
+
value?: unknown;
|
|
194
|
+
disabled?: boolean;
|
|
195
|
+
perform: (value?: unknown) => void;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Storage usage information returned by `Story.storage.getInfo()`.
|
|
200
|
+
* @see {@link ../../src/saves/types.ts} for the implementation.
|
|
201
|
+
*/
|
|
202
|
+
export interface StorageInfo {
|
|
203
|
+
saveCount: number;
|
|
204
|
+
playthroughCount: number;
|
|
205
|
+
totalBytes: number;
|
|
206
|
+
backend: 'indexeddb' | 'localstorage' | 'memory';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Browser storage quota estimate returned by `Story.storage.getQuota()`.
|
|
211
|
+
* @see {@link ../../src/saves/types.ts} for the implementation.
|
|
212
|
+
*/
|
|
213
|
+
export interface StorageQuota {
|
|
214
|
+
usage: number;
|
|
215
|
+
quota: number;
|
|
216
|
+
estimateSupported: boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Parameter metadata for a macro definition.
|
|
221
|
+
* @see {@link ../../src/registry.ts} for the implementation.
|
|
222
|
+
*/
|
|
223
|
+
export interface ParameterDef {
|
|
224
|
+
name: string;
|
|
225
|
+
required?: boolean;
|
|
226
|
+
description?: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Metadata about a registered macro, returned by `Story.getMacroRegistry()`.
|
|
231
|
+
* @see {@link ../../src/registry.ts} for the implementation.
|
|
232
|
+
*/
|
|
233
|
+
export interface MacroMetadata {
|
|
234
|
+
name: string;
|
|
235
|
+
block: boolean;
|
|
236
|
+
subMacros: string[];
|
|
237
|
+
storeVar?: boolean;
|
|
238
|
+
interpolate?: boolean;
|
|
239
|
+
merged?: boolean;
|
|
240
|
+
source: 'builtin' | 'user';
|
|
241
|
+
description?: string;
|
|
242
|
+
parameters?: ParameterDef[];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Props passed to a macro's render function.
|
|
247
|
+
* @see {@link ../../src/registry.ts} for the implementation.
|
|
248
|
+
*/
|
|
249
|
+
export interface MacroProps {
|
|
250
|
+
rawArgs: string;
|
|
251
|
+
className?: string;
|
|
252
|
+
id?: string;
|
|
253
|
+
children?: any[];
|
|
254
|
+
branches?: Array<{
|
|
255
|
+
rawArgs: string;
|
|
256
|
+
className?: string;
|
|
257
|
+
id?: string;
|
|
258
|
+
children: any[];
|
|
259
|
+
}>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Options for registering an interactive action via `ctx.useAction`.
|
|
264
|
+
* @see {@link ../../src/hooks/use-action.ts} for the implementation.
|
|
265
|
+
*/
|
|
266
|
+
export interface UseActionOptions {
|
|
267
|
+
type: ActionType;
|
|
268
|
+
key: string;
|
|
269
|
+
authorId?: string;
|
|
270
|
+
label: string;
|
|
271
|
+
target?: string;
|
|
272
|
+
variable?: string;
|
|
273
|
+
options?: string[];
|
|
274
|
+
value?: unknown;
|
|
275
|
+
disabled?: boolean;
|
|
276
|
+
perform: (value?: unknown) => void;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Context object passed to a macro's render function alongside props.
|
|
281
|
+
* Internal Preact/AST types are represented as `any` since consumers
|
|
282
|
+
* may not have Preact type definitions installed.
|
|
283
|
+
* @see {@link ../../src/define-macro.ts} for the implementation.
|
|
284
|
+
*/
|
|
285
|
+
export interface MacroContext {
|
|
286
|
+
className?: string;
|
|
287
|
+
id?: string;
|
|
288
|
+
resolve?: (s: string | undefined) => string | undefined;
|
|
289
|
+
cls: string;
|
|
290
|
+
mutate: (code: string) => void;
|
|
291
|
+
update: (key: string, value: unknown) => void;
|
|
292
|
+
getValues: () => Record<string, unknown>;
|
|
293
|
+
merged?: readonly [
|
|
294
|
+
Record<string, unknown>,
|
|
295
|
+
Record<string, unknown>,
|
|
296
|
+
Record<string, unknown>,
|
|
297
|
+
Record<string, unknown>,
|
|
298
|
+
];
|
|
299
|
+
varName?: string;
|
|
300
|
+
value?: unknown;
|
|
301
|
+
setValue?: (value: unknown) => void;
|
|
302
|
+
getValue?: () => unknown;
|
|
303
|
+
evaluate?: (expr: string) => unknown;
|
|
304
|
+
collectText: (nodes: any[]) => string;
|
|
305
|
+
sourceLocation: () => string;
|
|
306
|
+
parseVarArgs: (rawArgs: string) => { varName: string; placeholder: string };
|
|
307
|
+
extractOptions: (children: any[]) => string[];
|
|
308
|
+
wrap: (content: any) => any;
|
|
309
|
+
useAction: (opts: UseActionOptions) => string;
|
|
310
|
+
h: (type: any, props: any, ...children: any[]) => any;
|
|
311
|
+
renderNodes: (
|
|
312
|
+
nodes: any[],
|
|
313
|
+
options?: { nobr?: boolean; locals?: Record<string, unknown> },
|
|
314
|
+
) => any;
|
|
315
|
+
renderInlineNodes: (nodes: any[]) => any;
|
|
316
|
+
hooks: {
|
|
317
|
+
useState: any;
|
|
318
|
+
useRef: any;
|
|
319
|
+
useEffect: any;
|
|
320
|
+
useLayoutEffect: any;
|
|
321
|
+
useCallback: any;
|
|
322
|
+
useMemo: any;
|
|
323
|
+
useContext: any;
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Configuration object for `Story.defineMacro()`.
|
|
329
|
+
* @see {@link ../../src/define-macro.ts} for the implementation.
|
|
330
|
+
*/
|
|
331
|
+
export interface MacroDefinition {
|
|
332
|
+
name: string;
|
|
333
|
+
subMacros?: string[];
|
|
334
|
+
block?: boolean;
|
|
335
|
+
interpolate?: boolean;
|
|
336
|
+
merged?: boolean;
|
|
337
|
+
storeVar?: boolean;
|
|
338
|
+
description?: string;
|
|
339
|
+
parameters?: ParameterDef[];
|
|
340
|
+
render: (props: MacroProps, ctx: MacroContext) => any;
|
|
341
|
+
}
|
|
342
|
+
|
|
105
343
|
/**
|
|
106
344
|
* Metadata about a save slot, returned by `getSaveInfo()` and `listSaves()`.
|
|
107
345
|
* @see {@link ../../src/saves/types.ts} for the implementation.
|
|
@@ -127,12 +365,20 @@ export interface SaveInfo {
|
|
|
127
365
|
* @see {@link ../../src/story-api.ts} for the implementation.
|
|
128
366
|
*/
|
|
129
367
|
export interface StoryAPI {
|
|
130
|
-
/**
|
|
368
|
+
/**
|
|
369
|
+
* Get a variable value. Use '%name' prefix for transient variables.
|
|
370
|
+
* @example Story.get('health') // $health
|
|
371
|
+
* @example Story.get('%npcList') // %npcList (transient)
|
|
372
|
+
*/
|
|
131
373
|
get(name: string): unknown;
|
|
132
374
|
|
|
133
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* Set one or more variables. Use '%name' prefix for transient variables.
|
|
377
|
+
* @example Story.set('health', 100)
|
|
378
|
+
* @example Story.set('%npcList', [...])
|
|
379
|
+
* @example Story.set({ health: 100, '%npcList': [...] })
|
|
380
|
+
*/
|
|
134
381
|
set(name: string, value: unknown): void;
|
|
135
|
-
/** Set multiple story variables at once. */
|
|
136
382
|
set(vars: Record<string, unknown>): void;
|
|
137
383
|
|
|
138
384
|
/** Navigate to a passage by name. */
|
|
@@ -166,10 +412,10 @@ export interface StoryAPI {
|
|
|
166
412
|
deleteSave(slot?: string): void;
|
|
167
413
|
|
|
168
414
|
/** Return the number of times a passage has been visited. */
|
|
169
|
-
visited(name
|
|
415
|
+
visited(name?: string): number;
|
|
170
416
|
|
|
171
417
|
/** Check if a passage has been visited at least once. */
|
|
172
|
-
hasVisited(name
|
|
418
|
+
hasVisited(name?: string): boolean;
|
|
173
419
|
|
|
174
420
|
/** Check if any of the given passages have been visited. */
|
|
175
421
|
hasVisitedAny(...names: string[]): boolean;
|
|
@@ -178,10 +424,10 @@ export interface StoryAPI {
|
|
|
178
424
|
hasVisitedAll(...names: string[]): boolean;
|
|
179
425
|
|
|
180
426
|
/** Return the number of times a passage has been rendered. */
|
|
181
|
-
rendered(name
|
|
427
|
+
rendered(name?: string): number;
|
|
182
428
|
|
|
183
429
|
/** Check if a passage has been rendered at least once. */
|
|
184
|
-
hasRendered(name
|
|
430
|
+
hasRendered(name?: string): boolean;
|
|
185
431
|
|
|
186
432
|
/** Check if any of the given passages have been rendered. */
|
|
187
433
|
hasRenderedAny(...names: string[]): boolean;
|
|
@@ -228,4 +474,95 @@ export interface StoryAPI {
|
|
|
228
474
|
|
|
229
475
|
/** Check whether any dialog is currently open. */
|
|
230
476
|
isDialogOpen(): boolean;
|
|
477
|
+
|
|
478
|
+
/** Register a class constructor for use in story expressions. */
|
|
479
|
+
registerClass(name: string, ctor: new (...args: any[]) => any): void;
|
|
480
|
+
|
|
481
|
+
/** Register a custom macro. */
|
|
482
|
+
defineMacro(config: MacroDefinition): void;
|
|
483
|
+
|
|
484
|
+
/** Return metadata for all registered macros. */
|
|
485
|
+
getMacroRegistry(): MacroMetadata[];
|
|
486
|
+
|
|
487
|
+
/** Storage management API. */
|
|
488
|
+
readonly storage: {
|
|
489
|
+
/** Get storage usage information (save count, byte size, backend type). */
|
|
490
|
+
getInfo(): Promise<StorageInfo>;
|
|
491
|
+
/** Get browser storage quota estimate. */
|
|
492
|
+
getQuota(): Promise<StorageQuota>;
|
|
493
|
+
/** Delete all saves for the current game. */
|
|
494
|
+
clearGameData(): Promise<void>;
|
|
495
|
+
/** Delete all Spindle data across all games. */
|
|
496
|
+
clearAllData(): Promise<void>;
|
|
497
|
+
/** Delete a specific playthrough and its saves. */
|
|
498
|
+
deletePlaythrough(playthroughId: string): Promise<void>;
|
|
499
|
+
/** The active storage backend. */
|
|
500
|
+
readonly backend: 'indexeddb' | 'localstorage' | 'memory';
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/** Return all registered interactive actions. */
|
|
504
|
+
getActions(): StoryAction[];
|
|
505
|
+
|
|
506
|
+
/** Perform a registered action by ID. */
|
|
507
|
+
performAction(id: string, value?: unknown): void;
|
|
508
|
+
|
|
509
|
+
/** Subscribe to a story event. Returns an unsubscribe function. */
|
|
510
|
+
on<E extends StoryEvent>(
|
|
511
|
+
event: E,
|
|
512
|
+
callback: StoryEventCallback<E>,
|
|
513
|
+
): () => void;
|
|
514
|
+
|
|
515
|
+
/** Wait for the next frame's actions to be registered, then return them. */
|
|
516
|
+
waitForActions(): Promise<StoryAction[]>;
|
|
517
|
+
|
|
518
|
+
/** Register a trigger that fires when a condition expression becomes truthy. Returns an unsubscribe function. */
|
|
519
|
+
watch(
|
|
520
|
+
condition: string,
|
|
521
|
+
callbackOrOptions: (() => void) | WatchOptions,
|
|
522
|
+
): () => void;
|
|
523
|
+
|
|
524
|
+
/** Remove a named trigger registered with `watch()`. */
|
|
525
|
+
unwatch(name: string): void;
|
|
526
|
+
|
|
527
|
+
/** Enable or disable the `{nobr}` (no line breaks) rendering mode globally. */
|
|
528
|
+
setNobr(enabled: boolean): void;
|
|
529
|
+
|
|
530
|
+
/** Enable or disable the story stylesheet. */
|
|
531
|
+
setCSS(enabled: boolean): void;
|
|
532
|
+
|
|
533
|
+
/** Set the default passage transition. Pass `null` to clear. */
|
|
534
|
+
setTransition(config: TransitionConfig | null): void;
|
|
535
|
+
|
|
536
|
+
/** Set a one-time transition for the next navigation only. Pass `null` to clear. */
|
|
537
|
+
setNextTransition(config: TransitionConfig | null): void;
|
|
538
|
+
|
|
539
|
+
/** Defer initial passage rendering until `ready()` is called. */
|
|
540
|
+
deferRender(): void;
|
|
541
|
+
|
|
542
|
+
/** Unblock deferred rendering (call after `deferRender()`). */
|
|
543
|
+
ready(): void;
|
|
544
|
+
|
|
545
|
+
/** Return a random float in [0, 1). Uses the seeded PRNG if enabled, otherwise Math.random(). */
|
|
546
|
+
random(): number;
|
|
547
|
+
|
|
548
|
+
/** Return a random integer in [min, max] (inclusive). */
|
|
549
|
+
randomInt(min: number, max: number): number;
|
|
550
|
+
|
|
551
|
+
/** Story configuration. */
|
|
552
|
+
readonly config: {
|
|
553
|
+
/** Maximum number of history moments to retain. */
|
|
554
|
+
maxHistory: number;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
/** Seedable pseudo-random number generator. */
|
|
558
|
+
readonly prng: {
|
|
559
|
+
/** Initialize the PRNG with an optional seed. */
|
|
560
|
+
init(seed?: string, useEntropy?: boolean): void;
|
|
561
|
+
/** Check whether the seeded PRNG is active. */
|
|
562
|
+
isEnabled(): boolean;
|
|
563
|
+
/** The current PRNG seed. */
|
|
564
|
+
readonly seed: string;
|
|
565
|
+
/** The number of values pulled from the current seed. */
|
|
566
|
+
readonly pull: number;
|
|
567
|
+
};
|
|
231
568
|
}
|