@xrift/world-components 0.3.0 → 0.5.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/README.md CHANGED
@@ -48,6 +48,59 @@ function MyWorld() {
48
48
  }
49
49
  ```
50
50
 
51
+ ### useInstanceState フック
52
+
53
+ インスタンス全体で同期される状態を管理するフックです。React の `useState` と同じAPIを提供します。
54
+
55
+ ```tsx
56
+ import { useInstanceState } from '@xrift/world-components'
57
+
58
+ function MyWorld() {
59
+ // インスタンス全体で同期される状態
60
+ const [buttonState, setButtonState] = useInstanceState('button-1', { enabled: false })
61
+
62
+ return (
63
+ <Interactable
64
+ id="button-1"
65
+ onInteract={() => {
66
+ // 状態を更新(全てのクライアントで同期される)
67
+ setButtonState({ enabled: !buttonState.enabled })
68
+ }}
69
+ >
70
+ <mesh>
71
+ <meshStandardMaterial color={buttonState.enabled ? 'green' : 'red'} />
72
+ </mesh>
73
+ </Interactable>
74
+ )
75
+ }
76
+ ```
77
+
78
+ #### 使用方法
79
+
80
+ ```tsx
81
+ const [state, setState] = useInstanceState<T>(stateId, initialState)
82
+ ```
83
+
84
+ - `stateId`: 状態の一意識別子(インスタンス内で一意である必要があります)
85
+ - `initialState`: 初期状態
86
+ - `setState`: 状態を更新する関数(直接値 or 関数型アップデートをサポート)
87
+
88
+ #### 関数型アップデート
89
+
90
+ ```tsx
91
+ // 直接値を設定
92
+ setState({ enabled: true })
93
+
94
+ // 前の状態を基に更新
95
+ setState(prev => ({ enabled: !prev.enabled }))
96
+ ```
97
+
98
+ #### 注意事項
99
+
100
+ - Context未設定時はローカル `useState` として動作します
101
+ - プラットフォーム側(xrift-frontend)がWebSocket実装を注入することで、インスタンス全体での同期が有効になります
102
+ - 状態はシリアライズ可能な値(JSON)である必要があります
103
+
51
104
  ### Interactable コンポーネント
52
105
 
53
106
  3Dオブジェクトをインタラクション可能にするラッパーコンポーネントです。
@@ -0,0 +1,49 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * インスタンス状態を管理するためのインターフェース
4
+ * プラットフォーム側(xrift-frontend)がWebSocket実装を注入する
5
+ */
6
+ export interface InstanceStateContextValue {
7
+ /**
8
+ * 全ての状態を保持するMap
9
+ * stateId -> 状態の値
10
+ */
11
+ states: Map<string, unknown>;
12
+ /**
13
+ * 状態を送信する関数
14
+ * @param stateId 状態の一意識別子
15
+ * @param payload 送信する状態
16
+ */
17
+ sendState: (stateId: string, payload: unknown) => void;
18
+ }
19
+ /**
20
+ * インスタンス状態を管理するContext
21
+ * ワールド作成者はこのContextを通じてインスタンス全体で同期される状態にアクセスする
22
+ */
23
+ export declare const InstanceStateContext: import("react").Context<InstanceStateContextValue>;
24
+ interface Props {
25
+ /**
26
+ * プラットフォーム側が提供する実装(WebSocket同期など)
27
+ * 未指定の場合はデフォルト実装(ローカルstate)が使用される
28
+ */
29
+ implementation?: InstanceStateContextValue;
30
+ children: ReactNode;
31
+ }
32
+ /**
33
+ * インスタンス状態を提供するContextProvider
34
+ * プラットフォーム側(xrift-frontend)がWebSocket実装を注入するために使用
35
+ *
36
+ * @example
37
+ * // プラットフォーム側での使用例
38
+ * <InstanceStateProvider implementation={webSocketImplementation}>
39
+ * <WorldComponent />
40
+ * </InstanceStateProvider>
41
+ */
42
+ export declare const InstanceStateProvider: ({ implementation, children }: Props) => import("react/jsx-runtime").JSX.Element;
43
+ /**
44
+ * インスタンス状態のContextを取得するhook
45
+ * 通常、ワールド作成者は直接このhookを使用せず、useInstanceStateを使用する
46
+ */
47
+ export declare const useInstanceStateContext: () => InstanceStateContextValue;
48
+ export {};
49
+ //# sourceMappingURL=InstanceStateContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InstanceStateContext.d.ts","sourceRoot":"","sources":["../../src/contexts/InstanceStateContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAEjE;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC5B;;;;OAIG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACvD;AAiBD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,oDAEhC,CAAA;AAED,UAAU,KAAK;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAA;IAC1C,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAAI,8BAA8B,KAAK,4CASxE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAO,yBAE1C,CAAA"}
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ /**
4
+ * デフォルト実装: Context未設定時はローカル状態として動作
5
+ * 開発時やテスト時に使用される
6
+ */
7
+ const createDefaultImplementation = () => {
8
+ const states = new Map();
9
+ return {
10
+ states,
11
+ sendState: (stateId, payload) => {
12
+ states.set(stateId, payload);
13
+ },
14
+ };
15
+ };
16
+ /**
17
+ * インスタンス状態を管理するContext
18
+ * ワールド作成者はこのContextを通じてインスタンス全体で同期される状態にアクセスする
19
+ */
20
+ export const InstanceStateContext = createContext(createDefaultImplementation());
21
+ /**
22
+ * インスタンス状態を提供するContextProvider
23
+ * プラットフォーム側(xrift-frontend)がWebSocket実装を注入するために使用
24
+ *
25
+ * @example
26
+ * // プラットフォーム側での使用例
27
+ * <InstanceStateProvider implementation={webSocketImplementation}>
28
+ * <WorldComponent />
29
+ * </InstanceStateProvider>
30
+ */
31
+ export const InstanceStateProvider = ({ implementation, children }) => {
32
+ const defaultImplementation = createDefaultImplementation();
33
+ const value = implementation || defaultImplementation;
34
+ return (_jsx(InstanceStateContext.Provider, { value: value, children: children }));
35
+ };
36
+ /**
37
+ * インスタンス状態のContextを取得するhook
38
+ * 通常、ワールド作成者は直接このhookを使用せず、useInstanceStateを使用する
39
+ */
40
+ export const useInstanceStateContext = () => {
41
+ return useContext(InstanceStateContext);
42
+ };
43
+ //# sourceMappingURL=InstanceStateContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InstanceStateContext.js","sourceRoot":"","sources":["../../src/contexts/InstanceStateContext.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAkB,UAAU,EAAE,MAAM,OAAO,CAAA;AAoBjE;;;GAGG;AACH,MAAM,2BAA2B,GAAG,GAA8B,EAAE;IAClE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAA;IAEzC,OAAO;QACL,MAAM;QACN,SAAS,EAAE,CAAC,OAAe,EAAE,OAAgB,EAAE,EAAE;YAC/C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC9B,CAAC;KACF,CAAA;AACH,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAC/C,2BAA2B,EAAE,CAC9B,CAAA;AAWD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAS,EAAE,EAAE;IAC3E,MAAM,qBAAqB,GAAG,2BAA2B,EAAE,CAAA;IAC3D,MAAM,KAAK,GAAG,cAAc,IAAI,qBAAqB,CAAA;IAErD,OAAO,CACL,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACxC,QAAQ,GACqB,CACjC,CAAA;AACH,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAA8B,EAAE;IACrE,OAAO,UAAU,CAAC,oBAAoB,CAAC,CAAA;AACzC,CAAC,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { Object3D } from 'three';
3
+ import { type InstanceStateContextValue } from './InstanceStateContext';
3
4
  export interface XRiftContextValue {
4
5
  /**
5
6
  * ワールドのベースURL(CDNのディレクトリパス)
@@ -33,6 +34,11 @@ export declare const XRiftContext: import("react").Context<XRiftContextValue | n
33
34
  interface Props {
34
35
  baseUrl: string;
35
36
  currentTarget?: Object3D | null;
37
+ /**
38
+ * インスタンス状態管理の実装(オプション)
39
+ * 指定しない場合はデフォルト実装(ローカルstate)が使用される
40
+ */
41
+ instanceStateImplementation?: InstanceStateContextValue;
36
42
  children: ReactNode;
37
43
  }
38
44
  /**
@@ -40,7 +46,7 @@ interface Props {
40
46
  * Module Federationで動的にロードされたワールドコンポーネントに
41
47
  * 必要な情報を注入するために使用
42
48
  */
43
- export declare const XRiftProvider: ({ baseUrl, currentTarget, children }: Props) => import("react/jsx-runtime").JSX.Element;
49
+ export declare const XRiftProvider: ({ baseUrl, currentTarget, instanceStateImplementation, children }: Props) => import("react/jsx-runtime").JSX.Element;
44
50
  /**
45
51
  * XRift ワールドの情報を取得するhook
46
52
  * ワールドプロジェクト側でアセットの相対パスを絶対パスに変換する際に使用
@@ -1 +1 @@
1
- {"version":3,"file":"XRiftContext.d.ts","sourceRoot":"","sources":["../../src/contexts/XRiftContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAqC,MAAM,OAAO,CAAA;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAErC,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,aAAa,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC9B;;;OAGG;IACH,mBAAmB,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;IAClC;;OAEG;IACH,oBAAoB,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAA;IAChD;;OAEG;IACH,sBAAsB,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAA;CAKnD;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,mDAAgD,CAAA;AAEzE,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,sCAA6C,KAAK,4CA2B/E,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,QAAO,iBAQ3B,CAAA"}
1
+ {"version":3,"file":"XRiftContext.d.ts","sourceRoot":"","sources":["../../src/contexts/XRiftContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAqC,MAAM,OAAO,CAAA;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAyB,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAA;AAE9F,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,aAAa,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC9B;;;OAGG;IACH,mBAAmB,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;IAClC;;OAEG;IACH,oBAAoB,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAA;IAChD;;OAEG;IACH,sBAAsB,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAA;CAKnD;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,mDAAgD,CAAA;AAEzE,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,2BAA2B,CAAC,EAAE,yBAAyB,CAAA;IACvD,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,mEAK3B,KAAK,4CA6BP,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,QAAO,iBAQ3B,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useState } from 'react';
3
+ import { InstanceStateProvider } from './InstanceStateContext';
3
4
  /**
4
5
  * XRift ワールドの情報を提供するContext
5
6
  * ワールド側でこのContextを直接参照して情報を取得できる
@@ -10,7 +11,7 @@ export const XRiftContext = createContext(null);
10
11
  * Module Federationで動的にロードされたワールドコンポーネントに
11
12
  * 必要な情報を注入するために使用
12
13
  */
13
- export const XRiftProvider = ({ baseUrl, currentTarget = null, children }) => {
14
+ export const XRiftProvider = ({ baseUrl, currentTarget = null, instanceStateImplementation, children }) => {
14
15
  // インタラクト可能なオブジェクトの管理
15
16
  const [interactableObjects] = useState(() => new Set());
16
17
  // オブジェクトの登録
@@ -27,7 +28,7 @@ export const XRiftProvider = ({ baseUrl, currentTarget = null, children }) => {
27
28
  interactableObjects,
28
29
  registerInteractable,
29
30
  unregisterInteractable,
30
- }, children: children }));
31
+ }, children: _jsx(InstanceStateProvider, { implementation: instanceStateImplementation, children: children }) }));
31
32
  };
32
33
  /**
33
34
  * XRift ワールドの情報を取得するhook
@@ -1 +1 @@
1
- {"version":3,"file":"XRiftContext.js","sourceRoot":"","sources":["../../src/contexts/XRiftContext.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAkB,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAiCxF;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAA2B,IAAI,CAAC,CAAA;AAQzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,EAAE,QAAQ,EAAS,EAAE,EAAE;IAClF,qBAAqB;IACrB,MAAM,CAAC,mBAAmB,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,EAAY,CAAC,CAAA;IAEjE,YAAY;IACZ,MAAM,oBAAoB,GAAG,WAAW,CAAC,CAAC,MAAgB,EAAE,EAAE;QAC5D,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAA;IAEzB,cAAc;IACd,MAAM,sBAAsB,GAAG,WAAW,CAAC,CAAC,MAAgB,EAAE,EAAE;QAC9D,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAA;IAEzB,OAAO,CACL,KAAC,YAAY,CAAC,QAAQ,IACpB,KAAK,EAAE;YACL,OAAO;YACP,aAAa;YACb,mBAAmB;YACnB,oBAAoB;YACpB,sBAAsB;SACvB,YAEA,QAAQ,GACa,CACzB,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAsB,EAAE;IAC9C,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IAExC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
1
+ {"version":3,"file":"XRiftContext.js","sourceRoot":"","sources":["../../src/contexts/XRiftContext.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAkB,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExF,OAAO,EAAE,qBAAqB,EAAkC,MAAM,wBAAwB,CAAA;AAgC9F;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAA2B,IAAI,CAAC,CAAA;AAazE;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,OAAO,EACP,aAAa,GAAG,IAAI,EACpB,2BAA2B,EAC3B,QAAQ,EACF,EAAE,EAAE;IACV,qBAAqB;IACrB,MAAM,CAAC,mBAAmB,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,EAAY,CAAC,CAAA;IAEjE,YAAY;IACZ,MAAM,oBAAoB,GAAG,WAAW,CAAC,CAAC,MAAgB,EAAE,EAAE;QAC5D,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAA;IAEzB,cAAc;IACd,MAAM,sBAAsB,GAAG,WAAW,CAAC,CAAC,MAAgB,EAAE,EAAE;QAC9D,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAA;IAEzB,OAAO,CACL,KAAC,YAAY,CAAC,QAAQ,IACpB,KAAK,EAAE;YACL,OAAO;YACP,aAAa;YACb,mBAAmB;YACnB,oBAAoB;YACpB,sBAAsB;SACvB,YAED,KAAC,qBAAqB,IAAC,cAAc,EAAE,2BAA2B,YAC/D,QAAQ,GACa,GACF,CACzB,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAsB,EAAE;IAC9C,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IAExC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * インスタンス全体で同期される状態を管理するhook
3
+ *
4
+ * React の useState と同じAPIを提供します。
5
+ * Context未設定時はローカルstateとして動作し、
6
+ * プラットフォーム側が実装を注入することでWebSocket同期などが可能になります。
7
+ *
8
+ * @param stateId 状態の一意識別子(インスタンス内で一意である必要があります)
9
+ * @param initialState 初期状態
10
+ * @returns [現在の状態, 状態更新関数]
11
+ *
12
+ * @example
13
+ * // ボタンの有効/無効状態を管理
14
+ * const [buttonState, setButtonState] = useInstanceState('button-1', { enabled: false })
15
+ *
16
+ * // 直接値を設定
17
+ * setButtonState({ enabled: true })
18
+ *
19
+ * // 関数型アップデート
20
+ * setButtonState(prev => ({ enabled: !prev.enabled }))
21
+ *
22
+ * @example
23
+ * // より複雑な状態の管理
24
+ * interface DoorState {
25
+ * isOpen: boolean
26
+ * openedBy: string | null
27
+ * }
28
+ *
29
+ * const [doorState, setDoorState] = useInstanceState<DoorState>('main-door', {
30
+ * isOpen: false,
31
+ * openedBy: null
32
+ * })
33
+ *
34
+ * // ドアを開く
35
+ * setDoorState({ isOpen: true, openedBy: 'player-123' })
36
+ */
37
+ export declare function useInstanceState<T>(stateId: string, initialState: T): [T, (state: T | ((prevState: T) => T)) => void];
38
+ //# sourceMappingURL=useInstanceState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInstanceState.d.ts","sourceRoot":"","sources":["../../src/hooks/useInstanceState.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,CAAC,GACd,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAgCjD"}
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useInstanceStateContext } from '../contexts/InstanceStateContext';
3
+ /**
4
+ * インスタンス全体で同期される状態を管理するhook
5
+ *
6
+ * React の useState と同じAPIを提供します。
7
+ * Context未設定時はローカルstateとして動作し、
8
+ * プラットフォーム側が実装を注入することでWebSocket同期などが可能になります。
9
+ *
10
+ * @param stateId 状態の一意識別子(インスタンス内で一意である必要があります)
11
+ * @param initialState 初期状態
12
+ * @returns [現在の状態, 状態更新関数]
13
+ *
14
+ * @example
15
+ * // ボタンの有効/無効状態を管理
16
+ * const [buttonState, setButtonState] = useInstanceState('button-1', { enabled: false })
17
+ *
18
+ * // 直接値を設定
19
+ * setButtonState({ enabled: true })
20
+ *
21
+ * // 関数型アップデート
22
+ * setButtonState(prev => ({ enabled: !prev.enabled }))
23
+ *
24
+ * @example
25
+ * // より複雑な状態の管理
26
+ * interface DoorState {
27
+ * isOpen: boolean
28
+ * openedBy: string | null
29
+ * }
30
+ *
31
+ * const [doorState, setDoorState] = useInstanceState<DoorState>('main-door', {
32
+ * isOpen: false,
33
+ * openedBy: null
34
+ * })
35
+ *
36
+ * // ドアを開く
37
+ * setDoorState({ isOpen: true, openedBy: 'player-123' })
38
+ */
39
+ export function useInstanceState(stateId, initialState) {
40
+ const { states, sendState } = useInstanceStateContext();
41
+ // ローカル状態を保持して即座に更新を反映
42
+ const [localState, setLocalState] = useState(() => {
43
+ return states.get(stateId) ?? initialState;
44
+ });
45
+ // グローバル状態が変更されたらローカル状態を更新
46
+ useEffect(() => {
47
+ const globalState = states.get(stateId);
48
+ if (globalState !== undefined) {
49
+ setLocalState(globalState);
50
+ }
51
+ }, [states, stateId]);
52
+ // 関数型setStateをサポート(React の useState と同じAPI)
53
+ const setState = (newStateOrUpdater) => {
54
+ setLocalState(prev => {
55
+ // 関数の場合は前の状態を渡して新しい状態を計算
56
+ const newState = typeof newStateOrUpdater === 'function'
57
+ ? newStateOrUpdater(prev)
58
+ : newStateOrUpdater;
59
+ // グローバルに送信
60
+ sendState(stateId, newState);
61
+ return newState;
62
+ });
63
+ };
64
+ return [localState, setState];
65
+ }
66
+ //# sourceMappingURL=useInstanceState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInstanceState.js","sourceRoot":"","sources":["../../src/hooks/useInstanceState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAA;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,YAAe;IAEf,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,uBAAuB,EAAE,CAAA;IAEvD,sBAAsB;IACtB,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAI,GAAG,EAAE;QACnD,OAAQ,MAAM,CAAC,GAAG,CAAC,OAAO,CAAmB,IAAI,YAAY,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEF,0BAA0B;IAC1B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAkB,CAAA;QACxD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,aAAa,CAAC,WAAW,CAAC,CAAA;QAC5B,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAErB,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,CAAC,iBAA4C,EAAE,EAAE;QAChE,aAAa,CAAC,IAAI,CAAC,EAAE;YACnB,yBAAyB;YACzB,MAAM,QAAQ,GAAG,OAAO,iBAAiB,KAAK,UAAU;gBACtD,CAAC,CAAE,iBAAyC,CAAC,IAAI,CAAC;gBAClD,CAAC,CAAC,iBAAiB,CAAA;YAErB,WAAW;YACX,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;YAE5B,OAAO,QAAQ,CAAA;QACjB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;AAC/B,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { XRiftContext, XRiftProvider, useXRift, type XRiftContextValue, } from './contexts/XRiftContext';
2
+ export { InstanceStateContext, useInstanceStateContext, type InstanceStateContextValue, } from './contexts/InstanceStateContext';
2
3
  export { Interactable, type InteractableProps, } from './components/Interactable';
4
+ export { useInstanceState } from './hooks/useInstanceState';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,yBAAyB,GAC/B,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // Contexts
2
2
  export { XRiftContext, XRiftProvider, useXRift, } from './contexts/XRiftContext';
3
+ export { InstanceStateContext, useInstanceStateContext, } from './contexts/InstanceStateContext';
3
4
  // Components
4
5
  export { Interactable, } from './components/Interactable';
6
+ // Hooks
7
+ export { useInstanceState } from './hooks/useInstanceState';
5
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,GAET,MAAM,yBAAyB,CAAA;AAEhC,aAAa;AACb,OAAO,EACL,YAAY,GAEb,MAAM,2BAA2B,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,GAET,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GAExB,MAAM,iCAAiC,CAAA;AAExC,aAAa;AACb,OAAO,EACL,YAAY,GAEb,MAAM,2BAA2B,CAAA;AAElC,QAAQ;AACR,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrift/world-components",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared components and utilities for Xrift worlds",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",