Skip to content

Commit f09952d

Browse files
authored
feat(core): add ease option to viewport transform functions (#1848)
* chore(core): update d3-deps Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> * feat(core): add ease option to viewport transform functions Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> * chore(changeset): add * chore: cleanup Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> --------- Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
1 parent b792d7f commit f09952d

File tree

6 files changed

+88
-36
lines changed

6 files changed

+88
-36
lines changed

.changeset/hip-clocks-nail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@vue-flow/core": minor
3+
---
4+
5+
Add `ease` option to viewport altering functions.

packages/core/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@
8080
"@rollup/plugin-replace": "^5.0.3",
8181
"@tooling/eslint-config": "workspace:*",
8282
"@tooling/tsconfig": "workspace:*",
83-
"@types/d3-drag": "^3.0.4",
84-
"@types/d3-selection": "^3.0.7",
85-
"@types/d3-zoom": "^3.0.5",
83+
"@types/d3-drag": "^3.0.7",
84+
"@types/d3-selection": "^3.0.11",
85+
"@types/d3-transition": "^3.0.9",
86+
"@types/d3-zoom": "^3.0.8",
8687
"@vitejs/plugin-vue": "^4.4.0",
8788
"autoprefixer": "^10.4.16",
8889
"postcss": "^8.4.31",

packages/core/src/composables/useViewportHelper.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { zoomIdentity } from 'd3-zoom'
22
import { computed } from 'vue'
3-
import type { D3Selection, GraphNode, Project, State, ViewportFunctions } from '../types'
3+
import type { D3Selection, GraphNode, Project, State, TransitionOptions, ViewportFunctions } from '../types'
44
import { clampPosition, getRectOfNodes, getTransformForBounds, pointToRendererPoint, rendererPointToPoint, warn } from '../utils'
55

66
export interface ViewportHelper extends ViewportFunctions {
@@ -11,6 +11,10 @@ export interface ViewportHelper extends ViewportFunctions {
1111

1212
const DEFAULT_PADDING = 0.1
1313

14+
// taken from d3-ease: https://github.com/d3/d3-ease/blob/main/src/cubic.js
15+
// eslint-disable-next-line no-cond-assign
16+
const defaultEase = (t: number) => ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2
17+
1418
function noop() {
1519
warn('Viewport not initialized yet.')
1620

@@ -41,11 +45,11 @@ const initialViewportHelper: ViewportHelper = {
4145
* @param state
4246
*/
4347
export function useViewportHelper(state: State) {
44-
function zoom(scale: number, duration?: number) {
48+
function zoom(scale: number, transitionOptions?: TransitionOptions) {
4549
return new Promise<boolean>((resolve) => {
4650
if (state.d3Selection && state.d3Zoom) {
4751
state.d3Zoom.scaleBy(
48-
transition(state.d3Selection, duration, () => {
52+
getD3Transition(state.d3Selection, transitionOptions?.duration, transitionOptions?.ease, () => {
4953
resolve(true)
5054
}),
5155
scale,
@@ -56,7 +60,7 @@ export function useViewportHelper(state: State) {
5660
})
5761
}
5862

59-
function transformViewport(x: number, y: number, zoom: number, duration?: number) {
63+
function transformViewport(x: number, y: number, zoom: number, transitionOptions?: TransitionOptions) {
6064
return new Promise<boolean>((resolve) => {
6165
// enforce translate extent
6266
const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, state.translateExtent)
@@ -65,7 +69,7 @@ export function useViewportHelper(state: State) {
6569

6670
if (state.d3Selection && state.d3Zoom) {
6771
state.d3Zoom.transform(
68-
transition(state.d3Selection, duration, () => {
72+
getD3Transition(state.d3Selection, transitionOptions?.duration, transitionOptions?.ease, () => {
6973
resolve(true)
7074
}),
7175
nextTransform,
@@ -87,16 +91,16 @@ export function useViewportHelper(state: State) {
8791
viewportInitialized: true,
8892
// todo: allow passing scale as option
8993
zoomIn: (options) => {
90-
return zoom(1.2, options?.duration)
94+
return zoom(1.2, options)
9195
},
9296
zoomOut: (options) => {
93-
return zoom(1 / 1.2, options?.duration)
97+
return zoom(1 / 1.2, options)
9498
},
9599
zoomTo: (zoomLevel, options) => {
96100
return new Promise<boolean>((resolve) => {
97101
if (state.d3Selection && state.d3Zoom) {
98102
state.d3Zoom.scaleTo(
99-
transition(state.d3Selection, options?.duration, () => {
103+
getD3Transition(state.d3Selection, options?.duration, options?.ease, () => {
100104
resolve(true)
101105
}),
102106
zoomLevel,
@@ -107,10 +111,10 @@ export function useViewportHelper(state: State) {
107111
})
108112
},
109113
setViewport: (transform, options) => {
110-
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
114+
return transformViewport(transform.x, transform.y, transform.zoom, options)
111115
},
112116
setTransform: (transform, options) => {
113-
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
117+
return transformViewport(transform.x, transform.y, transform.zoom, options)
114118
},
115119
getViewport: () => ({
116120
x: state.viewport.x,
@@ -158,14 +162,14 @@ export function useViewportHelper(state: State) {
158162
options.offset,
159163
)
160164

161-
return transformViewport(x, y, zoom, options?.duration)
165+
return transformViewport(x, y, zoom, options)
162166
},
163167
setCenter: (x, y, options) => {
164168
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : state.maxZoom
165169
const centerX = state.dimensions.width / 2 - x * nextZoom
166170
const centerY = state.dimensions.height / 2 - y * nextZoom
167171

168-
return transformViewport(centerX, centerY, nextZoom, options?.duration)
172+
return transformViewport(centerX, centerY, nextZoom, options)
169173
},
170174
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
171175
const { x, y, zoom } = getTransformForBounds(
@@ -177,7 +181,7 @@ export function useViewportHelper(state: State) {
177181
options.padding,
178182
)
179183

180-
return transformViewport(x, y, zoom, options?.duration)
184+
return transformViewport(x, y, zoom, options)
181185
},
182186
project: (position) => pointToRendererPoint(position, state.viewport, state.snapToGrid, state.snapGrid),
183187
screenToFlowCoordinate: (position) => {
@@ -212,6 +216,12 @@ export function useViewportHelper(state: State) {
212216
})
213217
}
214218

215-
function transition(selection: D3Selection, ms = 0, onEnd: () => void) {
216-
return (selection as any).transition().duration(ms).on('end', onEnd)
219+
export function getD3Transition(selection: D3Selection, duration = 0, ease = defaultEase, onEnd = () => {}) {
220+
const hasDuration = typeof duration === 'number' && duration > 0
221+
222+
if (!hasDuration) {
223+
onEnd()
224+
}
225+
226+
return hasDuration ? selection.transition().duration(duration).ease(ease).on('end', onEnd) : selection
217227
}

packages/core/src/types/zoom.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import type { Selection } from 'd3-selection'
2+
3+
// eslint-disable-next-line unused-imports/no-unused-imports,@typescript-eslint/no-unused-vars -- this is needed for the Selection type to include the transition function :/
4+
import type { Transition } from 'd3-transition'
25
import type { ZoomBehavior } from 'd3-zoom'
6+
37
import type { Rect, XYPosition } from './flow'
48

59
export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
6-
export type D3Selection = Selection<HTMLDivElement, any, any, any>
10+
export type D3Selection = Selection<HTMLDivElement, unknown, null, undefined>
711
export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void
812

913
export enum PanOnScrollMode {
@@ -14,6 +18,7 @@ export enum PanOnScrollMode {
1418

1519
export interface TransitionOptions {
1620
duration?: number
21+
ease?: (t: number) => number
1722
}
1823

1924
export type FitViewParams = {

packages/core/tsconfig.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"baseUrl": ".",
55
"declarationDir": "./dist",
66
"resolveJsonModule": true,
7-
"types": [
8-
"vite/client",
9-
"vue/macros"
10-
]
117
},
128
"include": [
139
"./src"
1410
],
11+
"exclude": [
12+
"node_modules",
13+
"dist",
14+
],
1515
"references": [
1616
{
1717
"path": "./tsconfig.node.json"

pnpm-lock.yaml

Lines changed: 44 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)