@rohal12/spindle 0.37.1 → 0.38.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/PassageDisplay.tsx +13 -10
- package/src/index.tsx +9 -2
- package/src/store.ts +18 -0
- package/src/story-api.ts +35 -0
package/package.json
CHANGED
|
@@ -71,6 +71,7 @@ defineMacro({
|
|
|
71
71
|
render(_props, ctx) {
|
|
72
72
|
const currentPassage = useStoryStore((s) => s.currentPassage);
|
|
73
73
|
const storyData = useStoryStore((s) => s.storyData);
|
|
74
|
+
const renderDeferred = useStoryStore((s) => s.renderDeferred);
|
|
74
75
|
|
|
75
76
|
// Render-gating: displayedPassage controls which passage is visually shown.
|
|
76
77
|
// The store updates immediately, but the visual swap is deferred by the
|
|
@@ -245,12 +246,14 @@ defineMacro({
|
|
|
245
246
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
246
247
|
}, [currentPassage]);
|
|
247
248
|
|
|
248
|
-
//
|
|
249
|
-
const
|
|
250
|
-
? storyData?.passages.get(
|
|
251
|
-
:
|
|
249
|
+
// When render is deferred, show StoryLoading passage or nothing
|
|
250
|
+
const effectivePassage = renderDeferred
|
|
251
|
+
? (storyData?.passages.get('StoryLoading') ?? null)
|
|
252
|
+
: displayedPassage
|
|
253
|
+
? storyData?.passages.get(displayedPassage)
|
|
254
|
+
: null;
|
|
252
255
|
|
|
253
|
-
if (!
|
|
256
|
+
if (!effectivePassage && displayedPassage && !renderDeferred) {
|
|
254
257
|
return (
|
|
255
258
|
<div class="error">
|
|
256
259
|
Error: Passage “{displayedPassage}” not found.
|
|
@@ -265,7 +268,7 @@ defineMacro({
|
|
|
265
268
|
id={ctx.id ?? 'story'}
|
|
266
269
|
class={ctx.className ?? 'story'}
|
|
267
270
|
>
|
|
268
|
-
{readyPassage && (
|
|
271
|
+
{!renderDeferred && readyPassage && (
|
|
269
272
|
<div
|
|
270
273
|
key={`ready-${currentPassage}`}
|
|
271
274
|
hidden
|
|
@@ -277,11 +280,11 @@ defineMacro({
|
|
|
277
280
|
class="passage-container"
|
|
278
281
|
ref={containerRef}
|
|
279
282
|
>
|
|
280
|
-
{
|
|
283
|
+
{effectivePassage && (
|
|
281
284
|
<Passage
|
|
282
|
-
passage={
|
|
283
|
-
key={displayedPassage}
|
|
284
|
-
dataTransition={resolvedTypeRef.current}
|
|
285
|
+
passage={effectivePassage}
|
|
286
|
+
key={renderDeferred ? 'loading' : displayedPassage}
|
|
287
|
+
dataTransition={renderDeferred ? 'none' : resolvedTypeRef.current}
|
|
285
288
|
/>
|
|
286
289
|
)}
|
|
287
290
|
</div>
|
package/src/index.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { render } from 'preact';
|
|
|
2
2
|
import { App } from './components/App';
|
|
3
3
|
import { parseStoryData } from './parser';
|
|
4
4
|
import { useStoryStore, fireStoryInit } from './store';
|
|
5
|
-
import { installStoryAPI } from './story-api';
|
|
5
|
+
import { installStoryAPI, getReadyPromise } from './story-api';
|
|
6
6
|
import { resetIdCounters } from './action-registry';
|
|
7
7
|
import { executeStoryInit } from './story-init';
|
|
8
8
|
import { checkTriggers, reinitTriggerState } from './triggers';
|
|
@@ -200,7 +200,14 @@ function boot() {
|
|
|
200
200
|
|
|
201
201
|
render(<App />, root);
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
const pending = getReadyPromise();
|
|
204
|
+
if (pending) {
|
|
205
|
+
pending.then(() => {
|
|
206
|
+
document.dispatchEvent(new CustomEvent(':storyready'));
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
document.dispatchEvent(new CustomEvent(':storyready'));
|
|
210
|
+
}
|
|
204
211
|
}
|
|
205
212
|
|
|
206
213
|
if (document.readyState === 'loading') {
|
package/src/store.ts
CHANGED
|
@@ -41,6 +41,7 @@ const SPECIAL_PASSAGES = new Set([
|
|
|
41
41
|
'StoryInit',
|
|
42
42
|
'StoryInterface',
|
|
43
43
|
'StoryVariables',
|
|
44
|
+
'StoryLoading',
|
|
44
45
|
'SaveTitle',
|
|
45
46
|
'PassageReady',
|
|
46
47
|
'PassageHeader',
|
|
@@ -233,6 +234,7 @@ export interface StoryState {
|
|
|
233
234
|
transitionConfig: TransitionConfig | null;
|
|
234
235
|
nextTransition: TransitionConfig | null;
|
|
235
236
|
nobr: boolean;
|
|
237
|
+
renderDeferred: boolean;
|
|
236
238
|
|
|
237
239
|
setMaxHistory: (limit: number) => void;
|
|
238
240
|
init: (
|
|
@@ -263,6 +265,8 @@ export interface StoryState {
|
|
|
263
265
|
setTransition: (config: TransitionConfig | null) => void;
|
|
264
266
|
setNextTransition: (config: TransitionConfig | null) => void;
|
|
265
267
|
consumeNextTransition: () => TransitionConfig | null;
|
|
268
|
+
deferRender: () => void;
|
|
269
|
+
clearDeferredRender: () => void;
|
|
266
270
|
}
|
|
267
271
|
|
|
268
272
|
export const useStoryStore = create<StoryState>()(
|
|
@@ -284,6 +288,7 @@ export const useStoryStore = create<StoryState>()(
|
|
|
284
288
|
transitionConfig: null,
|
|
285
289
|
nextTransition: null,
|
|
286
290
|
nobr: false,
|
|
291
|
+
renderDeferred: false,
|
|
287
292
|
|
|
288
293
|
setMaxHistory: (limit: number) => {
|
|
289
294
|
set((state) => {
|
|
@@ -510,6 +515,7 @@ export const useStoryStore = create<StoryState>()(
|
|
|
510
515
|
state.historyIndex = 0;
|
|
511
516
|
state.visitCounts = { [startPassage.name]: 1 };
|
|
512
517
|
state.renderCounts = { [startPassage.name]: 1 };
|
|
518
|
+
state.renderDeferred = false;
|
|
513
519
|
});
|
|
514
520
|
|
|
515
521
|
lastNavigationVars = get().variables;
|
|
@@ -776,5 +782,17 @@ export const useStoryStore = create<StoryState>()(
|
|
|
776
782
|
}
|
|
777
783
|
return current;
|
|
778
784
|
},
|
|
785
|
+
|
|
786
|
+
deferRender: () => {
|
|
787
|
+
set((state) => {
|
|
788
|
+
state.renderDeferred = true;
|
|
789
|
+
});
|
|
790
|
+
},
|
|
791
|
+
|
|
792
|
+
clearDeferredRender: () => {
|
|
793
|
+
set((state) => {
|
|
794
|
+
state.renderDeferred = false;
|
|
795
|
+
});
|
|
796
|
+
},
|
|
779
797
|
})),
|
|
780
798
|
);
|
package/src/story-api.ts
CHANGED
|
@@ -50,6 +50,24 @@ import type { TransitionConfig } from './transition';
|
|
|
50
50
|
export type { StoryAction };
|
|
51
51
|
export type { MacroMetadata };
|
|
52
52
|
|
|
53
|
+
// Deferred-render promise lifecycle.
|
|
54
|
+
// deferRender() creates a promise; ready() resolves it.
|
|
55
|
+
// index.tsx reads getReadyPromise() AFTER render(), which is after both
|
|
56
|
+
// author JS and storyinit have run, so it always gets the final promise.
|
|
57
|
+
let readyResolve: (() => void) | null = null;
|
|
58
|
+
let readyPromise: Promise<void> | null = null;
|
|
59
|
+
|
|
60
|
+
/** Returns the current deferred-render promise, or null if not deferred. */
|
|
61
|
+
export function getReadyPromise(): Promise<void> | null {
|
|
62
|
+
return readyPromise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Test-only: reset module-level promise state. */
|
|
66
|
+
export function _resetReadyState(): void {
|
|
67
|
+
readyResolve = null;
|
|
68
|
+
readyPromise = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
type NavigateCallback = (to: string, from: string) => void;
|
|
54
72
|
type StoryInitCallback = () => void;
|
|
55
73
|
type ActionsChangedCallback = () => void;
|
|
@@ -121,6 +139,8 @@ export interface StoryAPI {
|
|
|
121
139
|
setCSS(enabled: boolean): void;
|
|
122
140
|
setTransition(config: TransitionConfig | null): void;
|
|
123
141
|
setNextTransition(config: TransitionConfig | null): void;
|
|
142
|
+
deferRender(): void;
|
|
143
|
+
ready(): void;
|
|
124
144
|
random(): number;
|
|
125
145
|
randomInt(min: number, max: number): number;
|
|
126
146
|
readonly config: {
|
|
@@ -452,6 +472,21 @@ function createStoryAPI(): StoryAPI {
|
|
|
452
472
|
useStoryStore.getState().setNextTransition(config);
|
|
453
473
|
},
|
|
454
474
|
|
|
475
|
+
deferRender(): void {
|
|
476
|
+
useStoryStore.getState().deferRender();
|
|
477
|
+
readyPromise = new Promise<void>((resolve) => {
|
|
478
|
+
readyResolve = resolve;
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
ready(): void {
|
|
483
|
+
if (!readyResolve) return;
|
|
484
|
+
useStoryStore.getState().clearDeferredRender();
|
|
485
|
+
readyResolve();
|
|
486
|
+
readyResolve = null;
|
|
487
|
+
readyPromise = null;
|
|
488
|
+
},
|
|
489
|
+
|
|
455
490
|
random(): number {
|
|
456
491
|
return random();
|
|
457
492
|
},
|