Peter il y a 1 an
Parent
commit
33daa91b9e
59 fichiers modifiés avec 1389 ajouts et 1278 suppressions
  1. 2 2
      docs/.vitepress/theme/components/LocalOrbitControls.vue
  2. 1 1
      docs/.vitepress/theme/components/StackBlitzEmbed.vue
  3. 1 1
      docs/.vitepress/theme/index.ts
  4. 2 1
      package.json
  5. 1 1
      playground/nuxt/components/EnvironmentLocal.vue
  6. 1 1
      playground/vue/src/composables/useFBX.ts
  7. 2 2
      playground/vue/src/composables/useGLTF.ts
  8. 1 1
      playground/vue/src/pages/advanced/devicePixelRatio/index.vue
  9. 2 2
      playground/vue/src/pages/advanced/fbo/useFBO.ts
  10. 2 2
      playground/vue/src/pages/basic/Lights.vue
  11. 2 2
      playground/vue/src/pages/basic/ready/index.vue
  12. 1 1
      playground/vue/src/pages/cameras/multipleCameras/index.vue
  13. 1 1
      playground/vue/src/pages/events/Blocking.vue
  14. 1 1
      playground/vue/src/pages/events/Connect.vue
  15. 1 1
      playground/vue/src/pages/events/DeprecatedEventNames.vue
  16. 0 156
      playground/vue/src/pages/events/EventModifierSelf.vue
  17. 0 116
      playground/vue/src/pages/events/EventModifierStop.vue
  18. 195 0
      playground/vue/src/pages/events/EventModifiers.vue
  19. 2 2
      playground/vue/src/pages/events/Filter.vue
  20. 1 1
      playground/vue/src/pages/events/NoEvents.vue
  21. 64 0
      playground/vue/src/pages/events/OnCanvasLeave.vue
  22. 1 1
      playground/vue/src/pages/events/PointerEnterLeave.vue
  23. 29 19
      playground/vue/src/pages/events/PointerOverOut.vue
  24. 1 1
      playground/vue/src/pages/events/RemoveInteractivity.vue
  25. 1 1
      playground/vue/src/pages/events/StopPropagation.vue
  26. 1 1
      playground/vue/src/pages/events/TargetEnabled.vue
  27. 1 1
      playground/vue/src/pages/events/index.vue
  28. 1 1
      playground/vue/src/pages/issues/701/TheExperience.vue
  29. 8 8
      playground/vue/src/router/routes/events.ts
  30. 126 89
      pnpm-lock.yaml
  31. 12 12
      src/components/TresCanvas.vue
  32. 4 4
      src/composables/useCamera/index.ts
  33. 1 1
      src/composables/useLoader/index.ts
  34. 1 1
      src/composables/useLoop/index.ts
  35. 2 2
      src/composables/useRenderLoop/index.ts
  36. 7 7
      src/composables/useRenderer/index.ts
  37. 1 1
      src/composables/useSizes/index.ts
  38. 1 1
      src/composables/useTexture/component.vue
  39. 1 1
      src/composables/useTexture/index.ts
  40. 6 6
      src/composables/useTresContextProvider/index.ts
  41. 1 1
      src/composables/useTresReady/createReadyEventHook/index.ts
  42. 1 1
      src/composables/useTresReady/index.ts
  43. 1 1
      src/core/catalogue.ts
  44. 1 1
      src/core/loop.test.ts
  45. 3 3
      src/core/loop.ts
  46. 3 3
      src/core/nodeOps.test.ts
  47. 2 3
      src/core/nodeOps.ts
  48. 5 5
      src/devtools/plugin.ts
  49. 2 2
      src/directives/vDistanceTo.ts
  50. 4 4
      src/directives/vLightHelper.ts
  51. 2 2
      src/utils/createEventManager/createEventManager.test.ts
  52. 781 741
      src/utils/createEventManager/eventsRaycast.test.ts
  53. 88 51
      src/utils/createEventManager/eventsRaycast.ts
  54. 1 1
      src/utils/createEventManager/useEventsOptions.test.ts
  55. 1 1
      src/utils/createEventManager/useEventsOptions.ts
  56. 1 1
      src/utils/createPriorityEventHook.ts
  57. 2 2
      src/utils/index.ts
  58. 1 1
      src/utils/normalize.ts
  59. 1 1
      src/utils/test-utils.ts

+ 2 - 2
docs/.vitepress/theme/components/LocalOrbitControls.vue

@@ -1,10 +1,10 @@
 <script lang="ts" setup>
+import type { TresVector3 } from '@tresjs/core'
+import type { Camera } from 'three'
 import { extend, useRenderLoop, useTresContext } from '@tresjs/core'
 import { useEventListener } from '@vueuse/core'
 import { OrbitControls } from 'three-stdlib'
 import { onMounted, onUnmounted, shallowRef, unref } from 'vue'
-import type { TresVector3 } from '@tresjs/core'
-import type { Camera } from 'three'
 
 export interface OrbitControlsProps {
   /**

+ 1 - 1
docs/.vitepress/theme/components/StackBlitzEmbed.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import type { EmbedOptions } from '@stackblitz/sdk'
 import sdk from '@stackblitz/sdk'
 import { ref, watch } from 'vue'
-import type { EmbedOptions } from '@stackblitz/sdk'
 
 const props = withDefaults(
   defineProps<{

+ 1 - 1
docs/.vitepress/theme/index.ts

@@ -1,5 +1,5 @@
-import VPTheme from 'vitepress/theme'
 import type { Theme } from 'vitepress'
+import VPTheme from 'vitepress/theme'
 
 import TresLayout from './TresLayout.vue'
 import './custom.css'

+ 2 - 1
package.json

@@ -52,7 +52,8 @@
     "dev": "cd playground/vue && npm run dev",
     "dev:nuxt": "cd playground/nuxt && npm run dev",
     "build": "vite build",
-    "test": "vitest",
+    "test": "vitest --silent",
+    "test:noisy": "vitest",
     "test:ci": "vitest run",
     "test:ui": "vitest --ui --coverage.enabled=true",
     "release": "release-it",

+ 1 - 1
playground/nuxt/components/EnvironmentLocal.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
+import type { LoaderProto } from '@tresjs/core'
 import { useLoader, useTresContext } from '@tresjs/core'
 import { CubeReflectionMapping, type CubeTexture, CubeTextureLoader, EquirectangularReflectionMapping, type Texture } from 'three'
 import { RGBELoader } from 'three-stdlib'
-import type { LoaderProto } from '@tresjs/core'
 
 /* const files = ref(['/px.jpg', '/nx.jpg', '/py.jpg', '/ny.jpg', '/pz.jpg', '/nz.jpg']) */
 const files = ref('venice/venice_sunset_1k.hdr')

+ 1 - 1
playground/vue/src/composables/useFBX.ts

@@ -1,6 +1,6 @@
+import type { Object3D } from 'three'
 import { useLoader } from '@tresjs/core'
 import { FBXLoader } from 'three-stdlib'
-import type { Object3D } from 'three'
 
 /**
  * Loads an FBX file and returns a THREE.Object3D.

+ 2 - 2
playground/vue/src/composables/useGLTF.ts

@@ -1,7 +1,7 @@
-import { type TresLoader, type TresObject3D, useLoader } from '@tresjs/core'
-import { DRACOLoader, GLTFLoader } from 'three-stdlib'
 import type { AnimationClip, Material, Scene } from 'three'
 import type { GLTF } from 'three-stdlib'
+import { type TresLoader, type TresObject3D, useLoader } from '@tresjs/core'
+import { DRACOLoader, GLTFLoader } from 'three-stdlib'
 
 export interface GLTFLoaderOptions {
   /**

+ 1 - 1
playground/vue/src/pages/advanced/devicePixelRatio/index.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import type { WebGLRenderer } from 'three'
 import { TresCanvas } from '@tresjs/core'
 import { shallowRef } from 'vue'
-import type { WebGLRenderer } from 'three'
 
 const rendererRef = shallowRef<WebGLRenderer | null>(null)
 const minDpr = 1

+ 2 - 2
playground/vue/src/pages/advanced/fbo/useFBO.ts

@@ -1,10 +1,10 @@
+import type { Camera, WebGLRenderTargetOptions } from 'three'
+import type { Ref } from 'vue'
 /* eslint-disable no-console */
 import { useLoop, useTresContext } from '@tresjs/core'
 import { useThrottleFn } from '@vueuse/core'
 import { DepthTexture, FloatType, HalfFloatType, LinearFilter, WebGLRenderTarget } from 'three'
 import { isReactive, onBeforeUnmount, reactive, ref, toRefs, watchEffect } from 'vue'
-import type { Camera, WebGLRenderTargetOptions } from 'three'
-import type { Ref } from 'vue'
 
 export interface FboOptions {
   /*

+ 2 - 2
playground/vue/src/pages/basic/Lights.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
+import type { TresObject } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas, vDistanceTo, vLightHelper, vLog } from '@tresjs/core'
-import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
 
-import type { TresObject } from '@tresjs/core'
+import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
 
 const gl = {
   clearColor: '#82DBC5',

+ 2 - 2
playground/vue/src/pages/basic/ready/index.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import { TresCanvas } from '@tresjs/core'
-import { ref } from 'vue'
 import type { TresContext } from '@tresjs/core'
 import type { ShallowRef } from 'vue'
+import { TresCanvas } from '@tresjs/core'
+import { ref } from 'vue'
 import LoopCallbackWatcher from './LoopCallbackWatcher.vue'
 import OnTresReadyWatcher from './OnTresReadyWatcher.vue'
 

+ 1 - 1
playground/vue/src/pages/cameras/multipleCameras/index.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
+import type { Camera } from 'three'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { TresLeches, useControls } from '@tresjs/leches'
-import type { Camera } from 'three'
 import TheCameraOperator from './TheCameraOperator.vue'
 import '@tresjs/leches/styles'
 

+ 1 - 1
playground/vue/src/pages/events/Blocking.vue

@@ -1,9 +1,9 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { Vector3 } from 'three'
-import type { ThreeEvent } from '@tresjs/core'
 
 function onClick(ev: ThreeEvent<MouseEvent>) {
   ev.eventObject.material.color.set('red')

+ 1 - 1
playground/vue/src/pages/events/Connect.vue

@@ -1,9 +1,9 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent, TresContext } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
-import type { ThreeEvent, TresContext } from '@tresjs/core'
 
 const gl = {
   clearColor: '#202020',

+ 1 - 1
playground/vue/src/pages/events/DeprecatedEventNames.vue

@@ -1,8 +1,8 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
-import type { ThreeEvent } from '@tresjs/core'
 import '@tresjs/leches/styles'
 
 function onClick(ev: ThreeEvent<MouseEvent>) {

+ 0 - 156
playground/vue/src/pages/events/EventModifierSelf.vue

@@ -1,156 +0,0 @@
-<!-- eslint-disable no-console -->
-<script setup lang="ts">
-import { OrbitControls } from '@tresjs/cientos'
-import { TresCanvas } from '@tresjs/core'
-import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
-import type { ThreeEvent } from '@tresjs/core'
-import type { DomEventName } from '../../../../../src/utils/createEventManager/const'
-
-const gl = {
-  clearColor: '#202020',
-  shadows: true,
-  alpha: false,
-  shadowMapType: BasicShadowMap,
-  outputColorSpace: SRGBColorSpace,
-  toneMapping: NoToneMapping,
-}
-
-const called = reactive<Partial<Record<DomEventName, boolean>>>({
-  click: false,
-  contextmenu: false,
-  dblclick: false,
-  pointerdown: false,
-  pointerup: false,
-  pointermove: false,
-  pointerover: false,
-  pointerout: false,
-  pointerenter: false,
-  pointerleave: false,
-  pointermissed: false,
-  wheel: false,
-})
-
-const calledWithSelf = reactive<Partial<Record<DomEventName, boolean>>>({
-  click: false,
-  contextmenu: false,
-  dblclick: false,
-  pointerdown: false,
-  pointerup: false,
-  pointermove: false,
-  pointerover: false,
-  pointerout: false,
-  pointerenter: false,
-  pointerleave: false,
-  pointermissed: false,
-  wheel: false,
-})
-
-const error = reactive<Partial<Record<DomEventName, boolean>>>({
-  click: false,
-  contextmenu: false,
-  dblclick: false,
-  pointerdown: false,
-  pointerup: false,
-  pointermove: false,
-  pointerover: false,
-  pointerout: false,
-  pointerenter: false,
-  pointerleave: false,
-  pointermissed: false,
-  wheel: false,
-})
-
-function onErrorIfNotSelf(name: DomEventName, e: ThreeEvent<any>) {
-  console.log('self')
-  if (e.target !== e.currentTarget) { error[name] = true }
-}
-
-function fn(name: DomEventName, e: ThreeEvent<any>) {
-  called[name] = true
-  calledWithSelf[name] = e.target === e.currentTarget
-}
-</script>
-
-<template>
-  <OverlayInfo
-    @click.middle="console.log"
-    @click.self="console.log"
-  >
-    <h1>Event Modifiers: self</h1>
-    <p>Tres supports the Vue event modifier <code>self</code></p>
-    <p>E.g.: <code>@click.self="..."</code></p>
-    <h2>Tests</h2>
-    <p>Interact with the 3D object. It has all supported pointer events + <code>self</code>. If events are passed to the containing TresGroup, an error will appear below.</p>
-    <table>
-      <tr v-for="domName of Object.keys(called)" :key="domName">
-        <td>
-          <span v-if="error[domName as DomEventName]">❌</span>
-          <span v-else-if="called[domName as DomEventName]">✅</span>
-          <span v-else> </span>
-          {{ domName }}
-        </td>
-        <td>
-          <span v-if="calledWithSelf[domName as DomEventName]">e.target === e.currentTarget</span>
-        </td>
-      </tr>
-    </table>
-    <p v-if="Object.values(called).every(b => b) && Object.values(error).every(b => !b)"><strong>✅ All tests pass</strong></p>
-    <hr />
-    <h2>NOTE</h2>
-    <p><code>pointermissed</code> is always a "self" event. (No Vue DOM equivalent.)</p>
-    <p><code>pointer{enter,leave}</code> are always "self" events.</p>
-    <a href="https://play.vuejs.org/#eNqNk8tuwjAQRX/F8oZWisKiXaG06kMs2kWL2i69iZIhmDq25UeKhPj3juMkIECBTTSeO9e+J7K39FnrtPFAZzSzheHaEQvO60cms2lsYIkLB7UWuQNcEZKVvCGheNKKSwcGwie1IJYPjBZKWiUgFaq6mSiPCtnrk1tGD50C8gZGnHv92Kma0SMH+cTn3ZitU3tXy3tAfA0zl/Is8zXU0XtKfQV3tJ5wXybvjEfkyL7wjnReskLiJAZLSC5LUqyg+CX9RtkU/1C8HF2VTYc7QxPqLE4ueZWurZJ427ZhNOSoNRdgPrXjuBOjM9IqQcuFUH/vbc8ZD0nfbw8+01/bTegxujBgwTTA6KC53FTgojz//oAN1oNYq9ILnB4RvwApfcgYx168LDH2wVyb9q3Wyjguqx873ziQtocKQcPkrp1nFJ/c6wj6Pu5det/6mNzR3T8WqD96">
-      See Vue DOM behavior.
-    </a>
-  </OverlayInfo>
-  <TresCanvas
-    window-size
-    v-bind="gl"
-  >
-    <TresPerspectiveCamera
-      :position="[11, 11, 11]"
-      :look-at="[0, 0, 0]"
-    />
-    <TresGroup
-      @click.middle="console.log"
-      @contextmenu.middle="console.log"
-      @click.self="(e) => onErrorIfNotSelf('click', e)"
-      @dblclick.self="(e) => onErrorIfNotSelf('dblclick', e)"
-      @wheel.self="(e) => onErrorIfNotSelf('wheel', e)"
-      @contextmenu.self="(e) => onErrorIfNotSelf('contextmenu', e)"
-      @pointerup.self="(e) => onErrorIfNotSelf('pointerup', e)"
-      @pointerdown.self="(e) => onErrorIfNotSelf('pointerdown', e)"
-      @pointerenter.self="(e) => onErrorIfNotSelf('pointerenter', e)"
-      @pointerleave.self="(e) => onErrorIfNotSelf('pointerleave', e)"
-      @pointerover.self="(e) => onErrorIfNotSelf('pointerover', e)"
-      @pointerout.self="(e) => onErrorIfNotSelf('pointerout', e)"
-      @pointermove.self="(e) => onErrorIfNotSelf('pointermove', e)"
-      @pointermissed.self="(e) => onErrorIfNotSelf('pointermissed', e)"
-
-      @click="(e) => fn('click', e)"
-      @dblclick="(e) => fn('dblclick', e)"
-      @wheel="(e) => fn('wheel', e)"
-      @contextmenu="(e) => fn('contextmenu', e)"
-      @pointerup="(e) => fn('pointerup', e)"
-      @pointerdown="(e) => fn('pointerdown', e)"
-      @pointerenter="(e) => fn('pointerenter', e)"
-      @pointerleave="(e) => fn('pointerleave', e)"
-      @pointerover="(e) => fn('pointerover', e)"
-      @pointerout="(e) => fn('pointerout', e)"
-      @pointermove="(e) => fn('pointermove', e)"
-      @pointermissed="(e) => fn('pointermissed', e)"
-    >
-      <TresMesh
-        @click.left="console.log('left')"
-        @click.right="console.log('right')"
-        @click.middle="console.log('middle')"
-      >
-        <TresSphereGeometry />
-        <TresMeshToonMaterial color="#efefef" />
-      </TresMesh>
-    </TresGroup>
-
-    <TresDirectionalLight :intensity="1" />
-    <TresAmbientLight :intensity="1" />
-  </TresCanvas>
-</template>

+ 0 - 116
playground/vue/src/pages/events/EventModifierStop.vue

@@ -1,116 +0,0 @@
-<!-- eslint-disable no-console -->
-<script setup lang="ts">
-import { OrbitControls } from '@tresjs/cientos'
-import { TresCanvas } from '@tresjs/core'
-import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
-import type { DomEventName } from '../../../../../src/utils/createEventManager/const'
-
-const gl = {
-  clearColor: '#202020',
-  shadows: true,
-  alpha: false,
-  shadowMapType: BasicShadowMap,
-  outputColorSpace: SRGBColorSpace,
-  toneMapping: NoToneMapping,
-}
-
-const called = reactive<Partial<Record<DomEventName, boolean>>>({
-  click: false,
-  contextmenu: false,
-  dblclick: false,
-  pointerdown: false,
-  pointerup: false,
-  pointermove: false,
-  pointerover: false,
-  pointerout: false,
-  pointerenter: false,
-  pointerleave: false,
-  pointermissed: false,
-  wheel: false,
-})
-
-const error = reactive<Partial<Record<DomEventName, boolean>>>({
-  click: false,
-  contextmenu: false,
-  dblclick: false,
-  pointerdown: false,
-  pointerup: false,
-  pointermove: false,
-  pointerover: false,
-  pointerout: false,
-  pointerenter: false,
-  pointerleave: false,
-  pointermissed: false,
-  wheel: false,
-})
-
-function onError(name: DomEventName) {
-  error[name] = true
-}
-
-function fn(name: DomEventName) {
-  called[name] = true
-}
-</script>
-
-<template>
-  <OverlayInfo>
-    <h2>Event Modifiers: stop</h2>
-    <p>Tres supports the Vue event modifier <code>stop</code></p>
-    <p>E.g.: <code>@click.stop="..."</code></p>
-    <h2>Tests</h2>
-    <p>Interact with the 3D object. It has all supported pointer events + <code>stop</code>. If events are passed to the containing TresGroup, an error will appear below.</p>
-    <p v-for="domName of Object.keys(called)" :key="domName">
-      <span v-if="error[domName as DomEventName]">❌</span>
-      <span v-else-if="called[domName as DomEventName]">✅</span>
-      <span v-else> </span>
-      {{ domName }}
-    </p>
-    <p v-if="Object.values(called).every(b => b) && Object.values(error).every(b => !b)"><strong>✅ All tests pass</strong></p>
-  </OverlayInfo>
-  <TresCanvas
-    window-size
-    v-bind="gl"
-  >
-    <TresPerspectiveCamera
-      :position="[11, 11, 11]"
-      :look-at="[0, 0, 0]"
-    />
-    <OrbitControls />
-    <TresGroup
-      @click="onError('click')"
-      @dblclick="onError('dblclick')"
-      @wheel="onError('wheel')"
-      @contextmenu="onError('contextmenu')"
-      @pointerup="onError('pointerup')"
-      @pointerdown="onError('pointerdown')"
-      @pointerenter="onError('pointerenter')"
-      @pointerleave="onError('pointerleave')"
-      @pointerover="onError('pointerover')"
-      @pointerout="onError('pointerout')"
-      @pointermove="onError('pointermove')"
-      @pointermissed="onError('pointermissed')"
-    >
-      <TresMesh
-        @click.stop="fn('click')"
-        @dblclick.stop="fn('dblclick')"
-        @wheel.stop="fn('wheel')"
-        @contextmenu.stop="fn('contextmenu')"
-        @pointerup.stop="fn('pointerup')"
-        @pointerdown.stop="fn('pointerdown')"
-        @pointerenter.stop="fn('pointerenter')"
-        @pointerleave.stop="fn('pointerleave')"
-        @pointerover.stop="fn('pointerover')"
-        @pointerout.stop="fn('pointerout')"
-        @pointermove.stop="fn('pointermove')"
-        @pointermissed.stop="fn('pointermissed')"
-      >
-        <TresSphereGeometry />
-        <TresMeshToonMaterial color="#efefef" />
-      </TresMesh>
-    </TresGroup>
-
-    <TresDirectionalLight :intensity="1" />
-    <TresAmbientLight :intensity="1" />
-  </TresCanvas>
-</template>

+ 195 - 0
playground/vue/src/pages/events/EventModifiers.vue

@@ -0,0 +1,195 @@
+<!-- eslint-disable no-console -->
+<script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core/types'
+import { Box, Cylinder, Sphere } from '@tresjs/cientos'
+import { TresCanvas } from '@tresjs/core'
+import { Color } from 'three'
+
+const COLORS = 'red|orange|yellow|green|blue|purple'.split('|')
+
+function choice<T>(arr: T[]): T {
+  return arr[(Math.floor(Math.random() * arr.length))]
+}
+
+function fn(e: ThreeEvent<any>) {
+  e.currentTarget.material.color = new Color(choice(COLORS))
+}
+</script>
+
+<template>
+  <div class="demo">
+    <div>
+      <h1>Event Modifiers</h1>
+      <p>Tres supports Vue-style events, meaning that most <a href="https://vuejs.org/guide/essentials/event-handling.html#event-modifiers">Vue event modifiers</a> work.</p>
+      <h3>Exceptions</h3>
+      <ul>
+        <li>once</li>
+        <li>capture</li>
+        <li>passive</li>
+      </ul>
+      <h2>Examples</h2>
+      <p>In the examples below, cylinder is a child of box, box is a child of sphere.</p>
+    </div>
+    <div class="container">
+      <div class="aspect-video">
+        <h2>@click (no event modifiers)</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click="fn">
+            <Box :position-y="-10" :scale="5" @click="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.right</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.right="fn">
+            <Box :position-y="-10" :scale="5" @click.right="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.right="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.right.prevent</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.right.prevent="fn">
+            <Box :position-y="-10" :scale="5" @click.right.prevent="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.right.prevent="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@pointerdown.middle.prevent</h2>
+        <TresCanvas clear-color="#333333" @pointerdown.middle.prevent>
+          <Sphere :position-y="0.6" :scale="0.06" @pointerdown.middle.prevent="fn">
+            <Box :position-y="-10" :scale="5" @pointerdown.middle.prevent="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @pointerdown.middle.prevent="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.self</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.self="fn">
+            <Box :position-y="-10" :scale="5" @click.self="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.self="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.stop</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.stop="fn">
+            <Box :position-y="-10" :scale="5" @click.stop="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.stop="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.shift</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.shift="fn">
+            <Box :position-y="-10" :scale="5" @click.shift="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.shift="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.shift.exact</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.shift.exact="fn">
+            <Box :position-y="-10" :scale="5" @click.shift.exact="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.shift.exact="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.alt</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.alt="fn">
+            <Box :position-y="-10" :scale="5" @click.alt="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.alt="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.alt.shift</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.alt.shift="fn">
+            <Box :position-y="-10" :scale="5" @click.alt.shift="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.alt.shift="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@click.meta</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @click.meta="fn">
+            <Box :position-y="-10" :scale="5" @click.meta="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @click.meta="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+      <div class="aspect-video">
+        <h2>@pointermove.shift</h2>
+        <TresCanvas clear-color="#333333">
+          <Sphere :position-y="0.6" :scale="0.06" @pointermove.shift="fn">
+            <Box :position-y="-10" :scale="5" @pointermove.shift="fn">
+              <Cylinder :position-y="-4" :scale="1.5" @pointermove.shift="fn" />
+            </Box>
+          </Sphere>
+        </TresCanvas>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+table {
+  width: 100%;
+}
+
+div.aspect-video {
+  max-height: 300px;
+  position: relative;
+  margin-bottom: 10px;
+}
+
+.aspect-video h2 {
+  position: absolute;
+  top: 0;
+  color: #fff;
+  z-index: 10;
+}
+
+.container {
+  display: grid;
+}
+
+.demo {
+  padding: 50px;
+}
+
+h1,
+h2,
+h3,
+p,
+li {
+  font-family: sans-serif;
+}
+
+body {
+  padding: 50px;
+}
+</style>

+ 2 - 2
playground/vue/src/pages/events/Filter.vue

@@ -1,10 +1,10 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent, TresObject } from '@tresjs/core'
+import type { Intersection, Object3D } from 'three'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { BasicShadowMap, Color, NoToneMapping, SRGBColorSpace } from 'three'
-import type { ThreeEvent, TresObject } from '@tresjs/core'
-import type { Intersection, Object3D } from 'three'
 
 const gl = {
   clearColor: '#202020',

+ 1 - 1
playground/vue/src/pages/events/NoEvents.vue

@@ -1,9 +1,9 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { BasicShadowMap, Color, NoToneMapping, SRGBColorSpace } from 'three'
-import type { ThreeEvent } from '@tresjs/core'
 
 const gl = {
   clearColor: '#202020',

+ 64 - 0
playground/vue/src/pages/events/OnCanvasLeave.vue

@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
+import { OrbitControls } from '@tresjs/cientos'
+import { TresCanvas } from '@tresjs/core'
+import { ref } from 'vue'
+
+function enter(ev: ThreeEvent<PointerEvent>) {
+  ev.eventObject.material.color.set('#00F')
+}
+
+function over(ev: ThreeEvent<PointerEvent>) {
+  ev.eventObject.material.color.set('#00F')
+}
+
+function leave(ev: ThreeEvent<PointerEvent>) {
+  ev.eventObject.material.color.set(ev.eventObject.color)
+}
+
+function out(ev: ThreeEvent<PointerEvent>) {
+  ev.eventObject.material.color.set(ev.eventObject.color)
+}
+
+const visible = ref(false)
+setInterval(() => {
+  visible.value = !visible.value
+}, 1000)
+</script>
+
+<template>
+  <OverlayInfo>
+    <h1>Canvas' <code>pointerleave</code></h1>
+    <p>When the canvas' <code>pointerleave</code> is triggered, "hovered" objects are <code>pointer{leave,out}</code>ed</p>
+    <h2>Setup</h2>
+    <ol>
+      <li>Put the pointer over the red object. It will change to blue.</li>
+      <li>Move the pointer onto this overlay or out of the window. A <code>pointerleave</code> will be triggered, causing the color to change back to red.</li>
+    </ol>
+  </OverlayInfo>
+  <TresCanvas
+    window-size
+  >
+    <TresPerspectiveCamera
+      :position="[0, 0, 11]"
+      :look-at="[0, 0, 0]"
+    />
+    <TresGroup :scale="10">
+      <TresMesh
+        color="red"
+        :position="[-0.5, 0.5, 0]"
+        @pointerover="over"
+        @pointerout="out"
+      >
+        <TresBoxGeometry :args="[1, 1, 1]" />
+        <TresMeshBasicMaterial color="red" />
+      </TresMesh>
+    </TresGroup>
+  </TresCanvas>
+</template>
+
+<style scoped>
+img {
+  border: 2px solid #ccc;
+}
+</style>

+ 1 - 1
playground/vue/src/pages/events/PointerEnterLeave.vue

@@ -1,9 +1,9 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { Box, OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { BasicShadowMap, BoxGeometry, MeshToonMaterial, NoToneMapping, SRGBColorSpace } from 'three'
-import type { ThreeEvent } from '@tresjs/core'
 import '@tresjs/leches/styles'
 
 const gl = {

+ 29 - 19
playground/vue/src/pages/events/PointerOverOut.vue

@@ -2,7 +2,7 @@
 <script setup lang="ts">
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
-import { shallowRef } from 'vue'
+import { Color } from 'three'
 
 const tresNumOver = ref(0)
 const tresNumOut = ref(0)
@@ -13,19 +13,15 @@ const domNumOver = ref(0)
 const domNumOut = ref(0)
 const domNumEnter = ref(0)
 const domNumLeave = ref(0)
-
-const config = ref({ priorIntersections: [] })
-
-const ready = (context) => {
-  config.value = context.eventManager.config
-}
 </script>
 
 <template>
-  <div>
-    {{ config.priorIntersections.map(intr => intr.object.uuid )}} aa
+  <div class="container">
+    <h1>DOM vs. Tres pointer comparison</h1>
+    <p>Below, both setups have identical pointer events attached only to the gray elements.</p>
+    <p>Pointer events are counted when handled and are expected to yield the same counts for the same actions on both sides.</p>
     <div class="grid-container">
-      <div class="col">
+      <div class="col test-subjects">
         <h2>DOM</h2>
         pointerover: {{ domNumOver }}<br />
         pointerout: {{ domNumOut }}<br />
@@ -45,30 +41,31 @@ const ready = (context) => {
       </div>
 
       <div class="col">
-        <h2>Tres</h2>
+        <h2>Tres (with :blocking)</h2>
         pointerover: {{ tresNumOver }}<br />
         pointerout: {{ tresNumOut }}<br />
         pointerenter: {{ tresNumEnter }}<br />
         pointerleave: {{ tresNumLeave }}<br />
         <div :style="{ width: 'auto', height: '200px' }">
-          <TresCanvas @ready="ready">
+          <TresCanvas>
             <OrbitControls />
-            <TresPerspectiveCamera :position="[0, 0, 5]" />
+            <TresPerspectiveCamera :position="[0, 0, 7]" />
             <TresMesh
               :blocking="true"
               @pointerenter="tresNumEnter++"
               @pointerleave="tresNumLeave++"
               @pointerover="tresNumOver++"
               @pointerout="tresNumOut++"
+              @pointerdown="(e) => e.currentTarget.material.color = new Color('black')"
             >
               <TresBoxGeometry :args="[7, 3, 4.5]" />
               <TresMeshToonMaterial color="gray" />
               <TresMesh>
                 <TresBoxGeometry :args="[5, 2, 5]" />
-                <TresMeshToonMaterial color="blue" />
+                <TresMeshBasicMaterial color="blue" />
                 <TresMesh :position-z="2.1">
-                  <TresBoxGeometry />
-                  <TresMeshToonMaterial color="purple" />
+                  <TresBoxGeometry :args="[3, 1, 1]" />
+                  <TresMeshBasicMaterial color="purple" />
                 </TresMesh>
               </TresMesh>
             </TresMesh>
@@ -82,9 +79,18 @@ const ready = (context) => {
 </template>
 
 <style>
-div {
-  padding: 20px;
-  margin: 20px;
+body {
+  font-family: sans-serif;
+}
+
+p {
+  max-width: 300px;
+}
+
+.test-subjects div {
+  position: relative;
+  padding: 15px;
+  margin: 15px;
   background-color: white;
   border: 1px solid #ccc;
 }
@@ -94,4 +100,8 @@ div {
   grid-template-columns: repeat(2, minmax(0, 1fr));
   grid-auto-flow: column;
 }
+
+.container {
+  padding: 20px;
+}
 </style>

+ 1 - 1
playground/vue/src/pages/events/RemoveInteractivity.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { ref } from 'vue'
-import type { ThreeEvent } from '@tresjs/core'
 
 function onClick(ev: ThreeEvent<MouseEvent>) {
   ev.eventObject.material.color.set('#008080')

+ 1 - 1
playground/vue/src/pages/events/StopPropagation.vue

@@ -1,8 +1,8 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
-import type { ThreeEvent } from '@tresjs/core'
 import '@tresjs/leches/styles'
 
 const msgs0 = ref(['Mouse over cube'])

+ 1 - 1
playground/vue/src/pages/events/TargetEnabled.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
-import type { ThreeEvent } from '@tresjs/core'
 
 function onClick(ev: ThreeEvent<MouseEvent>) {
   ev.eventObject.material?.color.set('#008080')

+ 1 - 1
playground/vue/src/pages/events/index.vue

@@ -1,10 +1,10 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { ThreeEvent } from '@tresjs/core'
 import { OrbitControls, StatsGl } from '@tresjs/cientos'
 import { TresCanvas } from '@tresjs/core'
 import { TresLeches, useControls } from '@tresjs/leches'
 import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
-import type { ThreeEvent } from '@tresjs/core'
 import '@tresjs/leches/styles'
 
 const gl = {

+ 1 - 1
playground/vue/src/pages/issues/701/TheExperience.vue

@@ -1,5 +1,6 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
+import type { BufferGeometry, Camera, Material } from 'three'
 import { useLoop } from '@tresjs/core'
 import {
   BoxGeometry,
@@ -12,7 +13,6 @@ import {
   TorusGeometry,
 } from 'three'
 import { onUnmounted, shallowRef } from 'vue'
-import type { BufferGeometry, Camera, Material } from 'three'
 
 const box = (() => {
   const box = new Mesh(

+ 8 - 8
playground/vue/src/router/routes/events.ts

@@ -39,6 +39,11 @@ export const eventsRoutes = [
     name: 'RemoveInteractivity',
     component: () => import('../../pages/events/RemoveInteractivity.vue'),
   },
+  {
+    path: '/events/on-canvas-leave',
+    name: 'On canvas leave',
+    component: () => import('../../pages/events/OnCanvasLeave.vue'),
+  },
   {
     path: '/events/connect',
     name: 'Connect (add/remove event listeners)',
@@ -55,14 +60,9 @@ export const eventsRoutes = [
     component: () => import('../../pages/events/Blocking.vue'),
   },
   {
-    path: '/events/event-modifier-self',
-    name: 'Event Modifier: "self"',
-    component: () => import('../../pages/events/EventModifierSelf.vue'),
-  },
-  {
-    path: '/events/event-modifier-stop',
-    name: 'Event Modifier: "stop"',
-    component: () => import('../../pages/events/EventModifierStop.vue'),
+    path: '/events/event-modifiers',
+    name: 'Vue Event Modifiers',
+    component: () => import('../../pages/events/EventModifiers.vue'),
   },
   {
     path: '/events/deprecated-event-names',

+ 126 - 89
pnpm-lock.yaml

@@ -101,13 +101,13 @@ importers:
         version: 0.168.0
       unocss:
         specifier: ^0.62.3
-        version: 0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
+        version: 0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       unplugin:
         specifier: ^1.13.1
         version: 1.13.1(webpack-sources@3.2.3)
       unplugin-vue-components:
         specifier: ^0.27.4
-        version: 0.27.4(@babel/parser@7.25.6)(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vue@3.5.3(typescript@5.5.4))(webpack-sources@3.2.3)
+        version: 0.27.4(@babel/parser@7.25.6)(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vue@3.5.3(typescript@5.5.4))(webpack-sources@3.2.3)
       vite:
         specifier: ^5.4.3
         version: 5.4.3(@types/node@22.5.4)(terser@5.31.6)
@@ -119,7 +119,7 @@ importers:
         version: 4.1.0(@types/node@22.5.4)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       vite-plugin-inspect:
         specifier: ^0.8.7
-        version: 0.8.7(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
+        version: 0.8.7(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       vite-plugin-require-transform:
         specifier: ^1.0.21
         version: 1.0.21
@@ -156,7 +156,7 @@ importers:
         version: 1.2.0
       unocss:
         specifier: ^0.62.3
-        version: 0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
+        version: 0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       vite-svg-loader:
         specifier: ^5.1.0
         version: 5.1.0(vue@3.5.3(typescript@5.5.4))
@@ -211,7 +211,7 @@ importers:
         version: 0.2.1(tweakpane@4.0.4)
       unplugin-auto-import:
         specifier: ^0.18.2
-        version: 0.18.2(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(@vueuse/core@11.0.3(vue@3.5.3(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3)
+        version: 0.18.2(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(@vueuse/core@11.0.3(vue@3.5.3(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3)
       vite-plugin-glsl:
         specifier: ^1.2.1
         version: 1.3.0(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
@@ -220,7 +220,7 @@ importers:
         version: 0.2.3(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       vite-plugin-vue-devtools:
         specifier: 7.4.3
-        version: 7.4.3(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4))
+        version: 7.4.3(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4))
       vue-tsc:
         specifier: ^2.1.4
         version: 2.1.4(typescript@5.5.4)
@@ -443,6 +443,10 @@ packages:
     resolution: {integrity: sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/helper-module-imports@7.22.15':
+    resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/helper-module-imports@7.24.7':
     resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==}
     engines: {node: '>=6.9.0'}
@@ -463,6 +467,10 @@ packages:
     resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/helper-plugin-utils@7.24.7':
+    resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/helper-plugin-utils@7.24.8':
     resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==}
     engines: {node: '>=6.9.0'}
@@ -598,8 +606,8 @@ packages:
     resolution: {integrity: sha512-Kf2ZcZVqsKbtYhlA7sP0z5A3q5hmCVYMKMWRWNK/5OVwHIve3JY1djVRmIVAx8FMueLIfZGKQDIILK2w8zO4mg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/traverse@7.25.6':
-    resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==}
+  '@babel/template@7.24.7':
+    resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
     engines: {node: '>=6.9.0'}
 
   '@babel/template@7.25.0':
@@ -1307,8 +1315,8 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@inquirer/figures@1.0.5':
-    resolution: {integrity: sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA==}
+  '@inquirer/figures@1.0.3':
+    resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==}
     engines: {node: '>=18'}
 
   '@ioredis/commands@1.2.0':
@@ -1607,8 +1615,8 @@ packages:
     resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==}
     engines: {node: '>=12.22.0'}
 
-  '@pnpm/npm-conf@2.3.1':
-    resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==}
+  '@pnpm/npm-conf@2.2.2':
+    resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==}
     engines: {node: '>=12'}
 
   '@polka/url@1.0.0-next.25':
@@ -2207,8 +2215,8 @@ packages:
   '@types/web-bluetooth@0.0.20':
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
 
-  '@types/webxr@0.5.20':
-    resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==}
+  '@types/webxr@0.5.19':
+    resolution: {integrity: sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==}
 
   '@typescript-eslint/eslint-plugin@8.4.0':
     resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==}
@@ -2887,8 +2895,8 @@ packages:
   '@vueuse/core@11.0.3':
     resolution: {integrity: sha512-RENlh64+SYA9XMExmmH1a3TPqeIuJBNNB/63GT35MZI+zpru3oMRUA6cEFr9HmGqEgUisurwGwnIieF6qu3aXw==}
 
-  '@vueuse/integrations@10.11.1':
-    resolution: {integrity: sha512-Y5hCGBguN+vuVYTZmdd/IMXLOdfS60zAmDmFYc4BKBcMUPZH1n4tdyDECCPjXm0bNT3ZRUy1xzTLGaUje8Xyaw==}
+  '@vueuse/integrations@10.11.0':
+    resolution: {integrity: sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==}
     peerDependencies:
       async-validator: ^4
       axios: ^1
@@ -4050,8 +4058,8 @@ packages:
   electron-to-chromium@1.5.17:
     resolution: {integrity: sha512-Q6Q+04tjC2KJ8qsSOSgovvhWcv5t+SmpH6/YfAWmhpE5/r+zw6KQy1/yWVFFNyEBvy68twTTXr2d5eLfCq7QIw==}
 
-  emoji-regex@10.4.0:
-    resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
+  emoji-regex@10.3.0:
+    resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
 
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -4129,6 +4137,10 @@ packages:
     resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
     engines: {node: '>=10'}
 
+  escape-string-regexp@5.0.0:
+    resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+    engines: {node: '>=12'}
+
   escodegen@2.1.0:
     resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
     engines: {node: '>=6.0'}
@@ -4330,6 +4342,10 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
+  esquery@1.5.0:
+    resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+    engines: {node: '>=0.10'}
+
   esquery@1.6.0:
     resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
     engines: {node: '>=0.10'}
@@ -4809,8 +4825,8 @@ packages:
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
-  ignore@5.3.2:
-    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+  ignore@5.3.1:
+    resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
     engines: {node: '>= 4'}
 
   ignore@5.3.2:
@@ -5100,6 +5116,9 @@ packages:
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
+  js-tokens@9.0.0:
+    resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==}
+
   js-yaml@4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
     hasBin: true
@@ -5347,8 +5366,8 @@ packages:
     resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
     engines: {node: '>=12'}
 
-  macos-release@3.3.0:
-    resolution: {integrity: sha512-tPJQ1HeyiU2vRruNGhZ+VleWuMQRro8iFtJxYgnS4NQe+EukKF6aGiIT+7flZhISAt2iaXBCfFGvAyif7/f8nQ==}
+  macos-release@3.2.0:
+    resolution: {integrity: sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
   magic-string-ast@0.6.2:
@@ -5412,8 +5431,8 @@ packages:
   micromark@2.11.4:
     resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
 
-  micromatch@4.0.8:
-    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+  micromatch@4.0.7:
+    resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
     engines: {node: '>=8.6'}
 
   mime-db@1.52.0:
@@ -6177,8 +6196,8 @@ packages:
   potpack@1.0.2:
     resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==}
 
-  preact@10.23.2:
-    resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==}
+  preact@10.22.1:
+    resolution: {integrity: sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==}
 
   prelude-ls@1.2.1:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
@@ -6232,6 +6251,10 @@ packages:
     resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==}
     engines: {node: '>=12.20'}
 
+  qrcode-terminal@0.12.0:
+    resolution: {integrity: sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==}
+    hasBin: true
+
   querystringify@2.2.0:
     resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
 
@@ -6785,6 +6808,9 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
+  svg-tags@1.0.0:
+    resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
+
   svgo@3.3.2:
     resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
     engines: {node: '>=14.0.0'}
@@ -7062,8 +7088,8 @@ packages:
   ufo@0.8.6:
     resolution: {integrity: sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==}
 
-  ufo@1.5.4:
-    resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
+  ufo@1.5.3:
+    resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
 
   ufo@1.5.4:
     resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
@@ -7393,6 +7419,12 @@ packages:
       '@nuxt/kit':
         optional: true
 
+  vite-plugin-qrcode@0.2.3:
+    resolution: {integrity: sha512-TFzhf20v29hnh2XEoZ2kxg8Ff/ui36pR7PGDaHaKEmsQaRagv31XacHxbw5O07HcC1Mkr4tKcYb+PFASSceHmw==}
+    engines: {node: ^14.13.1 || ^16.0.0 || >=18}
+    peerDependencies:
+      vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+
   vite-plugin-require-transform@1.0.21:
     resolution: {integrity: sha512-A3SrHhVg9tCW35O7E8kcuB71YTEdVd3EaM1zh6gbH4zxy4WzXSfcNf0UiWmaHHhr6wdFhiiAGdpR6S0SUxXkGQ==}
 
@@ -8052,7 +8084,7 @@ snapshots:
       '@babel/helper-optimise-call-expression': 7.24.7
       '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7)
       '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-      '@babel/traverse': 7.25.6
+      '@babel/helper-split-export-declaration': 7.24.7
       semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
@@ -8129,6 +8161,7 @@ snapshots:
       '@babel/helper-environment-visitor': 7.24.7
       '@babel/helper-module-imports': 7.24.7
       '@babel/helper-simple-access': 7.24.7
+      '@babel/helper-split-export-declaration': 7.24.7
       '@babel/helper-validator-identifier': 7.24.7
     transitivePeerDependencies:
       - supports-color
@@ -8157,7 +8190,6 @@ snapshots:
       '@babel/helper-environment-visitor': 7.24.7
       '@babel/helper-member-expression-to-functions': 7.24.7
       '@babel/helper-optimise-call-expression': 7.24.7
-      '@babel/traverse': 7.25.6
     transitivePeerDependencies:
       - supports-color
 
@@ -8222,7 +8254,7 @@ snapshots:
       js-tokens: 4.0.0
       picocolors: 1.1.0
 
-  '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.2)':
+  '@babel/parser@7.24.7':
     dependencies:
       '@babel/types': 7.24.7
 
@@ -8371,11 +8403,14 @@ snapshots:
   '@babel/traverse@7.24.7':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/generator': 7.25.6
-      '@babel/parser': 7.25.6
-      '@babel/template': 7.25.0
-      '@babel/types': 7.25.6
-      debug: 4.3.6
+      '@babel/generator': 7.24.7
+      '@babel/helper-environment-visitor': 7.24.7
+      '@babel/helper-function-name': 7.24.7
+      '@babel/helper-hoist-variables': 7.24.7
+      '@babel/helper-split-export-declaration': 7.24.7
+      '@babel/parser': 7.24.7
+      '@babel/types': 7.24.7
+      debug: 4.3.5
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -8877,7 +8912,7 @@ snapshots:
   '@jridgewell/gen-mapping@0.3.5':
     dependencies:
       '@jridgewell/set-array': 1.2.1
-      '@jridgewell/sourcemap-codec': 1.5.0
+      '@jridgewell/sourcemap-codec': 1.4.15
       '@jridgewell/trace-mapping': 0.3.25
 
   '@jridgewell/resolve-uri@3.1.2': {}
@@ -9113,10 +9148,10 @@ snapshots:
       - supports-color
       - webpack-sources
 
-  '@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3)':
+  '@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3)':
     dependencies:
       '@nuxt/schema': 3.13.1(rollup@4.21.2)(webpack-sources@3.2.3)
-      c12: 1.11.2
+      c12: 1.11.2(magicast@0.3.4)
       consola: 3.2.3
       defu: 6.1.4
       destr: 2.0.3
@@ -9483,7 +9518,7 @@ snapshots:
     dependencies:
       graceful-fs: 4.2.10
 
-  '@pnpm/npm-conf@2.3.1':
+  '@pnpm/npm-conf@2.2.2':
     dependencies:
       '@pnpm/config.env-replace': 1.1.0
       '@pnpm/network.ca-file': 1.0.2
@@ -10502,14 +10537,6 @@ snapshots:
       meshoptimizer: 0.18.1
 
   '@types/three@0.163.0':
-    dependencies:
-      '@tweenjs/tween.js': 23.1.3
-      '@types/stats.js': 0.17.3
-      '@types/webxr': 0.5.20
-      fflate: 0.8.2
-      meshoptimizer: 0.18.1
-
-  '@types/three@0.164.1':
     dependencies:
       '@tweenjs/tween.js': 23.1.2
       '@types/stats.js': 0.17.3
@@ -10564,7 +10591,7 @@ snapshots:
 
   '@types/web-bluetooth@0.0.20': {}
 
-  '@types/webxr@0.5.20': {}
+  '@types/webxr@0.5.19': {}
 
   '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)':
     dependencies:
@@ -10685,7 +10712,6 @@ snapshots:
       vite: 5.4.3(@types/node@22.5.2)(terser@5.31.6)
     transitivePeerDependencies:
       - rollup
-      - supports-color
 
   '@unocss/astro@0.59.4(rollup@4.18.0)(vite@5.4.3(@types/node@22.5.2)(terser@5.31.6))':
     dependencies:
@@ -10747,7 +10773,6 @@ snapshots:
       perfect-debounce: 1.0.0
     transitivePeerDependencies:
       - rollup
-      - supports-color
 
   '@unocss/cli@0.59.4(rollup@4.18.0)':
     dependencies:
@@ -11397,7 +11422,6 @@ snapshots:
       vite: 5.4.3(@types/node@22.5.2)(terser@5.31.6)
     transitivePeerDependencies:
       - rollup
-      - supports-color
 
   '@unocss/vite@0.59.4(rollup@4.18.0)(vite@5.4.3(@types/node@22.5.2)(terser@5.31.6))':
     dependencies:
@@ -11528,7 +11552,7 @@ snapshots:
       - rollup
       - supports-color
 
-  '@unocss/webpack@0.62.3(rollup@4.21.2)':
+  '@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1))':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@rollup/pluginutils': 5.1.0(rollup@4.21.2)
@@ -11538,6 +11562,7 @@ snapshots:
       magic-string: 0.30.11
       tinyglobby: 0.2.5
       unplugin: 1.13.1(webpack-sources@3.2.3)
+      webpack: 5.94.0(esbuild@0.23.1)
       webpack-sources: 3.2.3
     transitivePeerDependencies:
       - rollup
@@ -11595,13 +11620,13 @@ snapshots:
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@bcoe/v8-coverage': 0.2.3
-      debug: 4.3.6
+      debug: 4.3.5
       istanbul-lib-coverage: 3.2.2
       istanbul-lib-report: 3.0.1
       istanbul-lib-source-maps: 5.0.6
       istanbul-reports: 3.1.7
-      magic-string: 0.30.11
-      magicast: 0.3.5
+      magic-string: 0.30.10
+      magicast: 0.3.4
       std-env: 3.7.0
       test-exclude: 7.0.1
       tinyrainbow: 1.2.0
@@ -12438,7 +12463,7 @@ snapshots:
     optionalDependencies:
       magicast: 0.3.4
 
-  c12@1.11.2:
+  c12@1.11.2(magicast@0.3.4):
     dependencies:
       chokidar: 3.6.0
       confbox: 0.1.7
@@ -12452,6 +12477,8 @@ snapshots:
       perfect-debounce: 1.0.0
       pkg-types: 1.2.0
       rc9: 2.1.2
+    optionalDependencies:
+      magicast: 0.3.4
     optional: true
 
   c8@7.14.0:
@@ -12760,7 +12787,7 @@ snapshots:
       handlebars: 4.7.8
       json-stringify-safe: 5.0.1
       meow: 12.1.1
-      semver: 7.6.3
+      semver: 7.6.2
       split2: 4.2.0
 
   conventional-changelog@5.1.0:
@@ -13087,7 +13114,7 @@ snapshots:
       '@one-ini/wasm': 0.1.1
       commander: 10.0.1
       minimatch: 9.0.1
-      semver: 7.6.3
+      semver: 7.6.2
 
   ee-first@1.1.1: {}
 
@@ -13218,6 +13245,8 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
+  escape-string-regexp@5.0.0: {}
+
   escodegen@2.1.0:
     dependencies:
       esprima: 4.0.1
@@ -13523,6 +13552,10 @@ snapshots:
 
   esprima@4.0.1: {}
 
+  esquery@1.5.0:
+    dependencies:
+      estraverse: 5.3.0
+
   esquery@1.6.0:
     dependencies:
       estraverse: 5.3.0
@@ -13610,7 +13643,7 @@ snapshots:
       '@nodelib/fs.walk': 1.2.8
       glob-parent: 5.1.2
       merge2: 1.4.1
-      micromatch: 4.0.8
+      micromatch: 4.0.7
 
   fast-json-stable-stringify@2.1.0: {}
 
@@ -13870,7 +13903,7 @@ snapshots:
       dir-glob: 3.0.1
       fast-glob: 3.3.2
       glob: 7.2.3
-      ignore: 5.3.2
+      ignore: 5.3.1
       merge2: 1.4.1
       slash: 3.0.0
 
@@ -14035,7 +14068,7 @@ snapshots:
   https-proxy-agent@7.0.5:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.6
+      debug: 4.3.5
     transitivePeerDependencies:
       - supports-color
 
@@ -14103,7 +14136,7 @@ snapshots:
 
   inquirer@9.3.2:
     dependencies:
-      '@inquirer/figures': 1.0.5
+      '@inquirer/figures': 1.0.3
       ansi-escapes: 4.3.2
       cli-width: 4.1.0
       external-editor: 3.1.0
@@ -14284,7 +14317,7 @@ snapshots:
   istanbul-lib-source-maps@5.0.6:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.25
-      debug: 4.3.6
+      debug: 4.3.5
       istanbul-lib-coverage: 3.2.2
     transitivePeerDependencies:
       - supports-color
@@ -14324,6 +14357,8 @@ snapshots:
 
   js-tokens@4.0.0: {}
 
+  js-tokens@9.0.0: {}
+
   js-yaml@4.1.0:
     dependencies:
       argparse: 2.0.1
@@ -14593,7 +14628,7 @@ snapshots:
 
   lru-cache@7.18.3: {}
 
-  macos-release@3.3.0: {}
+  macos-release@3.2.0: {}
 
   magic-string-ast@0.6.2:
     dependencies:
@@ -14601,7 +14636,7 @@ snapshots:
 
   magic-string@0.30.10:
     dependencies:
-      '@jridgewell/sourcemap-codec': 1.5.0
+      '@jridgewell/sourcemap-codec': 1.4.15
 
   magic-string@0.30.11:
     dependencies:
@@ -14619,7 +14654,7 @@ snapshots:
 
   make-dir@4.0.0:
     dependencies:
-      semver: 7.6.3
+      semver: 7.6.2
 
   mark.js@8.11.1: {}
 
@@ -14658,7 +14693,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  micromatch@4.0.8:
+  micromatch@4.0.7:
     dependencies:
       braces: 3.0.3
       picomatch: 2.3.1
@@ -15166,7 +15201,7 @@ snapshots:
 
   os-name@5.1.0:
     dependencies:
-      macos-release: 3.3.0
+      macos-release: 3.2.0
       windows-release: 5.1.1
 
   os-tmpdir@1.0.2: {}
@@ -15561,7 +15596,7 @@ snapshots:
 
   potpack@1.0.2: {}
 
-  preact@10.23.2: {}
+  preact@10.22.1: {}
 
   prelude-ls@1.2.1: {}
 
@@ -15609,6 +15644,8 @@ snapshots:
     dependencies:
       escape-goat: 4.0.0
 
+  qrcode-terminal@0.12.0: {}
+
   querystringify@2.2.0: {}
 
   queue-microtask@1.2.3: {}
@@ -15729,7 +15766,7 @@ snapshots:
 
   registry-auth-token@5.0.2:
     dependencies:
-      '@pnpm/npm-conf': 2.3.1
+      '@pnpm/npm-conf': 2.2.2
 
   registry-url@6.0.1:
     dependencies:
@@ -16195,7 +16232,7 @@ snapshots:
 
   string-width@7.2.0:
     dependencies:
-      emoji-regex: 10.4.0
+      emoji-regex: 10.3.0
       get-east-asian-width: 1.2.0
       strip-ansi: 7.1.0
 
@@ -16269,6 +16306,8 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
+  svg-tags@1.0.0: {}
+
   svgo@3.3.2:
     dependencies:
       '@trysound/sax': 0.2.0
@@ -16550,7 +16589,7 @@ snapshots:
 
   ufo@0.8.6: {}
 
-  ufo@1.5.4: {}
+  ufo@1.5.3: {}
 
   ufo@1.5.4: {}
 
@@ -16563,9 +16602,7 @@ snapshots:
     dependencies:
       '@antfu/utils': 0.7.10
       defu: 6.1.4
-      importx: 0.3.11
-    transitivePeerDependencies:
-      - supports-color
+      jiti: 1.21.6
 
   unconfig@0.5.5:
     dependencies:
@@ -16783,7 +16820,7 @@ snapshots:
       - rollup
       - supports-color
 
-  unocss@0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6)):
+  unocss@0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6)):
     dependencies:
       '@unocss/astro': 0.62.3(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       '@unocss/cli': 0.62.3(rollup@4.21.2)
@@ -16806,14 +16843,14 @@ snapshots:
       '@unocss/transformer-variant-group': 0.62.3
       '@unocss/vite': 0.62.3(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
     optionalDependencies:
-      '@unocss/webpack': 0.62.3(rollup@4.21.2)
+      '@unocss/webpack': 0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1))
       vite: 5.4.3(@types/node@22.5.4)(terser@5.31.6)
     transitivePeerDependencies:
       - postcss
       - rollup
       - supports-color
 
-  unplugin-auto-import@0.18.2(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(@vueuse/core@11.0.3(vue@3.5.3(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3):
+  unplugin-auto-import@0.18.2(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(@vueuse/core@11.0.3(vue@3.5.3(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.0(rollup@4.21.2)
@@ -16824,13 +16861,13 @@ snapshots:
       unimport: 3.11.1(rollup@4.21.2)(webpack-sources@3.2.3)
       unplugin: 1.13.1(webpack-sources@3.2.3)
     optionalDependencies:
-      '@nuxt/kit': 3.13.1(rollup@4.21.2)(webpack-sources@3.2.3)
+      '@nuxt/kit': 3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3)
       '@vueuse/core': 11.0.3(vue@3.5.3(typescript@5.5.4))
     transitivePeerDependencies:
       - rollup
       - webpack-sources
 
-  unplugin-vue-components@0.27.4(@babel/parser@7.25.6)(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vue@3.5.3(typescript@5.5.4))(webpack-sources@3.2.3):
+  unplugin-vue-components@0.27.4(@babel/parser@7.25.6)(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vue@3.5.3(typescript@5.5.4))(webpack-sources@3.2.3):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.0(rollup@4.21.2)
@@ -16845,7 +16882,7 @@ snapshots:
       vue: 3.5.3(typescript@5.5.4)
     optionalDependencies:
       '@babel/parser': 7.25.6
-      '@nuxt/kit': 3.13.1(rollup@4.21.2)(webpack-sources@3.2.3)
+      '@nuxt/kit': 3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3)
     transitivePeerDependencies:
       - rollup
       - supports-color
@@ -17109,7 +17146,7 @@ snapshots:
       - rollup
       - supports-color
 
-  vite-plugin-inspect@0.8.7(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6)):
+  vite-plugin-inspect@0.8.7(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6)):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.0(rollup@4.21.2)
@@ -17122,7 +17159,7 @@ snapshots:
       sirv: 2.0.4
       vite: 5.4.3(@types/node@22.5.4)(terser@5.31.6)
     optionalDependencies:
-      '@nuxt/kit': 3.13.1(rollup@4.21.2)(webpack-sources@3.2.3)
+      '@nuxt/kit': 3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3)
     transitivePeerDependencies:
       - rollup
       - supports-color
@@ -17141,7 +17178,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  vite-plugin-vue-devtools@7.4.3(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4)):
+  vite-plugin-vue-devtools@7.4.3(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4)):
     dependencies:
       '@vue/devtools-core': 7.4.4(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4))
       '@vue/devtools-kit': 7.4.4
@@ -17149,7 +17186,7 @@ snapshots:
       execa: 8.0.1
       sirv: 2.0.4
       vite: 5.4.3(@types/node@22.5.4)(terser@5.31.6)
-      vite-plugin-inspect: 0.8.7(@nuxt/kit@3.13.1(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
+      vite-plugin-inspect: 0.8.7(@nuxt/kit@3.13.1(magicast@0.3.4)(rollup@4.21.2)(webpack-sources@3.2.3))(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
       vite-plugin-vue-inspector: 5.2.0(vite@5.4.3(@types/node@22.5.4)(terser@5.31.6))
     transitivePeerDependencies:
       - '@nuxt/kit'
@@ -17270,9 +17307,9 @@ snapshots:
       '@vitest/spy': 2.0.5
       '@vitest/utils': 2.0.5
       chai: 5.1.1
-      debug: 4.3.6
+      debug: 4.3.5
       execa: 8.0.1
-      magic-string: 0.30.11
+      magic-string: 0.30.10
       pathe: 1.1.2
       std-env: 3.7.0
       tinybench: 2.8.0
@@ -17554,7 +17591,7 @@ snapshots:
   yargs@17.7.2:
     dependencies:
       cliui: 8.0.1
-      escalade: 3.2.0
+      escalade: 3.1.2
       get-caller-file: 2.0.5
       require-directory: 2.1.1
       string-width: 4.2.3

+ 12 - 12
src/components/TresCanvas.vue

@@ -1,5 +1,16 @@
 <script setup lang="ts">
+import type { EventManagerProps } from 'src/utils/createEventManager/createEventManager'
+import type {
+  ColorSpace,
+  ShadowMapType,
+  ToneMapping,
+  WebGLRendererParameters,
+} from 'three'
+import type { App, Ref } from 'vue'
+import type { RendererPresetsType } from '../composables/useRenderer/const'
+import type { TresCamera, TresObject, TresScene } from '../types/'
 import { PerspectiveCamera, Scene } from 'three'
+
 import * as THREE from 'three'
 import {
   computed,
@@ -16,15 +27,6 @@ import {
   watch,
   watchEffect,
 } from 'vue'
-import type { EventManagerProps } from 'src/utils/createEventManager/createEventManager'
-import type {
-  ColorSpace,
-  ShadowMapType,
-  ToneMapping,
-  WebGLRendererParameters,
-} from 'three'
-import type { App, Ref } from 'vue'
-
 import pkg from '../../package.json'
 import {
   type TresContext,
@@ -32,12 +34,10 @@ import {
   useTresContextProvider,
 } from '../composables'
 import { extend } from '../core/catalogue'
+
 import { nodeOps } from '../core/nodeOps'
 import { registerTresDevtools } from '../devtools'
-
 import { disposeObject3D } from '../utils/'
-import type { RendererPresetsType } from '../composables/useRenderer/const'
-import type { TresCamera, TresObject, TresScene } from '../types/'
 
 export interface TresCanvasProps
   extends Omit<WebGLRendererParameters, 'canvas'> {

+ 4 - 4
src/composables/useCamera/index.ts

@@ -1,11 +1,11 @@
-import { Camera, PerspectiveCamera } from 'three'
-import { computed, onUnmounted, ref, watchEffect } from 'vue'
 import type { OrthographicCamera } from 'three'
-
-import { camera as isCamera } from '../../utils/is'
 import type { TresScene } from '../../types'
 import type { TresContext } from '../useTresContextProvider'
 
+import { Camera, PerspectiveCamera } from 'three'
+import { computed, onUnmounted, ref, watchEffect } from 'vue'
+import { camera as isCamera } from '../../utils/is'
+
 export const useCamera = ({ sizes }: Pick<TresContext, 'sizes'> & { scene: TresScene }) => {
   // the computed does not trigger, when for example the camera position changes
   const cameras = ref<Camera[]>([])

+ 1 - 1
src/composables/useLoader/index.ts

@@ -1,6 +1,6 @@
 import type { Loader, LoadingManager, Object3D } from 'three'
-import { useLogger } from '..'
 import type { TresObject } from '../../types'
+import { useLogger } from '..'
 
 export interface TresLoader<T> extends Loader {
   load: (

+ 1 - 1
src/composables/useLoop/index.ts

@@ -1,5 +1,5 @@
-import { useTresContext } from '../useTresContextProvider'
 import type { LoopCallbackFn } from './../../core/loop'
+import { useTresContext } from '../useTresContextProvider'
 
 export function useLoop() {
   const {

+ 2 - 2
src/composables/useRenderLoop/index.ts

@@ -1,7 +1,7 @@
-import { createEventHook, useRafFn } from '@vueuse/core'
-import { Clock } from 'three'
 import type { EventHookOn, Fn } from '@vueuse/core'
 import type { Ref } from 'vue'
+import { createEventHook, useRafFn } from '@vueuse/core'
+import { Clock } from 'three'
 
 export interface RenderLoop {
   delta: number

+ 7 - 7
src/composables/useRenderer/index.ts

@@ -1,3 +1,9 @@
+import type { ColorSpace, Scene, ShadowMapType, ToneMapping, WebGLRendererParameters } from 'three'
+import type { EmitEventFn, TresColor } from '../../types'
+
+import type { TresContext } from '../useTresContextProvider'
+
+import type { RendererPresetsType } from './const'
 import {
   type MaybeRefOrGetter,
   toValue,
@@ -5,22 +11,16 @@ import {
   useDevicePixelRatio,
 } from '@vueuse/core'
 import { ACESFilmicToneMapping, Color, WebGLRenderer } from 'three'
-
 import { computed, type MaybeRef, onUnmounted, shallowRef, watch, watchEffect } from 'vue'
 
-import type { ColorSpace, Scene, ShadowMapType, ToneMapping, WebGLRendererParameters } from 'three'
 // Solution taken from Thretle that actually support different versions https://github.com/threlte/threlte/blob/5fa541179460f0dadc7dc17ae5e6854d1689379e/packages/core/src/lib/lib/useRenderer.ts
 import { revision } from '../../core/revision'
 import { get, merge, set, setPixelRatio } from '../../utils'
-import { normalizeColor } from '../../utils/normalize'
 
+import { normalizeColor } from '../../utils/normalize'
 import { useLogger } from '../useLogger'
 import { rendererPresets } from './const'
 
-import type { EmitEventFn, TresColor } from '../../types'
-import type { TresContext } from '../useTresContextProvider'
-import type { RendererPresetsType } from './const'
-
 type TransformToMaybeRefOrGetter<T> = {
   [K in keyof T]: MaybeRefOrGetter<T[K]> | MaybeRefOrGetter<T[K]>;
 }

+ 1 - 1
src/composables/useSizes/index.ts

@@ -1,6 +1,6 @@
+import type { ComputedRef, MaybeRef, MaybeRefOrGetter, Ref } from 'vue'
 import { refDebounced, toValue, useElementSize, useWindowSize } from '@vueuse/core'
 import { computed, readonly } from 'vue'
-import type { ComputedRef, MaybeRef, MaybeRefOrGetter, Ref } from 'vue'
 
 export interface SizesType {
   height: Readonly<Ref<number>>

+ 1 - 1
src/composables/useTexture/component.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import type { PBRUseTextureMap } from './index'
 import { reactive } from 'vue'
 import { useTexture } from './index'
-import type { PBRUseTextureMap } from './index'
 
 const props = defineProps<PBRUseTextureMap>()
 

+ 1 - 1
src/composables/useTexture/index.ts

@@ -1,5 +1,5 @@
-import { TextureLoader } from 'three'
 import type { LoadingManager, Texture } from 'three'
+import { TextureLoader } from 'three'
 import { isArray } from '../../utils'
 
 export interface PBRMaterialOptions {

+ 6 - 6
src/composables/useTresContextProvider/index.ts

@@ -1,20 +1,20 @@
+import type { Camera, WebGLRenderer } from 'three'
+import type { ComputedRef, DeepReadonly, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
+import type { RendererLoop } from '../../core/loop'
+import type { EmitEventFn, TresControl, TresObject, TresScene } from '../../types'
+import type { UseRendererOptions } from '../useRenderer'
 import { useFps, useMemory, useRafFn } from '@vueuse/core'
 import { Raycaster } from 'three'
 import { computed, inject, onUnmounted, provide, readonly, ref, shallowRef } from 'vue'
-import type { Camera, WebGLRenderer } from 'three'
-import type { ComputedRef, DeepReadonly, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
 import { extend } from '../../core/catalogue'
 import { createRenderLoop } from '../../core/loop'
 import { type EventManager, useEventsOptions } from '../../utils/createEventManager'
 import { calculateMemoryUsage } from '../../utils/perf'
+
 import { useCamera } from '../useCamera'
 import { useRenderer } from '../useRenderer'
 import useSizes, { type SizesType } from '../useSizes'
-
 import { useTresReady } from '../useTresReady'
-import type { RendererLoop } from '../../core/loop'
-import type { EmitEventFn, TresControl, TresObject, TresScene } from '../../types'
-import type { UseRendererOptions } from '../useRenderer'
 
 export interface InternalState {
   priority: Ref<number>

+ 1 - 1
src/composables/useTresReady/createReadyEventHook/index.ts

@@ -1,5 +1,5 @@
-import { createEventHook } from '@vueuse/core'
 import type { EventHook, EventHookOn, IsAny } from '@vueuse/core'
+import { createEventHook } from '@vueuse/core'
 
 type Callback<T> =
   IsAny<T> extends true

+ 1 - 1
src/composables/useTresReady/index.ts

@@ -1,6 +1,6 @@
+import type { TresContext } from '../useTresContextProvider'
 import { useTresContext } from '../useTresContextProvider'
 import { createReadyEventHook } from './createReadyEventHook'
-import type { TresContext } from '../useTresContextProvider'
 
 const ctxToUseTresReady = new WeakMap<
   TresContext,

+ 1 - 1
src/core/catalogue.ts

@@ -1,6 +1,6 @@
-import { ref } from 'vue'
 import type { Ref } from 'vue'
 import type { TresCatalogue } from '../types'
+import { ref } from 'vue'
 
 export const catalogue: Ref<TresCatalogue> = ref({})
 

+ 1 - 1
src/core/loop.test.ts

@@ -1,6 +1,6 @@
+import type { TresContext } from '../composables/useTresContextProvider'
 import { afterEach, beforeEach, it } from 'vitest'
 import { createRenderLoop } from './loop'
-import type { TresContext } from '../composables/useTresContextProvider'
 
 let renderLoop
 

+ 3 - 3
src/core/loop.ts

@@ -1,10 +1,10 @@
-import { Clock, MathUtils } from 'three'
-import { ref, unref } from 'vue'
 import type { Fn } from '@vueuse/core'
 import type { Camera, EventDispatcher, Raycaster, Scene, WebGLRenderer } from 'three'
 import type { Ref } from 'vue'
-import { createPriorityEventHook } from '../utils/createPriorityEventHook'
 import type { Callback } from '../utils/createPriorityEventHook'
+import { Clock, MathUtils } from 'three'
+import { ref, unref } from 'vue'
+import { createPriorityEventHook } from '../utils/createPriorityEventHook'
 
 export type LoopStage = 'before' | 'render' | 'after'
 

+ 3 - 3
src/core/nodeOps.test.ts

@@ -1,12 +1,12 @@
+import type { TresContext } from 'src/composables'
+import type { Vector3 } from 'three'
+import type { TresObject } from '../types'
 import * as THREE from 'three'
 import { Mesh, Scene } from 'three'
 import { beforeAll, describe, expect, it, vi } from 'vitest'
 import { shallowRef } from 'vue'
-import type { TresContext } from 'src/composables'
-import type { Vector3 } from 'three'
 import { extend } from './catalogue'
 import { nodeOps as getNodeOps } from './nodeOps'
-import type { TresObject } from '../types'
 
 let nodeOps = getNodeOps(mockTresContext())
 const pool = []

+ 2 - 3
src/core/nodeOps.ts

@@ -1,3 +1,5 @@
+import type { TresContext } from '../composables'
+import type { DisposeType, LocalState, TresInstance, TresObject, TresObject3D, TresPrimitive, WithMathProps } from '../types'
 import { BufferAttribute, Object3D } from 'three'
 import { isRef, type RendererOptions } from 'vue'
 import { useLogger } from '../composables'
@@ -5,8 +7,6 @@ import { attach, deepArrayEqual, doRemoveDeregister, doRemoveDetach, invalidateI
 import * as is from '../utils/is'
 import { createRetargetingProxy } from '../utils/primitive/createRetargetingProxy'
 import { catalogue } from './catalogue'
-import type { TresContext } from '../composables'
-import type { DisposeType, LocalState, TresInstance, TresObject, TresObject3D, TresPrimitive, WithMathProps } from '../types'
 
 const { logError } = useLogger()
 
@@ -330,7 +330,6 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     invalidateInstance(node as TresObject)
   }
 
-  // eslint-disable-next-line unicorn/consistent-function-scoping
   function parentNode(node: TresObject): TresObject | null {
     return node?.__tres?.parent || null
   }

+ 5 - 5
src/devtools/plugin.ts

@@ -1,17 +1,17 @@
+import type {
+  App as DevtoolsApp,
+} from '@vue/devtools-api'
+import type { TresContext } from '../composables'
+import type { TresObject } from './../types'
 import {
   setupDevtoolsPlugin,
 } from '@vue/devtools-api'
 import { Color, type Mesh } from 'three'
 import { reactive } from 'vue'
-import type {
-  App as DevtoolsApp,
-} from '@vue/devtools-api'
 import { createHighlightMesh, editSceneObject } from '../utils'
 import * as is from '../utils/is'
 import { bytesToKB, calculateMemoryUsage } from '../utils/perf'
 import { toastMessage } from './utils'
-import type { TresContext } from '../composables'
-import type { TresObject } from './../types'
 
 export interface Tags {
   label: string

+ 2 - 2
src/directives/vDistanceTo.ts

@@ -1,8 +1,8 @@
-import { ArrowHelper } from 'three'
 import type { Ref } from 'vue'
+import type { TresObject } from '../types'
+import { ArrowHelper } from 'three'
 import { useLogger } from '../composables'
 import { extractBindingPosition } from '../utils'
-import type { TresObject } from '../types'
 
 const { logWarning } = useLogger()
 

+ 4 - 4
src/directives/vLightHelper.ts

@@ -1,3 +1,7 @@
+import type {
+  Light,
+} from 'three'
+import type { TresObject } from '../types'
 import {
   DirectionalLightHelper,
   HemisphereLightHelper,
@@ -5,11 +9,7 @@ import {
   SpotLightHelper,
 } from 'three'
 import { RectAreaLightHelper } from 'three-stdlib'
-import type {
-  Light,
-} from 'three'
 import { useLogger } from '../composables'
-import type { TresObject } from '../types'
 
 const { logWarning } = useLogger()
 

+ 2 - 2
src/utils/createEventManager/createEventManager.test.ts

@@ -1,11 +1,11 @@
+import type { TresContext } from '../../composables/useTresContextProvider'
+import type { CreateEventManagerProps } from './createEventManager'
 import { Scene } from 'three'
 import { beforeEach, describe, expect, it, vi } from 'vitest'
 import { shallowRef } from 'vue'
 import { nodeOps as getNodeOps } from './../../core/nodeOps'
 import { createEventManager } from './createEventManager'
 import { eventsNoop } from './eventsNoop'
-import type { TresContext } from '../../composables/useTresContextProvider'
-import type { CreateEventManagerProps } from './createEventManager'
 
 let t = mockTresInstance()
 let context = t.context

Fichier diff supprimé car celui-ci est trop grand
+ 781 - 741
src/utils/createEventManager/eventsRaycast.test.ts


+ 88 - 51
src/utils/createEventManager/eventsRaycast.ts

@@ -1,12 +1,12 @@
-import { Raycaster, Vector2, Vector3 } from 'three'
 import type { Object3D, Intersection as ThreeIntersection } from 'three'
+import { isProd, useLogger, type TresContext } from '../../composables'
+import type { EventHandlers, IntersectionEvent, Properties, ThreeEvent, TresCamera, TresInstance, TresObject } from '../../types'
+import type { CreateEventManagerProps } from './createEventManager'
+import { Raycaster, Vector2, Vector3 } from 'three'
 import { prepareTresInstance } from '..'
 import * as is from '../is'
 import { DOM_EVENT_NAMES, DOM_TO_PASSIVE, DOM_TO_THREE, type DomEventName, type DomEventTarget, POINTER_EVENT_NAMES, THREE_EVENT_NAMES } from './const'
 import { deprecatedEventsToNewEvents } from './deprecatedEvents'
-import type { TresContext } from '../../composables'
-import type { EventHandlers, IntersectionEvent, Properties, ThreeEvent, TresInstance, TresObject } from '../../types'
-import type { CreateEventManagerProps } from './createEventManager'
 
 // NOTE:
 // This file consists of type definitions and functions
@@ -23,7 +23,7 @@ import type { CreateEventManagerProps } from './createEventManager'
 // future modifications simpler if Event type changes.
 type RaycastEvent = MouseEvent | PointerEvent | WheelEvent
 type RaycastEventTarget = DomEventTarget
-type ThreeEventStub<DomEvent> = Omit<ThreeEvent<DomEvent>, 'eventObject' | 'object' | 'currentTarget' | 'target' | 'distance' | 'point'> & Partial<IntersectionEvent<DomEvent>>
+type ThreeEventStub<DomEvent> = Omit<ThreeEvent<DomEvent>, 'eventObject' | 'object' | 'currentTarget' | 'target' | 'distance' | 'point'> & Partial<IntersectionEvent<DomEvent>> & { currentTarget: TresObject | null | undefined }
 type Object3DWithEvents = Object3D & EventHandlers
 
 function getInitialEvent() {
@@ -60,9 +60,9 @@ function getInitialConfig(context: TresContext) {
     objectsWithEvents: [] as Object3D[],
 
     priorIntersections: [] as ThreeIntersection[],
+    priorInitialHits: new Set<Object3D>(),
     priorHits: new Set<Object3D>(),
-    // TODO: Use or remove
-    initialHits: new Set<Object3D>(),
+
     blockingObjects: new Set<Object3D>(),
 
     lastMoveEvent: getInitialEvent(),
@@ -79,16 +79,20 @@ function getLastEvent(config: Config) {
   return config.lastMoveEvent
 }
 
-function connect(target: RaycastEventTarget, eventManagerHandler: (ev: RaycastEvent) => void) {
+function connect(target: RaycastEventTarget, eventManagerHandler: (ev: RaycastEvent) => void, config: Config) {
   for (const domEventName of POINTER_EVENT_NAMES) {
     target.addEventListener(domEventName, eventManagerHandler, { passive: DOM_TO_PASSIVE[domEventName] })
   }
 
+  const leave = (pointerEvent: PointerEvent) => handleIntersections(pointerEvent, [], config)
+  target.addEventListener('pointerleave', leave, { passive: DOM_TO_PASSIVE.pointerleave })
+
   return {
     disconnect: () => {
       for (const domEventName of POINTER_EVENT_NAMES) {
         target.removeEventListener(domEventName, eventManagerHandler)
       }
+      target.removeEventListener('pointerleave', leave)
     },
   }
 }
@@ -173,6 +177,8 @@ function insert(_instance: TresObject, config: Config) {
 }
 
 const patchPropDomEventRE = new RegExp(`${THREE_EVENT_NAMES.join('|')}`)
+const patchPropDomEventWithUnsupportedModifiersRE = new RegExp(`(${THREE_EVENT_NAMES.join('|')})(Capture|Passive|Once)+`)
+let sentUnsupportedEventModifiersWarning = false
 
 function patchProp(instance: TresObject, propName: string, prevValue: any, nextValue: any, config: Config) {
   if (!is.object3D(instance)) { return false }
@@ -190,7 +196,16 @@ function patchProp(instance: TresObject, propName: string, prevValue: any, nextV
     return true
   }
 
-  if (!patchPropDomEventRE.test(deprecatedEventsToNewEvents(propName))) {
+  if (!isProd) {
+    if (patchPropDomEventWithUnsupportedModifiersRE.test(propName)) {
+      if (!sentUnsupportedEventModifiersWarning) {
+        sentUnsupportedEventModifiersWarning = true
+        useLogger().logWarning(`${propName} contains currently unsupported event modifiers. Handlers will not be called. Please remove. (No further warnings will be logged for event modifiers.)`)
+      }
+    }
+  }
+
+  if (!patchPropDomEventRE.test(propName)) {
     return false
   }
 
@@ -204,15 +219,15 @@ function patchProp(instance: TresObject, propName: string, prevValue: any, nextV
   return true
 }
 
-function remove(_instance: TresObject, config: Config) {
-  if (!_instance.isObject3D) { return }
+function remove(instance: TresObject, config: Config) {
+  if (!instance.isObject3D) { return }
 
   // NOTE: This function will typically be called with an object
   // that's about to be removed. We will only get the top-level
   // object and not the descendants.
   // Grab the object and all descendants.
-  const instanceAndDescendants = new Set()
-  const children = [_instance]
+  const instanceAndDescendants = new Set<Object3D>()
+  const children = [instance as Object3D]
 
   while (children.length) {
     const child = children.pop()
@@ -237,16 +252,20 @@ function remove(_instance: TresObject, config: Config) {
   handleIntersections(getLastEvent(config), intersections, config)
 
   // NOTE: Remove the remaining traces of the object and descendants
-  config.priorHits = config.priorHits.difference(instanceAndDescendants)
+  for (const instance of instanceAndDescendants) {
+    config.priorHits.delete(instance)
+  }
   config.isEventsDirty = true
 }
 
 function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIntersection[], config: Config) {
   // NOTE: STRUCTURE OF THIS FUNCTION BODY
-  // 1) Gather `hits`. This set includes all "hit" meshes and their ancestors.
-  // 2) Create an outgoing event "stub" with the fields common to all events
-  // handlers will receive.
-  // 3) Propagate the events to handlers.
+  // 1) Gather `hits`, `hitsEntered` and `hitsLeft`.
+  // The `hits` Set typically includes all `intersections`
+  // objects and their ancestors.
+  // 2) Create an outgoing event "stub" with the fields
+  // common to all events handlers will receive.
+  // 3) Call event handlers
 
   // NOTE:
   // 1) Gather `hits`
@@ -268,6 +287,9 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
   // to continue behind them, we'll stop processing intersections
   // after we see one containing a "blocking" object.
   const hits = new Set<Object3D>()
+  const hitsLeft = config.priorHits
+  const hitsEntered = new Set<Object3D>()
+
   const initialHits = new Set<Object3D>()
   const filteredIntersections = []
   const eventIntersections = []
@@ -287,7 +309,15 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
       if (obj.__tres?.eventCount) {
         eventIntersections.push({ ...intersection, eventObject: obj })
       }
-      hits.add(obj)
+      if (!hits.has(obj)) {
+        hits.add(obj)
+        if (hitsLeft.has(obj)) {
+          hitsLeft.delete(obj)
+        }
+        else {
+          hitsEntered.add(obj)
+        }
+      }
       obj = obj.parent
     }
     if (hasBlockingObject) {
@@ -322,13 +352,12 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
       pointer: config.pointer,
       delta: distance,
       ray: config.raycaster.ray,
-      camera: config.raycaster.camera,
+      camera: config.raycaster.camera as TresCamera,
       nativeEvent: incomingEvent,
       // NOTE: If the user has e.g. `@click.prevent`, Vue will
-      // call `preventDefault` internally without checking whether
-      // it exists and is a function. This will throw unless we
-      // add it to the outgoing event.
-      preventDefault: incomingEvent.preventDefault,
+      // call `preventDefault` on this event. That will throw
+      // if the method doesn't exist. So add it.
+      preventDefault: () => incomingEvent.preventDefault(),
       stopped: false,
       stopPropagation: () => {},
     },
@@ -339,12 +368,11 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
   // NOTE:
   // 3) Propagate the events to handlers.
   //
-  // NOTE: Propagate `pointermissed`.
-  // Propagated first so other user handlers can clean up the effects.
+  // NOTE: Call `pointermissed`.
+  // `pointermissed` is not bubbled and cannot be stopped with `stopPropagation`.
   if (incomingEvent.type === 'click' || incomingEvent.type === 'dblclick' || incomingEvent.type === 'contextmenu') {
     outgoingEvent.stopped = false
     for (const obj of config.objectsWithEvents) {
-      if (outgoingEvent.stopped) { break }
       if (hits.has(obj)) { continue }
       outgoingEvent.eventObject = obj
       outgoingEvent.currentTarget = obj
@@ -354,26 +382,15 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
     }
   }
 
-  const hitsLeft = config.priorHits.difference(hits)
-  const hitsEntered = hits.difference(config.priorHits)
-
   // NOTE: Call `pointer{leave,enter}`
-  /**
-   * Events mouseenter/mouseleave are like mouseover/mouseout.
-   * They trigger when the mouse pointer enters/leaves the element.
-   *
-   * But there are two important differences:
-   *
-   * - Transitions inside the element, to/from descendants, are not counted.
-   * - Events mouseenter/mouseleave do not bubble.
-   *
-   * https://javascript.info/mousemove-mouseover-mouseout-mouseenter-mouseleave#events-mouseenter-and-mouseleave
-   */
   if (hitsLeft.size) {
     // NOTE: Propagate `pointerleave`
-    // TODO: Should use config.initialHits, not config.priorIntersections
+    // `pointerleave` is not bubbled and cannot be stopped with `stopPropagation`.
+    // TODO: Should use config.priorHits, not config.priorIntersections
     callIntersectionObjectsIf('pointerleave', outgoingEvent, config.priorIntersections, (obj: Object3D) => { return hitsLeft.has(obj) })
+  }
 
+  {
     // NOTE: Bubble `pointerout`
     // NOTE: Should use config.initialHits, not config.priorIntersections
     const duplicates = new Set()
@@ -382,7 +399,7 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
       if (outgoingEvent.stopped) { break }
 
       let object: TresObject | null = intersection.object
-      if (hits.has(object) || duplicates.has(object)) { continue }
+      if (initialHits.has(object) || duplicates.has(object)) { continue }
 
       // NOTE: An event "is-a" `Intersection`,
       // so copy intersection values to the event.
@@ -402,26 +419,28 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
   if (hitsEntered.size) {
     // TODO: Should use config.initialHits, not intersections
     callIntersectionObjectsIf('pointerenter', outgoingEvent, intersections, (obj: Object3D) => { return hitsEntered.has(obj) })
+  }
 
+  {
     // NOTE: Bubble pointerover
+    const duplicates = new Set()
     outgoingEvent.stopped = false
-    const seenUUIDs: Record<string, boolean> = {}
     for (const intersection of filteredIntersections) {
       if (outgoingEvent.stopped) { break }
 
       let object: TresObject | null = intersection.object
-      if (config.priorHits.has(object) || object.uuid in seenUUIDs) { continue }
+      if (config.priorInitialHits.has(object) || duplicates.has(object)) { continue }
 
       // NOTE: An event "is-a" `Intersection`,
       // so copy intersection values to the event.
       Object.assign(outgoingEvent, intersection)
       outgoingEvent.target = object
 
-      while (object && !outgoingEvent.stopped && !(object.uuid in seenUUIDs)) {
+      while (object && !outgoingEvent.stopped && !duplicates.has(object)) {
         outgoingEvent.eventObject = object
         outgoingEvent.currentTarget = object
         object.onPointerover?.(outgoingEvent)
-        seenUUIDs[object.uuid] = true
+        duplicates.add(object)
         object = object.parent
       }
     }
@@ -453,26 +472,44 @@ function handleIntersections(incomingEvent: RaycastEvent, intersections: ThreeIn
   }
 
   config.priorIntersections = filteredIntersections
+  config.priorInitialHits = initialHits
   config.priorHits = hits
+  // NOTE:
+  // Like DOM events, we set this to null after we're done with the event.
+  // We reuse events for multiple handlers, changing `currentTarget` and
+  // other fields. This keeps us from creating new objects and copying
+  // values for every handler call.
+  // Users wanting to use an event at a later time should copy the event.
+  // @ts-expect-error – not typed for null; doing so would be a pain for users.
+  outgoingEvent.currentTarget = null
+  // @ts-expect-error – same
+  outgoingEvent.eventObject = null
+  // @ts-expect-error – same
+  outgoingEvent.target = null
+  // @ts-expect-error – same
+  outgoingEvent.object = null
 }
 
 function callIntersectionObjectsIf(domEventName: DomEventName, event: ThreeEventStub<MouseEvent>, intersections: ThreeIntersection[], cond: (a: any) => boolean) {
   const duplicates = new Set()
-
   event.stopped = false
 
-  for (const intersection of intersections) {
-    if (event.stopped) { break }
+  // NOTE: The events handled here are not "bubbled"
+  // They cannot be stopped with `stopPropagation`.
 
+  for (const intersection of intersections) {
     // NOTE: An event "is-a" `Intersection`,
     // so copy intersection values to the event.
     Object.assign(event, intersection)
     let object: Object3DWithEvents | null = intersection.object
-    while (object && !duplicates.has(object) && !event.stopped) {
+    while (object && !duplicates.has(object)) {
       duplicates.add(object)
       if (cond(object)) {
+        // NOTE: Non-bubbled events are "self" events
+        // so all the following fields are "self"
         event.eventObject = object
         event.currentTarget = object
+        event.object = object
         event.target = object
         event.eventObject[DOM_TO_THREE[domEventName]]?.(event)
       }

+ 1 - 1
src/utils/createEventManager/useEventsOptions.test.ts

@@ -1,6 +1,6 @@
+import type { TresContext } from 'src/composables/useTresContextProvider'
 import { describe, expect, it, vi } from 'vitest'
 import { computed, ref, shallowRef } from 'vue'
-import type { TresContext } from 'src/composables/useTresContextProvider'
 import { raycastProps } from '.'
 import { createEventManager } from './createEventManager'
 import { eventsRaycast } from './eventsRaycast'

+ 1 - 1
src/utils/createEventManager/useEventsOptions.ts

@@ -1,7 +1,7 @@
-import { toValue, watchEffect } from 'vue'
 import type { TresContext } from 'src/composables/useTresContextProvider'
 import type { EmitEventFn } from 'src/types'
 import type { MaybeRefOrGetter } from 'vue'
+import { toValue, watchEffect } from 'vue'
 import * as is from '../is'
 import { createEventManager, type EventManager, type EventManagerProps } from './createEventManager'
 import { eventsNoop } from './eventsNoop'

+ 1 - 1
src/utils/createPriorityEventHook.ts

@@ -1,5 +1,5 @@
-import { tryOnScopeDispose } from '@vueuse/core'
 import type { EventHookOff, IsAny } from '@vueuse/core'
+import { tryOnScopeDispose } from '@vueuse/core'
 
 // NOTE: Based on vueuse's createEventHook
 // https://github.com/vueuse/vueuse/blob/1558cd2b5b019abc1feda6d702caa1053a182903/packages/shared/createEventHook/index.ts

+ 2 - 2
src/utils/index.ts

@@ -1,10 +1,10 @@
-import { DoubleSide, MathUtils, MeshBasicMaterial, Scene, Vector3 } from 'three'
 import type { nodeOps } from 'src/core/nodeOps'
 import type { AttachType, LocalState, TresInstance, TresObject, TresPrimitive } from 'src/types'
 import type { Material, Mesh, Object3D, Texture } from 'three'
+import type { TresContext } from '../composables/useTresContextProvider'
+import { DoubleSide, MathUtils, MeshBasicMaterial, Scene, Vector3 } from 'three'
 import { HightlightMesh } from '../devtools/highlight'
 import * as is from './is'
-import type { TresContext } from '../composables/useTresContextProvider'
 
 export function toSetMethodName(key: string) {
   return `set${key[0].toUpperCase()}${key.slice(1)}`

+ 1 - 1
src/utils/normalize.ts

@@ -1,5 +1,5 @@
-import { Color, Vector3 } from 'three'
 import type { ColorRepresentation } from 'three'
+import { Color, Vector3 } from 'three'
 
 export type SizeFlexibleParams =
   | number[]

+ 1 - 1
src/utils/test-utils.ts

@@ -1,5 +1,5 @@
-import { createApp } from 'vue'
 import type { Fn } from '@vueuse/core'
+import { createApp } from 'vue'
 
 export function withSetup(composable: Fn) {
   let result

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff