feat(core): add onError hook
Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
Vendored
+3
-1
@@ -14,12 +14,13 @@ declare global {
|
||||
const EdgeId: typeof import('./context/index')['EdgeId']
|
||||
const EdgeRef: typeof import('./context/index')['EdgeRef']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ErrorCode: typeof import('./utils/errors')['ErrorCode']
|
||||
const NodeId: typeof import('./context/index')['NodeId']
|
||||
const NodeRef: typeof import('./context/index')['NodeRef']
|
||||
const Slots: typeof import('./context/index')['Slots']
|
||||
const Storage: typeof import('./composables/useVueFlow')['Storage']
|
||||
const VueFlow: typeof import('./context/index')['VueFlow']
|
||||
const VueFlowError: typeof import('./utils/log')['VueFlowError']
|
||||
const VueFlowError: typeof import('./utils/errors')['VueFlowError']
|
||||
const addEdge: typeof import('./utils/graph')['addEdge']
|
||||
const addEdgeToStore: typeof import('./utils/store')['addEdgeToStore']
|
||||
const applyChanges: typeof import('./utils/changes')['applyChanges']
|
||||
@@ -44,6 +45,7 @@ declare global {
|
||||
const createAdditionChange: typeof import('./utils/changes')['createAdditionChange']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createExtendedEventHook: typeof import('./utils/createExtendedEventHook')['createExtendedEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createGraphNodes: typeof import('./utils/store')['createGraphNodes']
|
||||
const createHooks: typeof import('./store/hooks')['createHooks']
|
||||
|
||||
@@ -235,7 +235,7 @@ const NodeWrapper = defineComponent({
|
||||
nextPos.y = snapGrid[1] * Math.round(nextPos.y / snapGrid[1])
|
||||
}
|
||||
|
||||
const { computedPosition, position } = calcNextPosition(node, nextPos, nodeExtent, parentNode)
|
||||
const { computedPosition, position } = calcNextPosition(node, nextPos, emits.error, nodeExtent, parentNode)
|
||||
|
||||
// only overwrite positions if there are changes when clamping
|
||||
if (node.computedPosition.x !== computedPosition.x || node.computedPosition.y !== computedPosition.y) {
|
||||
|
||||
@@ -40,6 +40,7 @@ function useDrag(params: UseDragParams) {
|
||||
removeSelectedElements,
|
||||
addSelectedNodes,
|
||||
updateNodePositions,
|
||||
emits,
|
||||
} = $(useVueFlow())
|
||||
|
||||
const { onStart, onDrag, onStop, el, disabled, id, selectable } = $(params)
|
||||
@@ -83,6 +84,7 @@ function useDrag(params: UseDragParams) {
|
||||
const { computedPosition } = calcNextPosition(
|
||||
n,
|
||||
nextPosition,
|
||||
emits.error,
|
||||
nodeExtent,
|
||||
n.parentNode ? findNode(n.parentNode) : undefined,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CustomEvent, ElementData } from '~/types'
|
||||
import { VueFlowError } from '~/utils/errors'
|
||||
|
||||
/**
|
||||
* Access an edge
|
||||
@@ -11,12 +12,12 @@ export default function useEdge<Data = ElementData, CustomEvents extends Record<
|
||||
const edgeId = id ?? inject(EdgeId, '')
|
||||
const edgeEl = inject(EdgeRef, null)
|
||||
|
||||
const { findEdge } = useVueFlow()
|
||||
const { findEdge, emits } = useVueFlow()
|
||||
|
||||
const edge = findEdge<Data, CustomEvents>(edgeId)
|
||||
|
||||
if (!edge) {
|
||||
throw new VueFlowError(`Edge with id ${edgeId} not found!`, 'useEdge')
|
||||
emits.error(new VueFlowError(ErrorCode.EDGE_NOT_FOUND, edgeId))
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { EdgeEventsEmit, EdgeEventsOn, GraphEdge, VueFlowStore } from '~/types'
|
||||
|
||||
const createEdgeHooks = () => ({
|
||||
doubleClick: createEventHook(),
|
||||
click: createEventHook(),
|
||||
mouseEnter: createEventHook(),
|
||||
mouseMove: createEventHook(),
|
||||
mouseLeave: createEventHook(),
|
||||
contextMenu: createEventHook(),
|
||||
updateStart: createEventHook(),
|
||||
update: createEventHook(),
|
||||
updateEnd: createEventHook(),
|
||||
doubleClick: createExtendedEventHook(),
|
||||
click: createExtendedEventHook(),
|
||||
mouseEnter: createExtendedEventHook(),
|
||||
mouseMove: createExtendedEventHook(),
|
||||
mouseLeave: createExtendedEventHook(),
|
||||
contextMenu: createExtendedEventHook(),
|
||||
updateStart: createExtendedEventHook(),
|
||||
update: createExtendedEventHook(),
|
||||
updateEnd: createExtendedEventHook(),
|
||||
})
|
||||
|
||||
export default function useEdgeHooks(edge: GraphEdge, emits: VueFlowStore['emits']): { emit: EdgeEventsEmit; on: EdgeEventsOn } {
|
||||
|
||||
@@ -11,19 +11,19 @@ export default function useNode<Data = ElementData, CustomEvents extends Record<
|
||||
const nodeId = id ?? inject(NodeId, '')
|
||||
const nodeEl = inject(NodeRef, null)
|
||||
|
||||
const { findNode, getEdges } = useVueFlow()
|
||||
const { findNode, getEdges, emits } = useVueFlow()
|
||||
|
||||
const node = findNode<Data, CustomEvents>(nodeId)
|
||||
|
||||
if (!node) {
|
||||
throw new VueFlowError(`Node with id ${nodeId} not found!`, 'useNode')
|
||||
emits.error(new VueFlowError(ErrorCode.NODE_NOT_FOUND, nodeId))
|
||||
}
|
||||
|
||||
return {
|
||||
id: nodeId,
|
||||
nodeEl,
|
||||
node,
|
||||
parentNode: computed(() => (node.parentNode ? findNode(node.parentNode) : undefined)),
|
||||
connectedEdges: computed(() => getConnectedEdges([node], getEdges.value)),
|
||||
parentNode: computed(() => (node!.parentNode ? findNode(node!.parentNode) : undefined)),
|
||||
connectedEdges: computed(() => getConnectedEdges([node!], getEdges.value)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { GraphNode, NodeEventsEmit, NodeEventsOn, VueFlowStore } from '~/types'
|
||||
|
||||
const createNodeHooks = () => ({
|
||||
doubleClick: createEventHook(),
|
||||
click: createEventHook(),
|
||||
mouseEnter: createEventHook(),
|
||||
mouseMove: createEventHook(),
|
||||
mouseLeave: createEventHook(),
|
||||
contextMenu: createEventHook(),
|
||||
dragStart: createEventHook(),
|
||||
drag: createEventHook(),
|
||||
dragStop: createEventHook(),
|
||||
doubleClick: createExtendedEventHook(),
|
||||
click: createExtendedEventHook(),
|
||||
mouseEnter: createExtendedEventHook(),
|
||||
mouseMove: createExtendedEventHook(),
|
||||
mouseLeave: createExtendedEventHook(),
|
||||
contextMenu: createExtendedEventHook(),
|
||||
dragStart: createExtendedEventHook(),
|
||||
drag: createExtendedEventHook(),
|
||||
dragStop: createExtendedEventHook(),
|
||||
})
|
||||
|
||||
export default function useNodeHooks(node: GraphNode, emits: VueFlowStore['emits']): { emit: NodeEventsEmit; on: NodeEventsOn } {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { NodeDragItem, XYPosition } from '~/types'
|
||||
|
||||
function useUpdateNodePositions() {
|
||||
const { getSelectedNodes, nodeExtent, updateNodePositions, findNode, snapGrid, snapToGrid, nodesDraggable } = useVueFlow()
|
||||
const { getSelectedNodes, nodeExtent, updateNodePositions, findNode, snapGrid, snapToGrid, nodesDraggable, emits } =
|
||||
useVueFlow()
|
||||
|
||||
return (positionDiff: XYPosition, isShiftPressed = false) => {
|
||||
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
||||
@@ -21,6 +22,7 @@ function useUpdateNodePositions() {
|
||||
const { computedPosition } = calcNextPosition(
|
||||
n,
|
||||
nextPosition,
|
||||
emits.error,
|
||||
nodeExtent.value,
|
||||
n.parentNode ? findNode(n.parentNode) : undefined,
|
||||
)
|
||||
|
||||
@@ -123,11 +123,6 @@ export default (options?: FlowProps): VueFlowStore => {
|
||||
if (options) vueFlow.setState(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue flow wasn't able to find any store instance - we can't proceed
|
||||
*/
|
||||
if (!vueFlow) throw new VueFlowError('Store instance not found.', 'useVueFlow')
|
||||
|
||||
// always provide a fresh instance into context on call
|
||||
if (scope) {
|
||||
provide(VueFlow, vueFlow)
|
||||
|
||||
@@ -19,6 +19,7 @@ const {
|
||||
getEdgeTypes,
|
||||
elevateEdgesOnSelect,
|
||||
dimensions,
|
||||
emits,
|
||||
} = $(useVueFlow())
|
||||
|
||||
const sourceNode = controlledComputed(
|
||||
@@ -75,7 +76,7 @@ function getType(type?: string, template?: GraphEdge['template']) {
|
||||
|
||||
const slot = slots?.[`edge-${name}`]
|
||||
if (!slot) {
|
||||
warn(`Edge type "${type}" not found and no edge-slot detected. Using fallback type "default".`)
|
||||
emits.error(new VueFlowError(ErrorCode.EDGE_TYPE_MISSING, edgeType))
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ function getType(type?: string, template?: GraphNode['template']) {
|
||||
|
||||
const slot = slots?.[`node-${name}`]
|
||||
if (!slot) {
|
||||
warn(`Node type "${type}" not found and no node-slot detected. Using fallback type "default".`)
|
||||
emits.error(new VueFlowError(ErrorCode.NODE_TYPE_MISSING, nodeType))
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +287,9 @@ function setDimensions() {
|
||||
|
||||
const { width, height } = getDimensions(viewportEl.value)
|
||||
|
||||
if (width === 0 || height === 0) warn('The Vue Flow parent container needs a width and a height to render the graph.')
|
||||
if (width === 0 || height === 0) {
|
||||
emits.error(new VueFlowError(ErrorCode.MISSING_VIEWPORT_DIMENSIONS))
|
||||
}
|
||||
|
||||
dimensions.width = width || 500
|
||||
dimensions.height = height || 500
|
||||
|
||||
@@ -71,6 +71,6 @@ export { default as useEdge } from './composables/useEdge'
|
||||
|
||||
export { useGetPointerPosition } from './composables/useGetPointerPosition'
|
||||
|
||||
export { VueFlowError } from './utils/log'
|
||||
export { VueFlowError, ErrorCode } from './utils/errors'
|
||||
|
||||
export * from './types'
|
||||
|
||||
@@ -290,7 +290,7 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
|
||||
if (!state.initialized && !nextNodes.length) return
|
||||
|
||||
state.nodes = createGraphNodes(nextNodes, findNode, state.nodes)
|
||||
state.nodes = createGraphNodes(nextNodes, state.nodes, findNode, state.hooks.error.trigger)
|
||||
}
|
||||
|
||||
const setEdges: Actions['setEdges'] = (edges) => {
|
||||
@@ -315,9 +315,21 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
const missingSource = !sourceNode || typeof sourceNode === 'undefined'
|
||||
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
||||
|
||||
if (missingSource) warn(`Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||
if (missingTarget) warn(`Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||
if (missingSource || missingTarget) return res
|
||||
if (missingSource && missingTarget) {
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_SOURCE_TARGET_MISSING, edge.id, edge.source, edge.target))
|
||||
} else {
|
||||
if (missingSource) {
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_SOURCE_MISSING, edge.id, edge.source))
|
||||
}
|
||||
|
||||
if (missingTarget) {
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_TARGET_MISSING, edge.id, edge.target))
|
||||
}
|
||||
}
|
||||
|
||||
if (missingSource || missingTarget) {
|
||||
return res
|
||||
}
|
||||
|
||||
const storedEdge = getters.getEdge.value(edge.id)
|
||||
|
||||
@@ -343,7 +355,7 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
const addNodes: Actions['addNodes'] = (nodes) => {
|
||||
const nextNodes = nodes instanceof Function ? nodes(state.nodes) : nodes
|
||||
|
||||
const graphNodes = createGraphNodes(nextNodes, findNode, state.nodes)
|
||||
const graphNodes = createGraphNodes(nextNodes, state.nodes, findNode, state.hooks.error.trigger)
|
||||
|
||||
const changes = graphNodes.map(createAdditionChange)
|
||||
|
||||
@@ -370,6 +382,7 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
...state.defaultEdgeOptions,
|
||||
},
|
||||
state.edges,
|
||||
state.hooks.error.trigger,
|
||||
)
|
||||
|
||||
if (edge) {
|
||||
@@ -379,9 +392,21 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
const missingSource = !sourceNode || typeof sourceNode === 'undefined'
|
||||
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
||||
|
||||
if (missingSource) warn(`Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||
if (missingTarget) warn(`Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||
if (missingTarget || missingSource) return acc
|
||||
if (missingSource && missingTarget) {
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_SOURCE_TARGET_MISSING, edge.id, edge.source, edge.target))
|
||||
} else {
|
||||
if (missingSource) {
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_SOURCE_MISSING, edge.id, edge.source))
|
||||
}
|
||||
|
||||
if (missingTarget) {
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_TARGET_MISSING, edge.id, edge.target))
|
||||
}
|
||||
}
|
||||
|
||||
if (missingSource || missingTarget) {
|
||||
return acc
|
||||
}
|
||||
|
||||
acc.push(
|
||||
createAdditionChange<GraphEdge>({
|
||||
@@ -445,7 +470,7 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
}
|
||||
|
||||
const updateEdge: Actions['updateEdge'] = (oldEdge, newConnection, shouldReplaceId = true) =>
|
||||
updateEdgeAction(oldEdge, newConnection, state.edges, findEdge, shouldReplaceId)
|
||||
updateEdgeAction(oldEdge, newConnection, state.edges, findEdge, shouldReplaceId, state.hooks.error.trigger)
|
||||
|
||||
const applyNodeChanges: Actions['applyNodeChanges'] = (changes) => applyChanges(changes, state.nodes)
|
||||
|
||||
@@ -468,8 +493,6 @@ export function useActions(state: State, getters: ComputedGetters): Actions {
|
||||
state.connectionPosition = position
|
||||
state.connectionStartHandle.result = result
|
||||
state.connectionStatus = status
|
||||
} else {
|
||||
warn('Cannot update connection as it has not been started')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export function useGetters(state: State): ComputedGetters {
|
||||
if (!source || !target) {
|
||||
state.edges = state.edges.filter((edge) => edge.id !== e.id)
|
||||
|
||||
warn(`Orphaned edge ${e.id} removed.`)
|
||||
state.hooks.error.trigger(new VueFlowError(ErrorCode.EDGE_ORPHANED, e.id))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +1,65 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { Emits, FlowHooks } from '~/types'
|
||||
import type { FlowHooks } from '~/types'
|
||||
|
||||
// flow event hooks
|
||||
export const createHooks = (): FlowHooks => ({
|
||||
edgesChange: createEventHook(),
|
||||
nodesChange: createEventHook(),
|
||||
nodeDoubleClick: createEventHook(),
|
||||
nodeClick: createEventHook(),
|
||||
nodeMouseEnter: createEventHook(),
|
||||
nodeMouseMove: createEventHook(),
|
||||
nodeMouseLeave: createEventHook(),
|
||||
nodeContextMenu: createEventHook(),
|
||||
nodeDragStart: createEventHook(),
|
||||
nodeDrag: createEventHook(),
|
||||
nodeDragStop: createEventHook(),
|
||||
nodesInitialized: createEventHook(),
|
||||
miniMapNodeClick: createEventHook(),
|
||||
miniMapNodeDoubleClick: createEventHook(),
|
||||
miniMapNodeMouseEnter: createEventHook(),
|
||||
miniMapNodeMouseMove: createEventHook(),
|
||||
miniMapNodeMouseLeave: createEventHook(),
|
||||
connect: createEventHook(),
|
||||
connectStart: createEventHook(),
|
||||
connectEnd: createEventHook(),
|
||||
paneReady: createEventHook(),
|
||||
move: createEventHook(),
|
||||
moveStart: createEventHook(),
|
||||
moveEnd: createEventHook(),
|
||||
selectionDragStart: createEventHook(),
|
||||
selectionDrag: createEventHook(),
|
||||
selectionDragStop: createEventHook(),
|
||||
selectionContextMenu: createEventHook(),
|
||||
selectionStart: createEventHook(),
|
||||
selectionEnd: createEventHook(),
|
||||
viewportChangeStart: createEventHook(),
|
||||
viewportChange: createEventHook(),
|
||||
viewportChangeEnd: createEventHook(),
|
||||
paneScroll: createEventHook(),
|
||||
paneClick: createEventHook(),
|
||||
paneContextMenu: createEventHook(),
|
||||
paneMouseEnter: createEventHook(),
|
||||
paneMouseMove: createEventHook(),
|
||||
paneMouseLeave: createEventHook(),
|
||||
edgeContextMenu: createEventHook(),
|
||||
edgeMouseEnter: createEventHook(),
|
||||
edgeMouseMove: createEventHook(),
|
||||
edgeMouseLeave: createEventHook(),
|
||||
edgeDoubleClick: createEventHook(),
|
||||
edgeClick: createEventHook(),
|
||||
edgeUpdateStart: createEventHook(),
|
||||
edgeUpdate: createEventHook(),
|
||||
edgeUpdateEnd: createEventHook(),
|
||||
updateNodeInternals: createEventHook(),
|
||||
edgesChange: createExtendedEventHook(),
|
||||
nodesChange: createExtendedEventHook(),
|
||||
nodeDoubleClick: createExtendedEventHook(),
|
||||
nodeClick: createExtendedEventHook(),
|
||||
nodeMouseEnter: createExtendedEventHook(),
|
||||
nodeMouseMove: createExtendedEventHook(),
|
||||
nodeMouseLeave: createExtendedEventHook(),
|
||||
nodeContextMenu: createExtendedEventHook(),
|
||||
nodeDragStart: createExtendedEventHook(),
|
||||
nodeDrag: createExtendedEventHook(),
|
||||
nodeDragStop: createExtendedEventHook(),
|
||||
nodesInitialized: createExtendedEventHook(),
|
||||
miniMapNodeClick: createExtendedEventHook(),
|
||||
miniMapNodeDoubleClick: createExtendedEventHook(),
|
||||
miniMapNodeMouseEnter: createExtendedEventHook(),
|
||||
miniMapNodeMouseMove: createExtendedEventHook(),
|
||||
miniMapNodeMouseLeave: createExtendedEventHook(),
|
||||
connect: createExtendedEventHook(),
|
||||
connectStart: createExtendedEventHook(),
|
||||
connectEnd: createExtendedEventHook(),
|
||||
paneReady: createExtendedEventHook(),
|
||||
move: createExtendedEventHook(),
|
||||
moveStart: createExtendedEventHook(),
|
||||
moveEnd: createExtendedEventHook(),
|
||||
selectionDragStart: createExtendedEventHook(),
|
||||
selectionDrag: createExtendedEventHook(),
|
||||
selectionDragStop: createExtendedEventHook(),
|
||||
selectionContextMenu: createExtendedEventHook(),
|
||||
selectionStart: createExtendedEventHook(),
|
||||
selectionEnd: createExtendedEventHook(),
|
||||
viewportChangeStart: createExtendedEventHook(),
|
||||
viewportChange: createExtendedEventHook(),
|
||||
viewportChangeEnd: createExtendedEventHook(),
|
||||
paneScroll: createExtendedEventHook(),
|
||||
paneClick: createExtendedEventHook(),
|
||||
paneContextMenu: createExtendedEventHook(),
|
||||
paneMouseEnter: createExtendedEventHook(),
|
||||
paneMouseMove: createExtendedEventHook(),
|
||||
paneMouseLeave: createExtendedEventHook(),
|
||||
edgeContextMenu: createExtendedEventHook(),
|
||||
edgeMouseEnter: createExtendedEventHook(),
|
||||
edgeMouseMove: createExtendedEventHook(),
|
||||
edgeMouseLeave: createExtendedEventHook(),
|
||||
edgeDoubleClick: createExtendedEventHook(),
|
||||
edgeClick: createExtendedEventHook(),
|
||||
edgeUpdateStart: createExtendedEventHook(),
|
||||
edgeUpdate: createExtendedEventHook(),
|
||||
edgeUpdateEnd: createExtendedEventHook(),
|
||||
updateNodeInternals: createExtendedEventHook(),
|
||||
error: createExtendedEventHook((err) => warn(err.message)),
|
||||
})
|
||||
|
||||
export function useHooks(emit: Emits, hooks: Ref<FlowHooks>) {
|
||||
export function useHooks(emit: (...args: any[]) => void, hooks: Ref<FlowHooks>) {
|
||||
onBeforeMount(() => {
|
||||
for (const [key, value] of Object.entries(hooks.value)) {
|
||||
const listener = (data: any) => {
|
||||
emit(key as Emits extends (event: infer Event) => void ? Event : never, data)
|
||||
emit(key, data)
|
||||
}
|
||||
|
||||
value.on(listener)
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Connection, OnConnectStartParams } from './connection'
|
||||
import type { ViewportTransform } from './zoom'
|
||||
import type { EdgeChange, NodeChange } from './changes'
|
||||
import type { VueFlowStore } from './store'
|
||||
import type { FlowElements } from './flow'
|
||||
import type { VueFlowError } from '~/utils/errors'
|
||||
|
||||
export type MouseTouchEvent = MouseEvent | TouchEvent
|
||||
|
||||
@@ -86,68 +86,7 @@ export interface FlowEvents {
|
||||
edgeUpdateStart: EdgeMouseEvent
|
||||
edgeUpdate: EdgeUpdateEvent
|
||||
edgeUpdateEnd: EdgeMouseEvent
|
||||
}
|
||||
|
||||
export interface Emits {
|
||||
(event: 'nodesChange', changes: NodeChange[]): void
|
||||
(event: 'edgesChange', changes: EdgeChange[]): void
|
||||
(event: 'nodeDoubleClick', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'nodeClick', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'nodeMouseEnter', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'nodeMouseMove', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'nodeMouseLeave', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'nodeContextMenu', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'nodeDragStart', nodeDragEvent: NodeDragEvent): void
|
||||
(event: 'nodeDrag', nodeDragEvent: NodeDragEvent): void
|
||||
(event: 'nodeDragStop', nodeDragEvent: NodeDragEvent): void
|
||||
(event: 'nodesInitialized'): void
|
||||
(event: 'miniMapNodeClick', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'miniMapNodeDoubleClick', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'miniMapNodeMouseEnter', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'miniMapNodeMouseMove', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'miniMapNodeMouseLeave', nodeMouseEvent: NodeMouseEvent): void
|
||||
(event: 'connect', connectionEvent: Connection): void
|
||||
(
|
||||
event: 'connectStart',
|
||||
connectionEvent: {
|
||||
event?: MouseEvent
|
||||
} & OnConnectStartParams,
|
||||
): void
|
||||
(event: 'connectEnd', connectionEvent?: MouseEvent): void
|
||||
(event: 'moveStart', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewportTransform }): void
|
||||
(event: 'move', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewportTransform }): void
|
||||
(event: 'moveEnd', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewportTransform }): void
|
||||
(event: 'selectionDragStart', selectionEvent: NodeDragEvent): void
|
||||
(event: 'selectionDrag', selectionEvent: NodeDragEvent): void
|
||||
(event: 'selectionDragStop', selectionEvent: NodeDragEvent): void
|
||||
(event: 'selectionContextMenu', selectionEvent: { event: MouseEvent; nodes: GraphNode[] }): void
|
||||
(event: 'selectionStart', selectionEvent: MouseEvent): void
|
||||
(event: 'selectionEnd', selectionEvent: MouseEvent): void
|
||||
(event: 'viewportChangeStart', viewport: ViewportTransform): void
|
||||
(event: 'viewportChange', viewport: ViewportTransform): void
|
||||
(event: 'viewportChangeEnd', viewport: ViewportTransform): void
|
||||
(event: 'paneReady', paneEvent: VueFlowStore): void
|
||||
(event: 'paneScroll', paneEvent: WheelEvent | undefined): void
|
||||
(event: 'paneClick', paneEvent: MouseEvent): void
|
||||
(event: 'paneContextMenu', paneEvent: MouseEvent): void
|
||||
(event: 'paneMouseEnter', paneEvent: MouseEvent): void
|
||||
(event: 'paneMouseMove', paneEvent: MouseEvent): void
|
||||
(event: 'paneMouseLeave', paneEvent: MouseEvent): void
|
||||
(event: 'edgeContextMenu', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeMouseEnter', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeMouseMove', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeMouseLeave', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeDoubleClick', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeClick', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeUpdateStart', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'edgeUpdate', edgeUpdateEvent: EdgeUpdateEvent): void
|
||||
(event: 'edgeUpdateEnd', edgeMouseEvent: EdgeMouseEvent): void
|
||||
(event: 'updateNodeInternals'): void
|
||||
|
||||
/** v-model event definitions */
|
||||
(event: 'update:modelValue', value: FlowElements): void
|
||||
(event: 'update:nodes', value: GraphNode[]): void
|
||||
(event: 'update:edges', value: GraphEdge[]): void
|
||||
error: VueFlowError
|
||||
}
|
||||
|
||||
export type FlowHooks = Readonly<{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { EventHook } from '@vueuse/core'
|
||||
|
||||
/**
|
||||
* Source code taken from https://github.com/vueuse/vueuse/blob/main/packages/shared/createEventHook/index.ts
|
||||
*
|
||||
* Modified to be able to check if there are any event listeners
|
||||
*/
|
||||
export interface EventHookExtended<T> extends EventHook<T> {
|
||||
fns: Ref<Set<(param: T) => void>>
|
||||
}
|
||||
|
||||
export function createExtendedEventHook<T = any>(defaultHandler: (param: T) => void = () => {}): EventHookExtended<T> {
|
||||
const fns = ref(new Set<(param: T) => void>())
|
||||
|
||||
if (defaultHandler) {
|
||||
fns.value.add(defaultHandler)
|
||||
}
|
||||
|
||||
const off = (fn: (param: T) => void) => {
|
||||
fns.value.delete(fn)
|
||||
}
|
||||
|
||||
const on = (fn: (param: T) => void) => {
|
||||
if (fns.value.has(defaultHandler)) {
|
||||
fns.value.delete(defaultHandler)
|
||||
}
|
||||
|
||||
fns.value.add(fn)
|
||||
const offFn = () => off(fn)
|
||||
|
||||
tryOnScopeDispose(offFn)
|
||||
|
||||
return {
|
||||
off: offFn,
|
||||
}
|
||||
}
|
||||
|
||||
const trigger = (param: T) => {
|
||||
return Promise.all(Array.from(fns.value).map((fn) => fn(param)))
|
||||
}
|
||||
|
||||
return {
|
||||
on,
|
||||
off,
|
||||
trigger,
|
||||
fns,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isNumber } from '@vueuse/shared'
|
||||
import type { Actions, CoordinateExtent, ExtendedParentExtent, GraphNode, NodeDragItem, XYPosition } from '~/types'
|
||||
import type { Actions, CoordinateExtent, ExtendedParentExtent, GraphNode, NodeDragItem, State, XYPosition } from '~/types'
|
||||
|
||||
export function hasSelector(target: Element, selector: string, node: Element): boolean {
|
||||
let current = target
|
||||
@@ -97,7 +97,12 @@ function getParentExtent(
|
||||
return false
|
||||
}
|
||||
|
||||
export function getExtent<T extends NodeDragItem | GraphNode>(item: T, extent?: CoordinateExtent, parent?: GraphNode) {
|
||||
export function getExtent<T extends NodeDragItem | GraphNode>(
|
||||
item: T,
|
||||
onError: State['hooks']['error']['trigger'],
|
||||
extent?: CoordinateExtent,
|
||||
parent?: GraphNode,
|
||||
) {
|
||||
let currentExtent = item.extent || extent
|
||||
|
||||
if (item.extent === 'parent' || (!Array.isArray(item.extent) && item.extent?.range === 'parent')) {
|
||||
@@ -108,7 +113,7 @@ export function getExtent<T extends NodeDragItem | GraphNode>(item: T, extent?:
|
||||
currentExtent = parentExtent
|
||||
}
|
||||
} else {
|
||||
warn('Only child nodes can use a parent extent.')
|
||||
onError(new VueFlowError(ErrorCode.NODE_EXTENT_INVALID, item.id))
|
||||
|
||||
currentExtent = extent
|
||||
}
|
||||
@@ -127,10 +132,11 @@ export function getExtent<T extends NodeDragItem | GraphNode>(item: T, extent?:
|
||||
export function calcNextPosition(
|
||||
node: GraphNode | NodeDragItem,
|
||||
nextPosition: XYPosition,
|
||||
onError: State['hooks']['error']['trigger'],
|
||||
nodeExtent?: CoordinateExtent,
|
||||
parentNode?: GraphNode,
|
||||
) {
|
||||
const extent = getExtent(node, nodeExtent, parentNode)
|
||||
const extent = getExtent(node, onError, nodeExtent, parentNode)
|
||||
|
||||
const clampedPos = clampPosition(nextPosition, extent)
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
export enum ErrorCode {
|
||||
MISSING_VIEWPORT_DIMENSIONS = 'MISSING_VIEWPORT_DIMENSIONS',
|
||||
NODE_NOT_FOUND = 'NODE_NOT_FOUND',
|
||||
NODE_MISSING_PARENT = 'NODE_MISSING_PARENT',
|
||||
NODE_TYPE_MISSING = 'NODE_TYPE_MISSING',
|
||||
NODE_EXTENT_INVALID = 'NODE_EXTENT_INVALID',
|
||||
EDGE_INVALID = 'EDGE_INVALID',
|
||||
EDGE_NOT_FOUND = 'EDGE_NOT_FOUND',
|
||||
EDGE_SOURCE_MISSING = 'EDGE_SOURCE_MISSING',
|
||||
EDGE_TARGET_MISSING = 'EDGE_TARGET_MISSING',
|
||||
EDGE_TYPE_MISSING = 'EDGE_TYPE_MISSING',
|
||||
EDGE_SOURCE_TARGET_SAME = 'EDGE_SOURCE_TARGET_SAME',
|
||||
EDGE_SOURCE_TARGET_MISSING = 'EDGE_SOURCE_TARGET_MISSING',
|
||||
EDGE_ORPHANED = 'EDGE_ORPHANED',
|
||||
}
|
||||
|
||||
const messages = {
|
||||
[ErrorCode.MISSING_VIEWPORT_DIMENSIONS]: () => 'The Vue Flow parent container needs a width and a height to render the graph',
|
||||
[ErrorCode.NODE_NOT_FOUND]: (id: string) => `Node not found\nNode: ${id}`,
|
||||
[ErrorCode.NODE_MISSING_PARENT]: (id: string, parentId: string) => `Node is missing a parent\nNode: ${id}\nParent: ${parentId}`,
|
||||
[ErrorCode.NODE_TYPE_MISSING]: (type: string) => `Node type is missing\nType: ${type}`,
|
||||
[ErrorCode.NODE_EXTENT_INVALID]: (id: string) => `Only child nodes can use a parent extent\nNode: ${id}`,
|
||||
[ErrorCode.EDGE_INVALID]: (id: string) => `An edge needs a source and a target\nEdge: ${id}`,
|
||||
[ErrorCode.EDGE_SOURCE_MISSING]: (id: string, source: string) => `Edge source is missing\nEdge: ${id} \nSource: ${source}`,
|
||||
[ErrorCode.EDGE_TARGET_MISSING]: (id: string, target: string) => `Edge target is missing\nEdge: ${id} \nTarget: ${target}`,
|
||||
[ErrorCode.EDGE_TYPE_MISSING]: (type: string) => `Edge type is missing\nType: ${type}`,
|
||||
[ErrorCode.EDGE_SOURCE_TARGET_SAME]: (id: string, source: string, target: string) =>
|
||||
`Edge source and target are the same\nEdge: ${id} \nSource: ${source} \nTarget: ${target}`,
|
||||
[ErrorCode.EDGE_SOURCE_TARGET_MISSING]: (id: string, source: string, target: string) =>
|
||||
`Edge source or target is missing\nEdge: ${id} \nSource: ${source} \nTarget: ${target}`,
|
||||
[ErrorCode.EDGE_ORPHANED]: (id: string) =>
|
||||
`Edge was orphaned (suddenly missing source or target) and has been removed\nEdge: ${id}`,
|
||||
[ErrorCode.EDGE_NOT_FOUND]: (id: string) => `Edge not found\nEdge: ${id}`,
|
||||
} as const
|
||||
|
||||
export class VueFlowError<T extends ErrorCode = ErrorCode> extends Error {
|
||||
code: T
|
||||
constructor(
|
||||
code: T,
|
||||
...args: (typeof messages)[T] extends (...args: any[]) => string ? Parameters<(typeof messages)[T]> : never
|
||||
) {
|
||||
// @ts-expect-error - TS doesn't know that the message is a key of messages
|
||||
super(messages[code]?.(...args))
|
||||
|
||||
this.code = code
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
const productionEnvs = ['production', 'prod']
|
||||
|
||||
export class VueFlowError extends Error {
|
||||
constructor(message: string, scope?: string) {
|
||||
super(`[Vue Flow]: ${scope ? `${scope} - ` : ''}${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function warn(message: string, ...args: any[]) {
|
||||
if (!productionEnvs.includes(__ENV__ || '')) {
|
||||
console.warn(`[Vue Flow]: ${message}`, ...args)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Actions, Connection, Edge, GraphEdge, GraphNode, Node } from '~/types'
|
||||
import type { Actions, Connection, Edge, GraphEdge, GraphNode, Node, State } from '~/types'
|
||||
|
||||
export const isDef = <T>(val: T): val is NonNullable<T> => typeof unref(val) !== 'undefined'
|
||||
|
||||
export function addEdgeToStore(edgeParams: Edge | Connection, edges: Edge[]) {
|
||||
export function addEdgeToStore(edgeParams: Edge | Connection, edges: Edge[], onError: State['hooks']['error']['trigger']) {
|
||||
if (!edgeParams.source || !edgeParams.target) {
|
||||
warn("Can't create edge. An edge needs a source and a target.")
|
||||
onError(new VueFlowError(ErrorCode.EDGE_INVALID, (edgeParams as Edge).id))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -31,16 +31,17 @@ export function updateEdgeAction(
|
||||
edges: GraphEdge[],
|
||||
findEdge: Actions['findEdge'],
|
||||
shouldReplaceId: boolean,
|
||||
onError: State['hooks']['error']['trigger'],
|
||||
) {
|
||||
if (!newConnection.source || !newConnection.target) {
|
||||
warn("Can't create new edge. An edge needs a source and a target.")
|
||||
onError(new VueFlowError(ErrorCode.EDGE_INVALID, edge.id))
|
||||
return false
|
||||
}
|
||||
|
||||
const foundEdge = findEdge(edge.id)
|
||||
|
||||
if (!foundEdge) {
|
||||
warn(`The old edge with id=${edge.id} does not exist.`)
|
||||
onError(new VueFlowError(ErrorCode.EDGE_NOT_FOUND, edge.id))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -60,7 +61,12 @@ export function updateEdgeAction(
|
||||
return newEdge
|
||||
}
|
||||
|
||||
export function createGraphNodes(nodes: Node[], findNode: Actions['findNode'], currGraphNodes: GraphNode[]) {
|
||||
export function createGraphNodes(
|
||||
nodes: Node[],
|
||||
currGraphNodes: GraphNode[],
|
||||
findNode: Actions['findNode'],
|
||||
onError: State['hooks']['error']['trigger'],
|
||||
) {
|
||||
const parentNodes: Record<string, true> = {}
|
||||
|
||||
const graphNodes = nodes.map((node) => {
|
||||
@@ -82,7 +88,7 @@ export function createGraphNodes(nodes: Node[], findNode: Actions['findNode'], c
|
||||
const parentNode = nextNodes.find((n) => n.id === node.parentNode)
|
||||
|
||||
if (node.parentNode && !parentNode) {
|
||||
warn(`Parent node ${node.parentNode} not found`)
|
||||
onError(new VueFlowError(ErrorCode.NODE_MISSING_PARENT, node.id, node.parentNode))
|
||||
}
|
||||
|
||||
if (node.parentNode || parentNodes[node.id]) {
|
||||
|
||||
Reference in New Issue
Block a user