@tiptap/extension-drag-handle-react 3.22.5 → 3.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(index_exports);
28
28
  // src/DragHandle.tsx
29
29
  var import_extension_drag_handle = require("@tiptap/extension-drag-handle");
30
30
  var import_react = require("react");
31
- var import_jsx_runtime = require("react/jsx-runtime");
31
+ var import_react_dom = require("react-dom");
32
32
  var DragHandle = (props) => {
33
33
  const {
34
34
  className = "drag-handle",
@@ -41,45 +41,48 @@ var DragHandle = (props) => {
41
41
  computePositionConfig = import_extension_drag_handle.defaultComputePositionConfig,
42
42
  nested = false
43
43
  } = props;
44
- const [element, setElement] = (0, import_react.useState)(null);
45
- const plugin = (0, import_react.useRef)(null);
44
+ const [element] = (0, import_react.useState)(() => {
45
+ if (typeof document === "undefined") {
46
+ return null;
47
+ }
48
+ return document.createElement("div");
49
+ });
46
50
  const nestedOptions = (0, import_react.useMemo)(() => (0, import_extension_drag_handle.normalizeNestedOptions)(nested), [JSON.stringify(nested)]);
47
51
  (0, import_react.useEffect)(() => {
48
- let initPlugin = null;
49
52
  if (!element) {
50
- return () => {
51
- plugin.current = null;
52
- };
53
+ return;
53
54
  }
54
- if (editor.isDestroyed) {
55
- return () => {
56
- plugin.current = null;
57
- };
55
+ element.className = className;
56
+ element.style.visibility = "hidden";
57
+ element.style.position = "absolute";
58
+ element.dataset.dragging = "false";
59
+ }, [className, element]);
60
+ (0, import_react.useEffect)(() => {
61
+ if (!element) {
62
+ return;
58
63
  }
59
- if (!plugin.current) {
60
- initPlugin = (0, import_extension_drag_handle.DragHandlePlugin)({
61
- editor,
62
- element,
63
- pluginKey,
64
- computePositionConfig: {
65
- ...import_extension_drag_handle.defaultComputePositionConfig,
66
- ...computePositionConfig
67
- },
68
- onElementDragStart,
69
- onElementDragEnd,
70
- onNodeChange,
71
- nestedOptions
72
- });
73
- plugin.current = initPlugin.plugin;
74
- editor.registerPlugin(plugin.current);
64
+ if (editor.isDestroyed) {
65
+ return;
75
66
  }
67
+ const { plugin, unbind } = (0, import_extension_drag_handle.DragHandlePlugin)({
68
+ editor,
69
+ element,
70
+ pluginKey,
71
+ computePositionConfig: {
72
+ ...import_extension_drag_handle.defaultComputePositionConfig,
73
+ ...computePositionConfig
74
+ },
75
+ onElementDragStart,
76
+ onElementDragEnd,
77
+ onNodeChange,
78
+ nestedOptions
79
+ });
80
+ editor.registerPlugin(plugin);
76
81
  return () => {
77
- editor.unregisterPlugin(pluginKey);
78
- plugin.current = null;
79
- if (initPlugin) {
80
- initPlugin.unbind();
81
- initPlugin = null;
82
+ if (!editor.isDestroyed) {
83
+ editor.unregisterPlugin(pluginKey);
82
84
  }
85
+ unbind();
83
86
  };
84
87
  }, [
85
88
  element,
@@ -91,16 +94,10 @@ var DragHandle = (props) => {
91
94
  onElementDragEnd,
92
95
  nestedOptions
93
96
  ]);
94
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
95
- "div",
96
- {
97
- className,
98
- style: { visibility: "hidden", position: "absolute" },
99
- "data-dragging": "false",
100
- ref: setElement,
101
- children
102
- }
103
- );
97
+ if (!element) {
98
+ return null;
99
+ }
100
+ return (0, import_react_dom.createPortal)(children, element);
104
101
  };
105
102
 
106
103
  // src/index.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/DragHandle.tsx"],"sourcesContent":["import { DragHandle } from './DragHandle.js'\n\nexport * from './DragHandle.js'\n\nexport default DragHandle\n","import {\n type DragHandlePluginProps,\n type NestedOptions,\n defaultComputePositionConfig,\n DragHandlePlugin,\n dragHandlePluginDefaultKey,\n normalizeNestedOptions,\n} from '@tiptap/extension-drag-handle'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Plugin } from '@tiptap/pm/state'\nimport type { Editor } from '@tiptap/react'\nimport { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'element' | 'nestedOptions'> & {\n className?: string\n onNodeChange?: (data: { node: Node | null; editor: Editor; pos: number }) => void\n children: ReactNode\n\n /**\n * Enable drag handles for nested content (list items, blockquotes, etc.).\n *\n * When enabled, the drag handle will appear for nested blocks, not just\n * top-level blocks. A rule-based scoring system determines which node\n * to target based on cursor position and configured rules.\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * <DragHandle editor={editor} nested>\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom configuration\n * <DragHandle\n * editor={editor}\n * nested={{\n * edgeDetection: 'left',\n * allowedContainers: ['bulletList', 'orderedList'],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom rules\n * <DragHandle\n * editor={editor}\n * nested={{\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n */\n nested?: boolean | NestedOptions\n}\n\nexport const DragHandle = (props: DragHandleProps) => {\n const {\n className = 'drag-handle',\n children,\n editor,\n pluginKey = dragHandlePluginDefaultKey,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n computePositionConfig = defaultComputePositionConfig,\n nested = false,\n } = props\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n const plugin = useRef<Plugin | null>(null)\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const nestedOptions = useMemo(() => normalizeNestedOptions(nested), [JSON.stringify(nested)])\n\n useEffect(() => {\n let initPlugin: {\n plugin: Plugin\n unbind: () => void\n } | null = null\n\n if (!element) {\n return () => {\n plugin.current = null\n }\n }\n\n if (editor.isDestroyed) {\n return () => {\n plugin.current = null\n }\n }\n\n if (!plugin.current) {\n initPlugin = DragHandlePlugin({\n editor,\n element,\n pluginKey,\n computePositionConfig: {\n ...defaultComputePositionConfig,\n ...computePositionConfig,\n },\n onElementDragStart,\n onElementDragEnd,\n onNodeChange,\n nestedOptions,\n })\n plugin.current = initPlugin.plugin\n\n editor.registerPlugin(plugin.current)\n }\n\n return () => {\n editor.unregisterPlugin(pluginKey)\n plugin.current = null\n if (initPlugin) {\n initPlugin.unbind()\n initPlugin = null\n }\n }\n }, [\n element,\n editor,\n onNodeChange,\n pluginKey,\n computePositionConfig,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n ])\n\n return (\n <div\n className={className}\n style={{ visibility: 'hidden', position: 'absolute' }}\n data-dragging=\"false\"\n ref={setElement}\n >\n {children}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mCAOO;AAIP,mBAAqE;AAgIjE;AA3EG,IAAM,aAAa,CAAC,UAA2B;AACpD,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,wBAAwB;AAAA,IACxB,SAAS;AAAA,EACX,IAAI;AACJ,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAgC,IAAI;AAClE,QAAM,aAAS,qBAAsB,IAAI;AAGzC,QAAM,oBAAgB,sBAAQ,UAAM,qDAAuB,MAAM,GAAG,CAAC,KAAK,UAAU,MAAM,CAAC,CAAC;AAE5F,8BAAU,MAAM;AACd,QAAI,aAGO;AAEX,QAAI,CAAC,SAAS;AACZ,aAAO,MAAM;AACX,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAEA,QAAI,OAAO,aAAa;AACtB,aAAO,MAAM;AACX,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,uBAAa,+CAAiB;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,QACA,uBAAuB;AAAA,UACrB,GAAG;AAAA,UACH,GAAG;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,UAAU,WAAW;AAE5B,aAAO,eAAe,OAAO,OAAO;AAAA,IACtC;AAEA,WAAO,MAAM;AACX,aAAO,iBAAiB,SAAS;AACjC,aAAO,UAAU;AACjB,UAAI,YAAY;AACd,mBAAW,OAAO;AAClB,qBAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO,EAAE,YAAY,UAAU,UAAU,WAAW;AAAA,MACpD,iBAAc;AAAA,MACd,KAAK;AAAA,MAEJ;AAAA;AAAA,EACH;AAEJ;;;ADhJA,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/DragHandle.tsx"],"sourcesContent":["import { DragHandle } from './DragHandle.js'\n\nexport * from './DragHandle.js'\n\nexport default DragHandle\n","import {\n type DragHandlePluginProps,\n type NestedOptions,\n defaultComputePositionConfig,\n DragHandlePlugin,\n dragHandlePluginDefaultKey,\n normalizeNestedOptions,\n} from '@tiptap/extension-drag-handle'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Editor } from '@tiptap/react'\nimport { type ReactNode, useEffect, useMemo, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'element' | 'nestedOptions'> & {\n className?: string\n onNodeChange?: (data: { node: Node | null; editor: Editor; pos: number }) => void\n children: ReactNode\n\n /**\n * Enable drag handles for nested content (list items, blockquotes, etc.).\n *\n * When enabled, the drag handle will appear for nested blocks, not just\n * top-level blocks. A rule-based scoring system determines which node\n * to target based on cursor position and configured rules.\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * <DragHandle editor={editor} nested>\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom configuration\n * <DragHandle\n * editor={editor}\n * nested={{\n * edgeDetection: 'left',\n * allowedContainers: ['bulletList', 'orderedList'],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom rules\n * <DragHandle\n * editor={editor}\n * nested={{\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n */\n nested?: boolean | NestedOptions\n}\n\nexport const DragHandle = (props: DragHandleProps) => {\n const {\n className = 'drag-handle',\n children,\n editor,\n pluginKey = dragHandlePluginDefaultKey,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n computePositionConfig = defaultComputePositionConfig,\n nested = false,\n } = props\n const [element] = useState<HTMLDivElement | null>(() => {\n if (typeof document === 'undefined') {\n return null\n }\n\n return document.createElement('div')\n })\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const nestedOptions = useMemo(() => normalizeNestedOptions(nested), [JSON.stringify(nested)])\n\n useEffect(() => {\n if (!element) {\n return\n }\n\n element.className = className\n element.style.visibility = 'hidden'\n element.style.position = 'absolute'\n element.dataset.dragging = 'false'\n }, [className, element])\n\n useEffect(() => {\n if (!element) {\n return\n }\n\n if (editor.isDestroyed) {\n return\n }\n\n const { plugin, unbind } = DragHandlePlugin({\n editor,\n element,\n pluginKey,\n computePositionConfig: {\n ...defaultComputePositionConfig,\n ...computePositionConfig,\n },\n onElementDragStart,\n onElementDragEnd,\n onNodeChange,\n nestedOptions,\n })\n\n editor.registerPlugin(plugin)\n\n return () => {\n if (!editor.isDestroyed) {\n editor.unregisterPlugin(pluginKey)\n }\n unbind()\n }\n }, [\n element,\n editor,\n onNodeChange,\n pluginKey,\n computePositionConfig,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n ])\n\n if (!element) {\n return null\n }\n\n return createPortal(children, element)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mCAOO;AAGP,mBAA6D;AAC7D,uBAA6B;AAqDtB,IAAM,aAAa,CAAC,UAA2B;AACpD,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,wBAAwB;AAAA,IACxB,SAAS;AAAA,EACX,IAAI;AACJ,QAAM,CAAC,OAAO,QAAI,uBAAgC,MAAM;AACtD,QAAI,OAAO,aAAa,aAAa;AACnC,aAAO;AAAA,IACT;AAEA,WAAO,SAAS,cAAc,KAAK;AAAA,EACrC,CAAC;AAGD,QAAM,oBAAgB,sBAAQ,UAAM,qDAAuB,MAAM,GAAG,CAAC,KAAK,UAAU,MAAM,CAAC,CAAC;AAE5F,8BAAU,MAAM;AACd,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,YAAQ,YAAY;AACpB,YAAQ,MAAM,aAAa;AAC3B,YAAQ,MAAM,WAAW;AACzB,YAAQ,QAAQ,WAAW;AAAA,EAC7B,GAAG,CAAC,WAAW,OAAO,CAAC;AAEvB,8BAAU,MAAM;AACd,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,QAAI,OAAO,aAAa;AACtB;AAAA,IACF;AAEA,UAAM,EAAE,QAAQ,OAAO,QAAI,+CAAiB;AAAA,MAC1C;AAAA,MACA;AAAA,MACA;AAAA,MACA,uBAAuB;AAAA,QACrB,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,eAAe,MAAM;AAE5B,WAAO,MAAM;AACX,UAAI,CAAC,OAAO,aAAa;AACvB,eAAO,iBAAiB,SAAS;AAAA,MACnC;AACA,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,aAAO,+BAAa,UAAU,OAAO;AACvC;;;AD7IA,IAAO,gBAAQ;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
2
3
  import { DragHandlePluginProps, NestedOptions } from '@tiptap/extension-drag-handle';
3
4
  import { Node } from '@tiptap/pm/model';
4
5
  import { Editor } from '@tiptap/react';
5
- import { ReactNode } from 'react';
6
6
 
7
7
  type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
8
8
  type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'element' | 'nestedOptions'> & {
@@ -56,6 +56,6 @@ type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'eleme
56
56
  */
57
57
  nested?: boolean | NestedOptions;
58
58
  };
59
- declare const DragHandle: (props: DragHandleProps) => react_jsx_runtime.JSX.Element;
59
+ declare const DragHandle: (props: DragHandleProps) => react.ReactPortal | null;
60
60
 
61
61
  export { DragHandle, type DragHandleProps, DragHandle as default };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
2
3
  import { DragHandlePluginProps, NestedOptions } from '@tiptap/extension-drag-handle';
3
4
  import { Node } from '@tiptap/pm/model';
4
5
  import { Editor } from '@tiptap/react';
5
- import { ReactNode } from 'react';
6
6
 
7
7
  type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
8
8
  type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'element' | 'nestedOptions'> & {
@@ -56,6 +56,6 @@ type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'eleme
56
56
  */
57
57
  nested?: boolean | NestedOptions;
58
58
  };
59
- declare const DragHandle: (props: DragHandleProps) => react_jsx_runtime.JSX.Element;
59
+ declare const DragHandle: (props: DragHandleProps) => react.ReactPortal | null;
60
60
 
61
61
  export { DragHandle, type DragHandleProps, DragHandle as default };
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  dragHandlePluginDefaultKey,
6
6
  normalizeNestedOptions
7
7
  } from "@tiptap/extension-drag-handle";
8
- import { useEffect, useMemo, useRef, useState } from "react";
9
- import { jsx } from "react/jsx-runtime";
8
+ import { useEffect, useMemo, useState } from "react";
9
+ import { createPortal } from "react-dom";
10
10
  var DragHandle = (props) => {
11
11
  const {
12
12
  className = "drag-handle",
@@ -19,45 +19,48 @@ var DragHandle = (props) => {
19
19
  computePositionConfig = defaultComputePositionConfig,
20
20
  nested = false
21
21
  } = props;
22
- const [element, setElement] = useState(null);
23
- const plugin = useRef(null);
22
+ const [element] = useState(() => {
23
+ if (typeof document === "undefined") {
24
+ return null;
25
+ }
26
+ return document.createElement("div");
27
+ });
24
28
  const nestedOptions = useMemo(() => normalizeNestedOptions(nested), [JSON.stringify(nested)]);
25
29
  useEffect(() => {
26
- let initPlugin = null;
27
30
  if (!element) {
28
- return () => {
29
- plugin.current = null;
30
- };
31
+ return;
31
32
  }
32
- if (editor.isDestroyed) {
33
- return () => {
34
- plugin.current = null;
35
- };
33
+ element.className = className;
34
+ element.style.visibility = "hidden";
35
+ element.style.position = "absolute";
36
+ element.dataset.dragging = "false";
37
+ }, [className, element]);
38
+ useEffect(() => {
39
+ if (!element) {
40
+ return;
36
41
  }
37
- if (!plugin.current) {
38
- initPlugin = DragHandlePlugin({
39
- editor,
40
- element,
41
- pluginKey,
42
- computePositionConfig: {
43
- ...defaultComputePositionConfig,
44
- ...computePositionConfig
45
- },
46
- onElementDragStart,
47
- onElementDragEnd,
48
- onNodeChange,
49
- nestedOptions
50
- });
51
- plugin.current = initPlugin.plugin;
52
- editor.registerPlugin(plugin.current);
42
+ if (editor.isDestroyed) {
43
+ return;
53
44
  }
45
+ const { plugin, unbind } = DragHandlePlugin({
46
+ editor,
47
+ element,
48
+ pluginKey,
49
+ computePositionConfig: {
50
+ ...defaultComputePositionConfig,
51
+ ...computePositionConfig
52
+ },
53
+ onElementDragStart,
54
+ onElementDragEnd,
55
+ onNodeChange,
56
+ nestedOptions
57
+ });
58
+ editor.registerPlugin(plugin);
54
59
  return () => {
55
- editor.unregisterPlugin(pluginKey);
56
- plugin.current = null;
57
- if (initPlugin) {
58
- initPlugin.unbind();
59
- initPlugin = null;
60
+ if (!editor.isDestroyed) {
61
+ editor.unregisterPlugin(pluginKey);
60
62
  }
63
+ unbind();
61
64
  };
62
65
  }, [
63
66
  element,
@@ -69,16 +72,10 @@ var DragHandle = (props) => {
69
72
  onElementDragEnd,
70
73
  nestedOptions
71
74
  ]);
72
- return /* @__PURE__ */ jsx(
73
- "div",
74
- {
75
- className,
76
- style: { visibility: "hidden", position: "absolute" },
77
- "data-dragging": "false",
78
- ref: setElement,
79
- children
80
- }
81
- );
75
+ if (!element) {
76
+ return null;
77
+ }
78
+ return createPortal(children, element);
82
79
  };
83
80
 
84
81
  // src/index.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/DragHandle.tsx","../src/index.ts"],"sourcesContent":["import {\n type DragHandlePluginProps,\n type NestedOptions,\n defaultComputePositionConfig,\n DragHandlePlugin,\n dragHandlePluginDefaultKey,\n normalizeNestedOptions,\n} from '@tiptap/extension-drag-handle'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Plugin } from '@tiptap/pm/state'\nimport type { Editor } from '@tiptap/react'\nimport { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'element' | 'nestedOptions'> & {\n className?: string\n onNodeChange?: (data: { node: Node | null; editor: Editor; pos: number }) => void\n children: ReactNode\n\n /**\n * Enable drag handles for nested content (list items, blockquotes, etc.).\n *\n * When enabled, the drag handle will appear for nested blocks, not just\n * top-level blocks. A rule-based scoring system determines which node\n * to target based on cursor position and configured rules.\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * <DragHandle editor={editor} nested>\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom configuration\n * <DragHandle\n * editor={editor}\n * nested={{\n * edgeDetection: 'left',\n * allowedContainers: ['bulletList', 'orderedList'],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom rules\n * <DragHandle\n * editor={editor}\n * nested={{\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n */\n nested?: boolean | NestedOptions\n}\n\nexport const DragHandle = (props: DragHandleProps) => {\n const {\n className = 'drag-handle',\n children,\n editor,\n pluginKey = dragHandlePluginDefaultKey,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n computePositionConfig = defaultComputePositionConfig,\n nested = false,\n } = props\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n const plugin = useRef<Plugin | null>(null)\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const nestedOptions = useMemo(() => normalizeNestedOptions(nested), [JSON.stringify(nested)])\n\n useEffect(() => {\n let initPlugin: {\n plugin: Plugin\n unbind: () => void\n } | null = null\n\n if (!element) {\n return () => {\n plugin.current = null\n }\n }\n\n if (editor.isDestroyed) {\n return () => {\n plugin.current = null\n }\n }\n\n if (!plugin.current) {\n initPlugin = DragHandlePlugin({\n editor,\n element,\n pluginKey,\n computePositionConfig: {\n ...defaultComputePositionConfig,\n ...computePositionConfig,\n },\n onElementDragStart,\n onElementDragEnd,\n onNodeChange,\n nestedOptions,\n })\n plugin.current = initPlugin.plugin\n\n editor.registerPlugin(plugin.current)\n }\n\n return () => {\n editor.unregisterPlugin(pluginKey)\n plugin.current = null\n if (initPlugin) {\n initPlugin.unbind()\n initPlugin = null\n }\n }\n }, [\n element,\n editor,\n onNodeChange,\n pluginKey,\n computePositionConfig,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n ])\n\n return (\n <div\n className={className}\n style={{ visibility: 'hidden', position: 'absolute' }}\n data-dragging=\"false\"\n ref={setElement}\n >\n {children}\n </div>\n )\n}\n","import { DragHandle } from './DragHandle.js'\n\nexport * from './DragHandle.js'\n\nexport default DragHandle\n"],"mappings":";AAAA;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,SAAyB,WAAW,SAAS,QAAQ,gBAAgB;AAgIjE;AA3EG,IAAM,aAAa,CAAC,UAA2B;AACpD,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,wBAAwB;AAAA,IACxB,SAAS;AAAA,EACX,IAAI;AACJ,QAAM,CAAC,SAAS,UAAU,IAAI,SAAgC,IAAI;AAClE,QAAM,SAAS,OAAsB,IAAI;AAGzC,QAAM,gBAAgB,QAAQ,MAAM,uBAAuB,MAAM,GAAG,CAAC,KAAK,UAAU,MAAM,CAAC,CAAC;AAE5F,YAAU,MAAM;AACd,QAAI,aAGO;AAEX,QAAI,CAAC,SAAS;AACZ,aAAO,MAAM;AACX,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAEA,QAAI,OAAO,aAAa;AACtB,aAAO,MAAM;AACX,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,mBAAa,iBAAiB;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,QACA,uBAAuB;AAAA,UACrB,GAAG;AAAA,UACH,GAAG;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,UAAU,WAAW;AAE5B,aAAO,eAAe,OAAO,OAAO;AAAA,IACtC;AAEA,WAAO,MAAM;AACX,aAAO,iBAAiB,SAAS;AACjC,aAAO,UAAU;AACjB,UAAI,YAAY;AACd,mBAAW,OAAO;AAClB,qBAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO,EAAE,YAAY,UAAU,UAAU,WAAW;AAAA,MACpD,iBAAc;AAAA,MACd,KAAK;AAAA,MAEJ;AAAA;AAAA,EACH;AAEJ;;;AChJA,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/DragHandle.tsx","../src/index.ts"],"sourcesContent":["import {\n type DragHandlePluginProps,\n type NestedOptions,\n defaultComputePositionConfig,\n DragHandlePlugin,\n dragHandlePluginDefaultKey,\n normalizeNestedOptions,\n} from '@tiptap/extension-drag-handle'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Editor } from '@tiptap/react'\nimport { type ReactNode, useEffect, useMemo, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type DragHandleProps = Omit<Optional<DragHandlePluginProps, 'pluginKey'>, 'element' | 'nestedOptions'> & {\n className?: string\n onNodeChange?: (data: { node: Node | null; editor: Editor; pos: number }) => void\n children: ReactNode\n\n /**\n * Enable drag handles for nested content (list items, blockquotes, etc.).\n *\n * When enabled, the drag handle will appear for nested blocks, not just\n * top-level blocks. A rule-based scoring system determines which node\n * to target based on cursor position and configured rules.\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * <DragHandle editor={editor} nested>\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom configuration\n * <DragHandle\n * editor={editor}\n * nested={{\n * edgeDetection: 'left',\n * allowedContainers: ['bulletList', 'orderedList'],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n *\n * @example\n * // With custom rules\n * <DragHandle\n * editor={editor}\n * nested={{\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * }}\n * >\n * <GripIcon />\n * </DragHandle>\n */\n nested?: boolean | NestedOptions\n}\n\nexport const DragHandle = (props: DragHandleProps) => {\n const {\n className = 'drag-handle',\n children,\n editor,\n pluginKey = dragHandlePluginDefaultKey,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n computePositionConfig = defaultComputePositionConfig,\n nested = false,\n } = props\n const [element] = useState<HTMLDivElement | null>(() => {\n if (typeof document === 'undefined') {\n return null\n }\n\n return document.createElement('div')\n })\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const nestedOptions = useMemo(() => normalizeNestedOptions(nested), [JSON.stringify(nested)])\n\n useEffect(() => {\n if (!element) {\n return\n }\n\n element.className = className\n element.style.visibility = 'hidden'\n element.style.position = 'absolute'\n element.dataset.dragging = 'false'\n }, [className, element])\n\n useEffect(() => {\n if (!element) {\n return\n }\n\n if (editor.isDestroyed) {\n return\n }\n\n const { plugin, unbind } = DragHandlePlugin({\n editor,\n element,\n pluginKey,\n computePositionConfig: {\n ...defaultComputePositionConfig,\n ...computePositionConfig,\n },\n onElementDragStart,\n onElementDragEnd,\n onNodeChange,\n nestedOptions,\n })\n\n editor.registerPlugin(plugin)\n\n return () => {\n if (!editor.isDestroyed) {\n editor.unregisterPlugin(pluginKey)\n }\n unbind()\n }\n }, [\n element,\n editor,\n onNodeChange,\n pluginKey,\n computePositionConfig,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n ])\n\n if (!element) {\n return null\n }\n\n return createPortal(children, element)\n}\n","import { DragHandle } from './DragHandle.js'\n\nexport * from './DragHandle.js'\n\nexport default DragHandle\n"],"mappings":";AAAA;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAyB,WAAW,SAAS,gBAAgB;AAC7D,SAAS,oBAAoB;AAqDtB,IAAM,aAAa,CAAC,UAA2B;AACpD,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,wBAAwB;AAAA,IACxB,SAAS;AAAA,EACX,IAAI;AACJ,QAAM,CAAC,OAAO,IAAI,SAAgC,MAAM;AACtD,QAAI,OAAO,aAAa,aAAa;AACnC,aAAO;AAAA,IACT;AAEA,WAAO,SAAS,cAAc,KAAK;AAAA,EACrC,CAAC;AAGD,QAAM,gBAAgB,QAAQ,MAAM,uBAAuB,MAAM,GAAG,CAAC,KAAK,UAAU,MAAM,CAAC,CAAC;AAE5F,YAAU,MAAM;AACd,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,YAAQ,YAAY;AACpB,YAAQ,MAAM,aAAa;AAC3B,YAAQ,MAAM,WAAW;AACzB,YAAQ,QAAQ,WAAW;AAAA,EAC7B,GAAG,CAAC,WAAW,OAAO,CAAC;AAEvB,YAAU,MAAM;AACd,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,QAAI,OAAO,aAAa;AACtB;AAAA,IACF;AAEA,UAAM,EAAE,QAAQ,OAAO,IAAI,iBAAiB;AAAA,MAC1C;AAAA,MACA;AAAA,MACA;AAAA,MACA,uBAAuB;AAAA,QACrB,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,eAAe,MAAM;AAE5B,WAAO,MAAM;AACX,UAAI,CAAC,OAAO,aAAa;AACvB,eAAO,iBAAiB,SAAS;AAAA,MACnC;AACA,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,SAAO,aAAa,UAAU,OAAO;AACvC;;;AC7IA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/extension-drag-handle-react",
3
3
  "description": "drag handle extension for tiptap with react",
4
- "version": "3.22.5",
4
+ "version": "3.23.1",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -38,18 +38,18 @@
38
38
  "peerDependencies": {
39
39
  "react": "^16.8 || ^17 || ^18 || ^19",
40
40
  "react-dom": "^16.8 || ^17 || ^18 || ^19",
41
- "@tiptap/extension-drag-handle": "3.22.5",
42
- "@tiptap/pm": "3.22.5",
43
- "@tiptap/react": "3.22.5"
41
+ "@tiptap/extension-drag-handle": "3.23.1",
42
+ "@tiptap/pm": "3.23.1",
43
+ "@tiptap/react": "3.23.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/react": "^18.0.0",
47
47
  "@types/react-dom": "^18.0.0",
48
48
  "react": "^18.0.0",
49
49
  "react-dom": "^18.0.0",
50
- "@tiptap/extension-drag-handle": "^3.22.5",
51
- "@tiptap/pm": "^3.22.5",
52
- "@tiptap/react": "^3.22.5"
50
+ "@tiptap/extension-drag-handle": "^3.23.1",
51
+ "@tiptap/pm": "^3.23.1",
52
+ "@tiptap/react": "^3.23.1"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsup",
@@ -7,9 +7,9 @@ import {
7
7
  normalizeNestedOptions,
8
8
  } from '@tiptap/extension-drag-handle'
9
9
  import type { Node } from '@tiptap/pm/model'
10
- import type { Plugin } from '@tiptap/pm/state'
11
10
  import type { Editor } from '@tiptap/react'
12
- import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
11
+ import { type ReactNode, useEffect, useMemo, useState } from 'react'
12
+ import { createPortal } from 'react-dom'
13
13
 
14
14
  type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
15
15
 
@@ -74,56 +74,58 @@ export const DragHandle = (props: DragHandleProps) => {
74
74
  computePositionConfig = defaultComputePositionConfig,
75
75
  nested = false,
76
76
  } = props
77
- const [element, setElement] = useState<HTMLDivElement | null>(null)
78
- const plugin = useRef<Plugin | null>(null)
77
+ const [element] = useState<HTMLDivElement | null>(() => {
78
+ if (typeof document === 'undefined') {
79
+ return null
80
+ }
81
+
82
+ return document.createElement('div')
83
+ })
79
84
 
80
85
  // eslint-disable-next-line react-hooks/exhaustive-deps
81
86
  const nestedOptions = useMemo(() => normalizeNestedOptions(nested), [JSON.stringify(nested)])
82
87
 
83
88
  useEffect(() => {
84
- let initPlugin: {
85
- plugin: Plugin
86
- unbind: () => void
87
- } | null = null
89
+ if (!element) {
90
+ return
91
+ }
92
+
93
+ element.className = className
94
+ element.style.visibility = 'hidden'
95
+ element.style.position = 'absolute'
96
+ element.dataset.dragging = 'false'
97
+ }, [className, element])
88
98
 
99
+ useEffect(() => {
89
100
  if (!element) {
90
- return () => {
91
- plugin.current = null
92
- }
101
+ return
93
102
  }
94
103
 
95
104
  if (editor.isDestroyed) {
96
- return () => {
97
- plugin.current = null
98
- }
105
+ return
99
106
  }
100
107
 
101
- if (!plugin.current) {
102
- initPlugin = DragHandlePlugin({
103
- editor,
104
- element,
105
- pluginKey,
106
- computePositionConfig: {
107
- ...defaultComputePositionConfig,
108
- ...computePositionConfig,
109
- },
110
- onElementDragStart,
111
- onElementDragEnd,
112
- onNodeChange,
113
- nestedOptions,
114
- })
115
- plugin.current = initPlugin.plugin
108
+ const { plugin, unbind } = DragHandlePlugin({
109
+ editor,
110
+ element,
111
+ pluginKey,
112
+ computePositionConfig: {
113
+ ...defaultComputePositionConfig,
114
+ ...computePositionConfig,
115
+ },
116
+ onElementDragStart,
117
+ onElementDragEnd,
118
+ onNodeChange,
119
+ nestedOptions,
120
+ })
116
121
 
117
- editor.registerPlugin(plugin.current)
118
- }
122
+ editor.registerPlugin(plugin)
119
123
 
120
124
  return () => {
121
- editor.unregisterPlugin(pluginKey)
122
- plugin.current = null
123
- if (initPlugin) {
124
- initPlugin.unbind()
125
- initPlugin = null
125
+ if (!editor.isDestroyed) {
126
+ editor.unregisterPlugin(pluginKey)
126
127
  }
128
+ unbind()
127
129
  }
128
130
  }, [
129
131
  element,
@@ -136,14 +138,9 @@ export const DragHandle = (props: DragHandleProps) => {
136
138
  nestedOptions,
137
139
  ])
138
140
 
139
- return (
140
- <div
141
- className={className}
142
- style={{ visibility: 'hidden', position: 'absolute' }}
143
- data-dragging="false"
144
- ref={setElement}
145
- >
146
- {children}
147
- </div>
148
- )
141
+ if (!element) {
142
+ return null
143
+ }
144
+
145
+ return createPortal(children, element)
149
146
  }