diff --git a/.changeset/fifty-rockets-report.md b/.changeset/fifty-rockets-report.md new file mode 100644 index 000000000..7ab72fa33 --- /dev/null +++ b/.changeset/fifty-rockets-report.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/minimap": patch +--- + +Update import of `getBoundsOfRects` diff --git a/.changeset/fluffy-tables-wash.md b/.changeset/fluffy-tables-wash.md new file mode 100644 index 000000000..df7f5a246 --- /dev/null +++ b/.changeset/fluffy-tables-wash.md @@ -0,0 +1,26 @@ +--- +"@vue-flow/core": minor +--- + +Replace existing graph utils exports with those already provided by `@xyflow/system`: + +- Replace utils + - `clamp` + - `clampPosition` + - `getDimensions` + - `getHostForElement` + - `getOverlappingArea` + - `rectToBox` + - `boxToRect` + - `getBoundsofRects` + - `getBoundsOfBoxes` + - `rendererPointToPoint` + - `getMarkerId` + - `isRect` + - `isNumeric` + - `calcAutoPan` + - `isMouseEvent` + - `getEventPosition` + +-Remove utils +- `isMacOS` diff --git a/.changeset/seven-ads-wait.md b/.changeset/seven-ads-wait.md new file mode 100644 index 000000000..6d7a11e61 --- /dev/null +++ b/.changeset/seven-ads-wait.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/minimap": minor +--- + +Replace d3 with xyflow minimap instance diff --git a/.changeset/six-spiders-pull.md b/.changeset/six-spiders-pull.md new file mode 100644 index 000000000..70e9e1c4d --- /dev/null +++ b/.changeset/six-spiders-pull.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/core": major +--- + +Rename transformationpane to viewport and viewport to zoompane. Also update corresponding css class names and styles. diff --git a/.changeset/tame-books-cross.md b/.changeset/tame-books-cross.md new file mode 100644 index 000000000..49a3225fd --- /dev/null +++ b/.changeset/tame-books-cross.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/core": major +--- + +Replace d3 zoom and pan with panzoom instance diff --git a/.changeset/ten-snails-flash.md b/.changeset/ten-snails-flash.md new file mode 100644 index 000000000..e92f8f092 --- /dev/null +++ b/.changeset/ten-snails-flash.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/core": minor +--- + +Replace existing edge utils with ones that are already provided by `@xyflow/system` and re-export them diff --git a/docs/src/guide/vue-flow/config.md b/docs/src/guide/vue-flow/config.md index 979d875df..88ea70e3e 100644 --- a/docs/src/guide/vue-flow/config.md +++ b/docs/src/guide/vue-flow/config.md @@ -500,7 +500,7 @@ const edges = ref([ ### default-viewport (optional) -- Type: [`ViewportTransform`](/typedocs/interfaces/ViewportTransform) +- Type: `Viewport` - Default: `{ zoom: 1, position: { x: 0, y: 0 } }` diff --git a/packages/core/package.json b/packages/core/package.json index 22253ca38..a7b092fca 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -74,7 +74,7 @@ "@vueuse/core": "^10.5.0", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" + "@xyflow/system": "^0.0.39" }, "devDependencies": { "@rollup/plugin-replace": "^5.0.3", @@ -82,7 +82,6 @@ "@tooling/tsconfig": "workspace:*", "@types/d3-drag": "^3.0.4", "@types/d3-selection": "^3.0.7", - "@types/d3-zoom": "^3.0.5", "@vitejs/plugin-vue": "^4.4.0", "autoprefixer": "^10.4.16", "postcss": "^8.4.31", diff --git a/packages/core/src/components/ConnectionLine/index.ts b/packages/core/src/components/ConnectionLine/index.ts index ffda18136..12cff4b79 100644 --- a/packages/core/src/components/ConnectionLine/index.ts +++ b/packages/core/src/components/ConnectionLine/index.ts @@ -1,10 +1,11 @@ import { computed, defineComponent, h, inject } from 'vue' +import { getBezierPath, getMarkerId, getSmoothStepPath } from '@xyflow/system' import type { HandleElement } from '../../types' import { ConnectionLineType, ConnectionMode, Position } from '../../types' -import { getHandlePosition, getMarkerId, oppositePosition } from '../../utils' +import { getHandlePosition, oppositePosition } from '../../utils' import { useVueFlow } from '../../composables' import { Slots } from '../../context' -import { getBezierPath, getSimpleBezierPath, getSmoothStepPath } from '../Edges/utils' +import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge' const ConnectionLine = defineComponent({ name: 'ConnectionLine', diff --git a/packages/core/src/components/Edges/BezierEdge.ts b/packages/core/src/components/Edges/BezierEdge.ts index 7dc0751f2..4ed7dd82a 100644 --- a/packages/core/src/components/Edges/BezierEdge.ts +++ b/packages/core/src/components/Edges/BezierEdge.ts @@ -1,8 +1,8 @@ import { defineComponent, h } from 'vue' +import { getBezierPath } from '@xyflow/system' import type { BezierEdgeProps } from '../../types' import { Position } from '../../types' import BaseEdge from './BaseEdge.vue' -import { getBezierPath } from './utils' const BezierEdge = defineComponent({ name: 'BezierEdge', diff --git a/packages/core/src/components/Edges/EdgeWrapper.ts b/packages/core/src/components/Edges/EdgeWrapper.ts index 95bb3960f..ba9c2041e 100644 --- a/packages/core/src/components/Edges/EdgeWrapper.ts +++ b/packages/core/src/components/Edges/EdgeWrapper.ts @@ -1,17 +1,10 @@ import { computed, defineComponent, getCurrentInstance, h, inject, provide, ref, resolveComponent, toRef } from 'vue' +import { getMarkerId } from '@xyflow/system' import type { Connection, EdgeComponent, HandleType, MouseTouchEvent } from '../../types' import { ConnectionMode, Position } from '../../types' import { useEdgeHooks, useHandle, useVueFlow } from '../../composables' import { EdgeId, EdgeRef, Slots } from '../../context' -import { - ARIA_EDGE_DESC_KEY, - ErrorCode, - VueFlowError, - elementSelectionKeys, - getEdgeHandle, - getHandlePosition, - getMarkerId, -} from '../../utils' +import { ARIA_EDGE_DESC_KEY, ErrorCode, VueFlowError, elementSelectionKeys, getEdgeHandle, getHandlePosition } from '../../utils' import EdgeAnchor from './EdgeAnchor' interface Props { diff --git a/packages/core/src/components/Edges/SimpleBezierEdge.ts b/packages/core/src/components/Edges/SimpleBezierEdge.ts index a33e7c76f..019c9fb8f 100644 --- a/packages/core/src/components/Edges/SimpleBezierEdge.ts +++ b/packages/core/src/components/Edges/SimpleBezierEdge.ts @@ -1,8 +1,99 @@ import { defineComponent, h } from 'vue' +import { getBezierEdgeCenter } from '@xyflow/system' import type { SimpleBezierEdgeProps } from '../../types' import { Position } from '../../types' import BaseEdge from './BaseEdge.vue' -import { getSimpleBezierPath } from './utils' + +export interface GetSimpleBezierPathParams { + sourceX: number + sourceY: number + sourcePosition?: Position + targetX: number + targetY: number + targetPosition?: Position +} + +interface GetControlParams { + pos: Position + x1: number + y1: number + x2: number + y2: number +} + +function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] { + let ctX: number, ctY: number + switch (pos) { + case Position.Left: + case Position.Right: + ctX = 0.5 * (x1 + x2) + ctY = y1 + break + case Position.Top: + case Position.Bottom: + ctX = x1 + ctY = 0.5 * (y1 + y2) + break + } + return [ctX, ctY] +} + +/** + * Get a simple bezier path from source to target handle (no curvature) + * @public + * + * @param simpleBezierPathParams + * @param simpleBezierPathParams.sourceX - The x position of the source handle + * @param simpleBezierPathParams.sourceY - The y position of the source handle + * @param simpleBezierPathParams.sourcePosition - The position of the source handle (default: Position.Bottom) + * @param simpleBezierPathParams.targetX - The x position of the target handle + * @param simpleBezierPathParams.targetY - The y position of the target handle + * @param simpleBezierPathParams.targetPosition - The position of the target handle (default: Position.Top) + * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label + */ +export function getSimpleBezierPath({ + sourceX, + sourceY, + sourcePosition = Position.Bottom, + targetX, + targetY, + targetPosition = Position.Top, +}: GetSimpleBezierPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] { + const [sourceControlX, sourceControlY] = getControl({ + pos: sourcePosition, + x1: sourceX, + y1: sourceY, + x2: targetX, + y2: targetY, + }) + + const [targetControlX, targetControlY] = getControl({ + pos: targetPosition, + x1: targetX, + y1: targetY, + x2: sourceX, + y2: sourceY, + }) + + const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({ + sourceX, + sourceY, + targetX, + targetY, + sourceControlX, + sourceControlY, + targetControlX, + targetControlY, + }) + + return [ + `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`, + labelX, + labelY, + offsetX, + offsetY, + ] +} const SimpleBezierEdge = defineComponent({ name: 'SimpleBezierEdge', diff --git a/packages/core/src/components/Edges/SmoothStepEdge.ts b/packages/core/src/components/Edges/SmoothStepEdge.ts index 88fd824d6..f43b49cd8 100644 --- a/packages/core/src/components/Edges/SmoothStepEdge.ts +++ b/packages/core/src/components/Edges/SmoothStepEdge.ts @@ -1,8 +1,8 @@ import { defineComponent, h } from 'vue' +import { getSmoothStepPath } from '@xyflow/system' import type { SmoothStepEdgeProps } from '../../types' import { Position } from '../../types' import BaseEdge from './BaseEdge.vue' -import { getSmoothStepPath } from './utils' const SmoothStepEdge = defineComponent({ name: 'SmoothStepEdge', diff --git a/packages/core/src/components/Edges/StraightEdge.ts b/packages/core/src/components/Edges/StraightEdge.ts index 38752a902..6d5b6b84d 100644 --- a/packages/core/src/components/Edges/StraightEdge.ts +++ b/packages/core/src/components/Edges/StraightEdge.ts @@ -1,7 +1,7 @@ import { defineComponent, h } from 'vue' +import { getStraightPath } from '@xyflow/system' import type { StraightEdgeProps } from '../../types' import BaseEdge from './BaseEdge.vue' -import { getStraightPath } from './utils' const StraightEdge = defineComponent({ name: 'StraightEdge', diff --git a/packages/core/src/components/Edges/utils/bezier.ts b/packages/core/src/components/Edges/utils/bezier.ts deleted file mode 100644 index 5ddca603c..000000000 --- a/packages/core/src/components/Edges/utils/bezier.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Position } from '../../../types' -import type { EdgePathParams } from './general' -import { getBezierEdgeCenter } from './general' - -export interface GetBezierPathParams { - sourceX: number - sourceY: number - sourcePosition?: Position - targetX: number - targetY: number - targetPosition?: Position - curvature?: number -} - -interface GetControlWithCurvatureParams { - pos: Position - x1: number - y1: number - x2: number - y2: number - c: number -} - -function calculateControlOffset(distance: number, curvature: number) { - if (distance >= 0) { - return 0.5 * distance - } else { - return curvature * 25 * Math.sqrt(-distance) - } -} - -function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] { - let ctX: number, ctY: number - switch (pos) { - case Position.Left: - ctX = x1 - calculateControlOffset(x1 - x2, c) - ctY = y1 - break - case Position.Right: - ctX = x1 + calculateControlOffset(x2 - x1, c) - ctY = y1 - break - case Position.Top: - ctX = x1 - ctY = y1 - calculateControlOffset(y1 - y2, c) - break - case Position.Bottom: - ctX = x1 - ctY = y1 + calculateControlOffset(y2 - y1, c) - break - } - return [ctX, ctY] -} - -/** - * Get a bezier path from source to target handle - * @public - * - * @param bezierPathParams - * @param bezierPathParams.sourceX - The x position of the source handle - * @param bezierPathParams.sourceY - The y position of the source handle - * @param bezierPathParams.sourcePosition - The position of the source handle (default: Position.Bottom) - * @param bezierPathParams.targetX - The x position of the target handle - * @param bezierPathParams.targetY - The y position of the target handle - * @param bezierPathParams.targetPosition - The position of the target handle (default: Position.Top) - * @param bezierPathParams.curvature - The curvature of the edge (default: 0.25) - * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label - */ -export function getBezierPath(bezierPathParams: GetBezierPathParams): EdgePathParams { - const { - sourceX, - sourceY, - sourcePosition = Position.Bottom, - targetX, - targetY, - targetPosition = Position.Top, - curvature = 0.25, - } = bezierPathParams - - const [sourceControlX, sourceControlY] = getControlWithCurvature({ - pos: sourcePosition, - x1: sourceX, - y1: sourceY, - x2: targetX, - y2: targetY, - c: curvature, - }) - const [targetControlX, targetControlY] = getControlWithCurvature({ - pos: targetPosition, - x1: targetX, - y1: targetY, - x2: sourceX, - y2: sourceY, - c: curvature, - }) - const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({ - sourceX, - sourceY, - targetX, - targetY, - sourceControlX, - sourceControlY, - targetControlX, - targetControlY, - }) - - return [ - `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`, - labelX, - labelY, - offsetX, - offsetY, - ] -} diff --git a/packages/core/src/components/Edges/utils/general.ts b/packages/core/src/components/Edges/utils/general.ts deleted file mode 100644 index 1b7bfab9c..000000000 --- a/packages/core/src/components/Edges/utils/general.ts +++ /dev/null @@ -1,51 +0,0 @@ -export type EdgePathParams = [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] - -// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB) -export function getSimpleEdgeCenter({ - sourceX, - sourceY, - targetX, - targetY, -}: { - sourceX: number - sourceY: number - targetX: number - targetY: number -}): [number, number, number, number] { - const xOffset = Math.abs(targetX - sourceX) / 2 - const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset - - const yOffset = Math.abs(targetY - sourceY) / 2 - const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset - - return [centerX, centerY, xOffset, yOffset] -} - -export function getBezierEdgeCenter({ - sourceX, - sourceY, - targetX, - targetY, - sourceControlX, - sourceControlY, - targetControlX, - targetControlY, -}: { - sourceX: number - sourceY: number - targetX: number - targetY: number - sourceControlX: number - sourceControlY: number - targetControlX: number - targetControlY: number -}): [number, number, number, number] { - // cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate - // https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve - const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125 - const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125 - const offsetX = Math.abs(centerX - sourceX) - const offsetY = Math.abs(centerY - sourceY) - - return [centerX, centerY, offsetX, offsetY] -} diff --git a/packages/core/src/components/Edges/utils/index.ts b/packages/core/src/components/Edges/utils/index.ts deleted file mode 100644 index 5993032ad..000000000 --- a/packages/core/src/components/Edges/utils/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './bezier' -export * from './general' -export * from './simple-bezier' -export * from './smoothstep' -export * from './straight' diff --git a/packages/core/src/components/Edges/utils/simple-bezier.ts b/packages/core/src/components/Edges/utils/simple-bezier.ts deleted file mode 100644 index 6f9c5f411..000000000 --- a/packages/core/src/components/Edges/utils/simple-bezier.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Position } from '../../../types' -import type { EdgePathParams } from './general' -import { getBezierEdgeCenter } from './general' - -export interface GetSimpleBezierPathParams { - sourceX: number - sourceY: number - sourcePosition?: Position - targetX: number - targetY: number - targetPosition?: Position -} - -interface GetControlParams { - pos: Position - x1: number - y1: number - x2: number - y2: number -} - -function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] { - let ctX: number, ctY: number - switch (pos) { - case Position.Left: - case Position.Right: - ctX = 0.5 * (x1 + x2) - ctY = y1 - break - case Position.Top: - case Position.Bottom: - ctX = x1 - ctY = 0.5 * (y1 + y2) - break - } - return [ctX, ctY] -} - -/** - * Get a simple bezier path from source to target handle (no curvature) - * @public - * - * @param simpleBezierPathParams - * @param simpleBezierPathParams.sourceX - The x position of the source handle - * @param simpleBezierPathParams.sourceY - The y position of the source handle - * @param simpleBezierPathParams.sourcePosition - The position of the source handle (default: Position.Bottom) - * @param simpleBezierPathParams.targetX - The x position of the target handle - * @param simpleBezierPathParams.targetY - The y position of the target handle - * @param simpleBezierPathParams.targetPosition - The position of the target handle (default: Position.Top) - * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label - */ -export function getSimpleBezierPath(simpleBezierPathParams: GetSimpleBezierPathParams): EdgePathParams { - const { - sourceX, - sourceY, - sourcePosition = Position.Bottom, - targetX, - targetY, - targetPosition = Position.Top, - } = simpleBezierPathParams - - const [sourceControlX, sourceControlY] = getControl({ - pos: sourcePosition, - x1: sourceX, - y1: sourceY, - x2: targetX, - y2: targetY, - }) - const [targetControlX, targetControlY] = getControl({ - pos: targetPosition, - x1: targetX, - y1: targetY, - x2: sourceX, - y2: sourceY, - }) - - const [centerX, centerY, offsetX, offsetY] = getBezierEdgeCenter({ - sourceX, - sourceY, - targetX, - targetY, - sourceControlX, - sourceControlY, - targetControlX, - targetControlY, - }) - - return [ - `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`, - centerX, - centerY, - offsetX, - offsetY, - ] -} diff --git a/packages/core/src/components/Edges/utils/smoothstep.ts b/packages/core/src/components/Edges/utils/smoothstep.ts deleted file mode 100644 index cc6477440..000000000 --- a/packages/core/src/components/Edges/utils/smoothstep.ts +++ /dev/null @@ -1,250 +0,0 @@ -import type { XYPosition } from '../../../types' -import { Position } from '../../../types' -import type { EdgePathParams } from './general' -import { getSimpleEdgeCenter } from './general' - -export interface GetSmoothStepPathParams { - sourceX: number - sourceY: number - sourcePosition?: Position - targetX: number - targetY: number - targetPosition?: Position - borderRadius?: number - centerX?: number - centerY?: number - offset?: number -} - -const handleDirections = { - [Position.Left]: { x: -1, y: 0 }, - [Position.Right]: { x: 1, y: 0 }, - [Position.Top]: { x: 0, y: -1 }, - [Position.Bottom]: { x: 0, y: 1 }, -} - -function getDirection({ - source, - sourcePosition = Position.Bottom, - target, -}: { - source: XYPosition - sourcePosition: Position - target: XYPosition -}): XYPosition { - if (sourcePosition === Position.Left || sourcePosition === Position.Right) { - return source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 } - } - return source.y < target.y ? { x: 0, y: 1 } : { x: 0, y: -1 } -} - -function distance(a: XYPosition, b: XYPosition) { - return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2) -} - -// With this function we try to mimic an orthogonal edge routing behaviour -// It's not as good as a real orthogonal edge routing, but it's faster and good enough as a default for step and smooth step edges -function getPoints({ - source, - sourcePosition = Position.Bottom, - target, - targetPosition = Position.Top, - center, - offset, -}: { - source: XYPosition - sourcePosition: Position - target: XYPosition - targetPosition: Position - center: Partial - offset: number -}): [XYPosition[], number, number, number, number] { - const sourceDir = handleDirections[sourcePosition] - const targetDir = handleDirections[targetPosition] - const sourceGapped: XYPosition = { x: source.x + sourceDir.x * offset, y: source.y + sourceDir.y * offset } - const targetGapped: XYPosition = { x: target.x + targetDir.x * offset, y: target.y + targetDir.y * offset } - const dir = getDirection({ - source: sourceGapped, - sourcePosition, - target: targetGapped, - }) - const dirAccessor = dir.x !== 0 ? 'x' : 'y' - const currDir = dir[dirAccessor] - - let points: XYPosition[] - let centerX, centerY - - const sourceGapOffset = { x: 0, y: 0 } - const targetGapOffset = { x: 0, y: 0 } - - const [defaultCenterX, defaultCenterY, defaultOffsetX, defaultOffsetY] = getSimpleEdgeCenter({ - sourceX: source.x, - sourceY: source.y, - targetX: target.x, - targetY: target.y, - }) - - // opposite handle positions, default case - if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { - centerX = center.x ?? defaultCenterX - centerY = center.y ?? defaultCenterY - // ---> - // | - // >--- - const verticalSplit: XYPosition[] = [ - { x: centerX, y: sourceGapped.y }, - { x: centerX, y: targetGapped.y }, - ] - // | - // --- - // | - const horizontalSplit: XYPosition[] = [ - { x: sourceGapped.x, y: centerY }, - { x: targetGapped.x, y: centerY }, - ] - - if (sourceDir[dirAccessor] === currDir) { - points = dirAccessor === 'x' ? verticalSplit : horizontalSplit - } else { - points = dirAccessor === 'x' ? horizontalSplit : verticalSplit - } - } else { - // sourceTarget means we take x from source and y from target, targetSource is the opposite - const sourceTarget: XYPosition[] = [{ x: sourceGapped.x, y: targetGapped.y }] - const targetSource: XYPosition[] = [{ x: targetGapped.x, y: sourceGapped.y }] - // this handles edges with same handle positions - if (dirAccessor === 'x') { - points = sourceDir.x === currDir ? targetSource : sourceTarget - } else { - points = sourceDir.y === currDir ? sourceTarget : targetSource - } - - if (sourcePosition === targetPosition) { - const diff = Math.abs(source[dirAccessor] - target[dirAccessor]) - - // if an edge goes from right to right for example (sourcePosition === targetPosition) and the distance between source.x and target.x is less than the offset, the added point and the gapped source/target will overlap. This leads to a weird edge path. To avoid this we add a gapOffset to the source/target - if (diff <= offset) { - const gapOffset = Math.min(offset - 1, offset - diff) - if (sourceDir[dirAccessor] === currDir) { - sourceGapOffset[dirAccessor] = (sourceGapped[dirAccessor] > source[dirAccessor] ? -1 : 1) * gapOffset - } else { - targetGapOffset[dirAccessor] = (targetGapped[dirAccessor] > target[dirAccessor] ? -1 : 1) * gapOffset - } - } - } - - // these are conditions for handling mixed handle positions like Right -> Bottom for example - if (sourcePosition !== targetPosition) { - const dirAccessorOpposite = dirAccessor === 'x' ? 'y' : 'x' - const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite] - const sourceGtTargetOppo = sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite] - const sourceLtTargetOppo = sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite] - const flipSourceTarget = - (sourceDir[dirAccessor] === 1 && ((!isSameDir && sourceGtTargetOppo) || (isSameDir && sourceLtTargetOppo))) || - (sourceDir[dirAccessor] !== 1 && ((!isSameDir && sourceLtTargetOppo) || (isSameDir && sourceGtTargetOppo))) - - if (flipSourceTarget) { - points = dirAccessor === 'x' ? sourceTarget : targetSource - } - } - - const sourceGapPoint = { x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y } - const targetGapPoint = { x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y } - const maxXDistance = Math.max(Math.abs(sourceGapPoint.x - points[0].x), Math.abs(targetGapPoint.x - points[0].x)) - const maxYDistance = Math.max(Math.abs(sourceGapPoint.y - points[0].y), Math.abs(targetGapPoint.y - points[0].y)) - - // we want to place the label on the longest segment of the edge - if (maxXDistance >= maxYDistance) { - centerX = (sourceGapPoint.x + targetGapPoint.x) / 2 - centerY = points[0].y - } else { - centerX = points[0].x - centerY = (sourceGapPoint.y + targetGapPoint.y) / 2 - } - } - - const pathPoints = [ - source, - { x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y }, - ...points, - { x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y }, - target, - ] - - return [pathPoints, centerX, centerY, defaultOffsetX, defaultOffsetY] -} - -function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): string { - const bendSize = Math.min(distance(a, b) / 2, distance(b, c) / 2, size) - const { x, y } = b - - // no bend - if ((a.x === x && x === c.x) || (a.y === y && y === c.y)) { - return `L${x} ${y}` - } - - // first segment is horizontal - if (a.y === y) { - const xDir = a.x < c.x ? -1 : 1 - const yDir = a.y < c.y ? 1 : -1 - return `L ${x + bendSize * xDir},${y}Q ${x},${y} ${x},${y + bendSize * yDir}` - } - - const xDir = a.x < c.x ? 1 : -1 - const yDir = a.y < c.y ? -1 : 1 - return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}` -} - -/** - * Get a smooth step path from source to target handle - * @public - * - * @param smoothStepPathParams - * @param smoothStepPathParams.sourceX - The x position of the source handle - * @param smoothStepPathParams.sourceY - The y position of the source handle - * @param smoothStepPathParams.sourcePosition - The position of the source handle (default: Position.Bottom) - * @param smoothStepPathParams.targetX - The x position of the target handle - * @param smoothStepPathParams.targetY - The y position of the target handle - * @param smoothStepPathParams.targetPosition - The position of the target handle (default: Position.Top) - * @param smoothStepPathParams.borderRadius - The border radius of the edge (default: 5) - * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label - */ -export function getSmoothStepPath(smoothStepPathParams: GetSmoothStepPathParams): EdgePathParams { - const { - sourceX, - sourceY, - sourcePosition = Position.Bottom, - targetX, - targetY, - targetPosition = Position.Top, - borderRadius = 5, - centerX, - centerY, - offset = 20, - } = smoothStepPathParams - - const [points, labelX, labelY, offsetX, offsetY] = getPoints({ - source: { x: sourceX, y: sourceY }, - sourcePosition, - target: { x: targetX, y: targetY }, - targetPosition, - center: { x: centerX, y: centerY }, - offset, - }) - - const path = points.reduce((res, p, i) => { - let segment - - if (i > 0 && i < points.length - 1) { - segment = getBend(points[i - 1], p, points[i + 1], borderRadius) - } else { - segment = `${i === 0 ? 'M' : 'L'}${p.x} ${p.y}` - } - - res += segment - - return res - }, '') - - return [path, labelX, labelY, offsetX, offsetY] -} diff --git a/packages/core/src/components/Edges/utils/straight.ts b/packages/core/src/components/Edges/utils/straight.ts deleted file mode 100644 index f8dba4a93..000000000 --- a/packages/core/src/components/Edges/utils/straight.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { EdgePathParams } from './general' -import { getSimpleEdgeCenter } from './general' - -export interface GetStraightPathParams { - sourceX: number - sourceY: number - targetX: number - targetY: number -} - -/** - * Get a straight path from source to target handle - * @public - * - * @param straightEdgeParams - * @param straightEdgeParams.sourceX - The x position of the source handle - * @param straightEdgeParams.sourceY - The y position of the source handle - * @param straightEdgeParams.targetX - The x position of the target handle - * @param straightEdgeParams.targetY - The y position of the target handle - * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label - */ -export function getStraightPath(straightEdgeParams: GetStraightPathParams): EdgePathParams { - const { sourceX, sourceY, targetX, targetY } = straightEdgeParams - - const [centerX, centerY, offsetX, offsetY] = getSimpleEdgeCenter({ - sourceX, - sourceY, - targetX, - targetY, - }) - - return [`M ${sourceX},${sourceY}L ${targetX},${targetY}`, centerX, centerY, offsetX, offsetY] -} diff --git a/packages/core/src/components/Handle/Handle.vue b/packages/core/src/components/Handle/Handle.vue index 63433b0e6..7a96fa4a1 100644 --- a/packages/core/src/components/Handle/Handle.vue +++ b/packages/core/src/components/Handle/Handle.vue @@ -1,9 +1,10 @@ - - - - diff --git a/packages/core/src/container/Viewport/Viewport.vue b/packages/core/src/container/Viewport/Viewport.vue index 698307136..8e3969af9 100644 --- a/packages/core/src/container/Viewport/Viewport.vue +++ b/packages/core/src/container/Viewport/Viewport.vue @@ -1,410 +1,18 @@ diff --git a/packages/core/src/container/VueFlow/VueFlow.vue b/packages/core/src/container/VueFlow/VueFlow.vue index f71a823b3..e4810efc6 100644 --- a/packages/core/src/container/VueFlow/VueFlow.vue +++ b/packages/core/src/container/VueFlow/VueFlow.vue @@ -1,7 +1,7 @@ + + + + diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b1c218cbc..5b1aa2b12 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -18,14 +18,12 @@ export { default as BaseEdge } from './components/Edges/BaseEdge.vue' export { default as EdgeText } from './components/Edges/EdgeText.vue' export { default as EdgeLabelRenderer } from './components/Edges/EdgeLabelRenderer.vue' -export { - getBezierPath, - getSimpleBezierPath, - getSmoothStepPath, - getStraightPath, - getSimpleEdgeCenter, - getBezierEdgeCenter, -} from './components/Edges/utils' +// re-export these utils from system +export { getBezierPath, getSmoothStepPath, getStraightPath, getBezierEdgeCenter } from '@xyflow/system' +export { getSimpleBezierPath } from './components/Edges/SimpleBezierEdge' + +// re-export graph utils +export { clamp, getBoundsOfRects, getBoundsOfBoxes, rendererPointToPoint, getMarkerId } from '@xyflow/system' export { isNode, @@ -38,12 +36,8 @@ export { getTransformForBounds, getRectOfNodes, pointToRendererPoint, - rendererPointToPoint, getNodesInside, - getMarkerId, - getBoundsofRects, connectionExists, - clamp, } from './utils/graph' /** @@ -74,3 +68,6 @@ export { useKeyPress } from './composables/useKeyPress' export { VueFlowError, ErrorCode, isErrorOfType } from './utils/errors' export * from './types' + +// todo: add more re-exports +export { type Viewport } from '@xyflow/system' diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index 882aac2ef..64517e52b 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -1,9 +1,8 @@ -import { zoomIdentity } from 'd3-zoom' import type { ComputedRef } from 'vue' import { until } from '@vueuse/core' +import { getDimensions, getOverlappingArea, isRectObject, panBy as panBySystem } from '@xyflow/system' import type { Actions, - CoordinateExtent, Edge, EdgeAddChange, EdgeLookup, @@ -26,7 +25,6 @@ import type { import { useViewportHelper } from '../composables' import { applyChanges, - clamp, createAdditionChange, createEdgeRemoveChange, createGraphEdges, @@ -34,17 +32,14 @@ import { createNodeRemoveChange, createSelectionChange, getConnectedEdges as getConnectedEdgesBase, - getDimensions, getHandleBounds, getIncomers as getIncomersBase, getOutgoers as getOutgoersBase, - getOverlappingArea, getSelectionChanges, isDef, isEdge, isGraphNode, isNode, - isRect, nodeToRect, updateConnectionLookup, updateEdgeAction, @@ -125,17 +120,11 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed } const updateNodeDimensions: Actions['updateNodeDimensions'] = (updates) => { - if (!state.vueFlowRef) { + if (!state.viewportRef) { return } - const viewportNode = state.vueFlowRef.querySelector('.vue-flow__transformationpane') as HTMLElement - - if (!viewportNode) { - return - } - - const style = window.getComputedStyle(viewportNode) + const style = window.getComputedStyle(state.viewportRef) const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform) const changes: NodeDimensionChange[] = [] @@ -292,17 +281,17 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed } const setMinZoom: Actions['setMinZoom'] = (minZoom) => { - state.d3Zoom?.scaleExtent([minZoom, state.maxZoom]) + state.panZoom?.setScaleExtent([minZoom, state.maxZoom]) state.minZoom = minZoom } const setMaxZoom: Actions['setMaxZoom'] = (maxZoom) => { - state.d3Zoom?.scaleExtent([state.minZoom, maxZoom]) + state.panZoom?.setScaleExtent([state.minZoom, maxZoom]) state.maxZoom = maxZoom } const setTranslateExtent: Actions['setTranslateExtent'] = (translateExtent) => { - state.d3Zoom?.translateExtent(translateExtent) + state.panZoom?.setTranslateExtent(translateExtent) state.translateExtent = translateExtent } @@ -312,7 +301,7 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed } const setPaneClickDistance: Actions['setPaneClickDistance'] = (clickDistance) => { - state.d3Zoom?.clickDistance(clickDistance) + state.panZoom?.setClickDistance(clickDistance) } const setInteractive: Actions['setInteractive'] = (isInteractive) => { @@ -626,7 +615,7 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed const getNodeRect = ( nodeOrRect: (Partial & { id: Node['id'] }) | Rect, ): [Rect | null, Node | null | undefined, boolean] => { - const isRectObj = isRect(nodeOrRect) + const isRectObj = isRectObject(nodeOrRect) const node = isRectObj ? null : isGraphNode(nodeOrRect as GraphNode) ? (nodeOrRect as GraphNode) : findNode(nodeOrRect.id) if (!isRectObj && !node) { @@ -677,44 +666,16 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed } const panBy: Actions['panBy'] = (delta) => { - const { viewport, dimensions, d3Zoom, d3Selection, translateExtent } = state - - if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) { - return false - } - - const nextTransform = zoomIdentity.translate(viewport.x + delta.x, viewport.y + delta.y).scale(viewport.zoom) + const { viewport, dimensions, translateExtent, panZoom } = state - const extent: CoordinateExtent = [ - [0, 0], - [dimensions.width, dimensions.height], - ] - - const constrainedTransform = d3Zoom.constrain()(nextTransform, extent, translateExtent) - - const transformChanged = - state.viewport.x !== constrainedTransform.x || - state.viewport.y !== constrainedTransform.y || - state.viewport.zoom !== constrainedTransform.k - - d3Zoom.transform(d3Selection, constrainedTransform) - - return transformChanged + return panBySystem({ delta, panZoom, transform: [viewport.x, viewport.y, viewport.zoom], translateExtent, ...dimensions }) } const setState: Actions['setState'] = (options) => { const opts = options instanceof Function ? options(state) : options // these options cannot be set after initialization - const exclude: (keyof typeof opts)[] = [ - 'd3Zoom', - 'd3Selection', - 'd3ZoomHandler', - 'viewportRef', - 'vueFlowRef', - 'dimensions', - 'hooks', - ] + const exclude: (keyof typeof opts)[] = ['viewportRef', 'vueFlowRef', 'dimensions', 'hooks'] // we need to set the default opts before setting any elements so the options are applied to the elements on first render if (isDef(opts.defaultEdgeOptions)) { @@ -760,7 +721,7 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed } } - until(() => state.d3Zoom) + until(() => state.panZoom) .not.toBeNull() .then(setSkippedOptions) @@ -849,21 +810,12 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed state.edges = [] state.nodes = [] - // reset the zoom state - if (state.d3Zoom && state.d3Selection) { - const updatedTransform = zoomIdentity - .translate(resetState.defaultViewport.x ?? 0, resetState.defaultViewport.y ?? 0) - .scale(clamp(resetState.defaultViewport.zoom ?? 1, resetState.minZoom, resetState.maxZoom)) - - const bbox = state.viewportRef!.getBoundingClientRect() - - const extent: CoordinateExtent = [ - [0, 0], - [bbox.width, bbox.height], - ] - - const constrainedTransform = state.d3Zoom.constrain()(updatedTransform, extent, resetState.translateExtent) - state.d3Zoom.transform(state.d3Selection, constrainedTransform) + if (state.panZoom) { + state.panZoom.setViewport({ + x: state.defaultViewport.x ?? 0, + y: state.defaultViewport.y ?? 0, + zoom: state.defaultViewport.zoom ?? 1, + }) } setState(resetState) @@ -915,9 +867,7 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed zoomOut: (transitionOpts) => viewportHelper.value.zoomOut(transitionOpts), zoomTo: (zoomLevel, transitionOpts) => viewportHelper.value.zoomTo(zoomLevel, transitionOpts), setViewport: (params, transitionOpts) => viewportHelper.value.setViewport(params, transitionOpts), - setTransform: (params, transitionOpts) => viewportHelper.value.setTransform(params, transitionOpts), getViewport: () => viewportHelper.value.getViewport(), - getTransform: () => viewportHelper.value.getTransform(), setCenter: (x, y, opts) => viewportHelper.value.setCenter(x, y, opts), fitBounds: (params, opts) => viewportHelper.value.fitBounds(params, opts), project: (params) => viewportHelper.value.project(params), diff --git a/packages/core/src/store/state.ts b/packages/core/src/store/state.ts index 2198b9465..b7caf66a8 100644 --- a/packages/core/src/store/state.ts +++ b/packages/core/src/store/state.ts @@ -1,7 +1,7 @@ +import { isMacOs } from '@xyflow/system' import type { FlowOptions, State } from '../types' import { ConnectionLineType, ConnectionMode, PanOnScrollMode, SelectionMode } from '../types' -import { isMacOs } from '../utils' import { createHooks } from './hooks' export function useState(): State { @@ -22,9 +22,7 @@ export function useState(): State { }, viewport: { x: 0, y: 0, zoom: 1 }, - d3Zoom: null, - d3Selection: null, - d3ZoomHandler: null, + panZoom: null, minZoom: 0.5, maxZoom: 2, diff --git a/packages/core/src/style.css b/packages/core/src/style.css index f2512de47..f82daea22 100644 --- a/packages/core/src/style.css +++ b/packages/core/src/style.css @@ -31,13 +31,13 @@ } } -.vue-flow__transformationpane { +.vue-flow__viewport { transform-origin: 0 0; z-index: 2; pointer-events: none; } -.vue-flow__viewport { +.vue-flow__zoom-pane { z-index: 4; overflow: clip; } diff --git a/packages/core/src/types/flow.ts b/packages/core/src/types/flow.ts index 4ea8134aa..88fcdb536 100644 --- a/packages/core/src/types/flow.ts +++ b/packages/core/src/types/flow.ts @@ -1,6 +1,6 @@ import type { CSSProperties } from 'vue' import type { KeyFilter } from '@vueuse/core' -import type { D3ZoomEvent } from 'd3-zoom' +import type { Viewport } from '@xyflow/system' import type { VueFlowError } from '../utils' import type { DefaultEdgeOptions, Edge, EdgeProps, EdgeUpdatable, GraphEdge } from './edge' import type { CoordinateExtent, CoordinateExtentRange, GraphNode, Node, NodeProps } from './node' @@ -13,9 +13,9 @@ import type { Connector, OnConnectStartParams, } from './connection' -import type { PanOnScrollMode, ViewportTransform } from './zoom' +import type { PanOnScrollMode } from './zoom' import type { EdgeTypesObject, NodeTypesObject } from './components' -import type { CustomEvent, EdgeMouseEvent, EdgeUpdateEvent, NodeDragEvent, NodeMouseEvent } from './hooks' +import type { CustomEvent, EdgeMouseEvent, EdgeUpdateEvent, MouseTouchEvent, NodeDragEvent, NodeMouseEvent } from './hooks' import type { ValidConnectionFunc } from './handle' import type { EdgeChange, NodeChange } from './changes' import type { VueFlowStore } from './store' @@ -137,7 +137,7 @@ export interface FlowExportObject { */ zoom: number /** exported viewport (position + zoom) */ - viewport: ViewportTransform + viewport: Viewport } export interface FlowProps { @@ -179,7 +179,7 @@ export interface FlowProps { panOnDrag?: boolean | number[] minZoom?: number maxZoom?: number - defaultViewport?: Partial + defaultViewport?: Partial translateExtent?: CoordinateExtent nodeExtent?: CoordinateExtent | CoordinateExtentRange defaultMarkerColor?: string @@ -264,10 +264,9 @@ export interface FlowEmits { ): void (event: 'clickConnectEnd', connectionEvent?: MouseEvent): void - (event: 'moveStart', moveEvent: { event: D3ZoomEvent; flowTransform: ViewportTransform }): void - (event: 'move', moveEvent: { event: D3ZoomEvent; flowTransform: ViewportTransform }): void - (event: 'moveEnd', moveEvent: { event: D3ZoomEvent; flowTransform: ViewportTransform }): void - + (event: 'moveStart', moveEvent: { event: MouseTouchEvent | null; viewport: Viewport }): void + (event: 'move', moveEvent: { event: MouseTouchEvent | null; viewport: Viewport }): void + (event: 'moveEnd', moveEvent: { event: MouseTouchEvent | null; viewport: Viewport }): void (event: 'selectionDragStart', selectionEvent: NodeDragEvent): void (event: 'selectionDrag', selectionEvent: NodeDragEvent): void (event: 'selectionDragStop', selectionEvent: NodeDragEvent): void @@ -275,9 +274,9 @@ export interface FlowEmits { (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: 'viewportChangeStart', viewport: Viewport): void + (event: 'viewportChange', viewport: Viewport): void + (event: 'viewportChangeEnd', viewport: Viewport): void (event: 'paneScroll', paneScrollEvent: WheelEvent | undefined): void ( diff --git a/packages/core/src/types/hooks.ts b/packages/core/src/types/hooks.ts index 03e8f1d17..c38ec0580 100644 --- a/packages/core/src/types/hooks.ts +++ b/packages/core/src/types/hooks.ts @@ -1,10 +1,9 @@ import type { EventHookOn, EventHookTrigger } from '@vueuse/core' -import type { D3ZoomEvent } from 'd3-zoom' +import type { Viewport } from '@xyflow/system' import type { EventHookExtended, VueFlowError } from '../utils' import type { GraphEdge } from './edge' import type { GraphNode } from './node' import type { Connection, OnConnectStartParams } from './connection' -import type { ViewportTransform } from './zoom' import type { EdgeChange, NodeChange } from './changes' import type { VueFlowStore } from './store' @@ -63,18 +62,18 @@ export interface FlowEvents { /** @deprecated use `init` instead */ paneReady: VueFlowStore init: VueFlowStore - move: { event: D3ZoomEvent | WheelEvent; flowTransform: ViewportTransform } - moveStart: { event: D3ZoomEvent | WheelEvent; flowTransform: ViewportTransform } - moveEnd: { event: D3ZoomEvent | WheelEvent; flowTransform: ViewportTransform } + move: { event: MouseTouchEvent | null; viewport: Viewport } + moveStart: { event: MouseTouchEvent | null; viewport: Viewport } + moveEnd: { event: MouseTouchEvent | null; viewport: Viewport } selectionDragStart: NodeDragEvent selectionDrag: NodeDragEvent selectionDragStop: NodeDragEvent selectionContextMenu: { event: MouseEvent; nodes: GraphNode[] } selectionStart: MouseEvent selectionEnd: MouseEvent - viewportChangeStart: ViewportTransform - viewportChange: ViewportTransform - viewportChangeEnd: ViewportTransform + viewportChangeStart: Viewport + viewportChange: Viewport + viewportChangeEnd: Viewport paneScroll: WheelEvent | undefined paneClick: MouseEvent paneContextMenu: MouseEvent diff --git a/packages/core/src/types/store.ts b/packages/core/src/types/store.ts index 0fd614f82..37f16ccab 100644 --- a/packages/core/src/types/store.ts +++ b/packages/core/src/types/store.ts @@ -1,5 +1,6 @@ import type { CSSProperties, ComputedRef, ToRefs } from 'vue' import type { KeyFilter } from '@vueuse/core' +import type { PanZoomInstance, Viewport } from '@xyflow/system' import type { ViewportHelper } from '../composables' import type { Dimensions, @@ -28,7 +29,7 @@ import type { } from './connection' import type { DefaultEdgeOptions, Edge, EdgeUpdatable, GraphEdge } from './edge' import type { CoordinateExtent, CoordinateExtentRange, GraphNode, Node } from './node' -import type { D3Selection, D3Zoom, D3ZoomHandler, PanOnScrollMode, ViewportTransform } from './zoom' +import type { PanOnScrollMode } from './zoom' import type { CustomEvent, FlowHooks, FlowHooksEmit, FlowHooksOn } from './hooks' import type { EdgeChange, NodeChange, NodeDragItem } from './changes' import type { ConnectingHandle, HandleType, ValidConnectionFunc } from './handle' @@ -59,15 +60,14 @@ export interface State extends Omit { connectionLookup: ConnectionLookup - readonly d3Zoom: D3Zoom | null - readonly d3Selection: D3Selection | null - readonly d3ZoomHandler: D3ZoomHandler | null + /** The panzoom instance */ + panZoom: PanZoomInstance | null /** use setMinZoom action to change minZoom */ minZoom: number /** use setMaxZoom action to change maxZoom */ maxZoom: number - defaultViewport: Partial + defaultViewport: Partial /** use setTranslateExtent action to change translateExtent */ translateExtent: CoordinateExtent nodeExtent: CoordinateExtent | CoordinateExtentRange @@ -75,7 +75,7 @@ export interface State extends Omit { /** viewport dimensions - do not change! */ readonly dimensions: Dimensions /** viewport transform x, y, z - do not change! */ - readonly viewport: ViewportTransform + readonly viewport: Viewport /** if true will skip rendering any elements currently not inside viewport until they become visible */ onlyRenderVisibleElements: boolean nodesSelectionActive: boolean @@ -281,11 +281,11 @@ export interface Actions extends Omit { removeSelectedNodes: (nodes: GraphNode[]) => void /** unselect selected elements (if none are passed, all elements are unselected) */ removeSelectedElements: (elements?: Elements) => void - /** apply min zoom value to d3 */ + /** apply min zoom value to panzoom */ setMinZoom: (zoom: number) => void - /** apply max zoom value to d3 */ + /** apply max zoom value to panzoom */ setMaxZoom: (zoom: number) => void - /** apply translate extent to d3 */ + /** apply translate extent to panzoom */ setTranslateExtent: (translateExtent: CoordinateExtent) => void /** apply extent to nodes */ setNodeExtent: (nodeExtent: CoordinateExtent | CoordinateExtentRange) => void @@ -325,7 +325,7 @@ export interface Actions extends Omit { /** get all connections of a handle belonging to a node */ getHandleConnections: ({ id, type, nodeId }: { id?: string | null; type: HandleType; nodeId: string }) => HandleConnection[] /** pan the viewport; return indicates if a transform has happened or not */ - panBy: (delta: XYPosition) => boolean + panBy: (delta: XYPosition) => Promise /** viewport helper instance */ viewportHelper: ComputedRef diff --git a/packages/core/src/types/zoom.ts b/packages/core/src/types/zoom.ts index 198baa1e8..ccf1ce36b 100644 --- a/packages/core/src/types/zoom.ts +++ b/packages/core/src/types/zoom.ts @@ -1,11 +1,6 @@ -import type { Selection } from 'd3-selection' -import type { ZoomBehavior } from 'd3-zoom' +import type { Viewport } from '@xyflow/system' import type { Rect, XYPosition } from './flow' -export type D3Zoom = ZoomBehavior -export type D3Selection = Selection -export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void - export enum PanOnScrollMode { Free = 'free', Vertical = 'vertical', @@ -28,12 +23,6 @@ export type FitViewParams = { nodes?: string[] } & TransitionOptions -export interface ViewportTransform { - x: number - y: number - zoom: number -} - export type SetCenterOptions = TransitionOptions & { zoom?: number } @@ -60,22 +49,18 @@ export type ZoomInOut = (options?: TransitionOptions) => Promise /** zoom to a specific level */ export type ZoomTo = (zoomLevel: number, options?: TransitionOptions) => Promise -/** get current viewport transform */ -export type GetViewport = () => ViewportTransform +/** get current viewport */ +export type GetViewport = () => Viewport -/** set current viewport transform */ -export type SetViewport = (transform: ViewportTransform, options?: TransitionOptions) => Promise +/** set current viewport */ +export type SetViewport = (viewport: Viewport, options?: TransitionOptions) => Promise export interface ViewportFunctions { zoomIn: ZoomInOut zoomOut: ZoomInOut zoomTo: ZoomTo setViewport: SetViewport - /** @deprecated use setViewport instead */ - setTransform: SetViewport getViewport: GetViewport - /** @deprecated use getViewport instead */ - getTransform: GetViewport fitView: FitView setCenter: SetCenter fitBounds: FitBounds diff --git a/packages/core/src/utils/autopan.ts b/packages/core/src/utils/autopan.ts deleted file mode 100644 index 140d698b1..000000000 --- a/packages/core/src/utils/autopan.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Dimensions, XYPosition } from '../types' -import { clamp } from './graph' - -// returns a number between 0 and 1 that represents the velocity of the movement -// when the mouse is close to the edge of the canvas -function calcAutoPanVelocity(value: number, min: number, max: number) { - if (value < min) { - return clamp(Math.abs(value - min), 1, min) / min - } - - if (value > max) { - return -clamp(Math.abs(value - max), 1, min) / min - } - - return 0 -} - -export function calcAutoPan( - pos: XYPosition, - bounds: Dimensions, - speed = 15, - distance = 40, -): [xMovement: number, yMovement: number] { - const xMovement = calcAutoPanVelocity(pos.x, distance, bounds.width - distance) * speed - const yMovement = calcAutoPanVelocity(pos.y, distance, bounds.height - distance) * speed - - return [xMovement, yMovement] -} diff --git a/packages/core/src/utils/drag.ts b/packages/core/src/utils/drag.ts index ccc107f2f..a2756ffb2 100644 --- a/packages/core/src/utils/drag.ts +++ b/packages/core/src/utils/drag.ts @@ -1,4 +1,6 @@ import { markRaw } from 'vue' +import type { UseDragEvent } from '@xyflow/system' +import { clampPosition } from '@xyflow/system' import type { Actions, CoordinateExtent, @@ -9,7 +11,11 @@ import type { State, XYPosition, } from '../types' -import { ErrorCode, VueFlowError, clampPosition, isParentSelected } from '.' +import { ErrorCode, VueFlowError, isParentSelected } from '.' + +export function isUseDragEvent(event: any): event is UseDragEvent { + return 'sourceEvent' in event +} export function hasSelector(target: Element, selector: string, node: Element): boolean { let current = target diff --git a/packages/core/src/utils/edge.ts b/packages/core/src/utils/edge.ts index 584c33582..93172dfb3 100644 --- a/packages/core/src/utils/edge.ts +++ b/packages/core/src/utils/edge.ts @@ -1,6 +1,8 @@ -import type { Actions, GraphEdge, GraphNode, HandleElement, ViewportTransform, XYPosition } from '../types' +import type { Viewport } from '@xyflow/system' +import { rectToBox } from '@xyflow/system' +import type { Actions, GraphEdge, GraphNode, HandleElement, XYPosition } from '../types' import { Position } from '../types' -import { getNodeDimensions, rectToBox } from '.' +import { getNodeDimensions } from '.' export function getHandlePosition( node: GraphNode, @@ -48,7 +50,7 @@ interface IsEdgeVisibleParams { targetHeight: number width: number height: number - viewport: ViewportTransform + viewport: Viewport } export function isEdgeVisible({ diff --git a/packages/core/src/utils/errors.ts b/packages/core/src/utils/errors.ts index 262a1b50e..ceebebe0c 100644 --- a/packages/core/src/utils/errors.ts +++ b/packages/core/src/utils/errors.ts @@ -23,22 +23,25 @@ const messages = { [ErrorCode.MISSING_STYLES]: () => `It seems that you haven't loaded the necessary styles. Please import '@vue-flow/core/dist/style.css' to ensure that the graph is rendered correctly`, [ErrorCode.MISSING_VIEWPORT_DIMENSIONS]: () => 'The Vue Flow parent container needs a width and a height to render the graph', - [ErrorCode.NODE_INVALID]: (id?: string) => `Node is invalid\nNode: ${id}`, - [ErrorCode.NODE_NOT_FOUND]: (id: string | null) => `Node not found\nNode: ${id}`, - [ErrorCode.NODE_MISSING_PARENT]: (id: string, parentId: string) => `Node is missing a parent\nNode: ${id}\nParent: ${parentId}`, + [ErrorCode.NODE_INVALID]: (id?: string) => `Node is invalid\nNode id: ${id}`, + [ErrorCode.NODE_NOT_FOUND]: (id: string | null) => `Node not found\nNode id: ${id}`, + [ErrorCode.NODE_MISSING_PARENT]: (id: string, parentId: string) => + `Node is missing a parent\nNode id: ${id}\nParent id: ${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.NODE_EXTENT_INVALID]: (id: string) => `Only child nodes can use a parent extent\nNode id: ${id}`, + [ErrorCode.EDGE_INVALID]: (id: string) => `An edge needs a source and a target\nEdge id: ${id}`, + [ErrorCode.EDGE_SOURCE_MISSING]: (id: string, source: string) => + `Edge source is missing\nEdge id: ${id} \nSource id: ${source}`, + [ErrorCode.EDGE_TARGET_MISSING]: (id: string, target: string) => + `Edge target is missing\nEdge id: ${id} \nTarget id: ${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}`, + `Edge source and target are the same\nEdge id: ${id} \nSource id: ${source} \nTarget id: ${target}`, [ErrorCode.EDGE_SOURCE_TARGET_MISSING]: (id: string, source: string, target: string) => - `Edge source or target is missing\nEdge: ${id} \nSource: ${source} \nTarget: ${target}`, + `Edge source or target is missing\nEdge id: ${id} \nSource id: ${source} \nTarget id: ${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}`, + `Edge was orphaned (suddenly missing source or target) and has been removed\nEdge id: ${id}`, + [ErrorCode.EDGE_NOT_FOUND]: (id: string) => `Edge not found\nEdge id: ${id}`, // deprecation errors [ErrorCode.USEVUEFLOW_OPTIONS]: () => diff --git a/packages/core/src/utils/general.ts b/packages/core/src/utils/general.ts index 5bc728af9..e26dab6af 100644 --- a/packages/core/src/utils/general.ts +++ b/packages/core/src/utils/general.ts @@ -1,27 +1,4 @@ -import type { GraphNode, SnapGrid, XYPosition } from '../types' -import type { UseDragEvent } from '../composables' - -export function isMouseEvent(event: MouseEvent | TouchEvent): event is MouseEvent { - return 'clientX' in event -} - -export function isUseDragEvent(event: any): event is UseDragEvent { - return 'sourceEvent' in event -} - -export function getEventPosition(event: MouseEvent | TouchEvent, bounds?: DOMRect) { - const isMouse = isMouseEvent(event) - - const evtX = isMouse ? event.clientX : event.touches?.[0].clientX - const evtY = isMouse ? event.clientY : event.touches?.[0].clientY - - return { - x: evtX - (bounds?.left ?? 0), - y: evtY - (bounds?.top ?? 0), - } -} - -export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0 +import type { GraphNode } from '../types' export function getNodeDimensions(node: GraphNode): { width: number; height: number } { return { @@ -29,10 +6,3 @@ export function getNodeDimensions(node: GraphNode): { width: number; height: num height: node.dimensions?.height ?? node.height ?? 0, } } - -export function snapPosition(position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition { - return { - x: snapGrid[0] * Math.round(position.x / snapGrid[0]), - y: snapGrid[1] * Math.round(position.y / snapGrid[1]), - } -} diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 51232e09e..6c419b1c0 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -1,13 +1,12 @@ import { markRaw } from 'vue' +import type { Viewport } from '@xyflow/system' +import { boxToRect, clamp, getBoundsOfBoxes, getOverlappingArea, rectToBox, snapPosition } from '@xyflow/system' import type { Actions, Box, Connection, - CoordinateExtent, DefaultEdgeOptions, - Dimensions, Edge, - EdgeMarkerType, Element, ElementData, Elements, @@ -18,11 +17,10 @@ import type { Node, Rect, SnapGrid, - ViewportTransform, XYPosition, XYZPosition, } from '../types' -import { isDef, snapPosition } from '.' +import { isDef } from '.' export function nodeToRect(node: GraphNode): Rect { return { @@ -32,41 +30,6 @@ export function nodeToRect(node: GraphNode): Rect { } } -export function getOverlappingArea(rectA: Rect, rectB: Rect) { - const xOverlap = Math.max(0, Math.min(rectA.x + rectA.width, rectB.x + rectB.width) - Math.max(rectA.x, rectB.x)) - const yOverlap = Math.max(0, Math.min(rectA.y + rectA.height, rectB.y + rectB.height) - Math.max(rectA.y, rectB.y)) - - return Math.ceil(xOverlap * yOverlap) -} - -export function getDimensions(node: HTMLElement): Dimensions { - return { - width: node.offsetWidth, - height: node.offsetHeight, - } -} - -export function clamp(val: number, min = 0, max = 1) { - return Math.min(Math.max(val, min), max) -} - -export function clampPosition(position: XYPosition, extent: CoordinateExtent): XYPosition { - return { - x: clamp(position.x, extent[0][0], extent[1][0]), - y: clamp(position.y, extent[0][1], extent[1][1]), - } -} - -export function getHostForElement(element: HTMLElement): Document { - const doc = element.getRootNode() as Document - - if ('elementFromPoint' in doc) { - return doc - } - - return window.document -} - export function isEdge(element: MaybeElement): element is Edge { return element && typeof element === 'object' && 'id' in element && 'source' in element && 'target' in element } @@ -83,14 +46,6 @@ export function isGraphNode(element: MaybeElement): element return isNode(element) && 'computedPosition' in element } -function isNumeric(n: any): n is number { - return !Number.isNaN(n) && Number.isFinite(n) -} - -export function isRect(obj: any): obj is Rect { - return isNumeric(obj.width) && isNumeric(obj.height) && isNumeric(obj.x) && isNumeric(obj.y) -} - export function parseNode(node: Node, existingNode?: GraphNode, parentNode?: string): GraphNode { const initialState = { id: node.id.toString(), @@ -223,15 +178,9 @@ export function connectionExists(edge: Edge | Connection, elements: Elements) { ) } -export function rendererPointToPoint({ x, y }: XYPosition, { x: tx, y: ty, zoom: tScale }: ViewportTransform): XYPosition { - return { - x: x * tScale + tx, - y: y * tScale + ty, - } -} export function pointToRendererPoint( { x, y }: XYPosition, - { x: tx, y: ty, zoom: tScale }: ViewportTransform, + { x: tx, y: ty, zoom: tScale }: Viewport, snapToGrid: boolean = false, snapGrid: SnapGrid = [1, 1], ): XYPosition { @@ -243,38 +192,6 @@ export function pointToRendererPoint( return snapToGrid ? snapPosition(position, snapGrid) : position } -function getBoundsOfBoxes(box1: Box, box2: Box): Box { - return { - x: Math.min(box1.x, box2.x), - y: Math.min(box1.y, box2.y), - x2: Math.max(box1.x2, box2.x2), - y2: Math.max(box1.y2, box2.y2), - } -} - -export function rectToBox({ x, y, width, height }: Rect): Box { - return { - x, - y, - x2: x + width, - y2: y + height, - } -} - -export function boxToRect({ x, y, x2, y2 }: Box): Rect { - return { - x, - y, - width: x2 - x, - height: y2 - y, - } -} - -// todo: fix typo -export function getBoundsofRects(rect1: Rect, rect2: Rect) { - return boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2))) -} - export function getRectOfNodes(nodes: GraphNode[]) { let box: Box = { x: Number.POSITIVE_INFINITY, @@ -300,7 +217,7 @@ export function getRectOfNodes(nodes: GraphNode[]) { export function getNodesInside( nodes: GraphNode[], rect: Rect, - viewport: ViewportTransform = { x: 0, y: 0, zoom: 1 }, + viewport: Viewport = { x: 0, y: 0, zoom: 1 }, partially = false, // set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute excludeNonSelectableNodes = false, @@ -384,7 +301,7 @@ export function getTransformForBounds( x?: number y?: number } = { x: 0, y: 0 }, -): ViewportTransform { +): Viewport { const xZoom = width / (bounds.width * (1 + padding)) const yZoom = height / (bounds.height * (1 + padding)) const zoom = Math.min(xZoom, yZoom) @@ -421,20 +338,3 @@ export function isParentSelected(node: GraphNode, findNode: Actions['findNode']) return isParentSelected(parent, findNode) } - -export function getMarkerId(marker: EdgeMarkerType | undefined, vueFlowId?: string) { - if (typeof marker === 'undefined') { - return '' - } - - if (typeof marker === 'string') { - return marker - } - - const idPrefix = vueFlowId ? `${vueFlowId}__` : '' - - return `${idPrefix}${Object.keys(marker) - .sort() - .map((key) => `${key}=${marker[key]}`) - .join('&')}` -} diff --git a/packages/core/src/utils/handle.ts b/packages/core/src/utils/handle.ts index ac1799217..0e55e7d61 100644 --- a/packages/core/src/utils/handle.ts +++ b/packages/core/src/utils/handle.ts @@ -1,3 +1,4 @@ +import { getEventPosition, getOverlappingArea } from '@xyflow/system' import { ConnectionMode, Position } from '../types' import type { Actions, @@ -14,7 +15,7 @@ import type { Result, XYPosition, } from '../types' -import { getEventPosition, getHandlePosition, getOverlappingArea, nodeToRect } from '.' +import { getHandlePosition, nodeToRect } from '.' const alwaysValid = () => true diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index d1ac0a0b8..8ff246d82 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,5 +1,4 @@ export * from './a11y' -export * from './autopan' export * from './changes' export * from './createExtendedEventHook' export * from './drag' diff --git a/packages/core/src/utils/node.ts b/packages/core/src/utils/node.ts index 2ee8afb53..2a493f8d8 100644 --- a/packages/core/src/utils/node.ts +++ b/packages/core/src/utils/node.ts @@ -1,7 +1,7 @@ import type { Ref } from 'vue' import { nextTick } from 'vue' +import { getDimensions } from '@xyflow/system' import type { Actions, GraphNode, HandleElement, HandleType, Position } from '../types' -import { getDimensions } from '.' export function getHandleBounds( type: HandleType, diff --git a/packages/minimap/package.json b/packages/minimap/package.json index 0e387411b..2b73b3220 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -61,15 +61,12 @@ "vue": "^3.3.0" }, "dependencies": { - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" + "@xyflow/system": "^0.0.39" }, "devDependencies": { "@tooling/eslint-config": "workspace:*", "@tooling/tsconfig": "workspace:*", "@tooling/vite-config": "workspace:*", - "@types/d3-selection": "^3.0.7", - "@types/d3-zoom": "^3.0.5", "@vue-flow/core": "workspace:*", "vue-tsc": "^1.8.16" }, diff --git a/packages/minimap/src/MiniMap.vue b/packages/minimap/src/MiniMap.vue index 14c583504..799976e78 100644 --- a/packages/minimap/src/MiniMap.vue +++ b/packages/minimap/src/MiniMap.vue @@ -1,10 +1,9 @@