Skip to content

Commit

Permalink
Add ability to specify a Vue component and property values for the dr…
Browse files Browse the repository at this point in the history
…ag image
  • Loading branch information
nruffing committed Dec 17, 2023
1 parent 8e0a7ea commit 0173ef5
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 19 deletions.
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Customizable native Vue3 drag-n-drop library with no dependencies. Includes Vue plugin that registers directives to configure draggable elements and drop zones.

This package is intended to just be a directive that wraps the browser's drag and drop API and combine it with Vue's reactive features. The actual HTML events are available in each of the event handler callbacks. It is recommended to still understand how the browser's API works to best decide how to wire-up the directives for your use case. MDN has a good primer on the browser API [here](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API).

## Install

```
Expand Down Expand Up @@ -39,13 +41,13 @@ createApp(App).use(DragonDropVue, dragonDropOptions)
<script setup lang="ts">
import { ref } from 'vue'
import type { DragonDropVueDragOptions, DragonDropVueOptions } from '../../lib/main'
import type { DragonDropVueDragOptions, DragonDropVueOptions } from 'dragon-drop-vue'
const columns = ref([0, 1, 2, 3, 4, 5, 6])
const dragging = ref<number | undefined>(undefined)
const image = new Image()
image.src = 'dragen.png'
image.src = 'dragon.png'
const dragImage = {
image,
xOffset: 30,
Expand Down Expand Up @@ -103,11 +105,49 @@ All event handler properties are of the following type.
| `onDragLeave` | Drag/drop event handler | `dragleave` event handler |
| `onDrop` | Drag/drop event handler | `drop` event handler |

## Drag Data

When drag data is specified to the drag directive it is set on the data transfer of the browser events as JSON. The drop directive will also retrieve the drag data from the date transfer object, deserialize it and set it as the drag data on the options passed to the `onDrop` callback.

## Vue Component as Drag Image

The drag directive contains the necessary boilerplate to take a Vue component definition and prop values to create an element to use as the drag image. It is also automatically removed from the DOM as part of `onDrop` handling.

```vue
<template>
<main>
<template v-for="column in columns">
<div v-drop="{ dragData: column, onDragEnter: onDragEnter, onDrop: onDrop }"></div>
<div v-drag="{ dragData: column, onDragStart: onDragStart }">{{ column }}</div>
</template>
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { DragonDropVueDragOptions, DragonDropVueOptions } from 'dragon-drop-vue'
import CustomComponent from '@/components/CustomComponent.vue'
const columns = ref([0, 1, 2, 3, 4, 5, 6])
const dragging = ref<number | undefined>(undefined)
function onDragStart(domEl: HTMLElement, dragEvent: DragEvent, dragOptions: DragonDropVueDragOptions<number>, options: DragonDropVueOptions) {
dragging.value = dragOptions.dragData
dragOptions.dragImage = {
rootComponent: CustomComponent,
rootComponentProps: { column: dragOptions.dragData },
}
}
</script>
```

---

## Release Notes

### v1.1.0
* Add ability to specify a Vue component and property values for the drag image.
* Parse data transfer drag data from drop event and pass to `onDrop` via the drag options drag data if drag data does not already exist.

### v1.0.0
Expand Down
19 changes: 18 additions & 1 deletion lib/eventHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { addClasses, removeClasses } from './htmlHelpers'
import type { DragonDropVueDragOptions, DragonDropVueOptions } from './options'
import constants from './constants'
import { log } from './logger'
import { createApp } from 'vue'

export function onDragStart(event: DragEvent, dragOpts: DragonDropVueDragOptions, opts: DragonDropVueOptions): boolean | undefined {
const domEl = event.target as HTMLElement
Expand Down Expand Up @@ -30,7 +31,18 @@ export function onDragStart(event: DragEvent, dragOpts: DragonDropVueDragOptions
}

if (dragOpts.dragImage) {
event.dataTransfer.setDragImage(dragOpts.dragImage.image, dragOpts.dragImage.xOffset, dragOpts.dragImage.yOffset)
if (!dragOpts.dragImage.image && dragOpts.dragImage.rootComponent) {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.top = '-1000px'
document.body.appendChild(div)
createApp(dragOpts.dragImage.rootComponent, dragOpts.dragImage.rootComponentProps ?? {}).mount(div)
dragOpts.dragImage.image = div
}

if (dragOpts.dragImage.image) {
event.dataTransfer.setDragImage(dragOpts.dragImage.image, dragOpts.dragImage.xOffset ?? 0, dragOpts.dragImage.yOffset ?? 0)
}
}
}
}
Expand All @@ -40,6 +52,11 @@ export function onDragEnd(event: DragEvent, dragOpts: DragonDropVueDragOptions,

log({ eventName: 'onDragEnd', event, domEl, dragOpts, opts })

// remove drag image from dom, browser will do this automatically for images since it created the image element
if (dragOpts.dragImage?.image && !(dragOpts.dragImage.image instanceof Image)) {
document.body.removeChild(dragOpts.dragImage.image)
}

// call consumer-defined handler
if (dragOpts.onDragEnd) {
var dontTerminate = dragOpts.onDragEnd(domEl, event, dragOpts, opts)
Expand Down
9 changes: 6 additions & 3 deletions lib/options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Component } from 'vue'
import type { DropEffect } from './htmlHelpers'

export interface DragonDropVueOptions {
Expand Down Expand Up @@ -48,7 +49,9 @@ export interface DragonDropVueDragOptions<T = any> {
}

export interface DragonDropVueDragImageOptions {
image: Element
xOffset: number
yOffset: number
image?: Element
xOffset?: number
yOffset?: number
rootComponent?: Component
rootComponentProps?: Record<string, unknown>
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dragon-drop-vue",
"version": "1.0.0",
"version": "1.1.0",
"description": "Customizable native Vue3 drag-n-drop library with no dependencies. Includes Vue plugin that registers directives to configure draggable elements and drop zones.",
"license": "MIT",
"author": {
Expand Down
15 changes: 3 additions & 12 deletions src/components/DragItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</template>

<script lang="ts">
import { defineComponent, createApp } from 'vue'
import { defineComponent } from 'vue'
import DragImage from './DragImage.vue'
import { type DragonDropVueDragOptions, type DragonDropVueOptions } from '../../lib/options'
Expand All @@ -26,20 +26,11 @@ export default defineComponent({
},
methods: {
onDragStart(domEl: HTMLElement, dragEvent: DragEvent, dragOptions: DragonDropVueDragOptions<DragDataType>, options: DragonDropVueOptions) {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.top = '-1000px'
document.body.appendChild(div)
createApp(DragImage, { num: this.num }).mount(div)
dragOptions.dragImage = {
image: div,
xOffset: 0,
yOffset: 0,
rootComponent: DragImage,
rootComponentProps: { num: this.num },
}
},
onDragEnd(domEl: HTMLElement, dragEvent: DragEvent, dragOptions: DragonDropVueDragOptions<DragDataType>, options: DragonDropVueOptions) {
document.body.removeChild(dragOptions.dragImage!.image)
},
},
})
</script>
Expand Down

0 comments on commit 0173ef5

Please sign in to comment.