refactor(flow!): Change transform to viewport
* Change array type to object type and rename transform type to viewport * Update examples & docs
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"build": "vuepress build src"
|
"build": "vuepress build src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braks/vue-flow": "^0.4.2-0",
|
"@braks/vue-flow": "^0.4.2-3",
|
||||||
"blobity": "^0.1.7"
|
"blobity": "^0.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -331,9 +331,9 @@ const edges = ref([
|
|||||||
|
|
||||||
- Details:
|
- Details:
|
||||||
|
|
||||||
Trigger fit view when transformation-pane is mounted.
|
Trigger fit view when viewport is mounted.
|
||||||
|
|
||||||
## Zoom & Pan Options
|
## Viewport Options
|
||||||
|
|
||||||
### zoom-activation-key-code <Badge class="text-white" style="line-height: inherit" text="optional" vertical="top" />
|
### zoom-activation-key-code <Badge class="text-white" style="line-height: inherit" text="optional" vertical="top" />
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@ const edges = ref([
|
|||||||
|
|
||||||
- Details:
|
- Details:
|
||||||
|
|
||||||
Default zoom pane position on initial load.
|
Default viewport position on initial load.
|
||||||
|
|
||||||
### translate-extent <Badge class="text-white" style="line-height: inherit" text="optional" vertical="top" />
|
### translate-extent <Badge class="text-white" style="line-height: inherit" text="optional" vertical="top" />
|
||||||
|
|
||||||
@@ -492,7 +492,7 @@ const edges = ref([
|
|||||||
|
|
||||||
- Details:
|
- Details:
|
||||||
|
|
||||||
The area in which the zoom pane can be moved around.
|
The area in which the viewport can be moved around.
|
||||||
|
|
||||||
## Selection Pane Options
|
## Selection Pane Options
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ title: Introduction
|
|||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
Vue Flow is a library for building node-based applications. These can be simple static diagrams or complex node-based
|
Vue Flow is a library for building node-based applications. These can be simple static diagrams or complex node-based
|
||||||
editors. You can implement custom nodes and edges. It also comes with components like a mini-map, zoom pane controls and
|
editors. You can implement custom nodes and edges. It also comes with components like a mini-map, viewport controls and
|
||||||
a background.
|
a background.
|
||||||
|
|
||||||
Check out the [examples](/examples/) if you want to dive directly into the code.
|
Check out the [examples](/examples/) if you want to dive directly into the code.
|
||||||
|
|||||||
@@ -1,349 +0,0 @@
|
|||||||
# Utilities
|
|
||||||
|
|
||||||
Vue Flow comes with built-in utilities to control the zoom-pane, state and more.
|
|
||||||
|
|
||||||
## Graph Utilities
|
|
||||||
|
|
||||||
### isEdge
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Confirms if an element is an edge.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```vue:no-line-numbers{13}
|
|
||||||
<script setup>
|
|
||||||
import { VueFlow, isEdge } from '@braks/vue-flow'
|
|
||||||
|
|
||||||
const elements = ref([
|
|
||||||
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, },
|
|
||||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, },
|
|
||||||
|
|
||||||
{ id: 'e1-2', source: '1', target: '2', class: 'light' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const toggleClass = () => {
|
|
||||||
elements.value.forEach((el) => {
|
|
||||||
if (isEdge(el)) {
|
|
||||||
el.class = el.class === 'light' ? 'dark' : 'light'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<VueFlow v-model="elements">
|
|
||||||
<button style="position: absolute; top: 10px; right: 10px;" @click="toggleClass">Toggle classes</button>
|
|
||||||
</VueFlow>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### isNode
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Confirms if an element is a node.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```vue:no-line-numbers{13}
|
|
||||||
<script setup>
|
|
||||||
import { VueFlow, isNode } from '@braks/vue-flow'
|
|
||||||
|
|
||||||
const elements = ref([
|
|
||||||
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
|
|
||||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
|
|
||||||
|
|
||||||
{ id: 'e1-2', source: '1', target: '2' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const toggleClass = () => {
|
|
||||||
elements.value.forEach((el) => {
|
|
||||||
if (isNode(el)) {
|
|
||||||
el.class = el.class === 'light' ? 'dark' : 'light'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<VueFlow v-model="elements">
|
|
||||||
<button style="position: absolute; top: 10px; right: 10px;" @click="toggleClass">Toggle classes</button>
|
|
||||||
</VueFlow>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### addEdge
|
|
||||||
|
|
||||||
::: warning
|
|
||||||
When using composition you should access `addEdges` from `useVueFlow`
|
|
||||||
:::
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Confirms if an element is a node.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```vue:no-line-numbers{12}
|
|
||||||
<script setup>
|
|
||||||
import { VueFlow, addEdge } from '@braks/vue-flow'
|
|
||||||
|
|
||||||
const elements = ref([
|
|
||||||
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
|
|
||||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
|
|
||||||
|
|
||||||
{ id: 'e1-2', source: '1', target: '2' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const onConnect = (params) => {
|
|
||||||
addEdge(params, elements.value)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<VueFlow v-model="elements" @connect="onConnect" />
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### updateEdge
|
|
||||||
|
|
||||||
::: warning
|
|
||||||
When using composition you should access `updateEdge` from `useVueFlow`
|
|
||||||
:::
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Updates an edge and applies new source/target.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```vue:no-line-numbers{12}
|
|
||||||
<script setup>
|
|
||||||
import { VueFlow, updateEdge } from '@braks/vue-flow'
|
|
||||||
|
|
||||||
const elements = ref([
|
|
||||||
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
|
|
||||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
|
|
||||||
|
|
||||||
{ id: 'e1-2', source: '1', target: '2' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const onEdgeUpdate = ({ edge, connection }) => {
|
|
||||||
elements.value = updateEdge(edge, connection, elements.value)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<VueFlow v-model="elements" @edge-update="onEdgeUpdate" />
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### [getOutgoers](https://types.vueflow.dev/modules.html#getOutgoers)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns all target elements of a node.
|
|
||||||
|
|
||||||
### [getIncomers](https://types.vueflow.dev/modules.html#getIncomers)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns all source elements of a node.
|
|
||||||
|
|
||||||
### [getConnectedEdges](https://types.vueflow.dev/modules.html#getConnectedEdges)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns all connected edges of a node.
|
|
||||||
|
|
||||||
### [getTransformForBounds](https://types.vueflow.dev/modules.html#getTransformForBounds)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns a transformation for the zoom pane according to input bounds.
|
|
||||||
|
|
||||||
### [getRectOfNodes](https://types.vueflow.dev/modules.html#getRectOfNodes)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns a rect of node elements.
|
|
||||||
|
|
||||||
Useful when you need to know the boundaries of a set of nodes.
|
|
||||||
|
|
||||||
### [getNodesInside](https://types.vueflow.dev/modules.html#getNodesInside)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns node elements that are inside a specified rect.
|
|
||||||
|
|
||||||
### [getMarkerId](https://types.vueflow.dev/modules.html#getMarkerId)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns a marker id for a marker definition.
|
|
||||||
|
|
||||||
## Instance
|
|
||||||
|
|
||||||
The Vue Flow instance provides easy access to zoom-pan-helper functions.
|
|
||||||
It can be accessed either directly from the state using `useVueFlow` or
|
|
||||||
you can receive the instance with a `onPaneReady` event handler.
|
|
||||||
|
|
||||||
|
|
||||||
<CodeGroup>
|
|
||||||
<CodeGroupItem title="Composition API" active>
|
|
||||||
|
|
||||||
```vue:no-line-numbers
|
|
||||||
<script setup>
|
|
||||||
import { VueFlow, useVueFlow } from '@braks/vue-flow'
|
|
||||||
|
|
||||||
const { onPaneReady, instance } = useVueFlow()
|
|
||||||
|
|
||||||
// event handler
|
|
||||||
onPaneReady((instance) => instance.fitView())
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// or directly try to access the instance
|
|
||||||
instance.value?.fitView()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
</CodeGroupItem>
|
|
||||||
|
|
||||||
|
|
||||||
<CodeGroupItem title="Options API">
|
|
||||||
|
|
||||||
```vue:no-line-numbers
|
|
||||||
<script>
|
|
||||||
import { VueFlow } from '@braks/vue-flow'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { VueFlow },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
instance: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onPaneReady(vueFlowInstance) {
|
|
||||||
vueFlowInstance.fitView()
|
|
||||||
this.instance = vueFlowInstance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<VueFlow @pane-ready="onPaneReady" />
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
</CodeGroupItem>
|
|
||||||
</CodeGroup>
|
|
||||||
|
|
||||||
### [project](https://types.vueflow.dev/modules.html#Project)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Transforms pixel coordinates to the internal VueFlow coordinate system.
|
|
||||||
|
|
||||||
This can be used when you drag nodes (from a sidebar for example) and need the internal position on the pane.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```ts:no-line-numbers
|
|
||||||
vueFlowInstance.project({ x: 100, y: 100 })
|
|
||||||
```
|
|
||||||
|
|
||||||
### [fitView](https://types.vueflow.dev/modules.html#FitView)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Fits the view port so that all nodes are visible.
|
|
||||||
|
|
||||||
Padding is 0.1 and includeHiddenNodes is false by default.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```ts:no-line-numbers
|
|
||||||
vueFlowInstance.fitView({ padding: 0.25, includeHiddenNodes: true })
|
|
||||||
```
|
|
||||||
|
|
||||||
### [fitBounds](https://types.vueflow.dev/modules.html#FitBounds)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Fits the view port according to the bounds' rect input.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```ts:no-line-numbers
|
|
||||||
vueFlowInstance.fitBounds(getRectOfNodes(nodes.value))
|
|
||||||
```
|
|
||||||
|
|
||||||
### [setTransform](https://types.vueflow.dev/modules.html#SetTransform)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Sets position and zoom of the pane.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```ts:no-line-numbers
|
|
||||||
vueFlowInstance.setTransform({ x: 100, y: 100, zoom: 1.5 })
|
|
||||||
```
|
|
||||||
|
|
||||||
### [getTransform](https://types.vueflow.dev/modules.html#GetTransform)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Gets position and zoom of the pane.
|
|
||||||
|
|
||||||
### [zoomIn](https://types.vueflow.dev/modules.html#ZoomInOut)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Zooms in.
|
|
||||||
|
|
||||||
|
|
||||||
### [zoomOut](https://types.vueflow.dev/modules.html#ZoomInOut)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Zooms out.
|
|
||||||
|
|
||||||
### [zoomTo](https://types.vueflow.dev/modules.html#ZoomTo)
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Zooms to specific level.
|
|
||||||
|
|
||||||
### getElements
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns currently stored elements (nodes + edges).
|
|
||||||
|
|
||||||
### getNodes
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns currently stored nodes.
|
|
||||||
|
|
||||||
### getEdges
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns currently stored edges.
|
|
||||||
|
|
||||||
### toObject
|
|
||||||
|
|
||||||
- Details:
|
|
||||||
|
|
||||||
Returns elements, position and zoom of the current flow state.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```ts:no-line-numbers
|
|
||||||
toObject = (): {
|
|
||||||
elements: FlowElements,
|
|
||||||
position: [x, y],
|
|
||||||
zoom: scale,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -154,7 +154,7 @@ const onEdgeUpdate = ({ edge, connection }) => {
|
|||||||
|
|
||||||
- Details:
|
- Details:
|
||||||
|
|
||||||
Returns a transformation for the zoom pane according to input bounds.
|
Returns a transformation for the viewport according to input bounds.
|
||||||
|
|
||||||
### getRectOfNodes
|
### getRectOfNodes
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"serve": "vite serve"
|
"serve": "vite serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braks/vue-flow": "^0.4.2-0"
|
"@braks/vue-flow": "^0.4.2-3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dagre": "^0.7.46",
|
"@types/dagre": "^0.7.46",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Handle, Position, getNodesInside, useVueFlow } from '@braks/vue-flow'
|
|||||||
import type { NodeProps } from '@braks/vue-flow'
|
import type { NodeProps } from '@braks/vue-flow'
|
||||||
|
|
||||||
const props = defineProps<NodeProps>()
|
const props = defineProps<NodeProps>()
|
||||||
const { onNodeDragStop, getNodes, transform } = useVueFlow()
|
const { onNodeDragStop, getNodes, viewport } = useVueFlow()
|
||||||
onNodeDragStop(({ node }) => {
|
onNodeDragStop(({ node }) => {
|
||||||
const nodes = getNodesInside(
|
const nodes = getNodesInside(
|
||||||
getNodes.value,
|
getNodes.value,
|
||||||
@@ -12,7 +12,7 @@ onNodeDragStop(({ node }) => {
|
|||||||
x: props.computedPosition.x,
|
x: props.computedPosition.x,
|
||||||
y: props.computedPosition.y,
|
y: props.computedPosition.y,
|
||||||
},
|
},
|
||||||
transform.value,
|
viewport.value,
|
||||||
)
|
)
|
||||||
if (nodes.some((n) => n.id === node.id && n.id !== props.id)) {
|
if (nodes.some((n) => n.id === node.id && n.id !== props.id)) {
|
||||||
node.label = `In ${props.id}`
|
node.label = `In ${props.id}`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useVueFlow } from '@braks/vue-flow'
|
import { useVueFlow } from '@braks/vue-flow'
|
||||||
const { nodesSelectionActive, addSelectedNodes, getNodes, transform } = useVueFlow()
|
const { nodesSelectionActive, addSelectedNodes, getNodes, viewport } = useVueFlow()
|
||||||
|
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
addSelectedNodes(getNodes.value)
|
addSelectedNodes(getNodes.value)
|
||||||
@@ -14,7 +14,7 @@ const selectAll = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="title">Zoom & pan transform</div>
|
<div class="title">Zoom & pan transform</div>
|
||||||
<div class="transform">
|
<div class="transform">
|
||||||
{{ [transform[0].toFixed(2), transform[1].toFixed(2), transform[2].toFixed(2)] }}
|
{{ [viewport.x.toFixed(2), viewport.y.toFixed(2), viewport.zoom.toFixed(2)] }}
|
||||||
</div>
|
</div>
|
||||||
<div class="title">Nodes</div>
|
<div class="title">Nodes</div>
|
||||||
<div v-for="node of getNodes" :key="node.id">
|
<div v-for="node of getNodes" :key="node.id">
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ const defaultColors: Record<BackgroundVariant, string> = {
|
|||||||
const { store } = useVueFlow()
|
const { store } = useVueFlow()
|
||||||
|
|
||||||
const background = computed(() => {
|
const background = computed(() => {
|
||||||
const scaledGap = props.gap && props.gap * store.transform[2]
|
const scaledGap = props.gap && props.gap * store.viewport.zoom
|
||||||
const xOffset = scaledGap && store.transform[0] % scaledGap
|
const xOffset = scaledGap && store.viewport.x % scaledGap
|
||||||
const yOffset = scaledGap && store.transform[1] % scaledGap
|
const yOffset = scaledGap && store.viewport.y % scaledGap
|
||||||
const size = props.size || 0.4 * store.transform[2]
|
const size = props.size || 0.4 * store.viewport.zoom
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scaledGap,
|
scaledGap,
|
||||||
|
|||||||
@@ -28,16 +28,17 @@ const nodeColorFunc: MiniMapNodeFunc = props.nodeColor instanceof Function ? pro
|
|||||||
const nodeStrokeColorFunc: MiniMapNodeFunc =
|
const nodeStrokeColorFunc: MiniMapNodeFunc =
|
||||||
props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor as string
|
props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor as string
|
||||||
|
|
||||||
const nodeClassNameFunc = props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName as MiniMapNodeFunc
|
const nodeClassNameFunc =
|
||||||
|
props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName as MiniMapNodeFunc
|
||||||
|
|
||||||
const shapeRendering: ShapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
|
const shapeRendering: ShapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
|
||||||
|
|
||||||
const bb = computed(() => getRectOfNodes(store.getNodes))
|
const bb = computed(() => getRectOfNodes(store.getNodes))
|
||||||
const viewBB = computed(() => ({
|
const viewBB = computed(() => ({
|
||||||
x: -store.transform[0] / store.transform[2],
|
x: -store.viewport.x / store.viewport.zoom,
|
||||||
y: -store.transform[1] / store.transform[2],
|
y: -store.viewport.y / store.viewport.zoom,
|
||||||
width: store.dimensions.width / store.transform[2],
|
width: store.dimensions.width / store.viewport.zoom,
|
||||||
height: store.dimensions.height / store.transform[2],
|
height: store.dimensions.height / store.viewport.zoom,
|
||||||
}))
|
}))
|
||||||
const viewBox = computed(() => {
|
const viewBox = computed(() => {
|
||||||
const boundingRect = store.getNodes && store.getNodes.length ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value
|
const boundingRect = store.getNodes && store.getNodes.length ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value
|
||||||
|
|||||||
Vendored
+21
-1
@@ -3,14 +3,17 @@
|
|||||||
declare global {
|
declare global {
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
|
|
||||||
const computed: typeof import('vue')['computed']
|
const computed: typeof import('vue')['computed']
|
||||||
|
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||||
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||||
|
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||||
const createApp: typeof import('vue')['createApp']
|
const createApp: typeof import('vue')['createApp']
|
||||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||||
|
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||||
@@ -31,6 +34,9 @@ declare global {
|
|||||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
const isRef: typeof import('vue')['isRef']
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const logicAnd: typeof import('@vueuse/core')['logicAnd']
|
||||||
|
const logicNot: typeof import('@vueuse/core')['logicNot']
|
||||||
|
const logicOr: typeof import('@vueuse/core')['logicOr']
|
||||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||||
const markRaw: typeof import('vue')['markRaw']
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
const nextTick: typeof import('vue')['nextTick']
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
@@ -61,12 +67,17 @@ declare global {
|
|||||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||||
const readonly: typeof import('vue')['readonly']
|
const readonly: typeof import('vue')['readonly']
|
||||||
const ref: typeof import('vue')['ref']
|
const ref: typeof import('vue')['ref']
|
||||||
|
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||||
|
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||||
|
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||||
|
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||||
|
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||||
@@ -75,6 +86,7 @@ declare global {
|
|||||||
const toRef: typeof import('vue')['toRef']
|
const toRef: typeof import('vue')['toRef']
|
||||||
const toRefs: typeof import('vue')['toRefs']
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
const triggerRef: typeof import('vue')['triggerRef']
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||||
@@ -102,6 +114,7 @@ declare global {
|
|||||||
const useCssVars: typeof import('vue')['useCssVars']
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||||
const useDark: typeof import('@vueuse/core')['useDark']
|
const useDark: typeof import('@vueuse/core')['useDark']
|
||||||
|
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||||
@@ -123,10 +136,12 @@ declare global {
|
|||||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||||
|
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||||
const useFps: typeof import('@vueuse/core')['useFps']
|
const useFps: typeof import('@vueuse/core')['useFps']
|
||||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||||
|
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||||
@@ -184,6 +199,7 @@ declare global {
|
|||||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||||
|
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||||
@@ -204,8 +220,12 @@ declare global {
|
|||||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||||
const watch: typeof import('vue')['watch']
|
const watch: typeof import('vue')['watch']
|
||||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||||
|
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||||
const watchEffect: typeof import('vue')['watchEffect']
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||||
|
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||||
|
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||||
const whenever: typeof import('@vueuse/core')['whenever']
|
const whenever: typeof import('@vueuse/core')['whenever']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ const sourceY = props.sourceNode.computedPosition.y + sourceHandleY
|
|||||||
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right
|
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right
|
||||||
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
|
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
|
||||||
|
|
||||||
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
|
const targetX = computed(() => (store.connectionPosition.x - store.viewport.x) / store.viewport.zoom)
|
||||||
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
|
const targetY = computed(() => (store.connectionPosition.y - store.viewport.y) / store.viewport.zoom)
|
||||||
|
|
||||||
const dAttr = computed(() => {
|
const dAttr = computed(() => {
|
||||||
let path = `M${sourceX},${sourceY} ${targetX.value},${targetY.value}`
|
let path = `M${sourceX},${sourceY} ${targetX.value},${targetY.value}`
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ const { scale, onDrag, onDragStart, onDragStop } = useDraggableCore(nodeElement,
|
|||||||
grid: props.snapGrid,
|
grid: props.snapGrid,
|
||||||
cancel: `.${store.noDragClassName}`,
|
cancel: `.${store.noDragClassName}`,
|
||||||
enableUserSelectHack: false,
|
enableUserSelectHack: false,
|
||||||
scale: store.transform[2],
|
scale: store.viewport.zoom,
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
debouncedWatch(
|
debouncedWatch(
|
||||||
() => store.transform[2],
|
() => store.viewport.zoom,
|
||||||
() => {
|
() => {
|
||||||
scale.value = store.transform[2]
|
scale.value = store.viewport.zoom
|
||||||
},
|
},
|
||||||
{ debounce: 5 },
|
{ debounce: 5 },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const innerStyle = computed(() => ({
|
|||||||
left: `${selectedNodesBBox.value.x}px`,
|
left: `${selectedNodesBBox.value.x}px`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`)
|
const transform = computed(() => `translate(${store.viewport.x}px,${store.viewport.y}px) scale(${store.viewport.zoom})`)
|
||||||
|
|
||||||
watch(selectedNodesBBox, (v) => (store.selectedNodesBbox = v))
|
watch(selectedNodesBBox, (v) => (store.selectedNodesBbox = v))
|
||||||
|
|
||||||
@@ -25,14 +25,14 @@ const onContextMenu = (event: MouseEvent) => store.hooks.selectionContextMenu.tr
|
|||||||
const { onDragStart, onDrag, onDragStop, scale } = useDraggableCore(el, {
|
const { onDragStart, onDrag, onDragStop, scale } = useDraggableCore(el, {
|
||||||
grid: store.snapToGrid ? store.snapGrid : undefined,
|
grid: store.snapToGrid ? store.snapGrid : undefined,
|
||||||
enableUserSelectHack: false,
|
enableUserSelectHack: false,
|
||||||
scale: store.transform[2],
|
scale: store.viewport.zoom,
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
debouncedWatch(
|
debouncedWatch(
|
||||||
() => store.transform[2],
|
() => store.viewport.zoom,
|
||||||
() => {
|
() => {
|
||||||
scale.value = store.transform[2]
|
scale.value = store.viewport.zoom
|
||||||
},
|
},
|
||||||
{ debounce: 5 },
|
{ debounce: 5 },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const onMouseMove = (event: MouseEvent) => {
|
|||||||
height: Math.abs(mousePos.y - startY),
|
height: Math.abs(mousePos.y - startY),
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedNodes = getNodesInside(store.getNodes, rect.value, store.transform)
|
const selectedNodes = getNodesInside(store.getNodes, rect.value, store.viewport)
|
||||||
const selectedEdges = getConnectedEdges(selectedNodes, store.getEdges)
|
const selectedEdges = getConnectedEdges(selectedNodes, store.getEdges)
|
||||||
|
|
||||||
rect.value = nextUserSelectRect
|
rect.value = nextUserSelectRect
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { zoomIdentity } from 'd3-zoom'
|
|||||||
import useVueFlow from './useVueFlow'
|
import useVueFlow from './useVueFlow'
|
||||||
import useWindow from './useWindow'
|
import useWindow from './useWindow'
|
||||||
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils'
|
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils'
|
||||||
import { GraphNode, Store, Viewport, D3Selection } from '~/types'
|
import { GraphNode, Store, ViewportFuncs, D3Selection } from '~/types'
|
||||||
|
|
||||||
const DEFAULT_PADDING = 0.1
|
const DEFAULT_PADDING = 0.1
|
||||||
|
|
||||||
const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms)
|
const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms)
|
||||||
|
|
||||||
export default (store: Store = useVueFlow().store): Viewport => ({
|
export default (store: Store = useVueFlow().store): ViewportFuncs => ({
|
||||||
zoomIn: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1.2),
|
zoomIn: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1.2),
|
||||||
zoomOut: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1 / 1.2),
|
zoomOut: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1 / 1.2),
|
||||||
zoomTo: (zoomLevel, options) =>
|
zoomTo: (zoomLevel, options) =>
|
||||||
@@ -18,9 +18,9 @@ export default (store: Store = useVueFlow().store): Viewport => ({
|
|||||||
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), nextTransform)
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), nextTransform)
|
||||||
},
|
},
|
||||||
getTransform: () => ({
|
getTransform: () => ({
|
||||||
x: store.transform[0],
|
x: store.viewport.x,
|
||||||
y: store.transform[1],
|
y: store.viewport.y,
|
||||||
zoom: store.transform[2],
|
zoom: store.viewport.zoom,
|
||||||
}),
|
}),
|
||||||
fitView: async (
|
fitView: async (
|
||||||
options = {
|
options = {
|
||||||
@@ -44,7 +44,7 @@ export default (store: Store = useVueFlow().store): Viewport => ({
|
|||||||
nodes = options.includeHiddenNodes ? store.nodes : store.getNodes
|
nodes = options.includeHiddenNodes ? store.nodes : store.getNodes
|
||||||
}
|
}
|
||||||
const bounds = getRectOfNodes(nodes)
|
const bounds = getRectOfNodes(nodes)
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
const { x, y, zoom } = getTransformForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
store.dimensions.width,
|
store.dimensions.width,
|
||||||
store.dimensions.height,
|
store.dimensions.height,
|
||||||
@@ -66,7 +66,7 @@ export default (store: Store = useVueFlow().store): Viewport => ({
|
|||||||
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform)
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform)
|
||||||
},
|
},
|
||||||
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
|
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
const { x, y, zoom } = getTransformForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
store.dimensions.width,
|
store.dimensions.width,
|
||||||
store.dimensions.height,
|
store.dimensions.height,
|
||||||
@@ -78,5 +78,5 @@ export default (store: Store = useVueFlow().store): Viewport => ({
|
|||||||
|
|
||||||
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options.duration), transform)
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options.duration), transform)
|
||||||
},
|
},
|
||||||
project: (position) => pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid),
|
project: (position) => pointToRendererPoint(position, store.viewport, store.snapToGrid, store.snapGrid),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import NodeRenderer from '../NodeRenderer/NodeRenderer.vue'
|
import NodeRenderer from '../NodeRenderer/NodeRenderer.vue'
|
||||||
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue'
|
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue'
|
||||||
import { useKeyPress, useVueFlow, useZoomPanHelper } from '../../composables'
|
import { useVueFlow, useZoomPanHelper } from '../../composables'
|
||||||
import { FlowInstance } from '../../types'
|
import { FlowInstance } from '../../types'
|
||||||
import { onLoadGetEdges, onLoadGetElements, onLoadGetNodes, onLoadProject, onLoadToObject } from '../../utils'
|
import { onLoadGetEdges, onLoadGetElements, onLoadGetNodes, onLoadProject, onLoadToObject } from '../../utils'
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ onMounted(() => {
|
|||||||
store.hooks.paneReady.trigger(instance)
|
store.hooks.paneReady.trigger(instance)
|
||||||
})
|
})
|
||||||
|
|
||||||
const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`)
|
const transform = computed(() => `translate(${store.viewport.x}px,${store.viewport.y}px) scale(${store.viewport.zoom})`)
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,33 +1,159 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { zoom } from 'd3-zoom'
|
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
|
||||||
import { select } from 'd3-selection'
|
import { pointer, select } from 'd3-selection'
|
||||||
import { useVueFlow } from '../../composables'
|
import { FlowTransform, PanOnScrollMode } from '../../types'
|
||||||
|
import { useKeyPress, useVueFlow } from '../../composables'
|
||||||
|
import { clamp, clampPosition } from '../../utils'
|
||||||
import SelectionPane from '../SelectionPane/SelectionPane.vue'
|
import SelectionPane from '../SelectionPane/SelectionPane.vue'
|
||||||
import Zoom from './Zoom.vue'
|
|
||||||
import Transform from './Transform.vue'
|
import Transform from './Transform.vue'
|
||||||
|
|
||||||
const { id, store } = useVueFlow()
|
const { id, store } = useVueFlow()
|
||||||
const zoomPaneEl = templateRef<HTMLDivElement>('viewport', null)
|
const viewportEl = templateRef<HTMLDivElement>('viewport', null)
|
||||||
|
|
||||||
const { width, height } = useElementBounding(zoomPaneEl)
|
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
||||||
|
(prevTransform.x !== eventTransform.x && !isNaN(eventTransform.x)) ||
|
||||||
|
(prevTransform.y !== eventTransform.y && !isNaN(eventTransform.y)) ||
|
||||||
|
(prevTransform.zoom !== eventTransform.k && !isNaN(eventTransform.k))
|
||||||
|
|
||||||
useResizeObserver(zoomPaneEl, () => {
|
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
|
||||||
store.dimensions.width = width.value
|
x: eventTransform.x,
|
||||||
store.dimensions.height = height.value
|
y: eventTransform.y,
|
||||||
|
zoom: eventTransform.k,
|
||||||
})
|
})
|
||||||
|
|
||||||
const d3Zoom = ref()
|
const isWrappedWithClass = (event: Event, className: string | undefined) => (event.target as Element).closest(`.${className}`)
|
||||||
const d3Selection = ref()
|
|
||||||
const d3ZoomHandler = ref()
|
const clampedZoom = clamp(store.defaultZoom, store.minZoom, store.maxZoom)
|
||||||
|
const transform = ref({
|
||||||
|
...clampPosition({ x: store.defaultPosition[0], y: store.defaultPosition[1] }, store.translateExtent),
|
||||||
|
zoom: clampedZoom,
|
||||||
|
})
|
||||||
|
const { width, height } = useElementBounding(viewportEl)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[width, height],
|
||||||
|
([newWidth, newHeight]) => {
|
||||||
|
store.dimensions.width = newWidth
|
||||||
|
store.dimensions.height = newHeight
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
d3Zoom.value = zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent)
|
const d3Zoom = zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent)
|
||||||
d3Selection.value = select(zoomPaneEl.value).call(d3Zoom.value)
|
const d3Selection = select(viewportEl.value).call(d3Zoom)
|
||||||
d3ZoomHandler.value = d3Selection.value.on('wheel.zoom')
|
const d3ZoomHandler = d3Selection.on('wheel.zoom')
|
||||||
|
|
||||||
|
const updatedTransform = zoomIdentity.translate(transform.value.x, transform.value.y).scale(transform.value.zoom)
|
||||||
|
d3Zoom.transform(d3Selection, updatedTransform)
|
||||||
|
|
||||||
store.setState({
|
store.setState({
|
||||||
d3Zoom: d3Zoom.value,
|
d3Zoom,
|
||||||
d3Selection: d3Selection.value,
|
d3Selection,
|
||||||
d3ZoomHandler: d3ZoomHandler.value,
|
d3ZoomHandler,
|
||||||
|
viewport: { x: updatedTransform.x, y: updatedTransform.y, zoom: updatedTransform.k },
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectionKeyPressed = useKeyPress(store.selectionKeyCode, (keyPress) => {
|
||||||
|
if (keyPress) {
|
||||||
|
d3Zoom.on('zoom', null)
|
||||||
|
} else {
|
||||||
|
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
|
store.setState({ viewport: { x: event.transform.x, y: event.transform.y, zoom: event.transform.k } })
|
||||||
|
const flowTransform = eventToFlowTransform(event.transform)
|
||||||
|
store.hooks.move.trigger({ event, flowTransform })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const zoomKeyPressed = useKeyPress(store.zoomActivationKeyCode)
|
||||||
|
|
||||||
|
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
|
const flowTransform = eventToFlowTransform(event.transform)
|
||||||
|
transform.value = flowTransform
|
||||||
|
store.hooks.moveStart.trigger({ event, flowTransform })
|
||||||
|
})
|
||||||
|
|
||||||
|
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
|
if (viewChanged(transform.value, event.transform)) {
|
||||||
|
const flowTransform = eventToFlowTransform(event.transform)
|
||||||
|
transform.value = flowTransform
|
||||||
|
store.hooks.moveEnd.trigger({ event, flowTransform })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
d3Selection
|
||||||
|
?.on('wheel', (event: WheelEvent) => {
|
||||||
|
if (store.panOnScroll && !zoomKeyPressed.value) {
|
||||||
|
if (isWrappedWithClass(event, store.noWheelClassName)) return
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
|
||||||
|
const currentZoom = d3Selection?.property('__zoom').k || 1
|
||||||
|
|
||||||
|
if (event.ctrlKey && store.zoomOnPinch) {
|
||||||
|
const point = pointer(event)
|
||||||
|
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
||||||
|
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10
|
||||||
|
const zoom = currentZoom * Math.pow(2, pinchDelta)
|
||||||
|
if (d3Selection) d3Zoom.scaleTo(d3Selection, zoom, point)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase scroll speed in firefox
|
||||||
|
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||||
|
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
|
||||||
|
const deltaX = store.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
|
||||||
|
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
|
||||||
|
|
||||||
|
if (d3Selection && store.panOnScrollSpeed)
|
||||||
|
d3Zoom?.translateBy(
|
||||||
|
d3Selection,
|
||||||
|
-(deltaX / currentZoom) * store.panOnScrollSpeed,
|
||||||
|
-(deltaY / currentZoom) * store.panOnScrollSpeed,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
(!store.zoomOnScroll && store.preventScrolling) ||
|
||||||
|
!store.preventScrolling ||
|
||||||
|
isWrappedWithClass(event, store.noWheelClassName)
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('wheel.zoom', store.panOnScroll || typeof d3ZoomHandler === 'undefined' ? null : (d3ZoomHandler as any))
|
||||||
|
|
||||||
|
d3Zoom.filter((event: MouseEvent) => {
|
||||||
|
const zoomScroll = zoomKeyPressed.value || store.zoomOnScroll
|
||||||
|
const pinchZoom = store.zoomOnPinch && event.ctrlKey
|
||||||
|
|
||||||
|
// if all interactions are disabled, we prevent all zoom events
|
||||||
|
if (!store.panOnDrag && !zoomScroll && !store.panOnScroll && !store.zoomOnDoubleClick && !store.zoomOnPinch) return false
|
||||||
|
|
||||||
|
// during a selection we prevent all other interactions
|
||||||
|
if (selectionKeyPressed.value) return false
|
||||||
|
|
||||||
|
// if zoom on double click is disabled, we prevent the double click event
|
||||||
|
if (!store.zoomOnDoubleClick && event.type === 'dblclick') return false
|
||||||
|
|
||||||
|
// if the target element is inside an element with the nowheel class, we prevent zooming
|
||||||
|
if (isWrappedWithClass(event, store.noWheelClassName) && event.type === 'wheel') return false
|
||||||
|
|
||||||
|
// if the target element is inside an element with the nopan class, we prevent panning
|
||||||
|
if (isWrappedWithClass(event, store.noPanClassName) && event.type !== 'wheel') return false
|
||||||
|
|
||||||
|
if (!store.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false
|
||||||
|
|
||||||
|
// when there is no scroll handling enabled, we prevent all wheel events
|
||||||
|
if (!zoomScroll && !store.panOnScroll && !pinchZoom && event.type === 'wheel') return false
|
||||||
|
|
||||||
|
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
||||||
|
if (!store.panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) return false
|
||||||
|
|
||||||
|
// default filter for d3-zoom
|
||||||
|
return (!event.ctrlKey || event.type === 'wheel') && !event.button
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -37,17 +163,10 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div ref="viewport" class="vue-flow__viewport vue-flow__container">
|
<div ref="viewport" :key="`viewport-${id}`" class="vue-flow__viewport vue-flow__container">
|
||||||
<Zoom
|
<Transform>
|
||||||
v-if="d3Zoom && d3Selection && d3ZoomHandler"
|
<slot />
|
||||||
:d3-zoom="d3Zoom"
|
</Transform>
|
||||||
:d3-selection="d3Selection"
|
|
||||||
:d3-zoom-handler="d3ZoomHandler"
|
|
||||||
>
|
|
||||||
<Transform>
|
|
||||||
<slot />
|
|
||||||
</Transform>
|
|
||||||
</Zoom>
|
|
||||||
<SelectionPane :key="`selection-pane-${id}`" />
|
<SelectionPane :key="`selection-pane-${id}`" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { D3ZoomEvent, zoomIdentity, ZoomTransform } from 'd3-zoom'
|
|
||||||
import { pointer } from 'd3-selection'
|
|
||||||
import { useKeyPress, useVueFlow } from '../../composables'
|
|
||||||
import { D3Selection, D3Zoom, D3ZoomHandler, FlowTransform, PanOnScrollMode } from '../../types'
|
|
||||||
import { clamp, clampPosition } from '../../utils'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
d3Zoom: D3Zoom
|
|
||||||
d3Selection: D3Selection
|
|
||||||
d3ZoomHandler: D3ZoomHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
const { store } = useVueFlow()
|
|
||||||
|
|
||||||
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
|
||||||
(prevTransform.x !== eventTransform.x && !isNaN(eventTransform.x)) ||
|
|
||||||
(prevTransform.y !== eventTransform.y && !isNaN(eventTransform.y)) ||
|
|
||||||
(prevTransform.zoom !== eventTransform.k && !isNaN(eventTransform.k))
|
|
||||||
|
|
||||||
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
|
|
||||||
x: eventTransform.x,
|
|
||||||
y: eventTransform.y,
|
|
||||||
zoom: eventTransform.k,
|
|
||||||
})
|
|
||||||
|
|
||||||
const isWrappedWithClass = (event: Event, className: string | undefined) => (event.target as Element).closest(`.${className}`)
|
|
||||||
|
|
||||||
const clampedZoom = clamp(store.defaultZoom, store.minZoom, store.maxZoom)
|
|
||||||
const transform = ref({
|
|
||||||
...clampPosition({ x: store.defaultPosition[0], y: store.defaultPosition[1] }, store.translateExtent),
|
|
||||||
zoom: clampedZoom,
|
|
||||||
})
|
|
||||||
|
|
||||||
const updatedTransform = zoomIdentity.translate(transform.value.x, transform.value.y).scale(transform.value.zoom)
|
|
||||||
props.d3Zoom.transform(props.d3Selection, updatedTransform)
|
|
||||||
|
|
||||||
store.transform = [updatedTransform.x, updatedTransform.y, updatedTransform.k]
|
|
||||||
|
|
||||||
const selectionKeyPressed = useKeyPress(store.selectionKeyCode, (keyPress) => {
|
|
||||||
if (keyPress) {
|
|
||||||
props.d3Zoom.on('zoom', null)
|
|
||||||
} else {
|
|
||||||
props.d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
|
||||||
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] })
|
|
||||||
const flowTransform = eventToFlowTransform(event.transform)
|
|
||||||
store.hooks.move.trigger({ event, flowTransform })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const zoomKeyPressed = useKeyPress(store.zoomActivationKeyCode)
|
|
||||||
|
|
||||||
props.d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
|
||||||
const flowTransform = eventToFlowTransform(event.transform)
|
|
||||||
transform.value = flowTransform
|
|
||||||
store.hooks.moveStart.trigger({ event, flowTransform })
|
|
||||||
})
|
|
||||||
|
|
||||||
props.d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
|
||||||
if (viewChanged(transform.value, event.transform)) {
|
|
||||||
const flowTransform = eventToFlowTransform(event.transform)
|
|
||||||
transform.value = flowTransform
|
|
||||||
store.hooks.moveEnd.trigger({ event, flowTransform })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
props.d3Selection
|
|
||||||
?.on('wheel', (event: WheelEvent) => {
|
|
||||||
if (store.panOnScroll && !zoomKeyPressed.value) {
|
|
||||||
if (isWrappedWithClass(event, store.noWheelClassName)) return
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopImmediatePropagation()
|
|
||||||
|
|
||||||
const currentZoom = props.d3Selection?.property('__zoom').k || 1
|
|
||||||
|
|
||||||
if (event.ctrlKey && store.zoomOnPinch) {
|
|
||||||
const point = pointer(event)
|
|
||||||
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
|
||||||
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10
|
|
||||||
const zoom = currentZoom * Math.pow(2, pinchDelta)
|
|
||||||
if (props.d3Selection) props.d3Zoom.scaleTo(props.d3Selection, zoom, point)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase scroll speed in firefox
|
|
||||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
|
||||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
|
|
||||||
const deltaX = store.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
|
|
||||||
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
|
|
||||||
|
|
||||||
if (props.d3Selection && store.panOnScrollSpeed)
|
|
||||||
props.d3Zoom?.translateBy(
|
|
||||||
props.d3Selection,
|
|
||||||
-(deltaX / currentZoom) * store.panOnScrollSpeed,
|
|
||||||
-(deltaY / currentZoom) * store.panOnScrollSpeed,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
(!store.zoomOnScroll && store.preventScrolling) ||
|
|
||||||
!store.preventScrolling ||
|
|
||||||
isWrappedWithClass(event, store.noWheelClassName)
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('wheel.zoom', store.panOnScroll || typeof props.d3ZoomHandler === 'undefined' ? null : (props.d3ZoomHandler as any))
|
|
||||||
|
|
||||||
props.d3Zoom.filter((event: MouseEvent) => {
|
|
||||||
const zoomScroll = zoomKeyPressed.value || store.zoomOnScroll
|
|
||||||
const pinchZoom = store.zoomOnPinch && event.ctrlKey
|
|
||||||
|
|
||||||
// if all interactions are disabled, we prevent all zoom events
|
|
||||||
if (!store.panOnDrag && !zoomScroll && !store.panOnScroll && !store.zoomOnDoubleClick && !store.zoomOnPinch) return false
|
|
||||||
|
|
||||||
// during a selection we prevent all other interactions
|
|
||||||
if (selectionKeyPressed.value) return false
|
|
||||||
|
|
||||||
// if zoom on double click is disabled, we prevent the double click event
|
|
||||||
if (!store.zoomOnDoubleClick && event.type === 'dblclick') return false
|
|
||||||
|
|
||||||
// if the target element is inside an element with the nowheel class, we prevent zooming
|
|
||||||
if (isWrappedWithClass(event, store.noWheelClassName) && event.type === 'wheel') return false
|
|
||||||
|
|
||||||
// if the target element is inside an element with the nopan class, we prevent panning
|
|
||||||
if (isWrappedWithClass(event, store.noPanClassName) && event.type !== 'wheel') return false
|
|
||||||
|
|
||||||
if (!store.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false
|
|
||||||
|
|
||||||
// when there is no scroll handling enabled, we prevent all wheel events
|
|
||||||
if (!zoomScroll && !store.panOnScroll && !pinchZoom && event.type === 'wheel') return false
|
|
||||||
|
|
||||||
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
|
||||||
if (!store.panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) return false
|
|
||||||
|
|
||||||
// default filter for d3-zoom
|
|
||||||
return (!event.ctrlKey || event.type === 'wheel') && !event.button
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'Zoom',
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
@@ -152,7 +152,7 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
dimensions.height &&
|
dimensions.height &&
|
||||||
(node.dimensions.width !== dimensions.width || node.dimensions.height !== dimensions.height || update.forceUpdate)
|
(node.dimensions.width !== dimensions.width || node.dimensions.height !== dimensions.height || update.forceUpdate)
|
||||||
)
|
)
|
||||||
node.handleBounds = getHandleBounds(update.nodeElement, state.transform[2])
|
node.handleBounds = getHandleBounds(update.nodeElement, state.viewport.zoom)
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
node.dimensions = dimensions
|
node.dimensions = dimensions
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default (state: State): ComputedGetters => {
|
|||||||
width: state.dimensions.width,
|
width: state.dimensions.width,
|
||||||
height: state.dimensions.height,
|
height: state.dimensions.height,
|
||||||
},
|
},
|
||||||
state.transform,
|
state.viewport,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
: nodes ?? []
|
: nodes ?? []
|
||||||
@@ -63,7 +63,7 @@ export default (state: State): ComputedGetters => {
|
|||||||
targetHeight: e.targetNode.dimensions.height,
|
targetHeight: e.targetNode.dimensions.height,
|
||||||
width: state.dimensions.width,
|
width: state.dimensions.width,
|
||||||
height: state.dimensions.height,
|
height: state.dimensions.height,
|
||||||
transform: state.transform,
|
viewport: state.viewport,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const defaultState = (): State => ({
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
},
|
},
|
||||||
transform: [0, 0, 1],
|
viewport: { x: 0, y: 0, zoom: 1 },
|
||||||
|
|
||||||
d3Zoom: null,
|
d3Zoom: null,
|
||||||
d3Selection: null,
|
d3Selection: null,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CSSProperties, VNode } from 'vue'
|
|||||||
import { GraphEdge, Edge, DefaultEdgeOptions } from './edge'
|
import { GraphEdge, Edge, DefaultEdgeOptions } from './edge'
|
||||||
import { GraphNode, CoordinateExtent, Node } from './node'
|
import { GraphNode, CoordinateExtent, Node } from './node'
|
||||||
import { ConnectionLineType, ConnectionMode } from './connection'
|
import { ConnectionLineType, ConnectionMode } from './connection'
|
||||||
import { KeyCode, PanOnScrollMode, Viewport } from './zoom'
|
import { KeyCode, PanOnScrollMode, ViewportFuncs } from './zoom'
|
||||||
import { DefaultEdgeTypes, DefaultNodeTypes, EdgeComponent, NodeComponent } from './components'
|
import { DefaultEdgeTypes, DefaultNodeTypes, EdgeComponent, NodeComponent } from './components'
|
||||||
|
|
||||||
export type ElementData = any
|
export type ElementData = any
|
||||||
@@ -36,9 +36,6 @@ export interface Element<Data extends ElementData = ElementData> {
|
|||||||
}
|
}
|
||||||
export type Elements<NodeData = ElementData, EdgeData = ElementData> = (Node<NodeData> | Edge<EdgeData>)[]
|
export type Elements<NodeData = ElementData, EdgeData = ElementData> = (Node<NodeData> | Edge<EdgeData>)[]
|
||||||
|
|
||||||
/** Transform x, y, z */
|
|
||||||
export type Transform = [number, number, number]
|
|
||||||
|
|
||||||
/** Handle Positions */
|
/** Handle Positions */
|
||||||
export enum Position {
|
export enum Position {
|
||||||
Left = 'left',
|
Left = 'left',
|
||||||
@@ -93,7 +90,7 @@ interface Exports {
|
|||||||
toObject: () => FlowExportObject
|
toObject: () => FlowExportObject
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlowInstance = Exports & Viewport
|
export type FlowInstance = Exports & ViewportFuncs
|
||||||
|
|
||||||
export interface FlowProps<NodeData = ElementData, EdgeData = ElementData> {
|
export interface FlowProps<NodeData = ElementData, EdgeData = ElementData> {
|
||||||
id?: string
|
id?: string
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
import { ComputedRef, CSSProperties, ToRefs } from 'vue'
|
import { ComputedRef, CSSProperties, ToRefs } from 'vue'
|
||||||
import {
|
import { Dimensions, ElementData, Elements, FlowElements, FlowInstance, FlowOptions, Rect, SnapGrid, XYPosition } from './flow'
|
||||||
Dimensions,
|
|
||||||
ElementData,
|
|
||||||
Elements,
|
|
||||||
FlowElements,
|
|
||||||
FlowInstance,
|
|
||||||
FlowOptions,
|
|
||||||
Rect,
|
|
||||||
SnapGrid,
|
|
||||||
Transform,
|
|
||||||
XYPosition,
|
|
||||||
} from './flow'
|
|
||||||
import { EdgeComponent, NodeComponent, DefaultNodeTypes, DefaultEdgeTypes } from './components'
|
import { EdgeComponent, NodeComponent, DefaultNodeTypes, DefaultEdgeTypes } from './components'
|
||||||
import { Connection, ConnectionLineType, ConnectionMode } from './connection'
|
import { Connection, ConnectionLineType, ConnectionMode } from './connection'
|
||||||
import { DefaultEdgeOptions, Edge, GraphEdge } from './edge'
|
import { DefaultEdgeOptions, Edge, GraphEdge } from './edge'
|
||||||
import { GraphNode, CoordinateExtent, Node } from './node'
|
import { GraphNode, CoordinateExtent, Node } from './node'
|
||||||
import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode } from './zoom'
|
import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode, Viewport } from './zoom'
|
||||||
import { FlowHooks, FlowHooksOn } from './hooks'
|
import { FlowHooks, FlowHooksOn } from './hooks'
|
||||||
import { NodeChange, EdgeChange } from './changes'
|
import { NodeChange, EdgeChange } from './changes'
|
||||||
import { StartHandle, HandleType } from './handle'
|
import { StartHandle, HandleType } from './handle'
|
||||||
@@ -44,7 +33,7 @@ export interface State<NodeData = ElementData, EdgeData = ElementData>
|
|||||||
/** viewport dimensions */
|
/** viewport dimensions */
|
||||||
dimensions: Dimensions
|
dimensions: Dimensions
|
||||||
/** viewport transform x, y, z */
|
/** viewport transform x, y, z */
|
||||||
transform: Transform
|
viewport: Viewport
|
||||||
|
|
||||||
onlyRenderVisibleElements: boolean
|
onlyRenderVisibleElements: boolean
|
||||||
defaultPosition: [number, number]
|
defaultPosition: [number, number]
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
|
|||||||
export type D3Selection = Selection<HTMLDivElement, unknown, any, any>
|
export type D3Selection = Selection<HTMLDivElement, unknown, any, any>
|
||||||
export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void
|
export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void
|
||||||
|
|
||||||
|
/** Transform x, y, z */
|
||||||
|
export type Viewport = { x: number; y: number; zoom: number }
|
||||||
|
|
||||||
export type KeyCode = number | string
|
export type KeyCode = number | string
|
||||||
|
|
||||||
export enum PanOnScrollMode {
|
export enum PanOnScrollMode {
|
||||||
@@ -13,7 +16,7 @@ export enum PanOnScrollMode {
|
|||||||
Horizontal = 'horizontal',
|
Horizontal = 'horizontal',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseZoomPanHelperOptions = {
|
export type ViewportFuncsOptions = {
|
||||||
duration?: number
|
duration?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ export type FitViewParams = {
|
|||||||
y?: number
|
y?: number
|
||||||
}
|
}
|
||||||
nodes?: string[]
|
nodes?: string[]
|
||||||
} & UseZoomPanHelperOptions
|
} & ViewportFuncsOptions
|
||||||
|
|
||||||
export type FlowTransform = {
|
export type FlowTransform = {
|
||||||
x: number
|
x: number
|
||||||
@@ -35,11 +38,11 @@ export type FlowTransform = {
|
|||||||
zoom: number
|
zoom: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SetCenterOptions = UseZoomPanHelperOptions & {
|
export type SetCenterOptions = ViewportFuncsOptions & {
|
||||||
zoom?: number
|
zoom?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FitBoundsOptions = UseZoomPanHelperOptions & {
|
export type FitBoundsOptions = ViewportFuncsOptions & {
|
||||||
padding?: number
|
padding?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,12 +50,12 @@ export type FitView = (fitViewOptions?: FitViewParams) => void
|
|||||||
export type Project = (position: XYPosition) => XYPosition
|
export type Project = (position: XYPosition) => XYPosition
|
||||||
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void
|
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void
|
||||||
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void
|
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void
|
||||||
export type ZoomInOut = (options?: UseZoomPanHelperOptions) => void
|
export type ZoomInOut = (options?: ViewportFuncsOptions) => void
|
||||||
export type ZoomTo = (zoomLevel: number, options?: UseZoomPanHelperOptions) => void
|
export type ZoomTo = (zoomLevel: number, options?: ViewportFuncsOptions) => void
|
||||||
export type GetTransform = () => FlowTransform
|
export type GetTransform = () => FlowTransform
|
||||||
export type SetTransform = (transform: FlowTransform, options?: UseZoomPanHelperOptions) => void
|
export type SetTransform = (transform: FlowTransform, options?: ViewportFuncsOptions) => void
|
||||||
|
|
||||||
export interface Viewport {
|
export interface ViewportFuncs {
|
||||||
zoomIn: ZoomInOut
|
zoomIn: ZoomInOut
|
||||||
zoomOut: ZoomInOut
|
zoomOut: ZoomInOut
|
||||||
zoomTo: ZoomTo
|
zoomTo: ZoomTo
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { rectToBox } from './graph'
|
import { rectToBox } from './graph'
|
||||||
import { EdgePositions, GraphEdge, GraphNode, HandleElement, Position, Rect, Transform, XYPosition } from '~/types'
|
import { EdgePositions, GraphEdge, GraphNode, HandleElement, Position, Rect, Viewport, XYPosition } from '~/types'
|
||||||
|
|
||||||
export const getHandlePosition = (position: Position, rect: Rect, handle?: HandleElement): XYPosition => {
|
export const getHandlePosition = (position: Position, rect: Rect, handle?: HandleElement): XYPosition => {
|
||||||
const x = (handle?.x ?? 0) + rect.x
|
const x = (handle?.x ?? 0) + rect.x
|
||||||
@@ -83,7 +83,7 @@ interface IsEdgeVisibleParams {
|
|||||||
targetHeight: number
|
targetHeight: number
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
transform: Transform
|
viewport: Viewport
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEdgeVisible({
|
export function isEdgeVisible({
|
||||||
@@ -95,7 +95,7 @@ export function isEdgeVisible({
|
|||||||
targetHeight,
|
targetHeight,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
transform,
|
viewport,
|
||||||
}: IsEdgeVisibleParams): boolean {
|
}: IsEdgeVisibleParams): boolean {
|
||||||
const edgeBox = {
|
const edgeBox = {
|
||||||
x: Math.min(sourcePos.x, targetPos.x),
|
x: Math.min(sourcePos.x, targetPos.x),
|
||||||
@@ -113,10 +113,10 @@ export function isEdgeVisible({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const viewBox = rectToBox({
|
const viewBox = rectToBox({
|
||||||
x: (0 - transform[0]) / transform[2],
|
x: (0 - viewport.x) / viewport.zoom,
|
||||||
y: (0 - transform[1]) / transform[2],
|
y: (0 - viewport.y) / viewport.zoom,
|
||||||
width: width / transform[2],
|
width: width / viewport.zoom,
|
||||||
height: height / transform[2],
|
height: height / viewport.zoom,
|
||||||
})
|
})
|
||||||
|
|
||||||
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x))
|
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
Rect,
|
Rect,
|
||||||
State,
|
State,
|
||||||
Store,
|
Store,
|
||||||
Transform,
|
Viewport,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
XYZPosition,
|
XYZPosition,
|
||||||
} from '~/types'
|
} from '~/types'
|
||||||
@@ -200,7 +200,7 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: E
|
|||||||
|
|
||||||
export const pointToRendererPoint = (
|
export const pointToRendererPoint = (
|
||||||
{ x, y }: XYPosition,
|
{ x, y }: XYPosition,
|
||||||
[tx, ty, tScale]: Transform,
|
{ x: tx, y: ty, zoom: tScale }: Viewport,
|
||||||
snapToGrid: boolean,
|
snapToGrid: boolean,
|
||||||
[snapX, snapY]: [number, number],
|
[snapX, snapY]: [number, number],
|
||||||
) => {
|
) => {
|
||||||
@@ -220,7 +220,7 @@ export const pointToRendererPoint = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const onLoadProject = (currentStore: Store) => (position: XYPosition) =>
|
export const onLoadProject = (currentStore: Store) => (position: XYPosition) =>
|
||||||
pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid)
|
pointToRendererPoint(position, currentStore.viewport, currentStore.snapToGrid, currentStore.snapGrid)
|
||||||
|
|
||||||
const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
|
const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
|
||||||
x: Math.min(box1.x, box2.x),
|
x: Math.min(box1.x, box2.x),
|
||||||
@@ -261,12 +261,17 @@ export const getRectOfNodes = (nodes: GraphNode[]) => {
|
|||||||
return boxToRect(box)
|
return boxToRect(box)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const graphPosToZoomedPos = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => ({
|
export const graphPosToZoomedPos = ({ x, y }: XYPosition, { x: tx, y: ty, zoom: tScale }: Viewport): XYPosition => ({
|
||||||
x: x * tScale + tx,
|
x: x * tScale + tx,
|
||||||
y: y * tScale + ty,
|
y: y * tScale + ty,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false) => {
|
export const getNodesInside = (
|
||||||
|
nodes: GraphNode[],
|
||||||
|
rect: Rect,
|
||||||
|
{ x: tx, y: ty, zoom: tScale }: Viewport = { x: 0, y: 0, zoom: 1 },
|
||||||
|
partially = false,
|
||||||
|
) => {
|
||||||
const rBox = rectToBox({
|
const rBox = rectToBox({
|
||||||
x: (rect.x - tx) / tScale,
|
x: (rect.x - tx) / tScale,
|
||||||
y: (rect.y - ty) / tScale,
|
y: (rect.y - ty) / tScale,
|
||||||
@@ -309,8 +314,8 @@ export const onLoadToObject = (store: State) => (): FlowExportObject => {
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
nodes: store.nodes,
|
nodes: store.nodes,
|
||||||
edges: store.edges,
|
edges: store.edges,
|
||||||
position: [store.transform[0], store.transform[1]],
|
position: [store.viewport.x, store.viewport.y],
|
||||||
zoom: store.transform[2],
|
zoom: store.viewport.zoom,
|
||||||
} as FlowExportObject),
|
} as FlowExportObject),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -326,7 +331,7 @@ export const getTransformForBounds = (
|
|||||||
x?: number
|
x?: number
|
||||||
y?: number
|
y?: number
|
||||||
} = { x: 0, y: 0 },
|
} = { x: 0, y: 0 },
|
||||||
): Transform => {
|
): Viewport => {
|
||||||
const xZoom = width / (bounds.width * (1 + padding))
|
const xZoom = width / (bounds.width * (1 + padding))
|
||||||
const yZoom = height / (bounds.height * (1 + padding))
|
const yZoom = height / (bounds.height * (1 + padding))
|
||||||
const zoom = Math.min(xZoom, yZoom)
|
const zoom = Math.min(xZoom, yZoom)
|
||||||
@@ -336,7 +341,7 @@ export const getTransformForBounds = (
|
|||||||
const x = width / 2 - boundsCenterX * clampedZoom + (offset.x ?? 0)
|
const x = width / 2 - boundsCenterX * clampedZoom + (offset.x ?? 0)
|
||||||
const y = height / 2 - boundsCenterY * clampedZoom + (offset.y ?? 0)
|
const y = height / 2 - boundsCenterY * clampedZoom + (offset.y ?? 0)
|
||||||
|
|
||||||
return [x, y, clampedZoom]
|
return { x, y, zoom: clampedZoom }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getXYZPos = (parentPos: XYZPosition, computedPosition: XYZPosition): XYZPosition => {
|
export const getXYZPos = (parentPos: XYZPosition, computedPosition: XYZPosition): XYZPosition => {
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
"open": "cypress open-ct"
|
"open": "cypress open-ct"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braks/vue-flow": "^0.4.0-17"
|
"@braks/vue-flow": "^0.4.2-3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cypress/vite-dev-server": "^2.2.1",
|
"@cypress/vite-dev-server": "^2.2.1",
|
||||||
|
|||||||
@@ -1196,18 +1196,6 @@
|
|||||||
"@vueuse/core" "^7.0.1"
|
"@vueuse/core" "^7.0.1"
|
||||||
vue-demi latest
|
vue-demi latest
|
||||||
|
|
||||||
"@braks/vue-flow@^0.4.0-17":
|
|
||||||
version "0.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@braks/vue-flow/-/vue-flow-0.4.1.tgz#81ce05e6a47f7232f09ec78f93ea371105b85b43"
|
|
||||||
integrity sha512-EOTo/zAvtnP7ZtXVJ8DSKJrQWMoajVVaFgzx5/ghVTn2KWU846S2AIaahy2L95x8gyzob0YmsJ73tkgoPgmuJg==
|
|
||||||
dependencies:
|
|
||||||
"@braks/revue-draggable" "^0.4.2"
|
|
||||||
"@types/d3" "^7.1.0"
|
|
||||||
"@vueuse/core" "^7.5.5"
|
|
||||||
d3 "^7.1.1"
|
|
||||||
d3-selection "^3.0.0"
|
|
||||||
d3-zoom "^3.0.0"
|
|
||||||
|
|
||||||
"@cypress/mount-utils@1.0.2":
|
"@cypress/mount-utils@1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@cypress/mount-utils/-/mount-utils-1.0.2.tgz#afbc4f8c350b7cd86edc5ad0db0cbe1e0181edc8"
|
resolved "https://registry.yarnpkg.com/@cypress/mount-utils/-/mount-utils-1.0.2.tgz#afbc4f8c350b7cd86edc5ad0db0cbe1e0181edc8"
|
||||||
@@ -2352,7 +2340,7 @@
|
|||||||
ora "^5.4.1"
|
ora "^5.4.1"
|
||||||
upath "^2.0.1"
|
upath "^2.0.1"
|
||||||
|
|
||||||
"@vueuse/core@^7.0.1", "@vueuse/core@^7.5.5":
|
"@vueuse/core@^7.0.1":
|
||||||
version "7.7.1"
|
version "7.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-7.7.1.tgz#fc284f4103de73c7fb79bc06579d8066790db511"
|
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-7.7.1.tgz#fc284f4103de73c7fb79bc06579d8066790db511"
|
||||||
integrity sha512-PRRgbATMpoeUmkCEBtUeJgOwtew8s+4UsEd+Pm7MhkjL2ihCNrSqxNVtM6NFE4uP2sWnkGcZpCjPuNSxowJ1Ow==
|
integrity sha512-PRRgbATMpoeUmkCEBtUeJgOwtew8s+4UsEd+Pm7MhkjL2ihCNrSqxNVtM6NFE4uP2sWnkGcZpCjPuNSxowJ1Ow==
|
||||||
@@ -3550,7 +3538,7 @@ d3-zoom@3, d3-zoom@^3.0.0:
|
|||||||
d3-selection "2 - 3"
|
d3-selection "2 - 3"
|
||||||
d3-transition "2 - 3"
|
d3-transition "2 - 3"
|
||||||
|
|
||||||
d3@^7.1.1, d3@^7.4.2:
|
d3@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/d3/-/d3-7.4.2.tgz#c5d1001459dc9517bc774949f71b95e8735b3b4e"
|
resolved "https://registry.yarnpkg.com/d3/-/d3-7.4.2.tgz#c5d1001459dc9517bc774949f71b95e8735b3b4e"
|
||||||
integrity sha512-7VK+QBAWtNDbP2EU/ThkXgjd0u1MsXYYgCK2ElQ4BBWh0usE75tHVVeYx47m2pqQEy4isYKAA0tAFSln0l+9EQ==
|
integrity sha512-7VK+QBAWtNDbP2EU/ThkXgjd0u1MsXYYgCK2ElQ4BBWh0usE75tHVVeYx47m2pqQEy4isYKAA0tAFSln0l+9EQ==
|
||||||
|
|||||||
Reference in New Issue
Block a user