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>
This commit is contained in:
5
.changeset/hip-clocks-nail.md
Normal file
5
.changeset/hip-clocks-nail.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@vue-flow/core": minor
|
||||
---
|
||||
|
||||
Add `ease` option to viewport altering functions.
|
||||
@@ -80,9 +80,10 @@
|
||||
"@rollup/plugin-replace": "^5.0.3",
|
||||
"@tooling/eslint-config": "workspace:*",
|
||||
"@tooling/tsconfig": "workspace:*",
|
||||
"@types/d3-drag": "^3.0.4",
|
||||
"@types/d3-selection": "^3.0.7",
|
||||
"@types/d3-zoom": "^3.0.5",
|
||||
"@types/d3-drag": "^3.0.7",
|
||||
"@types/d3-selection": "^3.0.11",
|
||||
"@types/d3-transition": "^3.0.9",
|
||||
"@types/d3-zoom": "^3.0.8",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"postcss": "^8.4.31",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { zoomIdentity } from 'd3-zoom'
|
||||
import { computed } from 'vue'
|
||||
import type { D3Selection, GraphNode, Project, State, ViewportFunctions } from '../types'
|
||||
import type { D3Selection, GraphNode, Project, State, TransitionOptions, ViewportFunctions } from '../types'
|
||||
import { clampPosition, getRectOfNodes, getTransformForBounds, pointToRendererPoint, rendererPointToPoint, warn } from '../utils'
|
||||
|
||||
export interface ViewportHelper extends ViewportFunctions {
|
||||
@@ -11,6 +11,10 @@ export interface ViewportHelper extends ViewportFunctions {
|
||||
|
||||
const DEFAULT_PADDING = 0.1
|
||||
|
||||
// taken from d3-ease: https://github.com/d3/d3-ease/blob/main/src/cubic.js
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
const defaultEase = (t: number) => ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2
|
||||
|
||||
function noop() {
|
||||
warn('Viewport not initialized yet.')
|
||||
|
||||
@@ -41,11 +45,11 @@ const initialViewportHelper: ViewportHelper = {
|
||||
* @param state
|
||||
*/
|
||||
export function useViewportHelper(state: State) {
|
||||
function zoom(scale: number, duration?: number) {
|
||||
function zoom(scale: number, transitionOptions?: TransitionOptions) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
if (state.d3Selection && state.d3Zoom) {
|
||||
state.d3Zoom.scaleBy(
|
||||
transition(state.d3Selection, duration, () => {
|
||||
getD3Transition(state.d3Selection, transitionOptions?.duration, transitionOptions?.ease, () => {
|
||||
resolve(true)
|
||||
}),
|
||||
scale,
|
||||
@@ -56,7 +60,7 @@ export function useViewportHelper(state: State) {
|
||||
})
|
||||
}
|
||||
|
||||
function transformViewport(x: number, y: number, zoom: number, duration?: number) {
|
||||
function transformViewport(x: number, y: number, zoom: number, transitionOptions?: TransitionOptions) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
// enforce translate extent
|
||||
const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, state.translateExtent)
|
||||
@@ -65,7 +69,7 @@ export function useViewportHelper(state: State) {
|
||||
|
||||
if (state.d3Selection && state.d3Zoom) {
|
||||
state.d3Zoom.transform(
|
||||
transition(state.d3Selection, duration, () => {
|
||||
getD3Transition(state.d3Selection, transitionOptions?.duration, transitionOptions?.ease, () => {
|
||||
resolve(true)
|
||||
}),
|
||||
nextTransform,
|
||||
@@ -87,16 +91,16 @@ export function useViewportHelper(state: State) {
|
||||
viewportInitialized: true,
|
||||
// todo: allow passing scale as option
|
||||
zoomIn: (options) => {
|
||||
return zoom(1.2, options?.duration)
|
||||
return zoom(1.2, options)
|
||||
},
|
||||
zoomOut: (options) => {
|
||||
return zoom(1 / 1.2, options?.duration)
|
||||
return zoom(1 / 1.2, options)
|
||||
},
|
||||
zoomTo: (zoomLevel, options) => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
if (state.d3Selection && state.d3Zoom) {
|
||||
state.d3Zoom.scaleTo(
|
||||
transition(state.d3Selection, options?.duration, () => {
|
||||
getD3Transition(state.d3Selection, options?.duration, options?.ease, () => {
|
||||
resolve(true)
|
||||
}),
|
||||
zoomLevel,
|
||||
@@ -107,10 +111,10 @@ export function useViewportHelper(state: State) {
|
||||
})
|
||||
},
|
||||
setViewport: (transform, options) => {
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options)
|
||||
},
|
||||
setTransform: (transform, options) => {
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options)
|
||||
},
|
||||
getViewport: () => ({
|
||||
x: state.viewport.x,
|
||||
@@ -158,14 +162,14 @@ export function useViewportHelper(state: State) {
|
||||
options.offset,
|
||||
)
|
||||
|
||||
return transformViewport(x, y, zoom, options?.duration)
|
||||
return transformViewport(x, y, zoom, options)
|
||||
},
|
||||
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
|
||||
|
||||
return transformViewport(centerX, centerY, nextZoom, options?.duration)
|
||||
return transformViewport(centerX, centerY, nextZoom, options)
|
||||
},
|
||||
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
|
||||
const { x, y, zoom } = getTransformForBounds(
|
||||
@@ -177,7 +181,7 @@ export function useViewportHelper(state: State) {
|
||||
options.padding,
|
||||
)
|
||||
|
||||
return transformViewport(x, y, zoom, options?.duration)
|
||||
return transformViewport(x, y, zoom, options)
|
||||
},
|
||||
project: (position) => pointToRendererPoint(position, state.viewport, state.snapToGrid, state.snapGrid),
|
||||
screenToFlowCoordinate: (position) => {
|
||||
@@ -212,6 +216,12 @@ export function useViewportHelper(state: State) {
|
||||
})
|
||||
}
|
||||
|
||||
function transition(selection: D3Selection, ms = 0, onEnd: () => void) {
|
||||
return (selection as any).transition().duration(ms).on('end', onEnd)
|
||||
export function getD3Transition(selection: D3Selection, duration = 0, ease = defaultEase, onEnd = () => {}) {
|
||||
const hasDuration = typeof duration === 'number' && duration > 0
|
||||
|
||||
if (!hasDuration) {
|
||||
onEnd()
|
||||
}
|
||||
|
||||
return hasDuration ? selection.transition().duration(duration).ease(ease).on('end', onEnd) : selection
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { Selection } from 'd3-selection'
|
||||
|
||||
// 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 :/
|
||||
import type { Transition } from 'd3-transition'
|
||||
import type { ZoomBehavior } from 'd3-zoom'
|
||||
|
||||
import type { Rect, XYPosition } from './flow'
|
||||
|
||||
export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
|
||||
export type D3Selection = Selection<HTMLDivElement, any, any, any>
|
||||
export type D3Selection = Selection<HTMLDivElement, unknown, null, undefined>
|
||||
export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void
|
||||
|
||||
export enum PanOnScrollMode {
|
||||
@@ -14,6 +18,7 @@ export enum PanOnScrollMode {
|
||||
|
||||
export interface TransitionOptions {
|
||||
duration?: number
|
||||
ease?: (t: number) => number
|
||||
}
|
||||
|
||||
export type FitViewParams = {
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "./dist",
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"vite/client",
|
||||
"vue/macros"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
|
||||
57
pnpm-lock.yaml
generated
57
pnpm-lock.yaml
generated
@@ -95,13 +95,13 @@ importers:
|
||||
version: 6.0.3(typedoc@0.26.11(typescript@5.4.5))
|
||||
unplugin-auto-import:
|
||||
specifier: ^0.18.3
|
||||
version: 0.18.3(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.12(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3)
|
||||
version: 0.18.3(@nuxt/kit@3.13.2(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.12(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3)
|
||||
unplugin-icons:
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0(@vue/compiler-sfc@3.5.12)(vue-template-compiler@2.7.14)(webpack-sources@3.2.3)
|
||||
unplugin-vue-components:
|
||||
specifier: ^0.27.4
|
||||
version: 0.27.4(@babel/parser@7.25.7)(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.21.0)(webpack-sources@3.2.3))(rollup@4.21.0)(vue@3.5.12(typescript@5.4.5))
|
||||
version: 0.27.4(@babel/parser@7.25.7)(@nuxt/kit@3.13.2(rollup@4.21.0)(webpack-sources@3.2.3))(rollup@4.21.0)(vue@3.5.12(typescript@5.4.5))
|
||||
vite-plugin-windicss:
|
||||
specifier: ^1.9.3
|
||||
version: 1.9.3(vite@5.4.10(@types/node@20.14.2)(sass@1.79.4)(terser@5.21.0))
|
||||
@@ -221,7 +221,7 @@ importers:
|
||||
version: 5.1.4(vite@5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.21.0))(vue@3.5.11(typescript@5.4.5))
|
||||
unplugin-auto-import:
|
||||
specifier: ^0.18.3
|
||||
version: 0.18.3(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.11(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3)
|
||||
version: 0.18.3(@nuxt/kit@3.13.2(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.11(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3)
|
||||
vite:
|
||||
specifier: ^5.4.8
|
||||
version: 5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.21.0)
|
||||
@@ -313,14 +313,17 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/tsconfig
|
||||
'@types/d3-drag':
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
'@types/d3-selection':
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7
|
||||
'@types/d3-selection':
|
||||
specifier: ^3.0.11
|
||||
version: 3.0.11
|
||||
'@types/d3-transition':
|
||||
specifier: ^3.0.9
|
||||
version: 3.0.9
|
||||
'@types/d3-zoom':
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.8
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0(vite@4.4.11(@types/node@20.14.2)(sass@1.79.4)(terser@5.21.0))(vue@3.3.4)
|
||||
@@ -2200,15 +2203,27 @@ packages:
|
||||
'@types/d3-drag@3.0.4':
|
||||
resolution: {integrity: sha512-/t53K1erTuUbP7WIX9SE0hlmytpTYRbIthlhbGkBHzCV5vPO++7yrk8OlisWPyIJO5TGowTmqCtGH2tokY5T/g==}
|
||||
|
||||
'@types/d3-drag@3.0.7':
|
||||
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
|
||||
|
||||
'@types/d3-interpolate@3.0.2':
|
||||
resolution: {integrity: sha512-zAbCj9lTqW9J9PlF4FwnvEjXZUy75NQqPm7DMHZXuxCFTpuTrdK2NMYGQekf4hlasL78fCYOLu4EE3/tXElwow==}
|
||||
|
||||
'@types/d3-selection@3.0.11':
|
||||
resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
|
||||
|
||||
'@types/d3-selection@3.0.7':
|
||||
resolution: {integrity: sha512-qoj2O7KjfqCobmtFOth8FMvjwMVPUAAmn6xiUbLl1ld7vQCPgffvyV5BBcEFfqWdilAUm+3zciU/3P3vZrUMlg==}
|
||||
|
||||
'@types/d3-transition@3.0.9':
|
||||
resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
|
||||
|
||||
'@types/d3-zoom@3.0.5':
|
||||
resolution: {integrity: sha512-mIefdTLtxuWUWTbBupCUXPAXVPmi8/Uwrq41gQpRh0rD25GMU1ku+oTELqNY2NuuiI0F3wXC5e1liBQi7YS7XQ==}
|
||||
|
||||
'@types/d3-zoom@3.0.8':
|
||||
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
|
||||
|
||||
'@types/estree@1.0.2':
|
||||
resolution: {integrity: sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==}
|
||||
|
||||
@@ -4073,6 +4088,7 @@ packages:
|
||||
eslint-plugin-i@2.28.1:
|
||||
resolution: {integrity: sha512-a4oVt0j3ixNhGhvV4XF6NS7OWRFK2rrJ0Q5C4S2dSRb8FxZi31J0uUd5WJLL58wnVJ/OiQ1BxiXnFA4dWQO1Cg==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Please migrate to the brand new `eslint-plugin-import-x` instead
|
||||
peerDependencies:
|
||||
eslint: ^7.2.0 || ^8
|
||||
|
||||
@@ -9541,18 +9557,33 @@ snapshots:
|
||||
|
||||
'@types/d3-drag@3.0.4':
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.7
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/d3-drag@3.0.7':
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/d3-interpolate@3.0.2':
|
||||
dependencies:
|
||||
'@types/d3-color': 3.1.1
|
||||
|
||||
'@types/d3-selection@3.0.11': {}
|
||||
|
||||
'@types/d3-selection@3.0.7': {}
|
||||
|
||||
'@types/d3-transition@3.0.9':
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/d3-zoom@3.0.5':
|
||||
dependencies:
|
||||
'@types/d3-interpolate': 3.0.2
|
||||
'@types/d3-selection': 3.0.7
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/d3-zoom@3.0.8':
|
||||
dependencies:
|
||||
'@types/d3-interpolate': 3.0.2
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/estree@1.0.2': {}
|
||||
|
||||
@@ -15073,7 +15104,7 @@ snapshots:
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
unplugin-auto-import@0.18.3(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.11(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3):
|
||||
unplugin-auto-import@0.18.3(@nuxt/kit@3.13.2(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.11(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3):
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.10
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
|
||||
@@ -15090,7 +15121,7 @@ snapshots:
|
||||
- rollup
|
||||
- webpack-sources
|
||||
|
||||
unplugin-auto-import@0.18.3(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.12(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3):
|
||||
unplugin-auto-import@0.18.3(@nuxt/kit@3.13.2(rollup@4.21.0)(webpack-sources@3.2.3))(@vueuse/core@11.2.0(vue@3.5.12(typescript@5.4.5)))(rollup@4.21.0)(webpack-sources@3.2.3):
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.10
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
|
||||
@@ -15123,7 +15154,7 @@ snapshots:
|
||||
- supports-color
|
||||
- webpack-sources
|
||||
|
||||
unplugin-vue-components@0.27.4(@babel/parser@7.25.7)(@nuxt/kit@3.13.2(magicast@0.3.5)(rollup@4.21.0)(webpack-sources@3.2.3))(rollup@4.21.0)(vue@3.5.12(typescript@5.4.5)):
|
||||
unplugin-vue-components@0.27.4(@babel/parser@7.25.7)(@nuxt/kit@3.13.2(rollup@4.21.0)(webpack-sources@3.2.3))(rollup@4.21.0)(vue@3.5.12(typescript@5.4.5)):
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.10
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
|
||||
|
||||
Reference in New Issue
Block a user