@tiptap/extension-drag-handle 3.18.0 → 3.20.0

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
@@ -88,6 +88,19 @@ var listWrapperDeprioritize = {
88
88
  return 0;
89
89
  }
90
90
  };
91
+ var tableStructure = {
92
+ id: "tableStructure",
93
+ evaluate: ({ node, parent }) => {
94
+ const tableStructureTypes = ["tableRow", "tableCell", "tableHeader"];
95
+ if (tableStructureTypes.includes(node.type.name)) {
96
+ return 1e3;
97
+ }
98
+ if (parent && parent.type.name === "tableHeader") {
99
+ return 1e3;
100
+ }
101
+ return 0;
102
+ }
103
+ };
91
104
  var inlineContent = {
92
105
  id: "inlineContent",
93
106
  evaluate: ({ node }) => {
@@ -97,7 +110,12 @@ var inlineContent = {
97
110
  return 0;
98
111
  }
99
112
  };
100
- var defaultRules = [listItemFirstChild, listWrapperDeprioritize, inlineContent];
113
+ var defaultRules = [
114
+ listItemFirstChild,
115
+ listWrapperDeprioritize,
116
+ tableStructure,
117
+ inlineContent
118
+ ];
101
119
 
102
120
  // src/helpers/edgeDetection.ts
103
121
  var DEFAULT_EDGE_CONFIG = {
@@ -228,6 +246,38 @@ function findBestDragTarget(view, coords, options) {
228
246
  const dom = view.nodeDOM(nodePos);
229
247
  return { node, pos: nodePos, depth, score, dom };
230
248
  }).filter((candidate) => candidate !== null);
249
+ const nodeAfter = $pos.nodeAfter;
250
+ if (nodeAfter && nodeAfter.isAtom && !nodeAfter.isInline) {
251
+ const nodePos = posInfo.pos;
252
+ const depth = $pos.depth + 1;
253
+ const parent = $pos.parent;
254
+ const index = $pos.index();
255
+ const siblingCount = parent.childCount;
256
+ let inAllowedContainer = true;
257
+ if (options.allowedContainers) {
258
+ inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers);
259
+ }
260
+ if (inAllowedContainer) {
261
+ const context = {
262
+ node: nodeAfter,
263
+ pos: nodePos,
264
+ depth,
265
+ parent,
266
+ index,
267
+ isFirst: index === 0,
268
+ isLast: index === siblingCount - 1,
269
+ $pos,
270
+ view
271
+ };
272
+ const score = calculateScore(context, rules, options.edgeDetection, coords);
273
+ if (score >= 0) {
274
+ const dom = view.nodeDOM(nodePos);
275
+ if (dom) {
276
+ candidates.push({ node: nodeAfter, pos: nodePos, depth, score, dom });
277
+ }
278
+ }
279
+ }
280
+ }
231
281
  if (candidates.length === 0) {
232
282
  return null;
233
283
  }
@@ -376,8 +426,9 @@ function getDragHandleRanges(event, editor, nestedOptions, dragContext) {
376
426
  if (!result.resultNode || result.pos === null) {
377
427
  return [];
378
428
  }
429
+ const offset = result.resultNode.isText || result.resultNode.isAtom ? 0 : -1;
379
430
  const $from = doc.resolve(result.pos);
380
- const $to = doc.resolve(result.pos + result.resultNode.nodeSize);
431
+ const $to = doc.resolve(result.pos + result.resultNode.nodeSize + offset);
381
432
  return (0, import_extension_node_range.getSelectionRanges)($from, $to, 0);
382
433
  }
383
434
  function dragHandler(event, editor, nestedOptions, dragContext) {
@@ -421,7 +472,8 @@ function dragHandler(event, editor, nestedOptions, dragContext) {
421
472
  document.body.append(wrapper);
422
473
  event.dataTransfer.clearData();
423
474
  event.dataTransfer.setDragImage(wrapper, 0, 0);
424
- view.dragging = { slice, move: true };
475
+ const nodeSelection = selection instanceof import_state.NodeSelection ? selection : void 0;
476
+ view.dragging = { slice, move: true, node: nodeSelection };
425
477
  tr.setSelection(selection);
426
478
  view.dispatch(tr);
427
479
  document.addEventListener("drop", () => removeNode(wrapper), { once: true });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/drag-handle.ts","../src/drag-handle-plugin.ts","../src/helpers/dragHandler.ts","../src/helpers/cloneElement.ts","../src/helpers/defaultRules.ts","../src/helpers/edgeDetection.ts","../src/helpers/scoring.ts","../src/helpers/findBestDragTarget.ts","../src/helpers/findNextElementFromCursor.ts","../src/helpers/removeNode.ts","../src/helpers/getOuterNode.ts","../src/helpers/normalizeOptions.ts"],"sourcesContent":["import { DragHandle } from './drag-handle.js'\n\nexport * from './drag-handle.js'\nexport * from './drag-handle-plugin.js'\nexport { defaultRules } from './helpers/defaultRules.js'\nexport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nexport type {\n EdgeDetectionConfig,\n EdgeDetectionPreset,\n NestedOptions,\n NormalizedNestedOptions,\n} from './types/options.js'\nexport type { DragHandleRule, RuleContext } from './types/rules.js'\n\nexport default DragHandle\n","import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'\nimport { type Editor, Extension } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\n\nimport { DragHandlePlugin } from './drag-handle-plugin.js'\nimport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nimport type { NestedOptions } from './types/options.js'\n\nexport const defaultComputePositionConfig: ComputePositionConfig = {\n placement: 'left-start',\n strategy: 'absolute',\n}\n\nexport interface DragHandleOptions {\n /**\n * Renders an element that is positioned with the floating-ui/dom package\n */\n render(): HTMLElement\n /**\n * Configuration for position computation of the drag handle\n * using the floating-ui/dom package\n */\n computePositionConfig?: ComputePositionConfig\n /**\n * A function that returns the virtual element for the drag handle.\n * This is useful when the menu needs to be positioned relative to a specific DOM element.\n */\n getReferencedVirtualElement?: () => VirtualElement | null\n /**\n * Locks the draghandle in place and visibility\n */\n locked?: boolean\n /**\n * Returns a node or null when a node is hovered over\n */\n onNodeChange?: (options: { node: Node | null; editor: Editor }) => void\n /**\n * The callback function that will be called when drag start.\n */\n onElementDragStart?: (e: DragEvent) => void\n /**\n * The callback function that will be called when drag end.\n */\n onElementDragEnd?: (e: DragEvent) => void\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 * **Values:**\n * - `false` (default): Only root-level blocks show drag handles\n * - `true`: Enable with sensible defaults (left edge detection, default rules)\n * - `NestedOptions`: Enable with custom configuration\n *\n * **Configuration options:**\n * - `rules`: Custom rules to determine which nodes are draggable\n * - `defaultRules`: Whether to include default rules (default: true)\n * - `allowedContainers`: Restrict nested dragging to specific container types\n * - `edgeDetection`: Control when to prefer parent over nested node\n * - `'left'` (default): Prefer parent near left/top edges\n * - `'right'`: Prefer parent near right/top edges (for RTL)\n * - `'both'`: Prefer parent near any horizontal edge\n * - `'none'`: Disable edge detection\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * DragHandle.configure({\n * nested: true,\n * })\n *\n * @example\n * // Restrict to specific containers\n * DragHandle.configure({\n * nested: {\n * allowedContainers: ['bulletList', 'orderedList'],\n * },\n * })\n *\n * @example\n * // With custom rules\n * DragHandle.configure({\n * nested: {\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * edgeDetection: 'none',\n * },\n * })\n */\n nested?: boolean | NestedOptions\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n dragHandle: {\n /**\n * Locks the draghandle in place and visibility\n */\n lockDragHandle: () => ReturnType\n /**\n * Unlocks the draghandle\n */\n unlockDragHandle: () => ReturnType\n /**\n * Toggle draghandle lock state\n */\n toggleDragHandle: () => ReturnType\n }\n }\n}\n\nexport const DragHandle = Extension.create<DragHandleOptions>({\n name: 'dragHandle',\n\n addOptions() {\n return {\n render() {\n const element = document.createElement('div')\n\n element.classList.add('drag-handle')\n\n return element\n },\n computePositionConfig: {},\n locked: false,\n onNodeChange: () => {\n return null\n },\n onElementDragStart: undefined,\n onElementDragEnd: undefined,\n nested: false,\n }\n },\n\n addCommands() {\n return {\n lockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = true\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n unlockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = false\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n toggleDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = !this.options.locked\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n }\n },\n\n addProseMirrorPlugins() {\n const element = this.options.render()\n const nestedOptions = normalizeNestedOptions(this.options.nested)\n\n return [\n DragHandlePlugin({\n computePositionConfig: { ...defaultComputePositionConfig, ...this.options.computePositionConfig },\n getReferencedVirtualElement: this.options.getReferencedVirtualElement,\n element,\n editor: this.editor,\n onNodeChange: this.options.onNodeChange,\n onElementDragStart: this.options.onElementDragStart,\n onElementDragEnd: this.options.onElementDragEnd,\n nestedOptions,\n }).plugin,\n ]\n },\n})\n","import { type ComputePositionConfig, type VirtualElement, computePosition } from '@floating-ui/dom'\nimport { type Editor, isFirefox } from '@tiptap/core'\nimport { isChangeOrigin } from '@tiptap/extension-collaboration'\nimport type { Node } from '@tiptap/pm/model'\nimport { type EditorState, type Transaction, Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport {\n absolutePositionToRelativePosition,\n relativePositionToAbsolutePosition,\n ySyncPluginKey,\n} from '@tiptap/y-tiptap'\n\nimport { dragHandler } from './helpers/dragHandler.js'\nimport { findElementNextToCoords } from './helpers/findNextElementFromCursor.js'\nimport { getOuterNode, getOuterNodePos } from './helpers/getOuterNode.js'\nimport { removeNode } from './helpers/removeNode.js'\nimport type { NormalizedNestedOptions } from './types/options.js'\n\ntype PluginState = {\n locked: boolean\n}\n\nconst getRelativePos = (state: EditorState, absolutePos: number) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return null\n }\n\n return absolutePositionToRelativePosition(absolutePos, ystate.type, ystate.binding.mapping)\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: y-prosemirror (and y-tiptap by extension) does not have types for relative positions\nconst getAbsolutePos = (state: EditorState, relativePos: any) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return -1\n }\n\n return relativePositionToAbsolutePosition(ystate.doc, ystate.type, relativePos, ystate.binding.mapping) || 0\n}\n\nconst getOuterDomNode = (view: EditorView, domNode: HTMLElement) => {\n let tmpDomNode = domNode\n\n // Traverse to top level node.\n while (tmpDomNode?.parentNode) {\n if (tmpDomNode.parentNode === view.dom) {\n break\n }\n\n tmpDomNode = tmpDomNode.parentNode as HTMLElement\n }\n\n return tmpDomNode\n}\n\nexport interface DragHandlePluginProps {\n pluginKey?: PluginKey | string\n editor: Editor\n element: HTMLElement\n onNodeChange?: (data: { editor: Editor; node: Node | null; pos: number }) => void\n onElementDragStart?: (e: DragEvent) => void\n onElementDragEnd?: (e: DragEvent) => void\n computePositionConfig?: ComputePositionConfig\n getReferencedVirtualElement?: () => VirtualElement | null\n nestedOptions: NormalizedNestedOptions\n}\n\nexport const dragHandlePluginDefaultKey = new PluginKey('dragHandle')\n\nexport const DragHandlePlugin = ({\n pluginKey = dragHandlePluginDefaultKey,\n element,\n editor,\n computePositionConfig,\n getReferencedVirtualElement,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n}: DragHandlePluginProps) => {\n const wrapper = document.createElement('div')\n let locked = false\n let currentNode: Node | null = null\n let currentNodePos = -1\n // biome-ignore lint/suspicious/noExplicitAny: See above - relative positions in y-prosemirror are not typed\n let currentNodeRelPos: any\n let rafId: number | null = null\n let pendingMouseCoords: { x: number; y: number } | null = null\n\n function hideHandle() {\n if (!element) {\n return\n }\n\n element.style.visibility = 'hidden'\n element.style.pointerEvents = 'none'\n }\n\n function showHandle() {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n element.style.visibility = ''\n element.style.pointerEvents = 'auto'\n }\n\n function repositionDragHandle(dom: Element) {\n const virtualElement = getReferencedVirtualElement?.() || {\n getBoundingClientRect: () => dom.getBoundingClientRect(),\n }\n\n computePosition(virtualElement, element, computePositionConfig).then(val => {\n Object.assign(element.style, {\n position: val.strategy,\n left: `${val.x}px`,\n top: `${val.y}px`,\n })\n })\n }\n\n function onDragStart(e: DragEvent) {\n onElementDragStart?.(e)\n // Push this to the end of the event cue\n // Fixes bug where incorrect drag pos is returned if drag handle has position: absolute\n // Pass the current node context to avoid recalculation issues during drag start\n dragHandler(e, editor, nestedOptions, { node: currentNode, pos: currentNodePos })\n\n if (element) {\n element.dataset.dragging = 'true'\n }\n\n setTimeout(() => {\n if (element) {\n element.style.pointerEvents = 'none'\n }\n }, 0)\n }\n\n function onDragEnd(e: DragEvent) {\n onElementDragEnd?.(e)\n hideHandle()\n if (element) {\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n }\n }\n\n function onDrop() {\n // Firefox has a bug where the caret becomes invisible after drag and drop.\n // This workaround forces Firefox to re-render the caret by toggling contentEditable.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1327834\n if (isFirefox()) {\n const editorElement = editor.view.dom\n\n // Use requestAnimationFrame to ensure the drop operation has completed\n requestAnimationFrame(() => {\n if (editorElement.isContentEditable) {\n editorElement.contentEditable = 'false'\n editorElement.contentEditable = 'true'\n }\n })\n }\n }\n\n element.addEventListener('dragstart', onDragStart)\n element.addEventListener('dragend', onDragEnd)\n document.addEventListener('drop', onDrop)\n\n wrapper.appendChild(element)\n\n return {\n unbind() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n },\n plugin: new Plugin({\n key: typeof pluginKey === 'string' ? new PluginKey(pluginKey) : pluginKey,\n\n state: {\n init() {\n return { locked: false }\n },\n apply(tr: Transaction, value: PluginState, _oldState: EditorState, state: EditorState) {\n const isLocked = tr.getMeta('lockDragHandle')\n const hideDragHandle = tr.getMeta('hideDragHandle')\n\n if (isLocked !== undefined) {\n locked = isLocked\n }\n\n if (hideDragHandle) {\n hideHandle()\n\n locked = false\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n return value\n }\n\n // Something has changed and drag handler is visible…\n if (tr.docChanged && currentNodePos !== -1 && element) {\n // Yjs replaces the entire document on every incoming change and needs a special handling.\n // If change comes from another user …\n if (isChangeOrigin(tr)) {\n // https://discuss.yjs.dev/t/y-prosemirror-mapping-a-single-relative-position-when-doc-changes/851/3\n const newPos = getAbsolutePos(state, currentNodeRelPos)\n\n if (newPos !== currentNodePos) {\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // We will get the outer node with data and position in views update method.\n }\n } else {\n // … otherwise use ProseMirror mapping to update the position.\n const newPos = tr.mapping.map(currentNodePos)\n\n if (newPos !== currentNodePos) {\n // TODO: Remove\n // console.log('Position has changed …', { old: currentNodePos, new: newPos }, tr);\n\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(state, currentNodePos)\n\n // We will get the outer node with data and position in views update method.\n }\n }\n }\n\n return value\n },\n },\n\n view: view => {\n element.draggable = true\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n\n editor.view.dom.parentElement?.appendChild(wrapper)\n\n wrapper.style.pointerEvents = 'none'\n wrapper.style.position = 'absolute'\n wrapper.style.top = '0'\n wrapper.style.left = '0'\n\n return {\n update(_, oldState) {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n // Prevent element being draggend while being open.\n if (locked) {\n element.draggable = false\n } else {\n element.draggable = true\n }\n\n // Recalculate popup position if doc has changend and drag handler is visible.\n if (view.state.doc.eq(oldState.doc) || currentNodePos === -1) {\n return\n }\n\n // Get domNode from (new) position.\n let domNode = view.nodeDOM(currentNodePos) as HTMLElement\n\n // Since old element could have been wrapped, we need to find\n // the outer node and take its position and node data.\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n const outerNode = getOuterNode(editor.state.doc, domNodePos)\n const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos) // TODO: needed?\n\n currentNode = outerNode\n currentNodePos = outerNodePos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n repositionDragHandle(domNode as Element)\n },\n\n // TODO: Kills even on hot reload\n destroy() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n\n if (element) {\n removeNode(wrapper)\n }\n },\n }\n },\n\n props: {\n handleDOMEvents: {\n keydown(view) {\n if (!element || locked) {\n return false\n }\n\n if (view.hasFocus()) {\n hideHandle()\n currentNode = null\n currentNodePos = -1\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n // We want to still continue with other keydown events.\n return false\n }\n\n return false\n },\n mouseleave(_view, e) {\n // Do not hide open popup on mouseleave.\n if (locked) {\n return false\n }\n\n // If e.target is not inside the wrapper, hide.\n if (e.target && !wrapper.contains(e.relatedTarget as HTMLElement)) {\n hideHandle()\n\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n }\n\n return false\n },\n\n mousemove(view, e) {\n // Do not continue if popup is not initialized or open.\n if (!element || locked) {\n return false\n }\n\n // Store latest mouse coords and schedule a single RAF per frame\n pendingMouseCoords = { x: e.clientX, y: e.clientY }\n\n if (rafId) {\n return false\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null\n\n if (!pendingMouseCoords) {\n return\n }\n\n const { x, y } = pendingMouseCoords\n pendingMouseCoords = null\n\n const nodeData = findElementNextToCoords({\n x,\n y,\n direction: 'right',\n editor,\n nestedOptions,\n })\n\n // Skip if there is no node next to coords\n if (!nodeData.resultElement) {\n return\n }\n\n let domNode = nodeData.resultElement as HTMLElement\n let targetNode = nodeData.resultNode\n let targetPos = nodeData.pos\n\n // In nested mode, the node data already contains the correct target\n // In non-nested mode, traverse to the top-level block\n if (!nestedOptions?.enabled) {\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n\n targetNode = getOuterNode(editor.state.doc, domNodePos)\n targetPos = getOuterNodePos(editor.state.doc, domNodePos)\n }\n\n if (targetNode !== currentNode) {\n currentNode = targetNode\n currentNodePos = targetPos ?? -1\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n // Set nodes clientRect.\n repositionDragHandle(domNode as Element)\n\n showHandle()\n }\n })\n\n return false\n },\n },\n },\n }),\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport { getSelectionRanges, NodeRangeSelection } from '@tiptap/extension-node-range'\nimport type { Node } from '@tiptap/pm/model'\nimport { type SelectionRange, NodeSelection } from '@tiptap/pm/state'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { cloneElement } from './cloneElement.js'\nimport { findElementNextToCoords } from './findNextElementFromCursor.js'\nimport { removeNode } from './removeNode.js'\n\nexport interface DragContext {\n node: Node | null\n pos: number\n}\n\nfunction getDragHandleRanges(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n): SelectionRange[] {\n const { doc } = editor.view.state\n\n // In nested mode with known context, use the pre-calculated position\n // This prevents recalculation issues when mouse position shifts during drag start\n if (nestedOptions?.enabled && dragContext?.node && dragContext.pos >= 0) {\n const nodeStart = dragContext.pos\n const nodeEnd = dragContext.pos + dragContext.node.nodeSize\n\n return [\n {\n $from: doc.resolve(nodeStart),\n $to: doc.resolve(nodeEnd),\n },\n ]\n }\n\n // Fallback: recalculate from mouse position (used in non-nested mode)\n const result = findElementNextToCoords({\n editor,\n x: event.clientX,\n y: event.clientY,\n direction: 'right',\n nestedOptions,\n })\n\n if (!result.resultNode || result.pos === null) {\n return []\n }\n\n // For non-nested mode, use depth 0 to select the outermost block\n const $from = doc.resolve(result.pos)\n const $to = doc.resolve(result.pos + result.resultNode.nodeSize)\n\n return getSelectionRanges($from, $to, 0)\n}\n\nexport function dragHandler(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n) {\n const { view } = editor\n\n if (!event.dataTransfer) {\n return\n }\n\n const { empty, $from, $to } = view.state.selection\n\n const dragHandleRanges = getDragHandleRanges(event, editor, nestedOptions, dragContext)\n\n const selectionRanges = getSelectionRanges($from, $to, 0)\n const isDragHandleWithinSelection = selectionRanges.some(range => {\n return dragHandleRanges.find(dragHandleRange => {\n return dragHandleRange.$from === range.$from && dragHandleRange.$to === range.$to\n })\n })\n\n const ranges = empty || !isDragHandleWithinSelection ? dragHandleRanges : selectionRanges\n\n if (!ranges.length) {\n return\n }\n\n const { tr } = view.state\n const wrapper = document.createElement('div')\n const from = ranges[0].$from.pos\n const to = ranges[ranges.length - 1].$to.pos\n\n // For nested mode, create slice directly to avoid NodeRangeSelection expanding to parent\n const isNestedDrag = nestedOptions?.enabled && dragContext?.node\n\n let slice\n let selection\n\n if (isNestedDrag) {\n // Create slice directly from the exact positions\n slice = view.state.doc.slice(from, to)\n\n // Use NodeSelection for nested mode to select exactly the target node\n // NodeRangeSelection would expand to the parent\n selection = NodeSelection.create(view.state.doc, from)\n } else {\n selection = NodeRangeSelection.create(view.state.doc, from, to)\n slice = selection.content()\n }\n\n ranges.forEach(range => {\n const element = view.nodeDOM(range.$from.pos) as HTMLElement\n const clonedElement = cloneElement(element)\n\n wrapper.append(clonedElement)\n })\n\n wrapper.style.position = 'absolute'\n wrapper.style.top = '-10000px'\n document.body.append(wrapper)\n\n event.dataTransfer.clearData()\n event.dataTransfer.setDragImage(wrapper, 0, 0)\n\n // tell ProseMirror the dragged content\n view.dragging = { slice, move: true }\n\n tr.setSelection(selection)\n\n view.dispatch(tr)\n\n // clean up\n document.addEventListener('drop', () => removeNode(wrapper), { once: true })\n}\n","function getCSSText(element: Element) {\n let value = ''\n const style = getComputedStyle(element)\n\n for (let i = 0; i < style.length; i += 1) {\n value += `${style[i]}:${style.getPropertyValue(style[i])};`\n }\n\n return value\n}\n\nexport function cloneElement(node: HTMLElement) {\n const clonedNode = node.cloneNode(true) as HTMLElement\n const sourceElements = [node, ...Array.from(node.getElementsByTagName('*'))] as HTMLElement[]\n const targetElements = [clonedNode, ...Array.from(clonedNode.getElementsByTagName('*'))] as HTMLElement[]\n\n sourceElements.forEach((sourceElement, index) => {\n targetElements[index].style.cssText = getCSSText(sourceElement)\n })\n\n return clonedNode\n}\n","import type { DragHandleRule } from '../types/rules.js'\n\n/**\n * The first child inside a list item is the list item's content.\n * It cannot be dragged separately - you drag the list item instead.\n *\n * Example: In `<li><p>Text</p></li>`, the paragraph is excluded,\n * but the listItem is draggable.\n */\nexport const listItemFirstChild: DragHandleRule = {\n id: 'listItemFirstChild',\n evaluate: ({ parent, isFirst }) => {\n if (!isFirst) {\n return 0\n }\n\n const listItemTypes = ['listItem', 'taskItem']\n\n if (parent && listItemTypes.includes(parent.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Nodes that contain list items (listItem/taskItem) as direct children\n * are deprioritized. This makes it easier to target individual list items\n * rather than the entire list wrapper.\n *\n * This rule detects list wrappers dynamically by checking if the first child\n * is a list item, rather than hardcoding wrapper type names.\n *\n * Users can still target the list wrapper by moving to the very edge\n * where edge detection kicks in.\n */\nexport const listWrapperDeprioritize: DragHandleRule = {\n id: 'listWrapperDeprioritize',\n evaluate: ({ node }) => {\n const listItemTypes = ['listItem', 'taskItem']\n\n const firstChild = node.firstChild\n\n if (firstChild && listItemTypes.includes(firstChild.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Inline nodes (text, marks, inline atoms) should never be drag targets.\n */\nexport const inlineContent: DragHandleRule = {\n id: 'inlineContent',\n evaluate: ({ node }) => {\n if (node.isInline || node.isText) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * All default rules.\n * Users can extend these or replace them entirely.\n */\nexport const defaultRules: DragHandleRule[] = [listItemFirstChild, listWrapperDeprioritize, inlineContent]\n","import type { EdgeDetectionConfig, EdgeDetectionPreset } from '../types/options.js'\n\n/**\n * Default edge detection configuration.\n */\nconst DEFAULT_EDGE_CONFIG: EdgeDetectionConfig = {\n edges: ['left', 'top'],\n threshold: 12,\n strength: 500,\n}\n\n/**\n * Normalizes edge detection presets or custom config into a full config object.\n * Partial configs are merged with defaults.\n *\n * @param input - The preset string or partial/full config\n * @returns A complete EdgeDetectionConfig\n */\nexport function normalizeEdgeDetection(\n input: EdgeDetectionPreset | Partial<EdgeDetectionConfig> | undefined,\n): EdgeDetectionConfig {\n if (input === undefined || input === 'left') {\n return { ...DEFAULT_EDGE_CONFIG }\n }\n\n if (input === 'right') {\n return { edges: ['right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'both') {\n return { edges: ['left', 'right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'none') {\n return { edges: [], threshold: 0, strength: 0 }\n }\n\n // Merge partial config with defaults\n return { ...DEFAULT_EDGE_CONFIG, ...input }\n}\n\n/**\n * Determines if cursor is near specified edges of an element.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against\n * @param config - The edge detection configuration\n * @returns True if the cursor is near any of the configured edges\n */\nexport function isNearEdge(\n coords: { x: number; y: number },\n element: HTMLElement,\n config: EdgeDetectionConfig,\n): boolean {\n if (config.edges.length === 0) {\n return false\n }\n\n const rect = element.getBoundingClientRect()\n const { threshold, edges } = config\n\n return edges.some(edge => {\n if (edge === 'left') {\n return coords.x - rect.left < threshold\n }\n\n if (edge === 'right') {\n return rect.right - coords.x < threshold\n }\n\n if (edge === 'top') {\n return coords.y - rect.top < threshold\n }\n\n if (edge === 'bottom') {\n return rect.bottom - coords.y < threshold\n }\n\n return false\n })\n}\n\n/**\n * Calculates score deduction for edge proximity.\n * Deeper nodes get larger deductions when near edges,\n * making shallower (parent) nodes win.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against (may be null)\n * @param config - The edge detection configuration\n * @param depth - The depth of the node in the document tree\n * @returns The score deduction to apply\n */\nexport function calculateEdgeDeduction(\n coords: { x: number; y: number },\n element: HTMLElement | null,\n config: EdgeDetectionConfig,\n depth: number,\n): number {\n if (!element || config.edges.length === 0) {\n return 0\n }\n\n if (isNearEdge(coords, element, config)) {\n return config.strength * depth\n }\n\n return 0\n}\n","import type { EdgeDetectionConfig } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { calculateEdgeDeduction } from './edgeDetection.js'\n\n/**\n * Base score for all nodes. Rules deduct from this score.\n * A node with score <= 0 is excluded from being a drag target.\n */\nexport const BASE_SCORE = 1000\n\n/**\n * Calculates the drag target score for a node.\n * Higher score = more likely to be selected.\n *\n * @param context - The rule context containing node information\n * @param rules - The rules to apply\n * @param edgeConfig - The edge detection configuration\n * @param coords - The cursor coordinates\n * @returns The calculated score, or -1 if the node is excluded\n */\nexport function calculateScore(\n context: RuleContext,\n rules: DragHandleRule[],\n edgeConfig: EdgeDetectionConfig,\n coords: { x: number; y: number },\n): number {\n let score = BASE_SCORE\n let excluded = false\n\n rules.every(rule => {\n const deduction = rule.evaluate(context)\n\n score -= deduction\n\n if (score <= 0) {\n excluded = true\n return false\n }\n\n return true\n })\n\n if (excluded) {\n return -1\n }\n\n const dom = context.view.nodeDOM(context.pos) as HTMLElement | null\n\n score -= calculateEdgeDeduction(coords, dom, edgeConfig, context.depth)\n\n if (score <= 0) {\n return -1\n }\n\n return score\n}\n","import type { Node, ResolvedPos } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { defaultRules } from './defaultRules.js'\nimport { calculateScore } from './scoring.js'\n\n/**\n * Represents a drag target with its node, position, and DOM element.\n */\nexport interface DragTarget {\n /** The ProseMirror node */\n node: Node\n\n /** The absolute position in the document */\n pos: number\n\n /** The corresponding DOM element */\n dom: HTMLElement\n}\n\n/**\n * Checks if any ancestor at or above the given depth is in the allowed list.\n *\n * @param $pos - The resolved position\n * @param depth - The current depth being checked\n * @param allowedTypes - The list of allowed node type names\n * @returns True if any ancestor is in the allowed list\n */\nfunction hasAncestorOfType($pos: ResolvedPos, depth: number, allowedTypes: string[]): boolean {\n const ancestorDepths = Array.from({ length: depth }, (_, i) => depth - 1 - i)\n\n return ancestorDepths.some(d => allowedTypes.includes($pos.node(d).type.name))\n}\n\n/**\n * Finds the best drag target at the given coordinates using the scoring system.\n *\n * @param view - The editor view\n * @param coords - The cursor coordinates\n * @param options - The normalized nested options\n * @returns The best drag target, or null if none found\n */\nexport function findBestDragTarget(\n view: EditorView,\n coords: { x: number; y: number },\n options: NormalizedNestedOptions,\n): DragTarget | null {\n // Validate coordinates are finite numbers to prevent DOM errors\n if (!Number.isFinite(coords.x) || !Number.isFinite(coords.y)) {\n return null\n }\n\n // ProseMirror expects { left, top } format for coordinates\n const posInfo = view.posAtCoords({ left: coords.x, top: coords.y })\n\n if (!posInfo) {\n return null\n }\n\n const { doc } = view.state\n const $pos = doc.resolve(posInfo.pos)\n\n const rules: DragHandleRule[] = []\n\n if (options.defaultRules) {\n rules.push(...defaultRules)\n }\n\n rules.push(...options.rules)\n\n // Start from depth 1 to exclude the doc node (depth 0) which should never be draggable\n const depthLevels = Array.from({ length: $pos.depth }, (_, i) => $pos.depth - i)\n\n const candidates = depthLevels\n .map(depth => {\n const node = $pos.node(depth)\n const nodePos = $pos.before(depth)\n\n if (options.allowedContainers && depth > 0) {\n const inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)\n\n if (!inAllowedContainer) {\n return null\n }\n }\n\n const parent = depth > 0 ? $pos.node(depth - 1) : null\n const index = depth > 0 ? $pos.index(depth - 1) : 0\n const siblingCount = parent ? parent.childCount : 1\n\n const context: RuleContext = {\n node,\n pos: nodePos,\n depth,\n parent,\n index,\n isFirst: index === 0,\n isLast: index === siblingCount - 1,\n $pos,\n view,\n }\n\n const score = calculateScore(context, rules, options.edgeDetection, coords)\n\n if (score < 0) {\n return null\n }\n\n const dom = view.nodeDOM(nodePos) as HTMLElement | null\n\n return { node, pos: nodePos, depth, score, dom }\n })\n .filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)\n\n if (candidates.length === 0) {\n return null\n }\n\n candidates.sort((a, b) => {\n if (b.score !== a.score) {\n return b.score - a.score\n }\n\n return b.depth - a.depth\n })\n\n const winner = candidates[0]\n\n if (!winner.dom) {\n return null\n }\n\n return {\n node: winner.node,\n pos: winner.pos,\n dom: winner.dom,\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { findBestDragTarget } from './findBestDragTarget.js'\n\nexport type FindElementNextToCoords = {\n x: number\n y: number\n direction?: 'left' | 'right'\n editor: Editor\n nestedOptions?: NormalizedNestedOptions\n}\n\n/**\n * Finds the draggable block element that is a direct child of view.dom\n */\nexport function findClosestTopLevelBlock(element: Element, view: EditorView): HTMLElement | undefined {\n let current: Element | null = element\n\n while (current?.parentElement && current.parentElement !== view.dom) {\n current = current.parentElement\n }\n\n return current?.parentElement === view.dom ? (current as HTMLElement) : undefined\n}\n\n/**\n * Checks if a DOMRect has valid, finite dimensions.\n */\nfunction isValidRect(rect: DOMRect): boolean {\n return (\n Number.isFinite(rect.top) &&\n Number.isFinite(rect.bottom) &&\n Number.isFinite(rect.left) &&\n Number.isFinite(rect.right) &&\n rect.width > 0 &&\n rect.height > 0\n )\n}\n\n/**\n * Clamps coordinates to content bounds with O(1) layout reads\n */\nfunction clampToContent(view: EditorView, x: number, y: number, inset = 5): { x: number; y: number } | null {\n // Validate input coordinates are finite numbers\n if (!Number.isFinite(x) || !Number.isFinite(y)) {\n return null\n }\n\n const container = view.dom\n const firstBlock = container.firstElementChild\n const lastBlock = container.lastElementChild\n\n if (!firstBlock || !lastBlock) {\n return null\n }\n\n // Clamp Y between first and last block\n const topRect = firstBlock.getBoundingClientRect()\n const botRect = lastBlock.getBoundingClientRect()\n\n // Validate bounding rects have finite values\n if (!isValidRect(topRect) || !isValidRect(botRect)) {\n return null\n }\n\n const clampedY = Math.min(Math.max(topRect.top + inset, y), botRect.bottom - inset)\n\n const epsilon = 0.5\n const sameLeft = Math.abs(topRect.left - botRect.left) < epsilon\n const sameRight = Math.abs(topRect.right - botRect.right) < epsilon\n\n let rowRect: DOMRect = topRect\n\n if (sameLeft && sameRight) {\n // Most of the time, every block has the same width\n rowRect = topRect\n } else {\n // TODO\n // find the actual block at the clamped Y\n // This case is rare, avoid for now\n }\n\n // Clamp X to the chosen block's bounds\n const clampedX = Math.min(Math.max(rowRect.left + inset, x), rowRect.right - inset)\n\n // Final validation of output coordinates\n if (!Number.isFinite(clampedX) || !Number.isFinite(clampedY)) {\n return null\n }\n\n return { x: clampedX, y: clampedY }\n}\n\nexport const findElementNextToCoords = (\n options: FindElementNextToCoords,\n): {\n resultElement: HTMLElement | null\n resultNode: Node | null\n pos: number | null\n} => {\n const { x, y, editor, nestedOptions } = options\n const { view, state } = editor\n\n const clamped = clampToContent(view, x, y, 5)\n\n // Return early if coordinates could not be clamped to valid bounds\n if (!clamped) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const { x: clampedX, y: clampedY } = clamped\n\n // When nested mode is enabled, use the scoring-based detection\n if (nestedOptions?.enabled) {\n const target = findBestDragTarget(view, { x: clampedX, y: clampedY }, nestedOptions)\n\n if (!target) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n return {\n resultElement: target.dom,\n resultNode: target.node,\n pos: target.pos,\n }\n }\n\n // Original root-level detection for non-nested mode\n const elements = view.root.elementsFromPoint(clampedX, clampedY)\n\n let block: HTMLElement | undefined\n\n Array.prototype.some.call(elements, (el: Element) => {\n if (!view.dom.contains(el)) {\n return false\n }\n const candidate = findClosestTopLevelBlock(el, view)\n if (candidate) {\n block = candidate\n return true\n }\n return false\n })\n\n if (!block) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n let pos: number\n try {\n pos = view.posAtDOM(block, 0)\n } catch {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const node = state.doc.nodeAt(pos)\n\n if (!node) {\n // This case occurs when an atom node is allowed to contain inline content.\n // We need to resolve the position here to ensure we target the correct parent node.\n const resolvedPos = state.doc.resolve(pos)\n const parent = resolvedPos.parent\n\n return {\n resultElement: block,\n resultNode: parent,\n pos: resolvedPos.start(),\n }\n }\n\n return {\n resultElement: block,\n resultNode: node,\n pos,\n }\n}\n","export function removeNode(node: HTMLElement) {\n node.parentNode?.removeChild(node)\n}\n","import type { Node } from '@tiptap/pm/model'\n\nexport const getOuterNodePos = (doc: Node, pos: number): number => {\n const resolvedPos = doc.resolve(pos)\n const { depth } = resolvedPos\n\n if (depth === 0) {\n return pos\n }\n\n const a = resolvedPos.pos - resolvedPos.parentOffset\n\n return a - 1\n}\n\nexport const getOuterNode = (doc: Node, pos: number): Node | null => {\n const node = doc.nodeAt(pos)\n const resolvedPos = doc.resolve(pos)\n\n let { depth } = resolvedPos\n let parent = node\n\n while (depth > 0) {\n const currentNode = resolvedPos.node(depth)\n\n depth -= 1\n\n if (depth === 0) {\n parent = currentNode\n }\n }\n\n return parent\n}\n","import type { NestedOptions, NormalizedNestedOptions } from '../types/options.js'\nimport { normalizeEdgeDetection } from './edgeDetection.js'\n\n/**\n * Normalizes the nested options input into a complete configuration object.\n *\n * @param input - The nested option (boolean, object, or undefined)\n * @returns A fully normalized options object\n *\n * @example\n * // Simple enable\n * normalizeNestedOptions(true)\n * // Returns: { enabled: true, rules: [], defaultRules: true, ... }\n *\n * @example\n * // Custom config\n * normalizeNestedOptions({ rules: [myRule], edgeDetection: 'none' })\n * // Returns: { enabled: true, rules: [myRule], edgeDetection: { edges: [], ... } }\n */\nexport function normalizeNestedOptions(input: boolean | NestedOptions | undefined): NormalizedNestedOptions {\n if (input === false || input === undefined) {\n return {\n enabled: false,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('none'),\n }\n }\n\n if (input === true) {\n return {\n enabled: true,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('left'),\n }\n }\n\n return {\n enabled: true,\n rules: input.rules ?? [],\n defaultRules: input.defaultRules ?? true,\n allowedContainers: input.allowedContainers,\n edgeDetection: normalizeEdgeDetection(input.edgeDetection),\n }\n}\n"],"mappings":"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","names":["import_core","import_state"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/drag-handle.ts","../src/drag-handle-plugin.ts","../src/helpers/dragHandler.ts","../src/helpers/cloneElement.ts","../src/helpers/defaultRules.ts","../src/helpers/edgeDetection.ts","../src/helpers/scoring.ts","../src/helpers/findBestDragTarget.ts","../src/helpers/findNextElementFromCursor.ts","../src/helpers/removeNode.ts","../src/helpers/getOuterNode.ts","../src/helpers/normalizeOptions.ts"],"sourcesContent":["import { DragHandle } from './drag-handle.js'\n\nexport * from './drag-handle.js'\nexport * from './drag-handle-plugin.js'\nexport { defaultRules } from './helpers/defaultRules.js'\nexport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nexport type {\n EdgeDetectionConfig,\n EdgeDetectionPreset,\n NestedOptions,\n NormalizedNestedOptions,\n} from './types/options.js'\nexport type { DragHandleRule, RuleContext } from './types/rules.js'\n\nexport default DragHandle\n","import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'\nimport { type Editor, Extension } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\n\nimport { DragHandlePlugin } from './drag-handle-plugin.js'\nimport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nimport type { NestedOptions } from './types/options.js'\n\nexport const defaultComputePositionConfig: ComputePositionConfig = {\n placement: 'left-start',\n strategy: 'absolute',\n}\n\nexport interface DragHandleOptions {\n /**\n * Renders an element that is positioned with the floating-ui/dom package\n */\n render(): HTMLElement\n /**\n * Configuration for position computation of the drag handle\n * using the floating-ui/dom package\n */\n computePositionConfig?: ComputePositionConfig\n /**\n * A function that returns the virtual element for the drag handle.\n * This is useful when the menu needs to be positioned relative to a specific DOM element.\n */\n getReferencedVirtualElement?: () => VirtualElement | null\n /**\n * Locks the draghandle in place and visibility\n */\n locked?: boolean\n /**\n * Returns a node or null when a node is hovered over\n */\n onNodeChange?: (options: { node: Node | null; editor: Editor }) => void\n /**\n * The callback function that will be called when drag start.\n */\n onElementDragStart?: (e: DragEvent) => void\n /**\n * The callback function that will be called when drag end.\n */\n onElementDragEnd?: (e: DragEvent) => void\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 * **Values:**\n * - `false` (default): Only root-level blocks show drag handles\n * - `true`: Enable with sensible defaults (left edge detection, default rules)\n * - `NestedOptions`: Enable with custom configuration\n *\n * **Configuration options:**\n * - `rules`: Custom rules to determine which nodes are draggable\n * - `defaultRules`: Whether to include default rules (default: true)\n * - `allowedContainers`: Restrict nested dragging to specific container types\n * - `edgeDetection`: Control when to prefer parent over nested node\n * - `'left'` (default): Prefer parent near left/top edges\n * - `'right'`: Prefer parent near right/top edges (for RTL)\n * - `'both'`: Prefer parent near any horizontal edge\n * - `'none'`: Disable edge detection\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * DragHandle.configure({\n * nested: true,\n * })\n *\n * @example\n * // Restrict to specific containers\n * DragHandle.configure({\n * nested: {\n * allowedContainers: ['bulletList', 'orderedList'],\n * },\n * })\n *\n * @example\n * // With custom rules\n * DragHandle.configure({\n * nested: {\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * edgeDetection: 'none',\n * },\n * })\n */\n nested?: boolean | NestedOptions\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n dragHandle: {\n /**\n * Locks the draghandle in place and visibility\n */\n lockDragHandle: () => ReturnType\n /**\n * Unlocks the draghandle\n */\n unlockDragHandle: () => ReturnType\n /**\n * Toggle draghandle lock state\n */\n toggleDragHandle: () => ReturnType\n }\n }\n}\n\nexport const DragHandle = Extension.create<DragHandleOptions>({\n name: 'dragHandle',\n\n addOptions() {\n return {\n render() {\n const element = document.createElement('div')\n\n element.classList.add('drag-handle')\n\n return element\n },\n computePositionConfig: {},\n locked: false,\n onNodeChange: () => {\n return null\n },\n onElementDragStart: undefined,\n onElementDragEnd: undefined,\n nested: false,\n }\n },\n\n addCommands() {\n return {\n lockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = true\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n unlockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = false\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n toggleDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = !this.options.locked\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n }\n },\n\n addProseMirrorPlugins() {\n const element = this.options.render()\n const nestedOptions = normalizeNestedOptions(this.options.nested)\n\n return [\n DragHandlePlugin({\n computePositionConfig: { ...defaultComputePositionConfig, ...this.options.computePositionConfig },\n getReferencedVirtualElement: this.options.getReferencedVirtualElement,\n element,\n editor: this.editor,\n onNodeChange: this.options.onNodeChange,\n onElementDragStart: this.options.onElementDragStart,\n onElementDragEnd: this.options.onElementDragEnd,\n nestedOptions,\n }).plugin,\n ]\n },\n})\n","import { type ComputePositionConfig, type VirtualElement, computePosition } from '@floating-ui/dom'\nimport { type Editor, isFirefox } from '@tiptap/core'\nimport { isChangeOrigin } from '@tiptap/extension-collaboration'\nimport type { Node } from '@tiptap/pm/model'\nimport { type EditorState, type Transaction, Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport {\n absolutePositionToRelativePosition,\n relativePositionToAbsolutePosition,\n ySyncPluginKey,\n} from '@tiptap/y-tiptap'\n\nimport { dragHandler } from './helpers/dragHandler.js'\nimport { findElementNextToCoords } from './helpers/findNextElementFromCursor.js'\nimport { getOuterNode, getOuterNodePos } from './helpers/getOuterNode.js'\nimport { removeNode } from './helpers/removeNode.js'\nimport type { NormalizedNestedOptions } from './types/options.js'\n\ntype PluginState = {\n locked: boolean\n}\n\nconst getRelativePos = (state: EditorState, absolutePos: number) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return null\n }\n\n return absolutePositionToRelativePosition(absolutePos, ystate.type, ystate.binding.mapping)\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: y-prosemirror (and y-tiptap by extension) does not have types for relative positions\nconst getAbsolutePos = (state: EditorState, relativePos: any) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return -1\n }\n\n return relativePositionToAbsolutePosition(ystate.doc, ystate.type, relativePos, ystate.binding.mapping) || 0\n}\n\nconst getOuterDomNode = (view: EditorView, domNode: HTMLElement) => {\n let tmpDomNode = domNode\n\n // Traverse to top level node.\n while (tmpDomNode?.parentNode) {\n if (tmpDomNode.parentNode === view.dom) {\n break\n }\n\n tmpDomNode = tmpDomNode.parentNode as HTMLElement\n }\n\n return tmpDomNode\n}\n\nexport interface DragHandlePluginProps {\n pluginKey?: PluginKey | string\n editor: Editor\n element: HTMLElement\n onNodeChange?: (data: { editor: Editor; node: Node | null; pos: number }) => void\n onElementDragStart?: (e: DragEvent) => void\n onElementDragEnd?: (e: DragEvent) => void\n computePositionConfig?: ComputePositionConfig\n getReferencedVirtualElement?: () => VirtualElement | null\n nestedOptions: NormalizedNestedOptions\n}\n\nexport const dragHandlePluginDefaultKey = new PluginKey('dragHandle')\n\nexport const DragHandlePlugin = ({\n pluginKey = dragHandlePluginDefaultKey,\n element,\n editor,\n computePositionConfig,\n getReferencedVirtualElement,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n}: DragHandlePluginProps) => {\n const wrapper = document.createElement('div')\n let locked = false\n let currentNode: Node | null = null\n let currentNodePos = -1\n // biome-ignore lint/suspicious/noExplicitAny: See above - relative positions in y-prosemirror are not typed\n let currentNodeRelPos: any\n let rafId: number | null = null\n let pendingMouseCoords: { x: number; y: number } | null = null\n\n function hideHandle() {\n if (!element) {\n return\n }\n\n element.style.visibility = 'hidden'\n element.style.pointerEvents = 'none'\n }\n\n function showHandle() {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n element.style.visibility = ''\n element.style.pointerEvents = 'auto'\n }\n\n function repositionDragHandle(dom: Element) {\n const virtualElement = getReferencedVirtualElement?.() || {\n getBoundingClientRect: () => dom.getBoundingClientRect(),\n }\n\n computePosition(virtualElement, element, computePositionConfig).then(val => {\n Object.assign(element.style, {\n position: val.strategy,\n left: `${val.x}px`,\n top: `${val.y}px`,\n })\n })\n }\n\n function onDragStart(e: DragEvent) {\n onElementDragStart?.(e)\n // Push this to the end of the event cue\n // Fixes bug where incorrect drag pos is returned if drag handle has position: absolute\n // Pass the current node context to avoid recalculation issues during drag start\n dragHandler(e, editor, nestedOptions, { node: currentNode, pos: currentNodePos })\n\n if (element) {\n element.dataset.dragging = 'true'\n }\n\n setTimeout(() => {\n if (element) {\n element.style.pointerEvents = 'none'\n }\n }, 0)\n }\n\n function onDragEnd(e: DragEvent) {\n onElementDragEnd?.(e)\n hideHandle()\n if (element) {\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n }\n }\n\n function onDrop() {\n // Firefox has a bug where the caret becomes invisible after drag and drop.\n // This workaround forces Firefox to re-render the caret by toggling contentEditable.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1327834\n if (isFirefox()) {\n const editorElement = editor.view.dom\n\n // Use requestAnimationFrame to ensure the drop operation has completed\n requestAnimationFrame(() => {\n if (editorElement.isContentEditable) {\n editorElement.contentEditable = 'false'\n editorElement.contentEditable = 'true'\n }\n })\n }\n }\n\n element.addEventListener('dragstart', onDragStart)\n element.addEventListener('dragend', onDragEnd)\n document.addEventListener('drop', onDrop)\n\n wrapper.appendChild(element)\n\n return {\n unbind() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n },\n plugin: new Plugin({\n key: typeof pluginKey === 'string' ? new PluginKey(pluginKey) : pluginKey,\n\n state: {\n init() {\n return { locked: false }\n },\n apply(tr: Transaction, value: PluginState, _oldState: EditorState, state: EditorState) {\n const isLocked = tr.getMeta('lockDragHandle')\n const hideDragHandle = tr.getMeta('hideDragHandle')\n\n if (isLocked !== undefined) {\n locked = isLocked\n }\n\n if (hideDragHandle) {\n hideHandle()\n\n locked = false\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n return value\n }\n\n // Something has changed and drag handler is visible…\n if (tr.docChanged && currentNodePos !== -1 && element) {\n // Yjs replaces the entire document on every incoming change and needs a special handling.\n // If change comes from another user …\n if (isChangeOrigin(tr)) {\n // https://discuss.yjs.dev/t/y-prosemirror-mapping-a-single-relative-position-when-doc-changes/851/3\n const newPos = getAbsolutePos(state, currentNodeRelPos)\n\n if (newPos !== currentNodePos) {\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // We will get the outer node with data and position in views update method.\n }\n } else {\n // … otherwise use ProseMirror mapping to update the position.\n const newPos = tr.mapping.map(currentNodePos)\n\n if (newPos !== currentNodePos) {\n // TODO: Remove\n // console.log('Position has changed …', { old: currentNodePos, new: newPos }, tr);\n\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(state, currentNodePos)\n\n // We will get the outer node with data and position in views update method.\n }\n }\n }\n\n return value\n },\n },\n\n view: view => {\n element.draggable = true\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n\n editor.view.dom.parentElement?.appendChild(wrapper)\n\n wrapper.style.pointerEvents = 'none'\n wrapper.style.position = 'absolute'\n wrapper.style.top = '0'\n wrapper.style.left = '0'\n\n return {\n update(_, oldState) {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n // Prevent element being draggend while being open.\n if (locked) {\n element.draggable = false\n } else {\n element.draggable = true\n }\n\n // Recalculate popup position if doc has changend and drag handler is visible.\n if (view.state.doc.eq(oldState.doc) || currentNodePos === -1) {\n return\n }\n\n // Get domNode from (new) position.\n let domNode = view.nodeDOM(currentNodePos) as HTMLElement\n\n // Since old element could have been wrapped, we need to find\n // the outer node and take its position and node data.\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n const outerNode = getOuterNode(editor.state.doc, domNodePos)\n const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos) // TODO: needed?\n\n currentNode = outerNode\n currentNodePos = outerNodePos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n repositionDragHandle(domNode as Element)\n },\n\n // TODO: Kills even on hot reload\n destroy() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n\n if (element) {\n removeNode(wrapper)\n }\n },\n }\n },\n\n props: {\n handleDOMEvents: {\n keydown(view) {\n if (!element || locked) {\n return false\n }\n\n if (view.hasFocus()) {\n hideHandle()\n currentNode = null\n currentNodePos = -1\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n // We want to still continue with other keydown events.\n return false\n }\n\n return false\n },\n mouseleave(_view, e) {\n // Do not hide open popup on mouseleave.\n if (locked) {\n return false\n }\n\n // If e.target is not inside the wrapper, hide.\n if (e.target && !wrapper.contains(e.relatedTarget as HTMLElement)) {\n hideHandle()\n\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n }\n\n return false\n },\n\n mousemove(view, e) {\n // Do not continue if popup is not initialized or open.\n if (!element || locked) {\n return false\n }\n\n // Store latest mouse coords and schedule a single RAF per frame\n pendingMouseCoords = { x: e.clientX, y: e.clientY }\n\n if (rafId) {\n return false\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null\n\n if (!pendingMouseCoords) {\n return\n }\n\n const { x, y } = pendingMouseCoords\n pendingMouseCoords = null\n\n const nodeData = findElementNextToCoords({\n x,\n y,\n direction: 'right',\n editor,\n nestedOptions,\n })\n\n // Skip if there is no node next to coords\n if (!nodeData.resultElement) {\n return\n }\n\n let domNode = nodeData.resultElement as HTMLElement\n let targetNode = nodeData.resultNode\n let targetPos = nodeData.pos\n\n // In nested mode, the node data already contains the correct target\n // In non-nested mode, traverse to the top-level block\n if (!nestedOptions?.enabled) {\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n\n targetNode = getOuterNode(editor.state.doc, domNodePos)\n targetPos = getOuterNodePos(editor.state.doc, domNodePos)\n }\n\n if (targetNode !== currentNode) {\n currentNode = targetNode\n currentNodePos = targetPos ?? -1\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n // Set nodes clientRect.\n repositionDragHandle(domNode as Element)\n\n showHandle()\n }\n })\n\n return false\n },\n },\n },\n }),\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport { getSelectionRanges, NodeRangeSelection } from '@tiptap/extension-node-range'\nimport type { Node } from '@tiptap/pm/model'\nimport { type SelectionRange, NodeSelection } from '@tiptap/pm/state'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { cloneElement } from './cloneElement.js'\nimport { findElementNextToCoords } from './findNextElementFromCursor.js'\nimport { removeNode } from './removeNode.js'\n\nexport interface DragContext {\n node: Node | null\n pos: number\n}\n\nfunction getDragHandleRanges(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n): SelectionRange[] {\n const { doc } = editor.view.state\n\n // In nested mode with known context, use the pre-calculated position\n // This prevents recalculation issues when mouse position shifts during drag start\n if (nestedOptions?.enabled && dragContext?.node && dragContext.pos >= 0) {\n const nodeStart = dragContext.pos\n const nodeEnd = dragContext.pos + dragContext.node.nodeSize\n\n return [\n {\n $from: doc.resolve(nodeStart),\n $to: doc.resolve(nodeEnd),\n },\n ]\n }\n\n // Fallback: recalculate from mouse position (used in non-nested mode)\n const result = findElementNextToCoords({\n editor,\n x: event.clientX,\n y: event.clientY,\n direction: 'right',\n nestedOptions,\n })\n\n if (!result.resultNode || result.pos === null) {\n return []\n }\n\n // For non-nested mode, use depth 0 to select the outermost block\n // Atom nodes (e.g. images) have nodeSize=1 with no opening/closing tokens,\n // so we must not subtract 1 or we'll create an empty range.\n const offset = result.resultNode.isText || result.resultNode.isAtom ? 0 : -1\n const $from = doc.resolve(result.pos)\n const $to = doc.resolve(result.pos + result.resultNode.nodeSize + offset)\n\n return getSelectionRanges($from, $to, 0)\n}\n\nexport function dragHandler(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n) {\n const { view } = editor\n\n if (!event.dataTransfer) {\n return\n }\n\n const { empty, $from, $to } = view.state.selection\n\n const dragHandleRanges = getDragHandleRanges(event, editor, nestedOptions, dragContext)\n\n const selectionRanges = getSelectionRanges($from, $to, 0)\n const isDragHandleWithinSelection = selectionRanges.some(range => {\n return dragHandleRanges.find(dragHandleRange => {\n return dragHandleRange.$from === range.$from && dragHandleRange.$to === range.$to\n })\n })\n\n const ranges = empty || !isDragHandleWithinSelection ? dragHandleRanges : selectionRanges\n\n if (!ranges.length) {\n return\n }\n\n const { tr } = view.state\n const wrapper = document.createElement('div')\n const from = ranges[0].$from.pos\n const to = ranges[ranges.length - 1].$to.pos\n\n // For nested mode, create slice directly to avoid NodeRangeSelection expanding to parent\n const isNestedDrag = nestedOptions?.enabled && dragContext?.node\n\n let slice\n let selection\n\n if (isNestedDrag) {\n // Create slice directly from the exact positions\n slice = view.state.doc.slice(from, to)\n\n // Use NodeSelection for nested mode to select exactly the target node\n // NodeRangeSelection would expand to the parent\n selection = NodeSelection.create(view.state.doc, from)\n } else {\n selection = NodeRangeSelection.create(view.state.doc, from, to)\n slice = selection.content()\n }\n\n ranges.forEach(range => {\n const element = view.nodeDOM(range.$from.pos) as HTMLElement\n const clonedElement = cloneElement(element)\n\n wrapper.append(clonedElement)\n })\n\n wrapper.style.position = 'absolute'\n wrapper.style.top = '-10000px'\n document.body.append(wrapper)\n\n event.dataTransfer.clearData()\n event.dataTransfer.setDragImage(wrapper, 0, 0)\n\n // Tell ProseMirror the dragged content.\n // Pass the NodeSelection as `node` so ProseMirror's drop handler can use it\n // to precisely delete the original node via `node.replace(tr)`. Without this,\n // ProseMirror falls back to `tr.deleteSelection()` which relies on the current\n // selection — but the browser may change the selection during drag, causing the\n // original node to not be deleted on drop.\n const nodeSelection = selection instanceof NodeSelection ? selection : undefined\n\n // The `node` property is used at runtime by ProseMirror's drop handler but is\n // not exposed in the public type declaration for `view.dragging`.\n view.dragging = { slice, move: true, node: nodeSelection } as typeof view.dragging\n\n tr.setSelection(selection)\n\n view.dispatch(tr)\n\n // clean up\n document.addEventListener('drop', () => removeNode(wrapper), { once: true })\n}\n","function getCSSText(element: Element) {\n let value = ''\n const style = getComputedStyle(element)\n\n for (let i = 0; i < style.length; i += 1) {\n value += `${style[i]}:${style.getPropertyValue(style[i])};`\n }\n\n return value\n}\n\nexport function cloneElement(node: HTMLElement) {\n const clonedNode = node.cloneNode(true) as HTMLElement\n const sourceElements = [node, ...Array.from(node.getElementsByTagName('*'))] as HTMLElement[]\n const targetElements = [clonedNode, ...Array.from(clonedNode.getElementsByTagName('*'))] as HTMLElement[]\n\n sourceElements.forEach((sourceElement, index) => {\n targetElements[index].style.cssText = getCSSText(sourceElement)\n })\n\n return clonedNode\n}\n","import type { DragHandleRule } from '../types/rules.js'\n\n/**\n * The first child inside a list item is the list item's content.\n * It cannot be dragged separately - you drag the list item instead.\n *\n * Example: In `<li><p>Text</p></li>`, the paragraph is excluded,\n * but the listItem is draggable.\n */\nexport const listItemFirstChild: DragHandleRule = {\n id: 'listItemFirstChild',\n evaluate: ({ parent, isFirst }) => {\n if (!isFirst) {\n return 0\n }\n\n const listItemTypes = ['listItem', 'taskItem']\n\n if (parent && listItemTypes.includes(parent.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Nodes that contain list items (listItem/taskItem) as direct children\n * are deprioritized. This makes it easier to target individual list items\n * rather than the entire list wrapper.\n *\n * This rule detects list wrappers dynamically by checking if the first child\n * is a list item, rather than hardcoding wrapper type names.\n *\n * Users can still target the list wrapper by moving to the very edge\n * where edge detection kicks in.\n */\nexport const listWrapperDeprioritize: DragHandleRule = {\n id: 'listWrapperDeprioritize',\n evaluate: ({ node }) => {\n const listItemTypes = ['listItem', 'taskItem']\n\n const firstChild = node.firstChild\n\n if (firstChild && listItemTypes.includes(firstChild.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Table rows, cells, and content inside table headers should never be drag targets.\n * Table dragging is handled by table extensions. Only the table wrapper\n * itself or content inside regular table cells should be draggable.\n */\nexport const tableStructure: DragHandleRule = {\n id: 'tableStructure',\n evaluate: ({ node, parent }) => {\n const tableStructureTypes = ['tableRow', 'tableCell', 'tableHeader']\n\n if (tableStructureTypes.includes(node.type.name)) {\n return 1000\n }\n\n if (parent && parent.type.name === 'tableHeader') {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Inline nodes (text, marks, inline atoms) should never be drag targets.\n */\nexport const inlineContent: DragHandleRule = {\n id: 'inlineContent',\n evaluate: ({ node }) => {\n if (node.isInline || node.isText) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * All default rules.\n * Users can extend these or replace them entirely.\n */\nexport const defaultRules: DragHandleRule[] = [\n listItemFirstChild,\n listWrapperDeprioritize,\n tableStructure,\n inlineContent,\n]\n","import type { EdgeDetectionConfig, EdgeDetectionPreset } from '../types/options.js'\n\n/**\n * Default edge detection configuration.\n */\nconst DEFAULT_EDGE_CONFIG: EdgeDetectionConfig = {\n edges: ['left', 'top'],\n threshold: 12,\n strength: 500,\n}\n\n/**\n * Normalizes edge detection presets or custom config into a full config object.\n * Partial configs are merged with defaults.\n *\n * @param input - The preset string or partial/full config\n * @returns A complete EdgeDetectionConfig\n */\nexport function normalizeEdgeDetection(\n input: EdgeDetectionPreset | Partial<EdgeDetectionConfig> | undefined,\n): EdgeDetectionConfig {\n if (input === undefined || input === 'left') {\n return { ...DEFAULT_EDGE_CONFIG }\n }\n\n if (input === 'right') {\n return { edges: ['right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'both') {\n return { edges: ['left', 'right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'none') {\n return { edges: [], threshold: 0, strength: 0 }\n }\n\n // Merge partial config with defaults\n return { ...DEFAULT_EDGE_CONFIG, ...input }\n}\n\n/**\n * Determines if cursor is near specified edges of an element.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against\n * @param config - The edge detection configuration\n * @returns True if the cursor is near any of the configured edges\n */\nexport function isNearEdge(\n coords: { x: number; y: number },\n element: HTMLElement,\n config: EdgeDetectionConfig,\n): boolean {\n if (config.edges.length === 0) {\n return false\n }\n\n const rect = element.getBoundingClientRect()\n const { threshold, edges } = config\n\n return edges.some(edge => {\n if (edge === 'left') {\n return coords.x - rect.left < threshold\n }\n\n if (edge === 'right') {\n return rect.right - coords.x < threshold\n }\n\n if (edge === 'top') {\n return coords.y - rect.top < threshold\n }\n\n if (edge === 'bottom') {\n return rect.bottom - coords.y < threshold\n }\n\n return false\n })\n}\n\n/**\n * Calculates score deduction for edge proximity.\n * Deeper nodes get larger deductions when near edges,\n * making shallower (parent) nodes win.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against (may be null)\n * @param config - The edge detection configuration\n * @param depth - The depth of the node in the document tree\n * @returns The score deduction to apply\n */\nexport function calculateEdgeDeduction(\n coords: { x: number; y: number },\n element: HTMLElement | null,\n config: EdgeDetectionConfig,\n depth: number,\n): number {\n if (!element || config.edges.length === 0) {\n return 0\n }\n\n if (isNearEdge(coords, element, config)) {\n return config.strength * depth\n }\n\n return 0\n}\n","import type { EdgeDetectionConfig } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { calculateEdgeDeduction } from './edgeDetection.js'\n\n/**\n * Base score for all nodes. Rules deduct from this score.\n * A node with score <= 0 is excluded from being a drag target.\n */\nexport const BASE_SCORE = 1000\n\n/**\n * Calculates the drag target score for a node.\n * Higher score = more likely to be selected.\n *\n * @param context - The rule context containing node information\n * @param rules - The rules to apply\n * @param edgeConfig - The edge detection configuration\n * @param coords - The cursor coordinates\n * @returns The calculated score, or -1 if the node is excluded\n */\nexport function calculateScore(\n context: RuleContext,\n rules: DragHandleRule[],\n edgeConfig: EdgeDetectionConfig,\n coords: { x: number; y: number },\n): number {\n let score = BASE_SCORE\n let excluded = false\n\n rules.every(rule => {\n const deduction = rule.evaluate(context)\n\n score -= deduction\n\n if (score <= 0) {\n excluded = true\n return false\n }\n\n return true\n })\n\n if (excluded) {\n return -1\n }\n\n const dom = context.view.nodeDOM(context.pos) as HTMLElement | null\n\n score -= calculateEdgeDeduction(coords, dom, edgeConfig, context.depth)\n\n if (score <= 0) {\n return -1\n }\n\n return score\n}\n","import type { Node, ResolvedPos } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { defaultRules } from './defaultRules.js'\nimport { calculateScore } from './scoring.js'\n\n/**\n * Represents a drag target with its node, position, and DOM element.\n */\nexport interface DragTarget {\n /** The ProseMirror node */\n node: Node\n\n /** The absolute position in the document */\n pos: number\n\n /** The corresponding DOM element */\n dom: HTMLElement\n}\n\n/**\n * Checks if any ancestor at or above the given depth is in the allowed list.\n *\n * @param $pos - The resolved position\n * @param depth - The current depth being checked\n * @param allowedTypes - The list of allowed node type names\n * @returns True if any ancestor is in the allowed list\n */\nfunction hasAncestorOfType($pos: ResolvedPos, depth: number, allowedTypes: string[]): boolean {\n const ancestorDepths = Array.from({ length: depth }, (_, i) => depth - 1 - i)\n\n return ancestorDepths.some(d => allowedTypes.includes($pos.node(d).type.name))\n}\n\n/**\n * Finds the best drag target at the given coordinates using the scoring system.\n *\n * @param view - The editor view\n * @param coords - The cursor coordinates\n * @param options - The normalized nested options\n * @returns The best drag target, or null if none found\n */\nexport function findBestDragTarget(\n view: EditorView,\n coords: { x: number; y: number },\n options: NormalizedNestedOptions,\n): DragTarget | null {\n // Validate coordinates are finite numbers to prevent DOM errors\n if (!Number.isFinite(coords.x) || !Number.isFinite(coords.y)) {\n return null\n }\n\n // ProseMirror expects { left, top } format for coordinates\n const posInfo = view.posAtCoords({ left: coords.x, top: coords.y })\n\n if (!posInfo) {\n return null\n }\n\n const { doc } = view.state\n const $pos = doc.resolve(posInfo.pos)\n\n const rules: DragHandleRule[] = []\n\n if (options.defaultRules) {\n rules.push(...defaultRules)\n }\n\n rules.push(...options.rules)\n\n // Start from depth 1 to exclude the doc node (depth 0) which should never be draggable\n const depthLevels = Array.from({ length: $pos.depth }, (_, i) => $pos.depth - i)\n\n const candidates = depthLevels\n .map(depth => {\n const node = $pos.node(depth)\n const nodePos = $pos.before(depth)\n\n if (options.allowedContainers && depth > 0) {\n const inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)\n\n if (!inAllowedContainer) {\n return null\n }\n }\n\n const parent = depth > 0 ? $pos.node(depth - 1) : null\n const index = depth > 0 ? $pos.index(depth - 1) : 0\n const siblingCount = parent ? parent.childCount : 1\n\n const context: RuleContext = {\n node,\n pos: nodePos,\n depth,\n parent,\n index,\n isFirst: index === 0,\n isLast: index === siblingCount - 1,\n $pos,\n view,\n }\n\n const score = calculateScore(context, rules, options.edgeDetection, coords)\n\n if (score < 0) {\n return null\n }\n\n const dom = view.nodeDOM(nodePos) as HTMLElement | null\n\n return { node, pos: nodePos, depth, score, dom }\n })\n .filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)\n\n // Atom/leaf nodes (e.g. images) are not ancestors of $pos — they sit at $pos.nodeAfter.\n // The depth loop above only walks ancestor nodes, so these are missed entirely.\n // Check for a nodeAfter and evaluate it as an additional candidate.\n const nodeAfter = $pos.nodeAfter\n\n if (nodeAfter && nodeAfter.isAtom && !nodeAfter.isInline) {\n const nodePos = posInfo.pos\n const depth = $pos.depth + 1\n const parent = $pos.parent\n const index = $pos.index()\n const siblingCount = parent.childCount\n\n let inAllowedContainer = true\n\n if (options.allowedContainers) {\n inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)\n }\n\n if (inAllowedContainer) {\n const context: RuleContext = {\n node: nodeAfter,\n pos: nodePos,\n depth,\n parent,\n index,\n isFirst: index === 0,\n isLast: index === siblingCount - 1,\n $pos,\n view,\n }\n\n const score = calculateScore(context, rules, options.edgeDetection, coords)\n\n if (score >= 0) {\n const dom = view.nodeDOM(nodePos) as HTMLElement | null\n\n if (dom) {\n candidates.push({ node: nodeAfter, pos: nodePos, depth, score, dom })\n }\n }\n }\n }\n\n if (candidates.length === 0) {\n return null\n }\n\n candidates.sort((a, b) => {\n if (b.score !== a.score) {\n return b.score - a.score\n }\n\n return b.depth - a.depth\n })\n\n const winner = candidates[0]\n\n if (!winner.dom) {\n return null\n }\n\n return {\n node: winner.node,\n pos: winner.pos,\n dom: winner.dom,\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { findBestDragTarget } from './findBestDragTarget.js'\n\nexport type FindElementNextToCoords = {\n x: number\n y: number\n direction?: 'left' | 'right'\n editor: Editor\n nestedOptions?: NormalizedNestedOptions\n}\n\n/**\n * Finds the draggable block element that is a direct child of view.dom\n */\nexport function findClosestTopLevelBlock(element: Element, view: EditorView): HTMLElement | undefined {\n let current: Element | null = element\n\n while (current?.parentElement && current.parentElement !== view.dom) {\n current = current.parentElement\n }\n\n return current?.parentElement === view.dom ? (current as HTMLElement) : undefined\n}\n\n/**\n * Checks if a DOMRect has valid, finite dimensions.\n */\nfunction isValidRect(rect: DOMRect): boolean {\n return (\n Number.isFinite(rect.top) &&\n Number.isFinite(rect.bottom) &&\n Number.isFinite(rect.left) &&\n Number.isFinite(rect.right) &&\n rect.width > 0 &&\n rect.height > 0\n )\n}\n\n/**\n * Clamps coordinates to content bounds with O(1) layout reads\n */\nfunction clampToContent(view: EditorView, x: number, y: number, inset = 5): { x: number; y: number } | null {\n // Validate input coordinates are finite numbers\n if (!Number.isFinite(x) || !Number.isFinite(y)) {\n return null\n }\n\n const container = view.dom\n const firstBlock = container.firstElementChild\n const lastBlock = container.lastElementChild\n\n if (!firstBlock || !lastBlock) {\n return null\n }\n\n // Clamp Y between first and last block\n const topRect = firstBlock.getBoundingClientRect()\n const botRect = lastBlock.getBoundingClientRect()\n\n // Validate bounding rects have finite values\n if (!isValidRect(topRect) || !isValidRect(botRect)) {\n return null\n }\n\n const clampedY = Math.min(Math.max(topRect.top + inset, y), botRect.bottom - inset)\n\n const epsilon = 0.5\n const sameLeft = Math.abs(topRect.left - botRect.left) < epsilon\n const sameRight = Math.abs(topRect.right - botRect.right) < epsilon\n\n let rowRect: DOMRect = topRect\n\n if (sameLeft && sameRight) {\n // Most of the time, every block has the same width\n rowRect = topRect\n } else {\n // TODO\n // find the actual block at the clamped Y\n // This case is rare, avoid for now\n }\n\n // Clamp X to the chosen block's bounds\n const clampedX = Math.min(Math.max(rowRect.left + inset, x), rowRect.right - inset)\n\n // Final validation of output coordinates\n if (!Number.isFinite(clampedX) || !Number.isFinite(clampedY)) {\n return null\n }\n\n return { x: clampedX, y: clampedY }\n}\n\nexport const findElementNextToCoords = (\n options: FindElementNextToCoords,\n): {\n resultElement: HTMLElement | null\n resultNode: Node | null\n pos: number | null\n} => {\n const { x, y, editor, nestedOptions } = options\n const { view, state } = editor\n\n const clamped = clampToContent(view, x, y, 5)\n\n // Return early if coordinates could not be clamped to valid bounds\n if (!clamped) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const { x: clampedX, y: clampedY } = clamped\n\n // When nested mode is enabled, use the scoring-based detection\n if (nestedOptions?.enabled) {\n const target = findBestDragTarget(view, { x: clampedX, y: clampedY }, nestedOptions)\n\n if (!target) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n return {\n resultElement: target.dom,\n resultNode: target.node,\n pos: target.pos,\n }\n }\n\n // Original root-level detection for non-nested mode\n const elements = view.root.elementsFromPoint(clampedX, clampedY)\n\n let block: HTMLElement | undefined\n\n Array.prototype.some.call(elements, (el: Element) => {\n if (!view.dom.contains(el)) {\n return false\n }\n const candidate = findClosestTopLevelBlock(el, view)\n if (candidate) {\n block = candidate\n return true\n }\n return false\n })\n\n if (!block) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n let pos: number\n try {\n pos = view.posAtDOM(block, 0)\n } catch {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const node = state.doc.nodeAt(pos)\n\n if (!node) {\n // This case occurs when an atom node is allowed to contain inline content.\n // We need to resolve the position here to ensure we target the correct parent node.\n const resolvedPos = state.doc.resolve(pos)\n const parent = resolvedPos.parent\n\n return {\n resultElement: block,\n resultNode: parent,\n pos: resolvedPos.start(),\n }\n }\n\n return {\n resultElement: block,\n resultNode: node,\n pos,\n }\n}\n","export function removeNode(node: HTMLElement) {\n node.parentNode?.removeChild(node)\n}\n","import type { Node } from '@tiptap/pm/model'\n\nexport const getOuterNodePos = (doc: Node, pos: number): number => {\n const resolvedPos = doc.resolve(pos)\n const { depth } = resolvedPos\n\n if (depth === 0) {\n return pos\n }\n\n const a = resolvedPos.pos - resolvedPos.parentOffset\n\n return a - 1\n}\n\nexport const getOuterNode = (doc: Node, pos: number): Node | null => {\n const node = doc.nodeAt(pos)\n const resolvedPos = doc.resolve(pos)\n\n let { depth } = resolvedPos\n let parent = node\n\n while (depth > 0) {\n const currentNode = resolvedPos.node(depth)\n\n depth -= 1\n\n if (depth === 0) {\n parent = currentNode\n }\n }\n\n return parent\n}\n","import type { NestedOptions, NormalizedNestedOptions } from '../types/options.js'\nimport { normalizeEdgeDetection } from './edgeDetection.js'\n\n/**\n * Normalizes the nested options input into a complete configuration object.\n *\n * @param input - The nested option (boolean, object, or undefined)\n * @returns A fully normalized options object\n *\n * @example\n * // Simple enable\n * normalizeNestedOptions(true)\n * // Returns: { enabled: true, rules: [], defaultRules: true, ... }\n *\n * @example\n * // Custom config\n * normalizeNestedOptions({ rules: [myRule], edgeDetection: 'none' })\n * // Returns: { enabled: true, rules: [myRule], edgeDetection: { edges: [], ... } }\n */\nexport function normalizeNestedOptions(input: boolean | NestedOptions | undefined): NormalizedNestedOptions {\n if (input === false || input === undefined) {\n return {\n enabled: false,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('none'),\n }\n }\n\n if (input === true) {\n return {\n enabled: true,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('left'),\n }\n }\n\n return {\n enabled: true,\n rules: input.rules ?? [],\n defaultRules: input.defaultRules ?? true,\n allowedContainers: input.allowedContainers,\n edgeDetection: normalizeEdgeDetection(input.edgeDetection),\n }\n}\n"],"mappings":"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","names":["import_core","import_state"]}
package/dist/index.js CHANGED
@@ -60,6 +60,19 @@ var listWrapperDeprioritize = {
60
60
  return 0;
61
61
  }
62
62
  };
63
+ var tableStructure = {
64
+ id: "tableStructure",
65
+ evaluate: ({ node, parent }) => {
66
+ const tableStructureTypes = ["tableRow", "tableCell", "tableHeader"];
67
+ if (tableStructureTypes.includes(node.type.name)) {
68
+ return 1e3;
69
+ }
70
+ if (parent && parent.type.name === "tableHeader") {
71
+ return 1e3;
72
+ }
73
+ return 0;
74
+ }
75
+ };
63
76
  var inlineContent = {
64
77
  id: "inlineContent",
65
78
  evaluate: ({ node }) => {
@@ -69,7 +82,12 @@ var inlineContent = {
69
82
  return 0;
70
83
  }
71
84
  };
72
- var defaultRules = [listItemFirstChild, listWrapperDeprioritize, inlineContent];
85
+ var defaultRules = [
86
+ listItemFirstChild,
87
+ listWrapperDeprioritize,
88
+ tableStructure,
89
+ inlineContent
90
+ ];
73
91
 
74
92
  // src/helpers/edgeDetection.ts
75
93
  var DEFAULT_EDGE_CONFIG = {
@@ -200,6 +218,38 @@ function findBestDragTarget(view, coords, options) {
200
218
  const dom = view.nodeDOM(nodePos);
201
219
  return { node, pos: nodePos, depth, score, dom };
202
220
  }).filter((candidate) => candidate !== null);
221
+ const nodeAfter = $pos.nodeAfter;
222
+ if (nodeAfter && nodeAfter.isAtom && !nodeAfter.isInline) {
223
+ const nodePos = posInfo.pos;
224
+ const depth = $pos.depth + 1;
225
+ const parent = $pos.parent;
226
+ const index = $pos.index();
227
+ const siblingCount = parent.childCount;
228
+ let inAllowedContainer = true;
229
+ if (options.allowedContainers) {
230
+ inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers);
231
+ }
232
+ if (inAllowedContainer) {
233
+ const context = {
234
+ node: nodeAfter,
235
+ pos: nodePos,
236
+ depth,
237
+ parent,
238
+ index,
239
+ isFirst: index === 0,
240
+ isLast: index === siblingCount - 1,
241
+ $pos,
242
+ view
243
+ };
244
+ const score = calculateScore(context, rules, options.edgeDetection, coords);
245
+ if (score >= 0) {
246
+ const dom = view.nodeDOM(nodePos);
247
+ if (dom) {
248
+ candidates.push({ node: nodeAfter, pos: nodePos, depth, score, dom });
249
+ }
250
+ }
251
+ }
252
+ }
203
253
  if (candidates.length === 0) {
204
254
  return null;
205
255
  }
@@ -348,8 +398,9 @@ function getDragHandleRanges(event, editor, nestedOptions, dragContext) {
348
398
  if (!result.resultNode || result.pos === null) {
349
399
  return [];
350
400
  }
401
+ const offset = result.resultNode.isText || result.resultNode.isAtom ? 0 : -1;
351
402
  const $from = doc.resolve(result.pos);
352
- const $to = doc.resolve(result.pos + result.resultNode.nodeSize);
403
+ const $to = doc.resolve(result.pos + result.resultNode.nodeSize + offset);
353
404
  return getSelectionRanges($from, $to, 0);
354
405
  }
355
406
  function dragHandler(event, editor, nestedOptions, dragContext) {
@@ -393,7 +444,8 @@ function dragHandler(event, editor, nestedOptions, dragContext) {
393
444
  document.body.append(wrapper);
394
445
  event.dataTransfer.clearData();
395
446
  event.dataTransfer.setDragImage(wrapper, 0, 0);
396
- view.dragging = { slice, move: true };
447
+ const nodeSelection = selection instanceof NodeSelection ? selection : void 0;
448
+ view.dragging = { slice, move: true, node: nodeSelection };
397
449
  tr.setSelection(selection);
398
450
  view.dispatch(tr);
399
451
  document.addEventListener("drop", () => removeNode(wrapper), { once: true });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/drag-handle.ts","../src/drag-handle-plugin.ts","../src/helpers/dragHandler.ts","../src/helpers/cloneElement.ts","../src/helpers/defaultRules.ts","../src/helpers/edgeDetection.ts","../src/helpers/scoring.ts","../src/helpers/findBestDragTarget.ts","../src/helpers/findNextElementFromCursor.ts","../src/helpers/removeNode.ts","../src/helpers/getOuterNode.ts","../src/helpers/normalizeOptions.ts","../src/index.ts"],"sourcesContent":["import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'\nimport { type Editor, Extension } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\n\nimport { DragHandlePlugin } from './drag-handle-plugin.js'\nimport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nimport type { NestedOptions } from './types/options.js'\n\nexport const defaultComputePositionConfig: ComputePositionConfig = {\n placement: 'left-start',\n strategy: 'absolute',\n}\n\nexport interface DragHandleOptions {\n /**\n * Renders an element that is positioned with the floating-ui/dom package\n */\n render(): HTMLElement\n /**\n * Configuration for position computation of the drag handle\n * using the floating-ui/dom package\n */\n computePositionConfig?: ComputePositionConfig\n /**\n * A function that returns the virtual element for the drag handle.\n * This is useful when the menu needs to be positioned relative to a specific DOM element.\n */\n getReferencedVirtualElement?: () => VirtualElement | null\n /**\n * Locks the draghandle in place and visibility\n */\n locked?: boolean\n /**\n * Returns a node or null when a node is hovered over\n */\n onNodeChange?: (options: { node: Node | null; editor: Editor }) => void\n /**\n * The callback function that will be called when drag start.\n */\n onElementDragStart?: (e: DragEvent) => void\n /**\n * The callback function that will be called when drag end.\n */\n onElementDragEnd?: (e: DragEvent) => void\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 * **Values:**\n * - `false` (default): Only root-level blocks show drag handles\n * - `true`: Enable with sensible defaults (left edge detection, default rules)\n * - `NestedOptions`: Enable with custom configuration\n *\n * **Configuration options:**\n * - `rules`: Custom rules to determine which nodes are draggable\n * - `defaultRules`: Whether to include default rules (default: true)\n * - `allowedContainers`: Restrict nested dragging to specific container types\n * - `edgeDetection`: Control when to prefer parent over nested node\n * - `'left'` (default): Prefer parent near left/top edges\n * - `'right'`: Prefer parent near right/top edges (for RTL)\n * - `'both'`: Prefer parent near any horizontal edge\n * - `'none'`: Disable edge detection\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * DragHandle.configure({\n * nested: true,\n * })\n *\n * @example\n * // Restrict to specific containers\n * DragHandle.configure({\n * nested: {\n * allowedContainers: ['bulletList', 'orderedList'],\n * },\n * })\n *\n * @example\n * // With custom rules\n * DragHandle.configure({\n * nested: {\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * edgeDetection: 'none',\n * },\n * })\n */\n nested?: boolean | NestedOptions\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n dragHandle: {\n /**\n * Locks the draghandle in place and visibility\n */\n lockDragHandle: () => ReturnType\n /**\n * Unlocks the draghandle\n */\n unlockDragHandle: () => ReturnType\n /**\n * Toggle draghandle lock state\n */\n toggleDragHandle: () => ReturnType\n }\n }\n}\n\nexport const DragHandle = Extension.create<DragHandleOptions>({\n name: 'dragHandle',\n\n addOptions() {\n return {\n render() {\n const element = document.createElement('div')\n\n element.classList.add('drag-handle')\n\n return element\n },\n computePositionConfig: {},\n locked: false,\n onNodeChange: () => {\n return null\n },\n onElementDragStart: undefined,\n onElementDragEnd: undefined,\n nested: false,\n }\n },\n\n addCommands() {\n return {\n lockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = true\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n unlockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = false\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n toggleDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = !this.options.locked\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n }\n },\n\n addProseMirrorPlugins() {\n const element = this.options.render()\n const nestedOptions = normalizeNestedOptions(this.options.nested)\n\n return [\n DragHandlePlugin({\n computePositionConfig: { ...defaultComputePositionConfig, ...this.options.computePositionConfig },\n getReferencedVirtualElement: this.options.getReferencedVirtualElement,\n element,\n editor: this.editor,\n onNodeChange: this.options.onNodeChange,\n onElementDragStart: this.options.onElementDragStart,\n onElementDragEnd: this.options.onElementDragEnd,\n nestedOptions,\n }).plugin,\n ]\n },\n})\n","import { type ComputePositionConfig, type VirtualElement, computePosition } from '@floating-ui/dom'\nimport { type Editor, isFirefox } from '@tiptap/core'\nimport { isChangeOrigin } from '@tiptap/extension-collaboration'\nimport type { Node } from '@tiptap/pm/model'\nimport { type EditorState, type Transaction, Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport {\n absolutePositionToRelativePosition,\n relativePositionToAbsolutePosition,\n ySyncPluginKey,\n} from '@tiptap/y-tiptap'\n\nimport { dragHandler } from './helpers/dragHandler.js'\nimport { findElementNextToCoords } from './helpers/findNextElementFromCursor.js'\nimport { getOuterNode, getOuterNodePos } from './helpers/getOuterNode.js'\nimport { removeNode } from './helpers/removeNode.js'\nimport type { NormalizedNestedOptions } from './types/options.js'\n\ntype PluginState = {\n locked: boolean\n}\n\nconst getRelativePos = (state: EditorState, absolutePos: number) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return null\n }\n\n return absolutePositionToRelativePosition(absolutePos, ystate.type, ystate.binding.mapping)\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: y-prosemirror (and y-tiptap by extension) does not have types for relative positions\nconst getAbsolutePos = (state: EditorState, relativePos: any) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return -1\n }\n\n return relativePositionToAbsolutePosition(ystate.doc, ystate.type, relativePos, ystate.binding.mapping) || 0\n}\n\nconst getOuterDomNode = (view: EditorView, domNode: HTMLElement) => {\n let tmpDomNode = domNode\n\n // Traverse to top level node.\n while (tmpDomNode?.parentNode) {\n if (tmpDomNode.parentNode === view.dom) {\n break\n }\n\n tmpDomNode = tmpDomNode.parentNode as HTMLElement\n }\n\n return tmpDomNode\n}\n\nexport interface DragHandlePluginProps {\n pluginKey?: PluginKey | string\n editor: Editor\n element: HTMLElement\n onNodeChange?: (data: { editor: Editor; node: Node | null; pos: number }) => void\n onElementDragStart?: (e: DragEvent) => void\n onElementDragEnd?: (e: DragEvent) => void\n computePositionConfig?: ComputePositionConfig\n getReferencedVirtualElement?: () => VirtualElement | null\n nestedOptions: NormalizedNestedOptions\n}\n\nexport const dragHandlePluginDefaultKey = new PluginKey('dragHandle')\n\nexport const DragHandlePlugin = ({\n pluginKey = dragHandlePluginDefaultKey,\n element,\n editor,\n computePositionConfig,\n getReferencedVirtualElement,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n}: DragHandlePluginProps) => {\n const wrapper = document.createElement('div')\n let locked = false\n let currentNode: Node | null = null\n let currentNodePos = -1\n // biome-ignore lint/suspicious/noExplicitAny: See above - relative positions in y-prosemirror are not typed\n let currentNodeRelPos: any\n let rafId: number | null = null\n let pendingMouseCoords: { x: number; y: number } | null = null\n\n function hideHandle() {\n if (!element) {\n return\n }\n\n element.style.visibility = 'hidden'\n element.style.pointerEvents = 'none'\n }\n\n function showHandle() {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n element.style.visibility = ''\n element.style.pointerEvents = 'auto'\n }\n\n function repositionDragHandle(dom: Element) {\n const virtualElement = getReferencedVirtualElement?.() || {\n getBoundingClientRect: () => dom.getBoundingClientRect(),\n }\n\n computePosition(virtualElement, element, computePositionConfig).then(val => {\n Object.assign(element.style, {\n position: val.strategy,\n left: `${val.x}px`,\n top: `${val.y}px`,\n })\n })\n }\n\n function onDragStart(e: DragEvent) {\n onElementDragStart?.(e)\n // Push this to the end of the event cue\n // Fixes bug where incorrect drag pos is returned if drag handle has position: absolute\n // Pass the current node context to avoid recalculation issues during drag start\n dragHandler(e, editor, nestedOptions, { node: currentNode, pos: currentNodePos })\n\n if (element) {\n element.dataset.dragging = 'true'\n }\n\n setTimeout(() => {\n if (element) {\n element.style.pointerEvents = 'none'\n }\n }, 0)\n }\n\n function onDragEnd(e: DragEvent) {\n onElementDragEnd?.(e)\n hideHandle()\n if (element) {\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n }\n }\n\n function onDrop() {\n // Firefox has a bug where the caret becomes invisible after drag and drop.\n // This workaround forces Firefox to re-render the caret by toggling contentEditable.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1327834\n if (isFirefox()) {\n const editorElement = editor.view.dom\n\n // Use requestAnimationFrame to ensure the drop operation has completed\n requestAnimationFrame(() => {\n if (editorElement.isContentEditable) {\n editorElement.contentEditable = 'false'\n editorElement.contentEditable = 'true'\n }\n })\n }\n }\n\n element.addEventListener('dragstart', onDragStart)\n element.addEventListener('dragend', onDragEnd)\n document.addEventListener('drop', onDrop)\n\n wrapper.appendChild(element)\n\n return {\n unbind() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n },\n plugin: new Plugin({\n key: typeof pluginKey === 'string' ? new PluginKey(pluginKey) : pluginKey,\n\n state: {\n init() {\n return { locked: false }\n },\n apply(tr: Transaction, value: PluginState, _oldState: EditorState, state: EditorState) {\n const isLocked = tr.getMeta('lockDragHandle')\n const hideDragHandle = tr.getMeta('hideDragHandle')\n\n if (isLocked !== undefined) {\n locked = isLocked\n }\n\n if (hideDragHandle) {\n hideHandle()\n\n locked = false\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n return value\n }\n\n // Something has changed and drag handler is visible…\n if (tr.docChanged && currentNodePos !== -1 && element) {\n // Yjs replaces the entire document on every incoming change and needs a special handling.\n // If change comes from another user …\n if (isChangeOrigin(tr)) {\n // https://discuss.yjs.dev/t/y-prosemirror-mapping-a-single-relative-position-when-doc-changes/851/3\n const newPos = getAbsolutePos(state, currentNodeRelPos)\n\n if (newPos !== currentNodePos) {\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // We will get the outer node with data and position in views update method.\n }\n } else {\n // … otherwise use ProseMirror mapping to update the position.\n const newPos = tr.mapping.map(currentNodePos)\n\n if (newPos !== currentNodePos) {\n // TODO: Remove\n // console.log('Position has changed …', { old: currentNodePos, new: newPos }, tr);\n\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(state, currentNodePos)\n\n // We will get the outer node with data and position in views update method.\n }\n }\n }\n\n return value\n },\n },\n\n view: view => {\n element.draggable = true\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n\n editor.view.dom.parentElement?.appendChild(wrapper)\n\n wrapper.style.pointerEvents = 'none'\n wrapper.style.position = 'absolute'\n wrapper.style.top = '0'\n wrapper.style.left = '0'\n\n return {\n update(_, oldState) {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n // Prevent element being draggend while being open.\n if (locked) {\n element.draggable = false\n } else {\n element.draggable = true\n }\n\n // Recalculate popup position if doc has changend and drag handler is visible.\n if (view.state.doc.eq(oldState.doc) || currentNodePos === -1) {\n return\n }\n\n // Get domNode from (new) position.\n let domNode = view.nodeDOM(currentNodePos) as HTMLElement\n\n // Since old element could have been wrapped, we need to find\n // the outer node and take its position and node data.\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n const outerNode = getOuterNode(editor.state.doc, domNodePos)\n const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos) // TODO: needed?\n\n currentNode = outerNode\n currentNodePos = outerNodePos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n repositionDragHandle(domNode as Element)\n },\n\n // TODO: Kills even on hot reload\n destroy() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n\n if (element) {\n removeNode(wrapper)\n }\n },\n }\n },\n\n props: {\n handleDOMEvents: {\n keydown(view) {\n if (!element || locked) {\n return false\n }\n\n if (view.hasFocus()) {\n hideHandle()\n currentNode = null\n currentNodePos = -1\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n // We want to still continue with other keydown events.\n return false\n }\n\n return false\n },\n mouseleave(_view, e) {\n // Do not hide open popup on mouseleave.\n if (locked) {\n return false\n }\n\n // If e.target is not inside the wrapper, hide.\n if (e.target && !wrapper.contains(e.relatedTarget as HTMLElement)) {\n hideHandle()\n\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n }\n\n return false\n },\n\n mousemove(view, e) {\n // Do not continue if popup is not initialized or open.\n if (!element || locked) {\n return false\n }\n\n // Store latest mouse coords and schedule a single RAF per frame\n pendingMouseCoords = { x: e.clientX, y: e.clientY }\n\n if (rafId) {\n return false\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null\n\n if (!pendingMouseCoords) {\n return\n }\n\n const { x, y } = pendingMouseCoords\n pendingMouseCoords = null\n\n const nodeData = findElementNextToCoords({\n x,\n y,\n direction: 'right',\n editor,\n nestedOptions,\n })\n\n // Skip if there is no node next to coords\n if (!nodeData.resultElement) {\n return\n }\n\n let domNode = nodeData.resultElement as HTMLElement\n let targetNode = nodeData.resultNode\n let targetPos = nodeData.pos\n\n // In nested mode, the node data already contains the correct target\n // In non-nested mode, traverse to the top-level block\n if (!nestedOptions?.enabled) {\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n\n targetNode = getOuterNode(editor.state.doc, domNodePos)\n targetPos = getOuterNodePos(editor.state.doc, domNodePos)\n }\n\n if (targetNode !== currentNode) {\n currentNode = targetNode\n currentNodePos = targetPos ?? -1\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n // Set nodes clientRect.\n repositionDragHandle(domNode as Element)\n\n showHandle()\n }\n })\n\n return false\n },\n },\n },\n }),\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport { getSelectionRanges, NodeRangeSelection } from '@tiptap/extension-node-range'\nimport type { Node } from '@tiptap/pm/model'\nimport { type SelectionRange, NodeSelection } from '@tiptap/pm/state'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { cloneElement } from './cloneElement.js'\nimport { findElementNextToCoords } from './findNextElementFromCursor.js'\nimport { removeNode } from './removeNode.js'\n\nexport interface DragContext {\n node: Node | null\n pos: number\n}\n\nfunction getDragHandleRanges(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n): SelectionRange[] {\n const { doc } = editor.view.state\n\n // In nested mode with known context, use the pre-calculated position\n // This prevents recalculation issues when mouse position shifts during drag start\n if (nestedOptions?.enabled && dragContext?.node && dragContext.pos >= 0) {\n const nodeStart = dragContext.pos\n const nodeEnd = dragContext.pos + dragContext.node.nodeSize\n\n return [\n {\n $from: doc.resolve(nodeStart),\n $to: doc.resolve(nodeEnd),\n },\n ]\n }\n\n // Fallback: recalculate from mouse position (used in non-nested mode)\n const result = findElementNextToCoords({\n editor,\n x: event.clientX,\n y: event.clientY,\n direction: 'right',\n nestedOptions,\n })\n\n if (!result.resultNode || result.pos === null) {\n return []\n }\n\n // For non-nested mode, use depth 0 to select the outermost block\n const $from = doc.resolve(result.pos)\n const $to = doc.resolve(result.pos + result.resultNode.nodeSize)\n\n return getSelectionRanges($from, $to, 0)\n}\n\nexport function dragHandler(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n) {\n const { view } = editor\n\n if (!event.dataTransfer) {\n return\n }\n\n const { empty, $from, $to } = view.state.selection\n\n const dragHandleRanges = getDragHandleRanges(event, editor, nestedOptions, dragContext)\n\n const selectionRanges = getSelectionRanges($from, $to, 0)\n const isDragHandleWithinSelection = selectionRanges.some(range => {\n return dragHandleRanges.find(dragHandleRange => {\n return dragHandleRange.$from === range.$from && dragHandleRange.$to === range.$to\n })\n })\n\n const ranges = empty || !isDragHandleWithinSelection ? dragHandleRanges : selectionRanges\n\n if (!ranges.length) {\n return\n }\n\n const { tr } = view.state\n const wrapper = document.createElement('div')\n const from = ranges[0].$from.pos\n const to = ranges[ranges.length - 1].$to.pos\n\n // For nested mode, create slice directly to avoid NodeRangeSelection expanding to parent\n const isNestedDrag = nestedOptions?.enabled && dragContext?.node\n\n let slice\n let selection\n\n if (isNestedDrag) {\n // Create slice directly from the exact positions\n slice = view.state.doc.slice(from, to)\n\n // Use NodeSelection for nested mode to select exactly the target node\n // NodeRangeSelection would expand to the parent\n selection = NodeSelection.create(view.state.doc, from)\n } else {\n selection = NodeRangeSelection.create(view.state.doc, from, to)\n slice = selection.content()\n }\n\n ranges.forEach(range => {\n const element = view.nodeDOM(range.$from.pos) as HTMLElement\n const clonedElement = cloneElement(element)\n\n wrapper.append(clonedElement)\n })\n\n wrapper.style.position = 'absolute'\n wrapper.style.top = '-10000px'\n document.body.append(wrapper)\n\n event.dataTransfer.clearData()\n event.dataTransfer.setDragImage(wrapper, 0, 0)\n\n // tell ProseMirror the dragged content\n view.dragging = { slice, move: true }\n\n tr.setSelection(selection)\n\n view.dispatch(tr)\n\n // clean up\n document.addEventListener('drop', () => removeNode(wrapper), { once: true })\n}\n","function getCSSText(element: Element) {\n let value = ''\n const style = getComputedStyle(element)\n\n for (let i = 0; i < style.length; i += 1) {\n value += `${style[i]}:${style.getPropertyValue(style[i])};`\n }\n\n return value\n}\n\nexport function cloneElement(node: HTMLElement) {\n const clonedNode = node.cloneNode(true) as HTMLElement\n const sourceElements = [node, ...Array.from(node.getElementsByTagName('*'))] as HTMLElement[]\n const targetElements = [clonedNode, ...Array.from(clonedNode.getElementsByTagName('*'))] as HTMLElement[]\n\n sourceElements.forEach((sourceElement, index) => {\n targetElements[index].style.cssText = getCSSText(sourceElement)\n })\n\n return clonedNode\n}\n","import type { DragHandleRule } from '../types/rules.js'\n\n/**\n * The first child inside a list item is the list item's content.\n * It cannot be dragged separately - you drag the list item instead.\n *\n * Example: In `<li><p>Text</p></li>`, the paragraph is excluded,\n * but the listItem is draggable.\n */\nexport const listItemFirstChild: DragHandleRule = {\n id: 'listItemFirstChild',\n evaluate: ({ parent, isFirst }) => {\n if (!isFirst) {\n return 0\n }\n\n const listItemTypes = ['listItem', 'taskItem']\n\n if (parent && listItemTypes.includes(parent.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Nodes that contain list items (listItem/taskItem) as direct children\n * are deprioritized. This makes it easier to target individual list items\n * rather than the entire list wrapper.\n *\n * This rule detects list wrappers dynamically by checking if the first child\n * is a list item, rather than hardcoding wrapper type names.\n *\n * Users can still target the list wrapper by moving to the very edge\n * where edge detection kicks in.\n */\nexport const listWrapperDeprioritize: DragHandleRule = {\n id: 'listWrapperDeprioritize',\n evaluate: ({ node }) => {\n const listItemTypes = ['listItem', 'taskItem']\n\n const firstChild = node.firstChild\n\n if (firstChild && listItemTypes.includes(firstChild.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Inline nodes (text, marks, inline atoms) should never be drag targets.\n */\nexport const inlineContent: DragHandleRule = {\n id: 'inlineContent',\n evaluate: ({ node }) => {\n if (node.isInline || node.isText) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * All default rules.\n * Users can extend these or replace them entirely.\n */\nexport const defaultRules: DragHandleRule[] = [listItemFirstChild, listWrapperDeprioritize, inlineContent]\n","import type { EdgeDetectionConfig, EdgeDetectionPreset } from '../types/options.js'\n\n/**\n * Default edge detection configuration.\n */\nconst DEFAULT_EDGE_CONFIG: EdgeDetectionConfig = {\n edges: ['left', 'top'],\n threshold: 12,\n strength: 500,\n}\n\n/**\n * Normalizes edge detection presets or custom config into a full config object.\n * Partial configs are merged with defaults.\n *\n * @param input - The preset string or partial/full config\n * @returns A complete EdgeDetectionConfig\n */\nexport function normalizeEdgeDetection(\n input: EdgeDetectionPreset | Partial<EdgeDetectionConfig> | undefined,\n): EdgeDetectionConfig {\n if (input === undefined || input === 'left') {\n return { ...DEFAULT_EDGE_CONFIG }\n }\n\n if (input === 'right') {\n return { edges: ['right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'both') {\n return { edges: ['left', 'right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'none') {\n return { edges: [], threshold: 0, strength: 0 }\n }\n\n // Merge partial config with defaults\n return { ...DEFAULT_EDGE_CONFIG, ...input }\n}\n\n/**\n * Determines if cursor is near specified edges of an element.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against\n * @param config - The edge detection configuration\n * @returns True if the cursor is near any of the configured edges\n */\nexport function isNearEdge(\n coords: { x: number; y: number },\n element: HTMLElement,\n config: EdgeDetectionConfig,\n): boolean {\n if (config.edges.length === 0) {\n return false\n }\n\n const rect = element.getBoundingClientRect()\n const { threshold, edges } = config\n\n return edges.some(edge => {\n if (edge === 'left') {\n return coords.x - rect.left < threshold\n }\n\n if (edge === 'right') {\n return rect.right - coords.x < threshold\n }\n\n if (edge === 'top') {\n return coords.y - rect.top < threshold\n }\n\n if (edge === 'bottom') {\n return rect.bottom - coords.y < threshold\n }\n\n return false\n })\n}\n\n/**\n * Calculates score deduction for edge proximity.\n * Deeper nodes get larger deductions when near edges,\n * making shallower (parent) nodes win.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against (may be null)\n * @param config - The edge detection configuration\n * @param depth - The depth of the node in the document tree\n * @returns The score deduction to apply\n */\nexport function calculateEdgeDeduction(\n coords: { x: number; y: number },\n element: HTMLElement | null,\n config: EdgeDetectionConfig,\n depth: number,\n): number {\n if (!element || config.edges.length === 0) {\n return 0\n }\n\n if (isNearEdge(coords, element, config)) {\n return config.strength * depth\n }\n\n return 0\n}\n","import type { EdgeDetectionConfig } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { calculateEdgeDeduction } from './edgeDetection.js'\n\n/**\n * Base score for all nodes. Rules deduct from this score.\n * A node with score <= 0 is excluded from being a drag target.\n */\nexport const BASE_SCORE = 1000\n\n/**\n * Calculates the drag target score for a node.\n * Higher score = more likely to be selected.\n *\n * @param context - The rule context containing node information\n * @param rules - The rules to apply\n * @param edgeConfig - The edge detection configuration\n * @param coords - The cursor coordinates\n * @returns The calculated score, or -1 if the node is excluded\n */\nexport function calculateScore(\n context: RuleContext,\n rules: DragHandleRule[],\n edgeConfig: EdgeDetectionConfig,\n coords: { x: number; y: number },\n): number {\n let score = BASE_SCORE\n let excluded = false\n\n rules.every(rule => {\n const deduction = rule.evaluate(context)\n\n score -= deduction\n\n if (score <= 0) {\n excluded = true\n return false\n }\n\n return true\n })\n\n if (excluded) {\n return -1\n }\n\n const dom = context.view.nodeDOM(context.pos) as HTMLElement | null\n\n score -= calculateEdgeDeduction(coords, dom, edgeConfig, context.depth)\n\n if (score <= 0) {\n return -1\n }\n\n return score\n}\n","import type { Node, ResolvedPos } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { defaultRules } from './defaultRules.js'\nimport { calculateScore } from './scoring.js'\n\n/**\n * Represents a drag target with its node, position, and DOM element.\n */\nexport interface DragTarget {\n /** The ProseMirror node */\n node: Node\n\n /** The absolute position in the document */\n pos: number\n\n /** The corresponding DOM element */\n dom: HTMLElement\n}\n\n/**\n * Checks if any ancestor at or above the given depth is in the allowed list.\n *\n * @param $pos - The resolved position\n * @param depth - The current depth being checked\n * @param allowedTypes - The list of allowed node type names\n * @returns True if any ancestor is in the allowed list\n */\nfunction hasAncestorOfType($pos: ResolvedPos, depth: number, allowedTypes: string[]): boolean {\n const ancestorDepths = Array.from({ length: depth }, (_, i) => depth - 1 - i)\n\n return ancestorDepths.some(d => allowedTypes.includes($pos.node(d).type.name))\n}\n\n/**\n * Finds the best drag target at the given coordinates using the scoring system.\n *\n * @param view - The editor view\n * @param coords - The cursor coordinates\n * @param options - The normalized nested options\n * @returns The best drag target, or null if none found\n */\nexport function findBestDragTarget(\n view: EditorView,\n coords: { x: number; y: number },\n options: NormalizedNestedOptions,\n): DragTarget | null {\n // Validate coordinates are finite numbers to prevent DOM errors\n if (!Number.isFinite(coords.x) || !Number.isFinite(coords.y)) {\n return null\n }\n\n // ProseMirror expects { left, top } format for coordinates\n const posInfo = view.posAtCoords({ left: coords.x, top: coords.y })\n\n if (!posInfo) {\n return null\n }\n\n const { doc } = view.state\n const $pos = doc.resolve(posInfo.pos)\n\n const rules: DragHandleRule[] = []\n\n if (options.defaultRules) {\n rules.push(...defaultRules)\n }\n\n rules.push(...options.rules)\n\n // Start from depth 1 to exclude the doc node (depth 0) which should never be draggable\n const depthLevels = Array.from({ length: $pos.depth }, (_, i) => $pos.depth - i)\n\n const candidates = depthLevels\n .map(depth => {\n const node = $pos.node(depth)\n const nodePos = $pos.before(depth)\n\n if (options.allowedContainers && depth > 0) {\n const inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)\n\n if (!inAllowedContainer) {\n return null\n }\n }\n\n const parent = depth > 0 ? $pos.node(depth - 1) : null\n const index = depth > 0 ? $pos.index(depth - 1) : 0\n const siblingCount = parent ? parent.childCount : 1\n\n const context: RuleContext = {\n node,\n pos: nodePos,\n depth,\n parent,\n index,\n isFirst: index === 0,\n isLast: index === siblingCount - 1,\n $pos,\n view,\n }\n\n const score = calculateScore(context, rules, options.edgeDetection, coords)\n\n if (score < 0) {\n return null\n }\n\n const dom = view.nodeDOM(nodePos) as HTMLElement | null\n\n return { node, pos: nodePos, depth, score, dom }\n })\n .filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)\n\n if (candidates.length === 0) {\n return null\n }\n\n candidates.sort((a, b) => {\n if (b.score !== a.score) {\n return b.score - a.score\n }\n\n return b.depth - a.depth\n })\n\n const winner = candidates[0]\n\n if (!winner.dom) {\n return null\n }\n\n return {\n node: winner.node,\n pos: winner.pos,\n dom: winner.dom,\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { findBestDragTarget } from './findBestDragTarget.js'\n\nexport type FindElementNextToCoords = {\n x: number\n y: number\n direction?: 'left' | 'right'\n editor: Editor\n nestedOptions?: NormalizedNestedOptions\n}\n\n/**\n * Finds the draggable block element that is a direct child of view.dom\n */\nexport function findClosestTopLevelBlock(element: Element, view: EditorView): HTMLElement | undefined {\n let current: Element | null = element\n\n while (current?.parentElement && current.parentElement !== view.dom) {\n current = current.parentElement\n }\n\n return current?.parentElement === view.dom ? (current as HTMLElement) : undefined\n}\n\n/**\n * Checks if a DOMRect has valid, finite dimensions.\n */\nfunction isValidRect(rect: DOMRect): boolean {\n return (\n Number.isFinite(rect.top) &&\n Number.isFinite(rect.bottom) &&\n Number.isFinite(rect.left) &&\n Number.isFinite(rect.right) &&\n rect.width > 0 &&\n rect.height > 0\n )\n}\n\n/**\n * Clamps coordinates to content bounds with O(1) layout reads\n */\nfunction clampToContent(view: EditorView, x: number, y: number, inset = 5): { x: number; y: number } | null {\n // Validate input coordinates are finite numbers\n if (!Number.isFinite(x) || !Number.isFinite(y)) {\n return null\n }\n\n const container = view.dom\n const firstBlock = container.firstElementChild\n const lastBlock = container.lastElementChild\n\n if (!firstBlock || !lastBlock) {\n return null\n }\n\n // Clamp Y between first and last block\n const topRect = firstBlock.getBoundingClientRect()\n const botRect = lastBlock.getBoundingClientRect()\n\n // Validate bounding rects have finite values\n if (!isValidRect(topRect) || !isValidRect(botRect)) {\n return null\n }\n\n const clampedY = Math.min(Math.max(topRect.top + inset, y), botRect.bottom - inset)\n\n const epsilon = 0.5\n const sameLeft = Math.abs(topRect.left - botRect.left) < epsilon\n const sameRight = Math.abs(topRect.right - botRect.right) < epsilon\n\n let rowRect: DOMRect = topRect\n\n if (sameLeft && sameRight) {\n // Most of the time, every block has the same width\n rowRect = topRect\n } else {\n // TODO\n // find the actual block at the clamped Y\n // This case is rare, avoid for now\n }\n\n // Clamp X to the chosen block's bounds\n const clampedX = Math.min(Math.max(rowRect.left + inset, x), rowRect.right - inset)\n\n // Final validation of output coordinates\n if (!Number.isFinite(clampedX) || !Number.isFinite(clampedY)) {\n return null\n }\n\n return { x: clampedX, y: clampedY }\n}\n\nexport const findElementNextToCoords = (\n options: FindElementNextToCoords,\n): {\n resultElement: HTMLElement | null\n resultNode: Node | null\n pos: number | null\n} => {\n const { x, y, editor, nestedOptions } = options\n const { view, state } = editor\n\n const clamped = clampToContent(view, x, y, 5)\n\n // Return early if coordinates could not be clamped to valid bounds\n if (!clamped) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const { x: clampedX, y: clampedY } = clamped\n\n // When nested mode is enabled, use the scoring-based detection\n if (nestedOptions?.enabled) {\n const target = findBestDragTarget(view, { x: clampedX, y: clampedY }, nestedOptions)\n\n if (!target) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n return {\n resultElement: target.dom,\n resultNode: target.node,\n pos: target.pos,\n }\n }\n\n // Original root-level detection for non-nested mode\n const elements = view.root.elementsFromPoint(clampedX, clampedY)\n\n let block: HTMLElement | undefined\n\n Array.prototype.some.call(elements, (el: Element) => {\n if (!view.dom.contains(el)) {\n return false\n }\n const candidate = findClosestTopLevelBlock(el, view)\n if (candidate) {\n block = candidate\n return true\n }\n return false\n })\n\n if (!block) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n let pos: number\n try {\n pos = view.posAtDOM(block, 0)\n } catch {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const node = state.doc.nodeAt(pos)\n\n if (!node) {\n // This case occurs when an atom node is allowed to contain inline content.\n // We need to resolve the position here to ensure we target the correct parent node.\n const resolvedPos = state.doc.resolve(pos)\n const parent = resolvedPos.parent\n\n return {\n resultElement: block,\n resultNode: parent,\n pos: resolvedPos.start(),\n }\n }\n\n return {\n resultElement: block,\n resultNode: node,\n pos,\n }\n}\n","export function removeNode(node: HTMLElement) {\n node.parentNode?.removeChild(node)\n}\n","import type { Node } from '@tiptap/pm/model'\n\nexport const getOuterNodePos = (doc: Node, pos: number): number => {\n const resolvedPos = doc.resolve(pos)\n const { depth } = resolvedPos\n\n if (depth === 0) {\n return pos\n }\n\n const a = resolvedPos.pos - resolvedPos.parentOffset\n\n return a - 1\n}\n\nexport const getOuterNode = (doc: Node, pos: number): Node | null => {\n const node = doc.nodeAt(pos)\n const resolvedPos = doc.resolve(pos)\n\n let { depth } = resolvedPos\n let parent = node\n\n while (depth > 0) {\n const currentNode = resolvedPos.node(depth)\n\n depth -= 1\n\n if (depth === 0) {\n parent = currentNode\n }\n }\n\n return parent\n}\n","import type { NestedOptions, NormalizedNestedOptions } from '../types/options.js'\nimport { normalizeEdgeDetection } from './edgeDetection.js'\n\n/**\n * Normalizes the nested options input into a complete configuration object.\n *\n * @param input - The nested option (boolean, object, or undefined)\n * @returns A fully normalized options object\n *\n * @example\n * // Simple enable\n * normalizeNestedOptions(true)\n * // Returns: { enabled: true, rules: [], defaultRules: true, ... }\n *\n * @example\n * // Custom config\n * normalizeNestedOptions({ rules: [myRule], edgeDetection: 'none' })\n * // Returns: { enabled: true, rules: [myRule], edgeDetection: { edges: [], ... } }\n */\nexport function normalizeNestedOptions(input: boolean | NestedOptions | undefined): NormalizedNestedOptions {\n if (input === false || input === undefined) {\n return {\n enabled: false,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('none'),\n }\n }\n\n if (input === true) {\n return {\n enabled: true,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('left'),\n }\n }\n\n return {\n enabled: true,\n rules: input.rules ?? [],\n defaultRules: input.defaultRules ?? true,\n allowedContainers: input.allowedContainers,\n edgeDetection: normalizeEdgeDetection(input.edgeDetection),\n }\n}\n","import { DragHandle } from './drag-handle.js'\n\nexport * from './drag-handle.js'\nexport * from './drag-handle-plugin.js'\nexport { defaultRules } from './helpers/defaultRules.js'\nexport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nexport type {\n EdgeDetectionConfig,\n EdgeDetectionPreset,\n NestedOptions,\n NormalizedNestedOptions,\n} from './types/options.js'\nexport type { DragHandleRule, RuleContext } from './types/rules.js'\n\nexport default DragHandle\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/drag-handle.ts","../src/drag-handle-plugin.ts","../src/helpers/dragHandler.ts","../src/helpers/cloneElement.ts","../src/helpers/defaultRules.ts","../src/helpers/edgeDetection.ts","../src/helpers/scoring.ts","../src/helpers/findBestDragTarget.ts","../src/helpers/findNextElementFromCursor.ts","../src/helpers/removeNode.ts","../src/helpers/getOuterNode.ts","../src/helpers/normalizeOptions.ts","../src/index.ts"],"sourcesContent":["import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'\nimport { type Editor, Extension } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\n\nimport { DragHandlePlugin } from './drag-handle-plugin.js'\nimport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nimport type { NestedOptions } from './types/options.js'\n\nexport const defaultComputePositionConfig: ComputePositionConfig = {\n placement: 'left-start',\n strategy: 'absolute',\n}\n\nexport interface DragHandleOptions {\n /**\n * Renders an element that is positioned with the floating-ui/dom package\n */\n render(): HTMLElement\n /**\n * Configuration for position computation of the drag handle\n * using the floating-ui/dom package\n */\n computePositionConfig?: ComputePositionConfig\n /**\n * A function that returns the virtual element for the drag handle.\n * This is useful when the menu needs to be positioned relative to a specific DOM element.\n */\n getReferencedVirtualElement?: () => VirtualElement | null\n /**\n * Locks the draghandle in place and visibility\n */\n locked?: boolean\n /**\n * Returns a node or null when a node is hovered over\n */\n onNodeChange?: (options: { node: Node | null; editor: Editor }) => void\n /**\n * The callback function that will be called when drag start.\n */\n onElementDragStart?: (e: DragEvent) => void\n /**\n * The callback function that will be called when drag end.\n */\n onElementDragEnd?: (e: DragEvent) => void\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 * **Values:**\n * - `false` (default): Only root-level blocks show drag handles\n * - `true`: Enable with sensible defaults (left edge detection, default rules)\n * - `NestedOptions`: Enable with custom configuration\n *\n * **Configuration options:**\n * - `rules`: Custom rules to determine which nodes are draggable\n * - `defaultRules`: Whether to include default rules (default: true)\n * - `allowedContainers`: Restrict nested dragging to specific container types\n * - `edgeDetection`: Control when to prefer parent over nested node\n * - `'left'` (default): Prefer parent near left/top edges\n * - `'right'`: Prefer parent near right/top edges (for RTL)\n * - `'both'`: Prefer parent near any horizontal edge\n * - `'none'`: Disable edge detection\n *\n * @default false\n *\n * @example\n * // Simple enable with sensible defaults\n * DragHandle.configure({\n * nested: true,\n * })\n *\n * @example\n * // Restrict to specific containers\n * DragHandle.configure({\n * nested: {\n * allowedContainers: ['bulletList', 'orderedList'],\n * },\n * })\n *\n * @example\n * // With custom rules\n * DragHandle.configure({\n * nested: {\n * rules: [{\n * id: 'excludeCodeBlocks',\n * evaluate: ({ node }) => node.type.name === 'codeBlock' ? 1000 : 0,\n * }],\n * edgeDetection: 'none',\n * },\n * })\n */\n nested?: boolean | NestedOptions\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n dragHandle: {\n /**\n * Locks the draghandle in place and visibility\n */\n lockDragHandle: () => ReturnType\n /**\n * Unlocks the draghandle\n */\n unlockDragHandle: () => ReturnType\n /**\n * Toggle draghandle lock state\n */\n toggleDragHandle: () => ReturnType\n }\n }\n}\n\nexport const DragHandle = Extension.create<DragHandleOptions>({\n name: 'dragHandle',\n\n addOptions() {\n return {\n render() {\n const element = document.createElement('div')\n\n element.classList.add('drag-handle')\n\n return element\n },\n computePositionConfig: {},\n locked: false,\n onNodeChange: () => {\n return null\n },\n onElementDragStart: undefined,\n onElementDragEnd: undefined,\n nested: false,\n }\n },\n\n addCommands() {\n return {\n lockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = true\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n unlockDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = false\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n toggleDragHandle:\n () =>\n ({ editor }) => {\n this.options.locked = !this.options.locked\n return editor.commands.setMeta('lockDragHandle', this.options.locked)\n },\n }\n },\n\n addProseMirrorPlugins() {\n const element = this.options.render()\n const nestedOptions = normalizeNestedOptions(this.options.nested)\n\n return [\n DragHandlePlugin({\n computePositionConfig: { ...defaultComputePositionConfig, ...this.options.computePositionConfig },\n getReferencedVirtualElement: this.options.getReferencedVirtualElement,\n element,\n editor: this.editor,\n onNodeChange: this.options.onNodeChange,\n onElementDragStart: this.options.onElementDragStart,\n onElementDragEnd: this.options.onElementDragEnd,\n nestedOptions,\n }).plugin,\n ]\n },\n})\n","import { type ComputePositionConfig, type VirtualElement, computePosition } from '@floating-ui/dom'\nimport { type Editor, isFirefox } from '@tiptap/core'\nimport { isChangeOrigin } from '@tiptap/extension-collaboration'\nimport type { Node } from '@tiptap/pm/model'\nimport { type EditorState, type Transaction, Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\nimport {\n absolutePositionToRelativePosition,\n relativePositionToAbsolutePosition,\n ySyncPluginKey,\n} from '@tiptap/y-tiptap'\n\nimport { dragHandler } from './helpers/dragHandler.js'\nimport { findElementNextToCoords } from './helpers/findNextElementFromCursor.js'\nimport { getOuterNode, getOuterNodePos } from './helpers/getOuterNode.js'\nimport { removeNode } from './helpers/removeNode.js'\nimport type { NormalizedNestedOptions } from './types/options.js'\n\ntype PluginState = {\n locked: boolean\n}\n\nconst getRelativePos = (state: EditorState, absolutePos: number) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return null\n }\n\n return absolutePositionToRelativePosition(absolutePos, ystate.type, ystate.binding.mapping)\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: y-prosemirror (and y-tiptap by extension) does not have types for relative positions\nconst getAbsolutePos = (state: EditorState, relativePos: any) => {\n const ystate = ySyncPluginKey.getState(state)\n\n if (!ystate) {\n return -1\n }\n\n return relativePositionToAbsolutePosition(ystate.doc, ystate.type, relativePos, ystate.binding.mapping) || 0\n}\n\nconst getOuterDomNode = (view: EditorView, domNode: HTMLElement) => {\n let tmpDomNode = domNode\n\n // Traverse to top level node.\n while (tmpDomNode?.parentNode) {\n if (tmpDomNode.parentNode === view.dom) {\n break\n }\n\n tmpDomNode = tmpDomNode.parentNode as HTMLElement\n }\n\n return tmpDomNode\n}\n\nexport interface DragHandlePluginProps {\n pluginKey?: PluginKey | string\n editor: Editor\n element: HTMLElement\n onNodeChange?: (data: { editor: Editor; node: Node | null; pos: number }) => void\n onElementDragStart?: (e: DragEvent) => void\n onElementDragEnd?: (e: DragEvent) => void\n computePositionConfig?: ComputePositionConfig\n getReferencedVirtualElement?: () => VirtualElement | null\n nestedOptions: NormalizedNestedOptions\n}\n\nexport const dragHandlePluginDefaultKey = new PluginKey('dragHandle')\n\nexport const DragHandlePlugin = ({\n pluginKey = dragHandlePluginDefaultKey,\n element,\n editor,\n computePositionConfig,\n getReferencedVirtualElement,\n onNodeChange,\n onElementDragStart,\n onElementDragEnd,\n nestedOptions,\n}: DragHandlePluginProps) => {\n const wrapper = document.createElement('div')\n let locked = false\n let currentNode: Node | null = null\n let currentNodePos = -1\n // biome-ignore lint/suspicious/noExplicitAny: See above - relative positions in y-prosemirror are not typed\n let currentNodeRelPos: any\n let rafId: number | null = null\n let pendingMouseCoords: { x: number; y: number } | null = null\n\n function hideHandle() {\n if (!element) {\n return\n }\n\n element.style.visibility = 'hidden'\n element.style.pointerEvents = 'none'\n }\n\n function showHandle() {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n element.style.visibility = ''\n element.style.pointerEvents = 'auto'\n }\n\n function repositionDragHandle(dom: Element) {\n const virtualElement = getReferencedVirtualElement?.() || {\n getBoundingClientRect: () => dom.getBoundingClientRect(),\n }\n\n computePosition(virtualElement, element, computePositionConfig).then(val => {\n Object.assign(element.style, {\n position: val.strategy,\n left: `${val.x}px`,\n top: `${val.y}px`,\n })\n })\n }\n\n function onDragStart(e: DragEvent) {\n onElementDragStart?.(e)\n // Push this to the end of the event cue\n // Fixes bug where incorrect drag pos is returned if drag handle has position: absolute\n // Pass the current node context to avoid recalculation issues during drag start\n dragHandler(e, editor, nestedOptions, { node: currentNode, pos: currentNodePos })\n\n if (element) {\n element.dataset.dragging = 'true'\n }\n\n setTimeout(() => {\n if (element) {\n element.style.pointerEvents = 'none'\n }\n }, 0)\n }\n\n function onDragEnd(e: DragEvent) {\n onElementDragEnd?.(e)\n hideHandle()\n if (element) {\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n }\n }\n\n function onDrop() {\n // Firefox has a bug where the caret becomes invisible after drag and drop.\n // This workaround forces Firefox to re-render the caret by toggling contentEditable.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1327834\n if (isFirefox()) {\n const editorElement = editor.view.dom\n\n // Use requestAnimationFrame to ensure the drop operation has completed\n requestAnimationFrame(() => {\n if (editorElement.isContentEditable) {\n editorElement.contentEditable = 'false'\n editorElement.contentEditable = 'true'\n }\n })\n }\n }\n\n element.addEventListener('dragstart', onDragStart)\n element.addEventListener('dragend', onDragEnd)\n document.addEventListener('drop', onDrop)\n\n wrapper.appendChild(element)\n\n return {\n unbind() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n },\n plugin: new Plugin({\n key: typeof pluginKey === 'string' ? new PluginKey(pluginKey) : pluginKey,\n\n state: {\n init() {\n return { locked: false }\n },\n apply(tr: Transaction, value: PluginState, _oldState: EditorState, state: EditorState) {\n const isLocked = tr.getMeta('lockDragHandle')\n const hideDragHandle = tr.getMeta('hideDragHandle')\n\n if (isLocked !== undefined) {\n locked = isLocked\n }\n\n if (hideDragHandle) {\n hideHandle()\n\n locked = false\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n return value\n }\n\n // Something has changed and drag handler is visible…\n if (tr.docChanged && currentNodePos !== -1 && element) {\n // Yjs replaces the entire document on every incoming change and needs a special handling.\n // If change comes from another user …\n if (isChangeOrigin(tr)) {\n // https://discuss.yjs.dev/t/y-prosemirror-mapping-a-single-relative-position-when-doc-changes/851/3\n const newPos = getAbsolutePos(state, currentNodeRelPos)\n\n if (newPos !== currentNodePos) {\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // We will get the outer node with data and position in views update method.\n }\n } else {\n // … otherwise use ProseMirror mapping to update the position.\n const newPos = tr.mapping.map(currentNodePos)\n\n if (newPos !== currentNodePos) {\n // TODO: Remove\n // console.log('Position has changed …', { old: currentNodePos, new: newPos }, tr);\n\n // Set the new position for our current node.\n currentNodePos = newPos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(state, currentNodePos)\n\n // We will get the outer node with data and position in views update method.\n }\n }\n }\n\n return value\n },\n },\n\n view: view => {\n element.draggable = true\n element.style.pointerEvents = 'auto'\n element.dataset.dragging = 'false'\n\n editor.view.dom.parentElement?.appendChild(wrapper)\n\n wrapper.style.pointerEvents = 'none'\n wrapper.style.position = 'absolute'\n wrapper.style.top = '0'\n wrapper.style.left = '0'\n\n return {\n update(_, oldState) {\n if (!element) {\n return\n }\n\n if (!editor.isEditable) {\n hideHandle()\n return\n }\n\n // Prevent element being draggend while being open.\n if (locked) {\n element.draggable = false\n } else {\n element.draggable = true\n }\n\n // Recalculate popup position if doc has changend and drag handler is visible.\n if (view.state.doc.eq(oldState.doc) || currentNodePos === -1) {\n return\n }\n\n // Get domNode from (new) position.\n let domNode = view.nodeDOM(currentNodePos) as HTMLElement\n\n // Since old element could have been wrapped, we need to find\n // the outer node and take its position and node data.\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n const outerNode = getOuterNode(editor.state.doc, domNodePos)\n const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos) // TODO: needed?\n\n currentNode = outerNode\n currentNodePos = outerNodePos\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n repositionDragHandle(domNode as Element)\n },\n\n // TODO: Kills even on hot reload\n destroy() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\n document.removeEventListener('drop', onDrop)\n\n if (rafId) {\n cancelAnimationFrame(rafId)\n rafId = null\n pendingMouseCoords = null\n }\n\n if (element) {\n removeNode(wrapper)\n }\n },\n }\n },\n\n props: {\n handleDOMEvents: {\n keydown(view) {\n if (!element || locked) {\n return false\n }\n\n if (view.hasFocus()) {\n hideHandle()\n currentNode = null\n currentNodePos = -1\n onNodeChange?.({ editor, node: null, pos: -1 })\n\n // We want to still continue with other keydown events.\n return false\n }\n\n return false\n },\n mouseleave(_view, e) {\n // Do not hide open popup on mouseleave.\n if (locked) {\n return false\n }\n\n // If e.target is not inside the wrapper, hide.\n if (e.target && !wrapper.contains(e.relatedTarget as HTMLElement)) {\n hideHandle()\n\n currentNode = null\n currentNodePos = -1\n\n onNodeChange?.({ editor, node: null, pos: -1 })\n }\n\n return false\n },\n\n mousemove(view, e) {\n // Do not continue if popup is not initialized or open.\n if (!element || locked) {\n return false\n }\n\n // Store latest mouse coords and schedule a single RAF per frame\n pendingMouseCoords = { x: e.clientX, y: e.clientY }\n\n if (rafId) {\n return false\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null\n\n if (!pendingMouseCoords) {\n return\n }\n\n const { x, y } = pendingMouseCoords\n pendingMouseCoords = null\n\n const nodeData = findElementNextToCoords({\n x,\n y,\n direction: 'right',\n editor,\n nestedOptions,\n })\n\n // Skip if there is no node next to coords\n if (!nodeData.resultElement) {\n return\n }\n\n let domNode = nodeData.resultElement as HTMLElement\n let targetNode = nodeData.resultNode\n let targetPos = nodeData.pos\n\n // In nested mode, the node data already contains the correct target\n // In non-nested mode, traverse to the top-level block\n if (!nestedOptions?.enabled) {\n domNode = getOuterDomNode(view, domNode)\n\n // Skip if domNode is editor dom.\n if (domNode === view.dom) {\n return\n }\n\n // We only want `Element`.\n if (domNode?.nodeType !== 1) {\n return\n }\n\n const domNodePos = view.posAtDOM(domNode, 0)\n\n targetNode = getOuterNode(editor.state.doc, domNodePos)\n targetPos = getOuterNodePos(editor.state.doc, domNodePos)\n }\n\n if (targetNode !== currentNode) {\n currentNode = targetNode\n currentNodePos = targetPos ?? -1\n\n // Memorize relative position to retrieve absolute position in case of collaboration\n currentNodeRelPos = getRelativePos(view.state, currentNodePos)\n\n onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n // Set nodes clientRect.\n repositionDragHandle(domNode as Element)\n\n showHandle()\n }\n })\n\n return false\n },\n },\n },\n }),\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport { getSelectionRanges, NodeRangeSelection } from '@tiptap/extension-node-range'\nimport type { Node } from '@tiptap/pm/model'\nimport { type SelectionRange, NodeSelection } from '@tiptap/pm/state'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { cloneElement } from './cloneElement.js'\nimport { findElementNextToCoords } from './findNextElementFromCursor.js'\nimport { removeNode } from './removeNode.js'\n\nexport interface DragContext {\n node: Node | null\n pos: number\n}\n\nfunction getDragHandleRanges(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n): SelectionRange[] {\n const { doc } = editor.view.state\n\n // In nested mode with known context, use the pre-calculated position\n // This prevents recalculation issues when mouse position shifts during drag start\n if (nestedOptions?.enabled && dragContext?.node && dragContext.pos >= 0) {\n const nodeStart = dragContext.pos\n const nodeEnd = dragContext.pos + dragContext.node.nodeSize\n\n return [\n {\n $from: doc.resolve(nodeStart),\n $to: doc.resolve(nodeEnd),\n },\n ]\n }\n\n // Fallback: recalculate from mouse position (used in non-nested mode)\n const result = findElementNextToCoords({\n editor,\n x: event.clientX,\n y: event.clientY,\n direction: 'right',\n nestedOptions,\n })\n\n if (!result.resultNode || result.pos === null) {\n return []\n }\n\n // For non-nested mode, use depth 0 to select the outermost block\n // Atom nodes (e.g. images) have nodeSize=1 with no opening/closing tokens,\n // so we must not subtract 1 or we'll create an empty range.\n const offset = result.resultNode.isText || result.resultNode.isAtom ? 0 : -1\n const $from = doc.resolve(result.pos)\n const $to = doc.resolve(result.pos + result.resultNode.nodeSize + offset)\n\n return getSelectionRanges($from, $to, 0)\n}\n\nexport function dragHandler(\n event: DragEvent,\n editor: Editor,\n nestedOptions?: NormalizedNestedOptions,\n dragContext?: DragContext,\n) {\n const { view } = editor\n\n if (!event.dataTransfer) {\n return\n }\n\n const { empty, $from, $to } = view.state.selection\n\n const dragHandleRanges = getDragHandleRanges(event, editor, nestedOptions, dragContext)\n\n const selectionRanges = getSelectionRanges($from, $to, 0)\n const isDragHandleWithinSelection = selectionRanges.some(range => {\n return dragHandleRanges.find(dragHandleRange => {\n return dragHandleRange.$from === range.$from && dragHandleRange.$to === range.$to\n })\n })\n\n const ranges = empty || !isDragHandleWithinSelection ? dragHandleRanges : selectionRanges\n\n if (!ranges.length) {\n return\n }\n\n const { tr } = view.state\n const wrapper = document.createElement('div')\n const from = ranges[0].$from.pos\n const to = ranges[ranges.length - 1].$to.pos\n\n // For nested mode, create slice directly to avoid NodeRangeSelection expanding to parent\n const isNestedDrag = nestedOptions?.enabled && dragContext?.node\n\n let slice\n let selection\n\n if (isNestedDrag) {\n // Create slice directly from the exact positions\n slice = view.state.doc.slice(from, to)\n\n // Use NodeSelection for nested mode to select exactly the target node\n // NodeRangeSelection would expand to the parent\n selection = NodeSelection.create(view.state.doc, from)\n } else {\n selection = NodeRangeSelection.create(view.state.doc, from, to)\n slice = selection.content()\n }\n\n ranges.forEach(range => {\n const element = view.nodeDOM(range.$from.pos) as HTMLElement\n const clonedElement = cloneElement(element)\n\n wrapper.append(clonedElement)\n })\n\n wrapper.style.position = 'absolute'\n wrapper.style.top = '-10000px'\n document.body.append(wrapper)\n\n event.dataTransfer.clearData()\n event.dataTransfer.setDragImage(wrapper, 0, 0)\n\n // Tell ProseMirror the dragged content.\n // Pass the NodeSelection as `node` so ProseMirror's drop handler can use it\n // to precisely delete the original node via `node.replace(tr)`. Without this,\n // ProseMirror falls back to `tr.deleteSelection()` which relies on the current\n // selection — but the browser may change the selection during drag, causing the\n // original node to not be deleted on drop.\n const nodeSelection = selection instanceof NodeSelection ? selection : undefined\n\n // The `node` property is used at runtime by ProseMirror's drop handler but is\n // not exposed in the public type declaration for `view.dragging`.\n view.dragging = { slice, move: true, node: nodeSelection } as typeof view.dragging\n\n tr.setSelection(selection)\n\n view.dispatch(tr)\n\n // clean up\n document.addEventListener('drop', () => removeNode(wrapper), { once: true })\n}\n","function getCSSText(element: Element) {\n let value = ''\n const style = getComputedStyle(element)\n\n for (let i = 0; i < style.length; i += 1) {\n value += `${style[i]}:${style.getPropertyValue(style[i])};`\n }\n\n return value\n}\n\nexport function cloneElement(node: HTMLElement) {\n const clonedNode = node.cloneNode(true) as HTMLElement\n const sourceElements = [node, ...Array.from(node.getElementsByTagName('*'))] as HTMLElement[]\n const targetElements = [clonedNode, ...Array.from(clonedNode.getElementsByTagName('*'))] as HTMLElement[]\n\n sourceElements.forEach((sourceElement, index) => {\n targetElements[index].style.cssText = getCSSText(sourceElement)\n })\n\n return clonedNode\n}\n","import type { DragHandleRule } from '../types/rules.js'\n\n/**\n * The first child inside a list item is the list item's content.\n * It cannot be dragged separately - you drag the list item instead.\n *\n * Example: In `<li><p>Text</p></li>`, the paragraph is excluded,\n * but the listItem is draggable.\n */\nexport const listItemFirstChild: DragHandleRule = {\n id: 'listItemFirstChild',\n evaluate: ({ parent, isFirst }) => {\n if (!isFirst) {\n return 0\n }\n\n const listItemTypes = ['listItem', 'taskItem']\n\n if (parent && listItemTypes.includes(parent.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Nodes that contain list items (listItem/taskItem) as direct children\n * are deprioritized. This makes it easier to target individual list items\n * rather than the entire list wrapper.\n *\n * This rule detects list wrappers dynamically by checking if the first child\n * is a list item, rather than hardcoding wrapper type names.\n *\n * Users can still target the list wrapper by moving to the very edge\n * where edge detection kicks in.\n */\nexport const listWrapperDeprioritize: DragHandleRule = {\n id: 'listWrapperDeprioritize',\n evaluate: ({ node }) => {\n const listItemTypes = ['listItem', 'taskItem']\n\n const firstChild = node.firstChild\n\n if (firstChild && listItemTypes.includes(firstChild.type.name)) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Table rows, cells, and content inside table headers should never be drag targets.\n * Table dragging is handled by table extensions. Only the table wrapper\n * itself or content inside regular table cells should be draggable.\n */\nexport const tableStructure: DragHandleRule = {\n id: 'tableStructure',\n evaluate: ({ node, parent }) => {\n const tableStructureTypes = ['tableRow', 'tableCell', 'tableHeader']\n\n if (tableStructureTypes.includes(node.type.name)) {\n return 1000\n }\n\n if (parent && parent.type.name === 'tableHeader') {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * Inline nodes (text, marks, inline atoms) should never be drag targets.\n */\nexport const inlineContent: DragHandleRule = {\n id: 'inlineContent',\n evaluate: ({ node }) => {\n if (node.isInline || node.isText) {\n return 1000\n }\n\n return 0\n },\n}\n\n/**\n * All default rules.\n * Users can extend these or replace them entirely.\n */\nexport const defaultRules: DragHandleRule[] = [\n listItemFirstChild,\n listWrapperDeprioritize,\n tableStructure,\n inlineContent,\n]\n","import type { EdgeDetectionConfig, EdgeDetectionPreset } from '../types/options.js'\n\n/**\n * Default edge detection configuration.\n */\nconst DEFAULT_EDGE_CONFIG: EdgeDetectionConfig = {\n edges: ['left', 'top'],\n threshold: 12,\n strength: 500,\n}\n\n/**\n * Normalizes edge detection presets or custom config into a full config object.\n * Partial configs are merged with defaults.\n *\n * @param input - The preset string or partial/full config\n * @returns A complete EdgeDetectionConfig\n */\nexport function normalizeEdgeDetection(\n input: EdgeDetectionPreset | Partial<EdgeDetectionConfig> | undefined,\n): EdgeDetectionConfig {\n if (input === undefined || input === 'left') {\n return { ...DEFAULT_EDGE_CONFIG }\n }\n\n if (input === 'right') {\n return { edges: ['right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'both') {\n return { edges: ['left', 'right', 'top'], threshold: 12, strength: 500 }\n }\n\n if (input === 'none') {\n return { edges: [], threshold: 0, strength: 0 }\n }\n\n // Merge partial config with defaults\n return { ...DEFAULT_EDGE_CONFIG, ...input }\n}\n\n/**\n * Determines if cursor is near specified edges of an element.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against\n * @param config - The edge detection configuration\n * @returns True if the cursor is near any of the configured edges\n */\nexport function isNearEdge(\n coords: { x: number; y: number },\n element: HTMLElement,\n config: EdgeDetectionConfig,\n): boolean {\n if (config.edges.length === 0) {\n return false\n }\n\n const rect = element.getBoundingClientRect()\n const { threshold, edges } = config\n\n return edges.some(edge => {\n if (edge === 'left') {\n return coords.x - rect.left < threshold\n }\n\n if (edge === 'right') {\n return rect.right - coords.x < threshold\n }\n\n if (edge === 'top') {\n return coords.y - rect.top < threshold\n }\n\n if (edge === 'bottom') {\n return rect.bottom - coords.y < threshold\n }\n\n return false\n })\n}\n\n/**\n * Calculates score deduction for edge proximity.\n * Deeper nodes get larger deductions when near edges,\n * making shallower (parent) nodes win.\n *\n * @param coords - The cursor coordinates\n * @param element - The element to check against (may be null)\n * @param config - The edge detection configuration\n * @param depth - The depth of the node in the document tree\n * @returns The score deduction to apply\n */\nexport function calculateEdgeDeduction(\n coords: { x: number; y: number },\n element: HTMLElement | null,\n config: EdgeDetectionConfig,\n depth: number,\n): number {\n if (!element || config.edges.length === 0) {\n return 0\n }\n\n if (isNearEdge(coords, element, config)) {\n return config.strength * depth\n }\n\n return 0\n}\n","import type { EdgeDetectionConfig } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { calculateEdgeDeduction } from './edgeDetection.js'\n\n/**\n * Base score for all nodes. Rules deduct from this score.\n * A node with score <= 0 is excluded from being a drag target.\n */\nexport const BASE_SCORE = 1000\n\n/**\n * Calculates the drag target score for a node.\n * Higher score = more likely to be selected.\n *\n * @param context - The rule context containing node information\n * @param rules - The rules to apply\n * @param edgeConfig - The edge detection configuration\n * @param coords - The cursor coordinates\n * @returns The calculated score, or -1 if the node is excluded\n */\nexport function calculateScore(\n context: RuleContext,\n rules: DragHandleRule[],\n edgeConfig: EdgeDetectionConfig,\n coords: { x: number; y: number },\n): number {\n let score = BASE_SCORE\n let excluded = false\n\n rules.every(rule => {\n const deduction = rule.evaluate(context)\n\n score -= deduction\n\n if (score <= 0) {\n excluded = true\n return false\n }\n\n return true\n })\n\n if (excluded) {\n return -1\n }\n\n const dom = context.view.nodeDOM(context.pos) as HTMLElement | null\n\n score -= calculateEdgeDeduction(coords, dom, edgeConfig, context.depth)\n\n if (score <= 0) {\n return -1\n }\n\n return score\n}\n","import type { Node, ResolvedPos } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport type { DragHandleRule, RuleContext } from '../types/rules.js'\nimport { defaultRules } from './defaultRules.js'\nimport { calculateScore } from './scoring.js'\n\n/**\n * Represents a drag target with its node, position, and DOM element.\n */\nexport interface DragTarget {\n /** The ProseMirror node */\n node: Node\n\n /** The absolute position in the document */\n pos: number\n\n /** The corresponding DOM element */\n dom: HTMLElement\n}\n\n/**\n * Checks if any ancestor at or above the given depth is in the allowed list.\n *\n * @param $pos - The resolved position\n * @param depth - The current depth being checked\n * @param allowedTypes - The list of allowed node type names\n * @returns True if any ancestor is in the allowed list\n */\nfunction hasAncestorOfType($pos: ResolvedPos, depth: number, allowedTypes: string[]): boolean {\n const ancestorDepths = Array.from({ length: depth }, (_, i) => depth - 1 - i)\n\n return ancestorDepths.some(d => allowedTypes.includes($pos.node(d).type.name))\n}\n\n/**\n * Finds the best drag target at the given coordinates using the scoring system.\n *\n * @param view - The editor view\n * @param coords - The cursor coordinates\n * @param options - The normalized nested options\n * @returns The best drag target, or null if none found\n */\nexport function findBestDragTarget(\n view: EditorView,\n coords: { x: number; y: number },\n options: NormalizedNestedOptions,\n): DragTarget | null {\n // Validate coordinates are finite numbers to prevent DOM errors\n if (!Number.isFinite(coords.x) || !Number.isFinite(coords.y)) {\n return null\n }\n\n // ProseMirror expects { left, top } format for coordinates\n const posInfo = view.posAtCoords({ left: coords.x, top: coords.y })\n\n if (!posInfo) {\n return null\n }\n\n const { doc } = view.state\n const $pos = doc.resolve(posInfo.pos)\n\n const rules: DragHandleRule[] = []\n\n if (options.defaultRules) {\n rules.push(...defaultRules)\n }\n\n rules.push(...options.rules)\n\n // Start from depth 1 to exclude the doc node (depth 0) which should never be draggable\n const depthLevels = Array.from({ length: $pos.depth }, (_, i) => $pos.depth - i)\n\n const candidates = depthLevels\n .map(depth => {\n const node = $pos.node(depth)\n const nodePos = $pos.before(depth)\n\n if (options.allowedContainers && depth > 0) {\n const inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)\n\n if (!inAllowedContainer) {\n return null\n }\n }\n\n const parent = depth > 0 ? $pos.node(depth - 1) : null\n const index = depth > 0 ? $pos.index(depth - 1) : 0\n const siblingCount = parent ? parent.childCount : 1\n\n const context: RuleContext = {\n node,\n pos: nodePos,\n depth,\n parent,\n index,\n isFirst: index === 0,\n isLast: index === siblingCount - 1,\n $pos,\n view,\n }\n\n const score = calculateScore(context, rules, options.edgeDetection, coords)\n\n if (score < 0) {\n return null\n }\n\n const dom = view.nodeDOM(nodePos) as HTMLElement | null\n\n return { node, pos: nodePos, depth, score, dom }\n })\n .filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)\n\n // Atom/leaf nodes (e.g. images) are not ancestors of $pos — they sit at $pos.nodeAfter.\n // The depth loop above only walks ancestor nodes, so these are missed entirely.\n // Check for a nodeAfter and evaluate it as an additional candidate.\n const nodeAfter = $pos.nodeAfter\n\n if (nodeAfter && nodeAfter.isAtom && !nodeAfter.isInline) {\n const nodePos = posInfo.pos\n const depth = $pos.depth + 1\n const parent = $pos.parent\n const index = $pos.index()\n const siblingCount = parent.childCount\n\n let inAllowedContainer = true\n\n if (options.allowedContainers) {\n inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)\n }\n\n if (inAllowedContainer) {\n const context: RuleContext = {\n node: nodeAfter,\n pos: nodePos,\n depth,\n parent,\n index,\n isFirst: index === 0,\n isLast: index === siblingCount - 1,\n $pos,\n view,\n }\n\n const score = calculateScore(context, rules, options.edgeDetection, coords)\n\n if (score >= 0) {\n const dom = view.nodeDOM(nodePos) as HTMLElement | null\n\n if (dom) {\n candidates.push({ node: nodeAfter, pos: nodePos, depth, score, dom })\n }\n }\n }\n }\n\n if (candidates.length === 0) {\n return null\n }\n\n candidates.sort((a, b) => {\n if (b.score !== a.score) {\n return b.score - a.score\n }\n\n return b.depth - a.depth\n })\n\n const winner = candidates[0]\n\n if (!winner.dom) {\n return null\n }\n\n return {\n node: winner.node,\n pos: winner.pos,\n dom: winner.dom,\n }\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport type { NormalizedNestedOptions } from '../types/options.js'\nimport { findBestDragTarget } from './findBestDragTarget.js'\n\nexport type FindElementNextToCoords = {\n x: number\n y: number\n direction?: 'left' | 'right'\n editor: Editor\n nestedOptions?: NormalizedNestedOptions\n}\n\n/**\n * Finds the draggable block element that is a direct child of view.dom\n */\nexport function findClosestTopLevelBlock(element: Element, view: EditorView): HTMLElement | undefined {\n let current: Element | null = element\n\n while (current?.parentElement && current.parentElement !== view.dom) {\n current = current.parentElement\n }\n\n return current?.parentElement === view.dom ? (current as HTMLElement) : undefined\n}\n\n/**\n * Checks if a DOMRect has valid, finite dimensions.\n */\nfunction isValidRect(rect: DOMRect): boolean {\n return (\n Number.isFinite(rect.top) &&\n Number.isFinite(rect.bottom) &&\n Number.isFinite(rect.left) &&\n Number.isFinite(rect.right) &&\n rect.width > 0 &&\n rect.height > 0\n )\n}\n\n/**\n * Clamps coordinates to content bounds with O(1) layout reads\n */\nfunction clampToContent(view: EditorView, x: number, y: number, inset = 5): { x: number; y: number } | null {\n // Validate input coordinates are finite numbers\n if (!Number.isFinite(x) || !Number.isFinite(y)) {\n return null\n }\n\n const container = view.dom\n const firstBlock = container.firstElementChild\n const lastBlock = container.lastElementChild\n\n if (!firstBlock || !lastBlock) {\n return null\n }\n\n // Clamp Y between first and last block\n const topRect = firstBlock.getBoundingClientRect()\n const botRect = lastBlock.getBoundingClientRect()\n\n // Validate bounding rects have finite values\n if (!isValidRect(topRect) || !isValidRect(botRect)) {\n return null\n }\n\n const clampedY = Math.min(Math.max(topRect.top + inset, y), botRect.bottom - inset)\n\n const epsilon = 0.5\n const sameLeft = Math.abs(topRect.left - botRect.left) < epsilon\n const sameRight = Math.abs(topRect.right - botRect.right) < epsilon\n\n let rowRect: DOMRect = topRect\n\n if (sameLeft && sameRight) {\n // Most of the time, every block has the same width\n rowRect = topRect\n } else {\n // TODO\n // find the actual block at the clamped Y\n // This case is rare, avoid for now\n }\n\n // Clamp X to the chosen block's bounds\n const clampedX = Math.min(Math.max(rowRect.left + inset, x), rowRect.right - inset)\n\n // Final validation of output coordinates\n if (!Number.isFinite(clampedX) || !Number.isFinite(clampedY)) {\n return null\n }\n\n return { x: clampedX, y: clampedY }\n}\n\nexport const findElementNextToCoords = (\n options: FindElementNextToCoords,\n): {\n resultElement: HTMLElement | null\n resultNode: Node | null\n pos: number | null\n} => {\n const { x, y, editor, nestedOptions } = options\n const { view, state } = editor\n\n const clamped = clampToContent(view, x, y, 5)\n\n // Return early if coordinates could not be clamped to valid bounds\n if (!clamped) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const { x: clampedX, y: clampedY } = clamped\n\n // When nested mode is enabled, use the scoring-based detection\n if (nestedOptions?.enabled) {\n const target = findBestDragTarget(view, { x: clampedX, y: clampedY }, nestedOptions)\n\n if (!target) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n return {\n resultElement: target.dom,\n resultNode: target.node,\n pos: target.pos,\n }\n }\n\n // Original root-level detection for non-nested mode\n const elements = view.root.elementsFromPoint(clampedX, clampedY)\n\n let block: HTMLElement | undefined\n\n Array.prototype.some.call(elements, (el: Element) => {\n if (!view.dom.contains(el)) {\n return false\n }\n const candidate = findClosestTopLevelBlock(el, view)\n if (candidate) {\n block = candidate\n return true\n }\n return false\n })\n\n if (!block) {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n let pos: number\n try {\n pos = view.posAtDOM(block, 0)\n } catch {\n return { resultElement: null, resultNode: null, pos: null }\n }\n\n const node = state.doc.nodeAt(pos)\n\n if (!node) {\n // This case occurs when an atom node is allowed to contain inline content.\n // We need to resolve the position here to ensure we target the correct parent node.\n const resolvedPos = state.doc.resolve(pos)\n const parent = resolvedPos.parent\n\n return {\n resultElement: block,\n resultNode: parent,\n pos: resolvedPos.start(),\n }\n }\n\n return {\n resultElement: block,\n resultNode: node,\n pos,\n }\n}\n","export function removeNode(node: HTMLElement) {\n node.parentNode?.removeChild(node)\n}\n","import type { Node } from '@tiptap/pm/model'\n\nexport const getOuterNodePos = (doc: Node, pos: number): number => {\n const resolvedPos = doc.resolve(pos)\n const { depth } = resolvedPos\n\n if (depth === 0) {\n return pos\n }\n\n const a = resolvedPos.pos - resolvedPos.parentOffset\n\n return a - 1\n}\n\nexport const getOuterNode = (doc: Node, pos: number): Node | null => {\n const node = doc.nodeAt(pos)\n const resolvedPos = doc.resolve(pos)\n\n let { depth } = resolvedPos\n let parent = node\n\n while (depth > 0) {\n const currentNode = resolvedPos.node(depth)\n\n depth -= 1\n\n if (depth === 0) {\n parent = currentNode\n }\n }\n\n return parent\n}\n","import type { NestedOptions, NormalizedNestedOptions } from '../types/options.js'\nimport { normalizeEdgeDetection } from './edgeDetection.js'\n\n/**\n * Normalizes the nested options input into a complete configuration object.\n *\n * @param input - The nested option (boolean, object, or undefined)\n * @returns A fully normalized options object\n *\n * @example\n * // Simple enable\n * normalizeNestedOptions(true)\n * // Returns: { enabled: true, rules: [], defaultRules: true, ... }\n *\n * @example\n * // Custom config\n * normalizeNestedOptions({ rules: [myRule], edgeDetection: 'none' })\n * // Returns: { enabled: true, rules: [myRule], edgeDetection: { edges: [], ... } }\n */\nexport function normalizeNestedOptions(input: boolean | NestedOptions | undefined): NormalizedNestedOptions {\n if (input === false || input === undefined) {\n return {\n enabled: false,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('none'),\n }\n }\n\n if (input === true) {\n return {\n enabled: true,\n rules: [],\n defaultRules: true,\n allowedContainers: undefined,\n edgeDetection: normalizeEdgeDetection('left'),\n }\n }\n\n return {\n enabled: true,\n rules: input.rules ?? [],\n defaultRules: input.defaultRules ?? true,\n allowedContainers: input.allowedContainers,\n edgeDetection: normalizeEdgeDetection(input.edgeDetection),\n }\n}\n","import { DragHandle } from './drag-handle.js'\n\nexport * from './drag-handle.js'\nexport * from './drag-handle-plugin.js'\nexport { defaultRules } from './helpers/defaultRules.js'\nexport { normalizeNestedOptions } from './helpers/normalizeOptions.js'\nexport type {\n EdgeDetectionConfig,\n EdgeDetectionPreset,\n NestedOptions,\n NormalizedNestedOptions,\n} from './types/options.js'\nexport type { DragHandleRule, RuleContext } from './types/rules.js'\n\nexport default DragHandle\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/extension-drag-handle",
3
3
  "description": "drag handle extension for tiptap",
4
- "version": "3.18.0",
4
+ "version": "3.20.0",
5
5
  "homepage": "https://tiptap.dev/docs/editor/extensions/functionality/drag-handle",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -37,10 +37,10 @@
37
37
  ],
38
38
  "peerDependencies": {
39
39
  "@tiptap/y-tiptap": "^3.0.2",
40
- "@tiptap/extension-node-range": "^3.18.0",
41
- "@tiptap/core": "^3.18.0",
42
- "@tiptap/extension-collaboration": "^3.18.0",
43
- "@tiptap/pm": "^3.18.0"
40
+ "@tiptap/extension-node-range": "^3.20.0",
41
+ "@tiptap/core": "^3.20.0",
42
+ "@tiptap/extension-collaboration": "^3.20.0",
43
+ "@tiptap/pm": "^3.20.0"
44
44
  },
45
45
  "dependencies": {
46
46
  "@floating-ui/dom": "^1.6.13"
@@ -48,10 +48,10 @@
48
48
  "devDependencies": {
49
49
  "@floating-ui/dom": "^1.6.13",
50
50
  "@tiptap/y-tiptap": "^3.0.2",
51
- "@tiptap/extension-node-range": "^3.18.0",
52
- "@tiptap/core": "^3.18.0",
53
- "@tiptap/extension-collaboration": "^3.18.0",
54
- "@tiptap/pm": "^3.18.0"
51
+ "@tiptap/extension-node-range": "^3.20.0",
52
+ "@tiptap/core": "^3.20.0",
53
+ "@tiptap/extension-collaboration": "^3.20.0",
54
+ "@tiptap/pm": "^3.20.0"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsup",
@@ -50,6 +50,28 @@ export const listWrapperDeprioritize: DragHandleRule = {
50
50
  },
51
51
  }
52
52
 
53
+ /**
54
+ * Table rows, cells, and content inside table headers should never be drag targets.
55
+ * Table dragging is handled by table extensions. Only the table wrapper
56
+ * itself or content inside regular table cells should be draggable.
57
+ */
58
+ export const tableStructure: DragHandleRule = {
59
+ id: 'tableStructure',
60
+ evaluate: ({ node, parent }) => {
61
+ const tableStructureTypes = ['tableRow', 'tableCell', 'tableHeader']
62
+
63
+ if (tableStructureTypes.includes(node.type.name)) {
64
+ return 1000
65
+ }
66
+
67
+ if (parent && parent.type.name === 'tableHeader') {
68
+ return 1000
69
+ }
70
+
71
+ return 0
72
+ },
73
+ }
74
+
53
75
  /**
54
76
  * Inline nodes (text, marks, inline atoms) should never be drag targets.
55
77
  */
@@ -68,4 +90,9 @@ export const inlineContent: DragHandleRule = {
68
90
  * All default rules.
69
91
  * Users can extend these or replace them entirely.
70
92
  */
71
- export const defaultRules: DragHandleRule[] = [listItemFirstChild, listWrapperDeprioritize, inlineContent]
93
+ export const defaultRules: DragHandleRule[] = [
94
+ listItemFirstChild,
95
+ listWrapperDeprioritize,
96
+ tableStructure,
97
+ inlineContent,
98
+ ]
@@ -49,8 +49,11 @@ function getDragHandleRanges(
49
49
  }
50
50
 
51
51
  // For non-nested mode, use depth 0 to select the outermost block
52
+ // Atom nodes (e.g. images) have nodeSize=1 with no opening/closing tokens,
53
+ // so we must not subtract 1 or we'll create an empty range.
54
+ const offset = result.resultNode.isText || result.resultNode.isAtom ? 0 : -1
52
55
  const $from = doc.resolve(result.pos)
53
- const $to = doc.resolve(result.pos + result.resultNode.nodeSize)
56
+ const $to = doc.resolve(result.pos + result.resultNode.nodeSize + offset)
54
57
 
55
58
  return getSelectionRanges($from, $to, 0)
56
59
  }
@@ -121,8 +124,17 @@ export function dragHandler(
121
124
  event.dataTransfer.clearData()
122
125
  event.dataTransfer.setDragImage(wrapper, 0, 0)
123
126
 
124
- // tell ProseMirror the dragged content
125
- view.dragging = { slice, move: true }
127
+ // Tell ProseMirror the dragged content.
128
+ // Pass the NodeSelection as `node` so ProseMirror's drop handler can use it
129
+ // to precisely delete the original node via `node.replace(tr)`. Without this,
130
+ // ProseMirror falls back to `tr.deleteSelection()` which relies on the current
131
+ // selection — but the browser may change the selection during drag, causing the
132
+ // original node to not be deleted on drop.
133
+ const nodeSelection = selection instanceof NodeSelection ? selection : undefined
134
+
135
+ // The `node` property is used at runtime by ProseMirror's drop handler but is
136
+ // not exposed in the public type declaration for `view.dragging`.
137
+ view.dragging = { slice, move: true, node: nodeSelection } as typeof view.dragging
126
138
 
127
139
  tr.setSelection(selection)
128
140
 
@@ -114,6 +114,49 @@ export function findBestDragTarget(
114
114
  })
115
115
  .filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)
116
116
 
117
+ // Atom/leaf nodes (e.g. images) are not ancestors of $pos — they sit at $pos.nodeAfter.
118
+ // The depth loop above only walks ancestor nodes, so these are missed entirely.
119
+ // Check for a nodeAfter and evaluate it as an additional candidate.
120
+ const nodeAfter = $pos.nodeAfter
121
+
122
+ if (nodeAfter && nodeAfter.isAtom && !nodeAfter.isInline) {
123
+ const nodePos = posInfo.pos
124
+ const depth = $pos.depth + 1
125
+ const parent = $pos.parent
126
+ const index = $pos.index()
127
+ const siblingCount = parent.childCount
128
+
129
+ let inAllowedContainer = true
130
+
131
+ if (options.allowedContainers) {
132
+ inAllowedContainer = hasAncestorOfType($pos, depth, options.allowedContainers)
133
+ }
134
+
135
+ if (inAllowedContainer) {
136
+ const context: RuleContext = {
137
+ node: nodeAfter,
138
+ pos: nodePos,
139
+ depth,
140
+ parent,
141
+ index,
142
+ isFirst: index === 0,
143
+ isLast: index === siblingCount - 1,
144
+ $pos,
145
+ view,
146
+ }
147
+
148
+ const score = calculateScore(context, rules, options.edgeDetection, coords)
149
+
150
+ if (score >= 0) {
151
+ const dom = view.nodeDOM(nodePos) as HTMLElement | null
152
+
153
+ if (dom) {
154
+ candidates.push({ node: nodeAfter, pos: nodePos, depth, score, dom })
155
+ }
156
+ }
157
+ }
158
+ }
159
+
117
160
  if (candidates.length === 0) {
118
161
  return null
119
162
  }