11import { Object3D , type Intersection } from "three"
2+ import type { Intersect } from "../playground/controls/type-utils.ts"
23import { $S3C } from "./constants.ts"
3- import type { Context , EventName , Meta , ThreeEvent } from "./types.ts"
4+ import type { BaseEvent , Context , EventName , Meta , StoppableEvent } from "./types.ts"
45import { isInstance } from "./utils.ts"
56
67const eventNameMap = {
@@ -52,36 +53,46 @@ export const isEventType = (type: string): type is EventName =>
5253
5354/**********************************************************************************/
5455/* */
55- /* Create Three Event */
56+ /* Events */
5657/* */
5758/**********************************************************************************/
59+ //
60+ /** Creates a `ThreeEvent` (intersection excluded) from the current `MouseEvent` | `WheelEvent`. */
61+ function createThreeEvent <
62+ TEvent extends Event ,
63+ TConfig extends { stoppable ?: boolean ; intersections ?: Intersection [ ] } ,
64+ > ( nativeEvent : TEvent , { stoppable = true , intersections } : TConfig = { } ) {
65+ const event : Record < string , any > = stoppable
66+ ? {
67+ nativeEvent,
68+ stopped : false ,
69+ stopPropagation ( ) {
70+ event . stopped = true
71+ } ,
72+ }
73+ : { nativeEvent }
5874
59- // Creates a `ThreeEvent` from the current `MouseEvent` | `WheelEvent`.
60- function createThreeEvent < TEvent extends Event > (
61- nativeEvent : TEvent ,
62- stoppable ?: true ,
63- ) : ThreeEvent < TEvent >
64- function createThreeEvent < TEvent extends Event > (
65- nativeEvent : TEvent ,
66- stoppable : false ,
67- ) : ThreeEvent < TEvent , false >
68- function createThreeEvent < TEvent extends Event > (
69- nativeEvent : TEvent ,
70- stoppable = true ,
71- ) : ThreeEvent < TEvent , boolean > {
72- if ( ! stoppable ) {
73- return {
74- nativeEvent,
75- }
75+ if ( intersections ) {
76+ event . intersections = intersections
77+ event . intersection = intersections [ 0 ]
7678 }
77- const event = {
78- nativeEvent,
79- stopped : false ,
80- stopPropagation ( ) {
81- event . stopped = true
82- } ,
83- }
84- return event
79+
80+ return event as Intersect <
81+ [
82+ TConfig [ "stoppable" ] extends false
83+ ? TConfig [ "stoppable" ] extends undefined
84+ ? StoppableEvent < TEvent >
85+ : BaseEvent < TEvent >
86+ : StoppableEvent < TEvent > ,
87+ TConfig [ "intersections" ] extends Intersection [ ]
88+ ? {
89+ intersections : Intersection [ ]
90+ intersection : Intersection
91+ currentIntersection ?: Intersection
92+ }
93+ : unknown ,
94+ ]
95+ >
8596}
8697
8798/**********************************************************************************/
@@ -139,7 +150,6 @@ function createMissableEventRegistry(
139150
140151 context . canvas . addEventListener ( eventNameMap [ type ] , nativeEvent => {
141152 if ( registry . array . length === 0 ) return
142- const event = createThreeEvent ( nativeEvent )
143153 const missedType = `${ type } Missed` as const
144154
145155 // Track which objects have been visited during event processing
@@ -149,22 +159,30 @@ function createMissableEventRegistry(
149159 // Phase #1 - Process normal click events
150160 const intersections = raycast ( context , registry . array , nativeEvent )
151161
152- for ( const { object } of intersections ) {
153- let node : Object3D | null = object
154- while ( node && ! event . stopped && ! visitedObjects . has ( node ) ) {
162+ const stoppableEvent = createThreeEvent ( nativeEvent , { intersections } )
163+
164+ for ( const intersection of intersections ) {
165+ let node : Object3D | null = intersection . object
166+
167+ stoppableEvent . currentIntersection = intersection
168+
169+ while ( node && ! stoppableEvent . stopped && ! visitedObjects . has ( node ) ) {
155170 missedObjects . delete ( node )
156171 visitedObjects . add ( node )
157172 if ( isInstance ( node ) ) {
158- node [ $S3C ] . props ?. [ type ] ?.( event )
173+ node [ $S3C ] . props [ type ] ?.( stoppableEvent )
159174 }
160175 node = node . parent
161176 }
162177 }
163178
164179 // Call the respective canvas event-handler
165180 // if event propagated all the way down
166- if ( ! event . stopped ) {
167- context . props [ type ] ?.( event )
181+ if ( ! stoppableEvent . stopped ) {
182+ if ( "currentIntersection" in stoppableEvent ) {
183+ delete stoppableEvent . currentIntersection
184+ }
185+ context . props [ type ] ?.( stoppableEvent )
168186 }
169187
170188 // Phase #2 - Raycast remaining missed objects
@@ -186,11 +204,11 @@ function createMissableEventRegistry(
186204 }
187205
188206 // Phase #3 - Fire missed event-handler on missed objects
189- const missedEvent = createThreeEvent ( nativeEvent , false )
207+ const missedEvent = createThreeEvent ( nativeEvent , { stoppable : false } )
190208
191209 for ( const object of missedObjects ) {
192210 if ( isInstance ( object ) ) {
193- object [ $S3C ] . props ?. [ missedType ] ?.( missedEvent )
211+ object [ $S3C ] . props [ missedType ] ?.( missedEvent )
194212 }
195213 }
196214
@@ -229,17 +247,20 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
229247 intersections = raycast ( context , registry . array , nativeEvent )
230248
231249 // Phase #1 - Enter
232- const enterEvent = createThreeEvent ( nativeEvent , false )
250+ const enterEvent = createThreeEvent ( nativeEvent , { stoppable : false , intersections } )
233251 const enterSet = new Set < Object3D > ( )
234252
235- for ( const { object } of intersections ) {
253+ for ( const intersection of intersections ) {
254+ // Mutate event
255+ enterEvent . intersection = intersection
256+
236257 // Bubble up
237- let current : Object3D | null = object
258+ let current : Object3D | null = intersection . object
238259 while ( current && ! enterSet . has ( current ) ) {
239260 enterSet . add ( current )
240261
241262 if ( isInstance ( current ) && ! hoveredSet . has ( current ) ) {
242- current [ $S3C ] . props ?. [ `on${ type } Enter` ] ?.( enterEvent )
263+ current [ $S3C ] . props [ `on${ type } Enter` ] ?.( enterEvent )
243264 }
244265 // We bubble a layer down.
245266 current = current . parent
@@ -252,18 +273,22 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
252273 }
253274
254275 // Phase #2 - Move
255- const moveEvent = createThreeEvent ( nativeEvent )
276+ const moveEvent = createThreeEvent ( nativeEvent , { intersections } )
256277 const moveSet = new Set ( )
257278
258- for ( const { object } of intersections ) {
259- if ( moveEvent . stopped ) break
279+ for ( const intersection of intersections ) {
280+ // Mutate currentIntersection
281+ moveEvent . currentIntersection = intersection
282+
260283 // Bubble up
261- let current : Object3D | null = object
284+ let current : Object3D | null = intersection . object
285+
262286 while ( current && ! moveSet . has ( current ) ) {
263287 moveSet . add ( current )
264288
265289 if ( isInstance ( current ) ) {
266- current [ $S3C ] . props ?. [ `on${ type } Move` ] ?.( moveEvent )
290+ // @ts -expect-error TODO: fix type-error
291+ current [ $S3C ] . props [ `on${ type } Move` ] ?.( moveEvent )
267292 // Break if event was
268293 if ( moveEvent . stopped ) {
269294 break
@@ -275,6 +300,8 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
275300 }
276301
277302 if ( ! moveEvent . stopped ) {
303+ // Remove currentIntersection from moveEvent
304+ delete moveEvent . currentIntersection
278305 context . props [ `on${ type } Move` ] ?.( moveEvent )
279306 }
280307
@@ -291,13 +318,13 @@ function createHoverEventRegistry(type: "Mouse" | "Pointer", context: Context) {
291318 } )
292319
293320 context . canvas . addEventListener ( eventNameMap [ `on${ type } Leave` ] , nativeEvent => {
294- const leaveEvent = createThreeEvent ( nativeEvent , false )
321+ const leaveEvent = createThreeEvent ( nativeEvent , { stoppable : false } )
295322 context . props [ `on${ type } Leave` ] ?.( leaveEvent )
296323 hoveredCanvas = false
297324
298325 for ( const object of hoveredSet ) {
299326 if ( isInstance ( object ) ) {
300- object [ $S3C ] . props ?. [ `on${ type } Leave` ] ?.( leaveEvent )
327+ object [ $S3C ] . props [ `on${ type } Leave` ] ?.( leaveEvent )
301328 }
302329 }
303330 hoveredSet . clear ( )
@@ -330,23 +357,29 @@ function createDefaultEventRegistry(
330357 context . canvas . addEventListener (
331358 eventNameMap [ type ] ,
332359 nativeEvent => {
333- const event = createThreeEvent ( nativeEvent )
334360 const intersections = raycast ( context , registry . array , nativeEvent )
361+ const event = createThreeEvent ( nativeEvent , { intersections } )
335362
336- for ( const { object } of intersections ) {
363+ for ( const intersection of intersections ) {
337364 // Bubble up
338- let node : Object3D | null = object
365+ let node : Object3D | null = intersection . object
366+
367+ event . intersection = intersection
339368
340369 while ( node && ! event . stopped ) {
341- if ( isInstance ( object ) ) {
370+ if ( isInstance ( intersection . object ) ) {
342371 // @ts -expect-error TODO: fix type-error
343- object [ $S3C ] . props ?. [ type ] ?.( event )
372+ intersection . object [ $S3C ] . props [ type ] ?.( event )
344373 }
345374 node = node . parent
346375 }
347376 }
348377
349378 if ( ! event . stopped ) {
379+ // Remove trailing intersection from event
380+ if ( "intersection" in event ) {
381+ delete event . intersection
382+ }
350383 // @ts -expect-error TODO: fix type-error
351384 context . props [ type ] ?.( event )
352385 }
0 commit comments