diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index 2ed6d3e..eb20bb3 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -36,4 +36,4 @@ jobs: run: pnpm build - name: Publish preview - run: pnpx pkg-pr-new publish --compact + run: pnpx pkg-pr-new publish --compact --template ./demo diff --git a/.gitignore b/.gitignore index 6fa23c6..dfac84e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ dist types node_modules +demo/node_modules packed/ diff --git a/README.md b/README.md index 39fac07..1a99f4c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ 3. [Components](#components) - [Canvas](#canvas) - [T](#t) + - [createEntity](#createentity) - [Entity](#entity) - [Portal](#portal) - [Resource](#resource) @@ -312,6 +313,22 @@ const T = createT({ Mesh, BoxGeometry, MeshBasicMaterial }) - **In Libraries**: create multiple `T` to allow for treeshaking or use [``](#entity) instead - **Multiple Ts**: Create multiple T instances for lazy loading different parts of three.js +#### createEntity + +`createT` is built on top of `createEntity`, which creates a single typed component from one Three.js constructor. Use it directly when you need a one-off component without building a full namespace: + +```tsx +import { createEntity } from "solid-three" +import { Mesh } from "three" + +const MeshComponent = createEntity(Mesh) + +// Equivalent to but without the full namespace + + ... + +``` + ### Portal The `Portal` component allows you to place children outside the regular scene graph while maintaining reactive updates. This is useful for rendering objects into different scenes or bypassing the normal parent-child relationships. @@ -390,7 +407,24 @@ Wrapper-component around ['useLoader'](#useloader). ### useThree -Provides access to the `three.js` context, including the renderer, scene, camera, and more. This hook can be used with or without a selector function for optimized access to specific properties. +Provides access to the `three.js` context, including the renderer, scene, camera, and more. + +**Signatures:** + +```tsx +// Returns the full context object directly +useThree(): Context + +// Returns a reactive accessor for a derived value +useThree(callback: (value: Context) => T): Accessor +``` + +Use the selector form to derive a specific value reactively: + +```tsx +const camera = useThree(ctx => ctx.camera) +// camera() is an Accessor +``` **Returns:** @@ -485,7 +519,7 @@ useFrame( priority?: number stage?: "before" | "after" } -) +): () => void ``` diff --git a/demo/.stackblitzrc b/demo/.stackblitzrc new file mode 100644 index 0000000..375ca13 --- /dev/null +++ b/demo/.stackblitzrc @@ -0,0 +1,4 @@ +{ + "installCommand": "pnpm install", + "startCommand": "pnpm run dev" +} diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..8599812 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,16 @@ + + + + + + solid-three + + + +
+ + + diff --git a/demo/package-lock.json b/demo/package-lock.json new file mode 100644 index 0000000..eeb4983 --- /dev/null +++ b/demo/package-lock.json @@ -0,0 +1,1904 @@ +{ + "name": "solid-three-demo", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "solid-three-demo", + "version": "0.0.0", + "dependencies": { + "solid-js": "^1.8.0", + "solid-three": "latest", + "three": "^0.164.0" + }, + "devDependencies": { + "@types/three": "^0.164.0", + "typescript": "^5.4.0", + "vite": "^5.0.0", + "vite-plugin-solid": "^2.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.164.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.164.1.tgz", + "integrity": "sha512-dR/trWDhyaNqJV38rl1TonlCA9DpnX7OPYDWD81bmBGn/+uEc3+zNalFxQcV4FlPTeDBhCY3SFWKvK6EJwL88g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "~23.1.1", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.6", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.6.tgz", + "integrity": "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", + "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.6" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.12" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge-anything": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", + "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seroval": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.2.tgz", + "integrity": "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.2.tgz", + "integrity": "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/solid-js": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.12.tgz", + "integrity": "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" + } + }, + "node_modules/solid-refresh": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz", + "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.23.6", + "@babel/helper-module-imports": "^7.22.15", + "@babel/types": "^7.23.6" + }, + "peerDependencies": { + "solid-js": "^1.3" + } + }, + "node_modules/solid-three": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/solid-three/-/solid-three-0.2.0.tgz", + "integrity": "sha512-CW3a8OFG8fGdEekdvri/Tx1wu1Mm6gUKI73bOxX0rA349WPoCYq3h1FNGjNS68ec5i2TvPpn7ZC4a3bq/S42fw==", + "license": "MIT", + "dependencies": { + "@types/three": "0.149.0", + "zustand": "^3.7.2" + }, + "peerDependencies": { + "solid-js": "*", + "three": "*" + } + }, + "node_modules/solid-three/node_modules/@types/three": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.149.0.tgz", + "integrity": "sha512-fgNBm9LWc65ER/W0cvoXdC0iMy7Ke9e2CONmEr6Jt8sDSY3sw4DgOubZfmdZ747dkPhbQrgRQAWwDEr2S/7IEg==", + "license": "MIT", + "dependencies": { + "@types/webxr": "*" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/three": { + "version": "0.164.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.164.1.tgz", + "integrity": "sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==", + "license": "MIT", + "peer": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-solid": { + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.12.tgz", + "integrity": "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.3", + "@types/babel__core": "^7.20.4", + "babel-preset-solid": "^1.8.4", + "merge-anything": "^5.1.7", + "solid-refresh": "^0.6.3", + "vitefu": "^1.0.4" + }, + "peerDependencies": { + "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", + "solid-js": "^1.7.2", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@testing-library/jest-dom": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + } + } +} diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..70570db --- /dev/null +++ b/demo/package.json @@ -0,0 +1,21 @@ +{ + "name": "solid-three-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "solid-js": "^1.8.0", + "solid-three": "latest", + "three": "^0.164.0" + }, + "devDependencies": { + "@types/three": "^0.164.0", + "typescript": "^5.4.0", + "vite": "^5.0.0", + "vite-plugin-solid": "^2.10.0" + } +} diff --git a/demo/src/App.tsx b/demo/src/App.tsx new file mode 100644 index 0000000..112cc97 --- /dev/null +++ b/demo/src/App.tsx @@ -0,0 +1,23 @@ +import { createSignal } from "solid-js" +import * as THREE from "three" +import { Canvas, createT } from "solid-three" + +const T = createT(THREE) + +export function App() { + const [hovered, setHovered] = createSignal(false) + + return ( + + + + setHovered(true)} + onPointerLeave={() => setHovered(false)} + > + + + + + ) +} diff --git a/demo/src/main.tsx b/demo/src/main.tsx new file mode 100644 index 0000000..bbf662e --- /dev/null +++ b/demo/src/main.tsx @@ -0,0 +1,4 @@ +import { render } from "solid-js/web" +import { App } from "./App.tsx" + +render(() => , document.getElementById("root")!) diff --git a/demo/tsconfig.json b/demo/tsconfig.json new file mode 100644 index 0000000..e6c6eec --- /dev/null +++ b/demo/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "isolatedModules": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "types": ["vite/client"] + } +} diff --git a/demo/vite.config.ts b/demo/vite.config.ts new file mode 100644 index 0000000..490f0e3 --- /dev/null +++ b/demo/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite" +import solid from "vite-plugin-solid" + +export default defineConfig({ + base: "./", + plugins: [solid()], +}) diff --git a/package.json b/package.json index d77f262..aa74917 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "lint:code": "eslint --max-warnings 0 src/**/*.{ts,tsx}", "lint:types": "tsc --noEmit", "prepublishOnly": "pnpm lint && pnpm build", - "test": "vitest", + "test": "vitest run", "types": "tsc --emitDeclarationOnly --declarationDir types" }, "exports": { diff --git a/src/create-events.ts b/src/create-events.ts index 720e227..d4851c1 100644 --- a/src/create-events.ts +++ b/src/create-events.ts @@ -150,8 +150,8 @@ function createMissableEventRegistry( const registry = createRegistry() context.canvas.addEventListener(eventNameMap[type], nativeEvent => { - if (registry.array.length === 0) return const missedType = `${type}Missed` as const + if (registry.array.length === 0 && !context.props[type] && !context.props[missedType]) return // Track which objects have been visited during event processing const missedObjects = new Set(registry.array) @@ -214,7 +214,7 @@ function createMissableEventRegistry( getMeta(object)?.props[missedType]?.(missedEvent) } - if (visitedObjects.size > 0) { + if (intersections.length === 0) { context.props[`${type}Missed`]?.(missedEvent) } }) diff --git a/src/create-t.tsx b/src/create-t.tsx index c8757e6..143fb4a 100644 --- a/src/create-t.tsx +++ b/src/create-t.tsx @@ -1,7 +1,7 @@ import { createMemo, type Component, type JSX } from "solid-js" import { useProps } from "./props.ts" import type { Props } from "./types.ts" -import { meta } from "./utils.ts" +import { autodispose, meta } from "./utils.ts" /**********************************************************************************/ /* */ @@ -47,7 +47,7 @@ export function createEntity( // listen to key changes props.key try { - return meta(new (Constructor as any)(...(props.args ?? [])), { props }) + return meta(autodispose(new (Constructor as any)(...(props.args ?? []))), { props }) } catch (e) { console.error(e) throw new Error("") diff --git a/src/data-structure/loader-cache.ts b/src/data-structure/loader-cache.ts index a2dcb38..725c9b2 100644 --- a/src/data-structure/loader-cache.ts +++ b/src/data-structure/loader-cache.ts @@ -20,7 +20,7 @@ export interface LoaderRegistry { * @param url The URL or path to the resource * @param data The resource promise or resolved data */ - set>( + set>( loader: TLoader, url: LoaderUrl, data: PromiseMaybe>, @@ -32,7 +32,7 @@ export interface LoaderRegistry { * @param url The URL or path to the resource * @returns The resource promise, resolved data, or undefined if not found */ - get>( + get>( loader: TLoader, url: LoaderUrl, warn?: boolean, @@ -46,11 +46,11 @@ export interface LoaderRegistry { /**********************************************************************************/ interface LoaderTreeRegistryMap extends Map, any> { - get>(loader: TLoader): LoaderTreeRegistry | undefined - set>(loader: TLoader, data: LoaderTreeRegistry): this + get>(loader: TLoader): LoaderTreeRegistry | undefined + set>(loader: TLoader, data: LoaderTreeRegistry): this } -interface LoaderTreeRegistry> extends TreeRegistry { +interface LoaderTreeRegistry> extends TreeRegistry { get(paths: LoaderUrl, warn?: boolean): CacheNode set(paths: LoaderUrl, data: CacheNode): void } @@ -70,7 +70,7 @@ export class LoaderCache implements LoaderRegistry { * @returns The tree registry for this loader * @private */ - #registry>(loader: TLoader) { + #registry>(loader: TLoader) { let registry = this.#treeRegistryMap.get(loader) if (!registry) { this.#treeRegistryMap.set( @@ -129,7 +129,7 @@ export class LoaderCache implements LoaderRegistry { * @param url The URL or path to the resource * @param options.force Force deletion even if resource has active references */ - delete>( + delete>( loader: TLoader, url: LoaderUrl, options?: { force?: boolean }, @@ -151,7 +151,7 @@ export class LoaderCache implements LoaderRegistry { * @param url The URL or path to the resource * @returns The resource promise, resolved data, or undefined if not found */ - get>( + get>( loader: TLoader, url: LoaderUrl, warn?: boolean, @@ -171,7 +171,7 @@ export class LoaderCache implements LoaderRegistry { * @param options.force Force update even if resource already exists * @returns The stored promise */ - set>( + set>( loader: TLoader, path: LoaderUrl, data: PromiseMaybe>, diff --git a/src/hooks.ts b/src/hooks.ts index 4e9f459..5a5a9af 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -85,8 +85,8 @@ export function useThree(callback?: (value: Context) => any) { /** Global cache of loader instances to prevent duplicates */ const LOADER_CACHE = new Map< - Constructor>, - Loader + Constructor>, + Loader >() /** diff --git a/src/index.ts b/src/index.ts index f83ab62..888d451 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,4 +6,4 @@ export { useFrame, useLoader, useThree } from "./hooks.ts" export { useProps } from "./props.ts" export * from "./raycasters.tsx" export * as S3 from "./types.ts" -export { autodispose, getMeta, hasMeta as hasMeta, load, meta } from "./utils.ts" +export { autodispose, getMeta, hasMeta, load, meta } from "./utils.ts" diff --git a/src/types.ts b/src/types.ts index 0da8d44..9f4c12b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -120,11 +120,11 @@ export type ConstructorOverloadParameters = T extends { ? U : never -export type LoaderData> = T extends Loader +export type LoaderData> = T extends Loader ? TData : never -export type LoaderUrl> = T extends Loader +export type LoaderUrl> = T extends Loader ? TUrl : never diff --git a/src/utils.ts b/src/utils.ts index c45d93f..89064e0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -56,7 +56,7 @@ export const isVector3 = (def: object): def is Vector3 => "isVector3" in def && export function autodispose void }>(object: T): T { if (object.dispose) { - onCleanup(object.dispose.bind(object)) + onCleanup(() => object.dispose?.()) } return object } diff --git a/tests/core/canvas-events.test.tsx b/tests/core/canvas-events.test.tsx new file mode 100644 index 0000000..8856632 --- /dev/null +++ b/tests/core/canvas-events.test.tsx @@ -0,0 +1,727 @@ +import { fireEvent } from "@solidjs/testing-library" +import * as THREE from "three" +import { describe, expect, it, vi } from "vitest" +import { createT } from "../../src/index.ts" +import { test } from "../../src/testing/index.tsx" + +const T = createT(THREE) + +// offsetX/Y that hits the 2×2 BoxGeometry centred at origin (camera at z=5) +const HIT_X = 640 +const HIT_Y = 400 + +// offsetX/Y that misses the mesh (top-left corner of canvas) +const MISS_X = 0 +const MISS_Y = 0 + +function makeEvent(type: string, offsetX: number, offsetY: number) { + const event = new Event(type) + Object.defineProperty(event, "offsetX", { get: () => offsetX }) + Object.defineProperty(event, "offsetY", { get: () => offsetY }) + return event +} + +function hitEvent(type: string) { + return makeEvent(type, HIT_X, HIT_Y) +} + +function missEvent(type: string) { + return makeEvent(type, MISS_X, MISS_Y) +} + +/** A plain 2×2 mesh at origin with no event handlers. */ +const BasicMesh = () => ( + + + + +) + +/** A 2×2 mesh at origin whose onClick stops propagation. */ +const StoppingMesh = (props: { eventType: string; handler?: (e: any) => void }) => { + const handlerProp = { [props.eventType]: (e: any) => { e.stopPropagation(); props.handler?.(e) } } + return ( + + + + + ) +} + +/** A 2×2 mesh at origin that registers for an event without stopping propagation. */ +const ListeningMesh = (props: { eventType: string; handler?: (e: any) => void }) => { + const handlerProp = { [props.eventType]: (e: any) => props.handler?.(e) } + return ( + + + + + ) +} + +/**********************************************************************************/ +/* */ +/* Missable Events */ +/* */ +/**********************************************************************************/ + +describe("canvas missable events", () => { + // + // onClick + // + describe("onClick", () => { + it("fires when canvas is clicked and no meshes are in the scene", () => { + const handleClick = vi.fn() + const { canvas } = test(() => null, { onClick: handleClick }) + + fireEvent(canvas, hitEvent("click")) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it("fires when click propagates through a mesh that does not stop it", () => { + const handleClick = vi.fn() + const { canvas } = test( + () => , + { onClick: handleClick }, + ) + + fireEvent(canvas, hitEvent("click")) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it("fires when click misses all meshes", () => { + const handleClick = vi.fn() + const { canvas } = test( + () => , + { onClick: handleClick }, + ) + + fireEvent(canvas, missEvent("click")) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleClick = vi.fn() + const { canvas } = test( + () => , + { onClick: handleClick }, + ) + + fireEvent(canvas, hitEvent("click")) + + expect(handleClick).not.toHaveBeenCalled() + }) + }) + + // + // onClickMissed + // + describe("onClickMissed", () => { + it("fires when click misses all registered meshes", () => { + const handleClickMissed = vi.fn() + const { canvas } = test( + () => , + { onClickMissed: handleClickMissed }, + ) + + fireEvent(canvas, missEvent("click")) + + expect(handleClickMissed).toHaveBeenCalledTimes(1) + }) + + it("fires when canvas is clicked with no meshes in the scene", () => { + const handleClickMissed = vi.fn() + const { canvas } = test(() => null, { onClickMissed: handleClickMissed }) + + fireEvent(canvas, hitEvent("click")) + + expect(handleClickMissed).toHaveBeenCalledTimes(1) + }) + + it("does not fire when click hits a registered mesh", () => { + const handleClickMissed = vi.fn() + const { canvas } = test( + () => , + { onClickMissed: handleClickMissed }, + ) + + fireEvent(canvas, hitEvent("click")) + + expect(handleClickMissed).not.toHaveBeenCalled() + }) + + it("does not fire when onClick is also registered and click hits a mesh", () => { + const handleClick = vi.fn() + const handleClickMissed = vi.fn() + const { canvas } = test( + () => , + { onClick: handleClick, onClickMissed: handleClickMissed }, + ) + + fireEvent(canvas, hitEvent("click")) + + expect(handleClick).toHaveBeenCalledTimes(1) + expect(handleClickMissed).not.toHaveBeenCalled() + }) + }) + + // + // onDoubleClick + // + describe("onDoubleClick", () => { + it("fires when canvas is double-clicked and no meshes are in the scene", () => { + const handleDoubleClick = vi.fn() + const { canvas } = test(() => null, { onDoubleClick: handleDoubleClick }) + + fireEvent(canvas, hitEvent("dblclick")) + + expect(handleDoubleClick).toHaveBeenCalledTimes(1) + }) + + it("fires when double-click propagates through a mesh that does not stop it", () => { + const handleDoubleClick = vi.fn() + const { canvas } = test( + () => , + { onDoubleClick: handleDoubleClick }, + ) + + fireEvent(canvas, hitEvent("dblclick")) + + expect(handleDoubleClick).toHaveBeenCalledTimes(1) + }) + + it("fires when double-click misses all meshes", () => { + const handleDoubleClick = vi.fn() + const { canvas } = test( + () => , + { onDoubleClick: handleDoubleClick }, + ) + + fireEvent(canvas, missEvent("dblclick")) + + expect(handleDoubleClick).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleDoubleClick = vi.fn() + const { canvas } = test( + () => , + { onDoubleClick: handleDoubleClick }, + ) + + fireEvent(canvas, hitEvent("dblclick")) + + expect(handleDoubleClick).not.toHaveBeenCalled() + }) + }) + + // + // onDoubleClickMissed + // + describe("onDoubleClickMissed", () => { + it("fires when double-click misses all registered meshes", () => { + const handleMissed = vi.fn() + const { canvas } = test( + () => , + { onDoubleClickMissed: handleMissed }, + ) + + fireEvent(canvas, missEvent("dblclick")) + + expect(handleMissed).toHaveBeenCalledTimes(1) + }) + + it("fires when canvas is double-clicked with no meshes in the scene", () => { + const handleMissed = vi.fn() + const { canvas } = test(() => null, { onDoubleClickMissed: handleMissed }) + + fireEvent(canvas, hitEvent("dblclick")) + + expect(handleMissed).toHaveBeenCalledTimes(1) + }) + + it("does not fire when double-click hits a registered mesh", () => { + const handleMissed = vi.fn() + const { canvas } = test( + () => , + { onDoubleClickMissed: handleMissed }, + ) + + fireEvent(canvas, hitEvent("dblclick")) + + expect(handleMissed).not.toHaveBeenCalled() + }) + }) + + // + // onContextMenu + // + describe("onContextMenu", () => { + it("fires when canvas receives contextmenu and no meshes are in the scene", () => { + const handleContextMenu = vi.fn() + const { canvas } = test(() => null, { onContextMenu: handleContextMenu }) + + fireEvent(canvas, hitEvent("contextmenu")) + + expect(handleContextMenu).toHaveBeenCalledTimes(1) + }) + + it("fires when contextmenu propagates through a mesh that does not stop it", () => { + const handleContextMenu = vi.fn() + const { canvas } = test( + () => , + { onContextMenu: handleContextMenu }, + ) + + fireEvent(canvas, hitEvent("contextmenu")) + + expect(handleContextMenu).toHaveBeenCalledTimes(1) + }) + + it("fires when contextmenu misses all meshes", () => { + const handleContextMenu = vi.fn() + const { canvas } = test( + () => , + { onContextMenu: handleContextMenu }, + ) + + fireEvent(canvas, missEvent("contextmenu")) + + expect(handleContextMenu).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleContextMenu = vi.fn() + const { canvas } = test( + () => , + { onContextMenu: handleContextMenu }, + ) + + fireEvent(canvas, hitEvent("contextmenu")) + + expect(handleContextMenu).not.toHaveBeenCalled() + }) + }) + + // + // onContextMenuMissed + // + describe("onContextMenuMissed", () => { + it("fires when contextmenu misses all registered meshes", () => { + const handleMissed = vi.fn() + const { canvas } = test( + () => , + { onContextMenuMissed: handleMissed }, + ) + + fireEvent(canvas, missEvent("contextmenu")) + + expect(handleMissed).toHaveBeenCalledTimes(1) + }) + + it("fires when canvas receives contextmenu with no meshes in the scene", () => { + const handleMissed = vi.fn() + const { canvas } = test(() => null, { onContextMenuMissed: handleMissed }) + + fireEvent(canvas, hitEvent("contextmenu")) + + expect(handleMissed).toHaveBeenCalledTimes(1) + }) + + it("does not fire when contextmenu hits a registered mesh", () => { + const handleMissed = vi.fn() + const { canvas } = test( + () => , + { onContextMenuMissed: handleMissed }, + ) + + fireEvent(canvas, hitEvent("contextmenu")) + + expect(handleMissed).not.toHaveBeenCalled() + }) + }) +}) + +/**********************************************************************************/ +/* */ +/* Default Events */ +/* */ +/**********************************************************************************/ + +describe("canvas default events", () => { + // + // onMouseDown + // + describe("onMouseDown", () => { + it("fires when mousedown occurs with no meshes in the scene", () => { + const handleMouseDown = vi.fn() + const { canvas } = test(() => null, { onMouseDown: handleMouseDown }) + + fireEvent(canvas, hitEvent("mousedown")) + + expect(handleMouseDown).toHaveBeenCalledTimes(1) + }) + + it("fires when mousedown propagates through a mesh that does not stop it", () => { + const handleMouseDown = vi.fn() + const { canvas } = test( + () => , + { onMouseDown: handleMouseDown }, + ) + + fireEvent(canvas, hitEvent("mousedown")) + + expect(handleMouseDown).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleMouseDown = vi.fn() + const { canvas } = test( + () => , + { onMouseDown: handleMouseDown }, + ) + + fireEvent(canvas, hitEvent("mousedown")) + + expect(handleMouseDown).not.toHaveBeenCalled() + }) + }) + + // + // onMouseUp + // + describe("onMouseUp", () => { + it("fires when mouseup occurs with no meshes in the scene", () => { + const handleMouseUp = vi.fn() + const { canvas } = test(() => null, { onMouseUp: handleMouseUp }) + + fireEvent(canvas, hitEvent("mouseup")) + + expect(handleMouseUp).toHaveBeenCalledTimes(1) + }) + + it("fires when mouseup propagates through a mesh that does not stop it", () => { + const handleMouseUp = vi.fn() + const { canvas } = test( + () => , + { onMouseUp: handleMouseUp }, + ) + + fireEvent(canvas, hitEvent("mouseup")) + + expect(handleMouseUp).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleMouseUp = vi.fn() + const { canvas } = test( + () => , + { onMouseUp: handleMouseUp }, + ) + + fireEvent(canvas, hitEvent("mouseup")) + + expect(handleMouseUp).not.toHaveBeenCalled() + }) + }) + + // + // onPointerDown + // + describe("onPointerDown", () => { + it("fires when pointerdown occurs with no meshes in the scene", () => { + const handlePointerDown = vi.fn() + const { canvas } = test(() => null, { onPointerDown: handlePointerDown }) + + fireEvent(canvas, hitEvent("pointerdown")) + + expect(handlePointerDown).toHaveBeenCalledTimes(1) + }) + + it("fires when pointerdown propagates through a mesh that does not stop it", () => { + const handlePointerDown = vi.fn() + const { canvas } = test( + () => , + { onPointerDown: handlePointerDown }, + ) + + fireEvent(canvas, hitEvent("pointerdown")) + + expect(handlePointerDown).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handlePointerDown = vi.fn() + const { canvas } = test( + () => , + { onPointerDown: handlePointerDown }, + ) + + fireEvent(canvas, hitEvent("pointerdown")) + + expect(handlePointerDown).not.toHaveBeenCalled() + }) + }) + + // + // onPointerUp + // + describe("onPointerUp", () => { + it("fires when pointerup occurs with no meshes in the scene", () => { + const handlePointerUp = vi.fn() + const { canvas } = test(() => null, { onPointerUp: handlePointerUp }) + + fireEvent(canvas, hitEvent("pointerup")) + + expect(handlePointerUp).toHaveBeenCalledTimes(1) + }) + + it("fires when pointerup propagates through a mesh that does not stop it", () => { + const handlePointerUp = vi.fn() + const { canvas } = test( + () => , + { onPointerUp: handlePointerUp }, + ) + + fireEvent(canvas, hitEvent("pointerup")) + + expect(handlePointerUp).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handlePointerUp = vi.fn() + const { canvas } = test( + () => , + { onPointerUp: handlePointerUp }, + ) + + fireEvent(canvas, hitEvent("pointerup")) + + expect(handlePointerUp).not.toHaveBeenCalled() + }) + }) + + // + // onWheel + // + describe("onWheel", () => { + it("fires when wheel event occurs with no meshes in the scene", () => { + const handleWheel = vi.fn() + const { canvas } = test(() => null, { onWheel: handleWheel }) + + const event = new WheelEvent("wheel", { deltaY: 100 }) + Object.defineProperty(event, "offsetX", { get: () => HIT_X }) + Object.defineProperty(event, "offsetY", { get: () => HIT_Y }) + fireEvent(canvas, event) + + expect(handleWheel).toHaveBeenCalledTimes(1) + }) + + it("fires when wheel event propagates through a mesh that does not stop it", () => { + const handleWheel = vi.fn() + const { canvas } = test( + () => , + { onWheel: handleWheel }, + ) + + const event = new WheelEvent("wheel", { deltaY: 100 }) + Object.defineProperty(event, "offsetX", { get: () => HIT_X }) + Object.defineProperty(event, "offsetY", { get: () => HIT_Y }) + fireEvent(canvas, event) + + expect(handleWheel).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleWheel = vi.fn() + const { canvas } = test( + () => , + { onWheel: handleWheel }, + ) + + const event = new WheelEvent("wheel", { deltaY: 100 }) + Object.defineProperty(event, "offsetX", { get: () => HIT_X }) + Object.defineProperty(event, "offsetY", { get: () => HIT_Y }) + fireEvent(canvas, event) + + expect(handleWheel).not.toHaveBeenCalled() + }) + }) +}) + +/**********************************************************************************/ +/* */ +/* Hover Events */ +/* */ +/**********************************************************************************/ + +describe("canvas hover events", () => { + // + // onPointerEnter / onPointerLeave / onPointerMove + // + describe("onPointerEnter", () => { + it("fires when the pointer first moves over the canvas", () => { + const handlePointerEnter = vi.fn() + const { canvas } = test(() => null, { onPointerEnter: handlePointerEnter }) + + fireEvent(canvas, hitEvent("pointermove")) + + expect(handlePointerEnter).toHaveBeenCalledTimes(1) + }) + + it("fires only once per canvas hover session", () => { + const handlePointerEnter = vi.fn() + const { canvas } = test(() => null, { onPointerEnter: handlePointerEnter }) + + fireEvent(canvas, hitEvent("pointermove")) + fireEvent(canvas, hitEvent("pointermove")) + fireEvent(canvas, hitEvent("pointermove")) + + expect(handlePointerEnter).toHaveBeenCalledTimes(1) + }) + + it("fires again after the pointer has left and re-entered", () => { + const handlePointerEnter = vi.fn() + const { canvas } = test(() => null, { onPointerEnter: handlePointerEnter }) + + fireEvent(canvas, hitEvent("pointermove")) + fireEvent(canvas, makeEvent("pointerleave", HIT_X, HIT_Y)) + fireEvent(canvas, hitEvent("pointermove")) + + expect(handlePointerEnter).toHaveBeenCalledTimes(2) + }) + }) + + describe("onPointerLeave", () => { + it("fires when the pointer leaves the canvas", () => { + const handlePointerLeave = vi.fn() + const { canvas } = test(() => null, { onPointerLeave: handlePointerLeave }) + + fireEvent(canvas, hitEvent("pointermove")) + fireEvent(canvas, makeEvent("pointerleave", HIT_X, HIT_Y)) + + expect(handlePointerLeave).toHaveBeenCalledTimes(1) + }) + }) + + describe("onPointerMove", () => { + it("fires when the pointer moves over the canvas with no meshes", () => { + const handlePointerMove = vi.fn() + const { canvas } = test(() => null, { onPointerMove: handlePointerMove }) + + fireEvent(canvas, hitEvent("pointermove")) + + expect(handlePointerMove).toHaveBeenCalledTimes(1) + }) + + it("fires when pointer move propagates through a mesh that does not stop it", () => { + const handlePointerMove = vi.fn() + const { canvas } = test( + () => , + { onPointerMove: handlePointerMove }, + ) + + fireEvent(canvas, hitEvent("pointermove")) + + expect(handlePointerMove).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handlePointerMove = vi.fn() + const { canvas } = test( + () => , + { onPointerMove: handlePointerMove }, + ) + + fireEvent(canvas, hitEvent("pointermove")) + + expect(handlePointerMove).not.toHaveBeenCalled() + }) + }) + + // + // onMouseEnter / onMouseLeave / onMouseMove + // + describe("onMouseEnter", () => { + it("fires when the mouse first moves over the canvas", () => { + const handleMouseEnter = vi.fn() + const { canvas } = test(() => null, { onMouseEnter: handleMouseEnter }) + + fireEvent(canvas, hitEvent("mousemove")) + + expect(handleMouseEnter).toHaveBeenCalledTimes(1) + }) + + it("fires only once per canvas hover session", () => { + const handleMouseEnter = vi.fn() + const { canvas } = test(() => null, { onMouseEnter: handleMouseEnter }) + + fireEvent(canvas, hitEvent("mousemove")) + fireEvent(canvas, hitEvent("mousemove")) + fireEvent(canvas, hitEvent("mousemove")) + + expect(handleMouseEnter).toHaveBeenCalledTimes(1) + }) + + it("fires again after the mouse has left and re-entered", () => { + const handleMouseEnter = vi.fn() + const { canvas } = test(() => null, { onMouseEnter: handleMouseEnter }) + + fireEvent(canvas, hitEvent("mousemove")) + fireEvent(canvas, makeEvent("mouseleave", HIT_X, HIT_Y)) + fireEvent(canvas, hitEvent("mousemove")) + + expect(handleMouseEnter).toHaveBeenCalledTimes(2) + }) + }) + + describe("onMouseLeave", () => { + it("fires when the mouse leaves the canvas", () => { + const handleMouseLeave = vi.fn() + const { canvas } = test(() => null, { onMouseLeave: handleMouseLeave }) + + fireEvent(canvas, hitEvent("mousemove")) + fireEvent(canvas, makeEvent("mouseleave", HIT_X, HIT_Y)) + + expect(handleMouseLeave).toHaveBeenCalledTimes(1) + }) + }) + + describe("onMouseMove", () => { + it("fires when the mouse moves over the canvas with no meshes", () => { + const handleMouseMove = vi.fn() + const { canvas } = test(() => null, { onMouseMove: handleMouseMove }) + + fireEvent(canvas, hitEvent("mousemove")) + + expect(handleMouseMove).toHaveBeenCalledTimes(1) + }) + + it("fires when mouse move propagates through a mesh that does not stop it", () => { + const handleMouseMove = vi.fn() + const { canvas } = test( + () => , + { onMouseMove: handleMouseMove }, + ) + + fireEvent(canvas, hitEvent("mousemove")) + + expect(handleMouseMove).toHaveBeenCalledTimes(1) + }) + + it("does not fire when a mesh stops propagation", () => { + const handleMouseMove = vi.fn() + const { canvas } = test( + () => , + { onMouseMove: handleMouseMove }, + ) + + fireEvent(canvas, hitEvent("mousemove")) + + expect(handleMouseMove).not.toHaveBeenCalled() + }) + }) +}) diff --git a/tests/core/disposal.test.tsx b/tests/core/disposal.test.tsx new file mode 100644 index 0000000..442811f --- /dev/null +++ b/tests/core/disposal.test.tsx @@ -0,0 +1,139 @@ +import { Show, createSignal } from "solid-js" +import * as THREE from "three" +import { beforeEach, describe, expect, it, vi } from "vitest" +import { Entity, createT } from "../../src/index.ts" +import { autodispose } from "../../src/index.ts" +import { test } from "../../src/testing/index.tsx" + +const T = createT(THREE) + +describe("autodispose", () => { + it("calls dispose() on the object when the owner scope is cleaned up", async () => { + const disposable = { dispose: vi.fn() } + + const { unmount } = await test(() => { + autodispose(disposable) + return null + }) + + unmount() + + + expect(disposable.dispose).toHaveBeenCalledTimes(1) + }) + + it("does not throw when the object has no dispose method", async () => { + const noDispose = {} + + await expect(async () => { + const { unmount } = await test(() => { + autodispose(noDispose as any) + return null + }) + unmount() + + }).not.toThrow() + }) +}) + +describe("Entity disposal", () => { + it("disposes a constructor-created instance when the component unmounts", async () => { + class DisposableGeometry extends THREE.BoxGeometry { + dispose = vi.fn(() => super.dispose()) + } + + const [visible, setVisible] = createSignal(true) + + let geometry!: DisposableGeometry + + await test(() => ( + + { + geometry = g as DisposableGeometry + }} + /> + + )) + + expect(geometry).toBeDefined() + expect(geometry.dispose).not.toHaveBeenCalled() + + setVisible(false) + + + expect(geometry.dispose).toHaveBeenCalledTimes(1) + }) + + it("does not dispose an existing instance passed via from=instance", async () => { + const geometry = new THREE.BoxGeometry() + geometry.dispose = vi.fn(geometry.dispose.bind(geometry)) + + const [visible, setVisible] = createSignal(true) + + await test(() => ( + + + + )) + + setVisible(false) + + + expect(geometry.dispose).not.toHaveBeenCalled() + }) +}) + +describe("T component disposal", () => { + it("disposes geometry created via T when the component unmounts", async () => { + const [visible, setVisible] = createSignal(true) + let geometry!: THREE.BoxGeometry + + await test(() => ( + + + { + geometry = g + }} + /> + + + + )) + + geometry.dispose = vi.fn(geometry.dispose.bind(geometry)) + + setVisible(false) + + + expect(geometry.dispose).toHaveBeenCalledTimes(1) + }) + + it("disposes material created via T when the component unmounts", async () => { + const [visible, setVisible] = createSignal(true) + let material!: THREE.MeshBasicMaterial + + await test(() => ( + + + + { + material = m + }} + /> + + + )) + + material.dispose = vi.fn(material.dispose.bind(material)) + + setVisible(false) + + + expect(material.dispose).toHaveBeenCalledTimes(1) + }) +}) diff --git a/tests/core/events.test.tsx b/tests/core/events.test.tsx index e5bd0bc..d3d910e 100644 --- a/tests/core/events.test.tsx +++ b/tests/core/events.test.tsx @@ -388,3 +388,156 @@ describe("events", () => { }) }) }) + +/**********************************************************************************/ +/* */ +/* Mesh-level onClickMissed */ +/* */ +/**********************************************************************************/ + +const HIT_X = 640 +const HIT_Y = 400 +const MISS_X = 0 +const MISS_Y = 0 + +function makeClickAt(offsetX: number, offsetY: number) { + const event = new Event("click") + Object.defineProperty(event, "offsetX", { get: () => offsetX }) + Object.defineProperty(event, "offsetY", { get: () => offsetY }) + return event +} + +describe("mesh onClickMissed", () => { + it("fires when a click misses the mesh", async () => { + const handleClickMissed = vi.fn() + + const { canvas } = await test(() => ( + + + + + )) + + fireEvent(canvas, makeClickAt(MISS_X, MISS_Y)) + + + expect(handleClickMissed).toHaveBeenCalledTimes(1) + }) + + it("does not fire when the mesh itself is clicked", async () => { + const handleClickMissed = vi.fn() + + const { canvas } = await test(() => ( + + + + + )) + + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + + expect(handleClickMissed).not.toHaveBeenCalled() + }) + + it("does not fire when a different mesh in the scene is clicked", async () => { + const handleClickMissed = vi.fn() + + // Mesh A: off-center (far right), has onClickMissed + // Mesh B: at origin (center of screen), gets clicked + const { canvas } = await test(() => ( + <> + + + + + + + + + + )) + + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + expect(handleClickMissed).not.toHaveBeenCalled() + }) + + it("does not fire on a parent when its child is clicked", async () => { + const handleParentClickMissed = vi.fn() + const handleChildClick = vi.fn() + + const { canvas } = await test(() => ( + + + + + + + )) + + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + + expect(handleChildClick).toHaveBeenCalledTimes(1) + expect(handleParentClickMissed).not.toHaveBeenCalled() + }) +}) + +/**********************************************************************************/ +/* */ +/* Event handler reactivity */ +/* */ +/**********************************************************************************/ + +describe("event handler reactivity", () => { + it("registers object in interaction list when event prop is added", async () => { + const handleClick = vi.fn() + const [onClick, setOnClick] = createSignal<((e: any) => void) | undefined>(undefined) + + const { canvas } = await test(() => ( + + + + + )) + + // No handler yet — click should not fire + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + expect(handleClick).not.toHaveBeenCalled() + + // Add the handler reactively + setOnClick(() => handleClick) + + + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it("removes object from interaction list when event prop is removed", async () => { + const handleClick = vi.fn() + const [onClick, setOnClick] = createSignal<((e: any) => void) | undefined>(handleClick) + + const { canvas } = await test(() => ( + + + + + )) + + // Handler active — click fires + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + expect(handleClick).toHaveBeenCalledTimes(1) + + // Remove handler reactively + setOnClick(undefined) + + + fireEvent(canvas, makeClickAt(HIT_X, HIT_Y)) + + expect(handleClick).toHaveBeenCalledTimes(1) // no new call + }) +}) diff --git a/tests/core/use-loader.test.tsx b/tests/core/use-loader.test.tsx new file mode 100644 index 0000000..8e999d9 --- /dev/null +++ b/tests/core/use-loader.test.tsx @@ -0,0 +1,183 @@ +import { createSignal } from "solid-js" +import * as THREE from "three" +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" +import { useLoader } from "../../src/index.ts" +import { LoaderCache } from "../../src/data-structure/loader-cache.ts" +import { test } from "../../src/testing/index.tsx" +import { asyncUtils } from "../utils/async-utils.ts" + +/**********************************************************************************/ +/* */ +/* Mock Loader */ +/* */ +/**********************************************************************************/ + +class MockResource { + constructor(public readonly url: string) {} + dispose = vi.fn() +} + +class MockLoader extends THREE.Loader { + load( + url: string, + onLoad: (result: MockResource) => void, + _onProgress?: (event: ProgressEvent) => void, + _onError?: (event: unknown) => void, + ) { + onLoad(new MockResource(url)) + } +} + +/**********************************************************************************/ +/* */ +/* Async helpers */ +/* */ +/**********************************************************************************/ + +const resolvers: (() => void)[] = [] +const { waitFor } = asyncUtils(resolver => resolvers.push(resolver)) + +/**********************************************************************************/ +/* */ +/* Tests */ +/* */ +/**********************************************************************************/ + +beforeEach(() => { + useLoader.cache = new LoaderCache() +}) + +afterEach(() => { + useLoader.cache = new LoaderCache() +}) + +describe("useLoader", () => { + it("returns the loaded resource", async () => { + let resource: (() => MockResource | undefined) | undefined + + function Component() { + resource = useLoader(MockLoader, "texture.png") as () => MockResource | undefined + return null + } + + test(() => ) + + await waitFor(() => resource?.() !== undefined) + + expect(resource?.()).toBeInstanceOf(MockResource) + expect(resource?.()?.url).toBe("texture.png") + }) + + it("caches the resource — same loader and URL return the same instance", async () => { + let first: (() => MockResource | undefined) | undefined + let second: (() => MockResource | undefined) | undefined + + function Component() { + first = useLoader(MockLoader, "texture.png") as () => MockResource | undefined + second = useLoader(MockLoader, "texture.png") as () => MockResource | undefined + return null + } + + test(() => ) + + await waitFor(() => first?.() !== undefined && second?.() !== undefined) + + expect(first?.()).toBeDefined() + expect(second?.()).toBeDefined() + expect(first?.()).toBe(second?.()) + }) + + it("returns distinct instances for different URLs", async () => { + let first: (() => MockResource | undefined) | undefined + let second: (() => MockResource | undefined) | undefined + + function Component() { + first = useLoader(MockLoader, "a.png") as () => MockResource | undefined + second = useLoader(MockLoader, "b.png") as () => MockResource | undefined + return null + } + + test(() => ) + + await waitFor(() => first?.() !== undefined && second?.() !== undefined) + + expect(first?.()?.url).toBe("a.png") + expect(second?.()?.url).toBe("b.png") + expect(first?.()).not.toBe(second?.()) + }) + + it("reloads when the URL changes reactively", async () => { + const [url, setUrl] = createSignal("first.png") + let resource: (() => MockResource | undefined) | undefined + + function Component() { + resource = useLoader(MockLoader, url) as () => MockResource | undefined + return null + } + + test(() => ) + + await waitFor(() => resource?.()?.url === "first.png") + + setUrl("second.png") + + await waitFor(() => resource?.()?.url === "second.png") + + expect(resource?.()?.url).toBe("second.png") + }) + + it("bypasses the cache when cache: false", async () => { + let first: (() => MockResource | undefined) | undefined + let second: (() => MockResource | undefined) | undefined + + function Component() { + first = useLoader(MockLoader, "texture.png", { cache: false }) as () => MockResource | undefined + second = useLoader(MockLoader, "texture.png", { cache: false }) as () => MockResource | undefined + return null + } + + test(() => ) + + await waitFor(() => first?.() !== undefined && second?.() !== undefined) + + expect(first?.()).toBeDefined() + expect(second?.()).toBeDefined() + expect(first?.()).not.toBe(second?.()) + }) + + it("calls onLoad callback after the resource is loaded", async () => { + const handleLoad = vi.fn() + let resource: (() => MockResource | undefined) | undefined + + function Component() { + resource = useLoader(MockLoader, "texture.png", { onLoad: handleLoad }) as () => MockResource | undefined + return null + } + + test(() => ) + + await waitFor(() => resource?.() !== undefined) + + expect(handleLoad).toHaveBeenCalledTimes(1) + expect(handleLoad).toHaveBeenCalledWith(expect.objectContaining({ url: "texture.png" })) + }) + + it("loads a record of URLs and returns a matching record of resources", async () => { + let resource: (() => Record | undefined) | undefined + + function Component() { + resource = useLoader(MockLoader, { diffuse: "diffuse.png", normal: "normal.png" }) as () => + Record | undefined + return null + } + + test(() => ) + + await waitFor(() => resource?.()?.diffuse !== undefined && resource?.()?.normal !== undefined) + + expect(resource?.()?.diffuse).toBeInstanceOf(MockResource) + expect(resource?.()?.normal).toBeInstanceOf(MockResource) + expect(resource?.()?.diffuse.url).toBe("diffuse.png") + expect(resource?.()?.normal.url).toBe("normal.png") + }) +}) diff --git a/tsconfig.json b/tsconfig.json index e4c2bdd..4c26156 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,5 +18,5 @@ "target": "ESNext", "verbatimModuleSyntax": true }, - "exclude": ["dist", "build", "node_modules"] + "exclude": ["dist", "build", "node_modules", "demo"] }