返回 presentation-ai
useDropNode.ts
根目录 / src / components / notebook / presentation / editor / dnd / hooks / useDropNode.ts
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
157 lines TYPESCRIPT