diff --git a/packages/core/src/composables/useOnInitHandler.ts b/packages/core/src/composables/useOnInitHandler.ts new file mode 100644 index 00000000..67a1af28 --- /dev/null +++ b/packages/core/src/composables/useOnInitHandler.ts @@ -0,0 +1,14 @@ +import { watch } from 'vue' +import { useVueFlow } from './useVueFlow' + +export function useOnInitHandler() { + const vfInstance = useVueFlow() + + watch(vfInstance.viewportInitialized, (isInitialized) => { + if (isInitialized) { + setTimeout(() => { + vfInstance.emits.paneReady(vfInstance) + }, 1) + } + }) +} diff --git a/packages/core/src/composables/useViewport.ts b/packages/core/src/composables/useViewport.ts index a3461e31..66e8ff39 100644 --- a/packages/core/src/composables/useViewport.ts +++ b/packages/core/src/composables/useViewport.ts @@ -4,7 +4,7 @@ import type { ComputedGetters, D3Selection, GraphNode, Project, State, ViewportF import { clampPosition, getRectOfNodes, getTransformForBounds, pointToRendererPoint, rendererPointToPoint, warn } from '../utils' interface ExtendedViewport extends ViewportFunctions { - initialized: boolean + viewportInitialized: boolean screenToFlowCoordinate: Project flowToScreenCoordinate: Project } @@ -31,7 +31,7 @@ const initialViewportHelper: ExtendedViewport = { setTransform: noop, getViewport: () => ({ x: 0, y: 0, zoom: 1 }), getTransform: () => ({ x: 0, y: 0, zoom: 1 }), - initialized: false, + viewportInitialized: false, } export function useViewport(state: State, getters: ComputedGetters) { @@ -73,136 +73,138 @@ export function useViewport(state: State, getters: ComputedGetters) { } return computed(() => { - if (state.d3Zoom && state.d3Selection && state.dimensions.width && state.dimensions.height) { - return { - initialized: true, - // todo: allow passing scale as option - zoomIn: (options) => { - return zoom(1.2, options?.duration) - }, - zoomOut: (options) => { - return zoom(1 / 1.2, options?.duration) - }, - zoomTo: (zoomLevel, options) => { - return new Promise((resolve) => { - if (state.d3Selection && state.d3Zoom) { - state.d3Zoom.scaleTo( - transition(state.d3Selection, options?.duration, () => { - resolve(true) - }), - zoomLevel, - ) - } else { - resolve(false) - } - }) - }, - setViewport: (transform, options) => { - return transformViewport(transform.x, transform.y, transform.zoom, options?.duration) - }, - setTransform: (transform, options) => { - return transformViewport(transform.x, transform.y, transform.zoom, options?.duration) - }, - getViewport: () => ({ + const isInitialized = state.d3Zoom && state.d3Selection && state.dimensions.width && state.dimensions.height + + if (!isInitialized) { + return initialViewportHelper + } + + return { + viewportInitialized: true, + // todo: allow passing scale as option + zoomIn: (options) => { + return zoom(1.2, options?.duration) + }, + zoomOut: (options) => { + return zoom(1 / 1.2, options?.duration) + }, + zoomTo: (zoomLevel, options) => { + return new Promise((resolve) => { + if (state.d3Selection && state.d3Zoom) { + state.d3Zoom.scaleTo( + transition(state.d3Selection, options?.duration, () => { + resolve(true) + }), + zoomLevel, + ) + } else { + resolve(false) + } + }) + }, + setViewport: (transform, options) => { + return transformViewport(transform.x, transform.y, transform.zoom, options?.duration) + }, + setTransform: (transform, options) => { + return transformViewport(transform.x, transform.y, transform.zoom, options?.duration) + }, + getViewport: () => ({ + x: state.viewport.x, + y: state.viewport.y, + zoom: state.viewport.zoom, + }), + getTransform: () => { + return { x: state.viewport.x, y: state.viewport.y, zoom: state.viewport.zoom, - }), - getTransform: () => { - return { - x: state.viewport.x, - y: state.viewport.y, - zoom: state.viewport.zoom, - } + } + }, + fitView: ( + options = { + padding: DEFAULT_PADDING, + includeHiddenNodes: false, + duration: 0, }, - fitView: ( - options = { - padding: DEFAULT_PADDING, - includeHiddenNodes: false, - duration: 0, - }, - ) => { - const nodesToFit: GraphNode[] = (options.includeHiddenNodes ? state.nodes : getNodes.value).filter((node) => { - const initialized = node.dimensions.width && node.dimensions.height - let shouldInclude = true + ) => { + const nodesToFit: GraphNode[] = (options.includeHiddenNodes ? state.nodes : getNodes.value).filter((node) => { + const initialized = node.dimensions.width && node.dimensions.height + let shouldInclude = true - if (options.nodes?.length) { - shouldInclude = options.nodes.includes(node.id) - } - - return initialized && shouldInclude - }) - - if (!nodesToFit.length) { - return Promise.resolve(false) + if (options.nodes?.length) { + shouldInclude = options.nodes.includes(node.id) } - const bounds = getRectOfNodes(nodesToFit) + return initialized && shouldInclude + }) - const { x, y, zoom } = getTransformForBounds( - bounds, - state.dimensions.width, - state.dimensions.height, - options.minZoom ?? state.minZoom, - options.maxZoom ?? state.maxZoom, - options.padding ?? DEFAULT_PADDING, - options.offset, - ) + if (!nodesToFit.length) { + return Promise.resolve(false) + } - return transformViewport(x, y, zoom, options?.duration) - }, - setCenter: (x, y, options) => { - const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : state.maxZoom - const centerX = state.dimensions.width / 2 - x * nextZoom - const centerY = state.dimensions.height / 2 - y * nextZoom + const bounds = getRectOfNodes(nodesToFit) - return transformViewport(centerX, centerY, nextZoom, options?.duration) - }, - fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => { - const { x, y, zoom } = getTransformForBounds( - bounds, - state.dimensions.width, - state.dimensions.height, - state.minZoom, - state.maxZoom, - options.padding, - ) + const { x, y, zoom } = getTransformForBounds( + bounds, + state.dimensions.width, + state.dimensions.height, + options.minZoom ?? state.minZoom, + options.maxZoom ?? state.maxZoom, + options.padding ?? DEFAULT_PADDING, + options.offset, + ) - return transformViewport(x, y, zoom, options?.duration) - }, - project: (position) => pointToRendererPoint(position, state.viewport, state.snapToGrid, state.snapGrid), - screenToFlowCoordinate: (position) => { - if (state.vueFlowRef) { - const { x: domX, y: domY } = state.vueFlowRef.getBoundingClientRect() + return transformViewport(x, y, zoom, options?.duration) + }, + setCenter: (x, y, options) => { + const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : state.maxZoom + const centerX = state.dimensions.width / 2 - x * nextZoom + const centerY = state.dimensions.height / 2 - y * nextZoom - const correctedPosition = { - x: position.x - domX, - y: position.y - domY, - } + return transformViewport(centerX, centerY, nextZoom, options?.duration) + }, + fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => { + const { x, y, zoom } = getTransformForBounds( + bounds, + state.dimensions.width, + state.dimensions.height, + state.minZoom, + state.maxZoom, + options.padding, + ) - return pointToRendererPoint(correctedPosition, state.viewport, state.snapToGrid, state.snapGrid) + return transformViewport(x, y, zoom, options?.duration) + }, + project: (position) => pointToRendererPoint(position, state.viewport, state.snapToGrid, state.snapGrid), + screenToFlowCoordinate: (position) => { + if (state.vueFlowRef) { + const { x: domX, y: domY } = state.vueFlowRef.getBoundingClientRect() + + const correctedPosition = { + x: position.x - domX, + y: position.y - domY, } - return { x: 0, y: 0 } - }, - flowToScreenCoordinate: (position) => { - if (state.vueFlowRef) { - const { x: domX, y: domY } = state.vueFlowRef.getBoundingClientRect() + return pointToRendererPoint(correctedPosition, state.viewport, state.snapToGrid, state.snapGrid) + } - const correctedPosition = { - x: position.x + domX, - y: position.y + domY, - } + return { x: 0, y: 0 } + }, + flowToScreenCoordinate: (position) => { + if (state.vueFlowRef) { + const { x: domX, y: domY } = state.vueFlowRef.getBoundingClientRect() - return rendererPointToPoint(correctedPosition, state.viewport) + const correctedPosition = { + x: position.x + domX, + y: position.y + domY, } - return { x: 0, y: 0 } - }, - } + return rendererPointToPoint(correctedPosition, state.viewport) + } + + return { x: 0, y: 0 } + }, } - - return initialViewportHelper }) } diff --git a/packages/core/src/composables/useVueFlow.ts b/packages/core/src/composables/useVueFlow.ts index 43336ee3..002732e1 100644 --- a/packages/core/src/composables/useVueFlow.ts +++ b/packages/core/src/composables/useVueFlow.ts @@ -56,7 +56,7 @@ export class Storage { const getters = useGetters(reactiveState, nodeIds, edgeIds) - const actions = useActions(id, emits, hooksOn, reactiveState, getters, nodeIds, edgeIds) + const actions = useActions(id, reactiveState, getters, nodeIds, edgeIds) actions.setState(reactiveState) diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index 46c42871..e6b6de30 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -54,8 +54,6 @@ import { useState } from './state' export function useActions( id: string, - emits: any, - hooksOn: any, state: State, getters: ComputedGetters, // todo: change to a Set @@ -846,7 +844,7 @@ export function useActions( const y = viewport?.y || position[1] const nextZoom = viewport?.zoom || zoom || state.viewport.zoom - return until(() => viewportHelper.value.initialized) + return until(() => viewportHelper.value.viewportInitialized) .toBe(true) .then(() => { viewportHelper.value @@ -891,7 +889,7 @@ export function useActions( setState(resetState) } - const actions: Actions = { + return { updateNodePositions, updateNodeDimensions, setElements, @@ -944,20 +942,4 @@ export function useActions( $reset, $destroy: () => {}, } - - until(() => viewportHelper.value.initialized) - .toBe(true) - .then(() => { - state.hooks.paneReady.trigger({ - id, - emits, - vueFlowVersion: typeof __VUE_FLOW_VERSION__ !== 'undefined' ? __VUE_FLOW_VERSION__ : 'UNKNOWN', - ...hooksOn, - ...state, - ...getters, - ...actions, - }) - }) - - return actions } diff --git a/packages/core/src/store/state.ts b/packages/core/src/store/state.ts index eab6361a..89ecdbf7 100644 --- a/packages/core/src/store/state.ts +++ b/packages/core/src/store/state.ts @@ -50,6 +50,9 @@ function defaultState(): State { d3Zoom: null, d3Selection: null, d3ZoomHandler: null, + + viewportInitialized: false, + minZoom: 0.5, maxZoom: 2, diff --git a/packages/core/src/types/store.ts b/packages/core/src/types/store.ts index 2646171a..74904543 100644 --- a/packages/core/src/types/store.ts +++ b/packages/core/src/types/store.ts @@ -57,6 +57,8 @@ export interface State extends Omit { readonly d3Selection: D3Selection | null readonly d3ZoomHandler: D3ZoomHandler | null + viewportInitialized: boolean + /** use setMinZoom action to change minZoom */ minZoom: number /** use setMaxZoom action to change maxZoom */