narraleaf-react 0.26.0 → 0.28.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/game/nlcore/action/actionTypes.d.ts +1 -1
- package/dist/game/nlcore/action/actions/sceneAction.d.ts +9 -2
- package/dist/game/nlcore/common/elements.d.ts +1 -0
- package/dist/game/nlcore/common/transition.d.ts +3 -1
- package/dist/game/nlcore/elements/character/word.d.ts +96 -0
- package/dist/game/nlcore/elements/scene.d.ts +13 -1
- package/dist/game/nlcore/elements/transition/transitions/image/exposure.d.ts +60 -0
- package/dist/game/nlcore/elements/transition/transitions/image/imageTransition.d.ts +14 -0
- package/dist/game/nlcore/gameTypes.d.ts +4 -3
- package/dist/game/player/elements/say/dialogOverlay.d.ts +52 -0
- package/dist/game/player/elements/say/wordRenderer.d.ts +29 -0
- package/dist/game/player/elements/scene/stageTransition.d.ts +47 -0
- package/dist/game/player/gameState.d.ts +19 -0
- package/dist/game/player/lib/elementProps.d.ts +1 -0
- package/dist/game/player/lib/useSuspendAdvance.d.ts +22 -0
- package/dist/game/player/libElements.d.ts +5 -1
- package/dist/main.js +47 -47
- package/package.json +1 -1
|
@@ -52,7 +52,7 @@ export type NvlBlockOptions = {
|
|
|
52
52
|
hideTransition?: Partial<TransformDefinitions.CommonTransformProps>;
|
|
53
53
|
};
|
|
54
54
|
export type SceneActionContentType = {
|
|
55
|
-
[K in typeof SceneActionTypes[keyof typeof SceneActionTypes]]: K extends typeof SceneActionTypes["action"] ? Scene : K extends typeof SceneActionTypes["init"] ? [Scene] : K extends typeof SceneActionTypes["exit"] ? [] : K extends typeof SceneActionTypes["jumpTo"] ? [Scene] : K extends typeof SceneActionTypes["setBackgroundMusic"] ? [Sound | null, number?] : K extends typeof SceneActionTypes["preUnmount"] ? [] : K extends typeof SceneActionTypes["transitionToScene"] ? [
|
|
55
|
+
[K in typeof SceneActionTypes[keyof typeof SceneActionTypes]]: K extends typeof SceneActionTypes["action"] ? Scene : K extends typeof SceneActionTypes["init"] ? [Scene] : K extends typeof SceneActionTypes["exit"] ? [] : K extends typeof SceneActionTypes["jumpTo"] ? [Scene] : K extends typeof SceneActionTypes["setBackgroundMusic"] ? [Sound | null, number?] : K extends typeof SceneActionTypes["preUnmount"] ? [] : K extends typeof SceneActionTypes["transitionToScene"] ? [Transition, Scene] : K extends typeof SceneActionTypes["nvlBlock"] ? [LogicAction.Actions[], NvlBlockOptions] : K extends typeof SceneActionTypes["nvlShow"] ? [Partial<TransformDefinitions.CommonTransformProps>?] : K extends typeof SceneActionTypes["nvlHide"] ? [Partial<TransformDefinitions.CommonTransformProps>?] : K extends typeof SceneActionTypes["nvlEnd"] ? [NvlBlockOptions?] : any;
|
|
56
56
|
};
|
|
57
57
|
export declare const StoryActionTypes: {
|
|
58
58
|
readonly action: "story:action";
|
|
@@ -7,7 +7,7 @@ import { ContentNode } from "../../action/tree/actionTree";
|
|
|
7
7
|
import { LogicAction } from "../../action/logicAction";
|
|
8
8
|
import { TypedAction } from "../../action/actions";
|
|
9
9
|
import { Story } from "../../elements/story";
|
|
10
|
-
import {
|
|
10
|
+
import type { Transition } from "../../elements/transition/transition";
|
|
11
11
|
import { ActionSearchOptions } from "../../types";
|
|
12
12
|
import { ExposedState, ExposedStateType } from "../../../player/type";
|
|
13
13
|
import type { TransformDefinitions } from "../../elements/transform/type";
|
|
@@ -46,7 +46,14 @@ export declare class SceneAction<T extends typeof SceneActionTypes[keyof typeof
|
|
|
46
46
|
static initBackgroundMusic(scene: Scene, exposed: ExposedState[ExposedStateType.scene]): Promise<void>;
|
|
47
47
|
static createSceneSnapshot(scene: Scene, state: GameState): SceneSnapshot;
|
|
48
48
|
static restoreSceneSnapshot(snapshot: SceneSnapshot, state: GameState): void;
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Play `transition` across the whole stage while jumping from this scene to `target`.
|
|
51
|
+
*
|
|
52
|
+
* Both scenes are mounted at this point — `scene:init` added the incoming one and
|
|
53
|
+
* `scene:exit` has not yet removed this one — so the transition drives the two live scene
|
|
54
|
+
* subtrees rather than swapping one image's source underneath them.
|
|
55
|
+
*/
|
|
56
|
+
applyStageTransition(gameState: GameState, transition: Transition, target: Scene, injection: ActionExecutionInjection): Awaitable<CalledActionResult, CalledActionResult>;
|
|
50
57
|
exit(state: GameState): void;
|
|
51
58
|
applyNvlVisibility(gameState: GameState, visible: boolean, options: Partial<TransformDefinitions.CommonTransformProps> | undefined, injection: ActionExecutionInjection): CalledActionResult | Awaitable<CalledActionResult, CalledActionResult>;
|
|
52
59
|
executeAction(gameState: GameState, injection: ActionExecutionInjection): ExecutedActionResult;
|
|
@@ -29,3 +29,4 @@ export type { LayeredDefinition, LayerGroupDefinition, LayerResolver, LayerSlot,
|
|
|
29
29
|
export type { SentenceMetadata } from "../elements/character/sentence";
|
|
30
30
|
export type { TextEventAppearance, TextEventConfig, TextEventExpression, } from "../elements/character/textEvent";
|
|
31
31
|
export type { CharacterPortraitConfig, DialogAvatar, DialogAvatarResolver, DialogAvatarResolverContext, DialogAvatarResolution, DialogAvatarSource, } from "../elements/character/avatar";
|
|
32
|
+
export type { WordConfig, WordRenderProps, WordRenderer, } from "../elements/character/word";
|
|
@@ -6,15 +6,17 @@ import { TextTransition } from "../elements/transition/transitions/text/textTran
|
|
|
6
6
|
import { BlurDissolve } from "../elements/transition/transitions/image/blurDissolve";
|
|
7
7
|
import { Push } from "../elements/transition/transitions/image/push";
|
|
8
8
|
import { Darkness } from "../elements/transition/transitions/image/darkness";
|
|
9
|
+
import { Exposure } from "../elements/transition/transitions/image/exposure";
|
|
9
10
|
import { ThroughColor } from "../elements/transition/transitions/image/throughColor";
|
|
10
11
|
import { Reveal } from "../elements/transition/transitions/image/reveal";
|
|
11
12
|
import { Mask } from "../elements/transition/transitions/image/mask";
|
|
12
|
-
export { Transition, ImageTransition, TextTransition, Dissolve, FadeIn, BlurDissolve, Push, Darkness, ThroughColor, Reveal, Mask, };
|
|
13
|
+
export { Transition, ImageTransition, TextTransition, Dissolve, FadeIn, BlurDissolve, Push, Darkness, Exposure, ThroughColor, Reveal, Mask, };
|
|
13
14
|
export type { DissolveOptions } from "../elements/transition/transitions/image/dissolve";
|
|
14
15
|
export type { FadeInOptions } from "../elements/transition/transitions/image/fadeIn";
|
|
15
16
|
export type { BlurDissolveOptions } from "../elements/transition/transitions/image/blurDissolve";
|
|
16
17
|
export type { PushOptions } from "../elements/transition/transitions/image/push";
|
|
17
18
|
export type { DarknessOptions } from "../elements/transition/transitions/image/darkness";
|
|
19
|
+
export type { ExposureOptions } from "../elements/transition/transitions/image/exposure";
|
|
18
20
|
export type { ThroughColorOptions, ThroughColorUncover, } from "../elements/transition/transitions/image/throughColor";
|
|
19
21
|
export type { RevealOptions } from "../elements/transition/transitions/image/reveal";
|
|
20
22
|
export type { MaskPattern, WipePatternOptions, BarnDoorPatternOptions, IrisPatternOptions, ClockPatternOptions, FanPatternOptions, BlindsPatternOptions, DotsPatternOptions, } from "../elements/transition/transitions/image/mask";
|
|
@@ -2,12 +2,63 @@ import { Color, Font } from "../../types";
|
|
|
2
2
|
import { DynamicWord } from "../../elements/character/sentence";
|
|
3
3
|
import { Pausing } from "../../elements/character/pause";
|
|
4
4
|
import { TextEvent } from "../../elements/character/textEvent";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
/**
|
|
7
|
+
* What a custom word renderer receives.
|
|
8
|
+
*
|
|
9
|
+
* A custom-rendered word is still an ordinary text word — it goes into the backlog, the read-text
|
|
10
|
+
* record and the voice pipeline as its plain text, and it is typed out character by character like
|
|
11
|
+
* any other. The renderer only decides what the revealed characters look like and what happens when
|
|
12
|
+
* they are interacted with.
|
|
13
|
+
*/
|
|
14
|
+
export type WordRenderProps<T = unknown> = {
|
|
15
|
+
/**
|
|
16
|
+
* The word's text as the engine has already laid it out — ruby, vertical writing mode and
|
|
17
|
+
* tate-chu-yoko are applied here. Render this rather than {@link WordRenderProps.text}, or those
|
|
18
|
+
* settings are silently dropped.
|
|
19
|
+
*/
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
/** The characters revealed so far. Equal to `fullText` once the typewriter has passed the word. */
|
|
22
|
+
text: string;
|
|
23
|
+
/** The whole word, revealed or not. */
|
|
24
|
+
fullText: string;
|
|
25
|
+
/** Whether every character of this word has been revealed. */
|
|
26
|
+
revealed: boolean;
|
|
27
|
+
/** Whether the whole line has finished revealing. */
|
|
28
|
+
done: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The resolved style chain for this word (engine defaults, then the dialog's text props, then
|
|
31
|
+
* the sentence, then the word). Already applied to the element wrapping the renderer; passed in
|
|
32
|
+
* so the renderer can measure against it or carry it into a portal.
|
|
33
|
+
*/
|
|
34
|
+
style: Readonly<React.CSSProperties>;
|
|
35
|
+
/** The word's own config, as given to {@link Word}. */
|
|
36
|
+
config: Readonly<Partial<WordConfig>>;
|
|
37
|
+
/** The payload passed as `data` when the word was created. */
|
|
38
|
+
data: T;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A component to render a word with, or the id of one registered with `registerWordRenderer`.
|
|
42
|
+
*
|
|
43
|
+
* Pass a component in code; pass an id when the word comes from data (a story file, a plugin) and
|
|
44
|
+
* cannot carry a function. An id that resolves to nothing renders as plain text.
|
|
45
|
+
*/
|
|
46
|
+
export type WordRenderer<T = any> = string | React.ComponentType<WordRenderProps<T>>;
|
|
5
47
|
export type WordConfig = {
|
|
6
48
|
className: string;
|
|
7
49
|
ruby: string;
|
|
8
50
|
color: Color;
|
|
9
51
|
pause: boolean;
|
|
10
52
|
cps?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Renders this word's revealed characters. See {@link Word.custom}.
|
|
55
|
+
*/
|
|
56
|
+
render?: WordRenderer;
|
|
57
|
+
/**
|
|
58
|
+
* Arbitrary payload handed to {@link WordConfig.render} as `data`. Never merged or cloned — the
|
|
59
|
+
* renderer receives the very value given here.
|
|
60
|
+
*/
|
|
61
|
+
data?: unknown;
|
|
11
62
|
} & Font;
|
|
12
63
|
export declare class Word<T extends string | DynamicWord | Pausing | TextEvent = string | DynamicWord | Pausing | TextEvent> {
|
|
13
64
|
static isWord(obj: any): obj is Word;
|
|
@@ -31,6 +82,51 @@ export declare class Word<T extends string | DynamicWord | Pausing | TextEvent =
|
|
|
31
82
|
* @param text - The text or word to italicize.
|
|
32
83
|
*/
|
|
33
84
|
static italic(text: string | Word): Word;
|
|
85
|
+
/**
|
|
86
|
+
* Render a word with a component of your own — an inline glossary term that opens a definition
|
|
87
|
+
* popup, a name that links into an in-game encyclopedia, anything the dialogue box cannot say
|
|
88
|
+
* with color and weight alone.
|
|
89
|
+
*
|
|
90
|
+
* The word stays a text word in every other respect: it is typed out character by character, it
|
|
91
|
+
* reaches the backlog and the read-text record as its plain text, and it is never serialized —
|
|
92
|
+
* the renderer is re-attached when the `say` action is evaluated again, so a save carries no
|
|
93
|
+
* trace of it.
|
|
94
|
+
*
|
|
95
|
+
* The renderer sits *inside* the element the engine styles, so the style chain (engine defaults
|
|
96
|
+
* → the dialog's text props → the sentence → the word) already applies to it; anything the
|
|
97
|
+
* renderer sets wins, being last. `render` propagates to plain strings a dynamic word returns,
|
|
98
|
+
* the same way `className` does.
|
|
99
|
+
*
|
|
100
|
+
* While a custom word is still being typed, a click on it advances the line as a click anywhere
|
|
101
|
+
* else would. Once revealed, the word takes its own clicks — the line does not advance behind
|
|
102
|
+
* the renderer's back.
|
|
103
|
+
*
|
|
104
|
+
* @param text - The word's text, or an existing word to re-render.
|
|
105
|
+
* @param render - A component, or the id of one registered with `registerWordRenderer`.
|
|
106
|
+
* @param config - Optional styling, plus the `data` payload handed to the renderer.
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* function GlossaryTerm({children, revealed, data}: WordRenderProps<{entry: string}>) {
|
|
110
|
+
* const [open, setOpen] = useState(false);
|
|
111
|
+
* return (
|
|
112
|
+
* <span className="underline decoration-dotted"
|
|
113
|
+
* onClick={() => revealed && setOpen(v => !v)}>
|
|
114
|
+
* {children}
|
|
115
|
+
* {open && <span className="absolute">{lookUp(data.entry)}</span>}
|
|
116
|
+
* </span>
|
|
117
|
+
* );
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* character.say([
|
|
121
|
+
* "今天的",
|
|
122
|
+
* Word.custom("以太浓度", GlossaryTerm, {data: {entry: "aether"}}),
|
|
123
|
+
* "高得反常。",
|
|
124
|
+
* ]);
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
static custom<T = unknown>(text: string | Word, render: WordRenderer<T>, config?: Partial<Omit<WordConfig, "render" | "data">> & {
|
|
128
|
+
data?: T;
|
|
129
|
+
}): Word;
|
|
34
130
|
/**
|
|
35
131
|
* Wrap raw data (string, dynamic function, or pause) into a `Word` for sentences.
|
|
36
132
|
* @param text - The payload shown in dialogue, which may be static, dynamic, or a pause.
|
|
@@ -10,6 +10,7 @@ import { NVLToken } from "./nvl";
|
|
|
10
10
|
import type { TransformDefinitions } from "../elements/transform/type";
|
|
11
11
|
import type { ActionStatements } from "../elements/type";
|
|
12
12
|
import type { Persistent } from "../elements/persistent";
|
|
13
|
+
import { Transition } from "../elements/transition/transition";
|
|
13
14
|
export interface ISceneUserConfig {
|
|
14
15
|
/**
|
|
15
16
|
* Background music
|
|
@@ -33,7 +34,15 @@ export interface ISceneUserConfig {
|
|
|
33
34
|
layers: Layer[];
|
|
34
35
|
}
|
|
35
36
|
export type JumpConfig = {
|
|
36
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Played across the whole stage while the scenes swap: the outgoing scene drives the
|
|
39
|
+
* transition's outgoing half and the incoming scene its incoming half, so sprites, text and
|
|
40
|
+
* every other layer take part rather than only the background.
|
|
41
|
+
*
|
|
42
|
+
* The dialogue box is deliberately not part of it — it is rendered outside the stage and is
|
|
43
|
+
* expected to be gone by the time a scene ends.
|
|
44
|
+
*/
|
|
45
|
+
transition: Transition;
|
|
37
46
|
};
|
|
38
47
|
type ChainableAction = Proxied<LogicAction.GameElement, Chained<LogicAction.Actions>> | LogicAction.Actions;
|
|
39
48
|
type ChainedScene = Proxied<Scene, Chained<LogicAction.Actions>>;
|
|
@@ -65,6 +74,9 @@ export declare class Scene extends Constructable<LogicAction.Actions, Scene> {
|
|
|
65
74
|
* Jump to another scene and discard the current one.
|
|
66
75
|
*
|
|
67
76
|
* After the jump the calling scene is unloaded and any actions that follow are ignored.
|
|
77
|
+
*
|
|
78
|
+
* A `transition` plays across the whole stage rather than across the background alone; see
|
|
79
|
+
* {@link JumpConfig.transition}.
|
|
68
80
|
* @param scene - The destination scene instance.
|
|
69
81
|
* @param config - Optional transition config (or transition object).
|
|
70
82
|
* @chainable
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TransitionAnimationType, TransitionTask } from "../../../../elements/transition/type";
|
|
2
|
+
import { TransformDefinitions } from "../../../../elements/transform/type";
|
|
3
|
+
import { ImageTransition } from "../../../../elements/transition/transitions/image/imageTransition";
|
|
4
|
+
type AnimationType = [TransitionAnimationType.Number];
|
|
5
|
+
export type ExposureOptions = {
|
|
6
|
+
/** Duration in milliseconds. */
|
|
7
|
+
duration: number;
|
|
8
|
+
/**
|
|
9
|
+
* Peak exposure in stops, driving the frame to a gain of `2 ** ev`. Every channel
|
|
10
|
+
* clips at white on the way, so a channel already near white blows out almost at
|
|
11
|
+
* once while a dark one holds on nearly to the end — the difference between this
|
|
12
|
+
* and a white plate. @default 4.6
|
|
13
|
+
*/
|
|
14
|
+
ev?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Shadow lift (0–1) mixed in ahead of the gain, ramped in with it.
|
|
17
|
+
*
|
|
18
|
+
* Gain alone never whitens pure black, so without a lift a night frame ends the
|
|
19
|
+
* burn as a black silhouette on white. This is the flare a real lens adds; 0.03–0.06
|
|
20
|
+
* carries the shadows up without touching the frame at rest. @default 0.04
|
|
21
|
+
*/
|
|
22
|
+
lift?: number;
|
|
23
|
+
/** Fraction (0–1) of the duration spent fully blown out. @default 0 */
|
|
24
|
+
hold?: number;
|
|
25
|
+
easing?: TransformDefinitions.EasingDefinition;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* An exposure transition: the outgoing frame is driven up in stops until it burns out
|
|
29
|
+
* to white, the images swap inside that white window, and the incoming frame comes
|
|
30
|
+
* back down to a normal exposure.
|
|
31
|
+
*
|
|
32
|
+
* Unlike a white plate faded over the frame — which moves every colour toward white at
|
|
33
|
+
* one rate — the whitening here is per-channel clipping, so highlights go first,
|
|
34
|
+
* saturated colours pass through a hue shift as their leading channel clips, and the
|
|
35
|
+
* shadows are the last thing left. {@link ThroughColor} with a white colour is the
|
|
36
|
+
* plate version; this is the photographic one.
|
|
37
|
+
*
|
|
38
|
+
* Emitted as a plain CSS `filter` chain, so it drives an `<img>` and a detached scene
|
|
39
|
+
* root alike. `invert brightness invert` is a lift with no clipping of its own
|
|
40
|
+
* (`c → lift + (1 - lift)·c`); the trailing `brightness` is the gain that does clip.
|
|
41
|
+
*/
|
|
42
|
+
export declare class Exposure extends ImageTransition<AnimationType> {
|
|
43
|
+
private duration;
|
|
44
|
+
private ev;
|
|
45
|
+
private lift;
|
|
46
|
+
private hold;
|
|
47
|
+
private easing?;
|
|
48
|
+
constructor(options: ExposureOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Style for a frame at a given burn amount (0 = untouched, 1 = fully blown out).
|
|
51
|
+
*
|
|
52
|
+
* The lift is scaled by the burn rather than applied flat, so a resting frame is
|
|
53
|
+
* bit-for-bit the source image and writes no filter at all — a filter left on a
|
|
54
|
+
* settled scene root would give it a compositing layer for nothing.
|
|
55
|
+
*/
|
|
56
|
+
private burnStyle;
|
|
57
|
+
createTask(): TransitionTask<HTMLImageElement, AnimationType>;
|
|
58
|
+
copy(): Exposure;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
@@ -9,9 +9,23 @@ export declare abstract class ImageTransition<T extends TransitionAnimationType[
|
|
|
9
9
|
/**@package */
|
|
10
10
|
private _currentSrc;
|
|
11
11
|
/**@package */
|
|
12
|
+
private _detached;
|
|
13
|
+
/**@package */
|
|
12
14
|
private _prevLayers;
|
|
13
15
|
/**@package */
|
|
14
16
|
private _targetLayers;
|
|
17
|
+
/**
|
|
18
|
+
* Detach the transition from any image source.
|
|
19
|
+
*
|
|
20
|
+
* A detached transition drives an element that owns its own content — the stage transition
|
|
21
|
+
* driver points the two halves at whole scene subtrees — so there is no src for the
|
|
22
|
+
* resolvers to carry, and `asPrev`/`asTarget` must contribute style only. Without this the
|
|
23
|
+
* src injection below throws, because "no src" is otherwise a genuine authoring error.
|
|
24
|
+
* @package
|
|
25
|
+
*/
|
|
26
|
+
_setDetached(detached: boolean): this;
|
|
27
|
+
/**@package */
|
|
28
|
+
_isDetached(): boolean;
|
|
15
29
|
/**@package */
|
|
16
30
|
_setPrevLayers(layers: (string | null)[]): this;
|
|
17
31
|
/**@package */
|
|
@@ -312,10 +312,11 @@ export type GameConfig = {
|
|
|
312
312
|
*/
|
|
313
313
|
allowSkipBackgroundTransform: boolean;
|
|
314
314
|
/**
|
|
315
|
-
* If true, when you press [GameConfig.player.skipKey], the game will skip the
|
|
316
|
-
* @
|
|
315
|
+
* If true, when you press [GameConfig.player.skipKey], the game will skip the transition
|
|
316
|
+
* played between scenes by {@link Scene.jumpTo}
|
|
317
|
+
* @default true
|
|
317
318
|
*/
|
|
318
|
-
|
|
319
|
+
allowSkipSceneTransition: boolean;
|
|
319
320
|
/**
|
|
320
321
|
* If true, when you press [GameConfig.player.skipKey], the game will skip the text transform
|
|
321
322
|
* @default true
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A rectangle in the overlay's own coordinates — the dialog box drawn at its authored size, before
|
|
4
|
+
* the stage scales it to the window.
|
|
5
|
+
*/
|
|
6
|
+
export type DialogOverlayRect = {
|
|
7
|
+
left: number;
|
|
8
|
+
top: number;
|
|
9
|
+
right: number;
|
|
10
|
+
bottom: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
export type DialogOverlay = {
|
|
15
|
+
/**
|
|
16
|
+
* Renders its children above the dialog, still inside the scaled stage, so a popup keeps the
|
|
17
|
+
* dialog's own scale and is not clipped by the text box it belongs to.
|
|
18
|
+
*/
|
|
19
|
+
Portal: React.FC<{
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Where an element sits in the overlay's coordinates. Feed the result straight to `left`/`top`
|
|
24
|
+
* on a child of {@link DialogOverlay.Portal}.
|
|
25
|
+
*/
|
|
26
|
+
measure: (element: Element | null) => DialogOverlayRect | null;
|
|
27
|
+
/** The overlay element, or `null` outside a dialog. */
|
|
28
|
+
container: HTMLElement | null;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Somewhere to draw things that belong to a line but must not live inside it — the definition popup
|
|
32
|
+
* of an inline glossary word, a tooltip on a name.
|
|
33
|
+
*
|
|
34
|
+
* The overlay covers the dialog and sits above it, inside the same scaled stage, so a popup is
|
|
35
|
+
* drawn at the dialog's own scale and escapes the text box's clipping. It lets clicks through
|
|
36
|
+
* everywhere its children do not paint; give the popup itself `pointer-events: auto`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const overlay = useDialogOverlay();
|
|
41
|
+
* const rect = overlay.measure(anchorRef.current);
|
|
42
|
+
*
|
|
43
|
+
* return rect && (
|
|
44
|
+
* <overlay.Portal>
|
|
45
|
+
* <div style={{position: "absolute", left: rect.left, top: rect.top, pointerEvents: "auto"}}>
|
|
46
|
+
* …
|
|
47
|
+
* </div>
|
|
48
|
+
* </overlay.Portal>
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function useDialogOverlay(): DialogOverlay;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { WordRenderProps } from "../../../nlcore/elements/character/word";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Register a component that words may name by id.
|
|
5
|
+
*
|
|
6
|
+
* Registering the same id again replaces the component; lines already on screen pick the new one up
|
|
7
|
+
* on their next render.
|
|
8
|
+
*
|
|
9
|
+
* @param id - The id words refer to, e.g. `"glossary"`.
|
|
10
|
+
* @param component - The component to render those words with.
|
|
11
|
+
* @returns A function that unregisters it again.
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* registerWordRenderer("glossary", GlossaryTerm);
|
|
15
|
+
* // a word compiled from story data can now ask for it by name
|
|
16
|
+
* new Word("以太浓度", {render: "glossary", data: {entry: "aether"}});
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function registerWordRenderer<T = unknown>(id: string, component: React.ComponentType<WordRenderProps<T>>): () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Drop a registration made by {@link registerWordRenderer}.
|
|
22
|
+
* @param id - The id to forget.
|
|
23
|
+
*/
|
|
24
|
+
export declare function unregisterWordRenderer(id: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* The component registered under an id, or `null`.
|
|
27
|
+
* @param id - The id to look up.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getWordRenderer(id: string): React.ComponentType<WordRenderProps<any>> | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Scene } from "../../../nlcore/elements/scene";
|
|
2
|
+
import type { CSSProps } from "../../../nlcore/elements/transition/type";
|
|
3
|
+
/**
|
|
4
|
+
* Which scene each half of a stage transition drives.
|
|
5
|
+
*
|
|
6
|
+
* A transition task's resolvers are already split by {@link Transition.asPrev} / {@link
|
|
7
|
+
* Transition.asTarget}; this is what those two roles mean at the stage level.
|
|
8
|
+
*/
|
|
9
|
+
export type StageTransitionRoles = {
|
|
10
|
+
/** The scene being left. Driven by the `asPrev` resolvers. */
|
|
11
|
+
from: Scene;
|
|
12
|
+
/** The scene being entered. Driven by the `asTarget` resolvers. */
|
|
13
|
+
to: Scene;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* What a scene root paints when no transition owns it.
|
|
17
|
+
*
|
|
18
|
+
* This must name **every** property any transition writes to a scene half. A property left out
|
|
19
|
+
* is not neutral — it keeps whatever the last frame put there. That is survivable for the
|
|
20
|
+
* outgoing scene (it is unmounted moments later) but not for the incoming one, which lives on:
|
|
21
|
+
* a `Reveal` leaves a fully-opaque `mask-image` behind, and the scene would then carry that mask
|
|
22
|
+
* for the rest of its life. Cancellation is worse still — it stops mid-flight with no final
|
|
23
|
+
* frame, so a half-way value would stick forever on both halves.
|
|
24
|
+
*
|
|
25
|
+
* `stageSettledStyle.test.ts` pins this list against what the built-in transitions actually
|
|
26
|
+
* write, so a transition cannot start writing a property without this naming it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stageSettledStyle(): CSSProps;
|
|
29
|
+
/**
|
|
30
|
+
* What the scene a transition moved away from paints once the transition is over: nothing.
|
|
31
|
+
*
|
|
32
|
+
* The transition finishing is not what removes the outgoing scene — `scene:exit` is a separate
|
|
33
|
+
* action, so the scene stays mounted for a moment afterwards, and during that moment it must not be
|
|
34
|
+
* on screen. Leaving that to the stacking order does not work: settling gives the incoming scene
|
|
35
|
+
* `z-index: auto` while the outgoing one still carries an explicit `0`, and those are the same
|
|
36
|
+
* stacking level, so document order decides — and the outgoing scene is the later node. It comes to
|
|
37
|
+
* the front, at whatever opacity its half of the transition left it at.
|
|
38
|
+
*
|
|
39
|
+
* A `Dissolve` hides that, because its outgoing half ends at zero opacity. A `Reveal` does not: its
|
|
40
|
+
* outgoing half is `{}`, never touched at all, because the effect is entirely the incoming scene
|
|
41
|
+
* being uncovered on top of it. So the scene the player just left reappears, at full opacity, in
|
|
42
|
+
* front — for as many frames as `scene:exit` takes to arrive.
|
|
43
|
+
*
|
|
44
|
+
* Hiding it is also what the per-element path has always done: `useDisplayable` discards the
|
|
45
|
+
* element that played the outgoing half the moment the transition completes.
|
|
46
|
+
*/
|
|
47
|
+
export declare function stageRetiredStyle(): CSSProps;
|
|
@@ -193,6 +193,7 @@ export declare class GameState {
|
|
|
193
193
|
readonly gameHistory: GameHistoryManager;
|
|
194
194
|
pageRouter: null;
|
|
195
195
|
private stageClickBuffer;
|
|
196
|
+
private readonly advanceSuspensions;
|
|
196
197
|
private readonly nvlAdvanceWaiters;
|
|
197
198
|
private advDialogState;
|
|
198
199
|
private _fastForwarding;
|
|
@@ -318,6 +319,24 @@ export declare class GameState {
|
|
|
318
319
|
settleAdvDialog(dialogId: string): this;
|
|
319
320
|
getAdvDialogState(): AdvDialogState | null;
|
|
320
321
|
recordStageClick(): this;
|
|
322
|
+
/**
|
|
323
|
+
* Hold the line where it is: while at least one suspension is out, clicking the stage and
|
|
324
|
+
* pressing the advance or skip key do nothing.
|
|
325
|
+
*
|
|
326
|
+
* Anything that opens on top of a line and wants the player's next keystroke needs this — a
|
|
327
|
+
* definition popup on an inline word, a term the player is reading. Without it a popup opens and
|
|
328
|
+
* the very next space bar advances the line behind it, which is the one thing the popup exists
|
|
329
|
+
* to prevent.
|
|
330
|
+
*
|
|
331
|
+
* Suspensions nest: the line resumes once every one of them has been released.
|
|
332
|
+
*
|
|
333
|
+
* @returns A function that releases this suspension. Safe to call more than once.
|
|
334
|
+
*/
|
|
335
|
+
suspendAdvance(): () => void;
|
|
336
|
+
/**
|
|
337
|
+
* Whether anything is currently holding the line — see {@link GameState.suspendAdvance}.
|
|
338
|
+
*/
|
|
339
|
+
isAdvanceSuspended(): boolean;
|
|
321
340
|
consumeStageClick(maxAgeMs?: number): boolean;
|
|
322
341
|
createDisplayable(displayable: LogicAction.DisplayableElements, scene?: Scene | null, layer?: Layer | null): this;
|
|
323
342
|
disposeDisplayable(displayable: LogicAction.DisplayableElements, scene?: Scene | null, layer?: Layer | null): this;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hold the line while something of yours is open.
|
|
3
|
+
*
|
|
4
|
+
* A popup drawn over a line — a definition of an inline word, a term the player is reading — has to
|
|
5
|
+
* stop the line advancing underneath it, or the space bar that should dismiss the popup skips to
|
|
6
|
+
* the next line instead. Pass `true` while it is open and the stage click, the advance key and the
|
|
7
|
+
* skip key all stop reaching the dialog; the hold is released on `false`, and on unmount, so a
|
|
8
|
+
* popup that disappears can never leave the game stuck.
|
|
9
|
+
*
|
|
10
|
+
* Several holds may be out at once; the line resumes when the last one is released.
|
|
11
|
+
*
|
|
12
|
+
* @param active - Whether to hold the line right now.
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* function GlossaryTerm({children, revealed, data}: WordRenderProps<{entry: string}>) {
|
|
16
|
+
* const [open, setOpen] = useState(false);
|
|
17
|
+
* useSuspendAdvance(open);
|
|
18
|
+
* return <span onClick={() => revealed && setOpen(v => !v)}>{children}</span>;
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function useSuspendAdvance(active: boolean): void;
|
|
@@ -21,6 +21,9 @@ import { NvlContainer } from "./elements/nvl/NvlContainer";
|
|
|
21
21
|
import { DefaultNvlContainer } from "./elements/nvl/DefaultNvlContainer";
|
|
22
22
|
import { NvlDialogList, DefaultNvlDialogItem } from "./elements/nvl/NvlDialogList";
|
|
23
23
|
import { NvlProvider, useNvl, useNvlDialogs, useIsNvlMode, useIsNvlVisible } from "./elements/nvl/NvlContext";
|
|
24
|
+
import { getWordRenderer, registerWordRenderer, unregisterWordRenderer } from "./elements/say/wordRenderer";
|
|
25
|
+
import { useDialogOverlay } from "./elements/say/dialogOverlay";
|
|
26
|
+
import { useSuspendAdvance } from "./lib/useSuspendAdvance";
|
|
24
27
|
export type { DialogAvatarContext } from "./elements/say/Avatar";
|
|
25
28
|
export type { BaseTextsProps, EntryTextsProps, RawTextsProps, TextAppearanceProps, TextsPreviewInput, TextsPreviewLoop, TextsPreviewProps, TextsProps, } from "./elements/say/Sentence";
|
|
26
29
|
/**
|
|
@@ -31,7 +34,8 @@ export type { BaseTextsProps, EntryTextsProps, RawTextsProps, TextAppearanceProp
|
|
|
31
34
|
* had no way to type either without restating the unions.
|
|
32
35
|
*/
|
|
33
36
|
export type { TateChuYoko, TextGlyphOrientation, TextWritingMode, } from "./lib/verticalText";
|
|
37
|
+
export type { DialogOverlay, DialogOverlayRect } from "./elements/say/dialogOverlay";
|
|
34
38
|
export type { NametagProps } from "./elements/say/Nametag";
|
|
35
39
|
export type { ItemProps } from "./elements/menu/UIMenu/Item";
|
|
36
40
|
export type { ChoiceEvaluated } from "./elements/menu/type";
|
|
37
|
-
export { Isolated, usePreference, Stage, GameMenu, Item, useUIMenuContext, Notifications, Texts, TextsPreview, Nametag, Dialog, Avatar, useAvatar, useDialog, useVoiceState, Page, Layout, LayoutRouterProvider, PageInjectContext, RootPath, FixedAspectRatioContainer, useKeyBinding, useLiveGame, NvlContainer, DefaultNvlContainer, NvlDialogList, DefaultNvlDialogItem, NvlProvider, useNvl, useNvlDialogs, useIsNvlMode, useIsNvlVisible, };
|
|
41
|
+
export { Isolated, usePreference, Stage, GameMenu, Item, useUIMenuContext, Notifications, Texts, TextsPreview, Nametag, Dialog, Avatar, useAvatar, useDialog, useDialogOverlay, useSuspendAdvance, registerWordRenderer, unregisterWordRenderer, getWordRenderer, useVoiceState, Page, Layout, LayoutRouterProvider, PageInjectContext, RootPath, FixedAspectRatioContainer, useKeyBinding, useLiveGame, NvlContainer, DefaultNvlContainer, NvlDialogList, DefaultNvlDialogItem, NvlProvider, useNvl, useNvlDialogs, useIsNvlMode, useIsNvlVisible, };
|