@luna-editor/engine 0.2.0 → 0.3.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.
Files changed (64) hide show
  1. package/dist/Player.d.ts +1 -1
  2. package/dist/Player.js +504 -77
  3. package/dist/api/conversationBranch.d.ts +4 -0
  4. package/dist/api/conversationBranch.js +83 -0
  5. package/dist/components/BackgroundLayer.d.ts +19 -0
  6. package/dist/components/BackgroundLayer.js +218 -0
  7. package/dist/components/ClickWaitIndicator.d.ts +10 -0
  8. package/dist/components/ClickWaitIndicator.js +31 -0
  9. package/dist/components/ConversationBranchBox.d.ts +2 -0
  10. package/dist/components/ConversationBranchBox.js +29 -0
  11. package/dist/components/DialogueBox.js +16 -1
  12. package/dist/components/FontSettingsPanel.d.ts +10 -0
  13. package/dist/components/FontSettingsPanel.js +30 -0
  14. package/dist/components/FullscreenTextBox.d.ts +6 -0
  15. package/dist/components/FullscreenTextBox.js +70 -0
  16. package/dist/components/GameScreen.d.ts +1 -0
  17. package/dist/components/GameScreen.js +363 -81
  18. package/dist/components/PluginComponentProvider.d.ts +2 -2
  19. package/dist/components/PluginComponentProvider.js +3 -3
  20. package/dist/components/TimeWaitIndicator.d.ts +15 -0
  21. package/dist/components/TimeWaitIndicator.js +17 -0
  22. package/dist/contexts/AudioContext.d.ts +1 -0
  23. package/dist/contexts/AudioContext.js +1 -0
  24. package/dist/contexts/DataContext.js +69 -11
  25. package/dist/hooks/useBacklog.js +3 -0
  26. package/dist/hooks/useConversationBranch.d.ts +16 -0
  27. package/dist/hooks/useConversationBranch.js +125 -0
  28. package/dist/hooks/useFontLoader.d.ts +23 -0
  29. package/dist/hooks/useFontLoader.js +153 -0
  30. package/dist/hooks/useFullscreenText.d.ts +17 -0
  31. package/dist/hooks/useFullscreenText.js +120 -0
  32. package/dist/hooks/usePlayerLogic.d.ts +10 -3
  33. package/dist/hooks/usePlayerLogic.js +115 -18
  34. package/dist/hooks/usePluginEvents.d.ts +4 -1
  35. package/dist/hooks/usePluginEvents.js +16 -11
  36. package/dist/hooks/usePreloadImages.js +27 -7
  37. package/dist/hooks/useSoundPlayer.d.ts +15 -0
  38. package/dist/hooks/useSoundPlayer.js +209 -0
  39. package/dist/hooks/useTypewriter.d.ts +6 -2
  40. package/dist/hooks/useTypewriter.js +42 -6
  41. package/dist/hooks/useVoice.js +4 -1
  42. package/dist/index.d.ts +4 -3
  43. package/dist/index.js +2 -1
  44. package/dist/plugin/PluginManager.d.ts +66 -2
  45. package/dist/plugin/PluginManager.js +349 -79
  46. package/dist/sdk.d.ts +178 -21
  47. package/dist/sdk.js +27 -2
  48. package/dist/types.d.ts +288 -4
  49. package/dist/utils/branchBlockConverter.d.ts +2 -0
  50. package/dist/utils/branchBlockConverter.js +21 -0
  51. package/dist/utils/branchNavigator.d.ts +14 -0
  52. package/dist/utils/branchNavigator.js +55 -0
  53. package/dist/utils/facePositionCalculator.js +0 -1
  54. package/dist/utils/variableManager.d.ts +18 -0
  55. package/dist/utils/variableManager.js +159 -0
  56. package/package.json +1 -1
  57. package/dist/components/ConversationLogUI.d.ts +0 -2
  58. package/dist/components/ConversationLogUI.js +0 -115
  59. package/dist/hooks/useConversationLog.d.ts +0 -14
  60. package/dist/hooks/useConversationLog.js +0 -82
  61. package/dist/hooks/useUIVisibility.d.ts +0 -9
  62. package/dist/hooks/useUIVisibility.js +0 -19
  63. package/dist/plugin/luna-react.d.ts +0 -41
  64. package/dist/plugin/luna-react.js +0 -99
package/dist/Player.js CHANGED
@@ -10,42 +10,75 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
12
12
  import { clsx } from "clsx";
13
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
+ import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
14
14
  import { useScreenSizeAtom } from "./atoms/screen-size";
15
+ import { BackgroundLayer } from "./components/BackgroundLayer";
16
+ import { ClickWaitIndicator } from "./components/ClickWaitIndicator";
17
+ import { ConversationBranchBox } from "./components/ConversationBranchBox";
15
18
  import { DialogueBox } from "./components/DialogueBox";
16
19
  import { EndScreen } from "./components/EndScreen";
20
+ import { FullscreenTextBox } from "./components/FullscreenTextBox";
17
21
  import { GameScreen } from "./components/GameScreen";
18
22
  import { OverlayUI } from "./components/OverlayUI";
19
23
  import { PluginComponentProvider } from "./components/PluginComponentProvider";
24
+ import { TimeWaitIndicator } from "./components/TimeWaitIndicator";
20
25
  import { AudioProvider } from "./contexts/AudioContext";
21
26
  import { DataProvider } from "./contexts/DataContext";
22
27
  import { useBacklog } from "./hooks/useBacklog";
28
+ import { useConversationBranch } from "./hooks/useConversationBranch";
29
+ import { useFontLoader } from "./hooks/useFontLoader";
23
30
  import { usePlayerLogic } from "./hooks/usePlayerLogic";
24
31
  import { setGlobalUIAPI } from "./hooks/usePluginAPI";
25
32
  import { usePluginEvents } from "./hooks/usePluginEvents";
26
33
  import { usePreloadImages } from "./hooks/usePreloadImages";
34
+ import { useSoundPlayer } from "./hooks/useSoundPlayer";
27
35
  import { useTypewriter } from "./hooks/useTypewriter";
28
36
  import { PluginManager } from "./plugin/PluginManager";
29
37
  import { ComponentType } from "./sdk";
30
- export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, onSettingsChange, className, autoplay = false, }) => {
31
- var _a;
38
+ import { convertBranchBlockToScenarioBlock } from "./utils/branchBlockConverter";
39
+ import { BranchNavigator } from "./utils/branchNavigator";
40
+ import { VariableManager } from "./utils/variableManager";
41
+ export const Player = ({ scenario: scenarioProp, settings, plugins = [], sounds = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, onSettingsChange, className, autoplay = false, preventDefaultScroll = true, screenSize: screenSizeProp, disableKeyboardNavigation = false, }) => {
42
+ var _a, _b, _c, _d, _e;
43
+ // scenario.blocks が存在しない場合は空の配列を使用
44
+ const scenario = useMemo(() => {
45
+ var _a;
46
+ return (Object.assign(Object.assign({}, scenarioProp), { blocks: (_a = scenarioProp.blocks) !== null && _a !== void 0 ? _a : [] }));
47
+ }, [scenarioProp]);
32
48
  // デフォルト値とマージ
33
- const mergedSettings = Object.assign({ textSpeed: 80, autoPlaySpeed: 3, bgmVolume: 1.0, seVolume: 1.0, voiceVolume: 1.0, effectVolume: 1.0, textSoundVolume: 1.0 }, settings);
49
+ const mergedSettings = Object.assign({ textSpeed: 80, autoPlaySpeed: 3, bgmVolume: 0.5, seVolume: 1.0, voiceVolume: 1.0, effectVolume: 1.0, textSoundVolume: 1.0, defaultBackgroundFadeDuration: 0 }, settings);
34
50
  // プラグインからの設定更新ハンドラ
35
51
  const handleSettingsUpdate = useCallback((updatedSettings) => {
36
52
  var _a, _b;
37
53
  const newSettings = Object.assign(Object.assign({ aspectRatio: (_a = settings === null || settings === void 0 ? void 0 : settings.aspectRatio) !== null && _a !== void 0 ? _a : "16:9", bgObjectFit: (_b = settings === null || settings === void 0 ? void 0 : settings.bgObjectFit) !== null && _b !== void 0 ? _b : "cover" }, settings), updatedSettings);
38
54
  onSettingsChange === null || onSettingsChange === void 0 ? void 0 : onSettingsChange(newSettings);
39
55
  }, [settings, onSettingsChange]);
40
- const pluginManagerRef = useRef(new PluginManager());
56
+ // 遅延初期化でPluginManagerを作成(毎レンダリングでnew PluginManager()が評価されるのを防ぐ)
57
+ const pluginManagerRef = useRef(undefined);
58
+ if (!pluginManagerRef.current) {
59
+ pluginManagerRef.current = new PluginManager();
60
+ }
61
+ // 以降の参照用(undefinedにならないことを保証)
62
+ const pluginManager = pluginManagerRef.current;
63
+ const handleChoiceSelectionRef = useRef(null);
41
64
  // グローバルUIAPIを初期化(プラグインコンポーネントから使用可能にする)
42
65
  useEffect(() => {
43
- const uiAPI = pluginManagerRef.current.getUIAPI();
44
- setGlobalUIAPI(uiAPI, pluginManagerRef.current);
45
- }, []);
66
+ const uiAPI = pluginManager.getUIAPI();
67
+ setGlobalUIAPI(uiAPI, pluginManager);
68
+ }, [pluginManager]);
69
+ // プラグインのミュート状態を設定
70
+ useEffect(() => {
71
+ var _a;
72
+ pluginManager.setMuteAudio((_a = mergedSettings.muteAudio) !== null && _a !== void 0 ? _a : false);
73
+ }, [pluginManager, mergedSettings.muteAudio]);
46
74
  // 画面サイズの初期化
47
75
  const [, setScreenSize] = useScreenSizeAtom();
48
76
  useEffect(() => {
77
+ // screenSizeが明示的に指定されている場合はそれを使用(プレビュー用)
78
+ if (screenSizeProp) {
79
+ setScreenSize(screenSizeProp);
80
+ return;
81
+ }
49
82
  // クライアントサイドでのみ実行
50
83
  if (typeof window === "undefined")
51
84
  return;
@@ -59,47 +92,199 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
59
92
  };
60
93
  window.addEventListener("resize", handleResize);
61
94
  return () => window.removeEventListener("resize", handleResize);
62
- }, [setScreenSize]);
95
+ }, [setScreenSize, screenSizeProp]);
63
96
  // 表示可能なブロックのインデックスを事前計算
64
97
  const displayableBlockIndices = useMemo(() => {
65
- const supportedBlockTypes = ["dialogue", "narration"];
66
- return scenario.blocks
98
+ const supportedBlockTypes = [
99
+ "dialogue",
100
+ "narration",
101
+ "conversation_branch",
102
+ "fullscreen_text",
103
+ "click_wait",
104
+ "time_wait",
105
+ ];
106
+ const indices = scenario.blocks
67
107
  .map((block, index) => ({ block, index }))
68
108
  .filter(({ block }) => supportedBlockTypes.includes(block.blockType))
69
109
  .map(({ index }) => index);
110
+ return indices;
70
111
  }, [scenario.blocks]);
112
+ // 変数マネージャーの初期化
113
+ const variableManagerRef = useRef(scenario.variables && scenario.variables.length > 0
114
+ ? new VariableManager(scenario.variables)
115
+ : null);
116
+ // メディアタイプを判定するヘルパー関数
117
+ const getMediaType = useCallback((url) => {
118
+ var _a, _b;
119
+ const extension = (_b = (_a = url.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "";
120
+ if (["mp4", "webm", "ogg", "mov", "avi", "mkv"].includes(extension)) {
121
+ return "video";
122
+ }
123
+ if (extension === "gif") {
124
+ return "gif";
125
+ }
126
+ return "image";
127
+ }, []);
128
+ // 現在の背景状態を計算(character_entranceパターンに倣う)
129
+ // background_groupの場合は複数の背景を配列で返す
130
+ const calculateCurrentBackground = useCallback((upToBlockIndex) => {
131
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
132
+ let currentBackgroundBlock = null;
133
+ let previousBackgroundBlock = null;
134
+ let backgroundGroupBlock = null;
135
+ // 現在のブロックまでを逆順で走査して、最後のbackground_changeまたはbackground_groupを見つける
136
+ // background_changeが先に見つかった場合、background_groupは無視する(背景がリセットされる)
137
+ for (let i = upToBlockIndex; i >= 0; i--) {
138
+ const block = scenario.blocks[i];
139
+ // background_groupの場合
140
+ if (block.blockType === "background_group" &&
141
+ block.backgroundGroupItems &&
142
+ block.backgroundGroupItems.length > 0) {
143
+ // すでにbackground_changeが見つかっている場合は無視
144
+ // (background_changeがbackground_groupより後にあるので、背景はリセットされている)
145
+ if (currentBackgroundBlock) {
146
+ break;
147
+ }
148
+ backgroundGroupBlock = block;
149
+ break;
150
+ }
151
+ // imageUrlが存在し、かつ空文字列でないことを確認
152
+ const imageUrl = (_a = block.backgroundState) === null || _a === void 0 ? void 0 : _a.imageUrl;
153
+ if (block.blockType === "background_change" &&
154
+ imageUrl &&
155
+ imageUrl.trim() !== "") {
156
+ if (!currentBackgroundBlock) {
157
+ currentBackgroundBlock = block;
158
+ }
159
+ else if (!previousBackgroundBlock) {
160
+ previousBackgroundBlock = block;
161
+ break; // 2つ見つかったら終了
162
+ }
163
+ }
164
+ }
165
+ // background_groupが見つかった場合(background_changeより後にない場合のみ)
166
+ if (backgroundGroupBlock) {
167
+ return ((_c = (_b = backgroundGroupBlock.backgroundGroupItems) === null || _b === void 0 ? void 0 : _b.filter((item) => !!item.state.imageUrl && item.state.imageUrl.trim() !== "").map((item) => {
168
+ var _a, _b;
169
+ return ({
170
+ objectId: item.objectId,
171
+ stateId: item.stateId,
172
+ objectName: item.object.name,
173
+ stateName: item.state.name,
174
+ imageUrl: item.state.imageUrl,
175
+ webmUrl: (_a = item.state.webmUrl) !== null && _a !== void 0 ? _a : null,
176
+ objectFit: item.objectFit,
177
+ loop: item.loop,
178
+ mediaType: getMediaType(item.state.imageUrl),
179
+ opacity: item.opacity,
180
+ layer: (_b = item.layer) !== null && _b !== void 0 ? _b : "background",
181
+ });
182
+ })) !== null && _c !== void 0 ? _c : []);
183
+ }
184
+ if (!currentBackgroundBlock) {
185
+ return [];
186
+ }
187
+ const imageUrl = (_e = (_d = currentBackgroundBlock.backgroundState) === null || _d === void 0 ? void 0 : _d.imageUrl) !== null && _e !== void 0 ? _e : "";
188
+ // 前の背景ブロックのfadeDurationを使用(前の背景→現在の背景へのフェード時間)
189
+ // ブロックに設定がない場合はデフォルト値を使用
190
+ const previousOptions = previousBackgroundBlock === null || previousBackgroundBlock === void 0 ? void 0 : previousBackgroundBlock.options;
191
+ const blockFadeDuration = typeof (previousOptions === null || previousOptions === void 0 ? void 0 : previousOptions.fadeDuration) === "number"
192
+ ? previousOptions.fadeDuration
193
+ : 0;
194
+ const fadeDuration = blockFadeDuration > 0
195
+ ? blockFadeDuration
196
+ : mergedSettings.defaultBackgroundFadeDuration;
197
+ return [
198
+ {
199
+ objectId: (_f = currentBackgroundBlock.backgroundObjectId) !== null && _f !== void 0 ? _f : "",
200
+ stateId: (_g = currentBackgroundBlock.backgroundStateId) !== null && _g !== void 0 ? _g : "",
201
+ objectName: (_j = (_h = currentBackgroundBlock.backgroundObject) === null || _h === void 0 ? void 0 : _h.name) !== null && _j !== void 0 ? _j : "",
202
+ stateName: (_l = (_k = currentBackgroundBlock.backgroundState) === null || _k === void 0 ? void 0 : _k.name) !== null && _l !== void 0 ? _l : "",
203
+ imageUrl: imageUrl,
204
+ webmUrl: (_o = (_m = currentBackgroundBlock.backgroundState) === null || _m === void 0 ? void 0 : _m.webmUrl) !== null && _o !== void 0 ? _o : null,
205
+ objectFit: (_p = currentBackgroundBlock.backgroundObjectFit) !== null && _p !== void 0 ? _p : "cover",
206
+ loop: (_q = currentBackgroundBlock.backgroundLoop) !== null && _q !== void 0 ? _q : true,
207
+ mediaType: getMediaType(imageUrl),
208
+ fadeDuration,
209
+ opacity: 1.0,
210
+ layer: "background",
211
+ },
212
+ ];
213
+ }, [
214
+ scenario.blocks,
215
+ getMediaType,
216
+ mergedSettings.defaultBackgroundFadeDuration,
217
+ ]);
71
218
  const [state, setState] = useState({
72
219
  currentBlockIndex: 0,
73
220
  isPlaying: autoplay,
74
221
  isEnded: false,
222
+ variables: (_a = variableManagerRef.current) === null || _a === void 0 ? void 0 : _a.getVariablesMap(),
75
223
  });
224
+ const [currentBranchBlock, setCurrentBranchBlock] = useState(null);
225
+ // 遷移の種類を追跡(クリック、自動、時間待ちなど)
226
+ const [transitionSource, setTransitionSource] = useState("click");
227
+ // シナリオIDを追跡して、変更時に状態をリセット(プレビュー用)
228
+ const previousScenarioIdRef = useRef(scenario.id);
229
+ // シナリオが変更されたときに状態をリセット(keyを変更せずに対応)
230
+ useEffect(() => {
231
+ var _a;
232
+ if (previousScenarioIdRef.current !== scenario.id) {
233
+ previousScenarioIdRef.current = scenario.id;
234
+ // 状態をリセット
235
+ setState({
236
+ currentBlockIndex: 0,
237
+ isPlaying: autoplay,
238
+ isEnded: false,
239
+ variables: (_a = variableManagerRef.current) === null || _a === void 0 ? void 0 : _a.getVariablesMap(),
240
+ });
241
+ setCurrentBranchBlock(null);
242
+ }
243
+ }, [scenario.id, autoplay]);
76
244
  // 画像を事前読み込み
77
245
  const imagesLoaded = usePreloadImages(scenario);
246
+ // フォントを読み込み
247
+ const { isLoaded: fontsLoaded } = useFontLoader(scenario.fonts);
78
248
  // プラグインの読み込み状態
79
249
  const [pluginsLoaded, setPluginsLoaded] = useState(false);
250
+ // 読み込み済みプラグインのパッケージ名を追跡
251
+ const loadedPluginNamesRef = useRef(new Set());
80
252
  // プラグインの読み込み
253
+ // biome-ignore lint/correctness/useExhaustiveDependencies: pluginManagerは安定した参照なので依存配列から除外しても安全
81
254
  useEffect(() => {
255
+ let isCancelled = false;
82
256
  const loadPlugins = () => __awaiter(void 0, void 0, void 0, function* () {
83
- console.log("Loading plugins:", plugins);
84
- setPluginsLoaded(false);
85
- for (const plugin of plugins) {
86
- console.log("Loading plugin:", plugin.packageName);
87
- yield pluginManagerRef.current.loadPlugin(plugin.packageName, plugin.bundleUrl, plugin.config);
257
+ // 作品のサウンドデータをPluginManagerに設定
258
+ if (sounds.length > 0) {
259
+ pluginManager.setWorkSounds(sounds);
260
+ }
261
+ // 未読み込みのプラグインのみを抽出
262
+ const newPlugins = plugins.filter((plugin) => !loadedPluginNamesRef.current.has(plugin.packageName));
263
+ // 新しいプラグインがある場合のみローディング状態にする
264
+ if (newPlugins.length > 0) {
265
+ setPluginsLoaded(false);
266
+ for (const plugin of newPlugins) {
267
+ if (isCancelled)
268
+ return;
269
+ yield pluginManager.loadPlugin(plugin.packageName, plugin.bundleUrl, plugin.config);
270
+ loadedPluginNamesRef.current.add(plugin.packageName);
271
+ }
272
+ if (isCancelled)
273
+ return;
274
+ pluginManager.callHook("onInit");
275
+ pluginManager.showUI(ComponentType.DialogueBox);
276
+ // 全てのアセットプリロードが完了するまで待機
277
+ yield pluginManager.waitForPreloads();
278
+ }
279
+ if (!isCancelled) {
280
+ setPluginsLoaded(true);
88
281
  }
89
- // プラグイン初期化フック - すべてのプラグインの読み込み完了後に実行
90
- console.log("Calling onInit hook");
91
- pluginManagerRef.current.callHook("onInit");
92
- // DialogueBoxの初期状態を表示に設定
93
- pluginManagerRef.current.showUI(ComponentType.DialogueBox);
94
- setPluginsLoaded(true);
95
- console.log("All plugins loaded successfully");
96
282
  });
97
283
  loadPlugins();
98
284
  return () => {
99
- const manager = pluginManagerRef.current;
100
- manager.cleanup();
285
+ isCancelled = true;
101
286
  };
102
- }, [plugins]);
287
+ }, [plugins, sounds]);
103
288
  // 初回レンダリング完了フラグ
104
289
  const [isFirstRenderComplete, setIsFirstRenderComplete] = useState(false);
105
290
  // シナリオ開始コールバック(後でcurrentBlockが定義された後に実行)
@@ -113,58 +298,222 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
113
298
  // data-character-id属性を持つ最初のキャラクター要素を取得
114
299
  const characterSpriteElement = document.querySelector("[data-character-id]");
115
300
  const gameScreenElement = document.querySelector(".h-screen.w-full");
116
- console.log("🔍 [StyleAPI] Element registration:", {
117
- speakerNameElement: !!speakerNameElement,
118
- dialogueElement: !!dialogueElement,
119
- characterSpriteElement: !!characterSpriteElement,
120
- gameScreenElement: !!gameScreenElement,
121
- });
122
301
  if (speakerNameElement) {
123
- pluginManagerRef.current.registerStyleElement("speakerName", speakerNameElement);
302
+ pluginManager.registerStyleElement("speakerName", speakerNameElement);
124
303
  }
125
304
  if (dialogueElement) {
126
- pluginManagerRef.current.registerStyleElement("dialogueBox", dialogueElement);
127
- pluginManagerRef.current.registerStyleElement("scenarioBlockContent", dialogueElement);
305
+ pluginManager.registerStyleElement("dialogueBox", dialogueElement);
306
+ pluginManager.registerStyleElement("scenarioBlockContent", dialogueElement);
128
307
  }
129
308
  if (characterSpriteElement) {
130
- pluginManagerRef.current.registerStyleElement("characterSprite", characterSpriteElement);
309
+ pluginManager.registerStyleElement("characterSprite", characterSpriteElement);
131
310
  }
132
311
  if (gameScreenElement) {
133
- pluginManagerRef.current.registerStyleElement("gameScreen", gameScreenElement);
134
- pluginManagerRef.current.registerStyleElement("background", gameScreenElement);
312
+ pluginManager.registerStyleElement("gameScreen", gameScreenElement);
313
+ pluginManager.registerStyleElement("background", gameScreenElement);
135
314
  }
136
315
  }
137
- }, [isFirstRenderComplete]);
138
- const { displayText, isTyping, skipTyping, startTyping } = useTypewriter({
316
+ }, [pluginManager, isFirstRenderComplete]);
317
+ // Fullscreen text要素の登録(ブロックタイプがfullscreen_textの時のみ)
318
+ useEffect(() => {
319
+ if (!isFirstRenderComplete)
320
+ return;
321
+ // 少し遅延させてDOMが更新されるのを待つ
322
+ const timeoutId = setTimeout(() => {
323
+ const fullscreenOverlay = document.querySelector("[data-fullscreen-text-element]");
324
+ const fullscreenContainer = document.querySelector("[data-fullscreen-text-container]");
325
+ if (fullscreenOverlay) {
326
+ pluginManager.registerStyleElement("fullscreenTextOverlay", fullscreenOverlay);
327
+ }
328
+ if (fullscreenContainer) {
329
+ pluginManager.registerStyleElement("fullscreenTextContainer", fullscreenContainer);
330
+ }
331
+ }, 100);
332
+ return () => clearTimeout(timeoutId);
333
+ }, [pluginManager, isFirstRenderComplete]);
334
+ const { displayText, isTyping, skipTyping, startTyping, resetAccumulated } = useTypewriter({
139
335
  speed: mergedSettings.textSpeed,
140
336
  });
141
- // 現在の表示可能なブロックを取得
142
- const currentBlock = displayableBlockIndices[state.currentBlockIndex] !== undefined
143
- ? scenario.blocks[displayableBlockIndices[state.currentBlockIndex]]
144
- : undefined;
337
+ // 現在の表示可能なブロックを取得(分岐ブロックが優先)
338
+ const currentBlock = currentBranchBlock ||
339
+ (displayableBlockIndices[state.currentBlockIndex] !== undefined
340
+ ? scenario.blocks[displayableBlockIndices[state.currentBlockIndex]]
341
+ : undefined);
342
+ // 前のブロックを取得(フェード処理用)
343
+ const previousBlock = useMemo(() => {
344
+ if (state.currentBlockIndex <= 0)
345
+ return null;
346
+ const prevIdx = displayableBlockIndices[state.currentBlockIndex - 1];
347
+ return prevIdx !== undefined ? scenario.blocks[prevIdx] : null;
348
+ }, [scenario.blocks, displayableBlockIndices, state.currentBlockIndex]);
145
349
  // usePlayerLogicに渡す実際のブロックインデックス
146
- const actualBlockIndex = (_a = displayableBlockIndices[state.currentBlockIndex]) !== null && _a !== void 0 ? _a : 0;
350
+ const actualBlockIndex = (_b = displayableBlockIndices[state.currentBlockIndex]) !== null && _b !== void 0 ? _b : 0;
147
351
  // バックログ機能
148
352
  const backlog = useBacklog({
149
353
  scenario,
150
354
  currentBlockIndex: actualBlockIndex,
151
355
  currentBlock: currentBlock || null,
152
356
  });
357
+ // サウンド再生機能
358
+ // actualBlockIndexの前後にあるサウンドブロックを処理するため、
359
+ // 表示ブロックに到達する直前のサウンドブロックを取得
360
+ const soundBlocksToProcess = useMemo(() => {
361
+ const blocks = [];
362
+ const currentDisplayableIdx = displayableBlockIndices[state.currentBlockIndex];
363
+ if (currentDisplayableIdx === undefined)
364
+ return blocks;
365
+ // 前の表示可能ブロックのインデックスを取得
366
+ const prevDisplayableIdx = state.currentBlockIndex > 0
367
+ ? displayableBlockIndices[state.currentBlockIndex - 1]
368
+ : -1;
369
+ // 前の表示ブロックから現在の表示ブロックまでの間にあるサウンドブロックを収集
370
+ for (let i = prevDisplayableIdx + 1; i < currentDisplayableIdx; i++) {
371
+ const block = scenario.blocks[i];
372
+ if (block.blockType === "bgm_play" ||
373
+ block.blockType === "se_play" ||
374
+ block.blockType === "bgm_stop") {
375
+ blocks.push(block);
376
+ }
377
+ }
378
+ return blocks;
379
+ }, [scenario.blocks, displayableBlockIndices, state.currentBlockIndex]);
380
+ // サウンド再生フック
381
+ useSoundPlayer({
382
+ soundBlocks: soundBlocksToProcess,
383
+ isFirstRenderComplete,
384
+ muteAudio: mergedSettings.muteAudio,
385
+ });
386
+ // 会話分岐機能
387
+ const branchNavigatorRef = useRef(new BranchNavigator());
388
+ const conversationBranch = useConversationBranch({
389
+ apiBaseUrl: typeof window !== "undefined" ? window.location.origin : "",
390
+ variableManager: variableManagerRef.current,
391
+ scenario,
392
+ });
393
+ // conversationBranchから安定した参照を抽出
394
+ const { loadBranch, selectChoice, getSelectedChoiceFullData, branchState } = conversationBranch;
395
+ // 選択肢選択処理
396
+ const handleChoiceSelection = useCallback((choiceId) => {
397
+ selectChoice(choiceId);
398
+ const selectedChoice = getSelectedChoiceFullData(choiceId);
399
+ if (selectedChoice) {
400
+ branchNavigatorRef.current.startBranchNavigation(selectedChoice);
401
+ pluginManager.callHook("onChoiceSelected", {
402
+ choiceId,
403
+ choiceText: selectedChoice.choiceText,
404
+ });
405
+ const firstBranchBlock = branchNavigatorRef.current.nextBranchBlock();
406
+ if (firstBranchBlock) {
407
+ setCurrentBranchBlock(convertBranchBlockToScenarioBlock(firstBranchBlock));
408
+ }
409
+ }
410
+ else {
411
+ console.error("[ConversationBranch] No choice data found for:", choiceId);
412
+ }
413
+ }, [pluginManager, selectChoice, getSelectedChoiceFullData]);
414
+ // handleChoiceSelectionRefを更新
415
+ handleChoiceSelectionRef.current = handleChoiceSelection;
416
+ // window.playerAPIとPluginManagerのコールバックを設定
417
+ useEffect(() => {
418
+ if (typeof window !== "undefined") {
419
+ window.playerAPI = {
420
+ selectChoice: (choiceId) => {
421
+ var _a;
422
+ (_a = handleChoiceSelectionRef.current) === null || _a === void 0 ? void 0 : _a.call(handleChoiceSelectionRef, choiceId);
423
+ },
424
+ };
425
+ }
426
+ pluginManager.setSelectChoiceCallback((choiceId) => {
427
+ var _a;
428
+ (_a = handleChoiceSelectionRef.current) === null || _a === void 0 ? void 0 : _a.call(handleChoiceSelectionRef, choiceId);
429
+ });
430
+ pluginManager.setGetBranchStateCallback(() => {
431
+ return branchState;
432
+ });
433
+ return () => {
434
+ if (typeof window !== "undefined") {
435
+ delete window.playerAPI;
436
+ }
437
+ };
438
+ }, [pluginManager, branchState]);
153
439
  // ダイアログ表示とアクションノード実行のuseEffectを分離
154
440
  useEffect(() => {
155
441
  if (currentBlock) {
156
- startTyping(currentBlock.content || "");
442
+ // fullscreen_text は独自のタイプライターを使うので、displayTextをクリアしてスキップ
443
+ if (currentBlock.blockType === "fullscreen_text") {
444
+ startTyping("");
445
+ return;
446
+ }
447
+ let content = currentBlock.content || "";
448
+ if (variableManagerRef.current &&
449
+ (currentBlock.blockType === "dialogue" ||
450
+ currentBlock.blockType === "narration")) {
451
+ content = variableManagerRef.current.interpolateText(content);
452
+ }
453
+ // 前のブロックのcontinueModeをチェック
454
+ const previousOptions = previousBlock === null || previousBlock === void 0 ? void 0 : previousBlock.options;
455
+ let continueMode = false;
456
+ if (previousOptions === null || previousOptions === void 0 ? void 0 : previousOptions.continueMode) {
457
+ const mode = previousOptions.continueMode;
458
+ if (mode === "continue" || mode === "continueWithNewline") {
459
+ continueMode = mode;
460
+ }
461
+ }
462
+ const isContinuableBlock = currentBlock.blockType === "dialogue" ||
463
+ currentBlock.blockType === "narration";
464
+ if (continueMode && isContinuableBlock) {
465
+ startTyping(content, continueMode);
466
+ }
467
+ else {
468
+ resetAccumulated();
469
+ startTyping(content, false);
470
+ }
471
+ }
472
+ }, [currentBlock, previousBlock, startTyping, resetAccumulated]);
473
+ // 分岐ブロック自動ロード処理
474
+ useEffect(() => {
475
+ if (currentBlock && currentBlock.blockType === "conversation_branch") {
476
+ loadBranch(currentBlock.id);
157
477
  }
158
- }, [currentBlock, startTyping]);
159
- // 画像読み込み完了後、GameScreenがマウントされてから表示する
478
+ }, [currentBlock, loadBranch]);
479
+ // プラグインフックを別のuseEffectで実行(branchStateが更新されてから)
160
480
  useEffect(() => {
161
- if (imagesLoaded && currentBlock && !isFirstRenderComplete) {
481
+ if (currentBlock &&
482
+ currentBlock.blockType === "conversation_branch" &&
483
+ branchState.isActive) {
484
+ pluginManager.callHook("onBranchStart", {
485
+ branchBlockId: currentBlock.id,
486
+ choices: branchState.currentChoices,
487
+ });
488
+ // 変数分岐の場合は自動で選択された分岐に進む
489
+ if (branchState.branchType === "variable") {
490
+ if (branchState.selectedChoiceId) {
491
+ handleChoiceSelection(branchState.selectedChoiceId);
492
+ }
493
+ else if (branchState.errorState === "NO_MATCHING_CONDITION") {
494
+ console.error("変数分岐: いずれの条件にも一致しませんでした。シナリオが停止します。");
495
+ }
496
+ }
497
+ }
498
+ }, [
499
+ pluginManager,
500
+ currentBlock,
501
+ branchState.isActive,
502
+ branchState.currentChoices,
503
+ branchState.branchType,
504
+ branchState.selectedChoiceId,
505
+ branchState.errorState,
506
+ handleChoiceSelection,
507
+ ]);
508
+ // 画像とフォント読み込み完了後、GameScreenがマウントされてから表示する
509
+ useEffect(() => {
510
+ if (imagesLoaded && fontsLoaded && currentBlock && !isFirstRenderComplete) {
162
511
  // requestAnimationFrameを使用してレンダリング完了を待つ
163
512
  requestAnimationFrame(() => {
164
513
  setIsFirstRenderComplete(true);
165
514
  });
166
515
  }
167
- }, [imagesLoaded, currentBlock, isFirstRenderComplete]);
516
+ }, [imagesLoaded, fontsLoaded, currentBlock, isFirstRenderComplete]);
168
517
  // シナリオ開始コールバック(currentBlock定義後)
169
518
  useEffect(() => {
170
519
  if (isFirstRenderComplete && !hasStarted && currentBlock) {
@@ -172,8 +521,30 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
172
521
  onScenarioStart === null || onScenarioStart === void 0 ? void 0 : onScenarioStart();
173
522
  }
174
523
  }, [isFirstRenderComplete, hasStarted, currentBlock, onScenarioStart]);
175
- const { handleNext: handleNextInternal, handlePrevious: handlePreviousInternal, togglePlay, // eslint-disable-line @typescript-eslint/no-unused-vars
176
- restart: restartInternal, displayedCharacters, } = usePlayerLogic({
524
+ // restartをusePlayerLogicの前に定義(分岐状態もリセット + キャンセルコールバック)
525
+ const restart = useCallback(() => {
526
+ // リスタート時はシナリオがキャンセルされたとみなす
527
+ if (hasStarted && !state.isEnded) {
528
+ onScenarioCancelled === null || onScenarioCancelled === void 0 ? void 0 : onScenarioCancelled();
529
+ }
530
+ setIsFirstRenderComplete(false);
531
+ setHasStarted(false);
532
+ setCurrentBranchBlock(null);
533
+ branchNavigatorRef.current.reset();
534
+ resetAccumulated(); // 蓄積テキストをクリア
535
+ setState({
536
+ currentBlockIndex: 0,
537
+ isPlaying: autoplay,
538
+ isEnded: false,
539
+ });
540
+ }, [
541
+ autoplay,
542
+ hasStarted,
543
+ state.isEnded,
544
+ onScenarioCancelled,
545
+ resetAccumulated,
546
+ ]);
547
+ const { handleNext: handleNextInternal, handlePrevious: handlePreviousInternal, togglePlay: _togglePlay, restart: _restartInternal, displayedCharacters, } = usePlayerLogic({
177
548
  state: Object.assign(Object.assign({}, state), { currentBlockIndex: actualBlockIndex }),
178
549
  setState: (newState) => {
179
550
  if (typeof newState === "function") {
@@ -199,13 +570,18 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
199
570
  onEnd,
200
571
  onScenarioEnd,
201
572
  autoplay,
573
+ branchState: branchState,
574
+ branchNavigator: branchNavigatorRef.current,
575
+ customRestart: restart,
576
+ variableManager: variableManagerRef.current,
577
+ disableKeyboardNavigation,
202
578
  });
203
579
  // プラグインイベント処理
204
580
  const realBlockIndex = currentBlock
205
581
  ? scenario.blocks.indexOf(currentBlock)
206
582
  : 0;
207
583
  usePluginEvents({
208
- pluginManager: pluginManagerRef.current,
584
+ pluginManager: pluginManager,
209
585
  currentBlock,
210
586
  displayedCharacters,
211
587
  blockIndex: state.currentBlockIndex,
@@ -213,6 +589,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
213
589
  allBlocks: scenario.blocks,
214
590
  realBlockIndex,
215
591
  pluginsLoaded,
592
+ transitionSource,
216
593
  });
217
594
  // 初期履歴構築(シナリオ開始時)
218
595
  useEffect(() => {
@@ -221,8 +598,31 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
221
598
  }
222
599
  }, [isFirstRenderComplete, actualBlockIndex, backlog]);
223
600
  // ハンドラーをラップして表示可能インデックスで動作するように
224
- const handleNext = useCallback(() => {
225
- if (state.currentBlockIndex < displayableBlockIndices.length - 1) {
601
+ const handleNext = useCallback((source = "click") => {
602
+ // time_waitブロックはクリックで遷移できないようにする
603
+ if (source === "click" && (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "time_wait") {
604
+ return;
605
+ }
606
+ // 遷移元を設定
607
+ setTransitionSource(source);
608
+ if (currentBranchBlock &&
609
+ branchNavigatorRef.current.hasMoreBranchBlocks()) {
610
+ const nextBranchBlock = branchNavigatorRef.current.nextBranchBlock();
611
+ if (nextBranchBlock) {
612
+ setCurrentBranchBlock(convertBranchBlockToScenarioBlock(nextBranchBlock));
613
+ }
614
+ else {
615
+ setCurrentBranchBlock(null);
616
+ branchNavigatorRef.current.reset();
617
+ handleNextInternal();
618
+ }
619
+ }
620
+ else if (currentBranchBlock) {
621
+ setCurrentBranchBlock(null);
622
+ branchNavigatorRef.current.reset();
623
+ handleNextInternal();
624
+ }
625
+ else if (state.currentBlockIndex < displayableBlockIndices.length - 1) {
226
626
  handleNextInternal();
227
627
  }
228
628
  else {
@@ -231,6 +631,8 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
231
631
  onScenarioEnd === null || onScenarioEnd === void 0 ? void 0 : onScenarioEnd();
232
632
  }
233
633
  }, [
634
+ currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType,
635
+ currentBranchBlock,
234
636
  state.currentBlockIndex,
235
637
  displayableBlockIndices.length,
236
638
  handleNextInternal,
@@ -238,24 +640,16 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
238
640
  onScenarioEnd,
239
641
  ]);
240
642
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
241
- const handlePrevious = useCallback(() => {
643
+ const _handlePrevious = useCallback(() => {
242
644
  if (state.currentBlockIndex > 0) {
243
645
  handlePreviousInternal();
244
646
  }
245
647
  }, [state.currentBlockIndex, handlePreviousInternal]);
246
- // restartをラップして、firstRenderCompleteもリセット
247
- const restart = useCallback(() => {
248
- // リスタート時はシナリオがキャンセルされたとみなす
249
- if (hasStarted && !state.isEnded) {
250
- onScenarioCancelled === null || onScenarioCancelled === void 0 ? void 0 : onScenarioCancelled();
251
- }
252
- setIsFirstRenderComplete(false);
253
- setHasStarted(false);
254
- restartInternal();
255
- }, [hasStarted, state.isEnded, onScenarioCancelled, restartInternal]);
648
+ // 現在の背景を計算
649
+ const currentBackground = useMemo(() => calculateCurrentBackground(actualBlockIndex), [calculateCurrentBackground, actualBlockIndex]);
256
650
  // DataContext の構築 - displayedCharactersが必要なため usePlayerLogic の後に配置
257
651
  const dataContext = useMemo(() => {
258
- var _a, _b;
652
+ var _a, _b, _c;
259
653
  return ({
260
654
  playback: {
261
655
  currentBlockIndex: actualBlockIndex,
@@ -263,7 +657,9 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
263
657
  scenarioId: scenario.id,
264
658
  scenarioName: scenario.name,
265
659
  currentBlock: currentBlock || null,
266
- displayText,
660
+ displayText: displayText.includes("\n")
661
+ ? displayText.split("\n").map((line, index, array) => (_jsxs(React.Fragment, { children: [line, index < array.length - 1 && _jsx("br", {})] }, index)))
662
+ : displayText,
267
663
  isTyping,
268
664
  displayedCharacters,
269
665
  },
@@ -285,11 +681,21 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
285
681
  },
286
682
  pluginAssets: {
287
683
  getAssetUrl: (pluginName, filename) => {
288
- return pluginManagerRef.current.getPluginAssetUrl(pluginName, filename);
684
+ return pluginManager.getPluginAssetUrl(pluginName, filename);
289
685
  },
290
686
  },
687
+ branchData: conversationBranch.branchState,
688
+ branchHistory: [],
689
+ background: currentBackground.length > 0 ? currentBackground[0] : null,
690
+ backgrounds: currentBackground,
691
+ fonts: {
692
+ fonts: (_c = scenario.fonts) !== null && _c !== void 0 ? _c : [],
693
+ selectedFontFamily: mergedSettings.selectedFontFamily,
694
+ isLoaded: fontsLoaded,
695
+ },
291
696
  });
292
697
  }, [
698
+ pluginManager,
293
699
  actualBlockIndex,
294
700
  scenario,
295
701
  currentBlock,
@@ -297,10 +703,22 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
297
703
  isTyping,
298
704
  displayedCharacters,
299
705
  backlog,
300
- settings,
706
+ conversationBranch.branchState,
707
+ mergedSettings.aspectRatio,
708
+ mergedSettings.autoPlaySpeed,
709
+ mergedSettings.bgObjectFit,
710
+ mergedSettings.bgmVolume,
711
+ mergedSettings.seVolume,
712
+ mergedSettings.textSpeed,
713
+ mergedSettings.voiceVolume,
714
+ mergedSettings.selectedFontFamily,
715
+ currentBackground,
716
+ fontsLoaded,
301
717
  ]);
302
- // マウスホイールとタッチジェスチャーを無効化
718
+ // マウスホイールとタッチジェスチャーを無効化(preventDefaultScrollがtrueの場合のみ)
303
719
  useEffect(() => {
720
+ if (!preventDefaultScroll)
721
+ return;
304
722
  const handleWheel = (e) => {
305
723
  e.preventDefault();
306
724
  };
@@ -321,7 +739,7 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
321
739
  document.removeEventListener("touchmove", handleTouchMove);
322
740
  document.removeEventListener("gesturestart", handleGestureStart);
323
741
  };
324
- }, []);
742
+ }, [preventDefaultScroll]);
325
743
  // アスペクト比を計算
326
744
  const getAspectRatio = () => {
327
745
  if (!(settings === null || settings === void 0 ? void 0 : settings.aspectRatio))
@@ -330,11 +748,18 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
330
748
  return `${width}/${height}`;
331
749
  };
332
750
  // 条件付きレンダリングを JSX で処理(フックの後、early return なし)
333
- return (_jsxs(_Fragment, { children: [!currentBlock && !state.isEnded && (_jsx("div", { className: clsx("flex items-center justify-center p-8", className), children: _jsx("p", { className: "text-gray-500", children: "\u30B7\u30CA\u30EA\u30AA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" }) })), state.isEnded && (_jsx(EndScreen, { scenarioName: scenario.name, onRestart: restart, className: className })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded || !isFirstRenderComplete) && (_jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center z-50", className), style: {
751
+ return (_jsxs(_Fragment, { children: [!currentBlock && !state.isEnded && (_jsx("div", { className: clsx("flex items-center justify-center p-8", className), children: _jsx("p", { className: "text-gray-500", children: "\u30B7\u30CA\u30EA\u30AA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" }) })), state.isEnded && (_jsx(EndScreen, { scenarioName: scenario.name, onRestart: restart, className: className })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded ||
752
+ !fontsLoaded ||
753
+ !isFirstRenderComplete ||
754
+ !pluginsLoaded) && (_jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center z-50", className), style: {
334
755
  touchAction: "none",
335
756
  userSelect: "none",
336
757
  WebkitUserSelect: "none",
337
- } })), _jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center", className, (!imagesLoaded || !isFirstRenderComplete) && "opacity-0"), onClick: handleNext, style: {
758
+ } })), _jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center", className, (!imagesLoaded ||
759
+ !fontsLoaded ||
760
+ !isFirstRenderComplete ||
761
+ !pluginsLoaded) &&
762
+ "opacity-0"), onClick: () => handleNext("click"), style: {
338
763
  touchAction: "none",
339
764
  userSelect: "none",
340
765
  WebkitUserSelect: "none",
@@ -344,8 +769,10 @@ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd,
344
769
  voiceVolume: mergedSettings.voiceVolume,
345
770
  effectVolume: mergedSettings.effectVolume,
346
771
  textSoundVolume: mergedSettings.textSoundVolume,
347
- }, children: [_jsx("div", { className: "h-full", children: _jsx(GameScreen, { scenario: scenario, currentBlock: currentBlock, displayedCharacters: displayedCharacters }) }), _jsx(OverlayUI, { children: _jsxs("div", { className: "h-full w-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.DialogueBox, pluginManager: pluginManagerRef.current, fallback: DialogueBox }), pluginManagerRef.current
772
+ muteAudio: (_c = mergedSettings.muteAudio) !== null && _c !== void 0 ? _c : false,
773
+ }, children: [_jsxs("div", { className: "h-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.Background, pluginManager: pluginManager, fallback: BackgroundLayer }, "background"), _jsx(GameScreen, { scenario: scenario, currentBlock: currentBlock, previousBlock: previousBlock, displayedCharacters: displayedCharacters }, "game-screen")] }), _jsx(OverlayUI, { children: _jsxs("div", { className: "h-full w-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.DialogueBox, pluginManager: pluginManager, fallback: DialogueBox }, "dialogue-box"), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "conversation_branch" && (_jsx(PluginComponentProvider, { type: ComponentType.ConversationBranch, pluginManager: pluginManager, fallback: ConversationBranchBox }, "conversation-branch")), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "fullscreen_text" && (_jsx(FullscreenTextBox, { onComplete: () => handleNext("click") }, "fullscreen-text")), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "click_wait" && (_jsx(ClickWaitIndicator, {}, "click-wait")), (currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.blockType) === "time_wait" && (_jsx(TimeWaitIndicator, { duration: (_e = (_d = currentBlock.options) === null || _d === void 0 ? void 0 : _d.duration) !== null && _e !== void 0 ? _e : 1, onComplete: () => handleNext("time_wait") }, "time-wait")), pluginManager
348
774
  .getRegisteredComponents()
349
- .filter((type) => type !== ComponentType.DialogueBox) // DialogueBoxは既に上でレンダリング済み
350
- .map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManagerRef.current }, componentType)))] }) })] }) }) }) })] }))] }));
775
+ .filter((type) => type !== ComponentType.DialogueBox &&
776
+ type !== ComponentType.ConversationBranch)
777
+ .map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManager }, componentType)))] }) })] }) }) }) })] }))] }));
351
778
  };