@codexo/exojs-react 0.14.0 → 0.15.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
@@ -37,7 +37,7 @@ function Game() {
37
37
  options={{ canvas: { width: 1280, height: 720 }, clearColor: someColor }}
38
38
  style={{ width: 1280, height: 720 }}
39
39
  >
40
- <Scenes active="game" transition={{ type: 'fade', duration: 0.3 }}>
40
+ <Scenes active="game" transition={{ type: 'fade', duration: 300 }}>
41
41
  <Scene name="title" component={TitleScene} />
42
42
  <Scene name="game" component={GameScene}>
43
43
  <Hud /> {/* absolutely-positioned React overlay, over the canvas */}
@@ -72,14 +72,15 @@ function Game() {
72
72
 
73
73
  | Export | Kind | Purpose |
74
74
  |---|---|---|
75
- | `ExoCanvas` | component | Batteries-included canvas host (wrapper div + canvas + context). |
76
- | `useExoApplication(options?, onReady?)` | hook | Headless: owns the `Application`, returns `{ app, canvasRef }`. |
75
+ | `ExoCanvas` | component | Batteries-included canvas host (wrapper div + canvas + context). Accepts `onReady`/`onError`. |
76
+ | `useExoApplication(options?, onReady?, onError?)` | hook | Headless: owns the `Application`, returns `{ app, canvasRef }`. `onError` mirrors `Application.onError`. |
77
77
  | `useExoApp()` | hook | The `Application` from the nearest `<ExoCanvas>`/provider. Throws if absent. |
78
78
  | `useExoContext()` | hook | Like `useExoApp` but returns `Application \| null` (no throw). |
79
79
  | `ExoContext` | context | The underlying context (advanced / testing). |
80
- | `useScene(SceneClass, deps?)` | hook | Instantiate + activate a single scene; returns it once live. |
81
- | `Scenes` / `Scene` | components | Declarative scene switch over the one-active-scene model. |
80
+ | `useScene(SceneClass, deps?)` | hook | Instantiate + activate a single scene; returns it once live. Load failures route to `app.onError`. |
81
+ | `Scenes` / `Scene` | components | Declarative scene switch over the one-active-scene model. Load failures route to `app.onError`. |
82
82
  | `useActiveScene()` | hook | The active scene instance from the nearest `<Scenes>`. |
83
+ | `useSignal(signal, getSnapshot)` | hook | Subscribes to an engine `Signal` and re-renders on every dispatch (e.g. `app.onFrame`). |
83
84
 
84
85
  ### Reactivity model
85
86
 
@@ -15,6 +15,13 @@ export interface ExoCanvasProps extends HTMLAttributes<HTMLDivElement> {
15
15
  * the first {@link import('./useScene').useScene} child calls `app.start()`.
16
16
  */
17
17
  onReady?: (app: Application) => void;
18
+ /**
19
+ * Called for every {@link Application.onError} dispatch — async init or
20
+ * scene-load failures (including the ones surfaced by
21
+ * {@link import('./useScene').useScene} and {@link import('./Scenes').Scenes}
22
+ * descendants) — while an Application exists.
23
+ */
24
+ onError?: (error: unknown) => void;
18
25
  /**
19
26
  * Props forwarded to the inner `<canvas>` (e.g. its own `style`/`className`).
20
27
  * `ref`, `width` and `height` are managed by the engine and cannot be set.
@@ -40,4 +47,4 @@ export interface ExoCanvasProps extends HTMLAttributes<HTMLDivElement> {
40
47
  * </ExoCanvas>
41
48
  * ```
42
49
  */
43
- export declare function ExoCanvas({ options, onReady, canvasProps, children, style, ...divProps }: ExoCanvasProps): ReactElement;
50
+ export declare function ExoCanvas({ options, onReady, onError, canvasProps, children, style, ...divProps }: ExoCanvasProps): ReactElement;
@@ -24,8 +24,8 @@ const defaultCanvasStyle = { display: 'block' };
24
24
  * </ExoCanvas>
25
25
  * ```
26
26
  */
27
- function ExoCanvas({ options, onReady, canvasProps, children, style, ...divProps }) {
28
- const { app, canvasRef } = useExoApplication(options, onReady);
27
+ function ExoCanvas({ options, onReady, onError, canvasProps, children, style, ...divProps }) {
28
+ const { app, canvasRef } = useExoApplication(options, onReady, onError);
29
29
  const { style: canvasStyle, ...restCanvasProps } = canvasProps ?? {};
30
30
  const wrapperStyle = { position: 'relative', ...style };
31
31
  const mergedCanvasStyle = { ...defaultCanvasStyle, ...canvasStyle };
@@ -1 +1 @@
1
- {"version":3,"file":"ExoCanvas.js","sources":["../../../src/ExoCanvas.tsx"],"sourcesContent":[null],"names":["_jsx","_jsxs"],"mappings":";;;;;AAMA;AACA,MAAM,kBAAkB,GAAkB,EAAE,OAAO,EAAE,OAAO,EAAE;AAuB9D;;;;;;;;;;;;;;;;;;AAkBG;SACa,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAkB,EAAA;AACvG,IAAA,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC;AAE9D,IAAA,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,GAAG,WAAW,IAAI,EAAE;IACpE,MAAM,YAAY,GAAkB,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE;IACtE,MAAM,iBAAiB,GAAkB,EAAE,GAAG,kBAAkB,EAAE,GAAG,WAAW,EAAE;AAElF,IAAA,QACEA,GAAA,CAAC,UAAU,CAAC,QAAQ,IAAC,KAAK,EAAE,GAAG,EAAA,QAAA,EAC7BC,cAAK,KAAK,EAAE,YAAY,EAAA,GAAM,QAAQ,EAAA,QAAA,EAAA,CACpCD,GAAA,CAAA,QAAA,EAAA,EAAQ,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,KAAM,eAAe,EAAA,CAAI,EACxE,GAAG,KAAK,IAAI,IAAI,QAAQ,CAAA,EAAA,CACrB,EAAA,CACc;AAE1B;;;;"}
1
+ {"version":3,"file":"ExoCanvas.js","sources":["../../../src/ExoCanvas.tsx"],"sourcesContent":[null],"names":["_jsx","_jsxs"],"mappings":";;;;;AAMA;AACA,MAAM,kBAAkB,GAAkB,EAAE,OAAO,EAAE,OAAO,EAAE;AA8B9D;;;;;;;;;;;;;;;;;;AAkBG;SACa,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAkB,EAAA;AAChH,IAAA,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;AAEvE,IAAA,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,GAAG,WAAW,IAAI,EAAE;IACpE,MAAM,YAAY,GAAkB,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE;IACtE,MAAM,iBAAiB,GAAkB,EAAE,GAAG,kBAAkB,EAAE,GAAG,WAAW,EAAE;AAElF,IAAA,QACEA,GAAA,CAAC,UAAU,CAAC,QAAQ,IAAC,KAAK,EAAE,GAAG,EAAA,QAAA,EAC7BC,cAAK,KAAK,EAAE,YAAY,EAAA,GAAM,QAAQ,EAAA,QAAA,EAAA,CACpCD,GAAA,CAAA,QAAA,EAAA,EAAQ,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,KAAM,eAAe,EAAA,CAAI,EACxE,GAAG,KAAK,IAAI,IAAI,QAAQ,CAAA,EAAA,CACrB,EAAA,CACc;AAE1B;;;;"}
@@ -38,6 +38,11 @@ export interface ScenesProps {
38
38
  * (HUD overlay) render alongside, and can read the instance via
39
39
  * {@link useActiveScene}.
40
40
  *
41
+ * A failure in `app.start()`/`app.scene.setScene()` (e.g. a scene's `onLoad`
42
+ * rejects) is caught and routed to {@link Application.onError} rather than
43
+ * left as an unhandled promise rejection — subscribe via `app.onError.add(...)`
44
+ * or the {@link import('./ExoCanvas').ExoCanvas} `onError` prop to observe it.
45
+ *
41
46
  * @example
42
47
  * ```tsx
43
48
  * <ExoCanvas>
@@ -30,6 +30,11 @@ function Scene(_props) {
30
30
  * (HUD overlay) render alongside, and can read the instance via
31
31
  * {@link useActiveScene}.
32
32
  *
33
+ * A failure in `app.start()`/`app.scene.setScene()` (e.g. a scene's `onLoad`
34
+ * rejects) is caught and routed to {@link Application.onError} rather than
35
+ * left as an unhandled promise rejection — subscribe via `app.onError.add(...)`
36
+ * or the {@link import('./ExoCanvas').ExoCanvas} `onError` prop to observe it.
37
+ *
33
38
  * @example
34
39
  * ```tsx
35
40
  * <ExoCanvas>
@@ -61,22 +66,32 @@ function Scenes({ active, transition, children }) {
61
66
  useEffect(() => {
62
67
  if (SceneClass === null) {
63
68
  setInstance(null);
64
- void app.scene.setScene(null);
69
+ void app.scene.setScene(null).catch((error) => {
70
+ app.onError.dispatch(error instanceof Error ? error : new Error(String(error)));
71
+ });
65
72
  return;
66
73
  }
67
74
  let cancelled = false;
68
75
  const scene = new SceneClass();
69
76
  const apply = async () => {
70
- if (app.status === ApplicationStatus.Stopped) {
71
- // First activation initializes the backend and starts the frame loop;
72
- // transitions only apply to subsequent switches.
73
- await app.start(scene);
74
- }
75
- else {
76
- await app.scene.setScene(scene, transition !== undefined ? { transition } : {});
77
+ try {
78
+ if (app.status === ApplicationStatus.Stopped) {
79
+ // First activation initializes the backend and starts the frame loop;
80
+ // transitions only apply to subsequent switches.
81
+ await app.start(scene);
82
+ }
83
+ else {
84
+ await app.scene.setScene(scene, transition !== undefined ? { transition } : {});
85
+ }
86
+ if (!cancelled) {
87
+ setInstance(scene);
88
+ }
77
89
  }
78
- if (!cancelled) {
79
- setInstance(scene);
90
+ catch (error) {
91
+ // Route to Application.onError instead of leaving an unhandled
92
+ // rejection — app.start()/setScene() reject rather than dispatching
93
+ // onError themselves.
94
+ app.onError.dispatch(error instanceof Error ? error : new Error(String(error)));
80
95
  }
81
96
  };
82
97
  void apply();
@@ -1 +1 @@
1
- {"version":3,"file":"Scenes.js","sources":["../../../src/Scenes.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":";;;;;AAeA;AACA,MAAM,kBAAkB,GAAG,aAAa,CAAkB,IAAI,CAAC;AAC/D,kBAAkB,CAAC,WAAW,GAAG,gBAAgB;AAEjD;;;;AAIG;SACa,cAAc,GAAA;AAC5B,IAAA,OAAO,UAAU,CAAC,kBAAkB,CAAa;AACnD;AAYA;;;;AAIG;AACG,SAAU,KAAK,CAAC,MAAkB,EAAA;AACtC,IAAA,OAAO,IAAI;AACb;AAYA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,SAAU,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAe,EAAA;AAClE,IAAA,MAAM,GAAG,GAAG,SAAS,EAAE;IACvB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAkB,IAAI,CAAC;;AAG/D,IAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAK;AAC5B,QAAA,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB;AACzC,QAAA,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,IAAG;YACjC,IAAI,cAAc,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE;AACjD,gBAAA,MAAM,KAAK,GAAG,KAAK,CAAC,KAAmB;gBACvC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;YAC5B;AACF,QAAA,CAAC,CAAC;AACF,QAAA,OAAO,GAAG;AACZ,IAAA,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IAEd,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;AAClC,IAAA,MAAM,UAAU,GAAG,KAAK,EAAE,SAAS,IAAI,IAAI;IAE3C,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,UAAU,KAAK,IAAI,EAAE;YACvB,WAAW,CAAC,IAAI,CAAC;YACjB,KAAK,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC7B;QACF;QAEA,IAAI,SAAS,GAAG,KAAK;AACrB,QAAA,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE;AAE9B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,IAAI,GAAG,CAAC,MAAM,KAAK,iBAAiB,CAAC,OAAO,EAAE;;;AAG5C,gBAAA,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;YACxB;iBAAO;gBACL,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,KAAK,SAAS,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;YACjF;YACA,IAAI,CAAC,SAAS,EAAE;gBACd,WAAW,CAAC,KAAK,CAAC;YACpB;AACF,QAAA,CAAC;QAED,KAAK,KAAK,EAAE;AAEZ,QAAA,OAAO,MAAK;YACV,SAAS,GAAG,IAAI;YAChB,WAAW,CAAC,IAAI,CAAC;AACnB,QAAA,CAAC;;;;AAIH,IAAA,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAEjB,IAAA,QACEA,GAAA,CAAC,kBAAkB,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,QAAQ,YACzC,QAAQ,KAAK,IAAI,IAAI,KAAK,EAAE,QAAQ,EAAA,CACT;AAElC;;;;"}
1
+ {"version":3,"file":"Scenes.js","sources":["../../../src/Scenes.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":"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"}
@@ -7,3 +7,4 @@ export { useExoApp } from './useExoApp';
7
7
  export type { ExoApplicationOptions, UseExoApplicationResult } from './useExoApplication';
8
8
  export { useExoApplication } from './useExoApplication';
9
9
  export { useScene } from './useScene';
10
+ export { useSignal } from './useSignal';
package/dist/esm/index.js CHANGED
@@ -4,4 +4,5 @@ export { Scene, Scenes, useActiveScene } from './Scenes.js';
4
4
  export { useExoApp } from './useExoApp.js';
5
5
  export { useExoApplication } from './useExoApplication.js';
6
6
  export { useScene } from './useScene.js';
7
+ export { useSignal } from './useSignal.js';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -6,11 +6,17 @@ import type { Application } from '@codexo/exojs';
6
6
  *
7
7
  * @throws {Error} When no `<ExoCanvas>` ancestor is present.
8
8
  *
9
+ * `app.frameCount` is a plain getter updated by the engine's own frame loop —
10
+ * reading it here does not, on its own, make `HudOverlay` re-render. Pair it
11
+ * with {@link import('./useSignal').useSignal} to subscribe to `app.onFrame`
12
+ * and re-render on every dispatch.
13
+ *
9
14
  * @example
10
15
  * ```tsx
11
16
  * function HudOverlay() {
12
17
  * const app = useExoApp();
13
- * return <span>Frame: {app.frameCount}</span>;
18
+ * const frameCount = useSignal(app.onFrame, () => app.frameCount);
19
+ * return <span>Frame: {frameCount}</span>;
14
20
  * }
15
21
  * ```
16
22
  */
@@ -7,11 +7,17 @@ import { useExoContext } from './ExoContext.js';
7
7
  *
8
8
  * @throws {Error} When no `<ExoCanvas>` ancestor is present.
9
9
  *
10
+ * `app.frameCount` is a plain getter updated by the engine's own frame loop —
11
+ * reading it here does not, on its own, make `HudOverlay` re-render. Pair it
12
+ * with {@link import('./useSignal').useSignal} to subscribe to `app.onFrame`
13
+ * and re-render on every dispatch.
14
+ *
10
15
  * @example
11
16
  * ```tsx
12
17
  * function HudOverlay() {
13
18
  * const app = useExoApp();
14
- * return <span>Frame: {app.frameCount}</span>;
19
+ * const frameCount = useSignal(app.onFrame, () => app.frameCount);
20
+ * return <span>Frame: {frameCount}</span>;
15
21
  * }
16
22
  * ```
17
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useExoApp.js","sources":["../../../src/useExoApp.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAIA;;;;;;;;;;;;;;AAcG;SACa,SAAS,GAAA;AACvB,IAAA,MAAM,GAAG,GAAG,aAAa,EAAE;AAE3B,IAAA,IAAI,GAAG,KAAK,IAAI,EAAE;AAChB,QAAA,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC;IAC5E;AAEA,IAAA,OAAO,GAAG;AACZ;;;;"}
1
+ {"version":3,"file":"useExoApp.js","sources":["../../../src/useExoApp.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAIA;;;;;;;;;;;;;;;;;;;;AAoBG;SACa,SAAS,GAAA;AACvB,IAAA,MAAM,GAAG,GAAG,aAAa,EAAE;AAE3B,IAAA,IAAI,GAAG,KAAK,IAAI,EAAE;AAChB,QAAA,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC;IAC5E;AAEA,IAAA,OAAO,GAAG;AACZ;;;;"}
@@ -53,5 +53,8 @@ export interface UseExoApplicationResult {
53
53
  *
54
54
  * @param options - Application options (the canvas element is the one you render).
55
55
  * @param onReady - Called once each time an Application is created.
56
+ * @param onError - Called for every {@link Application.onError} dispatch (async
57
+ * init/scene-load failures) while an Application exists. Re-subscribed
58
+ * automatically whenever the Application is (re)created.
56
59
  */
57
- export declare function useExoApplication(options?: ExoApplicationOptions, onReady?: (app: Application) => void): UseExoApplicationResult;
60
+ export declare function useExoApplication(options?: ExoApplicationOptions, onReady?: (app: Application) => void, onError?: (error: unknown) => void): UseExoApplicationResult;
@@ -36,8 +36,11 @@ function colorKey(color) {
36
36
  *
37
37
  * @param options - Application options (the canvas element is the one you render).
38
38
  * @param onReady - Called once each time an Application is created.
39
+ * @param onError - Called for every {@link Application.onError} dispatch (async
40
+ * init/scene-load failures) while an Application exists. Re-subscribed
41
+ * automatically whenever the Application is (re)created.
39
42
  */
40
- function useExoApplication(options, onReady) {
43
+ function useExoApplication(options, onReady, onError) {
41
44
  const canvasRef = useRef(null);
42
45
  const [app, setApp] = useState(null);
43
46
  // Latest onReady without retriggering the lifecycle effect. Updated in an
@@ -46,6 +49,11 @@ function useExoApplication(options, onReady) {
46
49
  useEffect(() => {
47
50
  onReadyRef.current = onReady;
48
51
  });
52
+ // Latest onError without retriggering the subscribe effect below.
53
+ const onErrorRef = useRef(onError);
54
+ useEffect(() => {
55
+ onErrorRef.current = onError;
56
+ });
49
57
  // Identity: only the backend type forces a full recreation.
50
58
  const backendKey = options?.backend?.type ?? 'auto';
51
59
  // ── Lifecycle: create on mount / recreate on backend change ───────────────
@@ -71,6 +79,19 @@ function useExoApplication(options, onReady) {
71
79
  // by the effects below. `options` is intentionally read at (re)create time.
72
80
  // eslint-disable-next-line react-hooks/exhaustive-deps
73
81
  }, [backendKey]);
82
+ // ── Live sync: error reporting ─────────────────────────────────────────────
83
+ useEffect(() => {
84
+ if (app === null) {
85
+ return;
86
+ }
87
+ const handleError = (error) => {
88
+ onErrorRef.current?.(error);
89
+ };
90
+ app.onError.add(handleError);
91
+ return () => {
92
+ app.onError.remove(handleError);
93
+ };
94
+ }, [app]);
74
95
  // ── Live sync: size ───────────────────────────────────────────────────────
75
96
  const width = options?.canvas?.width;
76
97
  const height = options?.canvas?.height;
@@ -1 +1 @@
1
- {"version":3,"file":"useExoApplication.js","sources":["../../../src/useExoApplication.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AA2BA;AACA,SAAS,QAAQ,CAAC,KAAwB,EAAA;IACxC,OAAO,KAAK,KAAK,SAAS,GAAG,SAAS,GAAG,CAAA,EAAG,KAAK,CAAC,CAAC,CAAA,CAAA,EAAI,KAAK,CAAC,CAAC,CAAA,CAAA,EAAI,KAAK,CAAC,CAAC,CAAA,CAAA,EAAI,KAAK,CAAC,CAAC,CAAA,CAAE;AACxF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;AACG,SAAU,iBAAiB,CAC/B,OAA+B,EAC/B,OAAoC,EAAA;AAEpC,IAAA,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC;;;AAIxD,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC;IAClC,SAAS,CAAC,MAAK;AACb,QAAA,UAAU,CAAC,OAAO,GAAG,OAAO;AAC9B,IAAA,CAAC,CAAC;;IAGF,MAAM,UAAU,GAAG,OAAO,EAAE,OAAO,EAAE,IAAI,IAAI,MAAM;;IAGnD,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO;QAChC,IAAI,CAAC,MAAM,EAAE;YACX;QACF;;;;AAKA,QAAA,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC;AAClC,YAAA,GAAG,OAAO;YACV,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;AAChD,SAAA,CAAC;QAEF,MAAM,CAAC,WAAW,CAAC;AACnB,QAAA,UAAU,CAAC,OAAO,GAAG,WAAW,CAAC;AAEjC,QAAA,OAAO,MAAK;YACV,WAAW,CAAC,OAAO,EAAE;YACrB,MAAM,CAAC,IAAI,CAAC;AACd,QAAA,CAAC;;;;AAIH,IAAA,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;;AAGhB,IAAA,MAAM,KAAK,GAAG,OAAO,EAAE,MAAM,EAAE,KAAK;AACpC,IAAA,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM;IACtC,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE;AAC/D,YAAA,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC;QAC3B;IACF,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;;AAGxB,IAAA,MAAM,UAAU,GAAG,OAAO,EAAE,MAAM,EAAE,UAAU;IAC9C,SAAS,CAAC,MAAK;QACb,IAAI,GAAG,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE;AAC5C,YAAA,GAAG,CAAC,UAAU,GAAG,UAAU;QAC7B;AACF,IAAA,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;;AAGrB,IAAA,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU;AACtC,IAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC;IACrC,SAAS,CAAC,MAAK;QACb,IAAI,GAAG,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE;AAC5C,YAAA,GAAG,CAAC,UAAU,GAAG,UAAU;QAC7B;;;AAGF,IAAA,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAEnB,IAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE;AAC3B;;;;"}
1
+ {"version":3,"file":"useExoApplication.js","sources":["../../../src/useExoApplication.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -12,6 +12,12 @@ import { type DependencyList } from 'react';
12
12
  * The scene is cleared (`setScene(null)`) when the component unmounts or
13
13
  * when `deps` change — mirroring `useEffect` semantics.
14
14
  *
15
+ * A failure in `app.start()`/`app.scene.setScene()` (e.g. a scene's `onLoad`
16
+ * rejects) is caught and routed to {@link Application.onError} rather than
17
+ * left as an unhandled promise rejection — subscribe via
18
+ * `app.onError.add(...)` or the {@link import('./ExoCanvas').ExoCanvas}
19
+ * `onError` prop to observe it.
20
+ *
15
21
  * @param SceneClass - Constructor for the scene to instantiate.
16
22
  * @param deps - Extra deps that trigger scene replacement when changed, in
17
23
  * addition to the stable `app` reference (same semantics as `useEffect`).
@@ -14,6 +14,12 @@ import { useExoApp } from './useExoApp.js';
14
14
  * The scene is cleared (`setScene(null)`) when the component unmounts or
15
15
  * when `deps` change — mirroring `useEffect` semantics.
16
16
  *
17
+ * A failure in `app.start()`/`app.scene.setScene()` (e.g. a scene's `onLoad`
18
+ * rejects) is caught and routed to {@link Application.onError} rather than
19
+ * left as an unhandled promise rejection — subscribe via
20
+ * `app.onError.add(...)` or the {@link import('./ExoCanvas').ExoCanvas}
21
+ * `onError` prop to observe it.
22
+ *
17
23
  * @param SceneClass - Constructor for the scene to instantiate.
18
24
  * @param deps - Extra deps that trigger scene replacement when changed, in
19
25
  * addition to the stable `app` reference (same semantics as `useEffect`).
@@ -36,16 +42,24 @@ function useScene(SceneClass, deps = []) {
36
42
  let cancelled = false;
37
43
  const s = new SceneClass();
38
44
  const apply = async () => {
39
- if (app.status === ApplicationStatus.Stopped) {
40
- // First activation initialize the backend and start the frame loop.
41
- await app.start(s);
42
- }
43
- else {
44
- // Engine already running — switch scenes without restarting.
45
- await app.scene.setScene(s);
45
+ try {
46
+ if (app.status === ApplicationStatus.Stopped) {
47
+ // First activation — initialize the backend and start the frame loop.
48
+ await app.start(s);
49
+ }
50
+ else {
51
+ // Engine already running — switch scenes without restarting.
52
+ await app.scene.setScene(s);
53
+ }
54
+ if (!cancelled) {
55
+ setScene(s);
56
+ }
46
57
  }
47
- if (!cancelled) {
48
- setScene(s);
58
+ catch (error) {
59
+ // Route to Application.onError instead of leaving an unhandled
60
+ // rejection — app.start()/setScene() reject rather than dispatching
61
+ // onError themselves.
62
+ app.onError.dispatch(error instanceof Error ? error : new Error(String(error)));
49
63
  }
50
64
  };
51
65
  void apply();
@@ -54,7 +68,9 @@ function useScene(SceneClass, deps = []) {
54
68
  setScene(null);
55
69
  // Best-effort scene clear; the Application.destroy() called by
56
70
  // ExoCanvas cleanup will also handle any remaining active scene.
57
- void app.scene.setScene(null);
71
+ void app.scene.setScene(null).catch((error) => {
72
+ app.onError.dispatch(error instanceof Error ? error : new Error(String(error)));
73
+ });
58
74
  };
59
75
  // SceneClass is intentionally excluded from deps: a new class reference
60
76
  // (e.g. inline arrow class) on every render would recreate the scene
@@ -1 +1 @@
1
- {"version":3,"file":"useScene.js","sources":["../../../src/useScene.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACH;SACgB,QAAQ,CAAkB,UAAuB,EAAE,OAAuB,EAAE,EAAA;AAC1F,IAAA,MAAM,GAAG,GAAG,SAAS,EAAE;IACvB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC;IAElD,SAAS,CAAC,MAAK;QACb,IAAI,SAAS,GAAG,KAAK;AACrB,QAAA,MAAM,CAAC,GAAG,IAAI,UAAU,EAAE;AAE1B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,IAAI,GAAG,CAAC,MAAM,KAAK,iBAAiB,CAAC,OAAO,EAAE;;AAE5C,gBAAA,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACpB;iBAAO;;gBAEL,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC7B;YAEA,IAAI,CAAC,SAAS,EAAE;gBACd,QAAQ,CAAC,CAAC,CAAC;YACb;AACF,QAAA,CAAC;QAED,KAAK,KAAK,EAAE;AAEZ,QAAA,OAAO,MAAK;YACV,SAAS,GAAG,IAAI;YAChB,QAAQ,CAAC,IAAI,CAAC;;;YAGd,KAAK,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;AAC/B,QAAA,CAAC;;;;IAIH,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAElB,IAAA,OAAO,KAAK;AACd;;;;"}
1
+ {"version":3,"file":"useScene.js","sources":["../../../src/useScene.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;AACH;SACgB,QAAQ,CAAkB,UAAuB,EAAE,OAAuB,EAAE,EAAA;AAC1F,IAAA,MAAM,GAAG,GAAG,SAAS,EAAE;IACvB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC;IAElD,SAAS,CAAC,MAAK;QACb,IAAI,SAAS,GAAG,KAAK;AACrB,QAAA,MAAM,CAAC,GAAG,IAAI,UAAU,EAAE;AAE1B,QAAA,MAAM,KAAK,GAAG,YAA0B;AACtC,YAAA,IAAI;gBACF,IAAI,GAAG,CAAC,MAAM,KAAK,iBAAiB,CAAC,OAAO,EAAE;;AAE5C,oBAAA,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;gBACpB;qBAAO;;oBAEL,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B;gBAEA,IAAI,CAAC,SAAS,EAAE;oBACd,QAAQ,CAAC,CAAC,CAAC;gBACb;YACF;YAAE,OAAO,KAAK,EAAE;;;;gBAId,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,YAAY,KAAK,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACjF;AACF,QAAA,CAAC;QAED,KAAK,KAAK,EAAE;AAEZ,QAAA,OAAO,MAAK;YACV,SAAS,GAAG,IAAI;YAChB,QAAQ,CAAC,IAAI,CAAC;;;AAGd,YAAA,KAAK,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,KAAI;gBACrD,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,YAAY,KAAK,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACjF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC;;;;IAIH,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAElB,IAAA,OAAO,KAAK;AACd;;;;"}
@@ -0,0 +1,28 @@
1
+ import type { Signal } from '@codexo/exojs';
2
+ /**
3
+ * Subscribes to an ExoJS {@link Signal} and returns the latest value computed by
4
+ * `getSnapshot`, re-rendering the component every time the signal dispatches.
5
+ * Built on `useSyncExternalStore`, so reads stay tear-free under concurrent
6
+ * rendering.
7
+ *
8
+ * The signal itself is only used to know *when* to re-read — `getSnapshot` is
9
+ * responsible for producing the actual value (usually a getter on the engine
10
+ * object the signal lives on).
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function FrameCounter() {
15
+ * const app = useExoApp();
16
+ * // Re-renders on every `onFrame` dispatch (i.e. every engine frame).
17
+ * const frameCount = useSignal(app.onFrame, () => app.frameCount);
18
+ * return <span>Frame: {frameCount}</span>;
19
+ * }
20
+ * ```
21
+ *
22
+ * @param signal - The signal to subscribe to. `null`/`undefined` is accepted
23
+ * (e.g. before an `Application` exists) — the hook simply does not subscribe
24
+ * to anything and `getSnapshot` still runs on every render.
25
+ * @param getSnapshot - Reads the current value. Called on mount and again after
26
+ * every dispatch of `signal`.
27
+ */
28
+ export declare function useSignal<Args extends unknown[], T>(signal: Signal<Args> | null | undefined, getSnapshot: () => T): T;
@@ -0,0 +1,45 @@
1
+ import { useCallback, useSyncExternalStore } from 'react';
2
+
3
+ /**
4
+ * Subscribes to an ExoJS {@link Signal} and returns the latest value computed by
5
+ * `getSnapshot`, re-rendering the component every time the signal dispatches.
6
+ * Built on `useSyncExternalStore`, so reads stay tear-free under concurrent
7
+ * rendering.
8
+ *
9
+ * The signal itself is only used to know *when* to re-read — `getSnapshot` is
10
+ * responsible for producing the actual value (usually a getter on the engine
11
+ * object the signal lives on).
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function FrameCounter() {
16
+ * const app = useExoApp();
17
+ * // Re-renders on every `onFrame` dispatch (i.e. every engine frame).
18
+ * const frameCount = useSignal(app.onFrame, () => app.frameCount);
19
+ * return <span>Frame: {frameCount}</span>;
20
+ * }
21
+ * ```
22
+ *
23
+ * @param signal - The signal to subscribe to. `null`/`undefined` is accepted
24
+ * (e.g. before an `Application` exists) — the hook simply does not subscribe
25
+ * to anything and `getSnapshot` still runs on every render.
26
+ * @param getSnapshot - Reads the current value. Called on mount and again after
27
+ * every dispatch of `signal`.
28
+ */
29
+ function useSignal(signal, getSnapshot) {
30
+ const subscribe = useCallback((onStoreChange) => {
31
+ if (!signal) {
32
+ // No signal to subscribe to (e.g. before an Application exists) — nothing to unsubscribe either.
33
+ // eslint-disable-next-line @typescript-eslint/no-empty-function -- intentional no-op unsubscribe
34
+ return () => { };
35
+ }
36
+ signal.add(onStoreChange);
37
+ return () => {
38
+ signal.remove(onStoreChange);
39
+ };
40
+ }, [signal]);
41
+ return useSyncExternalStore(subscribe, getSnapshot);
42
+ }
43
+
44
+ export { useSignal };
45
+ //# sourceMappingURL=useSignal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSignal.js","sources":["../../../src/useSignal.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,SAAS,CAA4B,MAAuC,EAAE,WAAoB,EAAA;AAChH,IAAA,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,aAAyB,KAAkB;QAC1C,IAAI,CAAC,MAAM,EAAE;;;AAGX,YAAA,OAAO,MAAK,EAAE,CAAC;QACjB;AAEA,QAAA,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;AAEzB,QAAA,OAAO,MAAK;AACV,YAAA,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;AAC9B,QAAA,CAAC;AACH,IAAA,CAAC,EACD,CAAC,MAAM,CAAC,CACT;AAED,IAAA,OAAO,oBAAoB,CAAC,SAAS,EAAE,WAAW,CAAC;AACrD;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexo/exojs-react",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "React integration for ExoJS.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,6 +8,7 @@
8
8
  "directory": "packages/exojs-react"
9
9
  },
10
10
  "type": "module",
11
+ "sideEffects": false,
11
12
  "main": "./dist/esm/index.js",
12
13
  "module": "./dist/esm/index.js",
13
14
  "types": "./dist/esm/index.d.ts",
@@ -25,7 +26,7 @@
25
26
  "LICENSE"
26
27
  ],
27
28
  "peerDependencies": {
28
- "@codexo/exojs": "0.14.x",
29
+ "@codexo/exojs": "0.15.x",
29
30
  "react": ">=18.0.0",
30
31
  "react-dom": ">=18.0.0"
31
32
  },
@@ -36,7 +37,7 @@
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/react": "^18.0.0",
39
- "@codexo/exojs": "0.14.0",
40
+ "@codexo/exojs": "0.15.0",
40
41
  "@codexo/exojs-config": "0.0.0"
41
42
  },
42
43
  "license": "MIT",