refactor(vue-flow,nodes): use absolute positions instead of deltas
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useDrag, useVueFlow } from '../../composables'
|
||||
import type { NodeComponent, SnapGrid, XYZPosition } from '../../types'
|
||||
import type { NodeComponent, SnapGrid } from '../../types'
|
||||
import { NodeId } from '../../context'
|
||||
import { getConnectedEdges, getHandleBounds, getXYZPos } from '../../utils'
|
||||
import { getConnectedEdges, getHandleBounds, getXYZPos, handleNodeClick } from '../../utils'
|
||||
|
||||
const { id, type, name, draggable, selectable, connectable, snapGrid, ...props } = defineProps<{
|
||||
id: string
|
||||
@@ -14,8 +14,6 @@ const { id, type, name, draggable, selectable, connectable, snapGrid, ...props }
|
||||
name: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
provide(NodeId, id)
|
||||
|
||||
const {
|
||||
@@ -26,7 +24,6 @@ const {
|
||||
emits,
|
||||
selectNodesOnDrag,
|
||||
setState,
|
||||
updateNodePosition,
|
||||
updateNodeDimensions,
|
||||
getNode,
|
||||
addSelectedNodes,
|
||||
@@ -40,45 +37,20 @@ const parentNode = $computed(() => (node.parentNode ? getNode(node.parentNode) :
|
||||
|
||||
const nodeElement = ref()
|
||||
|
||||
let dragging = $ref(false)
|
||||
|
||||
useDrag({
|
||||
const dragging = useDrag({
|
||||
id,
|
||||
el: nodeElement,
|
||||
disabled: computed(() => !draggable),
|
||||
noDragClassName: $$(noDragClassName) as any,
|
||||
handleSelector: node.dragHandle,
|
||||
onStart(event) {
|
||||
removeSelectedElements()
|
||||
if (selectNodesOnDrag && selectable) {
|
||||
setState({
|
||||
nodesSelectionActive: false,
|
||||
})
|
||||
|
||||
if (!node.selected) {
|
||||
addSelectedNodes([node])
|
||||
}
|
||||
} else if (!selectNodesOnDrag && !node.selected && selectable) {
|
||||
if (multiSelectionActive) {
|
||||
addSelectedNodes([node])
|
||||
} else {
|
||||
removeSelectedElements()
|
||||
setState({
|
||||
nodesSelectionActive: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
emits.nodeDragStart({ event: event.sourceEvent, node, nodes: [] })
|
||||
onStart(event, node, nodes) {
|
||||
emits.nodeDragStart({ event, node, nodes })
|
||||
},
|
||||
onDrag(event, { dx, dy }) {
|
||||
dragging = true
|
||||
updateNodePosition({ id, diff: { x: dx, y: dy } })
|
||||
emits.nodeDrag({ event: event.sourceEvent, node, nodes: [] })
|
||||
onDrag(event, node, nodes) {
|
||||
emits.nodeDrag({ event, node, nodes })
|
||||
},
|
||||
onStop(event) {
|
||||
dragging = false
|
||||
emits.nodeDragStop({ event: event.sourceEvent, node, nodes: [] })
|
||||
onStop(event, node, nodes) {
|
||||
emits.nodeDragStop({ event, node, nodes })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -156,22 +128,10 @@ const onDoubleClick = (event: MouseEvent) =>
|
||||
emits.nodeDoubleClick({ event, node, connectedEdges: getConnectedEdges([node], edges) })
|
||||
|
||||
const onSelectNode = (event: MouseEvent) => {
|
||||
if (!dragging) {
|
||||
if (selectable && !selectNodesOnDrag && !node.selected) {
|
||||
setState({
|
||||
nodesSelectionActive: false,
|
||||
})
|
||||
|
||||
if (!node.selected) {
|
||||
addSelectedNodes([node])
|
||||
} else if (node.selected && multiSelectionActive) {
|
||||
// todo add action to unselect nodes/edges easily
|
||||
getSelectedNodes.splice(getSelectedNodes.indexOf(node), 1)
|
||||
node.selected = false
|
||||
}
|
||||
}
|
||||
emits.nodeClick({ event, node, connectedEdges: getConnectedEdges([node], edges) })
|
||||
if (selectable && (!selectNodesOnDrag || !draggable)) {
|
||||
handleNodeClick(node, multiSelectionActive, addSelectedNodes, removeSelectedElements, setState)
|
||||
}
|
||||
emits.nodeClick({ event, node, connectedEdges: getConnectedEdges([node], edges) })
|
||||
}
|
||||
|
||||
const getClass = computed(() => {
|
||||
|
||||
@@ -4,19 +4,16 @@ import { select } from 'd3-selection'
|
||||
import type { Ref } from 'vue'
|
||||
import type { MaybeRef } from '@vueuse/core'
|
||||
import useVueFlow from './useVueFlow'
|
||||
import { pointToRendererPoint } from '~/utils'
|
||||
import type { GraphNode, XYPosition } from '~/types'
|
||||
import { handleNodeClick, pointToRendererPoint } from '~/utils'
|
||||
import type { NodeDragEvent, NodeDragItem, XYPosition } from '~/types'
|
||||
import { getDragItems, getEventHandlerParams, getParentNodePosition, hasSelector, updatePosition } from '~/utils/drag'
|
||||
|
||||
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>
|
||||
export interface UseDragData {
|
||||
dx: number
|
||||
dy: number
|
||||
}
|
||||
|
||||
interface UseDragParams {
|
||||
onStart: (event: UseDragEvent) => void
|
||||
onDrag: (event: UseDragEvent, data: UseDragData) => void
|
||||
onStop: (event: UseDragEvent) => void
|
||||
onStart: (event: NodeDragEvent['event'], currentNode: NodeDragEvent['node'], nodes: NodeDragEvent['nodes']) => void
|
||||
onDrag: (event: NodeDragEvent['event'], currentNode: NodeDragEvent['node'], nodes: NodeDragEvent['nodes']) => void
|
||||
onStop: (event: NodeDragEvent['event'], currentNode: NodeDragEvent['node'], nodes: NodeDragEvent['nodes']) => void
|
||||
el: Ref<Element>
|
||||
disabled?: MaybeRef<boolean>
|
||||
noDragClassName?: MaybeRef<string>
|
||||
@@ -24,103 +21,124 @@ interface UseDragParams {
|
||||
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 {
|
||||
viewport,
|
||||
snapToGrid,
|
||||
snapGrid,
|
||||
nodes,
|
||||
nodeExtent,
|
||||
getNode,
|
||||
multiSelectionActive,
|
||||
selectNodesOnDrag,
|
||||
removeSelectedElements,
|
||||
addSelectedNodes,
|
||||
setState,
|
||||
updateNodePositions,
|
||||
} = $(useVueFlow())
|
||||
const { onStart, onDrag, onStop, el, disabled = false, noDragClassName, id, handleSelector } = $(params)
|
||||
|
||||
let startPos = $ref<XYPosition>({ x: 0, y: 0 })
|
||||
const dragging = ref(false)
|
||||
let dragItems = $ref<NodeDragItem[]>()
|
||||
let lastPos = $ref<Partial<XYPosition>>({ x: undefined, y: undefined })
|
||||
let parentPos = $ref<XYPosition>({ x: 0, y: 0 })
|
||||
let dragHandler = $ref<any>()
|
||||
|
||||
const handleDrag = (event: UseDragEvent) => {
|
||||
const pos = pointToRendererPoint(
|
||||
const getMousePosition = (event: UseDragEvent) => {
|
||||
const mousePos = pointToRendererPoint(
|
||||
{
|
||||
x: event.x - startPos.x,
|
||||
y: event.y - startPos.y,
|
||||
x: event.sourceEvent.clientX,
|
||||
y: event.sourceEvent.clientY,
|
||||
},
|
||||
viewport.value,
|
||||
snapToGrid.value,
|
||||
snapGrid.value,
|
||||
viewport,
|
||||
snapToGrid,
|
||||
snapGrid,
|
||||
)
|
||||
|
||||
pos.x -= parentPos.x
|
||||
pos.y -= parentPos.y
|
||||
mousePos.x -= parentPos.x
|
||||
mousePos.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
|
||||
return mousePos
|
||||
}
|
||||
|
||||
return watch(
|
||||
watch(
|
||||
[() => disabled, () => noDragClassName, () => id, () => el],
|
||||
() => {
|
||||
if (el) {
|
||||
const selection = select(el)
|
||||
const node = id ? getNode.value(id) : undefined
|
||||
const node = id ? getNode(id) : undefined
|
||||
|
||||
if (disabled) {
|
||||
selection.on('.drag', null)
|
||||
} else {
|
||||
const dragHandler = drag()
|
||||
dragHandler = drag()
|
||||
.on('start', (event: UseDragEvent) => {
|
||||
const offset = getOffset(event, el)
|
||||
parentPos = getParentNodePosition(node && node.parentNode ? getNode.value(node!.parentNode!) : undefined)
|
||||
parentPos = getParentNodePosition(node && node.parentNode ? getNode(node!.parentNode!) : undefined)
|
||||
|
||||
startPos = {
|
||||
x: offset.x - viewport.value.x,
|
||||
y: offset.y - viewport.value.y,
|
||||
if (!selectNodesOnDrag && !multiSelectionActive && id) {
|
||||
if (!node?.selected) {
|
||||
removeSelectedElements()
|
||||
}
|
||||
}
|
||||
|
||||
onStart(event)
|
||||
if (node && !disabled && selectNodesOnDrag) {
|
||||
handleNodeClick(node, multiSelectionActive, addSelectedNodes, removeSelectedElements, setState)
|
||||
}
|
||||
|
||||
const mousePos = getMousePosition(event)
|
||||
dragItems = getDragItems(nodes, mousePos, getNode, id)
|
||||
|
||||
if (onStart && dragItems) {
|
||||
const [currentNode, nodes] = getEventHandlerParams({
|
||||
id,
|
||||
dragItems,
|
||||
node: node!,
|
||||
})
|
||||
onStart(event.sourceEvent, currentNode, nodes)
|
||||
}
|
||||
})
|
||||
.on('drag', handleDrag)
|
||||
.on('end', (event: UseDragEvent) => {
|
||||
if (node) lastPos = node.position
|
||||
onStop(event)
|
||||
.on('drag', (event: UseDragEvent) => {
|
||||
const mousePos = getMousePosition(event)
|
||||
|
||||
console.log(node?.label)
|
||||
|
||||
// skip events without movement
|
||||
if ((lastPos.x !== mousePos.x || lastPos.y !== mousePos.y) && dragItems) {
|
||||
lastPos = mousePos
|
||||
dragItems = dragItems.map((n) =>
|
||||
updatePosition(n, mousePos, n.parentNode ? getNode(n.parentNode) : undefined, nodeExtent),
|
||||
)
|
||||
|
||||
updateNodePositions(dragItems)
|
||||
dragging.value = true
|
||||
|
||||
if (onDrag) {
|
||||
const [currentNode, nodes] = getEventHandlerParams({
|
||||
id,
|
||||
dragItems,
|
||||
node: node!,
|
||||
})
|
||||
onDrag(event.sourceEvent, currentNode, nodes)
|
||||
}
|
||||
}
|
||||
|
||||
event.on('end', (event) => {
|
||||
if (onStop && dragItems) {
|
||||
const [currentNode, nodes] = getEventHandlerParams({
|
||||
id,
|
||||
dragItems,
|
||||
node: node!,
|
||||
})
|
||||
onStop(event.sourceEvent, currentNode, nodes)
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('end', (_: UseDragEvent) => {
|
||||
// if (node) lastPos = node.position
|
||||
})
|
||||
.filter((event: D3DragEvent<HTMLDivElement, null, SubjectPosition>['sourceEvent']) => {
|
||||
const filter = !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName)
|
||||
const filter =
|
||||
!event.ctrlKey && !event.button && (!noDragClassName || !event.target.className.includes(noDragClassName))
|
||||
|
||||
if (handleSelector) {
|
||||
return !hasSelector(event.sourceEvent.target, handleSelector, $$(el)) && filter
|
||||
@@ -135,6 +153,8 @@ function useDrag(params: UseDragParams) {
|
||||
},
|
||||
{ flush: 'post' },
|
||||
)
|
||||
|
||||
return dragging
|
||||
}
|
||||
|
||||
export default useDrag
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
applyChanges,
|
||||
createAdditionChange,
|
||||
createGraphNodes,
|
||||
createPositionChange,
|
||||
createSelectionChange,
|
||||
getDimensions,
|
||||
getHandleBounds,
|
||||
@@ -26,34 +25,26 @@ import {
|
||||
isGraphEdge,
|
||||
isGraphNode,
|
||||
isNode,
|
||||
isParentSelected,
|
||||
parseEdge,
|
||||
updateEdgeAction,
|
||||
} from '~/utils'
|
||||
|
||||
export default (state: State, getters: ComputedGetters): Actions => {
|
||||
const updateNodePosition: Actions['updateNodePosition'] = ({ id, diff = { x: 0, y: 0 } }) => {
|
||||
const nodePosPromise = new Promise<NodePositionChange[]>((resolve) => {
|
||||
const changes: NodePositionChange[] = []
|
||||
const curr = id ? getters.getNode.value(id)! : undefined
|
||||
if (curr) {
|
||||
changes.push(createPositionChange({ node: curr, diff, nodeExtent: state.nodeExtent }, getters.getNode.value))
|
||||
} else {
|
||||
getters.getSelectedNodes.value.forEach((node) => {
|
||||
if (!node.parentNode) {
|
||||
changes.push(createPositionChange({ node, diff, nodeExtent: state.nodeExtent }, getters.getNode.value))
|
||||
} else if (!isParentSelected(node, getters.getNode.value)) {
|
||||
changes.push(createPositionChange({ node, diff, nodeExtent: state.nodeExtent }, getters.getNode.value))
|
||||
}
|
||||
})
|
||||
const updateNodePositions: Actions['updateNodePositions'] = (dragItems) => {
|
||||
const changes: NodePositionChange[] = []
|
||||
|
||||
dragItems.forEach((node) => {
|
||||
const change: NodePositionChange = {
|
||||
id: node.id,
|
||||
type: 'position',
|
||||
position: node.position,
|
||||
}
|
||||
|
||||
if (changes.length) resolve(changes)
|
||||
changes.push(change)
|
||||
})
|
||||
|
||||
nodePosPromise.then((changes) => {
|
||||
if (changes?.length) {
|
||||
state.hooks.nodesChange.trigger(changes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updateNodeDimensions: Actions['updateNodeDimensions'] = (updates) => {
|
||||
@@ -274,7 +265,7 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
||||
}
|
||||
|
||||
return {
|
||||
updateNodePosition,
|
||||
updateNodePositions,
|
||||
updateNodeDimensions,
|
||||
setElements,
|
||||
setNodes,
|
||||
|
||||
@@ -2,6 +2,19 @@ import type { Dimensions, ElementData, XYPosition } from './flow'
|
||||
import type { GraphNode, NodeHandleBounds } from './node'
|
||||
import type { GraphEdge } from './edge'
|
||||
|
||||
export interface NodeDragItem {
|
||||
id: string
|
||||
// relative node position
|
||||
position: XYPosition
|
||||
// distance from the mouse cursor to the node when start dragging
|
||||
distance: XYPosition
|
||||
// delta to previous position
|
||||
delta: XYPosition
|
||||
dimensions: Dimensions
|
||||
extent?: Node['extent']
|
||||
parentNode?: string
|
||||
}
|
||||
|
||||
export interface NodeDimensionChange {
|
||||
id: string
|
||||
type: 'dimensions'
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { DefaultEdgeOptions, Edge, GraphEdge } from './edge'
|
||||
import type { CoordinateExtent, GraphNode, Node } from './node'
|
||||
import type { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode, Viewport } from './zoom'
|
||||
import type { FlowHooks, FlowHooksEmit, FlowHooksOn } from './hooks'
|
||||
import type { EdgeChange, NodeChange } from './changes'
|
||||
import type { EdgeChange, NodeChange, NodeDragItem } from "./changes";
|
||||
import type { HandleType, StartHandle } from './handle'
|
||||
|
||||
export interface UpdateNodeDimensionsParams {
|
||||
@@ -15,11 +15,6 @@ export interface UpdateNodeDimensionsParams {
|
||||
forceUpdate?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateNodePositionsParams {
|
||||
id?: string
|
||||
diff?: XYPosition
|
||||
}
|
||||
|
||||
export interface State extends Omit<FlowOptions, 'id' | 'modelValue'> {
|
||||
/** Event hooks, you can manipulate the triggers at your own peril */
|
||||
readonly hooks: FlowHooks
|
||||
@@ -118,7 +113,7 @@ export type SetState = (
|
||||
| Partial<FlowOptions & Omit<State, 'nodes' | 'edges' | 'modelValue'>>
|
||||
| ((state: State) => Partial<FlowOptions & Omit<State, 'nodes' | 'edges' | 'modelValue'>>),
|
||||
) => void
|
||||
export type UpdateNodePosition = (params: UpdateNodePositionsParams) => void
|
||||
export type UpdateNodePosition = (dragItems: NodeDragItem[]) => void
|
||||
export type UpdateNodeDimensions = (updates: UpdateNodeDimensionsParams[]) => void
|
||||
|
||||
export interface Actions {
|
||||
@@ -162,7 +157,7 @@ export interface Actions {
|
||||
setState: SetState
|
||||
|
||||
/** internal position updater, you probably don't want to use this */
|
||||
updateNodePosition: UpdateNodePosition
|
||||
updateNodePositions: UpdateNodePosition
|
||||
/** internal dimensions' updater, you probably don't want to use this */
|
||||
updateNodeDimensions: UpdateNodeDimensions
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { clampPosition, isGraphEdge, isGraphNode } from './graph'
|
||||
import { isGraphEdge, isGraphNode } from './graph'
|
||||
import type {
|
||||
CoordinateExtent,
|
||||
Edge,
|
||||
EdgeAddChange,
|
||||
EdgeChange,
|
||||
@@ -14,18 +13,11 @@ import type {
|
||||
Node,
|
||||
NodeAddChange,
|
||||
NodeChange,
|
||||
NodePositionChange,
|
||||
NodeSelectionChange,
|
||||
XYPosition,
|
||||
} from '~/types'
|
||||
|
||||
interface CreatePositionChangeParams {
|
||||
node: GraphNode
|
||||
nodeExtent: CoordinateExtent
|
||||
diff?: XYPosition
|
||||
}
|
||||
|
||||
function handleParentExpand(updateItem: GraphNode, parent: GraphNode) {
|
||||
function handleParentExpand(updateItem: GraphNode, curr: GraphNode[]) {
|
||||
const parent = updateItem.parentNode ? curr.find((el) => el.id === updateItem.parentNode) : undefined
|
||||
if (parent) {
|
||||
const extendWidth = updateItem.position.x + updateItem.dimensions.width - parent.dimensions.width
|
||||
const extendHeight = updateItem.position.y + updateItem.dimensions.height - parent.dimensions.height
|
||||
@@ -158,39 +150,10 @@ export const createSelectionChange = (id: string, selected: boolean): NodeSelect
|
||||
selected,
|
||||
})
|
||||
|
||||
export const createPositionChange = (
|
||||
{ node, diff, nodeExtent }: CreatePositionChangeParams,
|
||||
getNode: Getters['getNode'],
|
||||
): NodePositionChange => {
|
||||
const parent = node.parentNode ? getNode(node.parentNode) : undefined
|
||||
const change: NodePositionChange = {
|
||||
id: node.id,
|
||||
type: 'position',
|
||||
}
|
||||
|
||||
if (diff) {
|
||||
const nextPosition = { x: node.position.x + diff.x, y: node.position.y + diff.y }
|
||||
let currentExtent = node.extent === 'parent' || typeof node.extent === 'undefined' ? nodeExtent : node.extent
|
||||
|
||||
if (node.extent === 'parent' && parent && node.dimensions.width && node.dimensions.height) {
|
||||
if (parent.dimensions.width && parent.dimensions.height) {
|
||||
currentExtent = [
|
||||
[0, 0],
|
||||
[parent.dimensions.width - node.dimensions.width, parent.dimensions.height - node.dimensions.height],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
change.position = currentExtent ? clampPosition(nextPosition, currentExtent) : nextPosition
|
||||
}
|
||||
|
||||
return change
|
||||
}
|
||||
|
||||
export const createAdditionChange = <
|
||||
T extends GraphNode | GraphEdge = GraphNode,
|
||||
C extends NodeAddChange | EdgeAddChange = T extends GraphNode ? NodeAddChange : EdgeAddChange,
|
||||
>(
|
||||
>(
|
||||
item: T,
|
||||
): C =>
|
||||
<C>{
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { clampPosition, isParentSelected } from './graph'
|
||||
import type { CoordinateExtent, Getters, GraphNode, NodeDragItem, XYPosition } from '~/types'
|
||||
|
||||
export function getParentNodePosition(parent?: GraphNode): XYPosition {
|
||||
return {
|
||||
x: parent?.computedPosition?.x || 0,
|
||||
y: parent?.computedPosition?.y || 0,
|
||||
}
|
||||
}
|
||||
|
||||
export 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
|
||||
}
|
||||
|
||||
export function getDragItems(
|
||||
nodes: GraphNode[],
|
||||
mousePos: XYPosition,
|
||||
getNode: Getters['getNode'],
|
||||
nodeId?: string,
|
||||
): NodeDragItem[] {
|
||||
return nodes
|
||||
.filter((n) => (n.selected || n.id === nodeId) && (!n.parentNode || !isParentSelected(n, getNode)))
|
||||
.map((n) => ({
|
||||
id: n.id,
|
||||
position: n.position,
|
||||
distance: {
|
||||
x: mousePos.x - n.position.x,
|
||||
y: mousePos.y - n.position.y,
|
||||
},
|
||||
delta: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
extent: n.extent,
|
||||
parentNode: n.parentNode,
|
||||
dimensions: n.dimensions,
|
||||
}))
|
||||
}
|
||||
|
||||
export function getEventHandlerParams({
|
||||
id,
|
||||
dragItems,
|
||||
node,
|
||||
}: {
|
||||
id?: string
|
||||
dragItems: NodeDragItem[]
|
||||
node: GraphNode
|
||||
}): [GraphNode, GraphNode[]] {
|
||||
const extendedDragItems: GraphNode[] = dragItems.map((n) => {
|
||||
return {
|
||||
...node,
|
||||
position: n.position,
|
||||
computedPosition: {
|
||||
x: (node.computedPosition?.x || 0) + n.delta.x,
|
||||
y: (node.computedPosition?.y || 0) + n.delta.y,
|
||||
z: node.computedPosition.z,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return [id ? extendedDragItems.find((n) => n.id === id)! : extendedDragItems[0], extendedDragItems]
|
||||
}
|
||||
|
||||
export function updatePosition(
|
||||
dragItem: NodeDragItem,
|
||||
mousePos: XYPosition,
|
||||
parent?: GraphNode,
|
||||
nodeExtent?: CoordinateExtent,
|
||||
): NodeDragItem {
|
||||
let currentExtent = dragItem.extent || nodeExtent
|
||||
let nextPosition = { x: mousePos.x - dragItem.distance.x, y: mousePos.y - dragItem.distance.y }
|
||||
|
||||
if (dragItem.extent === 'parent') {
|
||||
if (dragItem.parentNode && dragItem.dimensions.width && dragItem.dimensions.height) {
|
||||
currentExtent =
|
||||
parent?.width && parent?.height
|
||||
? [
|
||||
[0, 0],
|
||||
[parent.dimensions.width - dragItem.dimensions.width, parent.dimensions.height - dragItem.dimensions.height],
|
||||
]
|
||||
: currentExtent
|
||||
} else {
|
||||
currentExtent = nodeExtent
|
||||
}
|
||||
}
|
||||
|
||||
nextPosition = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition
|
||||
|
||||
dragItem.delta = {
|
||||
x: nextPosition.x - dragItem.position.x,
|
||||
y: nextPosition.y - dragItem.position.y,
|
||||
}
|
||||
dragItem.position = nextPosition
|
||||
|
||||
return dragItem
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getDimensions } from './graph'
|
||||
import type { HandleElement, Position } from '~/types'
|
||||
import type { Actions, GraphNode, HandleElement, Position } from '~/types'
|
||||
|
||||
export const getHandleBoundsByHandleType = (
|
||||
selector: string,
|
||||
@@ -37,3 +37,19 @@ export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number, id?:
|
||||
target: getHandleBoundsByHandleType(`.target${id ? `.vue-flow__handle-${id}` : ''}`, nodeElement, bounds, scale) ?? undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export const handleNodeClick = (
|
||||
node: GraphNode,
|
||||
multiSelectionActive: boolean,
|
||||
addSelectedNodes: Actions['addSelectedNodes'],
|
||||
removeSelectedElements: Actions['removeSelectedElements'],
|
||||
setState: Actions['setState'],
|
||||
) => {
|
||||
setState({ nodesSelectionActive: false })
|
||||
|
||||
if (!node.selected) {
|
||||
addSelectedNodes([node])
|
||||
} else if (node.selected && multiSelectionActive) {
|
||||
removeSelectedElements({ nodes: [node] })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user