| 1 | /** biome-ignore-all lint/suspicious/noExplicitAny: This use requires any */ |
| 2 | import { DRAG_ITEM_BLOCK, type DragItemNode } from "@platejs/dnd"; |
| 3 | import { useEditorRef, type PlateEditor } from "platejs/react"; |
| 4 | import React, { type RefObject } from "react"; |
| 5 | import { type ConnectDragSource, type DropTargetMonitor } from "react-dnd"; |
| 6 | import { getEmptyImage, NativeTypes } from "react-dnd-html5-backend"; |
| 7 | |
| 8 | import { |
| 9 | registerFreeformDropNode, |
| 10 | type DropOrientation, |
| 11 | } from "../utils/freeformDrop"; |
| 12 | import { useDragNode, type UseDragNodeOptions } from "./useDragNode"; |
| 13 | import { useDropNode, type UseDropNodeOptions } from "./useDropNode"; |
| 14 | |
| 15 | export type UseDndNodeOptions = Pick<UseDropNodeOptions, "element"> & |
| 16 | Partial< |
| 17 | Pick< |
| 18 | UseDropNodeOptions, |
| 19 | "canDropNode" | "multiplePreviewRef" | "nodeRef" | "canCreateColumns" |
| 20 | > |
| 21 | > & |
| 22 | Partial<Pick<UseDragNodeOptions, "type">> & { |
| 23 | /** Options passed to the drag hook. */ |
| 24 | drag?: Partial<Omit<UseDragNodeOptions, "type">>; |
| 25 | /** Options passed to the drop hook, excluding element, nodeRef. */ |
| 26 | drop?: Partial< |
| 27 | Omit<UseDropNodeOptions, "canDropNode" | "element" | "nodeRef"> |
| 28 | >; |
| 29 | preview?: { |
| 30 | /** Whether to disable the preview. */ |
| 31 | disable?: boolean; |
| 32 | /** The reference to the preview element. */ |
| 33 | ref?: any; |
| 34 | }; |
| 35 | orientation?: DropOrientation; |
| 36 | onDropHandler?: ( |
| 37 | editor: PlateEditor, |
| 38 | props: { |
| 39 | id: string; |
| 40 | dragItem: DragItemNode; |
| 41 | monitor: DropTargetMonitor<DragItemNode, unknown>; |
| 42 | nodeRef: any; |
| 43 | }, |
| 44 | ) => boolean | undefined; |
| 45 | }; |
| 46 | |
| 47 | /** |
| 48 | * {@link useDragNode} and {@link useDropNode} hooks to drag and drop a node from |
| 49 | * the editor. A default preview is used to show the node being dragged, which |
| 50 | * can be customized or removed. |
| 51 | * |
| 52 | * @param canCreateColumns - If true, left/right drops create columns. If false, they reorder. |
| 53 | */ |
| 54 | export const useDndNode = ({ |
| 55 | canDropNode, |
| 56 | canCreateColumns = false, |
| 57 | drag: dragOptions, |
| 58 | drop: dropOptions, |
| 59 | element, |
| 60 | multiplePreviewRef, |
| 61 | nodeRef, |
| 62 | orientation = "vertical", |
| 63 | preview: previewOptions = {}, |
| 64 | type = DRAG_ITEM_BLOCK, |
| 65 | onDropHandler, |
| 66 | }: UseDndNodeOptions): { |
| 67 | dragRef: ConnectDragSource; |
| 68 | isAboutToDrag: boolean; |
| 69 | isDragging: boolean; |
| 70 | isOver: boolean; |
| 71 | } => { |
| 72 | const editor = useEditorRef(); |
| 73 | |
| 74 | const [{ isAboutToDrag, isDragging }, dragRef, preview] = useDragNode( |
| 75 | editor, |
| 76 | { |
| 77 | element, |
| 78 | type, |
| 79 | ...dragOptions, |
| 80 | }, |
| 81 | ); |
| 82 | |
| 83 | const [{ isOver }, drop] = useDropNode(editor, { |
| 84 | accept: [type, NativeTypes.FILE], |
| 85 | canDropNode, |
| 86 | canCreateColumns, |
| 87 | element, |
| 88 | multiplePreviewRef, |
| 89 | nodeRef, |
| 90 | onDropHandler, |
| 91 | ...dropOptions, |
| 92 | }); |
| 93 | |
| 94 | // Always use nodeRef for the drop target (actual DOM element) |
| 95 | drop(nodeRef); |
| 96 | |
| 97 | React.useEffect(() => { |
| 98 | if (!nodeRef || !element.id) return; |
| 99 | |
| 100 | return registerFreeformDropNode(editor, { |
| 101 | canCreateColumns, |
| 102 | canDropNode, |
| 103 | element, |
| 104 | id: element.id as string, |
| 105 | nodeRef: nodeRef as RefObject<HTMLElement | null>, |
| 106 | orientation, |
| 107 | }); |
| 108 | }, [canCreateColumns, canDropNode, editor, element, nodeRef, orientation]); |
| 109 | |
| 110 | // Handle preview based on options and whether we're dragging multiple nodes |
| 111 | if (previewOptions.disable) { |
| 112 | preview(getEmptyImage(), { captureDraggingState: true }); |
| 113 | } else if (previewOptions.ref) { |
| 114 | preview(previewOptions.ref); |
| 115 | } else { |
| 116 | preview(multiplePreviewRef); |
| 117 | } |
| 118 | |
| 119 | return { |
| 120 | dragRef, |
| 121 | isAboutToDrag, |
| 122 | isDragging, |
| 123 | isOver, |
| 124 | }; |
| 125 | }; |
| 126 |