Peter 7 miesięcy temu
rodzic
commit
6e88ab2d1b

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

@@ -87,7 +87,7 @@ onLoop(({ elapsed }) => {
   sphereRef.value.scale.set(scale2, scale2, scale2)
 
   // NOTE: Update events without needing the mouse to move
-  canvasRef.value?.context?.eventManager?.forceUpdate()
+  canvasRef.value?.context?.events?.forceUpdate()
 })
 </script>
 

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

@@ -46,16 +46,16 @@ function onWheel(ev: ThreeEvent<MouseEvent>) {
   ev.eventObject.material.color.set('#FFFF00')
 }
 
-const defaultConnecter = (target: EventTarget, eventManagerHandler: EventListener) => {
+const defaultConnecter = (target: EventTarget, eventHandler: EventListener) => {
   const POINTER_EVENT_NAMES = ['wheel', 'click', 'pointermove', 'pointerup', 'pointerdown', 'contextmenu', 'dblclick']
   for (const domEventName of POINTER_EVENT_NAMES) {
-    target.addEventListener(domEventName, eventManagerHandler)
+    target.addEventListener(domEventName, eventHandler)
   }
 
   return {
     disconnect: () => {
       for (const domEventName of POINTER_EVENT_NAMES) {
-        target.removeEventListener(domEventName, eventManagerHandler)
+        target.removeEventListener(domEventName, eventHandler)
       }
     },
   }
@@ -87,30 +87,30 @@ const eventsFns = {
 type DisconnectFn = () => void
 type ConnectFn = (target: HTMLElement) => void
 
-let eventManager: {
+let events: {
   connect: ConnectFn
   disconnect: DisconnectFn
   target: HTMLElement
 } | null = null
 
 function onChangeConnecterId(id: keyof typeof CONNECTERS) {
-  if (!eventManager) { return }
-  const target = eventManager.target
-  eventManager.disconnect()
+  if (!events) { return }
+  const target = events.target
+  events.disconnect()
   connecterIdRef.value = id
   eventsFns.connect = CONNECTERS[connecterIdRef.value]
-  eventManager.connect(target)
+  events.connect(target)
 }
 
 function ready(ctx: TresContext) {
-  eventManager = ctx.eventManager
+  events = ctx.events
 }
 </script>
 
 <template>
   <OverlayInfo>
     <h1><code>connect</code></h1>
-    <p>By default, <code>eventManager</code> will listen for DOM events emitted from the canvas. But if you don't need some of those events – particularly <code>pointermove</code>, you can get extra performance by not listening for them.</p>
+    <p>By default, the <code>Events</code> system will listen for DOM events emitted from the canvas. But if you don't need some of those events – particularly <code>pointermove</code>, you can get extra performance by not listening for them.</p>
     <p>:events has a settable <code>connect</code> function.</p>
     <code>&lt;TresCanvas :events="{connect: ...}" /&gt;</code>
     <hr />
@@ -123,7 +123,7 @@ function ready(ctx: TresContext) {
     <hr />
     <h2>NOTE</h2>
     <p>The <code>:events</code> prop is not reactive. But if you have a reference to it, you can redefine functions, which will then be called at some later point during the lifecycle.</p>
-    <p><code>connect</code> is not designed to be changed on the fly. To do so, as we have done here, you will have to get a handle on <code>context.eventManager</code> and call <code>disconnect</code>, then <code>connect</code>.</p>
+    <p><code>connect</code> is not designed to be changed on the fly. To do so, as we have done here, you will have to get a handle on <code>context.events</code> and call <code>disconnect</code>, then <code>connect</code>.</p>
   </OverlayInfo>
   <TresCanvas
     window-size

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

@@ -81,7 +81,7 @@ function distSqToCam(intersection: Intersection<Object3D>) {
 <template>
   <OverlayInfo>
     <h1><code>filter</code></h1>
-    <p>context.eventManager has a settable <code>filter</code> function. It allows you to sort and filter intersections before they are handled, possibly sorting intersections to process important "hits" earlier.</p>
+    <p>context.events has a settable <code>filter</code> function. It allows you to sort and filter intersections before they are handled, possibly sorting intersections to process important "hits" earlier.</p>
     <p>The filter can be set by passing a filter function on <code>TresCanvas</code> like:</p>
     <code>&lt;TresCanvas :events="{filter: {{ filterFnIdRef }}}" /&gt;</code>
     <hr />

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

@@ -36,7 +36,7 @@ setInterval(() => {
 
 <template>
   <OverlayInfo>
-    <h1><code>eventManager.remove</code></h1>
+    <h1><code>context.events.remove</code></h1>
     <p>When an object is removed from the scene, it should trigger <code>@pointer{out,leave}</code> events, if necessary.</p>
     <h2>Setup</h2>
     <p>A few boxes are added/removed from the scene. Their events "bubble up" to the larger parent box.</p>

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

@@ -88,7 +88,7 @@ function onChangeEventsEnabled(b: boolean) {
   </TresCanvas>
   <OverlayInfo>
     <h1><code>eventsTarget</code></h1>
-    <p>The <code>eventsManager</code> connects to <code>context.renderer.value.domElement</code> by default. But users can override this by setting <code>:events-target</code> on <code>TresCanvas</code>.</p>
+    <p>The <code>Events</code> system connects to <code>context.renderer.value.domElement</code> by default. But users can override this by setting <code>:events-target</code> on <code>TresCanvas</code>.</p>
     <h2>Select an <code>:events-target</code> to <code>connect</code> to.</h2>
     <div v-for="id of ['undefined', 'blue', 'red']" :key="id">
       <input :id="id" :checked="id === eventsTargetIdRef" type="radio" name="target" @change="() => onChangeEventTargetId(id)" />
@@ -98,12 +98,12 @@ function onChangeEventsEnabled(b: boolean) {
     <p>The on-screen objects should still respond to pointer events.</p>
     <hr />
     <h1><code>eventsEnabled</code></h1>
-    <p>Events are enabled by default but can be turned off by setting <code>:events-enabled="false"</code> on <code>TresCanvas</code></p>
+    <p>The <code>Events</code> system is enabled by default but can be turned off by setting <code>:events-enabled="false"</code> on <code>TresCanvas</code></p>
     <input id="enabled" type="checkbox" checked @change="(e) => onChangeEventsEnabled((e.target as null | HTMLFormElement)?.checked ?? false)" />
     <label for="enabled">Enable events</label>
     <hr />
     <h2>NOTE</h2>
-    <p>Tres controls like <code>OrbitControls</code> are unaffected by <code>eventManager</code> settings.</p>
+    <p>Tres controls like <code>OrbitControls</code> are unaffected by <code>context.events</code> settings.</p>
     <p>Additional steps must be taken to update them appropriately, in the case of changing the event target or disabling events.</p>
   </OverlayInfo>
 </template>

+ 7 - 7
src/components/TresCanvas.vue

@@ -5,10 +5,10 @@ import type {
   ToneMapping,
   WebGLRendererParameters,
 } from 'three'
-import type { App, Ref } from 'vue'
+import type { App, MaybeRef, MaybeRefOrGetter, Ref } from 'vue'
 import type { RendererPresetsType } from '../composables/useRenderer/const'
 import type { TresCamera, TresObject, TresScene } from '../types/'
-import type { EventManagerProps } from '../utils/createEventManager/createEventManager'
+import type { EventsProps } from '../utils/createEvents/createEvents'
 import { PerspectiveCamera, Scene } from 'three'
 
 import * as THREE from 'three'
@@ -55,11 +55,11 @@ export interface TresCanvasProps
   camera?: TresCamera
   preset?: RendererPresetsType
   windowSize?: boolean
-  // NOTE: used by `eventManager`
+  // NOTE: used by `events`
 
-  eventsEnabled?: boolean
-  eventsTarget?: EventTarget
-  events?: EventManagerProps
+  eventsEnabled?: MaybeRefOrGetter<boolean>
+  eventsTarget?: MaybeRefOrGetter<EventTarget | null>
+  events?: Partial<EventsProps>
 
   // Misc opt-out flags
   enableProvideBridge?: boolean
@@ -82,7 +82,7 @@ const props = withDefaults(defineProps<TresCanvasProps>(), {
   enableProvideBridge: true,
 })
 
-// Define emits for Pointer events, pass `emit` into useTresEventManager so we can emit events off of TresCanvas
+// Define emits for Pointer events, pass `emit` into useTresEvents so we can emit events off of TresCanvas
 // Not sure of this solution, but you have to have emits defined on the component to emit them in vue
 const emit = defineEmits([
   'render',

+ 12 - 9
src/composables/useTresContextProvider/index.ts

@@ -8,13 +8,14 @@ import { Raycaster } from 'three'
 import { computed, inject, onUnmounted, provide, readonly, ref, shallowRef } from 'vue'
 import { extend } from '../../core/catalogue'
 import { createRenderLoop } from '../../core/loop'
-import { type EventManager, useEventsOptions } from '../../utils/createEventManager'
+import { type Events, useEventsOptions as withEventsProps } from '../../utils/createEvents'
 import { calculateMemoryUsage } from '../../utils/perf'
 
 import { useCamera } from '../useCamera'
 import { useRenderer } from '../useRenderer'
 import useSizes, { type SizesType } from '../useSizes'
 import { useTresReady } from '../useTresReady'
+import TresCanvas, { type TresCanvasProps } from 'src/components/TresCanvas.vue'
 
 export interface InternalState {
   priority: Ref<number>
@@ -49,7 +50,7 @@ export interface PerformanceState {
 }
 
 export interface TresContext {
-  eventManager: EventManager
+  events: Events
   scene: ShallowRef<TresScene>
   sizes: SizesType
   extend: (objects: any) => void
@@ -75,10 +76,10 @@ export interface TresContext {
   setCameraActive: (cameraOrUuid: Camera | string) => void
   deregisterCamera: (maybeCamera: unknown) => void
   parentContext?: TresContext
-  // Events
-  // Temporaly add the methods to the context, this should be handled later by the EventManager state on the context https://github.com/Tresjs/tres/issues/515
-  // When thats done maybe we can short the names of the methods since the parent will give the context.
-  events?: { enabled: MaybeRefOrGetter<boolean> }
+  // The TresCanvas' emit.
+  emit: EmitEventFn
+  // The user's TresCanvas' props.
+  props: TresCanvasProps
 }
 
 export function useTresContextProvider({
@@ -93,7 +94,7 @@ export function useTresContextProvider({
   canvas: MaybeRef<HTMLCanvasElement>
   windowSize: MaybeRefOrGetter<boolean>
   rendererOptions: UseRendererOptions
-  props: any
+  props: TresCanvasProps
   emit: EmitEventFn
 
 }): TresContext {
@@ -142,7 +143,7 @@ export function useTresContextProvider({
     },
   )
 
-  const partialContext: Omit<TresContext, 'eventManager'> & { eventManager?: EventManager } = {
+  const partialContext: Omit<TresContext, 'events'> & { events?: Events } = {
     sizes,
     scene: localScene,
     camera,
@@ -170,9 +171,11 @@ export function useTresContextProvider({
     setCameraActive,
     deregisterCamera,
     loop: createRenderLoop(),
+    props,
+    emit,
   }
 
-  partialContext.eventManager = useEventsOptions(props, partialContext as TresContext, emit).eventManager
+  partialContext.events = withEventsProps(partialContext as TresContext).events
   const ctx = partialContext as TresContext
 
   provide('useTres', ctx)

+ 3 - 3
src/core/nodeOps.ts

@@ -95,7 +95,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     child = unboxTresPrimitive(childInstance)
     parent = unboxTresPrimitive(parentInstance)
 
-    context.eventManager?.insert(child)
+    context.events?.insert(child)
 
     context.registerCamera(child)
 
@@ -128,7 +128,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     if (!node) { return }
 
     if (dispose === undefined) {
-      context.eventManager?.remove(node)
+      context.events?.remove(node)
     }
 
     // NOTE: Derive `dispose` value for this `remove` call and
@@ -251,7 +251,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
       return
     }
 
-    if (context.eventManager?.patchProp(node, prop, prevValue, nextValue)) {
+    if (context.events?.patchProp(node, prop, prevValue, nextValue)) {
       return
     }
 

+ 1 - 1
src/types/index.ts

@@ -174,7 +174,7 @@ export interface EventHandlers {
 
 export interface PointerCaptureTarget {
   // NOTE: Below are "fake" DOM Element methods that allow objects
-  // to communicate with Tres' `EventManager` about pointer capture.
+  // to communicate with Tres' `Events` about pointer capture.
   // See: https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
   setPointerCapture: (pointerId: number) => void
   releasePointerCapture: (pointerId: number) => void

+ 0 - 21
src/utils/createEventManager/README.md

@@ -1,21 +0,0 @@
-# createEventManager
-
-The files in this directory work together to create an `EventManager`. There are 3 main parts:
-
-## createEventManager.ts
-
-`createEventManager` handles state creation/management, ordering of method calls and exporting of the user API. Start here to get an overview of how `EventManager` works.
-
-## eventsRaycast.ts
-
-This file exports the default configuration for the event system: an object composed of methods called by `EventManager`. `EventManager` passes state into the methods; they hold no state themselves (at the time of this writing).
-
-Hittesting and event propagation is implemented in this file.
-
-### eventsNoop.ts
-
-This file exports an object of the same type as exported by `eventsRaycast.ts` (though with the generics filled in differently) as `events`. It's loaded when the user specifies that they don't want to use the event system.
-
-## useEventsOptions.ts
-
-This file implements the reactive elements of `EventManager`. `EventManager` itself is not reactive, but can be made to respond to changing user props via `useEventsProps()`.

+ 0 - 6
src/utils/createEventManager/index.ts

@@ -1,6 +0,0 @@
-import { createEventManager, type EventManager, type EventManagerProps } from './createEventManager'
-import { eventsNoop as disableEvents } from './eventsNoop'
-import { eventsRaycast } from './eventsRaycast'
-import { useEventsOptions } from './useEventsOptions'
-
-export { createEventManager, disableEvents, EventManager, EventManagerProps, eventsRaycast as raycastProps, useEventsOptions }

+ 0 - 367
src/utils/createEventManager/useEventsOptions.test.ts

@@ -1,367 +0,0 @@
-import type { TresContext } from 'src/composables/useTresContextProvider'
-import { describe, expect, it, vi } from 'vitest'
-import { computed, ref, shallowRef } from 'vue'
-import { raycastProps } from '.'
-import { createEventManager } from './createEventManager'
-import { eventsRaycast } from './eventsRaycast'
-import { useEventsOptions } from './useEventsOptions'
-
-let context = mockTresContext()
-let eventManager = createEventManager(eventsRaycast, context)
-
-describe('useEventsOptions', () => {
-  beforeEach(() => {
-    context = mockTresContext()
-    eventManager = createEventManager(eventsRaycast, context)
-
-    vi.clearAllMocks()
-  })
-  describe('!props', () => {
-    it('sets `eventManager.enabled` to `true`', () => {
-      useEventsOptions(undefined, context, () => {}, eventManager)
-      expect(eventManager.enabled).toBe(true)
-    })
-    it('sets `eventManager.target` to `context.renderer.value.domElement`', () => {
-      useEventsOptions(undefined, context, () => {}, eventManager)
-      expect(eventManager.target).toBe(context.renderer.value.domElement)
-    })
-  })
-
-  describe('props.events', () => {
-    describe('`eventManager` is not passed', () => {
-      describe('!("events" in props)', () => {
-        it('uses `eventsRaycast` as default `events`', () => {
-          const emitFn = () => {}
-          const { eventManager } = useEventsOptions({}, context, emitFn)
-          const eventManager0 = createEventManager(raycastProps, context, emitFn)
-          // NOTE: `props.events` is encapsulated inside of `eventManager`
-          // so we an't access it directly.
-          // We can check the `eventManager.config`s for equality,
-          // since they're produced by a props method.
-          expect(eventManager.config).toStrictEqual(eventManager0.config)
-        })
-      })
-      describe('props.events === undefined', () => {
-        it('uses `eventsRaycast` as default `events`', () => {
-          const emitFn = () => {}
-          const { eventManager } = useEventsOptions({ events: undefined }, context, emitFn)
-          const eventManager0 = createEventManager(raycastProps, context, emitFn)
-          // NOTE: `props.events` is encapsulated inside of `eventManager`
-          // so we an't access it directly.
-          // We can check the `eventManager.config`s for equality,
-          // since they're produced by a props method.
-          expect(eventManager.config).toStrictEqual(eventManager0.config)
-        })
-      })
-      describe('props.events === true', () => {
-        it('uses `eventsRaycast` as default `events`', () => {
-          const emitFn = () => {}
-          // NOTE: This isn't permitted by the type system, but it's
-          // an easy case to cover and perhaps a common mistake.
-          // @ts-expect-error `true` is not an accepted value for `props.events`.
-          const { eventManager } = useEventsOptions({ events: true }, context, emitFn)
-          const eventManager0 = createEventManager(raycastProps, context, emitFn)
-          // NOTE: `props.events` is encapsulated inside of `eventManager`
-          // so we an't access it directly.
-          // We can check the `eventManager.config`s for equality,
-          // since they're produced by a props method.
-          expect(eventManager.config).toStrictEqual(eventManager0.config)
-        })
-      })
-      describe('props.events === 1', () => {
-        it('uses `eventsRaycast` as default `events`', () => {
-          const emitFn = () => {}
-          // NOTE: This isn't permitted by the type system, but it's
-          // an easy case to cover and perhaps a common mistake.
-          // @ts-expect-error `1` is not an accepted value for `props.events`.
-          const { eventManager } = useEventsOptions({ events: 1 }, context, emitFn)
-          const eventManager0 = createEventManager(raycastProps, context, emitFn)
-          // NOTE: `props.events` is encapsulated inside of `eventManager`
-          // so we an't access it directly.
-          // We can check the `eventManager.config`s for equality,
-          // since they're produced by a props method.
-          expect(eventManager.config).toStrictEqual(eventManager0.config)
-        })
-      })
-      describe('props.events === null', () => {
-        it('does not connect to a target', () => {
-          const { eventManager } = useEventsOptions({ events: null }, context, () => {})
-          const spy = vi.fn()
-          eventManager.connect({ addEventListener: spy, removeEventListener: spy })
-          expect(spy).not.toBeCalled()
-        })
-      })
-      describe('props.events === false', () => {
-        it('does not connect to a target', () => {
-          // NOTE: This isn't permitted by the type system, but it's
-          // an easy case to cover and might make sense for some users.
-          // @ts-expect-error `false` is not an accepted value for `props.events`.
-          const { eventManager } = useEventsOptions({ events: false }, context, () => {})
-          const spy = vi.fn()
-          eventManager.connect({ addEventListener: spy, removeEventListener: spy })
-          expect(spy).not.toBeCalled()
-        })
-      })
-      describe('props.events === 0', () => {
-        it('does not connect to a target', () => {
-          // NOTE: This isn't permitted by the type system.
-          // @ts-expect-error `0` is not an accepted value for `props.events`.
-          const { eventManager } = useEventsOptions({ events: 0 }, context, () => {})
-          const spy = vi.fn()
-          eventManager.connect({ addEventListener: spy, removeEventListener: spy })
-          expect(spy).not.toBeCalled()
-        })
-      })
-      describe('props.events === { ... }', () => {
-        it('fills in missing `events` methods using `eventsRaycast` methods', () => {
-          const events = {}
-          useEventsOptions({ events }, context, () => {})
-          expect(events).toStrictEqual(raycastProps)
-
-          const filter = () => []
-          const events0 = { filter }
-          useEventsOptions({ events: events0 }, context, () => {})
-          expect(events0.filter).not.toBe(raycastProps.filter)
-          expect(events0.filter).toBe(filter)
-        })
-      })
-    })
-  })
-
-  describe('props.eventsEnabled', () => {
-    describe('props.eventsEnabled === undefined', () => {
-      it('sets `enabled` to EventManager to `true`', () => {
-        const props = { }
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(eventManager.enabled).toBe(true)
-      })
-    })
-    describe('props.eventsEnabled === null', () => {
-      it('sets `enabled` to EventManager to `true`', () => {
-        const props = { eventsEnabled: null }
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(eventManager.enabled).toBe(true)
-      })
-    })
-    describe('props.eventsEnabled === boolean', () => {
-      it('sets `enabled` on EventManager when used in `useEventsOptions`', () => {
-        const props = { eventsEnabled: true }
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(eventManager.enabled).toBe(true)
-
-        props.eventsEnabled = false
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(eventManager.enabled).toBe(false)
-      })
-    })
-    describe('isRef(props.eventsEnabled)', () => {
-      it('sets `enabled` on EventManager when ref.value is updated', () => {
-        const props = { eventsEnabled: ref(true) }
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(eventManager.enabled).toBe(true)
-
-        props.eventsEnabled.value = false
-        expect(eventManager.enabled).toBe(false)
-
-        props.eventsEnabled.value = true
-        expect(eventManager.enabled).toBe(true)
-
-        props.eventsEnabled.value = false
-        expect(eventManager.enabled).toBe(false)
-
-        props.eventsEnabled.value = undefined
-        expect(eventManager.enabled).toBe(true)
-      })
-    })
-    describe('isRefOrGetter(props.eventsEnabled)', () => {
-      it('sets `enabled` on EventManager when ref.value is updated', () => {
-        const myNumber = ref(0)
-
-        const eventsEnabled = computed({
-          get() { return !!(myNumber.value % 2) },
-          set(n: number) { myNumber.value = n },
-        })
-        const props = { eventsEnabled }
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(eventManager.enabled).toBe(false)
-
-        props.eventsEnabled.value = 1
-        expect(eventManager.enabled).toBe(true)
-
-        props.eventsEnabled.value = 2
-        expect(eventManager.enabled).toBe(false)
-
-        props.eventsEnabled.value = 3
-        expect(eventManager.enabled).toBe(true)
-
-        props.eventsEnabled.value = 4
-        expect(eventManager.enabled).toBe(false)
-
-        props.eventsEnabled.value = 5
-        expect(eventManager.enabled).toBe(true)
-      })
-    })
-  })
-
-  describe('props.eventsTarget', () => {
-    describe('props.eventsTarget === undefined', () => {
-      it('calls eventManager.connect(context.renderer.value.domElement)', () => {
-        const props = { }
-        const connectSpy = vi.spyOn(eventManager, 'connect')
-
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(connectSpy).toBeCalledTimes(1)
-        expect(connectSpy).toBeCalledWith(context.renderer.value.domElement)
-      })
-    })
-
-    describe('props.eventsTarget === computed({ get..., set... })', () => {
-      it('calls eventManager.connect(props.eventsTarget.get()) when `set`', () => {
-        const connectSpy = vi.spyOn(eventManager, 'connect')
-
-        const myDom0 = document.createElement('div')
-        const target0 = document.createElement('canvas')
-        target0.addEventListener = vi.fn()
-        target0.removeEventListener = vi.fn()
-        myDom0.appendChild(target0)
-
-        const myDom1 = document.createElement('div')
-        const target1 = document.createElement('canvas')
-        target1.addEventListener = vi.fn()
-        target1.removeEventListener = vi.fn()
-        myDom1.appendChild(target1)
-
-        const domRef = shallowRef(myDom0)
-
-        const eventsTarget = computed({
-          get() { return domRef.value.firstChild },
-          set(dom: HTMLDivElement) { domRef.value = dom },
-        })
-
-        const props = { eventsTarget }
-        useEventsOptions(props, context, () => {}, eventManager)
-
-        expect(connectSpy).toBeCalledTimes(1)
-        expect(connectSpy).toBeCalledWith(target0)
-
-        expect(target0.addEventListener).toBeCalled()
-        expect(target1.addEventListener).not.toBeCalled()
-
-        vi.clearAllMocks()
-
-        domRef.value = myDom1
-
-        expect(connectSpy).toBeCalledTimes(1)
-        expect(connectSpy).toBeCalledWith(target1)
-
-        expect(target0.addEventListener).not.toBeCalled()
-        expect(target0.removeEventListener).toBeCalled()
-
-        expect(target1.addEventListener).toBeCalled()
-        expect(target1.removeEventListener).not.toBeCalled()
-      })
-    })
-
-    describe('props.eventsTarget === ref(EventTarget | null)', () => {
-      it('calls eventManager.connect(props.eventTarget) and updates on ref changes', () => {
-        const target0 = {
-          addEventListener: vi.fn(),
-          removeEventListener: vi.fn(),
-        } as unknown as HTMLElement
-        const target1 = {
-          addEventListener: vi.fn(),
-          removeEventListener: vi.fn(),
-        } as unknown as HTMLElement
-        const eventsTarget = shallowRef(target0)
-
-        const props = { eventsTarget }
-        const connectSpy = vi.spyOn(eventManager, 'connect')
-
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(connectSpy).toBeCalledTimes(1)
-        expect(connectSpy).toBeCalledWith(target0)
-
-        eventsTarget.value = target1
-        expect(connectSpy).toBeCalledTimes(2)
-        expect(connectSpy).toBeCalledWith(target1)
-
-        eventsTarget.value = target0
-        expect(connectSpy).toBeCalledTimes(3)
-        expect(connectSpy).toBeCalledWith(target0)
-
-        eventsTarget.value = null
-        expect(connectSpy).toBeCalledTimes(4)
-        expect(connectSpy).toBeCalledWith(context.renderer.value.domElement)
-      })
-    })
-
-    describe('props.eventsTarget = ref(null)', () => {
-      it('calls eventManager.connect(context.renderer.value.domElement)', () => {
-        const props = { eventsTarget: ref(null) }
-        const connectSpy = vi.spyOn(eventManager, 'connect')
-
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(connectSpy).toBeCalledTimes(1)
-        expect(connectSpy).toBeCalledWith(context.renderer.value.domElement)
-      })
-    })
-
-    describe('props.eventsTarget = EventTarget', () => {
-      it('calls eventManager.connect(props.eventsTarget)', () => {
-        const target0 = {
-          addEventListener: vi.fn(),
-          removeEventListener: vi.fn(),
-        } as unknown as HTMLElement
-        const eventsTarget = shallowRef(target0)
-
-        const props = { eventsTarget }
-        const connectSpy = vi.spyOn(eventManager, 'connect')
-
-        useEventsOptions(props, context, () => {}, eventManager)
-        expect(connectSpy).toBeCalledTimes(1)
-        expect(connectSpy).toBeCalledWith(target0)
-      })
-    })
-  })
-})
-
-describe('const { stop } = useEventsOptions(...)', () => {
-  it('stops the `events-target` watcher', () => {
-    const target0 = {
-      addEventListener: vi.fn(),
-      removeEventListener: vi.fn(),
-    } as unknown as HTMLElement
-    const target1 = {
-      addEventListener: vi.fn(),
-      removeEventListener: vi.fn(),
-    } as unknown as HTMLElement
-    const eventsTarget = shallowRef(target0)
-
-    const props = { eventsTarget }
-    const connectSpy = vi.spyOn(eventManager, 'connect')
-
-    const { stop } = useEventsOptions(props, context, () => {}, eventManager)
-    expect(connectSpy).toBeCalledTimes(1)
-    expect(connectSpy).toBeCalledWith(target0)
-
-    stop()
-
-    eventsTarget.value = target1
-    expect(connectSpy).toBeCalledTimes(1)
-    expect(connectSpy).toBeCalledWith(target0)
-    eventsTarget.value = null
-    expect(connectSpy).toBeCalledTimes(1)
-    expect(connectSpy).toBeCalledWith(target0)
-  })
-})
-
-function mockTresContext() {
-  const canvas = {
-    addEventListener: () => {},
-    removeEventListener: () => {},
-  }
-  return {
-    eventManager: {},
-    renderer: { value: { domElement: canvas } },
-  } as unknown as TresContext
-}

+ 0 - 78
src/utils/createEventManager/useEventsOptions.ts

@@ -1,78 +0,0 @@
-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'
-import { eventsRaycast as eventsDefault } from './eventsRaycast'
-
-export interface UseEventsOptions {
-  eventsEnabled?: MaybeRefOrGetter<boolean>
-  eventsTarget?: MaybeRefOrGetter<EventTarget>
-  events?: Partial<EventManagerProps>
-}
-
-/**
- * Apply reactivity to an `eventManager`.
- * Optionally create an `eventManager` if one is not passed.
- *
- * @param props `EventsOptions` values to apply to the `eventManager`
- * @param context a TresContext
- * @param emit an emit function to use in event handling
- * @param eventManager an `EventManager` to apply reactivity to.
- * @returns an object with `stop` – a function to stop reactivity and `eventManager`
- */
-export function useEventsOptions(props: UseEventsOptions, context: TresContext, emit: EmitEventFn, eventManager?: EventManager) {
-  // NOTE: Create eventManager if one was not passed.
-  if (!eventManager) {
-    if (is.und(props.events)) {
-      // NOTE: User probably hasn't defined `:events`.
-      // Use the default events setup.
-      eventManager = createEventManager(eventsDefault, context, emit)
-    }
-    else if (!props.events) {
-      // NOTE: User explicitly set `:events` to a falsy value
-      // other than `undefined`, e.g.:
-      // `<TresCanvas :events="null" />`
-      // `<TresCanvas :events="false" />`
-      // This signals they do not wish to use events at all,
-      // so create a "noop" `eventManager`.
-      eventManager = createEventManager(eventsNoop, context, emit)
-    }
-    else if (is.obj(props.events)) {
-      // NOTE: User set `:events` to `Partial<EventManagerProps>`.
-      // Fill in `:events` with defaults. Don't break the user's
-      // reference to the object – they may want to modify the object
-      // values later. Pass their object to `createEventManager`.
-      for (const [key, value] of Object.entries(eventsDefault)) {
-        if (!(key in props.events)) {
-          props.events[key as keyof typeof props.events] = value
-        }
-      }
-      eventManager = createEventManager(props.events as EventManagerProps, context, emit)
-    }
-    else {
-      // NOTE: User set `:events` to a non-object but truthy, e.g.:
-      // `<TresCanvas :events="true" />`
-      // `<TresCanvas :events="1" />`
-      // Even though this goes against the type system, assume the
-      // user wants to use the default events.
-      eventManager = createEventManager(eventsDefault, context, emit)
-    }
-  }
-
-  const enabledWatcher = watchEffect(() => {
-    eventManager.enabled = (toValue(props?.eventsEnabled) ?? true) as boolean
-  }, { flush: 'sync' })
-
-  const targetWatcher = watchEffect(() => {
-    eventManager.connect(toValue(props?.eventsTarget) ?? context.renderer.value.domElement)
-  }, { flush: 'sync' })
-
-  const unwatchers = [enabledWatcher, targetWatcher]
-  return {
-    stop: () => { unwatchers.forEach(fn => fn()) },
-    eventManager,
-  }
-}

+ 21 - 0
src/utils/createEvents/README.md

@@ -0,0 +1,21 @@
+# createEvents
+
+The files in this directory work together to create an `Events`. There are 3 main parts:
+
+## createEvents.ts
+
+`createEvents` handles state creation/management, ordering of method calls and exporting of the user API. Start here to get an overview of how `Events` works.
+
+## eventsRaycast.ts
+
+This file exports the default configuration for the event system: an object composed of methods called by `Events`. `Events` passes state into the methods; they hold no state themselves (at the time of this writing).
+
+Hittesting and event propagation is implemented in this file.
+
+### eventsNoop.ts
+
+This file exports an object of the same type as exported by `eventsRaycast.ts` (though with the generics filled in differently) as `events`. It's loaded when the user specifies that they don't want to use the event system.
+
+## useEventsOptions.ts
+
+This file implements the reactive elements of `Events`. `Events` itself is not reactive, but can be made to respond to changing user props via `useEventsProps()`.

+ 0 - 0
src/utils/createEventManager/const.ts → src/utils/createEvents/const.ts


+ 58 - 58
src/utils/createEventManager/createEventManager.test.ts → src/utils/createEvents/createEvents.test.ts

@@ -1,18 +1,18 @@
 import type { TresContext } from '../../composables/useTresContextProvider'
-import type { CreateEventManagerProps } from './createEventManager'
+import type { CreateEventsProps } from './createEvents'
 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 { createEvents } from './createEvents'
 import { eventsNoop } from './eventsNoop'
 
 let t = mockTresInstance()
 let context = t.context
 let { canvas, spies: canvasSpies } = mockCanvasAndListeners()
-let { props, spies: propsSpies } = mockEventManagerPropsAndSpies(context)
+let { props, spies: propsSpies } = mockEventsPropsAndSpies(context)
 
-describe('createEventManager', () => {
+describe('createEvents', () => {
   beforeEach(() => {
     t = mockTresInstance()
     context = t.context
@@ -21,46 +21,46 @@ describe('createEventManager', () => {
     canvas = c.canvas
     canvasSpies = c.spies
 
-    const p = mockEventManagerPropsAndSpies(context)
+    const p = mockEventsPropsAndSpies(context)
     props = p.props
     propsSpies = p.spies
     vi.clearAllMocks()
   })
 
-  describe('const eventManager = createEventManager(props, context)', () => {
+  describe('const events = createEvents(props, context)', () => {
     describe('noop props', () => {
       it('responds to all method calls without throwing', () => {
         const canvas = document.createElement('canvas')
-        const eventManager = createEventManager(eventsNoop, context)
-        expect(() => eventManager.config).not.toThrow()
+        const events = createEvents(eventsNoop, context)
+        expect(() => events.config).not.toThrow()
         expect(() => {
-          eventManager.disconnect()
-          eventManager.connect(canvas as null)
-          eventManager.disconnect()
+          events.disconnect()
+          events.connect(canvas as null)
+          events.disconnect()
         }).not.toThrow()
         expect(() => {
-          eventManager.enabled = false
-          eventManager.enabled = true
-          eventManager.enabled = false
+          events.enabled = false
+          events.enabled = true
+          events.enabled = false
         }).not.toThrow()
         expect(() => {
-          eventManager.handle(new MouseEvent('click') as null)
-          eventManager.handle(new MouseEvent('contextmenu') as null)
-          eventManager.handle(new MouseEvent('dblclick') as null)
+          events.handle(new MouseEvent('click') as null)
+          events.handle(new MouseEvent('contextmenu') as null)
+          events.handle(new MouseEvent('dblclick') as null)
         }).not.toThrow()
-        expect(() => eventManager.insert({})).not.toThrow()
-        expect(() => eventManager.isEventManager).not.toThrow()
-        expect(() => eventManager.patchProp({}, 'lookAt', undefined, [0, 1])).not.toThrow()
-        expect(() => eventManager.priority = 1).not.toThrow()
-        expect(() => eventManager.remove({})).not.toThrow()
-        expect(() => eventManager.target).not.toThrow()
-        expect(() => eventManager.test([{}])).not.toThrow()
+        expect(() => events.insert({})).not.toThrow()
+        expect(() => events.isEvents).not.toThrow()
+        expect(() => events.patchProp({}, 'lookAt', undefined, [0, 1])).not.toThrow()
+        expect(() => events.priority = 1).not.toThrow()
+        expect(() => events.remove({})).not.toThrow()
+        expect(() => events.target).not.toThrow()
+        expect(() => events.test([{}])).not.toThrow()
       })
     })
     describe('props', () => {
       it('allows methods to be changed on the fly', () => {
-        const eventManager = createEventManager(props, context)
-        const { handle } = eventManager
+        const events = createEvents(props, context)
+        const { handle } = events
 
         props.getIntersections = () => [1, 2, 3, 4]
         expect(handle(new MouseEvent('click'))).toStrictEqual([1, 2, 3, 4])
@@ -94,16 +94,16 @@ describe('createEventManager', () => {
         expect(handleIntersectionsSpy).toBeCalledTimes(1)
       })
     })
-    describe('eventManager.connect(eventTarget)', () => {
+    describe('events.connect(eventTarget)', () => {
       it('calls `props.connect` if not connected', () => {
-        const { connect } = createEventManager(props, context)
+        const { connect } = createEvents(props, context)
         connect(canvas)
         expect(propsSpies.connect).toBeCalled()
         expect(canvasSpies.addEventListener).toBeCalled()
       })
       it('`disconnect`s if connected to different target', () => {
         const { canvas: otherCanvas } = mockCanvasAndListeners()
-        const { connect } = createEventManager(props, context)
+        const { connect } = createEvents(props, context)
 
         connect(canvas)
         expect(propsSpies.connect).toBeCalled()
@@ -116,7 +116,7 @@ describe('createEventManager', () => {
       })
       it('calls `props.connect().disconnect` if already connected', () => {
         const otherCanvas = mockCanvasAndListeners().canvas
-        const { connect } = createEventManager(props, context)
+        const { connect } = createEvents(props, context)
 
         connect(canvas)
         expect(propsSpies.disconnect).not.toBeCalled()
@@ -124,7 +124,7 @@ describe('createEventManager', () => {
         expect(propsSpies.disconnect).toBeCalled()
       })
       it('does nothing if already connected to `target`', () => {
-        const { connect } = createEventManager(props, context)
+        const { connect } = createEvents(props, context)
         connect(canvas)
         expect(propsSpies.connect).toBeCalled()
         expect(propsSpies.disconnect).not.toBeCalled()
@@ -135,15 +135,15 @@ describe('createEventManager', () => {
         expect(propsSpies.disconnect).not.toBeCalled()
       })
     })
-    describe('eventManager.disconnect()', () => {
+    describe('events.disconnect()', () => {
       it('calls `props.connect().disconnect`', () => {
-        const { connect, disconnect } = createEventManager(props, context)
+        const { connect, disconnect } = createEvents(props, context)
         connect(canvas)
         disconnect()
         expect(propsSpies.disconnect).toBeCalled()
       })
       it('... but only if `connect`ed', () => {
-        const { connect, disconnect } = createEventManager(props, context)
+        const { connect, disconnect } = createEvents(props, context)
         disconnect()
         expect(propsSpies.disconnect).not.toBeCalled()
 
@@ -161,10 +161,10 @@ describe('createEventManager', () => {
         expect(propsSpies.disconnect).toBeCalledTimes(2)
       })
     })
-    describe('eventManager.test(pool, event)', () => {
+    describe('events.test(pool, event)', () => {
       it('returns `[]` if `!isSetup`', () => {
         props.isSetUp = () => false
-        const { test } = createEventManager(props, context)
+        const { test } = createEvents(props, context)
         const result = test([])
         expect(result).toStrictEqual([])
       })
@@ -173,17 +173,17 @@ describe('createEventManager', () => {
           isSetup: () => true,
           getIntersections: () => 'BBB',
         })
-        const { test } = createEventManager(props, context)
+        const { test } = createEvents(props, context)
         const result = test([])
         expect(result).toStrictEqual('BBB')
       })
     })
-    describe('eventManager.handle(event)', () => {
+    describe('events.handle(event)', () => {
       it('returns result of `props.getIntersections()`', () => {
         Object.assign(props, {
           getIntersections: () => 999,
         })
-        const { handle } = createEventManager(props, context)
+        const { handle } = createEvents(props, context)
         const result = handle(new MouseEvent('click'))
         expect(result).toStrictEqual(999)
       })
@@ -204,8 +204,8 @@ describe('createEventManager', () => {
             tearDown: fail,
           })
 
-          const eventManager = createEventManager(props, context)
-          const { handle } = eventManager
+          const events = createEvents(props, context)
+          const { handle } = events
           handle(new MouseEvent('click'))
           expect(succeeded).toBe(true)
           expect(failed).toBe(false)
@@ -228,8 +228,8 @@ describe('createEventManager', () => {
           { isSetUp: () => true },
           methods,
         )
-        const eventManager = createEventManager(myProps, context)
-        const { handle } = eventManager
+        const events = createEvents(myProps, context)
+        const { handle } = events
         handle(new MouseEvent('click'))
 
         for (let i = 0; i < EXPECTED_METHOD_CALL_ORDER.length; i++) {
@@ -252,16 +252,16 @@ describe('createEventManager', () => {
             handleIntersections: fail,
             tearDown: fail,
           })
-          const eventManager = createEventManager(props, context)
-          eventManager.enabled = false
-          eventManager.handle(new MouseEvent('click'))
+          const events = createEvents(props, context)
+          events.enabled = false
+          events.handle(new MouseEvent('click'))
           expect(failed).toStrictEqual(false)
         })
         it('returns `[]`', () => {
-          const eventManager = createEventManager(props, context)
+          const events = createEvents(props, context)
 
-          eventManager.enabled = false
-          const result1 = eventManager.handle(new MouseEvent('click'))
+          events.enabled = false
+          const result1 = events.handle(new MouseEvent('click'))
           expect(result1).toStrictEqual([])
         })
       })
@@ -273,7 +273,7 @@ describe('createEventManager', () => {
               getIntersections: () => { result.push(2); return [101, 102, 103, 104] },
               filter: (obj) => { obj = obj.filter(n => n % 2 === 1); return obj },
             })
-            const { handle } = createEventManager(props, context)
+            const { handle } = createEvents(props, context)
             const handleResult = handle(new MouseEvent('click'))
             expect(handleResult).toStrictEqual([101, 103])
           })
@@ -284,7 +284,7 @@ describe('createEventManager', () => {
             Object.assign(props, {
               getIntersections: () => { result.push(2); return [101, 102, 103, 104] },
             })
-            const { handle } = createEventManager(props, context)
+            const { handle } = createEvents(props, context)
 
             props.filter = undefined
             const handleResult = handle(new MouseEvent('click'))
@@ -293,12 +293,12 @@ describe('createEventManager', () => {
         })
       })
     })
-    describe('eventManager.remove', () => {
+    describe('events.remove', () => {
       it('forwards to `props.remove`', () => {
         const spy = vi.spyOn(props, 'remove')
-        const eventManager = createEventManager(props, context)
-        eventManager.enabled = false
-        eventManager.remove(2)
+        const events = createEvents(props, context)
+        events.enabled = false
+        events.remove(2)
         expect(spy).toBeCalledTimes(1)
       })
     })
@@ -308,7 +308,7 @@ describe('createEventManager', () => {
 function mockTresContext() {
   return {
     scene: shallowRef(new Scene()),
-    eventManager: null,
+    events: null,
     registerCamera: () => {},
     deregisterCamera: () => {},
   } as unknown as TresContext
@@ -354,7 +354,7 @@ function mockTresInstance() {
   }
 }
 
-function mockEventManagerPropsAndSpies(context?: TresContext) {
+function mockEventsPropsAndSpies(context?: TresContext) {
   const connectSpy = vi.fn()
   const disconnectSpy = vi.fn()
   let _isSetUp = false
@@ -363,7 +363,7 @@ function mockEventManagerPropsAndSpies(context?: TresContext) {
   context = context ?? mockTresContext()
   const initialConfig = { context, enabled: true, priority: 0, lastEvent: new MouseEvent('mousemove') }
   let lastEvent = new MouseEvent('mousemove')
-  const props: CreateEventManagerProps<{ context: typeof context }, typeof context, MouseEvent, number, number, HTMLCanvasElement> = {
+  const props: CreateEventsProps<{ context: typeof context }, typeof context, MouseEvent, number, number, HTMLCanvasElement> = {
     getInitialConfig: (_ctx: typeof context, _emit) => initialConfig,
     getInitialEvent: () => new MouseEvent('mousemove'),
     connect: (target: EventTarget, handle, _config: typeof initialConfig) => {

+ 10 - 10
src/utils/createEventManager/createEventManager.ts → src/utils/createEvents/createEvents.ts

@@ -1,9 +1,9 @@
 import type { EmitEventFn, EmitEventName } from '../../types'
 
-export type EventManager = CreateEventManagerReturn<any, any, any, any>
-export type EventManagerProps = CreateEventManagerProps<any, any, any, any, any, any>
+export type Events = CreateEventsReturn<any, any, any, any>
+export type EventsProps = CreateEventsProps<any, any, any, any, any, any>
 
-export interface CreateEventManagerReturn<TEvent, TIntersection, TObj, TTarget> {
+export interface CreateEventsReturn<TEvent, TIntersection, TObj, TTarget> {
   connect: (target: TTarget) => void
   disconnect: () => void
   handle: (event: TEvent) => TIntersection[]
@@ -16,10 +16,10 @@ export interface CreateEventManagerReturn<TEvent, TIntersection, TObj, TTarget>
   enabled: boolean
   target: TTarget | undefined
   priority: number
-  isEventManager: true
+  isEvents: true
 }
 
-export interface CreateEventManagerProps<
+export interface CreateEventsProps<
   TConfig,
   TCtx,
   TEvt,
@@ -52,11 +52,11 @@ export interface CreateEventManagerProps<
   getLastEvent: (config: TConfig) => TEvt
 }
 
-export function createEventManager<TConfig, TCtx, TEvent, TIntersection, TObj, TTarget>(
-  props: CreateEventManagerProps<TConfig, TCtx, TEvent, TIntersection, TObj, TTarget>,
+export function createEvents<TConfig, TCtx, TEvent, TIntersection, TObj, TTarget>(
+  props: CreateEventsProps<TConfig, TCtx, TEvent, TIntersection, TObj, TTarget>,
   context: TCtx,
   emit: EmitEventFn = (_event: EmitEventName, _args: any) => {},
-): CreateEventManagerReturn<TEvent, TIntersection, TObj, TTarget> {
+): CreateEventsReturn<TEvent, TIntersection, TObj, TTarget> {
   const config = props.getInitialConfig(context, emit)
   let enabled = true
   let priority = 1
@@ -64,7 +64,7 @@ export function createEventManager<TConfig, TCtx, TEvent, TIntersection, TObj, T
   let lastIntersections = []
   let propsDisconnect = () => {}
 
-  const handle: CreateEventManagerReturn<TEvent, TIntersection, TObj, TTarget>['handle'] = (event) => {
+  const handle: CreateEventsReturn<TEvent, TIntersection, TObj, TTarget>['handle'] = (event) => {
     if (!enabled) {
       return []
     }
@@ -141,6 +141,6 @@ export function createEventManager<TConfig, TCtx, TEvent, TIntersection, TObj, T
     set priority(n: number) { priority = n },
     get priority() { return priority },
     get target() { return target },
-    isEventManager: true,
+    isEvents: true,
   }
 }

+ 0 - 0
src/utils/createEventManager/deprecatedEvents.test.ts → src/utils/createEvents/deprecatedEvents.test.ts


+ 0 - 0
src/utils/createEventManager/deprecatedEvents.ts → src/utils/createEvents/deprecatedEvents.ts


+ 4 - 4
src/utils/createEventManager/eventsNoop.ts → src/utils/createEvents/eventsNoop.ts

@@ -1,11 +1,11 @@
 // NOTE:
 // This file consists of functions
-// used by `./createEventManager`.
+// used by `./createEvents`.
 //
-// In this case, it causes `eventManager` to
+// In this case, it causes `events` to
 // attach no events and do nothing.
 
-import type { CreateEventManagerProps } from './createEventManager'
+import type { CreateEventsProps } from './createEvents'
 
 function getInitialEvent() { return null }
 function getInitialConfig() { return {} }
@@ -22,7 +22,7 @@ function remove() { }
 function patchProp() { return false }
 function handleIntersections() { }
 
-export const eventsNoop: CreateEventManagerProps<any, any, null, null, any, null> = {
+export const eventsNoop: CreateEventsProps<any, any, null, null, any, null> = {
   connect,
   isSetUp,
   setUp,

+ 103 - 103
src/utils/createEventManager/eventsRaycast.test.ts → src/utils/createEvents/eventsRaycast.test.ts

@@ -8,7 +8,7 @@ import { shallowRef } from 'vue'
 import catalogue from '../../core/catalogue'
 import { nodeOps as getNodeOps } from '../../core/nodeOps'
 import { DOM_TO_THREE, type DomEventName, type ThreeEventName } from './const'
-import { createEventManager } from './createEventManager'
+import { createEvents } from './createEvents'
 import { eventsRaycast } from './eventsRaycast'
 
 const mockIntersections: THREE.Intersection<Object3D>[] = []
@@ -87,14 +87,14 @@ const DOM_NATIVE_CALL: { domEvent: DomEventName, nativeEvent: PointerEvent | Mou
 describe('eventsRaycast', () => {
   describe('eventsRaycast.getIntersectionsPool', () => {
     it('returns `[]` if no objects have events', () => {
-      const mock = mockTresUsingEventManagerProps()
+      const mock = mockTresUsingEventsProps()
       mock.add.DAG('g0 -> m0 m1; g2 -> m2')
-      const { config } = mock.context.eventManager
+      const { config } = mock.context.events
       expect(eventsRaycast.getIntersectionsPool(mockEvt('pointermove'), config)).toStrictEqual([])
     })
     it('returns an array including the object with events and all of its descendants', () => {
-      const mock = mockTresUsingEventManagerProps()
-      const { config } = mock.context.eventManager
+      const mock = mockTresUsingEventsProps()
+      const { config } = mock.context.events
       const { g0, m0, m1, mm0, g2, m2 } = mock.add.DAG('g0 -> m0 m1; m1 -> mm0; g2 -> m2')
 
       let pool = eventsRaycast.getIntersectionsPool(mockEvt('pointermove'), config)
@@ -140,8 +140,8 @@ describe('eventsRaycast', () => {
       expect(pool.includes(m2)).toBe(true)
     })
     it('updates when called after `nodeOps.patchProp` patches an event', () => {
-      const mock = mockTresUsingEventManagerProps()
-      const { config } = mock.context.eventManager
+      const mock = mockTresUsingEventsProps()
+      const { config } = mock.context.events
       const { g0, m0, m1, g2, m2 } = mock.add.DAG('g0 -> m0 m1; g2 -> m2')
 
       mock.nodeOps.patchProp(m0, 'onPointermove', undefined, () => {})
@@ -170,8 +170,8 @@ describe('eventsRaycast', () => {
       expect(pool.includes(m2)).toBe(true)
     })
     it('updates when called after `nodeOps.remove`', () => {
-      const mock = mockTresUsingEventManagerProps()
-      const { config } = mock.context.eventManager
+      const mock = mockTresUsingEventsProps()
+      const { config } = mock.context.events
       const { g0, m0, m1, g2, m2 } = mock.add.DAG('g0 -> m0 m1; g2 -> m2')
 
       mock.add.eventsTo(g0)
@@ -208,30 +208,30 @@ describe('eventsRaycast', () => {
     })
   })
 
-  describe('eventManager.test(pool: Iterable<Object3D>, event: MouseEvent | PointerEvent)', () => {
+  describe('events.test(pool: Iterable<Object3D>, event: MouseEvent | PointerEvent)', () => {
     it('returns `[]` if not `isSetUp()`', () => {
-      const mock = mockTresUsingEventManagerProps()
+      const mock = mockTresUsingEventsProps()
       const objects = mock.add.DAG('g0 -> m0')
       eventsRaycast.isSetUp = () => false
-      const { test } = mock.context.eventManager
+      const { test } = mock.context.events
       expect(test([objects.g0, objects.m0])).toStrictEqual([])
     })
     it('returns `raycaster.intersectObjects(objects)` if `isSetup()`', () => {
-      const mock = mockTresUsingEventManagerProps()
+      const mock = mockTresUsingEventsProps()
       const objects = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2')
       eventsRaycast.isSetUp = () => true
-      mock.context.eventManager.config.raycaster.intersectObjects = objects => objects.map(object => ({ object }))
+      mock.context.events.config.raycaster.intersectObjects = objects => objects.map(object => ({ object }))
 
-      const { test } = mock.context.eventManager
+      const { test } = mock.context.events
       const pool = [objects.g0, objects.m0]
-      expect(test(pool)).toStrictEqual(mock.context.eventManager.config.raycaster.intersectObjects(pool))
+      expect(test(pool)).toStrictEqual(mock.context.events.config.raycaster.intersectObjects(pool))
     })
   })
 
   describe('eventsRaycast.handleIntersections(incomingEvent, intersections, config)', () => {
     describe('map: native event => "@" event handlers', () => {
       it.each(DOM_NATIVE_CALL)('$nativeEvent.type => $domEvent', ({ domEvent, nativeEvent, call }) => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         let event: ThreeEvent<any> | null = null
         mock.nodeOps.patchProp(m, DOM_TO_THREE[domEvent], null, (e: ThreeEvent<any>) => { event = { ...e } })
@@ -241,7 +241,7 @@ describe('eventsRaycast', () => {
     })
     describe('handler `event`', () => {
       it.each(DOM_NATIVE_CALL)('is expected type in Tres\' $domEvent following DOM $nativeEvent.type', ({ domEvent, nativeEvent, call }) => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         let event: ThreeEvent<any> | null = null
         mock.nodeOps.patchProp(m, DOM_TO_THREE[domEvent], null, (e: ThreeEvent<any>) => { event = { ...e } })
@@ -304,7 +304,7 @@ describe('eventsRaycast', () => {
         expect(event).toHaveProperty('pointer')
         expect(event.pointer.isVector2).toBe(true)
 
-        expect(event.ray).toBe(mock.context.eventManager.config.raycaster.ray)
+        expect(event.ray).toBe(mock.context.events.config.raycaster.ray)
         expect(event.camera).toBe(mock.context.camera.value)
         expect(event).toHaveProperty('unprojectedPoint')
         expect(event.unprojectedPoint.isVector3).toBe(true)
@@ -315,7 +315,7 @@ describe('eventsRaycast', () => {
         describe('event.delta [sic]', () => {
           describe('if last event is a `click`, `contextmenu`, `dblclick`, or `pointerup`', () => {
             it('is the distance between prior `pointerdown` and the pointer position', () => {
-              const mock = mockTresUsingEventManagerProps()
+              const mock = mockTresUsingEventsProps()
               const { m } = mock.add.DAG('m')
               let lastEvent: ThreeEvent<any> | null = null
               mock.nodeOps.patchProp(m, 'onClick', undefined, (e: ThreeEvent<any>) => lastEvent = { ...e })
@@ -352,7 +352,7 @@ describe('eventsRaycast', () => {
           })
           describe('if last event is a not `click`, `contextmenu`, `dblclick`, or `pointerup`', () => {
             it('is `0`', () => {
-              const mock = mockTresUsingEventManagerProps()
+              const mock = mockTresUsingEventsProps()
               const { m } = mock.add.DAG('m')
               let lastEvent: ThreeEvent<any> | null = null
               for (const key of ['onPointerdown', 'onPointerup', 'onPointermove', 'wheel']) {
@@ -372,7 +372,7 @@ describe('eventsRaycast', () => {
         })
         describe('event.intersections', () => {
           it('is a THREE.Intersection[]', () => {
-            const mock = mockTresUsingEventManagerProps()
+            const mock = mockTresUsingEventsProps()
             const { m } = mock.add.DAG('m')
             mock.add.eventsTo(m)
             mock.apply('click').to([m])
@@ -387,7 +387,7 @@ describe('eventsRaycast', () => {
           })
 
           it('orders intersections by distance', () => {
-            const mock = mockTresUsingEventManagerProps()
+            const mock = mockTresUsingEventsProps()
             const { m0, m1, m2 } = mock.add.DAG('m0; m1; m2')
             mock.add.eventsTo(m0)
             mock.add.eventsTo(m1)
@@ -407,7 +407,7 @@ describe('eventsRaycast', () => {
           })
 
           it('orders intersections as [hit0, ...ancestors, hit1, ...ancestors, hit2 ...]', () => {
-            const mock = mockTresUsingEventManagerProps()
+            const mock = mockTresUsingEventsProps()
             const objects = mock.add.DAG('g -> m0; g -> m1; m0 -> m0a; m0 -> m0b; m1 -> m1a; m2 -> m2a; m2 -> m2b; m2b -> m2ba')
             const { g, m0a, m1a, m2a, m2ba } = objects
             for (const obj of Object.values(objects)) { mock.add.eventsTo(obj) }
@@ -437,7 +437,7 @@ describe('eventsRaycast', () => {
           })
 
           it('does not include objects without events on self', () => {
-            const mock = mockTresUsingEventManagerProps()
+            const mock = mockTresUsingEventsProps()
             const { m0, m1, m2, mNoEvents } = mock.add.DAG('m0 -> m1; m1 -> m2; mNoEvents')
             mock.add.eventsTo(m0)
             mock.add.eventsTo(m2)
@@ -450,7 +450,7 @@ describe('eventsRaycast', () => {
         describe('after handler is called', () => {
           describe('event.eventObject', () => {
             it('is `null`', () => {
-              const mock = mockTresUsingEventManagerProps()
+              const mock = mockTresUsingEventsProps()
               const { m } = mock.add.DAG('m')
               let event: any = {}
               mock.nodeOps.patchProp(m, 'onClick', undefined, (e) => { event = e })
@@ -460,7 +460,7 @@ describe('eventsRaycast', () => {
           })
           describe('event.currentTarget', () => {
             it('is `null`', () => {
-              const mock = mockTresUsingEventManagerProps()
+              const mock = mockTresUsingEventsProps()
               const { m } = mock.add.DAG('m')
               let event: any = {}
               mock.nodeOps.patchProp(m, 'onClick', undefined, (e) => { event = e })
@@ -483,7 +483,7 @@ describe('eventsRaycast', () => {
     )('$domEvent on the DOM element', ({ domEvent }) => {
       const threeEvent = DOM_TO_THREE[domEvent]
       it(`calls \`${threeEvent}\` methods on objects under pointer and ancestors`, () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g0, g1, m0, m2 } = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2')
         const g0Spies = mock.add.eventsTo(g0)
         const g1Spies = mock.add.eventsTo(g1)
@@ -501,7 +501,7 @@ describe('eventsRaycast', () => {
         expect(m2Spies[threeEvent]).toBeCalledTimes(1)
       })
       it(`calls \`${threeEvent}\` methods on hit objects and ancestors`, () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g0, m0, m1, m2 } = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2')
         const g0Spies = mock.add.eventsTo(g0)
         const m0Spies = mock.add.eventsTo(m0)
@@ -525,7 +525,7 @@ describe('eventsRaycast', () => {
         expect(m1Spies[threeEvent]).toBeCalledTimes(1)
       })
       it(`calls \`${threeEvent}\` once per object per event`, () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m0, m1, m2 } = mock.add.DAG('m0 -> m1 m2')
         const m0Spies = mock.add.eventsTo(m0)
         mock.add.eventsTo(m1)
@@ -542,7 +542,7 @@ describe('eventsRaycast', () => {
         expect(m0Spies[threeEvent]).toBeCalledTimes(1)
       })
       it('bubbles event to parents', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gParent, mChild, mGrandchild } = mock.add.DAG('gParent -> mChild; mChild -> mGrandchild')
         gParent.name = 'gParent'
         mock.add.eventsTo(gParent)
@@ -559,7 +559,7 @@ describe('eventsRaycast', () => {
 
     describe('pointermissed', () => {
       it('calls `pointermissed` on all elements in subtrees that were not hit', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         // NOTE: Names in DAG
         //
         // Prefixes denote type:
@@ -609,7 +609,7 @@ describe('eventsRaycast', () => {
     })
     describe('wheel (DOM)', () => {
       it('calls `wheel` handlers on "hit" objects', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const objects = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2')
         const { g0, m2 } = objects
         const g0Spies = mock.add.eventsTo(g0)
@@ -643,7 +643,7 @@ describe('eventsRaycast', () => {
         // on a child – set as the event `target`. Then the event is
         // sent "up" to ancestors.
         const threeEvent = DOM_TO_THREE[domEvent]
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gParent, mChild, mGrandchild } = mock.add.DAG('gParent -> mChild; mChild -> mGrandchild')
 
         mock.add.eventsTo(gParent)
@@ -668,7 +668,7 @@ describe('eventsRaycast', () => {
         // Events that are not "bubbled" are always "self" events:
         // `event.currentTarget === event.target`
         const threeEvent = DOM_TO_THREE[domEvent]
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gParent, mChild, mGrandchild } = mock.add.DAG('gParent -> mChild; mChild -> mGrandchild')
         mock.add.eventsTo(gParent)
         mock.add.eventsTo(mChild)
@@ -699,7 +699,7 @@ describe('eventsRaycast', () => {
         { domEventName: 'wheel', threeEventName: 'onWheel' },
       ] as { domEventName: DomEventName, threeEventName: ThreeEventName }[],
       )('stops $domEventName', ({ domEventName, threeEventName }) => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gListener, gStopper, mSource } = mock.add.DAG('gListener -> gStopper; gStopper -> mSource')
         const gListenerSpies = mock.add.eventsTo(gListener)
 
@@ -713,7 +713,7 @@ describe('eventsRaycast', () => {
         { domEventName: 'pointerover', threeEventName: 'onPointerover' },
         { domEventName: 'pointerout', threeEventName: 'onPointerout' },
       ])('stops $domEventName', ({ threeEventName }) => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gListener, gStopper, mSource } = mock.add.DAG('gListener -> gStopper; gStopper -> mSource')
         const gListenerSpies = mock.add.eventsTo(gListener)
 
@@ -729,7 +729,7 @@ describe('eventsRaycast', () => {
         { domEventName: 'pointerenter', threeEventName: 'onPointerenter' },
         { domEventName: 'pointerleave', threeEventName: 'onPointerleave' },
       ])('does not stop $domEventName (because it is not propagated)', ({ threeEventName }) => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gListener, gStopper, mSource } = mock.add.DAG('gListener -> gStopper; gStopper -> mSource')
         const gListenerSpies = mock.add.eventsTo(gListener)
 
@@ -742,7 +742,7 @@ describe('eventsRaycast', () => {
         expect(gListenerSpies[threeEventName]).toBeCalled()
       })
       it('does not stop propagation for \'pointermissed\' (because it is not propagated)', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gListener, gStopper } = mock.add.DAG('gListener -> gStopper; gStopper -> mSource')
         const gListenerSpies = mock.add.eventsTo(gListener)
 
@@ -758,7 +758,7 @@ describe('eventsRaycast', () => {
   describe('tres event handlers: handler-specific problems', () => {
     describe('pointerenter', () => {
       it('is called on objects newly under pointer', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
 
         const { g0, g1, m1, m2, mNotIncluded } = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2; m2 -> mNotIncluded')
         const g0Spies = mock.add.eventsTo(g0)
@@ -788,7 +788,7 @@ describe('eventsRaycast', () => {
       })
 
       it('is not called on objects still directly under pointer', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { mUnderPointer, mAlternates } = mock.add.DAG('mUnderPointer; mAlternates')
         const mUnderPointerSpies = mock.add.eventsTo(mUnderPointer)
         mock.add.eventsTo(mAlternates)
@@ -811,7 +811,7 @@ describe('eventsRaycast', () => {
       })
 
       it('is not called on objects still under pointer via a different child', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gStaysUnderPointer, m0, m1 } = mock.add.DAG('gStaysUnderPointer -> m0 m1')
         const gStaysUnderPointerSpies = mock.add.eventsTo(gStaysUnderPointer)
 
@@ -823,7 +823,7 @@ describe('eventsRaycast', () => {
       })
 
       describe('pointerenter event', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g0, m0 } = mock.add.DAG('g0 -> m0')
         let m0Event: ThreeEvent<PointerEvent> | null = null
         let g0Event: ThreeEvent<PointerEvent> | null = null
@@ -844,7 +844,7 @@ describe('eventsRaycast', () => {
 
     describe('pointerover', () => {
       it('is called on objects newly under pointer', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
 
         const { g0, g1, m1, m2, mNotIncluded } = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2; m2 -> mNotIncluded')
         const g0Spies = mock.add.eventsTo(g0)
@@ -874,7 +874,7 @@ describe('eventsRaycast', () => {
       })
 
       it('is called when pointer goes "deeper" into an object\'s descendants', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
 
         const { g0, g1, m0, m1, m2 } = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2')
         const g0Spies = mock.add.eventsTo(g0)
@@ -900,7 +900,7 @@ describe('eventsRaycast', () => {
       })
 
       it('is called on objects still under pointer via a different child', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { gStaysUnderPointer, m0, m1 } = mock.add.DAG('gStaysUnderPointer -> m0 m1')
         const gStaysUnderPointerSpies = mock.add.eventsTo(gStaysUnderPointer)
 
@@ -929,7 +929,7 @@ describe('eventsRaycast', () => {
       })
 
       describe(`with group -> mesh`, () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g0, m0 } = mock.add.DAG('g0 -> m0')
         const g0Spy = mock.add.eventsTo(g0).onPointerover
         const m0Spy = mock.add.eventsTo(m0).onPointerover
@@ -993,7 +993,7 @@ describe('eventsRaycast', () => {
 
       describe(`with mesh -> mesh`, () => {
         it('is called once for the same "over"', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m0, m1 } = mock.add.DAG('m0 -> m1')
           const m0Spy = vi.fn()
           const m1Spy = vi.fn()
@@ -1014,7 +1014,7 @@ describe('eventsRaycast', () => {
       })
 
       it('is bubbled every time there is a `pointerover` on a child, even if ancestor is still hit', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m0, m1 } = mock.add.DAG('m0 -> m1')
         const m0Spy = vi.fn()
         const m1Spy = vi.fn()
@@ -1054,7 +1054,7 @@ describe('eventsRaycast', () => {
       })
       describe('pointerover event', () => {
         it('has an `object` field containing the intersected object', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { g0, m0 } = mock.add.DAG('g0 -> m0')
           let m0Event: ThreeEvent<PointerEvent> | null = null
           let g0Event: ThreeEvent<PointerEvent> | null = null
@@ -1070,7 +1070,7 @@ describe('eventsRaycast', () => {
     })
 
     describe(`onPointerout(event: TresEvent)`, () => {
-      const mock = mockTresUsingEventManagerProps()
+      const mock = mockTresUsingEventsProps()
       const { g0, m0 } = mock.add.DAG('g0 -> m0')
       let g0Spy = 0
       let m0Spy = 0
@@ -1115,7 +1115,7 @@ describe('eventsRaycast', () => {
       })
 
       it('is called on ancestors when a descendant is left', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
 
         const { g0, g1, m0, m1, m2 } = mock.add.DAG('g0 -> m0 m1; m0 -> g1; g1 -> m2')
         const g0Spies = mock.add.eventsTo(g0)
@@ -1151,7 +1151,7 @@ describe('eventsRaycast', () => {
     })
 
     describe(`onPointerleave(event: TresEvent)`, () => {
-      const mock = mockTresUsingEventManagerProps()
+      const mock = mockTresUsingEventsProps()
       const { g0, m0 } = mock.add.DAG('g0 -> m0')
       let g0Spy = 0
       let m0Spy = 0
@@ -1222,7 +1222,7 @@ describe('eventsRaycast', () => {
         // - 5 `pointer{out,over}` calls
         // - 1 `pointer{enter,leave}` calls
 
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { outer, middle, inner } = mock.add.DAG('outer -> middle; middle -> inner')
         const outerSpies = mock.add.eventsTo(outer)
         mock.nodeOps.patchProp(outer, 'blocking', undefined, true)
@@ -1315,7 +1315,7 @@ describe('eventsRaycast', () => {
         // - 3 `pointer{out,over}` calls
         // - 1 `pointer{enter,leave}` calls
 
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { outer, middle, inner } = mock.add.DAG('outer -> middle; middle -> inner')
         const outerSpies = mock.add.eventsTo(outer)
 
@@ -1375,7 +1375,7 @@ describe('eventsRaycast', () => {
   describe('special cases', () => {
     describe(':blocking="true"', () => {
       it('stops objects "behind" :blocking objects from receiving `click`, `wheel`, `pointermove`, when `true`', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { mFront, mBehind } = mock.add.DAG('mFront; mBehind')
         const mFrontSpies = mock.add.eventsTo(mFront)
         const mBehindSpies = mock.add.eventsTo(mBehind)
@@ -1395,7 +1395,7 @@ describe('eventsRaycast', () => {
         expect(mFrontSpies.onClick).toBeCalledTimes(1)
       })
       it('bubbles events to parents (as normal) when `true`', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g, mFront, mBehind } = mock.add.DAG('g -> mFront; mBehind')
         const gSpies = mock.add.eventsTo(g)
 
@@ -1411,7 +1411,7 @@ describe('eventsRaycast', () => {
         expect(gSpies.onClick).toBeCalledTimes(1)
       })
       it('restores normal "non-blocking" behavior when `false`', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { mFront, mBehind } = mock.add.DAG('mFront; mBehind')
         const mFrontSpies = mock.add.eventsTo(mFront)
         const mBehindSpies = mock.add.eventsTo(mBehind)
@@ -1446,7 +1446,7 @@ describe('eventsRaycast', () => {
         expect(mFrontSpies.onClick).toBeCalledTimes(1)
       })
       it('calls @pointerleave on objects no longer receiving the pointer event after a new pointer event, even if they are still intersected', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { mFront, mBehind } = mock.add.DAG('mFront; mBehind')
         const mFrontSpies = mock.add.eventsTo(mFront)
         const mBehindSpies = mock.add.eventsTo(mBehind)
@@ -1465,10 +1465,10 @@ describe('eventsRaycast', () => {
     describe('when removing an object', () => {
       describe('pointerout', () => {
         it('is called if the object removed was under the pointer', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m0 } = mock.add.DAG('m0')
           const m0Spies = mock.add.eventsTo(m0)
-          const { remove } = mock.context.eventManager
+          const { remove } = mock.context.events
 
           mock.apply('pointerout').to([m0])
 
@@ -1478,8 +1478,8 @@ describe('eventsRaycast', () => {
         })
 
         it('bubbles to ancestors', () => {
-          const mock = mockTresUsingEventManagerProps()
-          const { remove } = mock.context.eventManager
+          const mock = mockTresUsingEventsProps()
+          const { remove } = mock.context.events
 
           const { g0, m0, m1, m2 } = mock.add.DAG('g0 -> m0 m1; m1 -> m2')
 
@@ -1513,10 +1513,10 @@ describe('eventsRaycast', () => {
       })
       describe('pointerleave', () => {
         it('is called if the object removed was under the pointer', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m0 } = mock.add.DAG('m0')
           const m0Spies = mock.add.eventsTo(m0)
-          const { remove } = mock.context.eventManager
+          const { remove } = mock.context.events
 
           mock.apply('pointermove').to([m0])
 
@@ -1526,8 +1526,8 @@ describe('eventsRaycast', () => {
         })
 
         it('is called on ancestors, if they are no longer under the pointer', () => {
-          const mock = mockTresUsingEventManagerProps()
-          const { remove } = mock.context.eventManager
+          const mock = mockTresUsingEventsProps()
+          const { remove } = mock.context.events
 
           const { g0, m0, m1, m2 } = mock.add.DAG('g0 -> m0 m1; m1 -> m2')
 
@@ -1563,23 +1563,23 @@ describe('eventsRaycast', () => {
     describe('when `pointerleave`s domElement', () => {
       describe('intersections', () => {
         it('are dropped', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { g, m2 } = mock.add.DAG('g -> m0 m1; m1 -> m2')
           mock.add.eventsTo(g)
-          mock.context.eventManager.connect(mock.canvas)
+          mock.context.events.connect(mock.canvas)
 
           mock.apply('pointermove').to([m2])
-          expect(mock.context.eventManager.config.priorIntersections.length).toBe(1)
+          expect(mock.context.events.config.priorIntersections.length).toBe(1)
           mock.canvas._call('pointerleave', mockEvt('pointerleave'))
-          expect(mock.context.eventManager.config.priorIntersections.length).toBe(0)
+          expect(mock.context.events.config.priorIntersections.length).toBe(0)
         })
       })
       describe('pointerleave', () => {
         it('is called where appropiate', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { g, m2 } = mock.add.DAG('g -> m0 m1; m1 -> m2')
           mock.add.eventsTo(g)
-          mock.context.eventManager.connect(mock.canvas)
+          mock.context.events.connect(mock.canvas)
 
           mock.apply('pointermove').to([m2])
           expect(getLast('pointerleave').on(g)).toBeNull()
@@ -1591,10 +1591,10 @@ describe('eventsRaycast', () => {
       })
       describe('pointerout', () => {
         it('is called where appropiate', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { g, m2 } = mock.add.DAG('g -> m0 m1; m1 -> m2')
           mock.add.eventsTo(g)
-          mock.context.eventManager.connect(mock.canvas)
+          mock.context.events.connect(mock.canvas)
 
           mock.apply('pointermove').to([m2])
           expect(getLast('pointerleave').on(g)).toBeNull()
@@ -1608,8 +1608,8 @@ describe('eventsRaycast', () => {
   describe('{set,release,lost}pointercapture', () => {
     const POINTER_ID = mockEvt('pointerdown').pointerId
     describe('object.setPointerCapture(pointerId) in event handler', () => {
-      it('calls `setPointerCapture(pointerId)` on `eventManager`\'s DOM Element', () => {
-        const mock = mockTresUsingEventManagerProps()
+      it('calls `setPointerCapture(pointerId)` on `events`\'s DOM Element', () => {
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
           e.eventObject.setPointerCapture(e.pointerId)
@@ -1622,7 +1622,7 @@ describe('eventsRaycast', () => {
     })
     describe('when a pointer is captured', () => {
       it('adds the captured object intersection to the end of `event.intersections` if it was not otherwise hit', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m, n, o, p } = mock.add.DAG('m; n; o; p')
         mock.add.eventsTo(m)
         mock.add.eventsTo(n)
@@ -1653,7 +1653,7 @@ describe('eventsRaycast', () => {
         expect(getLast('pointermove').on(o).intersections.map(intr => intr.eventObject.name).join('')).toBe('omp')
       })
       it('calls object\'s event handlers, if they exist, even if the object is not hit', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g, m } = mock.add.DAG('g -> m')
         mock.add.eventsTo(g)
         mock.add.eventsTo(m)
@@ -1670,7 +1670,7 @@ describe('eventsRaycast', () => {
         expect(getLast('pointermove').on(m)).not.toBeNull()
       })
       it('calls object\'s event handlers, if they exist, even if `stopPropagation` is called', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m, n, o } = mock.add.DAG('m; n; o')
         mock.add.eventsTo(m)
         mock.add.eventsTo(n)
@@ -1693,7 +1693,7 @@ describe('eventsRaycast', () => {
         expect(getLast('pointermove').on(n)).not.toBeNull()
       })
       it('calls object\'s event handlers, if they exist, even if objects are `:blocking`', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g, m, n, o } = mock.add.DAG('g -> m; g -> n; g -> o')
         mock.add.eventsTo(m)
         mock.add.eventsTo(n)
@@ -1714,7 +1714,7 @@ describe('eventsRaycast', () => {
         expect(getLast('pointermove').on(n)).not.toBeNull()
       })
       it('bubbles events from captured objects', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m, n, o } = mock.add.DAG('m -> n; n -> o')
         mock.add.eventsTo(m)
         mock.add.eventsTo(n)
@@ -1738,7 +1738,7 @@ describe('eventsRaycast', () => {
         expect(getLast('contextmenu').on(n)).not.toBeNull()
       })
       it('calls pointer{over,out,enter,leave}', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g, m, n, o } = mock.add.DAG('g -> m; n; o')
         mock.add.eventsTo(g)
         mock.add.eventsTo(m)
@@ -1752,8 +1752,8 @@ describe('eventsRaycast', () => {
       })
     })
     describe('object.releasePointerCapture(pointerId)', () => {
-      it('calls `releasePointerCapture(pointerId)` on `eventManager`\'s DOM Element if there are no remaining captures', () => {
-        const mock = mockTresUsingEventManagerProps()
+      it('calls `releasePointerCapture(pointerId)` on `events`\'s DOM Element if there are no remaining captures', () => {
+        const mock = mockTresUsingEventsProps()
         const { m, n } = mock.add.DAG('m; n')
         mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
           e.eventObject.setPointerCapture(e.pointerId)
@@ -1785,8 +1785,8 @@ describe('eventsRaycast', () => {
         mock.apply('pointermove').to([m, n])
         expect(mock.canvas.hasPointerCapture(POINTER_ID)).toBe(false)
       })
-      it('does not call `releasePointerCapture(pointerId)` on `eventManager`\'s DOM Element if there are remaining captures', () => {
-        const mock = mockTresUsingEventManagerProps()
+      it('does not call `releasePointerCapture(pointerId)` on `events`\'s DOM Element if there are remaining captures', () => {
+        const mock = mockTresUsingEventsProps()
         interface PointerCapture {
           setPointerCapture: (id: number) => void
           releasePointerCapture: (id: number) => void
@@ -1875,7 +1875,7 @@ describe('eventsRaycast', () => {
       })
       describe('pointerup', () => {
         it('releases a single pointer capture', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m } = mock.add.DAG('m')
           mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
             e.eventObject.setPointerCapture(e.pointerId)
@@ -1887,7 +1887,7 @@ describe('eventsRaycast', () => {
           expect(mock.canvas.hasPointerCapture(POINTER_ID)).toBe(false)
         })
         it('releases all pointer captures for a pointerId', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m, n, o, p } = mock.add.DAG('m; n; o; p')
           function down(e) { e.eventObject.setPointerCapture(e.pointerId) }
           mock.nodeOps.patchProp(m, 'onPointerdown', undefined, down)
@@ -1917,7 +1917,7 @@ describe('eventsRaycast', () => {
       })
       describe('pointercancel', () => {
         it('releases a single pointer capture', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m } = mock.add.DAG('m')
           mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
             e.eventObject.setPointerCapture(e.pointerId)
@@ -1929,7 +1929,7 @@ describe('eventsRaycast', () => {
           expect(mock.canvas.hasPointerCapture(POINTER_ID)).toBe(false)
         })
         it('releases all pointer captures', () => {
-          const mock = mockTresUsingEventManagerProps()
+          const mock = mockTresUsingEventsProps()
           const { m, n, o, p } = mock.add.DAG('m; n; o; p')
           function down(e) { e.eventObject.setPointerCapture(e.pointerId) }
           mock.nodeOps.patchProp(m, 'onPointerdown', undefined, down)
@@ -1960,13 +1960,13 @@ describe('eventsRaycast', () => {
     })
     describe('lostpointercapture', () => {
       it('exists', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         mock.add.eventsTo(m)
         expect('onLostpointercapture' in m).toBe(true)
       })
       it('is called when releasing the pointer via `e.target.releasePointerCapture`', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         mock.add.eventsTo(m)
         mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
@@ -1977,7 +1977,7 @@ describe('eventsRaycast', () => {
         expect(getLast('lostpointercapture').on(m)).toBeDefined()
       })
       it('is called when releasing the pointer via `pointercancel`', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         mock.add.eventsTo(m)
         mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
@@ -1990,7 +1990,7 @@ describe('eventsRaycast', () => {
         expect(getLast('lostpointercapture').on(m)).not.toBeNull()
       })
       it('is called with an object having the expected fields', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { m } = mock.add.DAG('m')
         mock.add.eventsTo(m)
         mock.nodeOps.patchProp(m, 'onPointerdown', undefined, (e) => {
@@ -2011,7 +2011,7 @@ describe('eventsRaycast', () => {
         expect(event.target).toBe(m)
       })
       it('is not called on objects that don\'t have the pointer capture', () => {
-        const mock = mockTresUsingEventManagerProps()
+        const mock = mockTresUsingEventsProps()
         const { g, m, n } = mock.add.DAG('g -> m; n')
         mock.add.eventsTo(g)
         mock.add.eventsTo(m)
@@ -2031,11 +2031,11 @@ describe('eventsRaycast', () => {
   })
 })
 
-function mockTresUsingEventManagerProps(props = eventsRaycast) {
+function mockTresUsingEventsProps(props = eventsRaycast) {
   function mockTresContext() {
     const context = {
       scene: shallowRef(new Scene()),
-      eventManager: null,
+      events: null,
       registerCamera: () => {},
       deregisterCamera: () => {},
       extend: catalogue.extend,
@@ -2043,8 +2043,8 @@ function mockTresUsingEventManagerProps(props = eventsRaycast) {
       camera: { value: new THREE.PerspectiveCamera() },
     } as unknown as TresContext
 
-    context.eventManager = createEventManager(props, context, () => {})
-    context.eventManager.config.raycaster.intersectObjects = (pool: any[]) => {
+    context.events = createEvents(props, context, () => {})
+    context.events.config.raycaster.intersectObjects = (pool: any[]) => {
       // NOTE:
       // We are not testing raycast "hits", but we do want to test `pool`,
       // which comes from `raycastProps.ts`.
@@ -2194,7 +2194,7 @@ function mockTresUsingEventManagerProps(props = eventsRaycast) {
         // Interpret this as an empty intersetion.
         for (const event of events) {
           set.mockIntersection([])
-          context.eventManager.handle(event)
+          context.events.handle(event)
         }
       }
       else if (!Array.isArray(intersectedGroupOrGroups[0])) {
@@ -2204,7 +2204,7 @@ function mockTresUsingEventManagerProps(props = eventsRaycast) {
         const targetGroup = intersectedGroupOrGroups as Object3D[]
         for (const event of events) {
           set.mockIntersection(targetGroup)
-          context.eventManager.handle(event)
+          context.events.handle(event)
         }
       }
       else {
@@ -2220,7 +2220,7 @@ function mockTresUsingEventManagerProps(props = eventsRaycast) {
         for (const intersectedGroup of intersectedGroups) {
           for (const event of events) {
             set.mockIntersection(intersectedGroup)
-            context.eventManager.handle(event)
+            context.events.handle(event)
           }
         }
       }

+ 7 - 7
src/utils/createEventManager/eventsRaycast.ts → src/utils/createEvents/eventsRaycast.ts

@@ -1,6 +1,6 @@
 import type { Object3D, Intersection as ThreeIntersection } from 'three'
 import type { EventHandlers, IntersectionEvent, PointerCaptureTarget, Properties, ThreeEvent, TresCamera, TresInstance, TresObject } from '../../types'
-import type { CreateEventManagerProps } from './createEventManager'
+import type { CreateEventsProps } from './createEvents'
 import { Raycaster, Vector2, Vector3 } from 'three'
 import { prepareTresInstance } from '..'
 import { isProd, type TresContext, useLogger } from '../../composables'
@@ -10,9 +10,9 @@ import { deprecatedEventsToNewEvents } from './deprecatedEvents'
 
 // NOTE:
 // This file consists of type definitions and functions
-// used by `./createEventManager`.
+// used by `./createEvents`.
 //
-// In particular, see `handle` in `./createEventManager` to
+// In particular, see `handle` in `./createEvents` to
 // see the call order of the functions here.
 
 // NOTE: Aliasing these and grouping here to make
@@ -30,7 +30,7 @@ function getInitialEvent() {
   return new MouseEvent('mousemove')
 }
 
-type RaycastProps = CreateEventManagerProps<
+type RaycastProps = CreateEventsProps<
   Config,
   TresContext,
   RaycastEvent,
@@ -83,9 +83,9 @@ function getLastEvent(config: Config) {
   return config.lastMoveEvent
 }
 
-function connect(target: RaycastEventTarget, eventManagerHandler: (ev: RaycastEvent) => void, config: Config) {
+function connect(target: RaycastEventTarget, eventsHandler: (ev: RaycastEvent) => void, config: Config) {
   for (const domEventName of POINTER_EVENT_NAMES) {
-    target.addEventListener(domEventName, eventManagerHandler, { passive: DOM_TO_PASSIVE[domEventName] })
+    target.addEventListener(domEventName, eventsHandler, { passive: DOM_TO_PASSIVE[domEventName] })
   }
 
   const leave = (pointerEvent: PointerEvent) => handleIntersections(pointerEvent, [], config)
@@ -94,7 +94,7 @@ function connect(target: RaycastEventTarget, eventManagerHandler: (ev: RaycastEv
   return {
     disconnect: () => {
       for (const domEventName of POINTER_EVENT_NAMES) {
-        target.removeEventListener(domEventName, eventManagerHandler)
+        target.removeEventListener(domEventName, eventsHandler)
       }
       target.removeEventListener('pointerleave', leave)
     },

+ 6 - 0
src/utils/createEvents/index.ts

@@ -0,0 +1,6 @@
+import { createEvents, type Events, type EventsProps } from './createEvents'
+import { eventsNoop as disableEvents } from './eventsNoop'
+import { eventsRaycast } from './eventsRaycast'
+import { withEventsProps } from './withEventsProps'
+
+export { createEvents, disableEvents, Events as Events, EventsProps as EventsProps, eventsRaycast as raycastProps, withEventsProps as useEventsOptions }

+ 3 - 0
src/utils/createEvents/useEvents.ts

@@ -0,0 +1,3 @@
+import { useTresContext } from 'src/composables'
+
+export function useEvents() { return useTresContext().events }

+ 371 - 0
src/utils/createEvents/withEventsProps.test.ts

@@ -0,0 +1,371 @@
+import type { TresContext } from 'src/composables/useTresContextProvider'
+import { describe, expect, it, vi } from 'vitest'
+import { computed, ref, shallowRef } from 'vue'
+import { raycastProps } from '.'
+import { createEvents } from './createEvents'
+import { eventsRaycast } from './eventsRaycast'
+import { withEventsProps } from './withEventsProps'
+
+let context = mockTresContext()
+let events = createEvents(eventsRaycast, context)
+
+describe('useEventsOptions', () => {
+  beforeEach(() => {
+    context = mockTresContext()
+    events = createEvents(eventsRaycast, context)
+    vi.clearAllMocks()
+  })
+  describe('!props', () => {
+    it('sets `events.enabled` to `true`', () => {
+      withEventsProps(context, events)
+      expect(events.enabled).toBe(true)
+    })
+    it('sets `events.target` to `context.renderer.value.domElement`', () => {
+      withEventsProps(context, events)
+      expect(events.target).toBe(context.renderer.value.domElement)
+    })
+  })
+
+  describe('props.events', () => {
+    describe('`events` is not passed', () => {
+      describe('!("events" in props)', () => {
+        it('uses `eventsRaycast` as default `events`', () => {
+          const { events } = withEventsProps(context)
+          const events0 = createEvents(raycastProps, context)
+          // NOTE: `props.events` is encapsulated inside of `events`
+          // so we an't access it directly.
+          // We can check the `events.config`s for equality,
+          // since they're produced by a props method.
+          expect(events.config).toStrictEqual(events0.config)
+        })
+      })
+      describe('props.events === undefined', () => {
+        it('uses `eventsRaycast` as default `events`', () => {
+          const { events } = withEventsProps(context)
+          const events0 = createEvents(raycastProps, context)
+          // NOTE: `props.events` is encapsulated inside of `events`
+          // so we an't access it directly.
+          // We can check the `events.config`s for equality,
+          // since they're produced by a props method.
+          expect(events.config).toStrictEqual(events0.config)
+        })
+      })
+      describe('props.events === true', () => {
+        it('uses `eventsRaycast` as default `events`', () => {
+          // NOTE: This isn't permitted by the type system, but it's
+          // an easy case to cover and perhaps a common mistake.
+          // @ts-expect-error `true` is not an accepted value for `props.events`.
+          context.props.events = true
+          const { events } = withEventsProps(context)
+          const events0 = createEvents(raycastProps, context)
+          // NOTE: `props.events` is encapsulated inside of `events`
+          // so we an't access it directly.
+          // We can check the `events.config`s for equality,
+          // since they're produced by a props method.
+          expect(events.config).toStrictEqual(events0.config)
+        })
+      })
+      describe('props.events === 1', () => {
+        it('uses `eventsRaycast` as default `events`', () => {
+          // NOTE: This isn't permitted by the type system, but it's
+          // an easy case to cover and perhaps a common mistake.
+          // @ts-expect-error `1` is not an accepted value for `props.events`.
+          context.props.events = 1
+          const { events } = withEventsProps(context)
+          const events0 = createEvents(raycastProps, context)
+          // NOTE: `props.events` is encapsulated inside of `events`
+          // so we an't access it directly.
+          // We can check the `events.config`s for equality,
+          // since they're produced by a props method.
+          expect(events.config).toStrictEqual(events0.config)
+        })
+      })
+      describe('props.events === null', () => {
+        it('does not connect to a target', () => {
+          context.props.events = null
+          const { events } = withEventsProps(context)
+          const spy = vi.fn()
+          events.connect({ addEventListener: spy, removeEventListener: spy })
+          expect(spy).not.toBeCalled()
+        })
+      })
+      describe('props.events === false', () => {
+        it('does not connect to a target', () => {
+          // NOTE: This isn't permitted by the type system, but it's
+          // an easy case to cover and might make sense for some users.
+          // @ts-expect-error `false` is not an accepted value for `props.events`.
+          context.props.events = false
+          const { events } = withEventsProps(context)
+          const spy = vi.fn()
+          events.connect({ addEventListener: spy, removeEventListener: spy })
+          expect(spy).not.toBeCalled()
+        })
+      })
+      describe('props.events === 0', () => {
+        it('does not connect to a target', () => {
+          // NOTE: This isn't permitted by the type system.
+          // @ts-expect-error `0` is not an accepted value for `props.events`.
+          context.props.events = 0
+          const { events } = withEventsProps(context)
+          const spy = vi.fn()
+          events.connect({ addEventListener: spy, removeEventListener: spy })
+          expect(spy).not.toBeCalled()
+        })
+      })
+      describe('props.events === { ... }', () => {
+        it('fills in missing `events` methods using `eventsRaycast` methods', () => {
+          context.props.events = {}
+          withEventsProps(context)
+          expect(context.props.events).toStrictEqual(raycastProps)
+
+          const filter = () => []
+          context.props.events = { filter }
+          withEventsProps(context)
+          expect(context.props.events.filter).not.toBe(raycastProps.filter)
+          expect(context.props.events.filter).toBe(filter)
+        })
+      })
+    })
+  })
+
+  describe('props.eventsEnabled', () => {
+    describe('props.eventsEnabled === undefined', () => {
+      it('sets `enabled` to Events to `true`', () => {
+        context.props = { }
+        withEventsProps(context, events)
+        expect(events.enabled).toBe(true)
+      })
+    })
+    describe('props.eventsEnabled === null', () => {
+      it('sets `enabled` to Events to `true`', () => {
+        context.props = { eventsEnabled: null }
+        withEventsProps(context, events)
+        expect(events.enabled).toBe(true)
+      })
+    })
+    describe('props.eventsEnabled === boolean', () => {
+      it('sets `enabled` on Events when used in `useEventsOptions`', () => {
+        context.props = { eventsEnabled: true }
+        withEventsProps(context, events)
+        expect(events.enabled).toBe(true)
+
+        context.props.eventsEnabled = false
+        withEventsProps(context, events)
+        expect(events.enabled).toBe(false)
+      })
+    })
+    describe('isRef(props.eventsEnabled)', () => {
+      it('sets `enabled` on Events when ref.value is updated', () => {
+        const r = ref(true)
+        context.props = { eventsEnabled: r }
+        withEventsProps(context, events)
+        expect(events.enabled).toBe(true)
+
+        r.value = false
+        expect(events.enabled).toBe(false)
+
+        r.value = true
+        expect(events.enabled).toBe(true)
+
+        r.value = false
+        expect(events.enabled).toBe(false)
+
+        r.value = undefined
+        expect(events.enabled).toBe(true)
+      })
+    })
+    describe('isRefOrGetter(props.eventsEnabled)', () => {
+      it('sets `enabled` on Events when ref.value is updated', () => {
+        const myNumber = ref(0)
+
+        const eventsEnabled = computed({
+          get() { return !!(myNumber.value % 2) },
+          set(n: number) { myNumber.value = n },
+        })
+        context.props = { eventsEnabled }
+        withEventsProps(context, events)
+        expect(events.enabled).toBe(false)
+
+        myNumber.value = 1
+        expect(events.enabled).toBe(true)
+
+        myNumber.value = 2
+        expect(events.enabled).toBe(false)
+
+        myNumber.value = 3
+        expect(events.enabled).toBe(true)
+
+        myNumber.value = 4
+        expect(events.enabled).toBe(false)
+
+        myNumber.value = 5
+        expect(events.enabled).toBe(true)
+      })
+    })
+  })
+
+  describe('props.eventsTarget', () => {
+    describe('props.eventsTarget === undefined', () => {
+      it('calls events.connect(context.renderer.value.domElement)', () => {
+        context.props = { }
+        const connectSpy = vi.spyOn(events, 'connect')
+
+        withEventsProps(context, events)
+        expect(connectSpy).toBeCalledTimes(1)
+        expect(connectSpy).toBeCalledWith(context.renderer.value.domElement)
+      })
+    })
+
+    describe('props.eventsTarget === computed({ get..., set... })', () => {
+      it('calls events.connect(props.eventsTarget.get()) when `set`', () => {
+        const connectSpy = vi.spyOn(events, 'connect')
+
+        const myDom0 = document.createElement('div')
+        const target0 = document.createElement('canvas')
+        target0.addEventListener = vi.fn()
+        target0.removeEventListener = vi.fn()
+        myDom0.appendChild(target0)
+
+        const myDom1 = document.createElement('div')
+        const target1 = document.createElement('canvas')
+        target1.addEventListener = vi.fn()
+        target1.removeEventListener = vi.fn()
+        myDom1.appendChild(target1)
+
+        const domRef = shallowRef(myDom0)
+
+        const eventsTarget = computed({
+          get() { return domRef.value.firstChild },
+          set(dom: HTMLDivElement) { domRef.value = dom },
+        })
+
+        context.props = { eventsTarget }
+        withEventsProps(context, events)
+
+        expect(connectSpy).toBeCalledTimes(1)
+        expect(connectSpy).toBeCalledWith(target0)
+
+        expect(target0.addEventListener).toBeCalled()
+        expect(target1.addEventListener).not.toBeCalled()
+
+        vi.clearAllMocks()
+
+        domRef.value = myDom1
+
+        expect(connectSpy).toBeCalledTimes(1)
+        expect(connectSpy).toBeCalledWith(target1)
+
+        expect(target0.addEventListener).not.toBeCalled()
+        expect(target0.removeEventListener).toBeCalled()
+
+        expect(target1.addEventListener).toBeCalled()
+        expect(target1.removeEventListener).not.toBeCalled()
+      })
+    })
+
+    describe('props.eventsTarget === ref(EventTarget | null)', () => {
+      it('calls events.connect(props.eventTarget) and updates on ref changes', () => {
+        const target0 = {
+          addEventListener: vi.fn(),
+          removeEventListener: vi.fn(),
+        } as unknown as HTMLElement
+        const target1 = {
+          addEventListener: vi.fn(),
+          removeEventListener: vi.fn(),
+        } as unknown as HTMLElement
+        const eventsTarget = shallowRef(target0)
+
+        context.props = { eventsTarget }
+        const connectSpy = vi.spyOn(events, 'connect')
+
+        withEventsProps(context, events)
+        expect(connectSpy).toBeCalledTimes(1)
+        expect(connectSpy).toBeCalledWith(target0)
+
+        eventsTarget.value = target1
+        expect(connectSpy).toBeCalledTimes(2)
+        expect(connectSpy).toBeCalledWith(target1)
+
+        eventsTarget.value = target0
+        expect(connectSpy).toBeCalledTimes(3)
+        expect(connectSpy).toBeCalledWith(target0)
+
+        eventsTarget.value = null
+        expect(connectSpy).toBeCalledTimes(4)
+        expect(connectSpy).toBeCalledWith(context.renderer.value.domElement)
+      })
+    })
+
+    describe('props.eventsTarget = ref(null)', () => {
+      it('calls events.connect(context.renderer.value.domElement)', () => {
+        context.props = { eventsTarget: ref(null) }
+        const connectSpy = vi.spyOn(events, 'connect')
+
+        withEventsProps(context, events)
+        expect(connectSpy).toBeCalledTimes(1)
+        expect(connectSpy).toBeCalledWith(context.renderer.value.domElement)
+      })
+    })
+
+    describe('props.eventsTarget = EventTarget', () => {
+      it('calls events.connect(props.eventsTarget)', () => {
+        const target0 = {
+          addEventListener: vi.fn(),
+          removeEventListener: vi.fn(),
+        } as unknown as HTMLElement
+        const eventsTarget = shallowRef(target0)
+
+        context.props = { eventsTarget }
+        const connectSpy = vi.spyOn(events, 'connect')
+
+        withEventsProps(context, events)
+        expect(connectSpy).toBeCalledTimes(1)
+        expect(connectSpy).toBeCalledWith(target0)
+      })
+    })
+  })
+
+  describe('const { stop } = useEventsOptions(...)', () => {
+    it('stops the `events-target` watcher', () => {
+      const target0 = {
+        addEventListener: vi.fn(),
+        removeEventListener: vi.fn(),
+      } as unknown as HTMLElement
+      const target1 = {
+        addEventListener: vi.fn(),
+        removeEventListener: vi.fn(),
+      } as unknown as HTMLElement
+      const eventsTarget = shallowRef(target0)
+
+      context.props = { eventsTarget }
+      const connectSpy = vi.spyOn(events, 'connect')
+
+      expect(connectSpy).toBeCalledTimes(0)
+      const { stop } = withEventsProps(context, events)
+      expect(connectSpy).toBeCalledTimes(1)
+      expect(connectSpy).toBeCalledWith(target0)
+
+      stop()
+
+      eventsTarget.value = target1
+      expect(connectSpy).toBeCalledTimes(1)
+      expect(connectSpy).toBeCalledWith(target0)
+      eventsTarget.value = null
+      expect(connectSpy).toBeCalledTimes(1)
+      expect(connectSpy).toBeCalledWith(target0)
+    })
+  })
+})
+
+function mockTresContext() {
+  const canvas = {
+    addEventListener: () => {},
+    removeEventListener: () => {},
+  }
+  return {
+    events: {},
+    renderer: { value: { domElement: canvas } },
+    emit: () => {},
+    props: {},
+  } as unknown as TresContext
+}

+ 77 - 0
src/utils/createEvents/withEventsProps.ts

@@ -0,0 +1,77 @@
+import type { TresContext } from 'src/composables/useTresContextProvider'
+import type { MaybeRefOrGetter } from 'vue'
+import { toValue, watchEffect } from 'vue'
+import * as is from '../is'
+import { createEvents, type Events, type EventsProps } from './createEvents'
+import { eventsNoop } from './eventsNoop'
+import { eventsRaycast as eventsDefault } from './eventsRaycast'
+
+export interface UseEventsProps {
+  eventsEnabled?: MaybeRefOrGetter<boolean>
+  eventsTarget?: MaybeRefOrGetter<EventTarget>
+  events?: Partial<EventsProps>
+}
+
+/**
+ * Apply reactivity to an `events`.
+ * Optionally create an `events` if one is not passed.
+ *
+ * @param context a Partial TresContext
+ * @param events an `Events` to apply reactivity to. An `events` will be created if not passed.
+ * @returns an object with `stop` – a function to stop reactivity and `events`
+ */
+export function withEventsProps(context: TresContext, events?: Events) {
+  // NOTE: Create events if one was not passed.
+  if (!events) {
+    if (is.und(context.props.events)) {
+    // NOTE: User probably hasn't defined `:events`.
+    // Use the default events setup.
+      events = createEvents(eventsDefault, context)
+    }
+    else if (!context.props.events) {
+    // NOTE: User explicitly set `:events` to a falsy value
+    // other than `undefined`, e.g.:
+    // `<TresCanvas :events="null" />`
+    // `<TresCanvas :events="false" />`
+    // This signals they do not wish to use events at all,
+    // so create a "noop" `events`.
+      events = createEvents(eventsNoop, context)
+    }
+    else if (is.obj(context.props.events)) {
+    // NOTE: User set `:events` to `Partial<EventsProps>`.
+    // Fill in `:events` with defaults. Don't break the user's
+    // reference to the object – they may want to modify the object
+    // values later. Pass their object to `createEvents`.
+      const eventsProps = context.props.events as Partial<EventsProps>
+      for (const [key, value] of Object.entries(eventsDefault)) {
+        if (!(key in eventsProps)) {
+          eventsProps[key as keyof typeof eventsProps] = value
+        }
+      }
+      events = createEvents(context.props.events as EventsProps, context)
+    }
+  }
+
+  if (!events) {
+    // NOTE: User set `:events` to a non-object but truthy, e.g.:
+    // `<TresCanvas :events="true" />`
+    // `<TresCanvas :events="1" />`
+    // Even though this goes against the type system, assume the
+    // user wants to use the default events.
+    events = createEvents(eventsDefault, context)
+  }
+
+  const enabledWatcher = watchEffect(() => {
+    events.enabled = (toValue(context.props.eventsEnabled) ?? true) as boolean
+  }, { flush: 'sync' })
+
+  const targetWatcher = watchEffect(() => {
+    events.connect(toValue(context.props.eventsTarget) ?? context.renderer.value.domElement)
+  }, { flush: 'sync' })
+
+  const unwatchers = [enabledWatcher, targetWatcher]
+  return {
+    stop: () => { unwatchers.forEach(fn => fn()) },
+    events,
+  }
+}

+ 1 - 1
src/utils/index.ts

@@ -573,7 +573,7 @@ export function doRemoveDeregister(node: TresObject, context: TresContext) {
   node.traverse?.((child: TresObject) => {
     context.deregisterCamera(child)
     // deregisterAtPointerEventHandlerIfRequired?.(child as TresObject)
-    context.eventManager?.remove(child)
+    context.events?.remove(child)
   })
 
   // NOTE: Deregister `node`

Plik diff jest za duży
+ 0 - 219
vite.config.ts.timestamp-1727348665686-a275c925ea43b.mjs


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików