feat(core): add onError hook

Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
braks
2023-03-23 15:58:30 +01:00
committed by Braks
parent 87249e5dde
commit 8c3f54ec28
22 changed files with 250 additions and 180 deletions
+3 -1
View File
@@ -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) {
+2
View File
@@ -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,
)
+3 -2
View File
@@ -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 } {
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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'
+34 -11
View File
@@ -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')
}
}
+1 -1
View File
@@ -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
}
+53 -52
View File
@@ -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)
+2 -63
View File
@@ -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,
}
}
+10 -4
View File
@@ -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)
+47
View File
@@ -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
}
}
-6
View File
@@ -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)
+13 -7
View File
@@ -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]) {