All files / web/src/plugins/codemirror dragAndDrop.ts

0% Statements 0/85
0% Branches 0/1
0% Functions 0/1
0% Lines 0/85

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                                                                                                                                                                                                                       
import { ViewPlugin, EditorView, type ViewUpdate } from '@codemirror/view'
import { StateField, StateEffect, type Extension } from '@codemirror/state'
import { useDragAndDrop } from '@/composables/ui/useDragAndDrop'
 
/**
 * State field to track drop cursor position
 */
const dropCursorPos = StateField.define<number | null>({
  create() {
    return null
  },
  update(value, tr) {
    for (const effect of tr.effects) {
      if (effect.is(setDropCursor)) {
        return effect.value
      }
    }
    return value
  }
})
 
const setDropCursor = StateEffect.define<number | null>()
 
/**
 * CodeMirror plugin that integrates with global drag & drop system
 * Allows inserting dragged variables into the editor
 */
export const dragAndDropPlugin = (): Extension => {
  const { isDragging, dragData, endDrag } = useDragAndDrop()
 
  return [
    dropCursorPos,
    ViewPlugin.fromClass(
    class {
      dropPos: number | null = null
      view: EditorView
 
      constructor(view: EditorView) {
        this.view = view
        // Add global mouseup listener to handle drop
        document.addEventListener('mouseup', this.handleGlobalMouseUp)
      }
 
      update(update: ViewUpdate) {
        // Update drop cursor if position changed
        const pos = update.state.field(dropCursorPos)
        if (pos !== this.dropPos) {
          this.dropPos = pos
        }
      }
 
      handleGlobalMouseUp = (e: MouseEvent) => {
        if (!isDragging.value || !dragData.value) return
 
        // Check if drop target is this editor
        const editorElement = this.view.dom
        const rect = editorElement.getBoundingClientRect()
        const isInEditor =
          e.clientX >= rect.left &&
          e.clientX <= rect.right &&
          e.clientY >= rect.top &&
          e.clientY <= rect.bottom
 
        if (isInEditor && this.dropPos !== null) {
          // Insert dragged data at cursor position
          const data = dragData.value.data
          this.view.dispatch({
            changes: {
              from: this.dropPos,
              to: this.dropPos,
              insert: data
            },
            selection: { anchor: this.dropPos + data.length }
          })
        }
 
        // Clear drop cursor
        this.view.dispatch({
          effects: setDropCursor.of(null)
        })
 
        endDrag()
      }
 
      destroy() {
        document.removeEventListener('mouseup', this.handleGlobalMouseUp)
      }
    },
    {
      eventHandlers: {
        mousemove(event, view) {
          if (!isDragging.value) return
 
          // Calculate drop position
          const pos = view.posAtCoords({ x: event.clientX, y: event.clientY })
          if (pos !== null) {
            view.dispatch({
              effects: setDropCursor.of(pos)
            })
          }
        },
 
        mouseleave(_event, view) {
          // Clear drop cursor when leaving editor
          view.dispatch({
            effects: setDropCursor.of(null)
          })
        }
      }
    }
  )
  ]
}
 
export { dropCursorPos }