diff --git a/lib/index.ts b/lib/index.ts
deleted file mode 100644
index bc81dd5..0000000
--- a/lib/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const add = (a: number, b: number) => a + b;
diff --git a/lib/panorama-controls-react.tsx b/lib/panorama-controls-react.tsx
new file mode 100644
index 0000000..e7d2b28
--- /dev/null
+++ b/lib/panorama-controls-react.tsx
@@ -0,0 +1,56 @@
+import { useFrame, useThree } from "@react-three/fiber";
+import { forwardRef, useEffect, useMemo } from "react";
+import { PerspectiveCamera } from "three";
+import { PanoramaControls as PanoramaControlsImpl } from "./panorama-controls";
+
+export type PanoramaControlsProps = {
+ makeDefault?: boolean;
+ enabled?: boolean;
+ zoomable?: boolean;
+};
+
+export const PanoramaControls = forwardRef<
+ PanoramaControlsImpl,
+ PanoramaControlsProps
+>(({ makeDefault, enabled = true, zoomable = true }, ref) => {
+ const { camera, gl, events, set, get } = useThree();
+ const domElement = events.connected ?? gl.domElement;
+
+ // Recreate only when camera changes because recreating on a
+ // changed `domElement` removes the existing listeners added
+ // through a ref.
+ const controls = useMemo(
+ () => new PanoramaControlsImpl(camera as PerspectiveCamera, domElement),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [camera]
+ );
+
+ useEffect(() => void (controls.enabled = enabled), [controls, enabled]);
+ useEffect(() => void (controls.zoomable = zoomable), [controls, zoomable]);
+
+ useEffect(() => {
+ // This needs to be in a `useEffect` because the
+ // `PanoramaControlsImpl` object is created before the controls are
+ // disposed in the `useEffect` destructor function which leads to
+ // the zoom being reset to default by the `dispose` function as all
+ // `PanoramaControlsImpl` instances use the same `camera` instance
+ // from react-three-fiber.
+ controls.initializeZoom();
+ return () => controls.dispose();
+ }, [controls]);
+ // Reconnect controls when the domElement changes.
+ useEffect(() => controls.reconnect(domElement), [controls, domElement]);
+ useEffect(() => {
+ if (makeDefault) {
+ const oldControls = get().controls;
+ set({ controls });
+
+ return () => set({ controls: oldControls });
+ }
+ }, [controls, get, makeDefault, set]);
+
+ // Call the controls animation loop.
+ useFrame(() => controls.enabled && controls.update());
+
+ return ;
+});
diff --git a/lib/panorama-controls.ts b/lib/panorama-controls.ts
new file mode 100644
index 0000000..7cecb42
--- /dev/null
+++ b/lib/panorama-controls.ts
@@ -0,0 +1,193 @@
+import { EventDispatcher, MathUtils, PerspectiveCamera, Vector3 } from "three";
+
+export type PanoramaEvents = {
+ change: { type: "change" };
+ start: { type: "start" };
+ end: { type: "end" };
+};
+
+export class PanoramaControls extends EventDispatcher {
+ private isEnabled = true;
+ public zoomable = true;
+
+ public minFov = 10;
+ public maxFov = 90;
+ private defaultFov = 50;
+
+ public zoomSpeed = 0.05;
+ public panSpeed = 0.1;
+
+ private onPointerDownMouseX = 0;
+ private onPointerDownMouseY = 0;
+ private onPointerDownLng = 0;
+ private onPointerDownLat = 0;
+
+ public lat = 0;
+ public lng = 0;
+
+ private controlsPosition: Vector3;
+
+ public set enabled(value: boolean) {
+ // If the controls were previously disabled and are being enabled
+ // now, then we set up the listeners again.
+ if (!this.isEnabled && value) {
+ this.setupListeners();
+ } else if (!value) {
+ // Disabling the controls by removing listeners.
+ this.disposeListeners();
+ }
+
+ this.isEnabled = value;
+ }
+
+ public get enabled() {
+ return this.isEnabled;
+ }
+
+ constructor(
+ public camera: PerspectiveCamera,
+ public domElement: HTMLElement
+ ) {
+ super();
+
+ this.initializeZoom();
+ this.controlsPosition = this.camera.position.clone();
+ // Set the initial position of the camera.
+ this.updateCameraLookAt();
+
+ this.setupListeners();
+ }
+
+ public initializeZoom() {
+ // Max zoom out on initial load.
+ this.camera.fov = this.maxFov;
+ this.camera.updateProjectionMatrix();
+ }
+
+ private setupListeners() {
+ // To make touch work with pointer events.
+ this.domElement.style.touchAction = "none";
+ this.domElement.addEventListener("pointerdown", this.onPointerDown);
+ this.domElement.addEventListener("wheel", this.onDocumentMouseWheel);
+ }
+
+ public disposeListeners() {
+ this.domElement.removeEventListener("pointerdown", this.onPointerDown);
+ this.domElement.removeEventListener("wheel", this.onDocumentMouseWheel);
+
+ // Adding other events to the document as the mouse can flow outside
+ // the element during interaction after the `pointerdown` event.
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ public dispose() {
+ this.camera.fov = this.defaultFov;
+ this.camera.updateProjectionMatrix();
+
+ this.disposeListeners();
+ }
+
+ // Can be used to reconnect controls to a different DOM element.
+ public reconnect(domElement: HTMLElement) {
+ this.disposeListeners();
+
+ this.domElement = domElement;
+ // Set up listeners with the new DOM element.
+ this.setupListeners();
+ }
+
+ public update() {
+ // Dispatch the `change` event if the camera's position changes.
+ if (!this.camera.position.equals(this.controlsPosition)) {
+ this.controlsPosition = this.camera.position.clone();
+ this.dispatchEvent({ type: "change" });
+ }
+ }
+
+ // Mouse Events
+
+ private onPointerDown = (event: PointerEvent) => {
+ if (event.isPrimary === false) {
+ return;
+ }
+
+ // This is to avoid selection of text/elements when moving mouse.
+ event.preventDefault();
+
+ this.onPointerDownMouseX = event.clientX;
+ this.onPointerDownMouseY = event.clientY;
+
+ this.onPointerDownLat = this.lat;
+ this.onPointerDownLng = this.lng;
+
+ this.dispatchEvent({ type: "start" });
+
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ };
+
+ private onPointerUp = (event: PointerEvent) => {
+ if (event.isPrimary === false) {
+ return;
+ }
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+
+ this.dispatchEvent({ type: "end" });
+ };
+
+ private onPointerMove = (event: PointerEvent) => {
+ if (event.isPrimary === false) {
+ return;
+ }
+
+ this.lat =
+ (event.clientY - this.onPointerDownMouseY) * this.panSpeed +
+ this.onPointerDownLat;
+ this.lng =
+ (this.onPointerDownMouseX - event.clientX) * this.panSpeed +
+ this.onPointerDownLng;
+
+ this.updateCameraLookAt();
+ this.dispatchEvent({ type: "change" });
+ };
+
+ private onDocumentMouseWheel = (event: WheelEvent) => {
+ if (!this.zoomable) {
+ return;
+ }
+
+ event.preventDefault();
+
+ const fov = this.camera.fov + event.deltaY * this.zoomSpeed;
+ const newFov = MathUtils.clamp(fov, this.minFov, this.maxFov);
+
+ // No update.
+ if (newFov === this.camera.fov) {
+ return;
+ }
+
+ this.camera.fov = newFov;
+ this.camera.updateProjectionMatrix();
+
+ this.dispatchEvent({ type: "change" });
+ // Dispatch the `end` event as well as there is no definite way to
+ // check when a mouse wheel has ended. One could use a timeout but
+ // this is how `OrbitControls` does it so should be fine.
+ this.dispatchEvent({ type: "end" });
+ };
+
+ private updateCameraLookAt() {
+ this.lat = Math.max(-85, Math.min(85, this.lat));
+ const phi = MathUtils.degToRad(90 - this.lat);
+ const theta = MathUtils.degToRad(this.lng);
+
+ const x = 500 * Math.sin(phi) * Math.cos(theta);
+ const y = 500 * Math.cos(phi);
+ const z = 500 * Math.sin(phi) * Math.sin(theta);
+
+ this.camera.lookAt(x, y, z);
+ }
+}
diff --git a/package.json b/package.json
index b936ce1..91dfa21 100644
--- a/package.json
+++ b/package.json
@@ -23,14 +23,19 @@
"bugs": {
"url": "https://github.com/9inpachi/three-panorama-controls/issues"
},
- "main": "./dist/cjs/index.js",
- "module": "./dist/esm/index.js",
- "types": "./dist/types/index.d.ts",
+ "main": "./dist/cjs/panorama-controls.js",
+ "module": "./dist/esm/panorama-controls.js",
+ "types": "./dist/types/panorama-controls.d.ts",
"exports": {
".": {
- "types": "./dist/types/index.d.ts",
- "import": "./dist/esm/index.js",
- "require": "./dist/cjs/index.js"
+ "types": "./dist/types/panorama-controls.d.ts",
+ "import": "./dist/esm/panorama-controls.js",
+ "require": "./dist/cjs/panorama-controls.js"
+ },
+ "./react": {
+ "types": "./dist/types/panorama-controls-react.d.ts",
+ "import": "./dist/esm/panorama-controls-react.js",
+ "require": "./dist/cjs/panorama-controls-react.js"
}
},
"scripts": {
@@ -45,13 +50,26 @@
"build:types": "pnpm tsc --emitDeclarationOnly true --declarationDir ./dist/types"
},
"peerDependencies": {
+ "@react-three/fiber": ">= 8",
+ "react": ">= 18",
"three": ">= 0.160"
},
"devDependencies": {
"@eslint/js": "^9.10.0",
+ "@react-three/fiber": "^8.17.6",
+ "@types/react": "^18.3.5",
+ "@types/three": "^0.168.0",
"eslint": "^9.10.0",
"globals": "^15.9.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "@react-three/fiber": {
+ "optional": true
+ }
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 69e7120..64ffc52 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,10 +7,26 @@ settings:
importers:
.:
+ dependencies:
+ react:
+ specifier: '>= 18'
+ version: 18.3.1
+ three:
+ specifier: '>= 0.160'
+ version: 0.168.0
devDependencies:
'@eslint/js':
specifier: ^9.10.0
version: 9.10.0
+ '@react-three/fiber':
+ specifier: ^8.17.6
+ version: 8.17.6(react@18.3.1)(three@0.168.0)
+ '@types/react':
+ specifier: ^18.3.5
+ version: 18.3.5
+ '@types/three':
+ specifier: ^0.168.0
+ version: 0.168.0
eslint:
specifier: ^9.10.0
version: 9.10.0
@@ -26,6 +42,10 @@ importers:
packages:
+ '@babel/runtime@7.25.6':
+ resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
+ engines: {node: '>=6.9.0'}
+
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -76,6 +96,58 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@react-three/fiber@8.17.6':
+ resolution: {integrity: sha512-RqZXSpEVY8alF3dWgFhUFePM9FE9jCZxeZJ3wEJ8z6Bd6AsrLXXs9wRW6WhCY/r0y7eW36v2t74QavM0coA3aA==}
+ peerDependencies:
+ expo: '>=43.0'
+ expo-asset: '>=8.4'
+ expo-file-system: '>=11.0'
+ expo-gl: '>=11.0'
+ react: '>=18.0'
+ react-dom: '>=18.0'
+ react-native: '>=0.64'
+ three: '>=0.133'
+ peerDependenciesMeta:
+ expo:
+ optional: true
+ expo-asset:
+ optional: true
+ expo-file-system:
+ optional: true
+ expo-gl:
+ optional: true
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
+ '@tweenjs/tween.js@23.1.3':
+ resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
+
+ '@types/debounce@1.2.4':
+ resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==}
+
+ '@types/prop-types@15.7.12':
+ resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
+
+ '@types/react-reconciler@0.26.7':
+ resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==}
+
+ '@types/react-reconciler@0.28.8':
+ resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==}
+
+ '@types/react@18.3.5':
+ resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==}
+
+ '@types/stats.js@0.17.3':
+ resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
+
+ '@types/three@0.168.0':
+ resolution: {integrity: sha512-qAGLGzbaYgkkonOBfwOr+TZpOskPfFjrDAj801WQSVkUz0/D9zwir4vhruJ/CC/GteywzR9pqeVVfs5th/2oKw==}
+
+ '@types/webxr@0.5.20':
+ resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==}
+
'@typescript-eslint/eslint-plugin@8.4.0':
resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -133,6 +205,9 @@ packages:
resolution: {integrity: sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@webgpu/types@0.1.44':
+ resolution: {integrity: sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ==}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -160,6 +235,9 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -170,6 +248,9 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -192,6 +273,12 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ debounce@1.2.1:
+ resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
@@ -266,6 +353,9 @@ packages:
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+ fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -308,6 +398,9 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -339,6 +432,14 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ its-fine@1.2.5:
+ resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==}
+ peerDependencies:
+ react: '>=18.0'
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
@@ -366,10 +467,17 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ meshoptimizer@0.18.1:
+ resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
+
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -426,6 +534,19 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ react-reconciler@0.27.0:
+ resolution: {integrity: sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==}
+ engines: {node: '>=0.10.0'}
+ peerDependencies:
+ react: ^18.0.0
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -437,6 +558,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ scheduler@0.21.0:
+ resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==}
+
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
@@ -462,9 +586,17 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
+ suspend-react@0.1.3:
+ resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==}
+ peerDependencies:
+ react: '>=17.0'
+
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+ three@0.168.0:
+ resolution: {integrity: sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==}
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -509,8 +641,21 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ zustand@3.7.2:
+ resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ react: '>=16.8'
+ peerDependenciesMeta:
+ react:
+ optional: true
+
snapshots:
+ '@babel/runtime@7.25.6':
+ dependencies:
+ regenerator-runtime: 0.14.1
+
'@eslint-community/eslint-utils@4.4.0(eslint@9.10.0)':
dependencies:
eslint: 9.10.0
@@ -564,6 +709,55 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
+ '@react-three/fiber@8.17.6(react@18.3.1)(three@0.168.0)':
+ dependencies:
+ '@babel/runtime': 7.25.6
+ '@types/debounce': 1.2.4
+ '@types/react-reconciler': 0.26.7
+ '@types/webxr': 0.5.20
+ base64-js: 1.5.1
+ buffer: 6.0.3
+ debounce: 1.2.1
+ its-fine: 1.2.5(react@18.3.1)
+ react: 18.3.1
+ react-reconciler: 0.27.0(react@18.3.1)
+ scheduler: 0.21.0
+ suspend-react: 0.1.3(react@18.3.1)
+ three: 0.168.0
+ zustand: 3.7.2(react@18.3.1)
+
+ '@tweenjs/tween.js@23.1.3': {}
+
+ '@types/debounce@1.2.4': {}
+
+ '@types/prop-types@15.7.12': {}
+
+ '@types/react-reconciler@0.26.7':
+ dependencies:
+ '@types/react': 18.3.5
+
+ '@types/react-reconciler@0.28.8':
+ dependencies:
+ '@types/react': 18.3.5
+
+ '@types/react@18.3.5':
+ dependencies:
+ '@types/prop-types': 15.7.12
+ csstype: 3.1.3
+
+ '@types/stats.js@0.17.3': {}
+
+ '@types/three@0.168.0':
+ dependencies:
+ '@tweenjs/tween.js': 23.1.3
+ '@types/stats.js': 0.17.3
+ '@types/webxr': 0.5.20
+ '@webgpu/types': 0.1.44
+ fflate: 0.8.2
+ meshoptimizer: 0.18.1
+
+ '@types/webxr@0.5.20': {}
+
'@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.10.0)(typescript@5.5.4))(eslint@9.10.0)(typescript@5.5.4)':
dependencies:
'@eslint-community/regexpp': 4.11.0
@@ -645,6 +839,8 @@ snapshots:
'@typescript-eslint/types': 8.4.0
eslint-visitor-keys: 3.4.3
+ '@webgpu/types@0.1.44': {}
+
acorn-jsx@5.3.2(acorn@8.12.1):
dependencies:
acorn: 8.12.1
@@ -668,6 +864,8 @@ snapshots:
balanced-match@1.0.2: {}
+ base64-js@1.5.1: {}
+
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@@ -681,6 +879,11 @@ snapshots:
dependencies:
fill-range: 7.1.1
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
callsites@3.1.0: {}
chalk@4.1.2:
@@ -702,6 +905,10 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ csstype@3.1.3: {}
+
+ debounce@1.2.1: {}
+
debug@4.3.7:
dependencies:
ms: 2.1.3
@@ -794,6 +1001,8 @@ snapshots:
dependencies:
reusify: 1.0.4
+ fflate@0.8.2: {}
+
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -830,6 +1039,8 @@ snapshots:
has-flag@4.0.0: {}
+ ieee754@1.2.1: {}
+
ignore@5.3.2: {}
import-fresh@3.3.0:
@@ -851,6 +1062,13 @@ snapshots:
isexe@2.0.0: {}
+ its-fine@1.2.5(react@18.3.1):
+ dependencies:
+ '@types/react-reconciler': 0.28.8
+ react: 18.3.1
+
+ js-tokens@4.0.0: {}
+
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
@@ -876,8 +1094,14 @@ snapshots:
lodash.merge@4.6.2: {}
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
merge2@1.4.1: {}
+ meshoptimizer@0.18.1: {}
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -928,6 +1152,18 @@ snapshots:
queue-microtask@1.2.3: {}
+ react-reconciler@0.27.0(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.21.0
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ regenerator-runtime@0.14.1: {}
+
resolve-from@4.0.0: {}
reusify@1.0.4: {}
@@ -936,6 +1172,10 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ scheduler@0.21.0:
+ dependencies:
+ loose-envify: 1.4.0
+
semver@7.6.3: {}
shebang-command@2.0.0:
@@ -954,8 +1194,14 @@ snapshots:
dependencies:
has-flag: 4.0.0
+ suspend-react@0.1.3(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
text-table@0.2.0: {}
+ three@0.168.0: {}
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -992,3 +1238,7 @@ snapshots:
word-wrap@1.2.5: {}
yocto-queue@0.1.0: {}
+
+ zustand@3.7.2(react@18.3.1):
+ optionalDependencies:
+ react: 18.3.1
diff --git a/tsconfig.json b/tsconfig.json
index 0e2cf54..04feeef 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,8 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "declaration": true
+ "declaration": true,
+ "jsx": "react-jsx"
},
"include": ["lib"]
}