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"] }