Skip to content

Commit

Permalink
Implement onDragOverEnter & onDragOverLeave
Browse files Browse the repository at this point in the history
  • Loading branch information
haywirez committed Jul 15, 2022
1 parent cdbeaf9 commit 2ca8219
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 7 deletions.
32 changes: 27 additions & 5 deletions packages/fiber/src/core/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export type Events = {
onClick: EventListener
onContextMenu: EventListener
onDoubleClick: EventListener
onDragOverEnter: EventListener
onDragOverLeave: EventListener
onWheel: EventListener
onPointerDown: EventListener
onPointerUp: EventListener
Expand All @@ -54,6 +56,8 @@ export type EventHandlers = {
onClick?: (event: ThreeEvent<MouseEvent>) => void
onContextMenu?: (event: ThreeEvent<MouseEvent>) => void
onDoubleClick?: (event: ThreeEvent<MouseEvent>) => void
onDragOverEnter?: (event: ThreeEvent<DragEvent>) => void
onDragOverLeave?: (event: DragEvent) => void
onPointerUp?: (event: ThreeEvent<PointerEvent>) => void
onPointerDown?: (event: ThreeEvent<PointerEvent>) => void
onPointerOver?: (event: ThreeEvent<PointerEvent>) => void
Expand Down Expand Up @@ -109,6 +113,7 @@ export function getEventPriority() {
case 'pointerdown':
case 'pointerup':
return DiscreteEventPriority
case 'dragover':
case 'pointermove':
case 'pointerout':
case 'pointerover':
Expand Down Expand Up @@ -171,10 +176,12 @@ export function createEvents(store: UseBoundStore<RootState>) {

/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects: THREE.Object3D[]) {
return objects.filter((obj) =>
['Move', 'Over', 'Enter', 'Out', 'Leave'].some(
(name) => (obj as unknown as Instance).__r3f?.handlers[('onPointer' + name) as keyof EventHandlers],
),
return objects.filter(
(obj) =>
['Move', 'Over', 'Enter', 'Out', 'Leave'].some(
(name) => (obj as unknown as Instance).__r3f?.handlers[('onPointer' + name) as keyof EventHandlers],
) ||
['Over'].some((name) => (obj as unknown as Instance).__r3f?.handlers[('onDrag' + name) as keyof EventHandlers]),
)
}

Expand Down Expand Up @@ -377,6 +384,8 @@ export function createEvents(store: UseBoundStore<RootState>) {
const data = { ...hoveredObj, intersections }
handlers.onPointerOut?.(data as ThreeEvent<PointerEvent>)
handlers.onPointerLeave?.(data as ThreeEvent<PointerEvent>)
// @ts-ignore
handlers.onDragOverLeave?.(data)
}
}
})
Expand Down Expand Up @@ -409,6 +418,7 @@ export function createEvents(store: UseBoundStore<RootState>) {

// Get fresh intersects
const isPointerMove = name === 'onPointerMove'
const isDragOver = name === 'onDragOverEnter' || name === 'onDragOverLeave'
const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick'
const filter = isPointerMove ? filterPointerEvents : undefined
//const hits = patchIntersects(intersect(filter), event)
Expand All @@ -430,7 +440,7 @@ export function createEvents(store: UseBoundStore<RootState>) {
}
}
// Take care of unhover
if (isPointerMove) cancelPointer(hits)
if (isPointerMove || isDragOver) cancelPointer(hits)

handleIntersects(hits, event, delta, (data: ThreeEvent<DomEvent>) => {
const eventObject = data.eventObject
Expand All @@ -457,6 +467,18 @@ export function createEvents(store: UseBoundStore<RootState>) {
}
// Call mouse move
handlers.onPointerMove?.(data as ThreeEvent<PointerEvent>)
} else if (isDragOver) {
// When enter or out is present take care of hover-state
const id = makeId(data)
const hoveredItem = internal.hovered.get(id)
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
internal.hovered.set(id, data)
handlers.onDragOverEnter?.(data as ThreeEvent<DragEvent>)
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation()
}
} else {
// All other events ...
const handler = handlers[name as keyof EventHandlers] as (event: ThreeEvent<PointerEvent>) => void
Expand Down
3 changes: 2 additions & 1 deletion packages/fiber/src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ export function diffProps(
// When props match bail out
if (is.equ(value, previous[key])) return
// Collect handlers and bail out
if (/^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(key)) return changes.push([key, value, true, []])
if (/^on(Pointer|DragOver|Click|DoubleClick|ContextMenu|Wheel)/.test(key))
return changes.push([key, value, true, []])
// Split dashed props
let entries: string[] = []
if (key.includes('-')) entries = key.split('-')
Expand Down
2 changes: 2 additions & 0 deletions packages/fiber/src/web/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const DOM_EVENTS = {
onClick: ['click', false],
onContextMenu: ['contextmenu', false],
onDoubleClick: ['dblclick', false],
onDragOverEnter: ['dragover', true],
onDragOverLeave: ['dragover', true],
onWheel: ['wheel', true],
onPointerDown: ['pointerdown', true],
onPointerUp: ['pointerup', true],
Expand Down
40 changes: 39 additions & 1 deletion packages/fiber/tests/core/events.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { render, fireEvent, RenderResult } from '@testing-library/react'
import { render, fireEvent, createEvent, RenderResult } from '@testing-library/react'

import { Canvas, act } from '../../src'

Expand Down Expand Up @@ -210,6 +210,44 @@ describe('events', () => {
expect(handlePointerOut).toHaveBeenCalled()
})

it('can handle onDragOverEnter & onDragOverLeave', async () => {
const handleDragOverEnter = jest.fn()
const handleDragOverLeave = jest.fn()

await act(async () => {
render(
<Canvas>
<mesh onDragOverEnter={handleDragOverEnter} onDragOverLeave={handleDragOverLeave}>
<boxGeometry args={[2, 2]} />
<meshBasicMaterial />
</mesh>
</Canvas>,
)
})

// Note: DragEvent is not implemented in jsdom yet: https://github.com/jsdom/jsdom/issues/2913
// https://developer.mozilla.org/en-US/docs/Web/API/DragEvent
// however, @react-testing/library does simulate it
let evt = createEvent.dragOver(getContainer())
//@ts-ignore
evt.offsetX = 577
//@ts-ignore
evt.offsetY = 480

fireEvent(getContainer(), evt)

expect(handleDragOverEnter).toHaveBeenCalled()

// pretend we moved out over from the target
//@ts-ignore
evt.offsetX = 1
//@ts-ignore
evt.offsetY = 1
fireEvent(getContainer(), evt)

expect(handleDragOverLeave).toHaveBeenCalled()
})

it('should handle stopPropagation', async () => {
const handlePointerEnter = jest.fn().mockImplementation((e) => {
expect(() => e.stopPropagation()).not.toThrow()
Expand Down

0 comments on commit 2ca8219

Please sign in to comment.