Skip to content

Commit ab76b39

Browse files
committed
feat: implement onDragOverMissed & onDropMissed (WIP)
1 parent e738f58 commit ab76b39

File tree

5 files changed

+63
-5
lines changed

5 files changed

+63
-5
lines changed

example/src/demos/FileDragDrop.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { OrbitControls } from '@react-three/drei'
55

66
export default function Box() {
77
const [active, setActive] = useState(0)
8+
const [activeBg, setActiveBg] = useState(0)
89
// create a common spring that will be used later to interpolate other values
910
const { spring } = useSpring({
1011
spring: active,
@@ -14,19 +15,27 @@ export default function Box() {
1415
const scale = spring.to([0, 1], [1, 2])
1516
const rotation = spring.to([0, 1], [0, Math.PI])
1617
const color = active ? spring.to([0, 1], ['#6246ea', '#e45858']) : spring.to([0, 1], ['#620000', '#e40000'])
18+
const bgColor = activeBg ? 'lightgreen' : 'lightgray'
1719
const preventDragDropDefaults = {
1820
onDrop: (e: SyntheticEvent) => e.preventDefault(),
1921
onDragEnter: (e: SyntheticEvent) => e.preventDefault(),
2022
onDragOver: (e: SyntheticEvent) => e.preventDefault(),
2123
}
2224
return (
23-
<Canvas {...preventDragDropDefaults}>
25+
<Canvas
26+
{...preventDragDropDefaults}
27+
onDropMissed={(e) => console.log('drop missed!')}
28+
onDragOverMissed={(e) => setActiveBg(1)}>
29+
<color attach="background" args={[bgColor]} />
2430
<a.mesh
2531
rotation-y={rotation}
2632
scale-x={scale}
2733
scale-z={scale}
2834
onDrop={(e) => console.log('dropped!', e)}
29-
onDragOverEnter={() => setActive(1)}
35+
onDragOverEnter={() => {
36+
setActive(1)
37+
setActiveBg(0)
38+
}}
3039
onDragOverLeave={() => setActive(0)}>
3140
<boxBufferGeometry />
3241
<a.meshBasicMaterial color={color} />

packages/fiber/src/core/events.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ export type EventHandlers = {
5858
onContextMenu?: (event: ThreeEvent<MouseEvent>) => void
5959
onDoubleClick?: (event: ThreeEvent<MouseEvent>) => void
6060
onDragOverEnter?: (event: ThreeEvent<DragEvent>) => void
61-
onDragOverLeave?: (event: DragEvent) => void
62-
onDrop?: (event: DragEvent) => void
61+
onDragOverLeave?: (event: ThreeEvent<DragEvent>) => void
62+
onDragOverMissed?: (event: DragEvent) => void
63+
onDrop?: (event: ThreeEvent<DragEvent>) => void
64+
onDropMissed?: (event: DragEvent) => void
6365
onPointerUp?: (event: ThreeEvent<PointerEvent>) => void
6466
onPointerDown?: (event: ThreeEvent<PointerEvent>) => void
6567
onPointerOver?: (event: ThreeEvent<PointerEvent>) => void
@@ -414,14 +416,15 @@ export function createEvents(store: UseBoundStore<RootState>) {
414416

415417
// Any other pointer goes here ...
416418
return (event: DomEvent) => {
417-
const { onPointerMissed, internal } = store.getState()
419+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = store.getState()
418420

419421
//prepareRay(event)
420422
internal.lastEvent.current = event
421423

422424
// Get fresh intersects
423425
const isPointerMove = name === 'onPointerMove'
424426
const isDragOver = name === 'onDragOverEnter' || name === 'onDragOverLeave'
427+
const isDrop = name === 'onDrop'
425428
const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick'
426429
const filter = isPointerMove ? filterPointerEvents : undefined
427430
//const hits = patchIntersects(intersect(filter), event)
@@ -442,6 +445,15 @@ export function createEvents(store: UseBoundStore<RootState>) {
442445
if (onPointerMissed) onPointerMissed(event)
443446
}
444447
}
448+
if (isDragOver && !hits.length) {
449+
dragOverMissed(event as DragEvent, internal.interaction)
450+
if (onDragOverMissed) onDragOverMissed(event as DragEvent)
451+
}
452+
if (isDrop && !hits.length) {
453+
dropMissed(event as DragEvent, internal.interaction)
454+
if (onDropMissed) onDropMissed(event as DragEvent)
455+
}
456+
445457
// Take care of unhover
446458
if (isPointerMove || isDragOver) cancelPointer(hits)
447459

@@ -481,6 +493,11 @@ export function createEvents(store: UseBoundStore<RootState>) {
481493
} else if (hoveredItem.stopped) {
482494
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
483495
data.stopPropagation()
496+
} else if (internal.initialHits.includes(eventObject)) {
497+
dragOverMissed(
498+
event as DragEvent,
499+
internal.interaction.filter((object) => !internal.initialHits.includes(object)),
500+
)
484501
}
485502
} else {
486503
// All other events ...
@@ -517,5 +534,15 @@ export function createEvents(store: UseBoundStore<RootState>) {
517534
)
518535
}
519536

537+
function dragOverMissed(event: DragEvent, objects: THREE.Object3D[]) {
538+
objects.forEach((object: THREE.Object3D) =>
539+
(object as unknown as Instance).__r3f?.handlers.onDragOverMissed?.(event),
540+
)
541+
}
542+
543+
function dropMissed(event: DragEvent, objects: THREE.Object3D[]) {
544+
objects.forEach((object: THREE.Object3D) => (object as unknown as Instance).__r3f?.handlers.onDropMissed?.(event))
545+
}
546+
520547
return { handlePointer }
521548
}

packages/fiber/src/core/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ export type RenderProps<TCanvas extends Element> = {
9898
onCreated?: (state: RootState) => void
9999
/** Response for pointer clicks that have missed any target */
100100
onPointerMissed?: (event: MouseEvent) => void
101+
/** Response for dragover events that have missed any target */
102+
onDragOverMissed?: (event: DragEvent) => void
103+
/** Response for drop events that have missed any target */
104+
onDropMissed?: (event: DragEvent) => void
101105
}
102106

103107
const createRendererInstance = <TElement extends Element>(gl: GLProps, canvas: TElement): THREE.WebGLRenderer => {
@@ -169,6 +173,8 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC
169173
raycaster: raycastOptions,
170174
camera: cameraOptions,
171175
onPointerMissed,
176+
onDragOverMissed,
177+
onDropMissed,
172178
} = props
173179

174180
let state = store.getState()
@@ -282,6 +288,10 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC
282288
if (state.frameloop !== frameloop) state.setFrameloop(frameloop)
283289
// Check pointer missed
284290
if (!state.onPointerMissed) state.set({ onPointerMissed })
291+
// Check dragover missed
292+
if (!state.onDragOverMissed) state.set({ onDragOverMissed })
293+
// Check drop missed
294+
if (!state.onDropMissed) state.set({ onDropMissed })
285295
// Check performance
286296
if (performance && !is.equ(performance, state.performance, shallowLoose))
287297
state.set((state) => ({ performance: { ...state.performance, ...performance } }))

packages/fiber/src/core/store.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ export type RootState = {
141141
setFrameloop: (frameloop?: 'always' | 'demand' | 'never') => void
142142
/** When the canvas was clicked but nothing was hit */
143143
onPointerMissed?: (event: MouseEvent) => void
144+
/** When the canvas was dragover but nothing was hit */
145+
onDragOverMissed?: (event: DragEvent) => void
146+
/** When the canvas was dropped but nothing was hit */
147+
onDropMissed?: (event: DragEvent) => void
144148
/** If this state model is layerd (via createPortal) then this contains the previous layer */
145149
previousRoot?: UseBoundStore<RootState, StoreApi<RootState>>
146150
/** Internals */
@@ -209,6 +213,8 @@ const createStore = (
209213

210214
frameloop: 'always',
211215
onPointerMissed: undefined,
216+
onDragOverMissed: undefined,
217+
onDropMissed: undefined,
212218

213219
performance: {
214220
current: 1,

packages/fiber/src/web/Canvas.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const Canvas = /*#__PURE__*/ React.forwardRef<HTMLCanvasElement, Props>(f
4040
raycaster,
4141
camera,
4242
onPointerMissed,
43+
onDragOverMissed,
44+
onDropMissed,
4345
onCreated,
4446
...props
4547
},
@@ -57,6 +59,8 @@ export const Canvas = /*#__PURE__*/ React.forwardRef<HTMLCanvasElement, Props>(f
5759
React.useImperativeHandle(forwardedRef, () => canvasRef.current)
5860

5961
const handlePointerMissed = useMutableCallback(onPointerMissed)
62+
const handleDragOverMissed = useMutableCallback(onDragOverMissed)
63+
const handleDropMissed = useMutableCallback(onDropMissed)
6064
const [block, setBlock] = React.useState<SetBlock>(false)
6165
const [error, setError] = React.useState<any>(false)
6266

@@ -85,6 +89,8 @@ export const Canvas = /*#__PURE__*/ React.forwardRef<HTMLCanvasElement, Props>(f
8589
size: { width, height },
8690
// Pass mutable reference to onPointerMissed so it's free to update
8791
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
92+
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
93+
onDropMissed: (...args) => handleDropMissed.current?.(...args),
8894
onCreated: (state) => {
8995
state.events.connect?.(divRef.current)
9096
onCreated?.(state)

0 commit comments

Comments
 (0)