@syncfusion/react-base
Version:
A common package of core React base, methods and class definitions
150 lines (149 loc) • 5.42 kB
JavaScript
import { useId, useLayoutEffect } from 'react';
import { Browser } from './browser';
import { addClass, isVisible, matches } from './dom';
import { compareElementParent } from './util';
import { EventHandler } from './event-handler';
import { useDragDropContext } from './dragdrop';
/**
* Creates a droppable instance with the specified element and properties.
*
* @private
* @param {RefObject<HTMLElement>} [element] - Reference to the HTML element to make droppable.
* @param {IDroppable} [props] - Configuration properties for the droppable instance.
* @returns {IDroppable} The configured droppable instance.
*/
export function useDroppable(element, props) {
const droppableId = useId();
const droppableContext = useDragDropContext();
const { registerDroppable, unregisterDroppable } = droppableContext || {};
const propsRef = {
accept: '',
scope: 'default',
dragData: {},
drop: null,
over: null,
out: null,
...props
};
/** Represents whether the mouse is over the droppable area */
let mouseOverRef = false;
/** Indicates if drag stop has been called */
let dragStopCalledRef = true;
/**
* Method to add drop events.
*
* @returns {void}
*/
function addEvent() {
EventHandler.add(element.current, Browser.isSafari() ? 'touchend' : Browser.touchEndEvent, propsRef.intDrop);
}
/**
* Handles interactions when a dragged item is over the droppable area.
*
* @param {MouseEvent | TouchEvent} event - Mouse or touch event arguments.
* @param {Element} [element] - The target element over which the drag is happening.
* @returns {void}
*/
propsRef.intOver = (event, element) => {
if (!mouseOverRef) {
const drag = propsRef.dragData[propsRef.scope];
if (propsRef.over) {
propsRef.over({ event, target: element, dragData: drag });
}
mouseOverRef = true;
}
};
/**
* Method for handling interactions when dragged item is out of the droppable area.
*
* @param {MouseEvent | TouchEvent} event - Mouse or touch event arguments.
* @param {Element} [element] - The target element from which the drag is moving out.
* @returns {void}
*/
propsRef.intOut = (event, element) => {
if (mouseOverRef) {
if (propsRef.out) {
propsRef.out({ event, target: element });
}
mouseOverRef = false;
}
};
/**
* Method to handle drop event.
*
* @param {MouseEvent | TouchEvent} evt - Mouse or touch event arguments.
* @param {HTMLElement} [element] - The target element where the drop is happening.
* @returns {void}
*/
propsRef.intDrop = (evt, element) => {
if (!dragStopCalledRef) {
return;
}
else {
dragStopCalledRef = false;
}
let accept = true;
const drag = propsRef.dragData[propsRef.scope];
const isDrag = drag ? (drag.helper && isVisible(drag.helper)) : false;
let area;
if (isDrag) {
area = isDropArea(evt, drag.helper, element);
if (propsRef.accept) {
accept = matches(drag.helper, propsRef.accept);
}
}
if (isDrag && propsRef.drop && area.canDrop && accept) {
propsRef.drop({ event: evt, target: area.target, droppedElement: drag.helper, dragData: drag });
}
mouseOverRef = false;
};
/**
* Method to check if the drop area is valid.
*
* @param {MouseEvent | TouchEvent} evt - Mouse or touch event arguments.
* @param {HTMLElement} helper - The helper element involved in the drag operation.
* @param {HTMLElement} [element] - The element to check for drop validity.
* @returns {DropData} - The result indicating if the area is a valid drop target and the target itself.
*/
function isDropArea(evt, helper, element) {
const area = { canDrop: true, target: element || evt.target };
const isTouch = evt.type === 'touchend';
if (isTouch || area.target === helper) {
helper.style.display = 'none';
const coord = isTouch ? (evt.changedTouches[0]) : evt;
const ele = document.elementFromPoint(coord.clientX, coord.clientY);
area.canDrop = false;
area.canDrop = compareElementParent(ele, element);
if (area.canDrop) {
area.target = ele;
}
helper.style.display = '';
}
return area;
}
/**
* Method to clean up and remove event handlers on the component destruction.
*
* @returns {void}
*/
function removeEvent() {
EventHandler.remove(element.current, Browser.isSafari() ? 'touchend' : Browser.touchEndEvent, propsRef.intDrop);
}
useLayoutEffect(() => {
addClass([element.current], ['sf-lib', 'sf-droppable']);
addEvent();
if (registerDroppable) {
registerDroppable(droppableId, {
...propsRef,
element: element
});
}
return () => {
if (unregisterDroppable) {
unregisterDroppable(droppableId);
}
removeEvent();
};
}, []);
return propsRef;
}