| 1 | /** biome-ignore-all lint/suspicious/noExplicitAny: This use requires any */ |
| 2 | import { |
| 3 | DndPlugin, |
| 4 | type DragItemNode, |
| 5 | type ElementDragItemNode, |
| 6 | type FileDragItemNode, |
| 7 | } from "@platejs/dnd"; |
| 8 | import { type NodeEntry, type TElement } from "platejs"; |
| 9 | import { type PlateEditor } from "platejs/react"; |
| 10 | import { |
| 11 | useDrop, |
| 12 | type DropTargetHookSpec, |
| 13 | type DropTargetMonitor, |
| 14 | } from "react-dnd"; |
| 15 | |
| 16 | import { onDropNode } from "../transforms/onDropNode"; |
| 17 | import { onHoverNode } from "../transforms/onHoverNode"; |
| 18 | import { getDropPath } from "../utils/getDropPath"; |
| 19 | |
| 20 | export type CanDropCallback = (args: { |
| 21 | dragEntry: NodeEntry<TElement>; |
| 22 | dragItem: DragItemNode; |
| 23 | dropEntry: NodeEntry<TElement>; |
| 24 | editor: PlateEditor; |
| 25 | }) => boolean; |
| 26 | |
| 27 | export interface UseDropNodeOptions extends DropTargetHookSpec< |
| 28 | DragItemNode, |
| 29 | unknown, |
| 30 | { isOver: boolean } |
| 31 | > { |
| 32 | /** The node to which the drop line is attached. */ |
| 33 | element: TElement; |
| 34 | |
| 35 | /** The reference to the node being dragged. */ |
| 36 | nodeRef: any; |
| 37 | |
| 38 | /** The reference to the multiple preview element */ |
| 39 | multiplePreviewRef: any; |
| 40 | |
| 41 | /** |
| 42 | * If true, left/right drops will create column layouts. |
| 43 | * If false, left/right drops will just reorder (same as top/bottom). |
| 44 | */ |
| 45 | canCreateColumns?: boolean; |
| 46 | |
| 47 | /** |
| 48 | * Intercepts the drop handling. If `false` is returned, the default drop |
| 49 | * behavior is called after. If `true` is returned, the default behavior is |
| 50 | * not called. |
| 51 | */ |
| 52 | canDropNode?: CanDropCallback; |
| 53 | |
| 54 | /** |
| 55 | * Handler for custom drop behavior |
| 56 | */ |
| 57 | onDropHandler?: ( |
| 58 | editor: PlateEditor, |
| 59 | props: { |
| 60 | id: string; |
| 61 | dragItem: DragItemNode; |
| 62 | monitor: DropTargetMonitor<DragItemNode, unknown>; |
| 63 | nodeRef: any; |
| 64 | }, |
| 65 | ) => boolean | undefined; |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * `useDrop` hook to drop a node on the editor. |
| 70 | * |
| 71 | * On drop: |
| 72 | * - Get hover direction (top, bottom, left, right), return early if undefined |
| 73 | * - For left/right: create columns if canCreateColumns=true, else reorder |
| 74 | * - For top/bottom: reorder nodes |
| 75 | * |
| 76 | * On hover: |
| 77 | * - Get drop line direction |
| 78 | * - If differs from dropLine, setDropLine is called |
| 79 | */ |
| 80 | export const useDropNode = ( |
| 81 | editor: PlateEditor, |
| 82 | { |
| 83 | canDropNode, |
| 84 | canCreateColumns = false, |
| 85 | element, |
| 86 | nodeRef, |
| 87 | onDropHandler, |
| 88 | ...options |
| 89 | }: UseDropNodeOptions, |
| 90 | ) => { |
| 91 | const id = element.id as string; |
| 92 | |
| 93 | return useDrop<DragItemNode, unknown, { isOver: boolean }>({ |
| 94 | collect: (monitor) => ({ |
| 95 | isOver: monitor.isOver({ |
| 96 | shallow: true, |
| 97 | }), |
| 98 | }), |
| 99 | drop: (dragItem, monitor) => { |
| 100 | // Don't call onDropNode if this is a file drop |
| 101 | if (!(dragItem as ElementDragItemNode).id) { |
| 102 | const result = getDropPath(editor, { |
| 103 | canDropNode, |
| 104 | canCreateColumns, |
| 105 | dragItem, |
| 106 | element, |
| 107 | monitor, |
| 108 | nodeRef, |
| 109 | }); |
| 110 | |
| 111 | const onDropFiles = editor.getOptions(DndPlugin).onDropFiles; |
| 112 | |
| 113 | if (!result || !onDropFiles) return; |
| 114 | |
| 115 | return onDropFiles({ |
| 116 | id, |
| 117 | dragItem: dragItem as FileDragItemNode, |
| 118 | editor, |
| 119 | monitor, |
| 120 | nodeRef, |
| 121 | target: result.to, |
| 122 | }); |
| 123 | } |
| 124 | |
| 125 | const handled = |
| 126 | !!onDropHandler && |
| 127 | onDropHandler(editor, { |
| 128 | id, |
| 129 | dragItem, |
| 130 | monitor, |
| 131 | nodeRef, |
| 132 | }); |
| 133 | |
| 134 | if (handled) return; |
| 135 | |
| 136 | onDropNode(editor, { |
| 137 | canDropNode, |
| 138 | canCreateColumns, |
| 139 | dragItem: dragItem as ElementDragItemNode, |
| 140 | element, |
| 141 | monitor, |
| 142 | nodeRef, |
| 143 | }); |
| 144 | }, |
| 145 | hover(item: DragItemNode, monitor: DropTargetMonitor) { |
| 146 | onHoverNode(editor, { |
| 147 | canDropNode, |
| 148 | dragItem: item, |
| 149 | element, |
| 150 | monitor, |
| 151 | nodeRef, |
| 152 | }); |
| 153 | }, |
| 154 | ...options, |
| 155 | }); |
| 156 | }; |
| 157 |