narraleaf-react 0.9.1 → 0.10.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.
@@ -3,9 +3,13 @@ import { DeepPartial, Hooks, StringKeyOf } from "../../util/data";
3
3
  import { LogicAction } from "./action/logicAction";
4
4
  import { LiveGame } from "./game/liveGame";
5
5
  import { Preference } from "./game/preference";
6
+ import { GameState } from "../player/gameState";
6
7
  import { Plugins, IGamePluginRegistry } from "./game/plugin/plugin";
7
8
  import { LayoutRouter } from "../player/lib/PageRouter/router";
8
9
  import { KeyMap } from "./game/keyMap";
10
+ import type { Storable } from "./elements/persistent/storable";
11
+ import type { Scene } from "./elements/scene";
12
+ import type { LiveGameEventHandler, LiveGameEventToken } from "./types";
9
13
  declare enum GameSettingsNamespace {
10
14
  game = "game"
11
15
  }
@@ -34,6 +38,17 @@ export type GameHooks = {
34
38
  */
35
39
  "afterRestore": [];
36
40
  };
41
+ export type GameLifecycleEventContext = {
42
+ game: Game;
43
+ gameState: GameState;
44
+ liveGame: LiveGame;
45
+ storable: Storable;
46
+ scene: Scene | null;
47
+ };
48
+ export type GameLifecycleEvents = {
49
+ "event:game.preloadComplete": [ctx: GameLifecycleEventContext];
50
+ "event:game.firstSceneReady": [ctx: GameLifecycleEventContext];
51
+ };
37
52
  export declare class Game {
38
53
  static GameSettingsNamespace: typeof GameSettingsNamespace;
39
54
  readonly hooks: Hooks<GameHooks>;
@@ -45,11 +60,17 @@ export declare class Game {
45
60
  * Game key bindings
46
61
  */
47
62
  keyMap: KeyMap;
63
+ static LifecycleEventTypes: {
64
+ [K in keyof GameLifecycleEvents]: K;
65
+ };
48
66
  /**
49
67
  * Plugin registry
50
68
  */
51
69
  plugins: Plugins;
52
70
  router: LayoutRouter;
71
+ private readonly lifecycleEvents;
72
+ private preloadCompleteContext;
73
+ private firstSceneReadyContext;
53
74
  /**
54
75
  * Create a new game
55
76
  * @param config - Game configuration
@@ -78,6 +99,45 @@ export declare class Game {
78
99
  * @param plugin - The plugin to use
79
100
  */
80
101
  use(plugin: IGamePluginRegistry): this;
102
+ /**
103
+ * Listen for the initial preload pass completing.
104
+ *
105
+ * This is the point where the initial preload pass has actually finished.
106
+ * Use {@link whenPreloadComplete} if the listener may be registered after the event has fired.
107
+ */
108
+ onPreloadComplete(fc: LiveGameEventHandler<GameLifecycleEvents["event:game.preloadComplete"]>): LiveGameEventToken;
109
+ /**
110
+ * Listen once for the initial preload pass completing.
111
+ */
112
+ oncePreloadComplete(fc: LiveGameEventHandler<GameLifecycleEvents["event:game.preloadComplete"]>): LiveGameEventToken;
113
+ /**
114
+ * Resolve when the initial preload pass has completed.
115
+ */
116
+ whenPreloadComplete(): Promise<GameLifecycleEventContext>;
117
+ /**
118
+ * Whether the initial preload pass has completed.
119
+ */
120
+ isPreloadComplete(): boolean;
121
+ /**
122
+ * Listen for the first scene being fully ready.
123
+ *
124
+ * This fires after the initial preload pass has actually finished, after the first
125
+ * scene component is mounted, and after the browser has had a frame to render it.
126
+ * Use {@link whenFirstSceneReady} if the listener may be registered after the event has fired.
127
+ */
128
+ onFirstSceneReady(fc: LiveGameEventHandler<GameLifecycleEvents["event:game.firstSceneReady"]>): LiveGameEventToken;
129
+ /**
130
+ * Listen once for the first scene being fully ready.
131
+ */
132
+ onceFirstSceneReady(fc: LiveGameEventHandler<GameLifecycleEvents["event:game.firstSceneReady"]>): LiveGameEventToken;
133
+ /**
134
+ * Resolve when the first scene is fully ready.
135
+ */
136
+ whenFirstSceneReady(): Promise<GameLifecycleEventContext>;
137
+ /**
138
+ * Whether the first scene is fully ready.
139
+ */
140
+ isFirstSceneReady(): boolean;
81
141
  getLiveGame(): LiveGame;
82
142
  /**
83
143
  * Dispose the game and all its resources
@@ -8,7 +8,7 @@ import { GuardConfig } from "../player/guard";
8
8
  import React from "react";
9
9
  import { StackModel, StackModelRawData } from "./action/stackModel";
10
10
  import { MenuComponent, NotificationComponent, NvlDialogComponent, SayComponent } from "./common/player";
11
- import { Color, LiveGameEventToken } from "./types";
11
+ import { LiveGameEventToken } from "./types";
12
12
  export interface SavedGameMetaData {
13
13
  /**
14
14
  * The timestamp of when the game was created
@@ -251,16 +251,6 @@ export type GameConfig = {
251
251
  * @default false
252
252
  */
253
253
  allowSkipVideo: boolean;
254
- /**
255
- * The default text color for the dialog
256
- * @default "#000"
257
- */
258
- defaultTextColor: Color;
259
- /**
260
- * The default text color for the character nametag
261
- * @default "#000"
262
- */
263
- defaultNametagColor: Color;
264
254
  /**
265
255
  * The component to use for the notification
266
256
  * @default DefaultNotification
@@ -310,36 +300,11 @@ export type GameConfig = {
310
300
  */
311
301
  guard: GuardConfig;
312
302
  };
313
- /**
314
- * Default font size for the game
315
- * @default "16px"
316
- */
317
- fontSize: React.CSSProperties["fontSize"];
318
- /**
319
- * Default font weight for the game
320
- * @default 400
321
- */
322
- fontWeight: number;
323
- /**
324
- * Default font weight for the game
325
- * @default 700
326
- */
327
- fontWeightBold: number;
328
- /**
329
- * Default font family for the game
330
- * @default "sans-serif"
331
- */
332
- fontFamily: React.CSSProperties["fontFamily"];
333
303
  /**
334
304
  * Override the default stage
335
305
  * @default null
336
306
  */
337
307
  stage: React.ReactNode | null;
338
- /**
339
- * The default color for the menu choices
340
- * @default "#000"
341
- */
342
- defaultMenuChoiceColor: Color;
343
308
  /**
344
309
  * The maximum number of times a stack model can loop
345
310
  * @default 1000
@@ -1,4 +1,4 @@
1
1
  import "client-only";
2
2
  import { PlayerProps } from "../elements/type";
3
3
  import React from "react";
4
- export default function Player({ story, width, height, className, onReady, onPreloadedReady, onEnd, onError, children, active, }: Readonly<PlayerProps>): React.JSX.Element;
4
+ export default function Player({ story, width, height, className, onReady, onPreloadComplete, onPreloadedReady, onFirstSceneReady, onEnd, onError, children, active, }: Readonly<PlayerProps>): React.JSX.Element;
@@ -1,5 +1,6 @@
1
+ import { type TextAppearanceProps } from "../../../elements/say/Sentence";
1
2
  import React from "react";
2
- export interface ItemProps {
3
+ export interface ItemProps extends TextAppearanceProps {
3
4
  className?: string;
4
5
  style?: React.CSSProperties;
5
6
  /**
@@ -9,4 +10,4 @@ export interface ItemProps {
9
10
  */
10
11
  bindKey?: string;
11
12
  }
12
- export default function Item({ className, style, bindKey }: ItemProps): React.JSX.Element;
13
+ export default function Item({ className, style, bindKey, defaultColor, fontSize, fontWeight, fontWeightBold, fontFamily, }: ItemProps): React.JSX.Element;
@@ -1,9 +1,12 @@
1
1
  import React from "react";
2
2
  import type { Character } from "../../../nlcore/elements/character";
3
3
  import type { NvlDialogEntry } from "../../gameState";
4
- type NametagProps = React.HTMLAttributes<HTMLDivElement> & {
4
+ import type { Color } from "../../../../game/nlcore/types";
5
+ export type NametagProps = Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "color"> & {
5
6
  entry?: NvlDialogEntry;
6
7
  character?: Character | null;
8
+ name?: React.ReactNode;
9
+ color?: Color;
10
+ children?: React.ReactNode;
7
11
  };
8
- export default function Nametag({ entry, character, ...props }: Readonly<NametagProps>): React.JSX.Element;
9
- export {};
12
+ export default function Nametag({ entry, character, name, color, children, style, ...props }: Readonly<NametagProps>): React.JSX.Element;
@@ -4,14 +4,19 @@ import { Word } from "../../../nlcore/elements/character/word";
4
4
  import { GameState } from "../../../../game/nlcore/common/game";
5
5
  import { Color } from "../../../../game/nlcore/types";
6
6
  import React from "react";
7
- import { DialogElementProps } from "./type";
8
7
  import { DialogState } from "./UIDialog";
9
8
  import type { NvlDialogEntry } from "../../gameState";
10
- type BaseTextsProps = {
9
+ export type TextAppearanceProps = {
11
10
  /**
12
11
  * The default color of the text
13
12
  */
14
13
  defaultColor?: Color;
14
+ fontSize?: React.CSSProperties["fontSize"];
15
+ fontWeight?: React.CSSProperties["fontWeight"];
16
+ fontWeightBold?: React.CSSProperties["fontWeight"];
17
+ fontFamily?: React.CSSProperties["fontFamily"];
18
+ };
19
+ export type BaseTextsProps = TextAppearanceProps & {
15
20
  className?: string;
16
21
  style?: React.CSSProperties;
17
22
  dialog?: DialogState;
@@ -84,19 +89,6 @@ export interface TextsPreviewProps extends Omit<React.HTMLAttributes<HTMLDivElem
84
89
  fontFamily?: React.CSSProperties["fontFamily"];
85
90
  onCompleted?: () => void;
86
91
  }
87
- /**
88
- * Props-based wrapper component
89
- * Provides a clean interface for direct prop usage
90
- */
91
- export interface TextsProps extends DialogElementProps {
92
- sentence: Sentence;
93
- gameState: GameState;
94
- useTypeEffect?: boolean;
95
- onCompleted?: () => void;
96
- finished?: boolean;
97
- count?: number;
98
- words?: Word<Pausing | string>[];
99
- }
100
92
  export type EntryTextsProps = BaseTextsProps & {
101
93
  entry: NvlDialogEntry;
102
94
  gameState: GameState;
@@ -105,10 +97,12 @@ export type EntryTextsProps = BaseTextsProps & {
105
97
  isActive: boolean;
106
98
  };
107
99
  export declare function TextsPreview({ text, sentence, words, useTypeEffect, loop, restartDelay, cps, gameSpeed, pauseDuration, defaultColor, className, style, fontSize, fontWeight, fontWeightBold, fontFamily, onCompleted, ...props }: TextsPreviewProps): React.JSX.Element;
100
+ export type RawTextsProps = BaseTextsProps;
108
101
  export declare function RawTexts(props: BaseTextsProps): React.JSX.Element;
109
102
  /**
110
103
  * Context-based wrapper component
111
104
  * Provides integration with the sentence context
112
105
  */
106
+ export type TextsProps = BaseTextsProps | EntryTextsProps;
113
107
  export declare function Texts(props: BaseTextsProps | EntryTextsProps): React.JSX.Element;
114
108
  export default Texts;
@@ -3,7 +3,7 @@ import { IDialogProps, SayElementProps } from "../elements/say/type";
3
3
  import { IUserMenuProps, MenuElementProps } from "../elements/menu/type";
4
4
  import { Story } from "../../nlcore/elements/story";
5
5
  import clsx from "clsx";
6
- import { Game } from "../../nlcore/game";
6
+ import { Game, type GameLifecycleEventContext } from "../../nlcore/game";
7
7
  import { GameState } from "../gameState";
8
8
  import { Storable } from "../../nlcore/elements/persistent/storable";
9
9
  import { LiveGame } from "../../nlcore/game/liveGame";
@@ -28,23 +28,42 @@ export type PlayerEventContext = {
28
28
  liveGame: LiveGame;
29
29
  storable: Storable;
30
30
  };
31
+ export type PlayerLifecycleEventContext = GameLifecycleEventContext;
31
32
  export interface PlayerProps {
32
33
  story?: Story;
33
34
  width?: string | number;
34
35
  height?: string | number;
35
36
  className?: clsx.ClassValue;
36
37
  /**
37
- * Once the game is ready to be played
38
+ * Once the Player is initialized.
39
+ *
40
+ * This is not a preload or first-render guarantee. Use
41
+ * {@link PlayerProps.onPreloadComplete} or {@link PlayerProps.onFirstSceneReady}
42
+ * when you need those exact lifecycle points.
38
43
  *
39
44
  * only called each lifecycle once
40
45
  */
41
46
  onReady?: (ctx: PlayerEventContext) => void;
47
+ /**
48
+ * Once the initial preload pass has actually completed.
49
+ *
50
+ * This fires before the first scene is guaranteed to be rendered.
51
+ */
52
+ onPreloadComplete?: (ctx: PlayerLifecycleEventContext) => void;
42
53
  /**
43
54
  * Once the internal preload pass is ready and the Player has committed that state.
44
55
  *
45
56
  * only called each lifecycle once
57
+ *
58
+ * @deprecated Use {@link PlayerProps.onPreloadComplete}.
46
59
  */
47
60
  onPreloadedReady?: (ctx: PlayerEventContext) => void;
61
+ /**
62
+ * Once the first scene has mounted and the browser has had a frame to render it.
63
+ *
64
+ * This is the most direct signal that the game is visually ready for the player.
65
+ */
66
+ onFirstSceneReady?: (ctx: PlayerLifecycleEventContext) => void;
48
67
  /**
49
68
  * Once the game is ended
50
69
  *
@@ -120,6 +120,8 @@ type GameStateEvents = {
120
120
  "event:state.player.lineEnd": [];
121
121
  "event:state.player.requestFlush": [];
122
122
  "event:state.player.stageClick": [];
123
+ "event:state.scene.mount": [scene: Scene];
124
+ "event:state.scene.unmount": [scene: Scene];
123
125
  "event.state.onExpose": [unknown, ExposedState[ExposedStateType]];
124
126
  "event:state.onRender": [];
125
127
  "event:state:flushPreloadedScenes": [];
@@ -27,6 +27,7 @@ export type PreloadedEventTypes = {
27
27
  "event:preloaded.change": [];
28
28
  "event:preloaded.mount": [];
29
29
  "event:preloaded.ready": [];
30
+ "event:preloaded.complete": [];
30
31
  "event:preloaded.unmount": [];
31
32
  };
32
33
  export declare class Preloaded {
@@ -21,5 +21,7 @@ import { DefaultNvlContainer } from "./elements/nvl/DefaultNvlContainer";
21
21
  import { NvlDialogList, DefaultNvlDialogItem } from "./elements/nvl/NvlDialogList";
22
22
  import { NvlProvider, useNvl, useNvlDialogs, useIsNvlMode, useIsNvlVisible } from "./elements/nvl/NvlContext";
23
23
  export type { DialogAvatarContext } from "./elements/say/Avatar";
24
- export type { TextsPreviewInput, TextsPreviewLoop, TextsPreviewProps } from "./elements/say/Sentence";
24
+ export type { BaseTextsProps, EntryTextsProps, RawTextsProps, TextAppearanceProps, TextsPreviewInput, TextsPreviewLoop, TextsPreviewProps, TextsProps, } from "./elements/say/Sentence";
25
+ export type { NametagProps } from "./elements/say/Nametag";
26
+ export type { ItemProps } from "./elements/menu/UIMenu/Item";
25
27
  export { Isolated, usePreference, Stage, GameMenu, Item, 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, };