{"version":3,"file":"use-tree-node-drag-drop.mjs","names":[],"sources":["../../../src/components/Tree/use-tree-node-drag-drop.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { findTreeNode } from './get-children-nodes-values/get-children-nodes-values';\nimport type { TreeDragDropPayload, TreeDragDropPosition } from './move-tree-node/move-tree-node';\nimport type { TreeDragState, TreeNodeData } from './Tree';\n\nexport type TreeAllowDrop = (payload: TreeDragDropPayload) => boolean;\n\ninterface UseTreeNodeDragDropInput {\n  nodeValue: string;\n  hasChildren: boolean;\n  data: TreeNodeData[];\n  onDragDrop: ((payload: TreeDragDropPayload) => void) | undefined;\n  dragStateRef: React.RefObject<TreeDragState>;\n  allowDrop: TreeAllowDrop | undefined;\n  withDragHandle: boolean | undefined;\n}\n\nexport interface TreeDragHandleProps {\n  onMouseDown: (event: React.MouseEvent) => void;\n}\n\nfunction isDescendantOf(\n  data: TreeNodeData[],\n  ancestorValue: string,\n  descendantValue: string\n): boolean {\n  const ancestor = findTreeNode(ancestorValue, data);\n  if (!ancestor || !ancestor.children) {\n    return false;\n  }\n\n  function check(nodes: TreeNodeData[]): boolean {\n    for (const node of nodes) {\n      if (node.value === descendantValue) {\n        return true;\n      }\n\n      if (node.children && check(node.children)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  return check(ancestor.children);\n}\n\nfunction getDragDropPosition(\n  event: React.DragEvent,\n  element: HTMLElement,\n  hasChildren: boolean\n): TreeDragDropPosition {\n  const rect = element.getBoundingClientRect();\n  const y = event.clientY - rect.top;\n  const height = rect.height;\n\n  if (hasChildren) {\n    if (y < height * 0.25) {\n      return 'before';\n    }\n\n    if (y > height * 0.75) {\n      return 'after';\n    }\n\n    return 'inside';\n  }\n\n  if (y < height * 0.5) {\n    return 'before';\n  }\n\n  return 'after';\n}\n\nconst EMPTY_DRAG_PROPS = { elementProps: {}, dragHandleProps: undefined } as const;\n\nexport function useTreeNodeDragDrop({\n  nodeValue,\n  hasChildren,\n  data,\n  onDragDrop,\n  dragStateRef,\n  allowDrop,\n  withDragHandle,\n}: UseTreeNodeDragDropInput) {\n  const [isDragHandleActive, setIsDragHandleActive] = useState(false);\n\n  useEffect(() => {\n    if (!withDragHandle || !isDragHandleActive) {\n      return undefined;\n    }\n\n    const handleWindowMouseUp = () => setIsDragHandleActive(false);\n    window.addEventListener('mouseup', handleWindowMouseUp);\n    return () => window.removeEventListener('mouseup', handleWindowMouseUp);\n  }, [withDragHandle, isDragHandleActive]);\n\n  if (!onDragDrop) {\n    return EMPTY_DRAG_PROPS;\n  }\n\n  const handleDragStart = (event: React.DragEvent) => {\n    if (withDragHandle && !isDragHandleActive) {\n      return;\n    }\n\n    event.stopPropagation();\n    event.dataTransfer.effectAllowed = 'move';\n    event.dataTransfer.setData('text/plain', nodeValue);\n    dragStateRef.current.draggedValue = nodeValue;\n\n    const target = event.currentTarget as HTMLElement;\n    requestAnimationFrame(() => {\n      target.setAttribute('data-dragging', 'true');\n    });\n  };\n\n  const handleDragOver = (event: React.DragEvent) => {\n    const draggedValue = dragStateRef.current.draggedValue;\n    if (!draggedValue || draggedValue === nodeValue) {\n      return;\n    }\n\n    if (isDescendantOf(data, draggedValue, nodeValue)) {\n      return;\n    }\n\n    const target = event.currentTarget as HTMLElement;\n    const position = getDragDropPosition(event, target, hasChildren);\n\n    if (allowDrop && !allowDrop({ draggedNode: draggedValue, targetNode: nodeValue, position })) {\n      const prevTarget = dragStateRef.current.currentDropTarget;\n      if (prevTarget && prevTarget !== target) {\n        prevTarget.removeAttribute('data-drag-over');\n      }\n      target.removeAttribute('data-drag-over');\n      dragStateRef.current.currentDropTarget = null;\n      return;\n    }\n\n    event.preventDefault();\n    event.stopPropagation();\n    event.dataTransfer.dropEffect = 'move';\n\n    const prevTarget = dragStateRef.current.currentDropTarget;\n    if (prevTarget && prevTarget !== target) {\n      prevTarget.removeAttribute('data-drag-over');\n    }\n\n    target.setAttribute('data-drag-over', position);\n    dragStateRef.current.currentDropTarget = target;\n  };\n\n  const handleDragLeave = (event: React.DragEvent) => {\n    const target = event.currentTarget as HTMLElement;\n    const related = event.relatedTarget as HTMLElement | null;\n\n    if (related && target.contains(related)) {\n      return;\n    }\n\n    target.removeAttribute('data-drag-over');\n\n    if (dragStateRef.current.currentDropTarget === target) {\n      dragStateRef.current.currentDropTarget = null;\n    }\n  };\n\n  const handleDrop = (event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n\n    const target = event.currentTarget as HTMLElement;\n    const position = target.getAttribute('data-drag-over') as TreeDragDropPosition | null;\n    target.removeAttribute('data-drag-over');\n\n    const draggedValue = dragStateRef.current.draggedValue;\n    if (draggedValue && position && draggedValue !== nodeValue) {\n      const payload = { draggedNode: draggedValue, targetNode: nodeValue, position };\n      if (!allowDrop || allowDrop(payload)) {\n        onDragDrop(payload);\n      }\n    }\n\n    dragStateRef.current.draggedValue = null;\n    dragStateRef.current.currentDropTarget = null;\n  };\n\n  const handleDragEnd = (event: React.DragEvent) => {\n    const target = event.currentTarget as HTMLElement;\n    target.removeAttribute('data-dragging');\n\n    const prevTarget = dragStateRef.current.currentDropTarget;\n    if (prevTarget) {\n      prevTarget.removeAttribute('data-drag-over');\n    }\n\n    dragStateRef.current.draggedValue = null;\n    dragStateRef.current.currentDropTarget = null;\n\n    if (withDragHandle) {\n      setIsDragHandleActive(false);\n    }\n  };\n\n  const elementProps = {\n    draggable: withDragHandle ? isDragHandleActive : true,\n    onDragStart: handleDragStart,\n    onDragOver: handleDragOver,\n    onDragLeave: handleDragLeave,\n    onDrop: handleDrop,\n    onDragEnd: handleDragEnd,\n  };\n\n  const dragHandleProps: TreeDragHandleProps | undefined = withDragHandle\n    ? { onMouseDown: () => setIsDragHandleActive(true) }\n    : undefined;\n\n  return { elementProps, dragHandleProps };\n}\n"],"mappings":"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"}