chore(docs): update drag and drop example
This commit is contained in:
@@ -1,73 +1,31 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { VueFlow, useVueFlow } from '@vue-flow/core'
|
||||
import { nextTick, watch } from 'vue'
|
||||
import DropzoneBackground from './DropzoneBackground.vue'
|
||||
import Sidebar from './Sidebar.vue'
|
||||
import useDragAndDrop from './useDnD'
|
||||
|
||||
let id = 0
|
||||
function getId() {
|
||||
return `dndnode_${id++}`
|
||||
}
|
||||
const { onConnect, addEdges } = useVueFlow()
|
||||
|
||||
const { findNode, onConnect, addEdges, addNodes, project, vueFlowRef } = useVueFlow({
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'input node',
|
||||
position: { x: 250, y: 25 },
|
||||
},
|
||||
],
|
||||
})
|
||||
const { onDragOver, onDrop, onDragLeave, isDragging } = useDragAndDrop()
|
||||
|
||||
function onDragOver(event) {
|
||||
event.preventDefault()
|
||||
const nodes = ref([])
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
onConnect((params) => addEdges(params))
|
||||
|
||||
function onDrop(event) {
|
||||
const type = event.dataTransfer?.getData('application/vueflow')
|
||||
|
||||
const { left, top } = vueFlowRef.value.getBoundingClientRect()
|
||||
|
||||
const position = project({
|
||||
x: event.clientX - left,
|
||||
y: event.clientY - top,
|
||||
})
|
||||
|
||||
const newNode = {
|
||||
id: getId(),
|
||||
type,
|
||||
position,
|
||||
label: `${type} node`,
|
||||
}
|
||||
|
||||
addNodes([newNode])
|
||||
|
||||
// align node position after drop, so it's centered to the mouse
|
||||
nextTick(() => {
|
||||
const node = findNode(newNode.id)
|
||||
const stop = watch(
|
||||
() => node.dimensions,
|
||||
(dimensions) => {
|
||||
if (dimensions.width > 0 && dimensions.height > 0) {
|
||||
node.position = { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 }
|
||||
stop()
|
||||
}
|
||||
},
|
||||
{ deep: true, flush: 'post' },
|
||||
)
|
||||
})
|
||||
}
|
||||
onConnect(addEdges)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dndflow" @drop="onDrop">
|
||||
<VueFlow @dragover="onDragOver" />
|
||||
<VueFlow :nodes="nodes" @dragover="onDragOver" @dragleave="onDragLeave">
|
||||
<DropzoneBackground
|
||||
:style="{
|
||||
backgroundColor: isDragging ? 'rgba(0, 0, 0, 0.1)' : 'transparent',
|
||||
borderColor: isDragging ? 'rgba(0, 0, 0, 0.2)' : 'transparent',
|
||||
transition: 'background-color 0.2s, border-color 0.2s',
|
||||
}"
|
||||
>
|
||||
</DropzoneBackground>
|
||||
</VueFlow>
|
||||
|
||||
<Sidebar />
|
||||
</div>
|
||||
|
||||
11
docs/examples/dnd/DropzoneBackground.vue
Normal file
11
docs/examples/dnd/DropzoneBackground.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { Background } from '@vue-flow/background'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height: 100%; width: 100%">
|
||||
<Background :size="2" :gap="20" pattern-color="#BDBDBD">
|
||||
<slot />
|
||||
</Background>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,10 +1,7 @@
|
||||
<script setup>
|
||||
function onDragStart(event, nodeType) {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.setData('application/vueflow', nodeType)
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
}
|
||||
import useDragAndDrop from './useDnD'
|
||||
|
||||
const { onDragStart } = useDragAndDrop()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export { default as DndApp } from './App.vue?raw'
|
||||
export { default as DndSidebar } from './Sidebar.vue?raw'
|
||||
export { default as DndBackground } from './DropzoneBackground.vue?raw'
|
||||
export { default as DndCSS } from './style.css?inline'
|
||||
export { default as DndScript } from './useDnD.js?raw'
|
||||
|
||||
120
docs/examples/dnd/useDnD.js
Normal file
120
docs/examples/dnd/useDnD.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useVueFlow } from '@vue-flow/core'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
let id = 0
|
||||
|
||||
/**
|
||||
* @returns {string} - A unique id.
|
||||
*/
|
||||
function getId() {
|
||||
return `dndnode_${id++}`
|
||||
}
|
||||
|
||||
/**
|
||||
* In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.
|
||||
* @type {{draggedType: Ref<string|null>, isDragOver: Ref<boolean>, isDragging: Ref<boolean>}}
|
||||
*/
|
||||
const state = {
|
||||
/**
|
||||
* The type of the node being dragged.
|
||||
*/
|
||||
draggedType: ref(null),
|
||||
isDragOver: ref(false),
|
||||
isDragging: ref(false),
|
||||
}
|
||||
|
||||
export default function useDragAndDrop() {
|
||||
const { draggedType, isDragOver, isDragging } = state
|
||||
|
||||
const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow()
|
||||
|
||||
watch(isDragging, (dragging) => {
|
||||
document.body.style.userSelect = dragging ? 'none' : ''
|
||||
})
|
||||
|
||||
function onDragStart(event, type) {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.setData('application/vueflow', type)
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
|
||||
draggedType.value = type
|
||||
isDragging.value = true
|
||||
|
||||
document.addEventListener('drop', onDragEnd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the drag over event.
|
||||
*
|
||||
* @param {DragEvent} event
|
||||
*/
|
||||
function onDragOver(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (draggedType.value) {
|
||||
isDragOver.value = true
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDragLeave() {
|
||||
isDragOver.value = false
|
||||
}
|
||||
|
||||
function onDragEnd() {
|
||||
isDragging.value = false
|
||||
isDragOver.value = false
|
||||
draggedType.value = null
|
||||
document.removeEventListener('drop', onDragEnd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the drop event.
|
||||
*
|
||||
* @param {DragEvent} event
|
||||
*/
|
||||
function onDrop(event) {
|
||||
const position = screenToFlowCoordinate({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
})
|
||||
|
||||
const nodeId = getId()
|
||||
|
||||
const newNode = {
|
||||
id: nodeId,
|
||||
type: draggedType.value,
|
||||
position,
|
||||
label: `[${nodeId}]`,
|
||||
}
|
||||
|
||||
/**
|
||||
* Align node position after drop, so it's centered to the mouse
|
||||
*
|
||||
* We can hook into events even in a callback, and we can remove the event listener after it's been called.
|
||||
*/
|
||||
const { off } = onNodesInitialized(() => {
|
||||
updateNode(nodeId, (node) => ({
|
||||
position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 },
|
||||
}))
|
||||
|
||||
off()
|
||||
})
|
||||
|
||||
addNodes(newNode)
|
||||
}
|
||||
|
||||
return {
|
||||
draggedType,
|
||||
isDragOver,
|
||||
isDragging,
|
||||
onDragStart,
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
import { h, ref } from 'vue'
|
||||
import { Background } from '@vue-flow/background'
|
||||
import { MarkerType, VueFlow } from '@vue-flow/core'
|
||||
import { h, ref } from 'vue'
|
||||
import CustomEdge from './CustomEdge.vue'
|
||||
import CustomEdge2 from './CustomEdge2.vue'
|
||||
import CustomEdgeLabel from './CustomEdgeLabel.vue'
|
||||
|
||||
const elements = ref([
|
||||
const nodes = ref([
|
||||
{ id: '1', type: 'input', label: 'Start', position: { x: 50, y: 0 }, style: { borderColor: '#10b981' } },
|
||||
{ id: '2', label: 'Node 2', position: { x: 150, y: 100 } },
|
||||
{ id: '2a', label: 'Node 2a', position: { x: 0, y: 180 } },
|
||||
@@ -18,6 +18,9 @@ const elements = ref([
|
||||
{ id: '7', type: 'output', label: 'Output 7', position: { x: 50, y: 600 } },
|
||||
{ id: '8', type: 'output', label: 'Output 8', position: { x: 350, y: 600 } },
|
||||
{ id: '9', type: 'output', label: 'Output 9', position: { x: 550, y: 400 } },
|
||||
])
|
||||
|
||||
const edges = ref([
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', class: 'normal-edge' },
|
||||
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
|
||||
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
|
||||
@@ -61,7 +64,7 @@ const elements = ref([
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements" :fit-view-on-init="true" :snap-to-grid="true">
|
||||
<VueFlow :nodes="nodes" :edges="edges" fit-view-on-init>
|
||||
<template #edge-custom="props">
|
||||
<CustomEdge v-bind="props" />
|
||||
</template>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UpdateEdgeApp } from './update-edge'
|
||||
import { UpdateNodeApp, UpdateNodeCSS } from './update-node'
|
||||
import { ValidationApp, ValidationCSS, ValidationCustomInput, ValidationCustomNode } from './validation'
|
||||
import { SaveRestoreApp, SaveRestoreCSS, SaveRestoreControls } from './save-restore'
|
||||
import { DndApp, DndCSS, DndSidebar } from './dnd'
|
||||
import { DndApp, DndBackground, DndCSS, DndScript, DndSidebar } from './dnd'
|
||||
import { EmptyApp } from './empty'
|
||||
import { HiddenApp } from './hidden'
|
||||
import { InteractionApp, InteractionCSS, InteractionControls } from './interaction'
|
||||
@@ -75,7 +75,9 @@ export const exampleImports = {
|
||||
dnd: {
|
||||
'App.vue': DndApp,
|
||||
'Sidebar.vue': DndSidebar,
|
||||
'DropzoneBackground.vue': DndBackground,
|
||||
'style.css': DndCSS,
|
||||
'useDnD.js': DndScript,
|
||||
},
|
||||
empty: {
|
||||
'App.vue': EmptyApp,
|
||||
|
||||
Reference in New Issue
Block a user