@navaramap/three-react 0.0.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/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2026 MapLibre contributors
2
+
3
+ This project is dual-licensed under either of
4
+
5
+ - Apache License, Version 2.0 (see LICENSE-APACHE)
6
+ - MIT license (see LICENSE-MIT)
7
+
8
+ at your option.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @navaramap/three-react
2
+
3
+ React bindings for Navara Three. It provides a small set of components and hooks to use `@navaramap/three` declaratively from React apps.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @navaramap/three @navaramap/three-react react react-dom three
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ Wrap your app with `ViewProvider`, then add layers with the `Layer` component.
14
+
15
+ ```tsx
16
+ // App.tsx
17
+ import { ViewProvider } from "@navaramap/three-react";
18
+ import { Layers } from "./Layers";
19
+
20
+ export default function App() {
21
+ return (
22
+ <ViewProvider>
23
+ <Layers />
24
+ </ViewProvider>
25
+ );
26
+ }
27
+ ```
28
+
29
+ ```tsx
30
+ // Layers.tsx
31
+ import {
32
+ JAPAN_GSI_ELEVATION_DECODER,
33
+ type LayerDescription,
34
+ type TilesLayer,
35
+ Layer as NavaraLayer,
36
+ } from "@navaramap/three";
37
+ import { useViewContext, Layer } from "@navaramap/three-react";
38
+ import { useEffect, useMemo } from "react";
39
+
40
+ export function Layers() {
41
+ const { view } = useViewContext();
42
+
43
+ // Credit the data through the built-in attribution UI.
44
+ useEffect(() => {
45
+ view?.attribution?.add([
46
+ {
47
+ attribution: "Geospatial Information Authority of Japan (GSI)",
48
+ attributionUrl: "https://maps.gsi.go.jp/development/ichiran.html",
49
+ },
50
+ ]);
51
+ }, [view]);
52
+
53
+ const baseTiles = useMemo<LayerDescription>(
54
+ () => ({
55
+ type: "tiles",
56
+ data: { url: "…" },
57
+ raster_tile: { min_zoom: 2, max_zoom: 18 },
58
+ }),
59
+ [],
60
+ );
61
+
62
+ const terrain = useMemo<LayerDescription>(
63
+ () => ({
64
+ type: "terrain",
65
+ data: { url: "…" },
66
+ raster_terrain: {
67
+ min_zoom: 6,
68
+ max_zoom: 15,
69
+ elevation_decoder: JAPAN_GSI_ELEVATION_DECODER(),
70
+ cast_shadow: true,
71
+ receive_shadow: true,
72
+ },
73
+ }),
74
+ [],
75
+ );
76
+
77
+ return (
78
+ <>
79
+ <Layer config={baseTiles} />
80
+ <Layer config={terrain} />
81
+ </>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ## API
87
+
88
+ - `ViewProvider`
89
+ - Props: `{ canvas?: HTMLCanvasElement | RefObject<HTMLCanvasElement> }`.
90
+ - Creates a `ThreeView` and provides it via context. If no `canvas` is given, a fullscreen canvas is appended to `document.body`.
91
+
92
+ - `useViewContext<CustomLayerDescriptions>()`
93
+ - Returns `{ view }` where `view` is the underlying `ThreeView<CustomLayerDescriptions>`.
94
+ - Must be used inside `ViewProvider`.
95
+
96
+ - `Layer`
97
+ - Props: `{ config: LayerDescription; onReady?: (handle) => void }`.
98
+ - Declaratively adds a layer on mount and updates it when `config` changes. The `onReady` callback receives a layer handle.
99
+
100
+ ## Canvas control (optional)
101
+
102
+ If you want to host the canvas inside your own layout, pass a ref to `ViewProvider`:
103
+
104
+ ```tsx
105
+ import { useRef } from "react";
106
+ import { ViewProvider } from "@navaramap/three-react";
107
+
108
+ export function AppWithCanvas() {
109
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
110
+ return (
111
+ <div style={{ position: "relative", width: "100vw", height: "100vh" }}>
112
+ <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />
113
+ <ViewProvider canvas={canvasRef}>
114
+ <Layers />
115
+ </ViewProvider>
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ ## Notes
122
+
123
+ - This package is client-side only; if using SSR, render these components on the client.
124
+ - Types are included.
125
+
126
+ ## License
127
+
128
+ MIT OR Apache-2.0
@@ -0,0 +1,61 @@
1
+ import { BaseHandle } from '@navaramap/three';
2
+ import { BuiltInEffectDescription } from '@navaramap/three';
3
+ import { default as default_2 } from '@navaramap/three';
4
+ import { Descriptions } from '@navaramap/three';
5
+ import { EffectConfig } from '@navaramap/three';
6
+ import { EffectDesc as EffectDesc_2 } from '@navaramap/three';
7
+ import { EmptyDescriptions } from '@navaramap/three';
8
+ import { FC } from 'react';
9
+ import { Layer as Layer_2 } from '@navaramap/three';
10
+ import { LayerDescription } from '@navaramap/three';
11
+ import { LightConfig } from '@navaramap/three';
12
+ import { LightDesc as LightDesc_2 } from '@navaramap/three';
13
+ import { MeshConfig } from '@navaramap/three';
14
+ import { MeshDesc as MeshDesc_2 } from '@navaramap/three';
15
+ import { Options } from '@navaramap/three';
16
+ import { Plugin as Plugin_2 } from '@navaramap/three';
17
+ import { PropsWithChildren } from 'react';
18
+ import { RefObject } from 'react';
19
+
20
+ export declare function EffectDesc<L extends EffectDesc_2 = EffectDesc_2>({ config, onReady, }: PropsWithChildren<EffectDescProps<L>>): null;
21
+
22
+ declare type EffectDescProps<L extends EffectDesc_2 = EffectDesc_2> = {
23
+ config: EffectConfig | BuiltInEffectDescription;
24
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void;
25
+ };
26
+
27
+ export declare function Layer({ config, onReady }: PropsWithChildren<LayerProps>): null;
28
+
29
+ declare type LayerProps = {
30
+ config: LayerDescription;
31
+ onReady?: (handle: Layer_2) => (() => void) | void;
32
+ };
33
+
34
+ export declare function LightDesc<L extends LightDesc_2 = LightDesc_2>({ config, onReady, }: PropsWithChildren<LightDescProps<L>>): null;
35
+
36
+ declare type LightDescProps<L extends LightDesc_2 = LightDesc_2> = {
37
+ config: LightConfig;
38
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void;
39
+ };
40
+
41
+ export declare function MeshDesc<L extends MeshDesc_2 = MeshDesc_2>({ config, onReady, }: PropsWithChildren<MeshDescProps<L>>): null;
42
+
43
+ declare type MeshDescProps<L extends MeshDesc_2 = MeshDesc_2> = {
44
+ config: MeshConfig;
45
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void;
46
+ };
47
+
48
+ export declare const useViewContext: <D extends Descriptions = EmptyDescriptions>() => Required<ViewContextValues<D>>;
49
+
50
+ declare type ViewContextValues<D extends Descriptions = EmptyDescriptions> = {
51
+ view?: default_2<D>;
52
+ };
53
+
54
+ export declare const ViewProvider: FC<PropsWithChildren<ViewProviderProps>>;
55
+
56
+ export declare type ViewProviderProps = {
57
+ canvas?: HTMLCanvasElement | RefObject<HTMLCanvasElement>;
58
+ plugins?: Plugin_2[];
59
+ } & Options;
60
+
61
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,105 @@
1
+ import e from "@navaramap/three";
2
+ import { createContext as t, useCallback as n, useContext as r, useEffect as i, useRef as a, useState as o } from "react";
3
+ //#region \0rolldown/runtime.js
4
+ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), c = /* @__PURE__ */ s(((e) => {
5
+ var t = Symbol.for("react.transitional.element");
6
+ function n(e, n, r) {
7
+ var i = null;
8
+ if (r !== void 0 && (i = "" + r), n.key !== void 0 && (i = "" + n.key), "key" in n) for (var a in r = {}, n) a !== "key" && (r[a] = n[a]);
9
+ else r = n;
10
+ return n = r.ref, {
11
+ $$typeof: t,
12
+ type: e,
13
+ key: i,
14
+ ref: n === void 0 ? null : n,
15
+ props: r
16
+ };
17
+ }
18
+ e.jsx = n;
19
+ })), l = (/* @__PURE__ */ s(((e, t) => {
20
+ t.exports = c();
21
+ })))(), u = t(void 0), d = () => {
22
+ let e = r(u);
23
+ if (!e) throw Error("Navara React Error: You have to invoke this hook inside of ViewProvider.");
24
+ return e;
25
+ }, f = ({ canvas: t, children: n, plugins: r, ...s }) => {
26
+ let [c, d] = o(), [f, p] = o(!1), m = a(void 0);
27
+ return i(() => {
28
+ if (m.current) {
29
+ console.warn("You need to recreate ThreeView.");
30
+ return;
31
+ }
32
+ let n = new e({
33
+ canvas: t && "current" in t ? t.current : t,
34
+ ...s
35
+ });
36
+ if (r) for (let e of r) n.addPlugin(e);
37
+ return d(n), m.current = n, (async () => {
38
+ try {
39
+ await n.init(), p(!0);
40
+ } catch (e) {
41
+ console.error("Navara init failed:", e);
42
+ }
43
+ })(), () => {};
44
+ }, [
45
+ t,
46
+ s,
47
+ r
48
+ ]), /* @__PURE__ */ (0, l.jsx)(u.Provider, {
49
+ value: { view: c },
50
+ children: f ? n : null
51
+ });
52
+ };
53
+ //#endregion
54
+ //#region src/Layer.tsx
55
+ function p({ config: e, onReady: t }) {
56
+ let { view: n } = d(), r = a(null), o = a(e), s = a(t);
57
+ return i(() => {
58
+ o.current = e, s.current = t;
59
+ }, [e, t]), i(() => {
60
+ let e = n.addLayer(o.current);
61
+ r.current = e;
62
+ let t = s.current?.(e);
63
+ return () => {
64
+ t?.(), e.delete(), r.current = null;
65
+ };
66
+ }, [n]), i(() => {
67
+ if (r.current) if ("data" in e) {
68
+ let { data: t, ...n } = e;
69
+ r.current.update(n);
70
+ } else r.current.update(e);
71
+ }, [e]), null;
72
+ }
73
+ function m(e, t, n, r) {
74
+ let o = a(null), s = a(e), c = a(n), l = a(r);
75
+ i(() => {
76
+ s.current = e, c.current = n, l.current = r;
77
+ }, [
78
+ e,
79
+ n,
80
+ r
81
+ ]), i(() => {
82
+ let e = s.current(c.current);
83
+ o.current = e;
84
+ let t = l.current?.(e);
85
+ return () => {
86
+ t?.(), e.delete(), o.current = null;
87
+ };
88
+ }, [t]), i(() => {
89
+ o.current && o.current.update(n);
90
+ }, [n]);
91
+ }
92
+ function h({ config: e, onReady: t }) {
93
+ let { view: r } = d();
94
+ return m(n((e) => r.addMesh(e), [r]), r, e, t), null;
95
+ }
96
+ function g({ config: e, onReady: t }) {
97
+ let { view: r } = d();
98
+ return m(n((e) => r.addLight(e), [r]), r, e, t), null;
99
+ }
100
+ function _({ config: e, onReady: t }) {
101
+ let { view: r } = d();
102
+ return m(n((e) => r.addEffect(e), [r]), r, e, t), null;
103
+ }
104
+ //#endregion
105
+ export { _ as EffectDesc, p as Layer, g as LightDesc, h as MeshDesc, f as ViewProvider, d as useViewContext };
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@navaramap/three"),require("react")):typeof define==`function`&&define.amd?define([`exports`,`@navaramap/three`,`react`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreeReact={},e._navaramap_three,e.react))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),u=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),u(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var d=l((e=>{var t=Symbol.for(`react.transitional.element`);function n(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.jsx=n})),f=l(((e,t)=>{t.exports=d()}))(),p=(0,n.createContext)(void 0),m=()=>{let e=(0,n.useContext)(p);if(!e)throw Error(`Navara React Error: You have to invoke this hook inside of ViewProvider.`);return e},h=({canvas:e,children:r,plugins:i,...a})=>{let[o,s]=(0,n.useState)(),[c,l]=(0,n.useState)(!1),u=(0,n.useRef)(void 0);return(0,n.useEffect)(()=>{if(u.current){console.warn(`You need to recreate ThreeView.`);return}let n=new t.default({canvas:e&&`current`in e?e.current:e,...a});if(i)for(let e of i)n.addPlugin(e);return s(n),u.current=n,(async()=>{try{await n.init(),l(!0)}catch(e){console.error(`Navara init failed:`,e)}})(),()=>{}},[e,a,i]),(0,f.jsx)(p.Provider,{value:{view:o},children:c?r:null})};function g({config:e,onReady:t}){let{view:r}=m(),i=(0,n.useRef)(null),a=(0,n.useRef)(e),o=(0,n.useRef)(t);return(0,n.useEffect)(()=>{a.current=e,o.current=t},[e,t]),(0,n.useEffect)(()=>{let e=r.addLayer(a.current);i.current=e;let t=o.current?.(e);return()=>{t?.(),e.delete(),i.current=null}},[r]),(0,n.useEffect)(()=>{if(i.current)if(`data`in e){let{data:t,...n}=e;i.current.update(n)}else i.current.update(e)},[e]),null}function _(e,t,r,i){let a=(0,n.useRef)(null),o=(0,n.useRef)(e),s=(0,n.useRef)(r),c=(0,n.useRef)(i);(0,n.useEffect)(()=>{o.current=e,s.current=r,c.current=i},[e,r,i]),(0,n.useEffect)(()=>{let e=o.current(s.current);a.current=e;let t=c.current?.(e);return()=>{t?.(),e.delete(),a.current=null}},[t]),(0,n.useEffect)(()=>{a.current&&a.current.update(r)},[r])}function v({config:e,onReady:t}){let{view:r}=m();return _((0,n.useCallback)(e=>r.addMesh(e),[r]),r,e,t),null}function y({config:e,onReady:t}){let{view:r}=m();return _((0,n.useCallback)(e=>r.addLight(e),[r]),r,e,t),null}function b({config:e,onReady:t}){let{view:r}=m();return _((0,n.useCallback)(e=>r.addEffect(e),[r]),r,e,t),null}e.EffectDesc=b,e.Layer=g,e.LightDesc=y,e.MeshDesc=v,e.ViewProvider=h,e.useViewContext=m});
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@navaramap/three-react",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "main": "dist/index.umd.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "src",
10
+ "dist"
11
+ ],
12
+ "license": "MIT OR Apache-2.0",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/reearth/navara.git",
16
+ "directory": "web/navara_three_react"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "peerDependencies": {
22
+ "react": ">=19",
23
+ "three": ">=0.183.0",
24
+ "@navaramap/three": "0.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "19.2.17",
28
+ "@types/react-dom": "19.2.3",
29
+ "@types/three": "0.185.1",
30
+ "three": "0.185.1",
31
+ "@navaramap/three": "0.0.0"
32
+ },
33
+ "scripts": {
34
+ "type-check": "tsc && tsc -p tsconfig.node.json",
35
+ "build": "pnpm type-check && vite build --watch=false",
36
+ "build:dev": "vite build --mode development",
37
+ "build:dev:watch": "pnpm build:dev --watch",
38
+ "test": "vitest --passWithNoTests --watch=false",
39
+ "lint": "eslint . --fix",
40
+ "lint:check": "eslint .",
41
+ "format": "prettier --write --ignore-path ../../.gitignore --ignore-path ../../.prettierignore .",
42
+ "format:check": "prettier --check --ignore-path ../../.gitignore --ignore-path ../../.prettierignore ."
43
+ }
44
+ }
package/src/Layer.tsx ADDED
@@ -0,0 +1,173 @@
1
+ import {
2
+ type BaseDesc,
3
+ type BaseHandle,
4
+ type MeshDesc as MeshDescClass,
5
+ type LightDesc as LightDescClass,
6
+ type EffectDesc as EffectDescClass,
7
+ type Layer as NavaraLayer,
8
+ type LayerDescription,
9
+ type MeshConfig,
10
+ type LightConfig,
11
+ type EffectConfig,
12
+ type BuiltInEffectDescription,
13
+ } from "@navaramap/three";
14
+ import { useCallback, useEffect, useRef, type PropsWithChildren } from "react";
15
+
16
+ import { useViewContext } from "./ViewContext";
17
+
18
+ type LayerProps = {
19
+ config: LayerDescription;
20
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
21
+ onReady?: (handle: NavaraLayer) => (() => void) | void;
22
+ };
23
+
24
+ export function Layer({ config, onReady }: PropsWithChildren<LayerProps>) {
25
+ const { view } = useViewContext();
26
+
27
+ const handleRef = useRef<NavaraLayer | null>(null);
28
+
29
+ const configRef = useRef(config);
30
+ const onReadyRef = useRef(onReady);
31
+
32
+ // Sync the latest-value refs outside render (react-hooks/refs). This effect
33
+ // is declared first so it runs before the mount effect below re-runs.
34
+ useEffect(() => {
35
+ configRef.current = config;
36
+ onReadyRef.current = onReady;
37
+ }, [config, onReady]);
38
+
39
+ useEffect(() => {
40
+ const handle = view.addLayer(configRef.current);
41
+ handleRef.current = handle;
42
+ const unmount = onReadyRef.current?.(handle);
43
+ return () => {
44
+ unmount?.();
45
+ handle.delete();
46
+ handleRef.current = null;
47
+ };
48
+ }, [view]);
49
+
50
+ useEffect(() => {
51
+ if (handleRef.current) {
52
+ if ("data" in config) {
53
+ const { data: _data, ...withoutData } = config;
54
+ handleRef.current.update(withoutData);
55
+ } else {
56
+ handleRef.current.update(config);
57
+ }
58
+ }
59
+ }, [config]);
60
+
61
+ return null;
62
+ }
63
+
64
+ function useDeclarationLayer<L extends BaseDesc>(
65
+ addFn: (config: Record<string, unknown>) => BaseHandle<L>,
66
+ view: object,
67
+ config: Record<string, unknown>,
68
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
69
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void,
70
+ ) {
71
+ const handleRef = useRef<BaseHandle<L> | null>(null);
72
+ const addFnRef = useRef(addFn);
73
+ const configRef = useRef(config);
74
+ const onReadyRef = useRef(onReady);
75
+
76
+ // Sync the latest-value refs outside render (react-hooks/refs). This effect
77
+ // is declared first so it runs before the mount effect below re-runs.
78
+ useEffect(() => {
79
+ addFnRef.current = addFn;
80
+ configRef.current = config;
81
+ onReadyRef.current = onReady;
82
+ }, [addFn, config, onReady]);
83
+
84
+ useEffect(() => {
85
+ const handle = addFnRef.current(configRef.current);
86
+ handleRef.current = handle;
87
+ const unmount = onReadyRef.current?.(handle);
88
+ return () => {
89
+ unmount?.();
90
+ handle.delete();
91
+ handleRef.current = null;
92
+ };
93
+ }, [view]);
94
+
95
+ useEffect(() => {
96
+ if (handleRef.current) {
97
+ handleRef.current.update(config as never);
98
+ }
99
+ }, [config]);
100
+ }
101
+
102
+ type MeshDescProps<L extends MeshDescClass = MeshDescClass> = {
103
+ config: MeshConfig;
104
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
105
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void;
106
+ };
107
+
108
+ export function MeshDesc<L extends MeshDescClass = MeshDescClass>({
109
+ config,
110
+ onReady,
111
+ }: PropsWithChildren<MeshDescProps<L>>) {
112
+ const { view } = useViewContext();
113
+ const addFn = useCallback(
114
+ (c: Record<string, unknown>) => view.addMesh<L>(c as MeshConfig),
115
+ [view],
116
+ );
117
+ useDeclarationLayer<L>(
118
+ addFn,
119
+ view,
120
+ config as Record<string, unknown>,
121
+ onReady,
122
+ );
123
+ return null;
124
+ }
125
+
126
+ type LightDescProps<L extends LightDescClass = LightDescClass> = {
127
+ config: LightConfig;
128
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
129
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void;
130
+ };
131
+
132
+ export function LightDesc<L extends LightDescClass = LightDescClass>({
133
+ config,
134
+ onReady,
135
+ }: PropsWithChildren<LightDescProps<L>>) {
136
+ const { view } = useViewContext();
137
+ const addFn = useCallback(
138
+ (c: Record<string, unknown>) => view.addLight<L>(c as LightConfig),
139
+ [view],
140
+ );
141
+ useDeclarationLayer<L>(
142
+ addFn,
143
+ view,
144
+ config as Record<string, unknown>,
145
+ onReady,
146
+ );
147
+ return null;
148
+ }
149
+
150
+ type EffectDescProps<L extends EffectDescClass = EffectDescClass> = {
151
+ config: EffectConfig | BuiltInEffectDescription;
152
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
153
+ onReady?: (handle: BaseHandle<L>) => (() => void) | void;
154
+ };
155
+
156
+ export function EffectDesc<L extends EffectDescClass = EffectDescClass>({
157
+ config,
158
+ onReady,
159
+ }: PropsWithChildren<EffectDescProps<L>>) {
160
+ const { view } = useViewContext();
161
+ const addFn = useCallback(
162
+ (c: Record<string, unknown>) =>
163
+ view.addEffect<L>(c as EffectConfig | BuiltInEffectDescription),
164
+ [view],
165
+ );
166
+ useDeclarationLayer<L>(
167
+ addFn,
168
+ view,
169
+ config as Record<string, unknown>,
170
+ onReady,
171
+ );
172
+ return null;
173
+ }
@@ -0,0 +1,85 @@
1
+ import ThreeView from "@navaramap/three";
2
+ import type {
3
+ Plugin,
4
+ Options,
5
+ Descriptions,
6
+ EmptyDescriptions,
7
+ } from "@navaramap/three";
8
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
9
+ import type { FC, PropsWithChildren, RefObject } from "react";
10
+
11
+ type ViewContextValues<D extends Descriptions = EmptyDescriptions> = {
12
+ view?: ThreeView<D>;
13
+ };
14
+
15
+ const ViewContext = createContext<ViewContextValues | undefined>(undefined);
16
+
17
+ // eslint-disable-next-line react-refresh/only-export-components
18
+ export const useViewContext = <
19
+ D extends Descriptions = EmptyDescriptions,
20
+ >() => {
21
+ const ctx = useContext(ViewContext);
22
+ if (!ctx) {
23
+ throw new Error(
24
+ "Navara React Error: You have to invoke this hook inside of ViewProvider.",
25
+ );
26
+ }
27
+
28
+ return ctx as Required<ViewContextValues<D>>;
29
+ };
30
+
31
+ export type ViewProviderProps = {
32
+ canvas?: HTMLCanvasElement | RefObject<HTMLCanvasElement>;
33
+ plugins?: Plugin[];
34
+ } & Options;
35
+
36
+ export const ViewProvider: FC<PropsWithChildren<ViewProviderProps>> = ({
37
+ canvas,
38
+ children,
39
+ plugins,
40
+ ...opts
41
+ }) => {
42
+ const [view, setView] = useState<ThreeView | undefined>();
43
+ const [isReady, setIsReady] = useState(false);
44
+ // The ref is written when the view is created in the effect below; syncing it
45
+ // during render is both redundant and disallowed by react-hooks/refs.
46
+ const viewRef = useRef<ThreeView | undefined>(undefined);
47
+
48
+ useEffect(() => {
49
+ if (viewRef.current) {
50
+ console.warn("You need to recreate ThreeView.");
51
+ return;
52
+ }
53
+
54
+ const innerCanvas = canvas && "current" in canvas ? canvas.current : canvas;
55
+
56
+ const v = new ThreeView({ canvas: innerCanvas, ...opts });
57
+ if (plugins) {
58
+ for (const plugin of plugins) {
59
+ v.addPlugin(plugin);
60
+ }
61
+ }
62
+ setView(v);
63
+ viewRef.current = v;
64
+
65
+ (async () => {
66
+ try {
67
+ await v.init();
68
+ setIsReady(true);
69
+ } catch (e) {
70
+ console.error("Navara init failed:", e);
71
+ }
72
+ })();
73
+
74
+ return () => {
75
+ // TODO
76
+ // v.dispose();
77
+ };
78
+ }, [canvas, opts, plugins]);
79
+
80
+ return (
81
+ <ViewContext.Provider value={{ view }}>
82
+ {isReady ? children : null}
83
+ </ViewContext.Provider>
84
+ );
85
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { Layer, MeshDesc, LightDesc, EffectDesc } from "./Layer";
2
+ export * from "./ViewContext";
@@ -0,0 +1,6 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module "*.glsl" {
4
+ const shader: string;
5
+ export default shader;
6
+ }