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