diff --git a/package/src/additional-components/Background/Background.vue b/package/src/additional-components/Background/Background.vue index 029b2a47..8d909bc5 100644 --- a/package/src/additional-components/Background/Background.vue +++ b/package/src/additional-components/Background/Background.vue @@ -18,13 +18,13 @@ const defaultColors: Record = { [BackgroundVariant.Lines]: '#eee', } -const { store } = useVueFlow() +const { viewport } = $(useVueFlow()) -const background = computed(() => { - const scaledGap = props.gap && props.gap * store.viewport.zoom - const xOffset = scaledGap && store.viewport.x % scaledGap - const yOffset = scaledGap && store.viewport.y % scaledGap - const size = props.size || 0.4 * store.viewport.zoom +const background = $computed(() => { + const scaledGap = props.gap && props.gap * viewport.zoom + const xOffset = scaledGap && viewport.x % scaledGap + const yOffset = scaledGap && viewport.y % scaledGap + const size = props.size || 0.4 * viewport.zoom return { scaledGap, @@ -36,14 +36,13 @@ const background = computed(() => { // when there are multiple flows on a page we need to make sure that every background gets its own pattern. const patternId = `pattern-${Math.floor(Math.random() * 100000)}` + const patternColor = computed(() => props.patternColor ? props.patternColor : defaultColors[props.variant || BackgroundVariant.Dots], ) + const d = computed( - () => - `M${background.value.scaledGap / 2} 0 V${background.value.scaledGap} M0 ${background.value.scaledGap / 2} H${ - background.value.scaledGap - }`, + () => `M${background.scaledGap / 2} 0 V${background.scaledGap} M0 ${background.scaledGap / 2} H${background.scaledGap}`, ) diff --git a/package/src/additional-components/MiniMap/MiniMap.vue b/package/src/additional-components/MiniMap/MiniMap.vue index fd901ffb..b94d67cb 100644 --- a/package/src/additional-components/MiniMap/MiniMap.vue +++ b/package/src/additional-components/MiniMap/MiniMap.vue @@ -15,16 +15,20 @@ const props = withDefaults(defineProps(), { }) const attrs: Record = useAttrs() + const window = useWindow() const defaultWidth = 200 const defaultHeight = 150 -const { store } = useVueFlow() +const { viewport, dimensions, hooks, getNodes } = $(useVueFlow()) const elementWidth = attrs.style?.width ?? defaultWidth + const elementHeight = attrs.style?.height ?? defaultHeight + const nodeColorFunc: MiniMapNodeFunc = props.nodeColor instanceof Function ? props.nodeColor : () => props.nodeColor as string + const nodeStrokeColorFunc: MiniMapNodeFunc = props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor as string @@ -33,54 +37,58 @@ const nodeClassNameFunc = const shapeRendering: ShapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision' -const bb = computed(() => { - return getRectOfNodes(store.getNodes) +const bb = $computed(() => { + return getRectOfNodes(getNodes) }) -const viewBB = computed(() => ({ - x: -store.viewport.x / store.viewport.zoom, - y: -store.viewport.y / store.viewport.zoom, - width: store.dimensions.width / store.viewport.zoom, - height: store.dimensions.height / store.viewport.zoom, +const viewBB = $computed(() => ({ + x: -viewport.x / viewport.zoom, + y: -viewport.y / viewport.zoom, + width: dimensions.width / viewport.zoom, + height: dimensions.height / viewport.zoom, })) -const viewBox = controlledComputed(viewBB, () => { - const boundingRect = store.getNodes && store.getNodes.length ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value - const scaledWidth = boundingRect.width / elementWidth - const scaledHeight = boundingRect.height / elementHeight - const viewScale = Math.max(scaledWidth, scaledHeight) - const viewWidth = viewScale * elementWidth - const viewHeight = viewScale * elementHeight - const offset = 5 * viewScale - return { - offset, - x: boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset, - y: boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset, - width: viewWidth + offset * 2, - height: viewHeight + offset * 2, +const viewBox = $( + controlledComputed($$(viewBB), () => { + const boundingRect = getNodes && getNodes.length ? getBoundsofRects(bb, viewBB) : viewBB + const scaledWidth = boundingRect.width / elementWidth + const scaledHeight = boundingRect.height / elementHeight + const viewScale = Math.max(scaledWidth, scaledHeight) + const viewWidth = viewScale * elementWidth + const viewHeight = viewScale * elementHeight + const offset = 5 * viewScale + return { + offset, + x: boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset, + y: boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset, + width: viewWidth + offset * 2, + height: viewHeight + offset * 2, + } + }), +) + +const d = controlledComputed($$(viewBox), () => { + if (viewBox.x && viewBox.y) { + return ` + M${viewBox.x - viewBox.offset},${viewBox.y - viewBox.offset} + h${viewBox.width + viewBox.offset * 2} + v${viewBox.height + viewBox.offset * 2} + h${-viewBox.width - viewBox.offset * 2}z + M${viewBB.x},${viewBB.y} + h${viewBB.width} + v${viewBB.height} + h${-viewBB.width}z` + } else { + return '' } }) -const d = controlledComputed(viewBox, () => { - if (viewBox.value.x && viewBox.value.y) - return ` - M${viewBox.value.x - viewBox.value.offset},${viewBox.value.y - viewBox.value.offset} - h${viewBox.value.width + viewBox.value.offset * 2} - v${viewBox.value.height + viewBox.value.offset * 2} - h${-viewBox.value.width - viewBox.value.offset * 2}z - M${viewBB.value.x},${viewBB.value.y} - h${viewBB.value.width} - v${viewBB.value.height} - h${-viewBB.value.width}z` - else return '' -}) - const onNodeClick = (event: MouseEvent, node: GraphNode) => { - store.hooks.miniMapNodeClick.trigger({ event, node }) + hooks.miniMapNodeClick.trigger({ event, node }) } const onNodeDblClick = (event: MouseEvent, node: GraphNode) => { - store.hooks.miniMapNodeDoubleClick.trigger({ event, node }) + hooks.miniMapNodeDoubleClick.trigger({ event, node }) } diff --git a/package/src/components/UserSelection/UserSelection.vue b/package/src/components/UserSelection/UserSelection.vue index 50e678b9..58434de4 100644 --- a/package/src/components/UserSelection/UserSelection.vue +++ b/package/src/components/UserSelection/UserSelection.vue @@ -5,11 +5,11 @@ import { getConnectedEdges, getNodesInside } from '../../utils' import SelectionRect from './SelectionRect.vue' import { getMousePosition } from './utils' -const { store } = useVueFlow() +const { userSelectionActive, setState, getNodes, getEdges, viewport, addSelectedEdges, addSelectedNodes } = $(useVueFlow()) const el = templateRef('user-selection', null) -const prevNodes = ref(0) -const prevEdges = ref(0) +let prevNodes = $ref(0) +let prevEdges = $ref(0) const initialRect = () => ({ width: 0, height: 0, @@ -20,20 +20,23 @@ const initialRect = () => ({ draw: false, }) -const rect = ref(initialRect()) +let rect = $ref(initialRect()) const reset = () => { - rect.value = initialRect() - store.userSelectionActive = false - prevNodes.value = 0 - prevEdges.value = 0 + rect = initialRect() + prevNodes = 0 + prevEdges = 0 + + setState({ + userSelectionActive: false, + }) } const onMouseDown = (event: MouseEvent) => { const mousePos = getMousePosition(event) if (!mousePos) return - rect.value = { + rect = { width: 0, height: 0, startX: mousePos.x, @@ -43,47 +46,55 @@ const onMouseDown = (event: MouseEvent) => { draw: true, } - store.userSelectionActive = true - store.nodesSelectionActive = false + setState({ + userSelectionActive: true, + nodesSelectionActive: false, + }) } const onMouseMove = (event: MouseEvent) => { - if (!store.userSelectionActive || !rect.value.draw) return + if (!userSelectionActive || !rect.draw) return const mousePos = getMousePosition(event) if (!mousePos) return - const startX = rect.value.startX - const startY = rect.value.startY + const startX = rect.startX + const startY = rect.startY const nextUserSelectRect: Rect = { - ...rect.value, - x: mousePos.x < startX ? mousePos.x : rect.value.x, - y: mousePos.y < startY ? mousePos.y : rect.value.y, + ...rect, + x: mousePos.x < startX ? mousePos.x : rect.x, + y: mousePos.y < startY ? mousePos.y : rect.y, width: Math.abs(mousePos.x - startX), height: Math.abs(mousePos.y - startY), } - const selectedNodes = getNodesInside(store.getNodes, rect.value, store.viewport) - const selectedEdges = getConnectedEdges(selectedNodes, store.getEdges) + const selectedNodes = getNodesInside(getNodes, rect, viewport) + const selectedEdges = getConnectedEdges(selectedNodes, getEdges) - rect.value = nextUserSelectRect + rect = nextUserSelectRect - store.addSelectedNodes(selectedNodes) - store.addSelectedEdges(selectedEdges) + addSelectedNodes(selectedNodes) + addSelectedEdges(selectedEdges) - prevNodes.value = selectedNodes.length - prevEdges.value = selectedEdges.length + prevNodes = selectedNodes.length + prevEdges = selectedEdges.length } const onMouseUp = () => { - store.nodesSelectionActive = prevNodes.value > 0 - rect.value = initialRect() - store.userSelectionActive = false + rect = initialRect() + + setState({ + nodesSelectionActive: prevNodes > 0, + userSelectionActive: false, + }) } const onMouseLeave = () => { - store.nodesSelectionActive = false + setState({ + nodesSelectionActive: prevNodes > 0, + }) + reset() } diff --git a/package/src/composables/useHandle.ts b/package/src/composables/useHandle.ts index 42695bf5..84fc932f 100644 --- a/package/src/composables/useHandle.ts +++ b/package/src/composables/useHandle.ts @@ -1,6 +1,6 @@ import useVueFlow from './useVueFlow' import { getHostForElement } from '~/utils' -import { Connection, ConnectionMode, Getters, GraphEdge, HandleType, Store, ValidConnectionFunc } from '~/types' +import { Connection, ConnectionMode, Getters, GraphEdge, HandleType, ValidConnectionFunc } from '~/types' type Result = { elementBelow: Element | null @@ -71,7 +71,19 @@ const resetRecentHandle = (hoveredHandle: Element): void => { hoveredHandle?.classList.remove('vue-flow__handle-connecting') } -export default (store: Store = useVueFlow().store) => { +export default () => { + const { + edges, + connectOnClick, + nodesConnectable, + connectionStartHandle, + connectionPosition, + connectionMode, + hooks, + setState, + getNode, + } = $(useVueFlow()) + let recentHoveredHandle: Element const onMouseDown = ( @@ -85,16 +97,20 @@ export default (store: Store = useVueFlow().store) => { onEdgeUpdateEnd?: () => void, ) => { const flowNode = (event.target as Element).closest('.vue-flow') + const doc = getHostForElement(event.target as HTMLElement) if (!doc) return let validConnectFunc: ValidConnectionFunc = isValidConnection ?? (() => true) - const node = store.getNode(nodeId) - if (node && (typeof node.connectable === 'undefined' ? store.nodesConnectable : node.connectable) === false) return + const node = getNode(nodeId) + + if (node && (typeof node.connectable === 'undefined' ? nodesConnectable : node.connectable) === false) return + if (!isValidConnection) { if (node) validConnectFunc = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) ?? (() => true) } + const elementBelow = doc.elementFromPoint(event.clientX, event.clientY) const elementBelowIsTarget = elementBelow?.classList.contains('target') const elementBelowIsSource = elementBelow?.classList.contains('source') @@ -102,9 +118,10 @@ export default (store: Store = useVueFlow().store) => { if (!flowNode || (!elementBelowIsTarget && !elementBelowIsSource && !elementEdgeUpdaterType)) return const handleType = elementEdgeUpdaterType ?? (elementBelowIsTarget ? 'target' : 'source') + const containerBounds = flowNode.getBoundingClientRect() - store.setState({ + setState({ connectionPosition: { x: event.clientX - containerBounds.left, y: event.clientY - containerBounds.top, @@ -114,22 +131,22 @@ export default (store: Store = useVueFlow().store) => { connectionHandleType: handleType, }) - store.hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) + hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) function onMouseMove(event: MouseEvent) { - store.connectionPosition.x = event.clientX - containerBounds.left - store.connectionPosition.y = event.clientY - containerBounds.top + connectionPosition.x = event.clientX - containerBounds.left + connectionPosition.y = event.clientY - containerBounds.top const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid( event, - store.connectionMode, + connectionMode, isTarget, nodeId, handleId, validConnectFunc, doc, - store.edges, - store.getNode, + edges, + getNode, ) if (!isHoveringHandle) return resetRecentHandle(recentHoveredHandle) @@ -146,30 +163,32 @@ export default (store: Store = useVueFlow().store) => { function onMouseUp(event: MouseEvent) { const { connection, isValid } = checkElementBelowIsValid( event, - store.connectionMode, + connectionMode, isTarget, nodeId, handleId, validConnectFunc, doc, - store.edges, - store.getNode, + edges, + getNode, ) - store.hooks.connectStop.trigger(event) + hooks.connectStop.trigger(event) + const isOwnHandle = connection.source === connection.target if (isValid && !isOwnHandle) { - if (!onEdgeUpdate) store.hooks.connect.trigger(connection) + if (!onEdgeUpdate) hooks.connect.trigger(connection) else onEdgeUpdate(connection) } - store.hooks.connectEnd.trigger(event) + hooks.connectEnd.trigger(event) if (elementEdgeUpdaterType) onEdgeUpdateEnd?.() resetRecentHandle(recentHoveredHandle) - store.setState({ + + setState({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null, @@ -191,40 +210,44 @@ export default (store: Store = useVueFlow().store) => { handleType: HandleType, isValidConnection?: ValidConnectionFunc, ) => { - if (!store.connectOnClick) return - if (!store.connectionStartHandle) { - store.hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) - store.setState({ connectionStartHandle: { nodeId, type: handleType, handleId } }) + if (!connectOnClick) return + if (!connectionStartHandle) { + hooks.connectStart.trigger({ event, nodeId, handleId, handleType }) + setState({ connectionStartHandle: { nodeId, type: handleType, handleId } }) } else { let validConnectFunc: ValidConnectionFunc = isValidConnection ?? (() => true) - const node = store.getNode(nodeId) - if (node && (typeof node.connectable === 'undefined' ? store.nodesConnectable : node.connectable) === false) return + const node = getNode(nodeId) + + if (node && (typeof node.connectable === 'undefined' ? nodesConnectable : node.connectable) === false) return + if (!isValidConnection) { if (node) validConnectFunc = (handleType !== 'target' ? node.isValidTargetPos : node.isValidSourcePos) ?? (() => true) } + const doc = getHostForElement(event.target as HTMLElement) + const { connection, isValid } = checkElementBelowIsValid( event as MouseEvent, - store.connectionMode, - store.connectionStartHandle.type === 'target', - store.connectionStartHandle.nodeId, - store.connectionStartHandle.handleId || null, + connectionMode, + connectionStartHandle.type === 'target', + connectionStartHandle.nodeId, + connectionStartHandle.handleId || null, validConnectFunc, doc, - store.edges, - store.getNode, + edges, + getNode, ) const isOwnHandle = connection.source === connection.target - store.hooks.connectStop.trigger(event) + hooks.connectStop.trigger(event) - if (isValid && !isOwnHandle) store.hooks.connect.trigger(connection) + if (isValid && !isOwnHandle) hooks.connect.trigger(connection) - store.hooks.connectEnd.trigger(event) + hooks.connectEnd.trigger(event) - store.setState({ connectionStartHandle: null }) + setState({ connectionStartHandle: null }) } } diff --git a/package/src/composables/useVueFlow.ts b/package/src/composables/useVueFlow.ts index 93bc53ac..598803e6 100644 --- a/package/src/composables/useVueFlow.ts +++ b/package/src/composables/useVueFlow.ts @@ -1,14 +1,17 @@ import { EffectScope } from 'vue' import { MaybeRef } from '@vueuse/core' -import { FlowHooksOn, FlowOptions, FlowProps, State, UseVueFlow } from '~/types' +import { FlowHooksOn, FlowOptions, FlowProps, State, VueFlowStore } from '~/types' import { VueFlow } from '~/context' import useState from '~/store/state' import useGetters from '~/store/getters' import useActions from '~/store/actions' +/** + * Stores all currently created store instances + */ export class Storage { public currentId = 0 - public flows = new Map() + public flows = new Map() static instance: Storage public static getInstance(): Storage { @@ -19,7 +22,7 @@ export class Storage { return Storage.instance } - public set(id: string, flow: UseVueFlow) { + public set(id: string, flow: VueFlowStore) { return this.flows.set(id, flow) } @@ -31,16 +34,21 @@ export class Storage { return this.flows.delete(id) } - public create(id: string, preloadedState?: FlowOptions): UseVueFlow { + public create(id: string, preloadedState?: FlowOptions): VueFlowStore { const state: State = useState(preloadedState) + const reactiveState = reactive(state) + const getters = useGetters(reactiveState) + const actions = useActions(reactiveState, getters) + const hooksOn: FlowHooksOn = {} Object.entries(reactiveState.hooks).forEach(([n, h]) => { const name = `on${n.charAt(0).toUpperCase() + n.slice(1)}` as keyof FlowHooksOn hooksOn[name] = h.on as any }) + actions.setState(reactiveState) if (preloadedState) { if (preloadedState.modelValue) actions.setElements(preloadedState.modelValue) @@ -48,20 +56,12 @@ export class Storage { if (preloadedState.edges) actions.setEdges(preloadedState.edges) } - const store = reactive({ - ...hooksOn, - ...toRefs(reactiveState), - ...getters, - ...actions, - }) - - const flow: UseVueFlow = { + const flow: VueFlowStore = { ...hooksOn, ...getters, ...actions, ...toRefs(reactiveState), id, - store, } this.set(id, flow) @@ -74,13 +74,15 @@ export class Storage { } } -type Injection = UseVueFlow | null | undefined +type Injection = VueFlowStore | null | undefined type Scope = (EffectScope & { vueFlowId: string }) | undefined type Options = { [key in keyof FlowProps]: MaybeRef } -export default (options?: Options): UseVueFlow => { +export default (options?: Options): VueFlowStore => { const reactiveOptions = options ? reactive(options) : undefined + const storage = Storage.getInstance() + const scope = getCurrentScope() as Scope const id = reactiveOptions?.id @@ -88,15 +90,28 @@ export default (options?: Options): UseVueFlow => { let vueFlow: Injection + /** + * check if we can get a store instance through injections + * this should be the regular way after initialization + */ if (scope) { const injection = inject(VueFlow, null) if (typeof injection !== 'undefined' && injection !== null) vueFlow = injection } + /** + * check if we can get a store instance through storage + * this requires options id or an id on the current scope + */ if (!vueFlow) { if (vueFlowId) vueFlow = storage.get(vueFlowId) } + /** + * If we cannot find any store instance in the previous steps + * _or_ if the store instance we found does not match up with provided ids + * create a new store instance and register it in storage + */ if (!vueFlow || (vueFlow && id && id !== vueFlow.id)) { const name = id ?? storage.getId() @@ -105,18 +120,32 @@ export default (options?: Options): UseVueFlow => { if (scope) { scope.vueFlowId = name + // dispose of state values and storage entry onScopeDispose(() => { - vueFlow!.$reset() - storage.remove(vueFlow!.id) + vueFlow?.$reset() + storage.remove(name) vueFlow = null }) + + if (reactiveOptions) { + scope.run(() => { + watch(reactiveOptions, (opts) => { + vueFlow?.setState(opts) + }) + }) + } } } else { + // if composable was called with additional options after initialization, overwrite state with the options values if (reactiveOptions) vueFlow.setState(reactiveOptions) } + /** + * Vue flow wasn't able to find any store instance - we can't proceed + */ if (!vueFlow) throw new Error('[vueflow]: store instance not found.') + // always provide a fresh instance into context on call if (scope) { provide(VueFlow, vueFlow) } diff --git a/package/src/composables/useZoomPanHelper.ts b/package/src/composables/useZoomPanHelper.ts index bd328a07..14a6e38f 100644 --- a/package/src/composables/useZoomPanHelper.ts +++ b/package/src/composables/useZoomPanHelper.ts @@ -2,22 +2,22 @@ import { zoomIdentity } from 'd3-zoom' import useVueFlow from './useVueFlow' import useWindow from './useWindow' import { getRectOfNodes, pointToRendererPoint, getTransformForBounds, clampPosition } from '~/utils' -import { GraphNode, Store, ViewportFuncs, D3Selection } from '~/types' +import { GraphNode, ViewportFuncs, D3Selection, Getters, Dimensions } from '~/types' const DEFAULT_PADDING = 0.1 const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms) -const untilDimensions = async (store: Store) => { +const untilDimensions = async (dimensions: Dimensions, getNodes: Getters['getNodes']) => { // if ssr we can't wait for dimensions, they'll never really exist const window = useWindow() if ('screen' in window) { // wait until viewport dimensions has been established - await until(store.dimensions).toMatch(({ height, width }) => !isNaN(width) && width > 0 && !isNaN(height) && height > 0) + await until(dimensions).toMatch(({ height, width }) => !isNaN(width) && width > 0 && !isNaN(height) && height > 0) // if initial nodes are present, wait until the node dimensions have been established - if (store.getNodes.length > 0) { - await until(store.getNodes).toMatch( + if (getNodes.length > 0) { + await until(getNodes).toMatch( (nodes) => !!nodes.filter(({ dimensions: { width, height } }) => !isNaN(width) && width > 0 && !isNaN(height) && height > 0) .length, @@ -28,23 +28,27 @@ const untilDimensions = async (store: Store) => { return true } -export default (store: Store = useVueFlow().store): ViewportFuncs => { - const hasDimensions = ref(false) - store.hooks.paneReady.on(() => (hasDimensions.value = true)) +export default (): ViewportFuncs => { + const { hooks, d3Zoom, d3Selection, dimensions, translateExtent, minZoom, maxZoom, viewport, snapToGrid, snapGrid, getNodes } = + $(useVueFlow()) + + let hasDimensions = $ref(false) + + hooks.paneReady.on(() => (hasDimensions = true)) const zoomTo: ViewportFuncs['zoomTo'] = async (zoomLevel, options) => { - if (!hasDimensions.value) await untilDimensions(store) + if (!hasDimensions) await untilDimensions(dimensions, getNodes) - if (store.d3Selection && store.d3Zoom) { - store.d3Zoom.scaleTo(transition(store.d3Selection, options?.duration), zoomLevel) + if (d3Selection && d3Zoom) { + d3Zoom.scaleTo(transition(d3Selection, options?.duration), zoomLevel) } } const zoom = async (scale: number, duration?: number) => { - if (!hasDimensions.value) await untilDimensions(store) + if (!hasDimensions) await untilDimensions(dimensions, getNodes) - if (store.d3Selection && store.d3Zoom) { - store.d3Zoom.scaleBy(transition(store.d3Selection, duration), scale) + if (d3Selection && d3Zoom) { + d3Zoom.scaleBy(transition(d3Selection, duration), scale) } } @@ -58,12 +62,12 @@ export default (store: Store = useVueFlow().store): ViewportFuncs => { const transformViewport = (x: number, y: number, zoom: number, duration?: number) => { // enforce translate extent - const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, store.translateExtent) + const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, translateExtent) const nextTransform = zoomIdentity.translate(-clampedX, -clampedY).scale(zoom) - if (store.d3Selection && store.d3Zoom) { - store.d3Zoom.transform(transition(store.d3Selection, duration), nextTransform) + if (d3Selection && d3Zoom) { + d3Zoom.transform(transition(d3Selection, duration), nextTransform) } } @@ -72,13 +76,13 @@ export default (store: Store = useVueFlow().store): ViewportFuncs => { zoomOut, zoomTo, setTransform: async (transform, options) => { - if (!hasDimensions.value) await untilDimensions(store) + if (!hasDimensions) await untilDimensions(dimensions, getNodes) transformViewport(transform.x, transform.y, transform.zoom, options?.duration) }, getTransform: () => ({ - x: store.viewport.x, - y: store.viewport.y, - zoom: store.viewport.zoom, + x: viewport.x, + y: viewport.y, + zoom: viewport.zoom, }), fitView: async ( options = { @@ -87,27 +91,27 @@ export default (store: Store = useVueFlow().store): ViewportFuncs => { duration: 0, }, ) => { - if (!hasDimensions.value) await untilDimensions(store) + if (!hasDimensions) await untilDimensions(dimensions, getNodes) - if (!store.getNodes.length) return + if (!getNodes.length) return let nodes: GraphNode[] = [] if (options.nodes) { - nodes = store.nodes.filter((n) => options.nodes?.includes(n.id)) + nodes = nodes.filter((n) => options.nodes?.includes(n.id)) } if (!nodes || !nodes.length) { - nodes = options.includeHiddenNodes ? store.nodes : store.getNodes + nodes = options.includeHiddenNodes ? nodes : getNodes } const bounds = getRectOfNodes(nodes) const { x, y, zoom } = getTransformForBounds( bounds, - store.dimensions.width, - store.dimensions.height, - options.minZoom ?? store.minZoom, - options.maxZoom ?? store.maxZoom, + dimensions.width, + dimensions.height, + options.minZoom ?? minZoom, + options.maxZoom ?? maxZoom, options.padding ?? DEFAULT_PADDING, options.offset, ) @@ -115,28 +119,21 @@ export default (store: Store = useVueFlow().store): ViewportFuncs => { transformViewport(x, y, zoom, options?.duration) }, setCenter: async (x, y, options) => { - if (!hasDimensions.value) await untilDimensions(store) + if (!hasDimensions) await untilDimensions(dimensions, getNodes) - const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : store.maxZoom - const centerX = store.dimensions.width / 2 - x * nextZoom - const centerY = store.dimensions.height / 2 - y * nextZoom + const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom + const centerX = dimensions.width / 2 - x * nextZoom + const centerY = dimensions.height / 2 - y * nextZoom transformViewport(centerX, centerY, nextZoom, options?.duration) }, fitBounds: async (bounds, options = { padding: DEFAULT_PADDING }) => { - if (!hasDimensions.value) await untilDimensions(store) + if (!hasDimensions) await untilDimensions(dimensions, getNodes) - const { x, y, zoom } = getTransformForBounds( - bounds, - store.dimensions.width, - store.dimensions.height, - store.minZoom, - store.maxZoom, - options.padding, - ) + const { x, y, zoom } = getTransformForBounds(bounds, dimensions.width, dimensions.height, minZoom, maxZoom, options.padding) transformViewport(x, y, zoom, options?.duration) }, - project: (position) => pointToRendererPoint(position, store.viewport, store.snapToGrid, store.snapGrid), + project: (position) => pointToRendererPoint(position, viewport, snapToGrid, snapGrid), } } diff --git a/package/src/container/EdgeRenderer/EdgeRenderer.vue b/package/src/container/EdgeRenderer/EdgeRenderer.vue index 26974e89..832aefc6 100644 --- a/package/src/container/EdgeRenderer/EdgeRenderer.vue +++ b/package/src/container/EdgeRenderer/EdgeRenderer.vue @@ -5,30 +5,43 @@ import { useVueFlow } from '../../composables' import { groupEdgesByZLevel } from '../../utils' import MarkerDefinitions from './MarkerDefinitions.vue' -const { store } = useVueFlow() +const { + connectionNodeId, + nodesConnectable, + connectionHandleType, + defaultMarkerColor, + edgesUpdatable, + elementsSelectable, + getNode, + getEdges, +} = $(useVueFlow()) -const sourceNode = controlledComputed( - () => store.connectionNodeId, - () => { - if (store.connectionNodeId) return store.getNode(store.connectionNodeId) - return false - }, +const sourceNode = $( + controlledComputed( + () => connectionNodeId, + () => { + if (connectionNodeId) return getNode(connectionNodeId) + return false + }, + ), ) -const connectionLineVisible = controlledComputed( - () => store.connectionNodeId, - () => - !!( - sourceNode.value && - (typeof sourceNode.value.connectable === 'undefined' ? store.nodesConnectable : sourceNode.value.connectable) && - store.connectionNodeId && - store.connectionHandleType - ), +const connectionLineVisible = $( + controlledComputed( + () => connectionNodeId, + () => + !!( + sourceNode && + (typeof sourceNode.connectable === 'undefined' ? nodesConnectable : sourceNode.connectable) && + connectionNodeId && + connectionHandleType + ), + ), ) -const getNode = (node: string) => store.getNode(node)! +const getNodeWrapped = (node: string) => getNode(node)! -const groups = computed(() => groupEdgesByZLevel(store.getEdges, getNode)) +const groups = computed(() => groupEdgesByZLevel(getEdges, getNodeWrapped)) diff --git a/package/src/container/SelectionPane/SelectionPane.vue b/package/src/container/SelectionPane/SelectionPane.vue index 9f6d70f2..3ad2b3f4 100644 --- a/package/src/container/SelectionPane/SelectionPane.vue +++ b/package/src/container/SelectionPane/SelectionPane.vue @@ -5,23 +5,39 @@ import { getConnectedEdges } from '../../utils' import NodesSelection from '../../components/NodesSelection/NodesSelection.vue' import UserSelection from '../../components/UserSelection/UserSelection.vue' -const { id, store, deleteKeyCode, selectionKeyCode, multiSelectionKeyCode } = useVueFlow() +const { + id, + edges, + deleteKeyCode, + selectionKeyCode, + multiSelectionKeyCode, + hooks, + nodesSelectionActive, + userSelectionActive, + elementsSelectable, + resetSelectedElements, + setState, + getSelectedEdges, + getSelectedNodes, +} = $(useVueFlow()) const onClick = (event: MouseEvent) => { - store.hooks.paneClick.trigger(event) - store.nodesSelectionActive = false - store.resetSelectedElements() + hooks.paneClick.trigger(event) + setState({ + nodesSelectionActive: false, + }) + resetSelectedElements() } -const onContextMenu = (event: MouseEvent) => store.hooks.paneContextMenu.trigger(event) +const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event) -const onWheel = (event: WheelEvent) => store.hooks.paneScroll.trigger(event) +const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event) -useKeyPress(deleteKeyCode, (keyPressed) => { - const selectedNodes = store.getSelectedNodes - const selectedEdges = store.getSelectedEdges +useKeyPress($$(deleteKeyCode), (keyPressed) => { + const selectedNodes = getSelectedNodes + const selectedEdges = getSelectedEdges if (keyPressed && (selectedNodes || selectedEdges)) { - const connectedEdges = (selectedNodes && getConnectedEdges(selectedNodes, store.edges)) ?? [] + const connectedEdges = (selectedNodes && getConnectedEdges(selectedNodes, edges)) ?? [] const nodeChanges: NodeChange[] = selectedNodes.map((n) => ({ id: n.id, type: 'remove' })) const edgeChanges: EdgeChange[] = [...selectedEdges, ...connectedEdges].map((e) => ({ @@ -29,21 +45,28 @@ useKeyPress(deleteKeyCode, (keyPressed) => { type: 'remove', })) - store.hooks.nodesChange.trigger(nodeChanges) - store.hooks.edgesChange.trigger(edgeChanges) - store.nodesSelectionActive = false + hooks.nodesChange.trigger(nodeChanges) + hooks.edgesChange.trigger(edgeChanges) - store.resetSelectedElements() + setState({ + nodesSelectionActive: false, + }) + + resetSelectedElements() } }) -useKeyPress(multiSelectionKeyCode, (keyPressed) => { - store.multiSelectionActive = keyPressed +useKeyPress($$(multiSelectionKeyCode), (keyPressed) => { + setState({ + multiSelectionActive: keyPressed, + }) }) -const selectionKeyPressed = useKeyPress(selectionKeyCode, (keyPressed) => { - if (store.userSelectionActive && keyPressed) return - store.userSelectionActive = keyPressed && store.elementsSelectable +const selectionKeyPressed = useKeyPress($$(selectionKeyCode), (keyPressed) => { + if (userSelectionActive && keyPressed) return + setState({ + userSelectionActive: keyPressed && elementsSelectable, + }) })