diff --git a/cypress/integration/2-flow/container.spec.ts b/cypress/integration/2-flow/container.spec.ts index c3cda8ec..266a9cbd 100644 --- a/cypress/integration/2-flow/container.spec.ts +++ b/cypress/integration/2-flow/container.spec.ts @@ -2,6 +2,7 @@ import { mount } from '@cypress/vue' import VueFlow from '../../../src/container/VueFlow/VueFlow.vue' import { Elements } from '~/types' import { isEdge, isNode } from '~/utils' +import '~/theme-default.css' describe('Render VueFlow', () => { const elements: Elements = [ diff --git a/src/composables/useElementState.ts b/src/composables/useElementState.ts deleted file mode 100644 index 46fee38b..00000000 --- a/src/composables/useElementState.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { - FlowStore, - GraphEdge, - NodeChange, - EdgeChange, - UseEdgesStateOptions, - UseEdgeState, - Edge, - Connection, - GraphNode, - UseNodesStateOptions, - UseNodesState, -} from '~/types' -import { connectionExists, getEdgeId, isEdge, isGraphEdge, parseEdge } from '~/utils' -import { parseChildren } from '~/store' - -const addEdge = (edgeParams: Edge | Connection, edges: Edge[]) => { - if (!edgeParams.source || !edgeParams.target) { - console.warn("Can't create edge. An edge needs a source and a target.") - return false - } - - let edge - if (isEdge(edgeParams)) { - edge = { ...edgeParams } - } else { - edge = { - ...edgeParams, - id: getEdgeId(edgeParams), - } as Edge - } - edge = parseEdge(edge) - if (connectionExists(edge, edges)) return false - return edge -} - -const updateEdge = (edge: GraphEdge, newConnection: Connection, edges: GraphEdge[]) => { - if (!newConnection.source || !newConnection.target) { - console.warn("Can't create new edge. An edge needs a source and a target.") - return false - } - - const foundEdge = edges.find((e) => isGraphEdge(e) && e.id === edge.id) - - if (!foundEdge) { - console.warn(`The old edge with id=${edge.id} does not exist.`) - return false - } - - edge.id = getEdgeId(newConnection) - edge.source = newConnection.source - edge.target = newConnection.target - edge.sourceHandle = newConnection.sourceHandle - edge.targetHandle = newConnection.targetHandle - - return edge -} - -export const useEdgesState = ( - store: FlowStore, - apply: (changes: EdgeChange[]) => GraphEdge[], -): ((options?: UseEdgesStateOptions) => UseEdgeState) => { - return ({ edges, applyDefault } = { applyDefault: true }) => { - if (edges && edges.length) store.setEdges(edges) - if (applyDefault) store.hooksOn.OnEdgesChange((e) => apply(e)) - return { - edges: store.edges, - setEdges: store.setEdges, - addEdges: (params) => { - params.forEach((param) => { - const edge = addEdge(param, store.edges) - if (edge) { - const sourceNode = store.getNode(edge.source)! - const targetNode = store.getNode(edge.target)! - if (!sourceNode || typeof sourceNode === 'undefined') - console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`) - if (!targetNode || typeof targetNode === 'undefined') - console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`) - - store.edges.push({ - ...edge, - sourceNode, - targetNode, - }) - } - }) - return store.edges - }, - updateEdge: (oldEdge, newConnection) => updateEdge(oldEdge, newConnection, store.edges), - OnEdgesChange: store.hooksOn.OnEdgesChange, - } - } -} - -export const useNodesState = ( - store: FlowStore, - apply: (changes: NodeChange[]) => GraphNode[], -): ((options?: UseNodesStateOptions) => UseNodesState) => { - return ({ nodes, applyDefault } = { applyDefault: true }) => { - if (nodes && nodes.length) store.setNodes(nodes) - if (applyDefault) store.hooksOn.OnNodesChange((e) => apply(e)) - return { - nodes: store.nodes, - setNodes: store.setNodes, - addNodes: (nodes, extent) => { - const parsed = nodes.flatMap((node) => { - const children: GraphNode[] = [] - parseChildren(node, undefined, children, extent ?? store.nodeExtent, store.getNode) - return children - }) - store.nodes.push(...parsed) - return store.nodes - }, - OnNodesChange: store.hooksOn.OnNodesChange, - } - } -} diff --git a/src/composables/useElementsState.ts b/src/composables/useElementsState.ts new file mode 100644 index 00000000..5bc563de --- /dev/null +++ b/src/composables/useElementsState.ts @@ -0,0 +1,129 @@ +import useVueFlow from './useVueFlow' +import { + GraphEdge, + NodeChange, + EdgeChange, + UseEdgesStateOptions, + UseEdgesState, + Edge, + Connection, + GraphNode, + UseNodesStateOptions, + UseNodesState, + UseElementsStateOptions, + UseElementsState, +} from '~/types' +import { applyChanges, connectionExists, getEdgeId, isEdge, isGraphEdge, parseEdge } from '~/utils' +import { parseChildren } from '~/store' + +const addEdge = (edgeParams: Edge | Connection, edges: Edge[]) => { + if (!edgeParams.source || !edgeParams.target) { + console.warn("Can't create edge. An edge needs a source and a target.") + return false + } + + let edge + if (isEdge(edgeParams)) { + edge = { ...edgeParams } + } else { + edge = { + ...edgeParams, + id: getEdgeId(edgeParams), + } as Edge + } + edge = parseEdge(edge) + if (connectionExists(edge, edges)) return false + return edge +} + +const updateEdge = (edge: GraphEdge, newConnection: Connection, edges: GraphEdge[]) => { + if (!newConnection.source || !newConnection.target) { + console.warn("Can't create new edge. An edge needs a source and a target.") + return false + } + + const foundEdge = edges.find((e) => isGraphEdge(e) && e.id === edge.id) + + if (!foundEdge) { + console.warn(`The old edge with id=${edge.id} does not exist.`) + return false + } + + edge.id = getEdgeId(newConnection) + edge.source = newConnection.source + edge.target = newConnection.target + edge.sourceHandle = newConnection.sourceHandle + edge.targetHandle = newConnection.targetHandle + + return edge +} + +const applyNodeChanges = (changes: NodeChange[], nodes: GraphNode[]) => applyChanges(changes, nodes) +const applyEdgeChanges = (changes: EdgeChange[], edges: GraphEdge[]) => applyChanges(changes, edges) + +export const useEdgesState = ({ edges, applyDefault }: UseEdgesStateOptions = { applyDefault: true }): UseEdgesState => { + const { store } = useVueFlow() + const applyEdges = (changes: EdgeChange[]) => applyEdgeChanges(changes, store.edges) + if (edges && edges.length) store.setEdges(edges) + if (applyDefault) store.hooksOn.OnEdgesChange((e) => applyEdges(e)) + return { + edges: store.edges, + setEdges: store.setEdges, + addEdges: (params) => { + params.forEach((param) => { + const edge = addEdge(param, store.edges) + if (edge) { + const sourceNode = store.getNode(edge.source)! + const targetNode = store.getNode(edge.target)! + if (!sourceNode || typeof sourceNode === 'undefined') + console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`) + if (!targetNode || typeof targetNode === 'undefined') + console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`) + + store.edges.push({ + ...edge, + sourceNode, + targetNode, + }) + } + }) + return store.edges + }, + updateEdge: (oldEdge, newConnection) => updateEdge(oldEdge, newConnection, store.edges), + OnEdgesChange: store.hooksOn.OnEdgesChange, + } +} + +export const useNodesState = ({ nodes, applyDefault }: UseNodesStateOptions = { applyDefault: true }): UseNodesState => { + const { id, store } = useVueFlow() + const applyNodes = (changes: NodeChange[]) => applyNodeChanges(changes, store.nodes) + if (nodes && nodes.length) store.setNodes(nodes) + if (applyDefault) store.hooksOn.OnNodesChange((e) => applyNodes(e)) + + return { + nodes: store.nodes, + setNodes: store.setNodes, + addNodes: (nodes, extent) => { + const parsed = nodes.flatMap((node) => { + const children: GraphNode[] = [] + parseChildren(node, undefined, children, extent ?? store.nodeExtent, store.getNode) + return children + }) + store.nodes.push(...parsed) + return store.nodes + }, + OnNodesChange: store.hooksOn.OnNodesChange, + } +} + +export const useElementsState = ( + { edges, nodes, applyDefault }: UseElementsStateOptions = { applyDefault: true }, +): UseElementsState => { + const nodesState = useNodesState({ nodes, applyDefault }) + const edgesState = useEdgesState({ edges, applyDefault }) + + return { + ...nodesState, + ...edgesState, + } +} diff --git a/src/composables/useVueFlow.ts b/src/composables/useVueFlow.ts index d1aed5e1..8f5d2855 100644 --- a/src/composables/useVueFlow.ts +++ b/src/composables/useVueFlow.ts @@ -1,34 +1,28 @@ import { getCurrentInstance } from 'vue' -import { useEdgesState, useNodesState } from './useElementState' -import { EdgeChange, FlowOptions, GraphEdge, GraphNode, NodeChange, UseVueFlow } from '~/types' -import { applyChanges } from '~/utils' +import { FlowOptions, UseVueFlow } from '~/types' import { VueFlow } from '~/context' import { useStore } from '~/store' -const applyNodeChanges = (changes: NodeChange[], nodes: GraphNode[]) => applyChanges(changes, nodes) -const applyEdgeChanges = (changes: EdgeChange[], edges: GraphEdge[]) => applyChanges(changes, edges) - let id = 0 export default (options?: FlowOptions): UseVueFlow => { const currentInstance = getCurrentInstance() - let vueFlow: UseVueFlow | false | undefined = currentInstance ? inject(VueFlow, undefined) : false + let vueFlow: UseVueFlow | false | undefined = currentInstance + ? inject(VueFlow, undefined) ?? (currentInstance.vueFlow as UseVueFlow) + : false if (!vueFlow || (vueFlow && options?.id && options.id !== vueFlow.id)) { const name = options?.id ?? `vue-flow-${id++}` const store = reactive(useStore(name, options)) - const applyNodes = (changes: NodeChange[]) => applyNodeChanges(changes, store.nodes) - const applyEdges = (changes: EdgeChange[]) => applyEdgeChanges(changes, store.edges) vueFlow = { id: name, store, ...toRefs(store), - useNodesState: (nodes, applyDefault = true) => useNodesState(store, applyNodes)({ nodes, applyDefault }), - useEdgesState: (edges, applyDefault = true) => useEdgesState(store, applyEdges)({ edges, applyDefault }), - applyNodeChanges: applyNodes, - applyEdgeChanges: applyEdges, ...store.hooksOn, } } - if (currentInstance) provide(VueFlow, vueFlow) + if (currentInstance) { + provide(VueFlow, vueFlow) + currentInstance.vueFlow = vueFlow + } return vueFlow } diff --git a/src/index.ts b/src/index.ts index b5748a96..992a1fe9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ export { default as useZoomPanHelper } from './composables/useZoomPanHelper' export { default as useVueFlow } from './composables/useVueFlow' export { default as useHandle } from './composables/useHandle' export { default as useWindow } from './composables/useWindow' +export * from './composables/useElementsState' export * from './additional-components' export * from './types' diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts index a191098d..7ed080c8 100644 --- a/src/shims-vue.d.ts +++ b/src/shims-vue.d.ts @@ -1,6 +1,14 @@ +import { UseVueFlow } from '~/types' + declare module '*.vue' { import { DefineComponent } from 'vue' // eslint-disable-next-line @typescript-eslint/ban-types const component: DefineComponent<{}, {}, any> export default component } + +declare module '@vue/runtime-core' { + interface ComponentInternalInstance { + vueFlow: UseVueFlow + } +} diff --git a/src/types/flow.ts b/src/types/flow.ts index d2a1d0df..1d89da7d 100644 --- a/src/types/flow.ts +++ b/src/types/flow.ts @@ -126,31 +126,32 @@ export type FlowOptions = FlowProps type UseStateOptions = { applyDefault?: boolean } -export type UseNodesState = { - nodes: GraphNode[] - setNodes: FlowActions['setNodes'] - addNodes: (nodes: Node[], extent?: CoordinateExtent) => GraphNode[] - OnNodesChange: FlowHooksOn['OnNodesChange'] -} export interface UseNodesStateOptions extends UseStateOptions { nodes?: Node[] } export interface UseEdgesStateOptions extends UseStateOptions { edges?: Edge[] } -export type UseEdgeState = { +export interface UseElementsStateOptions extends UseStateOptions { + edges?: Edge[] + nodes?: Node[] +} +export type UseNodesState = { + nodes: GraphNode[] + setNodes: FlowActions['setNodes'] + addNodes: (nodes: Node[], extent?: CoordinateExtent) => GraphNode[] + OnNodesChange: FlowHooksOn['OnNodesChange'] +} +export type UseEdgesState = { edges: GraphEdge[] setEdges: FlowActions['setEdges'] addEdges: (params: (Edge | Connection)[]) => GraphEdge[] updateEdge: (oldEdge: GraphEdge, newConnection: Connection) => GraphEdge | false OnEdgesChange: FlowHooksOn['OnEdgesChange'] } +export type UseElementsState = UseNodesState & UseEdgesState export type UseVueFlow = { id: string store: FlowStore - useNodesState: (nodes?: Node[], applyDefault?: boolean) => UseNodesState - useEdgesState: (edges?: Edge[], applyDefault?: boolean) => UseEdgeState - applyNodeChanges: (changes: NodeChange[]) => void - applyEdgeChanges: (changes: EdgeChange[]) => void } & FlowHooksOn & ToRefs