refactor: use d3-drag

This commit is contained in:
Braks
2022-05-27 23:36:01 +02:00
parent 643f75273d
commit 95e36d0c2c
7 changed files with 765 additions and 1069 deletions
+1 -1
View File
@@ -37,10 +37,10 @@
"vue": "^3.2.25"
},
"dependencies": {
"@braks/revue-draggable": "^0.4.2",
"@types/d3": "^7.1.0",
"@vueuse/core": "^8.4.2",
"d3": "^7.4.4",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
},
@@ -1,14 +1,14 @@
<script lang="ts" setup>
import { useDraggableCore } from '@braks/revue-draggable'
import type { CSSProperties } from 'vue'
import { useVueFlow } from '../../composables'
import type { GraphNode, NodeComponent, SnapGrid } from '../../types'
import { CSSProperties } from 'vue'
import { useVueFlow, useDrag } from '../../composables'
import { GraphNode, NodeComponent, SnapGrid } from '../../types'
import { NodeId } from '../../context'
import { getConnectedEdges, getHandleBounds, getXYZPos } from '../../utils'
const { id, type, name, node, draggable, selectable, connectable, snapGrid } = defineProps<{
const { id, type, name, node, parentNode, draggable, selectable, connectable, snapGrid } = defineProps<{
id: string
node: GraphNode
parentNode?: GraphNode
draggable: boolean
selectable: boolean
connectable: boolean
@@ -31,60 +31,63 @@ const {
updateNodeDimensions,
getNode,
addSelectedNodes,
multiSelectionActive,
resetSelectedElements,
} = $(useVueFlow())
const nodeElement = ref()
const { scale, disabled, handle, cancel, grid, onDrag, onDragStart, onDragStop } = useDraggableCore(nodeElement, {
handle: node.dragHandle,
disabled: !draggable,
grid: snapGrid,
cancel: `.${noDragClassName}`,
enableUserSelectHack: false,
scale: viewport.zoom,
useDrag({
id,
el: nodeElement,
disabled: computed(() => !draggable),
noDragClassName: $$(noDragClassName) as any,
handleSelector: node.dragHandle,
onStart(event) {
if (selectNodesOnDrag && selectable) {
setState({
nodesSelectionActive: false,
})
if (!node.selected) {
addSelectedNodes([node])
}
} else if (!selectNodesOnDrag && !node.selected && selectable) {
if (multiSelectionActive) {
addSelectedNodes([node])
} else {
resetSelectedElements()
setState({
nodesSelectionActive: false,
})
}
}
emits.nodeDragStart({ event: event.sourceEvent, node, connectedEdges: getConnectedEdges([node], edges) })
},
onDrag(event, { dx, dy }) {
updateNodePosition({ id: node.id, diff: { x: dx, y: dy }, dragging: true })
emits.nodeDrag({ event: event.sourceEvent, node, connectedEdges: getConnectedEdges([node], edges) })
},
onStop(event) {
if (!node.dragging) {
if (selectable && !selectNodesOnDrag && !node.selected) {
addSelectedNodes([node])
}
emits.nodeClick({ event: event.sourceEvent, node, connectedEdges: getConnectedEdges([node], edges) })
return
}
updateNodePosition({ id: node.id, dragging: false })
emits.nodeDragStop({ event: event.sourceEvent, node, connectedEdges: getConnectedEdges([node], edges) })
},
})
onBeforeMount(() => {
updateNodePosition({ id: node.id, diff: { x: 0, y: 0 } })
updateNodePosition({ id: node.id, diff: { x: 0, y: 0 }, dragging: false })
})
onMounted(() => {
debouncedWatch(
() => viewport.zoom,
() => {
scale.value = viewport.zoom
},
{ debounce: 5, flush: 'post' },
)
watch(
() => draggable,
() => {
disabled.value = !draggable
},
)
watch(
() => node.dragHandle,
() => {
if (node.dragHandle) handle.value = node.dragHandle
},
)
watch($$(noDragClassName), () => {
if (noDragClassName) cancel.value = noDragClassName as any
})
watch(
() => snapGrid,
() => {
if (grid) grid.value = snapGrid
},
)
})
const parent = $computed(() => (node.parentNode ? getNode(node.parentNode) : undefined))
onMounted(() => {
const observer = useResizeObserver(nodeElement, () =>
updateNodeDimensions([{ id: node.id, nodeElement: nodeElement.value, forceUpdate: true }]),
@@ -103,7 +106,7 @@ onMounted(() => {
updateNodeDimensions([{ id: node.id, nodeElement: nodeElement.value, forceUpdate: true }])
watch(
[() => node.position, () => parent?.computedPosition, () => node.selected, () => parent?.selected],
[() => node.position, () => parentNode?.computedPosition, () => node.selected, () => parentNode?.selected],
([pos, parent]) => {
const xyzPos = {
...pos,
@@ -117,7 +120,7 @@ onMounted(() => {
graphNode.computedPosition = xyzPos
}
graphNode.handleBounds = getHandleBounds(nodeElement.value, scale.value)
graphNode.handleBounds = getHandleBounds(nodeElement.value, viewport.zoom)
},
{ deep: true, flush: 'post' },
)
@@ -169,44 +172,6 @@ const onSelectNode = (event: MouseEvent) => {
}
}
onDragStart(({ event }) => {
addSelectedNodes([])
emits.nodeDragStart({ event, node, connectedEdges: getConnectedEdges([node], edges) })
if (selectNodesOnDrag && selectable) {
setState({
nodesSelectionActive: false,
})
if (!node.selected) addSelectedNodes([node])
} else if (!selectNodesOnDrag && !node.selected && selectable) {
setState({
nodesSelectionActive: false,
})
addSelectedNodes([])
}
})
onDrag(({ event, data: { deltaX, deltaY } }) => {
updateNodePosition({ id: node.id, diff: { x: deltaX, y: deltaY }, dragging: true })
emits.nodeDrag({ event, node, connectedEdges: getConnectedEdges([node], edges) })
})
onDragStop(({ event, data: { deltaX, deltaY } }) => {
// onDragStop also gets called when user just clicks on a node.
// Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!node.dragging) {
if (selectable && !selectNodesOnDrag && !node.selected) {
addSelectedNodes([node])
}
emits.nodeClick({ event, node, connectedEdges: getConnectedEdges([node], edges) })
return
}
updateNodePosition({ id: node.id, diff: { x: deltaX, y: deltaY }, dragging: false })
emits.nodeDragStop({ event, node, connectedEdges: getConnectedEdges([node], edges) })
})
const getClass = computed(() => {
const extraClass = node.class instanceof Function ? node.class(node) : node.class
return [
@@ -1,12 +1,26 @@
<script lang="ts" setup>
import { useDraggableCore } from '@braks/revue-draggable'
import { useVueFlow } from '../../composables'
import { useVueFlow, useDrag } from '../../composables'
import { getRectOfNodes } from '../../utils'
const { emits, setState, viewport, getSelectedNodes, snapToGrid, snapGrid, updateNodePosition, noPanClassName } = $(useVueFlow())
const el = templateRef<HTMLDivElement>('el', null)
useDrag({
el,
onStart(event) {
emits.selectionDragStart({ event: event.sourceEvent, nodes: getSelectedNodes })
},
onDrag(event, { dx, dy }) {
emits.selectionDrag({ event: event.sourceEvent, nodes: getSelectedNodes })
updateNodePosition({ diff: { x: dx, y: dy }, dragging: true })
},
onStop(event) {
emits.selectionDragStop({ event: event.sourceEvent, nodes: getSelectedNodes })
getSelectedNodes.forEach((node) => (node.dragging = false))
},
})
const selectedNodesBBox = $computed(() => getRectOfNodes(getSelectedNodes))
const innerStyle = computed(() => ({
@@ -25,34 +39,6 @@ watch($$(selectedNodesBBox), (v) => {
})
const onContextMenu = (event: MouseEvent) => emits.selectionContextMenu({ event, nodes: getSelectedNodes })
const { onDragStart, onDrag, onDragStop, scale } = useDraggableCore(el, {
grid: snapToGrid ? snapGrid : undefined,
enableUserSelectHack: false,
scale: viewport.zoom,
})
onMounted(() => {
debouncedWatch(
() => viewport.zoom,
() => {
scale.value = viewport.zoom
},
{ debounce: 5 },
)
})
onDragStart(({ event }) => emits.selectionDragStart({ event, nodes: getSelectedNodes }))
onDrag(({ event, data: { deltaX, deltaY } }) => {
emits.selectionDrag({ event, nodes: getSelectedNodes })
updateNodePosition({ diff: { x: deltaX, y: deltaY }, dragging: true })
})
onDragStop(({ event }) => {
emits.selectionDragStop({ event, nodes: getSelectedNodes })
getSelectedNodes.forEach((node) => (node.dragging = false))
})
</script>
<script lang="ts">
@@ -3,3 +3,4 @@ export { default as useKeyPress } from './useKeyPress'
export { default as useZoomPanHelper } from './useZoomPanHelper'
export { default as useWindow } from './useWindow'
export { default as useVueFlow } from './useVueFlow'
export { default as useDrag } from './useDrag'
@@ -0,0 +1,128 @@
import { D3DragEvent, drag, SubjectPosition } from 'd3-drag'
import { select } from 'd3-selection'
import { Ref } from 'vue'
import { MaybeRef } from '@vueuse/core'
import useVueFlow from './useVueFlow'
import { pointToRendererPoint } from '~/utils'
import { GraphNode, XYPosition } from '~/types'
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>
export type UseDragData = { dx: number; dy: number }
type UseDragParams = {
onStart: (event: UseDragEvent) => void
onDrag: (event: UseDragEvent, data: UseDragData) => void
onStop: (event: UseDragEvent) => void
el: Ref<Element>
disabled?: MaybeRef<boolean>
noDragClassName?: MaybeRef<string>
handleSelector?: string
id?: string
}
function getOffset(event: UseDragEvent, el: Element): XYPosition {
const bounds = el.getBoundingClientRect() || { x: 0, y: 0 }
const parent = (el as HTMLDivElement).offsetParent
const parentBounds = parent?.getBoundingClientRect() || { x: 0, y: 0 }
return {
x: event.x - (bounds.x - parentBounds.x - (parent?.scrollLeft || 0)),
y: event.y - (bounds.y - parentBounds.y - (parent?.scrollTop || 0)),
}
}
function getParentNodePosition(parent?: GraphNode): XYPosition {
return {
x: parent?.computedPosition?.x || 0,
y: parent?.computedPosition?.y || 0,
}
}
function hasSelector(target: Element, selector: string, node: Ref<Element>): boolean {
let current = target
do {
if (current && current.matches(selector)) return true
else if (current === node.value) return false
current = current.parentElement as Element
} while (current)
return false
}
function useDrag(params: UseDragParams) {
const { viewport, snapToGrid, snapGrid, getNode } = useVueFlow()
const { onStart, onDrag, onStop, el, disabled = false, noDragClassName, id, handleSelector } = $(params)
let startPos = $ref<XYPosition>({ x: 0, y: 0 })
let lastPos = $ref<Partial<XYPosition>>({ x: undefined, y: undefined })
let parentPos = $ref<XYPosition>({ x: 0, y: 0 })
return watch(
[() => disabled, () => noDragClassName, () => id, () => el],
() => {
if (el) {
const selection = select(el)
const node = id ? getNode.value(id) : undefined
if (disabled) {
selection.on('.drag', null)
} else {
const dragHandler = drag()
.on('start', (event: UseDragEvent) => {
const offset = getOffset(event, el)
parentPos = getParentNodePosition(node && node.parentNode ? getNode.value(node!.parentNode!) : undefined)
startPos = {
x: offset.x - viewport.value.x,
y: offset.y - viewport.value.y,
}
onStart(event)
})
.on('drag', (event: UseDragEvent) => {
const pos = pointToRendererPoint(
{
x: event.x - startPos.x,
y: event.y - startPos.y,
},
viewport.value,
snapToGrid.value,
snapGrid.value,
)
pos.x -= parentPos.x
pos.y -= parentPos.y
// skip events without movement
if (lastPos.x !== pos.x || lastPos.y !== pos.y) {
if (lastPos.x && lastPos.y) {
onDrag(event, {
dx: pos.x - lastPos.x,
dy: pos.y - lastPos.y,
})
}
lastPos = pos
}
})
.on('end', onStop)
.filter((event: any) => {
if (handleSelector) {
return !hasSelector(event.sourceEvent.target, handleSelector, $$(el))
}
return !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName)
})
selection.call(dragHandler)
}
}
},
{ flush: 'post' },
)
}
export default useDrag
+2 -1
View File
@@ -1,5 +1,4 @@
import type { EventHook, EventHookOn, EventHookTrigger } from '@vueuse/core'
import type { MouseTouchEvent } from '@braks/revue-draggable'
import type { D3ZoomEvent } from 'd3-zoom'
import type { FlowInstance } from './flow'
import type { GraphEdge } from './edge'
@@ -8,6 +7,8 @@ import type { Connection, OnConnectStartParams } from './connection'
import type { FlowTransform } from './zoom'
import type { EdgeChange, NodeChange } from './changes'
export type MouseTouchEvent = MouseEvent | TouchEvent | PointerEvent
export interface NodeMouseEvent {
event: MouseTouchEvent
node: GraphNode
+562 -947
View File
File diff suppressed because it is too large Load Diff