@blockslides/extension-drag-handle 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/drag-handle.ts","../src/drag-handle-plugin.ts","../src/helpers/dragHandler.ts","../src/helpers/cloneElement.ts","../src/helpers/findNextElementFromCursor.ts","../src/helpers/getComputedStyle.ts","../src/helpers/minMax.ts","../src/helpers/getInnerCoords.ts","../src/helpers/removeNode.ts","../src/helpers/getOuterNode.ts","../src/index.ts"],"sourcesContent":["import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'\nimport type { Editor } from '@blockslides/core'\nimport type { Node } from '@blockslides/pm/model'\nimport { Extension } from '@blockslides/core'\n\nimport { DragHandlePlugin } from './drag-handle-plugin.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\ndeclare module '@blockslides/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 }\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\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 }).plugin,\n ]\n },\n})\n","import { type ComputePositionConfig, type VirtualElement, computePosition } from '@floating-ui/dom'\nimport type { Editor } from '@blockslides/core'\nimport type { Node } from '@blockslides/pm/model'\nimport { type EditorState, type Transaction, Plugin, PluginKey } from '@blockslides/pm/state'\nimport type { EditorView } from '@blockslides/pm/view'\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'\n\ntype PluginState = {\n locked: boolean\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}\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}: DragHandlePluginProps) => {\n const wrapper = document.createElement('div')\n let locked = false\n let currentNode: Node | null = null\n let currentNodePos = -1\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 // @ts-ignore\n dragHandler(e, editor)\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 element.addEventListener('dragstart', onDragStart)\n element.addEventListener('dragend', onDragEnd)\n\n wrapper.appendChild(element)\n\n return {\n unbind() {\n element.removeEventListener('dragstart', onDragStart)\n element.removeEventListener('dragend', onDragEnd)\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 const newPos = tr.mapping.map(currentNodePos)\n\n if (newPos !== currentNodePos) {\n currentNodePos = newPos\n // We will get the outer node with data and position in views update method.\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 onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })\n\n repositionDragHandle(domNode as Element)\n },\n\n // TODO: Kills even on hot reload\n destroy() {\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 })\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\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\n if (outerNode !== currentNode) {\n const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos)\n\n currentNode = outerNode\n currentNodePos = outerNodePos\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 '@blockslides/core'\nimport { getSelectionRanges, NodeRangeSelection } from '@blockslides/extension-node-range'\nimport type { SelectionRange } from '@blockslides/pm/state'\n\nimport { cloneElement } from './cloneElement.js'\nimport { findElementNextToCoords } from './findNextElementFromCursor.js'\nimport { getInnerCoords } from './getInnerCoords.js'\nimport { removeNode } from './removeNode.js'\n\nfunction getDragHandleRanges(event: DragEvent, editor: Editor): SelectionRange[] {\n const { doc } = editor.view.state\n\n const result = findElementNextToCoords({\n editor,\n x: event.clientX,\n y: event.clientY,\n direction: 'right',\n })\n\n if (!result.resultNode || result.pos === null) {\n return []\n }\n\n const x = event.clientX\n\n // @ts-ignore\n const coords = getInnerCoords(editor.view, x, event.clientY)\n const posAtCoords = editor.view.posAtCoords(coords)\n\n if (!posAtCoords) {\n return []\n }\n\n const { pos } = posAtCoords\n const nodeAt = doc.resolve(pos).parent\n\n if (!nodeAt) {\n return []\n }\n\n const $from = doc.resolve(result.pos)\n const $to = doc.resolve(result.pos + 1)\n\n return getSelectionRanges($from, $to, 0)\n}\n\nexport function dragHandler(event: DragEvent, editor: Editor) {\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)\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 const selection = NodeRangeSelection.create(view.state.doc, from, to)\n const slice = selection.content()\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 { Editor } from '@blockslides/core'\nimport type { Node } from '@blockslides/pm/model'\nimport type { EditorView } from '@blockslides/pm/view'\n\nexport type FindElementNextToCoords = {\n x: number\n y: number\n direction?: 'left' | 'right'\n editor: Editor\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 * 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 } {\n const container = view.dom\n const firstBlock = container.firstElementChild\n const lastBlock = container.lastElementChild\n\n if (!firstBlock || !lastBlock) {\n // this condition will never be met, as the first child element will be treated as last child element too\n return { x, y }\n }\n\n // Clamp Y between first and last block\n const topRect = firstBlock.getBoundingClientRect()\n const botRect = lastBlock.getBoundingClientRect()\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 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 } = options\n const { view, state } = editor\n\n const { x: clampedX, y: clampedY } = clampToContent(view, x, y, 5)\n\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 getComputedStyle(node: Element, property: keyof CSSStyleDeclaration): any {\n const style = window.getComputedStyle(node)\n\n return style[property]\n}\n","export function minMax(value = 0, min = 0, max = 0): number {\n return Math.min(Math.max(value, min), max)\n}\n","import type { EditorView } from '@blockslides/pm/view'\n\nimport { getComputedStyle } from './getComputedStyle.js'\nimport { minMax } from './minMax.js'\n\nexport function getInnerCoords(view: EditorView, x: number, y: number): { left: number; top: number } {\n const paddingLeft = parseInt(getComputedStyle(view.dom, 'paddingLeft'), 10)\n const paddingRight = parseInt(getComputedStyle(view.dom, 'paddingRight'), 10)\n const borderLeft = parseInt(getComputedStyle(view.dom, 'borderLeftWidth'), 10)\n const borderRight = parseInt(getComputedStyle(view.dom, 'borderLeftWidth'), 10)\n const bounds = view.dom.getBoundingClientRect()\n const coords = {\n left: minMax(x, bounds.left + paddingLeft + borderLeft, bounds.right - paddingRight - borderRight),\n top: y,\n }\n\n return coords\n}\n","export function removeNode(node: HTMLElement) {\n node.parentNode?.removeChild(node)\n}\n","import type { Node } from '@blockslides/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 { DragHandle } from './drag-handle.js'\n\nexport * from './drag-handle.js'\nexport * from './drag-handle-plugin.js'\n\nexport default DragHandle\n"],"mappings":"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","names":["getComputedStyle","getComputedStyle"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@blockslides/extension-drag-handle",
3
+ "description": "Drag handle plugin for BlockSlides editors",
4
+ "version": "0.1.0",
5
+ "homepage": "https://github.com/keivanmojmali/blockslides",
6
+ "keywords": [
7
+ "blockslides",
8
+ "blockslides extension",
9
+ "prosemirror"
10
+ ],
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/keivanmojmali/blockslides",
15
+ "directory": "packages/extension-drag-handle"
16
+ },
17
+ "type": "module",
18
+ "exports": {
19
+ ".": {
20
+ "types": {
21
+ "import": "./dist/index.d.ts",
22
+ "require": "./dist/index.d.cts"
23
+ },
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "main": "dist/index.cjs",
29
+ "module": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
+ "files": [
32
+ "src",
33
+ "dist"
34
+ ],
35
+ "peerDependencies": {
36
+ "@floating-ui/dom": "^1.6.13",
37
+ "@blockslides/pm": "^0.1.0",
38
+ "@blockslides/core": "^0.1.0",
39
+ "@blockslides/extension-node-range": "^0.1.0"
40
+ },
41
+ "dependencies": {
42
+ "@floating-ui/dom": "^1.6.13"
43
+ },
44
+ "devDependencies": {
45
+ "@floating-ui/dom": "^1.6.13",
46
+ "@blockslides/core": "^0.1.0",
47
+ "@blockslides/extension-node-range": "^0.1.0",
48
+ "@blockslides/pm": "^0.1.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
53
+ }
54
+ }
@@ -0,0 +1,371 @@
1
+ import { type ComputePositionConfig, type VirtualElement, computePosition } from '@floating-ui/dom'
2
+ import type { Editor } from '@blockslides/core'
3
+ import type { Node } from '@blockslides/pm/model'
4
+ import { type EditorState, type Transaction, Plugin, PluginKey } from '@blockslides/pm/state'
5
+ import type { EditorView } from '@blockslides/pm/view'
6
+
7
+ import { dragHandler } from './helpers/dragHandler.js'
8
+ import { findElementNextToCoords } from './helpers/findNextElementFromCursor.js'
9
+ import { getOuterNode, getOuterNodePos } from './helpers/getOuterNode.js'
10
+ import { removeNode } from './helpers/removeNode.js'
11
+
12
+ type PluginState = {
13
+ locked: boolean
14
+ }
15
+
16
+ const getOuterDomNode = (view: EditorView, domNode: HTMLElement) => {
17
+ let tmpDomNode = domNode
18
+
19
+ // Traverse to top level node.
20
+ while (tmpDomNode?.parentNode) {
21
+ if (tmpDomNode.parentNode === view.dom) {
22
+ break
23
+ }
24
+
25
+ tmpDomNode = tmpDomNode.parentNode as HTMLElement
26
+ }
27
+
28
+ return tmpDomNode
29
+ }
30
+
31
+ export interface DragHandlePluginProps {
32
+ pluginKey?: PluginKey | string
33
+ editor: Editor
34
+ element: HTMLElement
35
+ onNodeChange?: (data: { editor: Editor; node: Node | null; pos: number }) => void
36
+ onElementDragStart?: (e: DragEvent) => void
37
+ onElementDragEnd?: (e: DragEvent) => void
38
+ computePositionConfig?: ComputePositionConfig
39
+ getReferencedVirtualElement?: () => VirtualElement | null
40
+ }
41
+
42
+ export const dragHandlePluginDefaultKey = new PluginKey('dragHandle')
43
+
44
+ export const DragHandlePlugin = ({
45
+ pluginKey = dragHandlePluginDefaultKey,
46
+ element,
47
+ editor,
48
+ computePositionConfig,
49
+ getReferencedVirtualElement,
50
+ onNodeChange,
51
+ onElementDragStart,
52
+ onElementDragEnd,
53
+ }: DragHandlePluginProps) => {
54
+ const wrapper = document.createElement('div')
55
+ let locked = false
56
+ let currentNode: Node | null = null
57
+ let currentNodePos = -1
58
+ let rafId: number | null = null
59
+ let pendingMouseCoords: { x: number; y: number } | null = null
60
+
61
+ function hideHandle() {
62
+ if (!element) {
63
+ return
64
+ }
65
+
66
+ element.style.visibility = 'hidden'
67
+ element.style.pointerEvents = 'none'
68
+ }
69
+
70
+ function showHandle() {
71
+ if (!element) {
72
+ return
73
+ }
74
+
75
+ if (!editor.isEditable) {
76
+ hideHandle()
77
+ return
78
+ }
79
+
80
+ element.style.visibility = ''
81
+ element.style.pointerEvents = 'auto'
82
+ }
83
+
84
+ function repositionDragHandle(dom: Element) {
85
+ const virtualElement = getReferencedVirtualElement?.() || {
86
+ getBoundingClientRect: () => dom.getBoundingClientRect(),
87
+ }
88
+
89
+ computePosition(virtualElement, element, computePositionConfig).then(val => {
90
+ Object.assign(element.style, {
91
+ position: val.strategy,
92
+ left: `${val.x}px`,
93
+ top: `${val.y}px`,
94
+ })
95
+ })
96
+ }
97
+
98
+ function onDragStart(e: DragEvent) {
99
+ onElementDragStart?.(e)
100
+ // Push this to the end of the event cue
101
+ // Fixes bug where incorrect drag pos is returned if drag handle has position: absolute
102
+ // @ts-ignore
103
+ dragHandler(e, editor)
104
+
105
+ if (element) {
106
+ element.dataset.dragging = 'true'
107
+ }
108
+
109
+ setTimeout(() => {
110
+ if (element) {
111
+ element.style.pointerEvents = 'none'
112
+ }
113
+ }, 0)
114
+ }
115
+
116
+ function onDragEnd(e: DragEvent) {
117
+ onElementDragEnd?.(e)
118
+ hideHandle()
119
+ if (element) {
120
+ element.style.pointerEvents = 'auto'
121
+ element.dataset.dragging = 'false'
122
+ }
123
+ }
124
+
125
+ element.addEventListener('dragstart', onDragStart)
126
+ element.addEventListener('dragend', onDragEnd)
127
+
128
+ wrapper.appendChild(element)
129
+
130
+ return {
131
+ unbind() {
132
+ element.removeEventListener('dragstart', onDragStart)
133
+ element.removeEventListener('dragend', onDragEnd)
134
+ if (rafId) {
135
+ cancelAnimationFrame(rafId)
136
+ rafId = null
137
+ pendingMouseCoords = null
138
+ }
139
+ },
140
+ plugin: new Plugin({
141
+ key: typeof pluginKey === 'string' ? new PluginKey(pluginKey) : pluginKey,
142
+
143
+ state: {
144
+ init() {
145
+ return { locked: false }
146
+ },
147
+ apply(tr: Transaction, value: PluginState, _oldState: EditorState, state: EditorState) {
148
+ const isLocked = tr.getMeta('lockDragHandle')
149
+ const hideDragHandle = tr.getMeta('hideDragHandle')
150
+
151
+ if (isLocked !== undefined) {
152
+ locked = isLocked
153
+ }
154
+
155
+ if (hideDragHandle) {
156
+ hideHandle()
157
+
158
+ locked = false
159
+ currentNode = null
160
+ currentNodePos = -1
161
+
162
+ onNodeChange?.({ editor, node: null, pos: -1 })
163
+
164
+ return value
165
+ }
166
+
167
+ // Something has changed and drag handler is visible…
168
+ if (tr.docChanged && currentNodePos !== -1 && element) {
169
+ const newPos = tr.mapping.map(currentNodePos)
170
+
171
+ if (newPos !== currentNodePos) {
172
+ currentNodePos = newPos
173
+ // We will get the outer node with data and position in views update method.
174
+ }
175
+ }
176
+
177
+ return value
178
+ },
179
+ },
180
+
181
+ view: view => {
182
+ element.draggable = true
183
+ element.style.pointerEvents = 'auto'
184
+ element.dataset.dragging = 'false'
185
+
186
+ editor.view.dom.parentElement?.appendChild(wrapper)
187
+
188
+ wrapper.style.pointerEvents = 'none'
189
+ wrapper.style.position = 'absolute'
190
+ wrapper.style.top = '0'
191
+ wrapper.style.left = '0'
192
+
193
+ return {
194
+ update(_, oldState) {
195
+ if (!element) {
196
+ return
197
+ }
198
+
199
+ if (!editor.isEditable) {
200
+ hideHandle()
201
+ return
202
+ }
203
+
204
+ // Prevent element being draggend while being open.
205
+ if (locked) {
206
+ element.draggable = false
207
+ } else {
208
+ element.draggable = true
209
+ }
210
+
211
+ // Recalculate popup position if doc has changend and drag handler is visible.
212
+ if (view.state.doc.eq(oldState.doc) || currentNodePos === -1) {
213
+ return
214
+ }
215
+
216
+ // Get domNode from (new) position.
217
+ let domNode = view.nodeDOM(currentNodePos) as HTMLElement
218
+
219
+ // Since old element could have been wrapped, we need to find
220
+ // the outer node and take its position and node data.
221
+ domNode = getOuterDomNode(view, domNode)
222
+
223
+ // Skip if domNode is editor dom.
224
+ if (domNode === view.dom) {
225
+ return
226
+ }
227
+
228
+ // We only want `Element`.
229
+ if (domNode?.nodeType !== 1) {
230
+ return
231
+ }
232
+
233
+ const domNodePos = view.posAtDOM(domNode, 0)
234
+ const outerNode = getOuterNode(editor.state.doc, domNodePos)
235
+ const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos) // TODO: needed?
236
+
237
+ currentNode = outerNode
238
+ currentNodePos = outerNodePos
239
+
240
+ onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })
241
+
242
+ repositionDragHandle(domNode as Element)
243
+ },
244
+
245
+ // TODO: Kills even on hot reload
246
+ destroy() {
247
+ if (rafId) {
248
+ cancelAnimationFrame(rafId)
249
+ rafId = null
250
+ pendingMouseCoords = null
251
+ }
252
+
253
+ if (element) {
254
+ removeNode(wrapper)
255
+ }
256
+ },
257
+ }
258
+ },
259
+
260
+ props: {
261
+ handleDOMEvents: {
262
+ keydown(view) {
263
+ if (!element || locked) {
264
+ return false
265
+ }
266
+
267
+ if (view.hasFocus()) {
268
+ hideHandle()
269
+ currentNode = null
270
+ currentNodePos = -1
271
+ onNodeChange?.({ editor, node: null, pos: -1 })
272
+
273
+ // We want to still continue with other keydown events.
274
+ return false
275
+ }
276
+
277
+ return false
278
+ },
279
+ mouseleave(_view, e) {
280
+ // Do not hide open popup on mouseleave.
281
+ if (locked) {
282
+ return false
283
+ }
284
+
285
+ // If e.target is not inside the wrapper, hide.
286
+ if (e.target && !wrapper.contains(e.relatedTarget as HTMLElement)) {
287
+ hideHandle()
288
+
289
+ currentNode = null
290
+ currentNodePos = -1
291
+
292
+ onNodeChange?.({ editor, node: null, pos: -1 })
293
+ }
294
+
295
+ return false
296
+ },
297
+
298
+ mousemove(view, e) {
299
+ // Do not continue if popup is not initialized or open.
300
+ if (!element || locked) {
301
+ return false
302
+ }
303
+
304
+ // Store latest mouse coords and schedule a single RAF per frame
305
+ pendingMouseCoords = { x: e.clientX, y: e.clientY }
306
+
307
+ if (rafId) {
308
+ return false
309
+ }
310
+
311
+ rafId = requestAnimationFrame(() => {
312
+ rafId = null
313
+
314
+ if (!pendingMouseCoords) {
315
+ return
316
+ }
317
+
318
+ const { x, y } = pendingMouseCoords
319
+ pendingMouseCoords = null
320
+
321
+ const nodeData = findElementNextToCoords({
322
+ x,
323
+ y,
324
+ direction: 'right',
325
+ editor,
326
+ })
327
+
328
+ // Skip if there is no node next to coords
329
+ if (!nodeData.resultElement) {
330
+ return
331
+ }
332
+
333
+ let domNode = nodeData.resultElement as HTMLElement
334
+
335
+ domNode = getOuterDomNode(view, domNode)
336
+
337
+ // Skip if domNode is editor dom.
338
+ if (domNode === view.dom) {
339
+ return
340
+ }
341
+
342
+ // We only want `Element`.
343
+ if (domNode?.nodeType !== 1) {
344
+ return
345
+ }
346
+
347
+ const domNodePos = view.posAtDOM(domNode, 0)
348
+ const outerNode = getOuterNode(editor.state.doc, domNodePos)
349
+
350
+ if (outerNode !== currentNode) {
351
+ const outerNodePos = getOuterNodePos(editor.state.doc, domNodePos)
352
+
353
+ currentNode = outerNode
354
+ currentNodePos = outerNodePos
355
+
356
+ onNodeChange?.({ editor, node: currentNode, pos: currentNodePos })
357
+
358
+ // Set nodes clientRect.
359
+ repositionDragHandle(domNode as Element)
360
+
361
+ showHandle()
362
+ }
363
+ })
364
+
365
+ return false
366
+ },
367
+ },
368
+ },
369
+ }),
370
+ }
371
+ }
@@ -0,0 +1,125 @@
1
+ import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'
2
+ import type { Editor } from '@blockslides/core'
3
+ import type { Node } from '@blockslides/pm/model'
4
+ import { Extension } from '@blockslides/core'
5
+
6
+ import { DragHandlePlugin } from './drag-handle-plugin.js'
7
+
8
+ export const defaultComputePositionConfig: ComputePositionConfig = {
9
+ placement: 'left-start',
10
+ strategy: 'absolute',
11
+ }
12
+
13
+ export interface DragHandleOptions {
14
+ /**
15
+ * Renders an element that is positioned with the floating-ui/dom package
16
+ */
17
+ render(): HTMLElement
18
+ /**
19
+ * Configuration for position computation of the drag handle
20
+ * using the floating-ui/dom package
21
+ */
22
+ computePositionConfig?: ComputePositionConfig
23
+ /**
24
+ * A function that returns the virtual element for the drag handle.
25
+ * This is useful when the menu needs to be positioned relative to a specific DOM element.
26
+ */
27
+ getReferencedVirtualElement?: () => VirtualElement | null
28
+ /**
29
+ * Locks the draghandle in place and visibility
30
+ */
31
+ locked?: boolean
32
+ /**
33
+ * Returns a node or null when a node is hovered over
34
+ */
35
+ onNodeChange?: (options: { node: Node | null; editor: Editor }) => void
36
+ /**
37
+ * The callback function that will be called when drag start.
38
+ */
39
+ onElementDragStart?: (e: DragEvent) => void
40
+ /**
41
+ * The callback function that will be called when drag end.
42
+ */
43
+ onElementDragEnd?: (e: DragEvent) => void
44
+ }
45
+
46
+ declare module '@blockslides/core' {
47
+ interface Commands<ReturnType> {
48
+ dragHandle: {
49
+ /**
50
+ * Locks the draghandle in place and visibility
51
+ */
52
+ lockDragHandle: () => ReturnType
53
+ /**
54
+ * Unlocks the draghandle
55
+ */
56
+ unlockDragHandle: () => ReturnType
57
+ /**
58
+ * Toggle draghandle lock state
59
+ */
60
+ toggleDragHandle: () => ReturnType
61
+ }
62
+ }
63
+ }
64
+
65
+ export const DragHandle = Extension.create<DragHandleOptions>({
66
+ name: 'dragHandle',
67
+
68
+ addOptions() {
69
+ return {
70
+ render() {
71
+ const element = document.createElement('div')
72
+
73
+ element.classList.add('drag-handle')
74
+
75
+ return element
76
+ },
77
+ computePositionConfig: {},
78
+ locked: false,
79
+ onNodeChange: () => {
80
+ return null
81
+ },
82
+ onElementDragStart: undefined,
83
+ onElementDragEnd: undefined,
84
+ }
85
+ },
86
+
87
+ addCommands() {
88
+ return {
89
+ lockDragHandle:
90
+ () =>
91
+ ({ editor }) => {
92
+ this.options.locked = true
93
+ return editor.commands.setMeta('lockDragHandle', this.options.locked)
94
+ },
95
+ unlockDragHandle:
96
+ () =>
97
+ ({ editor }) => {
98
+ this.options.locked = false
99
+ return editor.commands.setMeta('lockDragHandle', this.options.locked)
100
+ },
101
+ toggleDragHandle:
102
+ () =>
103
+ ({ editor }) => {
104
+ this.options.locked = !this.options.locked
105
+ return editor.commands.setMeta('lockDragHandle', this.options.locked)
106
+ },
107
+ }
108
+ },
109
+
110
+ addProseMirrorPlugins() {
111
+ const element = this.options.render()
112
+
113
+ return [
114
+ DragHandlePlugin({
115
+ computePositionConfig: { ...defaultComputePositionConfig, ...this.options.computePositionConfig },
116
+ getReferencedVirtualElement: this.options.getReferencedVirtualElement,
117
+ element,
118
+ editor: this.editor,
119
+ onNodeChange: this.options.onNodeChange,
120
+ onElementDragStart: this.options.onElementDragStart,
121
+ onElementDragEnd: this.options.onElementDragEnd,
122
+ }).plugin,
123
+ ]
124
+ },
125
+ })
@@ -0,0 +1,22 @@
1
+ function getCSSText(element: Element) {
2
+ let value = ''
3
+ const style = getComputedStyle(element)
4
+
5
+ for (let i = 0; i < style.length; i += 1) {
6
+ value += `${style[i]}:${style.getPropertyValue(style[i])};`
7
+ }
8
+
9
+ return value
10
+ }
11
+
12
+ export function cloneElement(node: HTMLElement) {
13
+ const clonedNode = node.cloneNode(true) as HTMLElement
14
+ const sourceElements = [node, ...Array.from(node.getElementsByTagName('*'))] as HTMLElement[]
15
+ const targetElements = [clonedNode, ...Array.from(clonedNode.getElementsByTagName('*'))] as HTMLElement[]
16
+
17
+ sourceElements.forEach((sourceElement, index) => {
18
+ targetElements[index].style.cssText = getCSSText(sourceElement)
19
+ })
20
+
21
+ return clonedNode
22
+ }