2024-09-19 19:26:10 +05:30
import { memo , useEffect , useRef , useState } from "react"
2024-09-17 11:55:21 +05:30
import { useDragWidgetContext } from "./draggableWidgetContext"
import { useDragContext } from "../../components/draggable/draggableContext"
2024-09-17 21:23:01 +05:30
// FIXME: sometimes even after drag end the showDroppable is visible
2024-09-17 18:32:33 +05:30
/ * *
* @ param { } - widgetRef - the widget ref for your widget
* @ param { boolean } - enableDraggable - should the widget be draggable
* @ param { string } - dragElementType - the widget type of widget so the droppable knows if the widget can be accepted
* @ param { ( ) => void } - onDrop - the widget type of widget so the droppable knows if the widget can be accepted
* @ param { string [ ] } - droppableTags - array of widget that can be dropped on the widget
*
* /
const WidgetDraggable = memo ( ( { widgetRef , enableDrag = true , dragElementType = "widget" ,
2024-09-21 18:37:28 +05:30
onDragEnter , onDragLeave , onDrop , style = { } ,
2024-09-22 19:23:06 +05:30
droppableTags = { } , ... props } ) => {
2024-09-17 11:55:21 +05:30
// const { draggedElement, onDragStart, onDragEnd } = useDragWidgetContext()
2024-09-17 18:32:33 +05:30
const { draggedElement , onDragStart , onDragEnd , overElement , setOverElement } = useDragContext ( )
2024-09-17 11:55:21 +05:30
2024-09-17 18:32:33 +05:30
// const [dragEnabled, setDragEnabled] = useState(enableDraggable)
2024-09-17 11:55:21 +05:30
const [ isDragging , setIsDragging ] = useState ( false )
const [ showDroppable , setShowDroppable ] = useState ( {
show : false ,
allow : false
} )
const handleDragStart = ( e ) => {
2024-09-19 19:26:10 +05:30
e . stopPropagation ( )
2024-09-17 11:55:21 +05:30
setIsDragging ( true )
onDragStart ( widgetRef ? . current || null )
2024-09-19 19:26:10 +05:30
console . log ( "Drag start: " , widgetRef . current )
2024-09-17 11:55:21 +05:30
// Create custom drag image with full opacity, this will ensure the image isn't taken from part of the canvas
const dragImage = widgetRef ? . current . cloneNode ( true )
dragImage . style . opacity = '1' // Ensure full opacity
dragImage . style . position = 'absolute'
dragImage . style . top = '-9999px' // Move it out of view
document . body . appendChild ( dragImage )
const rect = widgetRef ? . current . getBoundingClientRect ( )
const offsetX = e . clientX - rect . left
const offsetY = e . clientY - rect . top
// Set the custom drag image with correct offset to avoid snapping to the top-left corner
e . dataTransfer . setDragImage ( dragImage , offsetX , offsetY )
// Remove the custom drag image after some time to avoid leaving it in the DOM
setTimeout ( ( ) => {
document . body . removeChild ( dragImage )
} , 0 )
}
const handleDragEnter = ( e ) => {
const dragEleType = draggedElement . getAttribute ( "data-draggable-type" )
2024-09-17 18:32:33 +05:30
// console.log("Drag entering...", overElement === e.currentTarget)
2024-09-19 19:26:10 +05:30
if ( draggedElement === widgetRef . current ) {
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
}
2024-09-17 18:32:33 +05:30
setOverElement ( e . currentTarget )
let showDrop = {
allow : true ,
show : true
}
2024-09-22 19:23:06 +05:30
const allowDrop = ( Object . keys ( droppableTags ) . length === 0 ||
( droppableTags . include . length > 0 && droppableTags . include . includes ( dragEleType ) ) ||
( droppableTags . exclude . length > 0 && ! droppableTags . exclude . includes ( dragEleType ) )
)
2024-09-17 18:32:33 +05:30
2024-09-22 19:23:06 +05:30
if ( allowDrop ) {
2024-09-17 18:32:33 +05:30
showDrop = {
2024-09-17 11:55:21 +05:30
allow : true ,
show : true
2024-09-17 18:32:33 +05:30
}
2024-09-17 11:55:21 +05:30
} else {
2024-09-17 18:32:33 +05:30
showDrop = {
2024-09-17 11:55:21 +05:30
allow : false ,
show : true
2024-09-17 18:32:33 +05:30
}
2024-09-17 11:55:21 +05:30
}
2024-09-17 18:32:33 +05:30
setShowDroppable ( showDrop )
if ( onDragEnter )
onDragEnter ( { element : draggedElement , showDrop } )
2024-09-17 11:55:21 +05:30
}
const handleDragOver = ( e ) => {
2024-09-19 19:26:10 +05:30
if ( draggedElement === widgetRef . current ) {
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
}
2024-09-17 11:55:21 +05:30
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
const dragEleType = draggedElement . getAttribute ( "data-draggable-type" )
2024-09-22 19:23:06 +05:30
const allowDrop = ( Object . keys ( droppableTags ) . length === 0 ||
( droppableTags . include . length > 0 && droppableTags . include . includes ( dragEleType ) ) ||
( droppableTags . exclude . length > 0 && ! droppableTags . exclude . includes ( dragEleType ) )
)
if ( allowDrop ) {
2024-09-17 11:55:21 +05:30
e . preventDefault ( ) // this is necessary to allow drop to take place
}
}
const handleDropEvent = ( e ) => {
2024-09-17 18:32:33 +05:30
e . preventDefault ( )
e . stopPropagation ( )
2024-09-19 19:26:10 +05:30
console . log ( "Dropped: " , draggedElement , props . children )
2024-09-21 18:37:28 +05:30
setShowDroppable ( {
allow : false ,
show : false
} )
if ( onDrop ) {
onDrop ( e , draggedElement )
2024-09-19 19:26:10 +05:30
}
2024-09-21 18:37:28 +05:30
// if (draggedElement === widgetRef.current){
// // prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
// return
// }
2024-09-19 19:26:10 +05:30
let currentElement = e . currentTarget
while ( currentElement ) {
if ( currentElement === draggedElement ) {
console . log ( "Dropped into a descendant element, ignoring drop" )
return // Exit early to prevent the drop
}
currentElement = currentElement . parentElement // Traverse up to check ancestors
}
2024-09-17 11:55:21 +05:30
}
const handleDragLeave = ( e ) => {
if ( ! e . currentTarget . contains ( e . relatedTarget ) ) {
setShowDroppable ( {
allow : false ,
show : false
} )
2024-09-17 18:32:33 +05:30
if ( onDragLeave )
onDragLeave ( )
2024-09-17 11:55:21 +05:30
}
}
const handleDragEnd = ( ) => {
onDragEnd ( )
setIsDragging ( false )
}
2024-09-21 18:37:28 +05:30
// TODO: FIXME, currently the draggable div doesn't move with the child, instead only child div moves, simulating childrens movement, add color and check
2024-09-17 11:55:21 +05:30
return (
2024-09-21 18:37:28 +05:30
< div className = { ` ${ props . className || "" } ` }
2024-09-17 11:55:21 +05:30
onDragOver = { handleDragOver }
onDrop = { handleDropEvent }
onDragEnter = { handleDragEnter }
onDragLeave = { handleDragLeave }
onDragStart = { handleDragStart }
onDragEnd = { handleDragEnd }
2024-09-17 18:32:33 +05:30
draggable = { enableDrag }
2024-09-21 18:37:28 +05:30
style = { { opacity : isDragging ? 0.3 : 1 , ... style } } // hide the initial position when dragging
2024-09-17 18:32:33 +05:30
>
2024-09-17 11:55:21 +05:30
{ props . children }
< / d i v >
)
} )
export default WidgetDraggable