@codexo/exojs-react 0.15.2 → 0.16.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 +16 -16
- package/dist/esm/ExoCanvas.d.ts +11 -10
- package/dist/esm/ExoCanvas.d.ts.map +1 -0
- package/dist/esm/ExoCanvas.js +48 -29
- package/dist/esm/ExoCanvas.js.map +1 -1
- package/dist/esm/ExoContext.d.ts +4 -3
- package/dist/esm/ExoContext.d.ts.map +1 -0
- package/dist/esm/ExoContext.js +16 -17
- package/dist/esm/ExoContext.js.map +1 -1
- package/dist/esm/Scenes.d.ts +15 -11
- package/dist/esm/Scenes.d.ts.map +1 -0
- package/dist/esm/Scenes.js +84 -96
- package/dist/esm/Scenes.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +9 -8
- package/dist/esm/useExoApp.d.ts +3 -2
- package/dist/esm/useExoApp.d.ts.map +1 -0
- package/dist/esm/useExoApp.js +29 -29
- package/dist/esm/useExoApp.js.map +1 -1
- package/dist/esm/useExoApplication.d.ts +13 -8
- package/dist/esm/useExoApplication.d.ts.map +1 -0
- package/dist/esm/useExoApplication.js +102 -119
- package/dist/esm/useExoApplication.js.map +1 -1
- package/dist/esm/useScene.d.ts +18 -11
- package/dist/esm/useScene.d.ts.map +1 -0
- package/dist/esm/useScene.js +72 -79
- package/dist/esm/useScene.js.map +1 -1
- package/dist/esm/useSignal.d.ts +4 -3
- package/dist/esm/useSignal.d.ts.map +1 -0
- package/dist/esm/useSignal.js +39 -41
- package/dist/esm/useSignal.js.map +1 -1
- package/package.json +13 -9
- package/dist/esm/index.js.map +0 -1
package/dist/esm/useExoApp.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ 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
|
|
9
|
+
* `app.frameCount` is a plain getter updated by the engine's own frame loop -
|
|
10
10
|
* reading it here does not, on its own, make `HudOverlay` re-render. Pair it
|
|
11
11
|
* with {@link import('./useSignal').useSignal} to subscribe to `app.onFrame`
|
|
12
12
|
* and re-render on every dispatch.
|
|
@@ -20,4 +20,5 @@ import type { Application } from '@codexo/exojs';
|
|
|
20
20
|
* }
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
|
-
export declare
|
|
23
|
+
export declare const useExoApp: () => Application;
|
|
24
|
+
//# sourceMappingURL=useExoApp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExoApp.d.ts","sourceRoot":"","sources":["../../src/useExoApp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS,QAAO,WAQ5B,CAAC"}
|
package/dist/esm/useExoApp.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { useExoContext } from
|
|
1
|
+
import { useExoContext } from "./ExoContext.js";
|
|
2
2
|
|
|
3
|
+
//#region src/useExoApp.ts
|
|
3
4
|
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return app;
|
|
30
|
-
}
|
|
5
|
+
* Returns the {@link Application} instance from the nearest {@link ExoCanvas}
|
|
6
|
+
* ancestor. Throws an informative error when called outside of an
|
|
7
|
+
* `<ExoCanvas>` tree.
|
|
8
|
+
*
|
|
9
|
+
* @throws {Error} When no `<ExoCanvas>` ancestor is present.
|
|
10
|
+
*
|
|
11
|
+
* `app.frameCount` is a plain getter updated by the engine's own frame loop -
|
|
12
|
+
* reading it here does not, on its own, make `HudOverlay` re-render. Pair it
|
|
13
|
+
* with {@link import('./useSignal').useSignal} to subscribe to `app.onFrame`
|
|
14
|
+
* and re-render on every dispatch.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function HudOverlay() {
|
|
19
|
+
* const app = useExoApp();
|
|
20
|
+
* const frameCount = useSignal(app.onFrame, () => app.frameCount);
|
|
21
|
+
* return <span>Frame: {frameCount}</span>;
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
const useExoApp = () => {
|
|
26
|
+
const app = useExoContext();
|
|
27
|
+
if (app === null) throw new Error("useExoApp must be used inside an <ExoCanvas> component.");
|
|
28
|
+
return app;
|
|
29
|
+
};
|
|
31
30
|
|
|
31
|
+
//#endregion
|
|
32
32
|
export { useExoApp };
|
|
33
|
-
//# sourceMappingURL=useExoApp.js.map
|
|
33
|
+
//# sourceMappingURL=useExoApp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExoApp.js","sources":["
|
|
1
|
+
{"version":3,"file":"useExoApp.js","names":[],"sources":["../../src/useExoApp.ts"],"sourcesContent":["import type { Application } from '@codexo/exojs';\n\nimport { useExoContext } from './ExoContext';\n\n/**\n * Returns the {@link Application} instance from the nearest {@link ExoCanvas}\n * ancestor. Throws an informative error when called outside of an\n * `<ExoCanvas>` tree.\n *\n * @throws {Error} When no `<ExoCanvas>` ancestor is present.\n *\n * `app.frameCount` is a plain getter updated by the engine's own frame loop -\n * reading it here does not, on its own, make `HudOverlay` re-render. Pair it\n * with {@link import('./useSignal').useSignal} to subscribe to `app.onFrame`\n * and re-render on every dispatch.\n *\n * @example\n * ```tsx\n * function HudOverlay() {\n * const app = useExoApp();\n * const frameCount = useSignal(app.onFrame, () => app.frameCount);\n * return <span>Frame: {frameCount}</span>;\n * }\n * ```\n */\nexport const useExoApp = (): Application => {\n const app = useExoContext();\n\n if (app === null) {\n throw new Error('useExoApp must be used inside an <ExoCanvas> component.');\n }\n\n return app;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,kBAA+B;CAC1C,MAAM,MAAM,cAAc;CAE1B,IAAI,QAAQ,MACV,MAAM,IAAI,MAAM,yDAAyD;CAG3E,OAAO;AACT"}
|
|
@@ -6,7 +6,7 @@ import { type Ref } from 'react';
|
|
|
6
6
|
* Same as {@link ApplicationOptions} but the `canvas.element` and `canvas.mount`
|
|
7
7
|
* fields are managed for you (the Application binds to the canvas the hook
|
|
8
8
|
* references), so they are omitted. You may still pass
|
|
9
|
-
* `canvas.width`/`height`/`
|
|
9
|
+
* `canvas.width`/`height`/`sizing`/etc.
|
|
10
10
|
*/
|
|
11
11
|
export type ExoApplicationOptions = Omit<ApplicationOptions, 'canvas'> & {
|
|
12
12
|
readonly canvas?: Omit<CanvasApplicationOptions, 'element' | 'mount'>;
|
|
@@ -25,7 +25,7 @@ export interface UseExoApplicationResult {
|
|
|
25
25
|
/**
|
|
26
26
|
* Creates and owns an ExoJS {@link Application}, binding it to a `<canvas>` you
|
|
27
27
|
* render yourself and attach the returned `canvasRef` to. The hook renders no
|
|
28
|
-
* DOM of its own
|
|
28
|
+
* DOM of its own - you keep full control over the canvas element, its container,
|
|
29
29
|
* and its styling.
|
|
30
30
|
*
|
|
31
31
|
* ```tsx
|
|
@@ -36,20 +36,24 @@ export interface UseExoApplicationResult {
|
|
|
36
36
|
* ```
|
|
37
37
|
*
|
|
38
38
|
* **Reactivity model.** The Application is recreated only when an *identity*
|
|
39
|
-
* option changes
|
|
39
|
+
* option changes - currently the render `backend` (you cannot hot-swap WebGL2 ↔
|
|
40
40
|
* WebGPU). All other supported options are applied *live* without tearing the
|
|
41
41
|
* app down:
|
|
42
42
|
*
|
|
43
43
|
* - `canvas.width` / `canvas.height` → `app.resize(...)`
|
|
44
|
-
* - `canvas.sizingMode` → `app.sizingMode`
|
|
45
44
|
* - `clearColor` → `app.clearColor`
|
|
46
45
|
*
|
|
47
46
|
* Options without a live setter (e.g. `canvas.pixelRatio`, `seed`, `extensions`)
|
|
48
47
|
* are captured at creation; change the `backend` or remount to apply them.
|
|
49
48
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
49
|
+
* `canvas.sizing` is captured at creation too, deliberately: a policy is an
|
|
50
|
+
* object, so a new instance on every render would detach and re-attach the
|
|
51
|
+
* previous one each time. Assign `app.sizing` yourself to switch strategies.
|
|
52
|
+
*
|
|
53
|
+
* Styling note: with no `canvas.sizing` the engine keeps the canvas at the base
|
|
54
|
+
* resolution and writes only that size, so you may style it freely. A sizing
|
|
55
|
+
* policy owns the canvas's `width`/`height` styles - don't fight it with a
|
|
56
|
+
* `style` prop.
|
|
53
57
|
*
|
|
54
58
|
* @param options - Application options (the canvas element is the one you render).
|
|
55
59
|
* @param onReady - Called once each time an Application is created.
|
|
@@ -57,4 +61,5 @@ export interface UseExoApplicationResult {
|
|
|
57
61
|
* init/scene-load failures) while an Application exists. Re-subscribed
|
|
58
62
|
* automatically whenever the Application is (re)created.
|
|
59
63
|
*/
|
|
60
|
-
export declare
|
|
64
|
+
export declare const useExoApplication: (options?: ExoApplicationOptions, onReady?: (app: Application) => void, onError?: (error: unknown) => void) => UseExoApplicationResult;
|
|
65
|
+
//# sourceMappingURL=useExoApplication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExoApplication.d.ts","sourceRoot":"","sources":["../../src/useExoApplication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAAc,MAAM,eAAe,CAAC;AAChH,OAAO,EAAE,KAAK,GAAG,EAA+B,MAAM,OAAO,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,GAAG;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,OAAO,CAAC,CAAC;CACvE,CAAC;AAEF,iDAAiD;AACjD,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;CAC5C;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,iBAAiB,GAC5B,UAAU,qBAAqB,EAC/B,UAAU,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,EACpC,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KACjC,uBAwFF,CAAC"}
|
|
@@ -1,124 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Application } from "@codexo/exojs";
|
|
3
3
|
|
|
4
|
+
//#region src/useExoApplication.ts
|
|
4
5
|
/** Stable string key for the colour so the sync effect can depend on its value. */
|
|
5
|
-
|
|
6
|
-
return color === undefined ? undefined : `${color.r},${color.g},${color.b},${color.a}`;
|
|
7
|
-
}
|
|
6
|
+
const colorKey = (color) => color === void 0 ? void 0 : `${color.r},${color.g},${color.b},${color.a}`;
|
|
8
7
|
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (app !== null && sizingMode !== undefined) {
|
|
107
|
-
app.sizingMode = sizingMode;
|
|
108
|
-
}
|
|
109
|
-
}, [app, sizingMode]);
|
|
110
|
-
// ── Live sync: clear colour ───────────────────────────────────────────────
|
|
111
|
-
const clearColor = options?.clearColor;
|
|
112
|
-
const clearKey = colorKey(clearColor);
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (app !== null && clearColor !== undefined) {
|
|
115
|
-
app.clearColor = clearColor;
|
|
116
|
-
}
|
|
117
|
-
// clearColor identity is unstable; depend on its value key instead.
|
|
118
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
-
}, [app, clearKey]);
|
|
120
|
-
return { app, canvasRef };
|
|
121
|
-
}
|
|
8
|
+
* Creates and owns an ExoJS {@link Application}, binding it to a `<canvas>` you
|
|
9
|
+
* render yourself and attach the returned `canvasRef` to. The hook renders no
|
|
10
|
+
* DOM of its own - you keep full control over the canvas element, its container,
|
|
11
|
+
* and its styling.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function Game() {
|
|
15
|
+
* const { app, canvasRef } = useExoApplication({ canvas: { width: 800, height: 600 } });
|
|
16
|
+
* return <canvas ref={canvasRef} className="game" />;
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* **Reactivity model.** The Application is recreated only when an *identity*
|
|
21
|
+
* option changes - currently the render `backend` (you cannot hot-swap WebGL2 ↔
|
|
22
|
+
* WebGPU). All other supported options are applied *live* without tearing the
|
|
23
|
+
* app down:
|
|
24
|
+
*
|
|
25
|
+
* - `canvas.width` / `canvas.height` → `app.resize(...)`
|
|
26
|
+
* - `clearColor` → `app.clearColor`
|
|
27
|
+
*
|
|
28
|
+
* Options without a live setter (e.g. `canvas.pixelRatio`, `seed`, `extensions`)
|
|
29
|
+
* are captured at creation; change the `backend` or remount to apply them.
|
|
30
|
+
*
|
|
31
|
+
* `canvas.sizing` is captured at creation too, deliberately: a policy is an
|
|
32
|
+
* object, so a new instance on every render would detach and re-attach the
|
|
33
|
+
* previous one each time. Assign `app.sizing` yourself to switch strategies.
|
|
34
|
+
*
|
|
35
|
+
* Styling note: with no `canvas.sizing` the engine keeps the canvas at the base
|
|
36
|
+
* resolution and writes only that size, so you may style it freely. A sizing
|
|
37
|
+
* policy owns the canvas's `width`/`height` styles - don't fight it with a
|
|
38
|
+
* `style` prop.
|
|
39
|
+
*
|
|
40
|
+
* @param options - Application options (the canvas element is the one you render).
|
|
41
|
+
* @param onReady - Called once each time an Application is created.
|
|
42
|
+
* @param onError - Called for every {@link Application.onError} dispatch (async
|
|
43
|
+
* init/scene-load failures) while an Application exists. Re-subscribed
|
|
44
|
+
* automatically whenever the Application is (re)created.
|
|
45
|
+
*/
|
|
46
|
+
const useExoApplication = (options, onReady, onError) => {
|
|
47
|
+
const canvasRef = useRef(null);
|
|
48
|
+
const [app, setApp] = useState(null);
|
|
49
|
+
const onReadyRef = useRef(onReady);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
onReadyRef.current = onReady;
|
|
52
|
+
});
|
|
53
|
+
const onErrorRef = useRef(onError);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
onErrorRef.current = onError;
|
|
56
|
+
});
|
|
57
|
+
const backendKey = options?.backend?.type ?? "auto";
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const canvas = canvasRef.current;
|
|
60
|
+
if (!canvas) return;
|
|
61
|
+
const application = new Application({
|
|
62
|
+
...options,
|
|
63
|
+
canvas: {
|
|
64
|
+
...options?.canvas,
|
|
65
|
+
element: canvas
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
setApp(application);
|
|
69
|
+
onReadyRef.current?.(application);
|
|
70
|
+
return () => {
|
|
71
|
+
application.destroy();
|
|
72
|
+
setApp(null);
|
|
73
|
+
};
|
|
74
|
+
}, [backendKey]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (app === null) return;
|
|
77
|
+
const handleError = (error) => {
|
|
78
|
+
onErrorRef.current?.(error);
|
|
79
|
+
};
|
|
80
|
+
app.onError.add(handleError);
|
|
81
|
+
return () => {
|
|
82
|
+
app.onError.remove(handleError);
|
|
83
|
+
};
|
|
84
|
+
}, [app]);
|
|
85
|
+
const width = options?.canvas?.width;
|
|
86
|
+
const height = options?.canvas?.height;
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (app !== null && width !== void 0 && height !== void 0) app.resize(width, height);
|
|
89
|
+
}, [
|
|
90
|
+
app,
|
|
91
|
+
width,
|
|
92
|
+
height
|
|
93
|
+
]);
|
|
94
|
+
const clearColor = options?.clearColor;
|
|
95
|
+
const clearKey = colorKey(clearColor);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (app !== null && clearColor !== void 0) app.clearColor = clearColor;
|
|
98
|
+
}, [app, clearKey]);
|
|
99
|
+
return {
|
|
100
|
+
app,
|
|
101
|
+
canvasRef
|
|
102
|
+
};
|
|
103
|
+
};
|
|
122
104
|
|
|
105
|
+
//#endregion
|
|
123
106
|
export { useExoApplication };
|
|
124
|
-
//# sourceMappingURL=useExoApplication.js.map
|
|
107
|
+
//# sourceMappingURL=useExoApplication.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExoApplication.js","sources":["
|
|
1
|
+
{"version":3,"file":"useExoApplication.js","names":[],"sources":["../../src/useExoApplication.ts"],"sourcesContent":["import { Application, type ApplicationOptions, type CanvasApplicationOptions, type Color } from '@codexo/exojs';\nimport { type Ref, useEffect, useRef, useState } from 'react';\n\n/**\n * Options for {@link useExoApplication} / {@link import('./ExoCanvas').ExoCanvas}.\n *\n * Same as {@link ApplicationOptions} but the `canvas.element` and `canvas.mount`\n * fields are managed for you (the Application binds to the canvas the hook\n * references), so they are omitted. You may still pass\n * `canvas.width`/`height`/`sizing`/etc.\n */\nexport type ExoApplicationOptions = Omit<ApplicationOptions, 'canvas'> & {\n readonly canvas?: Omit<CanvasApplicationOptions, 'element' | 'mount'>;\n};\n\n/** Return value of {@link useExoApplication}. */\nexport interface UseExoApplicationResult {\n /** The Application instance, or `null` until it has been created. */\n readonly app: Application | null;\n /**\n * Attach this to the `<canvas>` element the Application should bind to. Typed\n * as `Ref` (not `RefObject`) so the same code type-checks against both\n * `@types/react` 18 and 19, whose `useRef`/`RefObject` nullability differ.\n */\n readonly canvasRef: Ref<HTMLCanvasElement>;\n}\n\n/** Stable string key for the colour so the sync effect can depend on its value. */\nconst colorKey = (color: Color | undefined): string | undefined => (color === undefined ? undefined : `${color.r},${color.g},${color.b},${color.a}`);\n\n/**\n * Creates and owns an ExoJS {@link Application}, binding it to a `<canvas>` you\n * render yourself and attach the returned `canvasRef` to. The hook renders no\n * DOM of its own - you keep full control over the canvas element, its container,\n * and its styling.\n *\n * ```tsx\n * function Game() {\n * const { app, canvasRef } = useExoApplication({ canvas: { width: 800, height: 600 } });\n * return <canvas ref={canvasRef} className=\"game\" />;\n * }\n * ```\n *\n * **Reactivity model.** The Application is recreated only when an *identity*\n * option changes - currently the render `backend` (you cannot hot-swap WebGL2 ↔\n * WebGPU). All other supported options are applied *live* without tearing the\n * app down:\n *\n * - `canvas.width` / `canvas.height` → `app.resize(...)`\n * - `clearColor` → `app.clearColor`\n *\n * Options without a live setter (e.g. `canvas.pixelRatio`, `seed`, `extensions`)\n * are captured at creation; change the `backend` or remount to apply them.\n *\n * `canvas.sizing` is captured at creation too, deliberately: a policy is an\n * object, so a new instance on every render would detach and re-attach the\n * previous one each time. Assign `app.sizing` yourself to switch strategies.\n *\n * Styling note: with no `canvas.sizing` the engine keeps the canvas at the base\n * resolution and writes only that size, so you may style it freely. A sizing\n * policy owns the canvas's `width`/`height` styles - don't fight it with a\n * `style` prop.\n *\n * @param options - Application options (the canvas element is the one you render).\n * @param onReady - Called once each time an Application is created.\n * @param onError - Called for every {@link Application.onError} dispatch (async\n * init/scene-load failures) while an Application exists. Re-subscribed\n * automatically whenever the Application is (re)created.\n */\nexport const useExoApplication = (\n options?: ExoApplicationOptions,\n onReady?: (app: Application) => void,\n onError?: (error: unknown) => void,\n): UseExoApplicationResult => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [app, setApp] = useState<Application | null>(null);\n\n // Latest onReady without retriggering the lifecycle effect. Updated in an\n // effect (not during render) so the ref-write happens after commit.\n const onReadyRef = useRef(onReady);\n useEffect(() => {\n onReadyRef.current = onReady;\n });\n\n // Latest onError without retriggering the subscribe effect below.\n const onErrorRef = useRef(onError);\n useEffect(() => {\n onErrorRef.current = onError;\n });\n\n // Identity: only the backend type forces a full recreation.\n const backendKey = options?.backend?.type ?? 'auto';\n\n // ── Lifecycle: create on mount / recreate on backend change ───────────────\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) {\n return;\n }\n\n // Bind to the React-rendered canvas. The engine never removes a canvas it\n // did not create (Application.destroy leaves it in the DOM), so React stays\n // the sole owner of the element's lifecycle.\n const application = new Application({\n ...options,\n canvas: { ...options?.canvas, element: canvas },\n });\n\n setApp(application);\n onReadyRef.current?.(application);\n\n return () => {\n // A React cleanup function cannot await, and teardown failures already\n // travel through the application's own error pipeline - so the Promise\n // is deliberately dropped rather than reported a second time here.\n void application.destroy();\n setApp(null);\n };\n // Recreate only when the backend identity changes; live options are synced\n // by the effects below. `options` is intentionally read at (re)create time.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [backendKey]);\n\n // ── Live sync: error reporting ─────────────────────────────────────────────\n useEffect(() => {\n if (app === null) {\n return;\n }\n\n const handleError = (error: Error): void => {\n onErrorRef.current?.(error);\n };\n\n app.onError.add(handleError);\n\n return () => {\n app.onError.remove(handleError);\n };\n }, [app]);\n\n // ── Live sync: size ───────────────────────────────────────────────────────\n const width = options?.canvas?.width;\n const height = options?.canvas?.height;\n useEffect(() => {\n if (app !== null && width !== undefined && height !== undefined) {\n app.resize(width, height);\n }\n }, [app, width, height]);\n\n // ── Live sync: clear colour ───────────────────────────────────────────────\n const clearColor = options?.clearColor;\n const clearKey = colorKey(clearColor);\n useEffect(() => {\n if (app !== null && clearColor !== undefined) {\n app.clearColor = clearColor;\n }\n // clearColor identity is unstable; depend on its value key instead.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [app, clearKey]);\n\n return { app, canvasRef };\n};\n"],"mappings":";;;;;AA4BA,MAAM,YAAY,UAAkD,UAAU,SAAY,SAAY,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyChJ,MAAa,qBACX,SACA,SACA,YAC4B;CAC5B,MAAM,YAAY,OAA0B,IAAI;CAChD,MAAM,CAAC,KAAK,UAAU,SAA6B,IAAI;CAIvD,MAAM,aAAa,OAAO,OAAO;CACjC,gBAAgB;EACd,WAAW,UAAU;CACvB,CAAC;CAGD,MAAM,aAAa,OAAO,OAAO;CACjC,gBAAgB;EACd,WAAW,UAAU;CACvB,CAAC;CAGD,MAAM,aAAa,SAAS,SAAS,QAAQ;CAG7C,gBAAgB;EACd,MAAM,SAAS,UAAU;EACzB,IAAI,CAAC,QACH;EAMF,MAAM,cAAc,IAAI,YAAY;GAClC,GAAG;GACH,QAAQ;IAAE,GAAG,SAAS;IAAQ,SAAS;GAAO;EAChD,CAAC;EAED,OAAO,WAAW;EAClB,WAAW,UAAU,WAAW;EAEhC,aAAa;GAIX,AAAK,YAAY,QAAQ;GACzB,OAAO,IAAI;EACb;CAIF,GAAG,CAAC,UAAU,CAAC;CAGf,gBAAgB;EACd,IAAI,QAAQ,MACV;EAGF,MAAM,eAAe,UAAuB;GAC1C,WAAW,UAAU,KAAK;EAC5B;EAEA,IAAI,QAAQ,IAAI,WAAW;EAE3B,aAAa;GACX,IAAI,QAAQ,OAAO,WAAW;EAChC;CACF,GAAG,CAAC,GAAG,CAAC;CAGR,MAAM,QAAQ,SAAS,QAAQ;CAC/B,MAAM,SAAS,SAAS,QAAQ;CAChC,gBAAgB;EACd,IAAI,QAAQ,QAAQ,UAAU,UAAa,WAAW,QACpD,IAAI,OAAO,OAAO,MAAM;CAE5B,GAAG;EAAC;EAAK;EAAO;CAAM,CAAC;CAGvB,MAAM,aAAa,SAAS;CAC5B,MAAM,WAAW,SAAS,UAAU;CACpC,gBAAgB;EACd,IAAI,QAAQ,QAAQ,eAAe,QACjC,IAAI,aAAa;CAIrB,GAAG,CAAC,KAAK,QAAQ,CAAC;CAElB,OAAO;EAAE;EAAK;CAAU;AAC1B"}
|
package/dist/esm/useScene.d.ts
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import { type Scene } from '@codexo/exojs';
|
|
2
2
|
import { type DependencyList } from 'react';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Activates `SceneClass` on the ExoJS {@link Application} and returns the
|
|
5
|
+
* resulting instance once it is live. `SceneClass` must be registered in
|
|
6
|
+
* `ApplicationOptions.scenes` (passed to {@link import('./useExoApplication').useExoApplication}
|
|
7
|
+
* / {@link import('./ExoCanvas').ExoCanvas}) - unregistered targets reject in
|
|
8
|
+
* development builds.
|
|
6
9
|
*
|
|
7
|
-
* On first call (engine not yet started) this hook calls `app.start(
|
|
10
|
+
* On first call (engine not yet started) this hook calls `app.start(SceneClass)`,
|
|
8
11
|
* which initializes the render backend and begins the per-frame loop. On
|
|
9
|
-
* subsequent dep-change remounts it calls `app.
|
|
10
|
-
* switch scenes without restarting the engine.
|
|
12
|
+
* subsequent dep-change remounts it calls `app.scenes.change(SceneClass)` to
|
|
13
|
+
* switch scenes without restarting the engine, constructing a fresh instance.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
15
|
+
* Effects that run while startup is still in flight - React StrictMode
|
|
16
|
+
* double-mounts every effect in development - join that `app.start()` call
|
|
17
|
+
* instead of racing a second navigation against it, and only activate
|
|
18
|
+
* `SceneClass` afterwards if startup did not already leave it active. A
|
|
19
|
+
* StrictMode double mount therefore activates the scene exactly once.
|
|
14
20
|
*
|
|
15
|
-
* A failure in `app.start()`/`app.
|
|
21
|
+
* A failure in `app.start()`/`app.scenes.change()` (e.g. a scene's `load()`
|
|
16
22
|
* rejects) is caught and routed to {@link Application.onError} rather than
|
|
17
|
-
* left as an unhandled promise rejection
|
|
23
|
+
* left as an unhandled promise rejection - subscribe via
|
|
18
24
|
* `app.onError.add(...)` or the {@link import('./ExoCanvas').ExoCanvas}
|
|
19
25
|
* `onError` prop to observe it.
|
|
20
26
|
*
|
|
21
|
-
* @param SceneClass - Constructor for the scene to
|
|
27
|
+
* @param SceneClass - Constructor for the scene to activate.
|
|
22
28
|
* @param deps - Extra deps that trigger scene replacement when changed, in
|
|
23
29
|
* addition to the stable `app` reference (same semantics as `useEffect`).
|
|
24
30
|
* @returns The active scene instance, or `null` while it is loading.
|
|
@@ -32,4 +38,5 @@ import { type DependencyList } from 'react';
|
|
|
32
38
|
* }
|
|
33
39
|
* ```
|
|
34
40
|
*/
|
|
35
|
-
export declare
|
|
41
|
+
export declare const useScene: <T extends Scene>(SceneClass: new () => T, deps?: DependencyList) => T | null;
|
|
42
|
+
//# sourceMappingURL=useScene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScene.d.ts","sourceRoot":"","sources":["../../src/useScene.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAyB,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,KAAK,cAAc,EAA+B,MAAM,OAAO,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,KAAK,EAAE,YAAY,UAAU,CAAC,EAAE,OAAM,cAAmB,KAAG,CAAC,GAAG,IAkFlG,CAAC"}
|