@luna-editor/engine 0.3.0 → 0.3.2

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.
@@ -1,12 +1,30 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { useDataAPI } from "../contexts/DataContext";
2
4
  /**
3
5
  * Wrapper component that renders a plugin-registered component or a fallback
4
- * All data is provided via DataContext, so no props are passed
6
+ * DataAPI is provided via props to avoid React context issues across different React instances
5
7
  */
6
8
  export function PluginComponentProvider({ type, pluginManager, fallback: FallbackComponent, }) {
9
+ // Get DataAPI in Player's React context
10
+ const dataAPI = useDataAPI();
11
+ // 【emotionEffect監視】: emotionEffectの変更を監視して再レンダリング
12
+ // 【実装方針】: プラグインコンポーネントがdataAPI.get()を使用して状態を取得する場合、
13
+ // Reactフックを使えないため、親側で変更を監視して再レンダリングを強制する
14
+ const [, forceUpdate] = useState({});
15
+ useEffect(() => {
16
+ // 全てのプラグインコンポーネントでemotionEffectの変更を監視
17
+ const unsubscribe = dataAPI.subscribe("emotionEffect", () => {
18
+ forceUpdate({});
19
+ });
20
+ return () => {
21
+ unsubscribe();
22
+ };
23
+ }, [dataAPI]);
7
24
  const Component = pluginManager.getComponent(type);
8
25
  if (Component) {
9
- return _jsx(Component, {});
26
+ // Pass DataAPI as prop to avoid context issues
27
+ return _jsx(Component, { dataAPI: dataAPI });
10
28
  }
11
29
  if (FallbackComponent) {
12
30
  return _jsx(FallbackComponent, {});
@@ -1,6 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
- import type { DataAPI, DataContext, PlayerSettingsData } from "../sdk";
2
+ import type { DataAPI, DataContext, EmotionEffectState, PlayerSettingsData } from "../sdk";
3
3
  type SettingsUpdater = (settings: Partial<PlayerSettingsData>) => void;
4
+ type EmotionEffectUpdater = (state: EmotionEffectState | null) => void;
4
5
  /**
5
6
  * データプロバイダー
6
7
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -17,6 +18,7 @@ type SettingsUpdater = (settings: Partial<PlayerSettingsData>) => void;
17
18
  export declare const DataProvider: React.FC<{
18
19
  data: DataContext;
19
20
  onSettingsUpdate?: SettingsUpdater;
21
+ onEmotionEffectUpdate?: EmotionEffectUpdater;
20
22
  children: ReactNode;
21
23
  }>;
22
24
  /**
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, } from "react";
3
3
  const DataContextInstance = createContext(null);
4
+ // データへの参照を共有するContext(Context value自体は変わらない)
5
+ const DataRefContext = createContext(null);
4
6
  const SubscribersContext = createContext(null);
5
7
  const SettingsUpdaterContext = createContext(null);
8
+ const EmotionEffectUpdaterContext = createContext(null);
6
9
  /**
7
10
  * データプロバイダー
8
11
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -16,9 +19,15 @@ const SettingsUpdaterContext = createContext(null);
16
19
  * </DataProvider>
17
20
  * ```
18
21
  */
19
- export const DataProvider = ({ data, onSettingsUpdate, children }) => {
22
+ export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, children }) => {
20
23
  const subscribers = useMemo(() => new Map(), []);
21
24
  const previousDataRef = useRef(data);
25
+ // 安定したデータ参照ホルダー(Context valueとして提供)
26
+ // オブジェクト自体の参照は変わらず、currentプロパティを更新
27
+ // これにより、useContextを使うコンポーネントは再レンダリングされない
28
+ const dataRefHolder = useMemo(() => ({ current: data }), [data]);
29
+ // dataが変わるたびにcurrentを更新
30
+ dataRefHolder.current = data;
22
31
  // データ変更時に購読者に通知
23
32
  useEffect(() => {
24
33
  const prevData = previousDataRef.current;
@@ -52,29 +61,38 @@ export const DataProvider = ({ data, onSettingsUpdate, children }) => {
52
61
  }
53
62
  previousDataRef.current = data;
54
63
  }, [data, subscribers]);
55
- return (_jsx(SubscribersContext.Provider, { value: subscribers, children: _jsx(SettingsUpdaterContext.Provider, { value: onSettingsUpdate !== null && onSettingsUpdate !== void 0 ? onSettingsUpdate : null, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }) }));
64
+ return (_jsx(SubscribersContext.Provider, { value: subscribers, children: _jsx(SettingsUpdaterContext.Provider, { value: onSettingsUpdate !== null && onSettingsUpdate !== void 0 ? onSettingsUpdate : null, children: _jsx(EmotionEffectUpdaterContext.Provider, { value: onEmotionEffectUpdate !== null && onEmotionEffectUpdate !== void 0 ? onEmotionEffectUpdate : null, children: _jsx(DataRefContext.Provider, { value: dataRefHolder, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }) }) }) }));
56
65
  };
57
66
  /**
58
67
  * DataAPI実装を提供するフック
59
68
  * プラグインから呼び出される
60
69
  */
61
70
  export function useDataAPI() {
62
- const data = useContext(DataContextInstance);
71
+ var _a;
72
+ // DataRefContextから安定したデータ参照を取得
73
+ // これはContext valueが変わらないので、再レンダリングを引き起こさない
74
+ const dataRefHolder = useContext(DataRefContext);
63
75
  const subscribers = useContext(SubscribersContext);
64
76
  const settingsUpdater = useContext(SettingsUpdaterContext);
65
- if (!data || !subscribers) {
77
+ const emotionEffectUpdater = useContext(EmotionEffectUpdaterContext);
78
+ // 注意: usePlaybackTextOptional()をここで呼ぶと、PlaybackTextContextの更新で
79
+ // useDataAPIを使う全コンポーネントが再レンダリングされてしまう
80
+ // 代わりに、displayTextが必要なコンポーネントはusePlaybackText()を直接使う
81
+ if (!dataRefHolder || !subscribers) {
66
82
  throw new Error("useDataAPI must be used within DataProvider");
67
83
  }
68
84
  const get = useCallback((key, property) => {
85
+ const currentData = dataRefHolder.current;
69
86
  if (property !== undefined) {
70
- const value = data[key];
87
+ const value = currentData[key];
71
88
  if (value && typeof value === "object" && property in value) {
72
89
  return value[property];
73
90
  }
74
91
  return undefined;
75
92
  }
76
- return data[key];
77
- }, [data]);
93
+ return currentData[key];
94
+ }, [dataRefHolder.current] // 依存配列を空にして、get関数を安定化
95
+ );
78
96
  const subscribe = useCallback((key, callback) => {
79
97
  var _a;
80
98
  const subscriberKey = key;
@@ -140,12 +158,21 @@ export function useDataAPI() {
140
158
  }, [updateSettings]);
141
159
  const getBlockOption = useCallback((key) => {
142
160
  var _a;
143
- const currentBlock = (_a = data.playback) === null || _a === void 0 ? void 0 : _a.currentBlock;
161
+ const currentBlock = (_a = dataRefHolder.current.playback) === null || _a === void 0 ? void 0 : _a.currentBlock;
144
162
  if (!(currentBlock === null || currentBlock === void 0 ? void 0 : currentBlock.options)) {
145
163
  return undefined;
146
164
  }
147
165
  return currentBlock.options[key];
148
- }, [data]);
166
+ }, [(_a = dataRefHolder.current.playback) === null || _a === void 0 ? void 0 : _a.currentBlock] // 依存配列を空にして安定化
167
+ );
168
+ const updateEmotionEffect = useCallback((state) => {
169
+ if (emotionEffectUpdater) {
170
+ emotionEffectUpdater(state);
171
+ }
172
+ else {
173
+ console.warn("updateEmotionEffect called but no emotion effect updater provided");
174
+ }
175
+ }, [emotionEffectUpdater]);
149
176
  return useMemo(() => ({
150
177
  get,
151
178
  subscribe,
@@ -156,6 +183,7 @@ export function useDataAPI() {
156
183
  setVoiceVolume,
157
184
  setVolumes,
158
185
  getBlockOption,
186
+ updateEmotionEffect,
159
187
  }), [
160
188
  get,
161
189
  subscribe,
@@ -166,5 +194,6 @@ export function useDataAPI() {
166
194
  setVoiceVolume,
167
195
  setVolumes,
168
196
  getBlockOption,
197
+ updateEmotionEffect,
169
198
  ]);
170
199
  }
@@ -0,0 +1,32 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * 文字送り用の軽量コンテキスト
4
+ * displayText と isTyping は頻繁に更新されるため、
5
+ * DataContext から分離して不要な再レンダリングを防ぐ
6
+ */
7
+ interface PlaybackTextContextValue {
8
+ /** 現在表示中のテキスト(文字送り中は部分テキスト) */
9
+ displayText: string | React.ReactNode;
10
+ /** 文字送り中かどうか */
11
+ isTyping: boolean;
12
+ }
13
+ /**
14
+ * PlaybackTextProvider
15
+ * 文字送り状態を提供するProvider
16
+ * DataProviderとは別に、displayTextとisTypingのみを管理
17
+ */
18
+ export declare const PlaybackTextProvider: React.FC<{
19
+ displayText: string | React.ReactNode;
20
+ isTyping: boolean;
21
+ children: ReactNode;
22
+ }>;
23
+ /**
24
+ * 文字送り状態を取得するフック
25
+ */
26
+ export declare function usePlaybackText(): PlaybackTextContextValue;
27
+ /**
28
+ * 文字送り状態を取得するフック(オプショナル版)
29
+ * PlaybackTextProviderの外でも使用可能(その場合はnullを返す)
30
+ */
31
+ export declare function usePlaybackTextOptional(): PlaybackTextContextValue | null;
32
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ const PlaybackTextContextInstance = createContext(null);
4
+ /**
5
+ * PlaybackTextProvider
6
+ * 文字送り状態を提供するProvider
7
+ * DataProviderとは別に、displayTextとisTypingのみを管理
8
+ */
9
+ export const PlaybackTextProvider = ({ displayText, isTyping, children }) => {
10
+ const value = useMemo(() => ({ displayText, isTyping }), [displayText, isTyping]);
11
+ return (_jsx(PlaybackTextContextInstance.Provider, { value: value, children: children }));
12
+ };
13
+ /**
14
+ * 文字送り状態を取得するフック
15
+ */
16
+ export function usePlaybackText() {
17
+ const context = useContext(PlaybackTextContextInstance);
18
+ if (!context) {
19
+ throw new Error("usePlaybackText must be used within PlaybackTextProvider");
20
+ }
21
+ return context;
22
+ }
23
+ /**
24
+ * 文字送り状態を取得するフック(オプショナル版)
25
+ * PlaybackTextProviderの外でも使用可能(その場合はnullを返す)
26
+ */
27
+ export function usePlaybackTextOptional() {
28
+ return useContext(PlaybackTextContextInstance);
29
+ }
@@ -0,0 +1,251 @@
1
+ import type React from "react";
2
+ import type { EmotionEffectState } from "./emotion-effect-types";
3
+ import type { BackgroundData, BacklogEntry, BranchChoiceHistory, ConversationBranchState, DisplayedCharacter, ScenarioBlock, WorkFont } from "./types";
4
+ /**
5
+ * シナリオ再生の動的データ
6
+ */
7
+ export interface ScenarioPlaybackData {
8
+ /** 現在のブロックインデックス(0始まり) */
9
+ currentBlockIndex: number;
10
+ /** 総ブロック数 */
11
+ totalBlocks: number;
12
+ /** 現在のシナリオID */
13
+ scenarioId: string;
14
+ /** シナリオ名 */
15
+ scenarioName: string;
16
+ /** 現在のブロック */
17
+ currentBlock: ScenarioBlock | null;
18
+ /** 表示中のテキスト(改行は自動的に<br />に変換されます) */
19
+ displayText?: string | React.ReactNode;
20
+ /** テキストアニメーション中かどうか */
21
+ isTyping?: boolean;
22
+ /** 表示中のキャラクター */
23
+ displayedCharacters?: DisplayedCharacter[];
24
+ }
25
+ /**
26
+ * バックログデータ
27
+ */
28
+ export interface BacklogData {
29
+ /** バックログエントリ */
30
+ entries: BacklogEntry[];
31
+ /** 総エントリ数 */
32
+ totalEntries: number;
33
+ /** ログエントリを追加 */
34
+ addLogEntry?: (entry: BacklogEntry) => void;
35
+ /** ログをクリア */
36
+ clearLogs?: () => void;
37
+ }
38
+ /**
39
+ * プレイヤー設定データ
40
+ */
41
+ export interface PlayerSettingsData {
42
+ /** テキスト速度 (ミリ秒/文字) */
43
+ textSpeed: number;
44
+ /** 自動再生速度 (秒) */
45
+ autoPlaySpeed: number;
46
+ /** BGM音量 (0-1) */
47
+ bgmVolume: number;
48
+ /** SE音量 (0-1) */
49
+ seVolume: number;
50
+ /** ボイス音量 (0-1) */
51
+ voiceVolume: number;
52
+ /** スキップ設定 */
53
+ skipMode?: "all" | "unread" | "none";
54
+ /** 選択されたフォントファミリー(テキスト用) */
55
+ selectedFontFamily?: string;
56
+ /** 選択されたUIフォントファミリー(メニュー・ラベル等) */
57
+ selectedUIFontFamily?: string;
58
+ }
59
+ /**
60
+ * プラグインアセットデータ
61
+ */
62
+ export interface PluginAssetsData {
63
+ /**
64
+ * プラグインのアセットURLを取得
65
+ * @param pluginName - プラグインのパッケージ名
66
+ * @param filename - アセットファイル名
67
+ * @returns アセットのURL
68
+ */
69
+ getAssetUrl: (pluginName: string, filename: string) => string;
70
+ }
71
+ /**
72
+ * フォントデータ
73
+ */
74
+ export interface FontsData {
75
+ /** 利用可能なフォント一覧 */
76
+ fonts: WorkFont[];
77
+ /** 選択されているフォントファミリー(テキスト用) */
78
+ selectedFontFamily: string | undefined;
79
+ /** 選択されているUIフォントファミリー(メニュー・ラベル等) */
80
+ selectedUIFontFamily: string | undefined;
81
+ /** フォントが読み込み完了しているかどうか */
82
+ isLoaded: boolean;
83
+ }
84
+ /**
85
+ * 全データコンテキスト
86
+ *
87
+ * @remarks
88
+ * このインターフェースは、プラグインがDataAPI経由でアクセスできる全てのデータカテゴリを定義します。
89
+ * 各プロパティは、プラグインが特定のデータにアクセスする際のキーとして使用されます。
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // 現在のブロックインデックスを取得
94
+ * const blockIndex = api.data.get('playback', 'currentBlockIndex');
95
+ *
96
+ * // 再生データ全体を購読
97
+ * api.data.subscribe('playback', (data) => {
98
+ * console.log('Playback updated:', data);
99
+ * });
100
+ * ```
101
+ */
102
+ export interface DataContext {
103
+ /** シナリオ再生情報 */
104
+ playback: ScenarioPlaybackData;
105
+ /** バックログ情報 */
106
+ backlog: BacklogData;
107
+ /** プレイヤー設定 */
108
+ settings: PlayerSettingsData;
109
+ /** プラグインアセット */
110
+ pluginAssets: PluginAssetsData;
111
+ /** 会話分岐データ */
112
+ branchData?: ConversationBranchState;
113
+ /** 分岐履歴 */
114
+ branchHistory?: BranchChoiceHistory[];
115
+ /** 現在の背景データ(単一、後方互換性用) */
116
+ background?: BackgroundData | null;
117
+ /** 現在の背景データ(複数、背景グループ対応) */
118
+ backgrounds?: BackgroundData[];
119
+ /** フォント設定 */
120
+ fonts?: FontsData;
121
+ /**
122
+ * 【機能概要】: 感情エフェクトの表示状態
123
+ * 【実装方針】: オプショナルプロパティとして追加し、後方互換性を保つ
124
+ * 【テスト対応】: TC-007, TC-014を通すための実装
125
+ * 🔵 信頼性レベル: 要件定義書(行101-111)とnote.md(行244-251)で明確に定義されている
126
+ *
127
+ * @see EmotionEffectState - 完全な型定義は emotion-effect-types.ts を参照
128
+ */
129
+ emotionEffect?: EmotionEffectState | null;
130
+ }
131
+ /**
132
+ * データ購読コールバック
133
+ *
134
+ * @typeParam T - 購読するデータの型
135
+ */
136
+ export type DataSubscriber<T> = (data: T) => void;
137
+ /**
138
+ * データAPI - プラグインからのデータアクセスインターフェース
139
+ *
140
+ * @remarks
141
+ * このAPIは、プラグインがシナリオエンジンの状態データにアクセスするための2つのアプローチを提供します:
142
+ * - Pull型: `get()` メソッドで現在の値を取得
143
+ * - Push型: `subscribe()` / `watch()` メソッドで変更を監視
144
+ *
145
+ * @example Pull型(現在値の取得)
146
+ * ```typescript
147
+ * const blockIndex = api.data.get('playback', 'currentBlockIndex');
148
+ * const playback = api.data.get('playback');
149
+ * ```
150
+ *
151
+ * @example Push型(変更の監視)
152
+ * ```typescript
153
+ * const unsubscribe = api.data.subscribe('playback', (data) => {
154
+ * console.log('Playback updated:', data);
155
+ * });
156
+ * // cleanup
157
+ * unsubscribe();
158
+ * ```
159
+ *
160
+ * @example プロパティ単位での監視
161
+ * ```typescript
162
+ * api.data.watch('playback', 'currentBlockIndex', (index) => {
163
+ * console.log('Block changed to:', index);
164
+ * });
165
+ * ```
166
+ */
167
+ export interface DataAPI {
168
+ /**
169
+ * データの現在値を取得(Pull型)
170
+ * @param key - データカテゴリ
171
+ */
172
+ get<K extends keyof DataContext>(key: K): DataContext[K];
173
+ /**
174
+ * データの特定プロパティを取得(Pull型)
175
+ * @param key - データカテゴリ
176
+ * @param property - プロパティ名
177
+ */
178
+ get<K extends keyof DataContext, P extends keyof DataContext[K]>(key: K, property: P): DataContext[K][P];
179
+ /**
180
+ * データカテゴリ全体の変更を監視(Push型)
181
+ * @param key - データカテゴリ
182
+ * @param callback - 変更時のコールバック
183
+ * @returns unsubscribe関数
184
+ */
185
+ subscribe<K extends keyof DataContext>(key: K, callback: DataSubscriber<DataContext[K]>): () => void;
186
+ /**
187
+ * 特定プロパティの変更を監視(Push型)
188
+ * @param key - データカテゴリ
189
+ * @param property - プロパティ名
190
+ * @param callback - 変更時のコールバック
191
+ * @returns unsubscribe関数
192
+ */
193
+ watch<K extends keyof DataContext, P extends keyof DataContext[K]>(key: K, property: P, callback: DataSubscriber<DataContext[K][P]>): () => void;
194
+ /**
195
+ * プレイヤー設定を更新
196
+ * @param settings - 更新する設定値(部分更新可能)
197
+ */
198
+ updateSettings(settings: Partial<PlayerSettingsData>): void;
199
+ /**
200
+ * BGM音量を設定
201
+ * @param volume - 音量(0-1の範囲)
202
+ */
203
+ setBgmVolume(volume: number): void;
204
+ /**
205
+ * SE音量を設定
206
+ * @param volume - 音量(0-1の範囲)
207
+ */
208
+ setSeVolume(volume: number): void;
209
+ /**
210
+ * ボイス音量を設定
211
+ * @param volume - 音量(0-1の範囲)
212
+ */
213
+ setVoiceVolume(volume: number): void;
214
+ /**
215
+ * 全カテゴリの音量を一括設定
216
+ * @param volumes - カテゴリ別音量設定
217
+ */
218
+ setVolumes(volumes: {
219
+ bgm?: number;
220
+ se?: number;
221
+ voice?: number;
222
+ }): void;
223
+ /**
224
+ * 現在のブロックのオプション値を型安全に取得
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * // プラグイン側で型を定義
229
+ * type MyBlockOptions = {
230
+ * ultemist_bubble_style: "normal" | "monologue" | "star";
231
+ * ultemist_effect_type: "none" | "shake" | "fade";
232
+ * };
233
+ *
234
+ * // コンポーネントで使用
235
+ * const bubbleStyle = dataAPI.getBlockOption<MyBlockOptions, "ultemist_bubble_style">("ultemist_bubble_style");
236
+ * // 型: "normal" | "monologue" | "star" | undefined
237
+ *
238
+ * // または簡易版(キーのみ指定)
239
+ * const bubbleStyle = dataAPI.getBlockOption<MyBlockOptions>("ultemist_bubble_style");
240
+ * ```
241
+ *
242
+ * @param key - オプションのキー
243
+ * @returns オプションの値、または未設定の場合undefined
244
+ */
245
+ getBlockOption<T extends Record<string, unknown>, K extends keyof T = keyof T>(key: K): T[K] | undefined;
246
+ /**
247
+ * 感情エフェクトの状態を更新
248
+ * @param state - 更新する感情エフェクトの状態
249
+ */
250
+ updateEmotionEffect(state: EmotionEffectState | null): void;
251
+ }
@@ -0,0 +1,6 @@
1
+ // ============================================================================
2
+ // Data API Types - プラグインからのデータアクセス用型定義
3
+ // ============================================================================
4
+ // このファイルはDataAPIで使用される型定義を提供します
5
+ // SDK全体のファイルサイズを管理するために分離されています
6
+ export {};
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 【機能概要】: 7種類の感情エフェクトタイプを定義
3
+ * 【実装方針】: 文字列リテラル型のユニオン型として定義し、型安全性を保証
4
+ * 【テスト対応】: TC-001, TC-010を通すための実装
5
+ * 🔵 信頼性レベル: 要件定義書(sdk-datacontext-extension-requirements.md行60-62)とnote.md(行201-210)で明確に定義されている
6
+ */
7
+ export type EmotionType = "runrun" | "shonbori" | "kirakira" | "akire" | "heart" | "bikkuri" | "punpun";
8
+ /**
9
+ * 【機能概要】: エフェクト配置の基準位置タイプを定義
10
+ * 【実装方針】: キャラクター全体または顔を基準とする2択を文字列リテラル型で表現
11
+ * 【テスト対応】: TC-002, TC-011を通すための実装
12
+ * 🔵 信頼性レベル: 要件定義書(行64-67)とnote.md(行212-214)で明確に定義されている
13
+ */
14
+ export type PositionType = "character" | "face";
15
+ /**
16
+ * 【機能概要】: エフェクトの相対位置を定義
17
+ * 【実装方針】: 7種類の位置指定を文字列リテラル型で表現
18
+ * 【テスト対応】: TC-003, TC-012を通すための実装
19
+ * 🔵 信頼性レベル: 要件定義書(行69-72)とnote.md(行216-224)で明確に定義されている
20
+ */
21
+ export type EffectPosition = "above" | "below" | "left" | "right" | "center" | "top-left" | "bottom-left";
22
+ /**
23
+ * 【機能概要】: キャラクターの顔位置情報を定義
24
+ * 【実装方針】: 顔の中心座標と幅・高さを表現するインターフェース
25
+ * 【テスト対応】: TC-004, TC-009, TC-020を通すための実装
26
+ * 🔵 信頼性レベル: 要件定義書(行82-88)、note.md(行234-243)、既存のCharacterSpeakContext定義(sdk.ts行552-558)に基づく
27
+ */
28
+ export interface FacePosition {
29
+ /** 顔中心のX座標(キャラクター要素内の相対位置、単位: px) */
30
+ x: number;
31
+ /** 顔中心のY座標(キャラクター要素内の相対位置、単位: px) */
32
+ y: number;
33
+ /** 顔の幅(単位: px) */
34
+ width: number;
35
+ /** 顔の高さ(単位: px) */
36
+ height: number;
37
+ }
38
+ /**
39
+ * 【機能概要】: エフェクトの位置設定を統合的に管理
40
+ * 【実装方針】: 基準位置タイプ、相対位置、オフセット値を組み合わせた設定オブジェクト
41
+ * 【テスト対応】: TC-005, TC-015, TC-016, TC-017を通すための実装
42
+ * 🔵 信頼性レベル: 要件定義書(行74-80)とnote.md(行226-231)で明確に定義されている
43
+ */
44
+ export interface EmotionEffectPosition {
45
+ /** 基準位置タイプ(キャラクター全体または顔) */
46
+ positionType: PositionType;
47
+ /** エフェクトの相対位置 */
48
+ position: EffectPosition;
49
+ /** X軸オフセット(-500〜500px、デフォルト0) */
50
+ offsetX: number;
51
+ /** Y軸オフセット(-500〜500px、デフォルト0) */
52
+ offsetY: number;
53
+ }
54
+ /**
55
+ * 【機能概要】: 感情エフェクトの表示状態を包括的に管理
56
+ * 【実装方針】: エフェクト表示に必要な全ての状態情報を集約したインターフェース
57
+ * 【テスト対応】: TC-006, TC-008, TC-009, TC-013, TC-018, TC-021, TC-022を通すための実装
58
+ * 🔵 信頼性レベル: 要件定義書(行88-97)とnote.md(行187-198)で明確に定義されている
59
+ *
60
+ * @remarks
61
+ * このインターフェースは、感情エフェクトの表示に必要な全ての状態を表現します。
62
+ * - speaker と facePosition はオプショナルで、プラグインがキャラクター情報を必要とする場合に使用します
63
+ * - targetCharacterId が null の場合、現在の発話キャラクターを対象とします
64
+ * - position には基準位置タイプと相対位置、オフセット値が含まれます
65
+ */
66
+ export interface EmotionEffectState {
67
+ /** エフェクト表示が有効か(true: 表示、false: 非表示) */
68
+ enabled: boolean;
69
+ /** 感情タイプ(7種類のいずれか) */
70
+ emotionType: EmotionType;
71
+ /** 対象キャラクターID(nullは発話キャラクター) */
72
+ targetCharacterId: string | null;
73
+ /** エフェクト位置設定 */
74
+ position: EmotionEffectPosition;
75
+ /** 音声再生するか(true: 再生、false: 再生しない) */
76
+ playSound: boolean;
77
+ /** 発話キャラクター情報(オプショナル) */
78
+ speaker?: {
79
+ id: string;
80
+ name: string;
81
+ state?: string;
82
+ position?: number;
83
+ };
84
+ /** 顔位置情報(FacePosition型、オプショナル) */
85
+ facePosition?: FacePosition;
86
+ }
@@ -0,0 +1,6 @@
1
+ // ============================================================================
2
+ // Emotion Effect Types - 感情エフェクト関連の型定義
3
+ // ============================================================================
4
+ // このファイルは感情エフェクトプラグインで使用される型定義を提供します
5
+ // SDK全体のファイルサイズを管理するために分離されています
6
+ export {};
@@ -25,7 +25,6 @@ export function useBacklog({ scenario, currentBlockIndex, currentBlock, }) {
25
25
  .slice(0, upToIndex + 1)
26
26
  .map((block, index) => ({ block, realIndex: index }))
27
27
  .filter(({ block }) => block.blockType === "dialogue" || block.blockType === "narration");
28
- console.log(`Building conversation history up to index ${upToIndex}, found ${conversationBlocks.length} conversation blocks`);
29
28
  conversationBlocks.forEach(({ block, realIndex }) => {
30
29
  var _a, _b;
31
30
  if (!processedBlocks.current.has(realIndex)) {
@@ -3,9 +3,11 @@ import type { WorkFont } from "../types";
3
3
  * フォントを読み込むカスタムフック
4
4
  * - Googleフォント: <link>タグで読み込み + document.fonts.load()でプリロード
5
5
  * - カスタムフォント: FontFace APIで読み込み
6
- * - すべてのフォントがプリロード&キャッシュされてからisLoaded=trueになる
6
+ * - すべてのフォントが完全にダウンロード&キャッシュされてからisLoaded=trueになる
7
+ * - 再生開始前に全フォントを一括ダウンロードし、再生中のフォントスワップを防止する
8
+ * - preloadText: シナリオ内の全テキストを渡すと、使用される全ての文字でフォントをプリロード
7
9
  */
8
- export declare function useFontLoader(fonts: WorkFont[] | undefined): {
10
+ export declare function useFontLoader(fonts: WorkFont[] | undefined, preloadText?: string): {
9
11
  isLoaded: boolean;
10
12
  loadedFonts: string[];
11
13
  };
@@ -13,6 +15,11 @@ export declare function useFontLoader(fonts: WorkFont[] | undefined): {
13
15
  * フォントファミリーに基づいてCSSのfont-family値を生成
14
16
  */
15
17
  export declare function getFontFamilyStyle(selectedFontFamily: string | undefined, fonts: WorkFont[] | undefined): string;
18
+ /**
19
+ * UIフォントファミリーに基づいてCSSのfont-family値を生成
20
+ * メニュー、ボタン、ラベルなどのUI要素に使用
21
+ */
22
+ export declare function getUIFontFamilyStyle(selectedUIFontFamily: string | undefined, fonts: WorkFont[] | undefined): string;
16
23
  /**
17
24
  * フォントがキャッシュに存在するかチェック
18
25
  */