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