fix: zoom pane not scaling

Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
Braks
2021-11-05 23:29:14 +01:00
parent 3931a59524
commit a8f7cc2389
65 changed files with 5030 additions and 14 deletions

View File

@@ -57,7 +57,7 @@ const onDrop = (event: DragEvent) => {
}
</script>
<template>
<div class="flex flex-col md:flex-row h-full">
<div class="flex flex-col md:flex-row w-full h-full">
<div class="flex-1 h-full" @drop="onDrop">
<Flow :elements="elements" @elements-remove="onElementsRemove" @load="onLoad" @connect="onConnect" @dragover="onDragOver">
<Controls />

5
examples-app/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

11
examples-app/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Vue 3 + Typescript + Vite
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.

View File

@@ -2,11 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Flow Examples</title>
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/examples/main.ts"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

26
examples-app/package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "vue-flow-example",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"@braks/vue-flow": "link:..",
"dagre": "^0.8.5",
"localforage": "^1.10.0",
"vue": "^3.2.16",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@types/dagre": "^0.7.46",
"@vitejs/plugin-vue": "^1.9.3",
"typescript": "^4.4.3",
"unplugin-auto-import": "^0.4.13",
"vite": "^2.6.4",
"vite-plugin-windicss": "^1.5.1",
"vue-tsc": "^0.3.0",
"windicss": "^3.2.1"
}
}

View File

21
examples-app/src/App.vue Normal file
View File

@@ -0,0 +1,21 @@
<script lang="ts" setup>
import Sidebar from './Sidebar.vue'
import Header from './Header.vue'
</script>
<template>
<Header />
<div id="app" class="flex">
<Sidebar />
<div id="vue-flow-docs" class="flex-1">
<router-view />
</div>
</div>
</template>
<style>
@import 'assets/index.css';
@import '@braks/vue-flow/dist/style.css';
#app {
height: calc(100% - 92px);
}
</style>

View File

@@ -0,0 +1,91 @@
<script lang="ts" setup>
import {
Flow,
MiniMap,
Controls,
Background,
Connection,
Edge,
Elements,
FlowElement,
FlowInstance,
addEdge,
isNode,
removeElements,
Node,
} from '@braks/vue-flow'
const onNodeDragStop = ({ node }: { node: Node }) => console.log('drag stop', node)
const onElementClick = ({ node }: { node: Node }) => console.log('click', node)
const elements = ref<Elements>([
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
])
const rfInstance = ref<FlowInstance | null>(null)
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Edge | Connection) => (elements.value = addEdge(params, elements.value))
const onLoad = (flowInstance: FlowInstance) => {
flowInstance?.fitView({ padding: 0.1 })
rfInstance.value = flowInstance
}
const updatePos = () => {
elements.value = elements.value.map((el: FlowElement) => {
if (isNode(el)) {
el.position = {
x: Math.random() * 400,
y: Math.random() * 400,
}
}
return el
})
}
const logToObject = () => console.log(rfInstance.value?.toObject())
const resetTransform = () => rfInstance.value?.setTransform({ x: 0, y: 0, zoom: 1 })
const toggleClassnames = () => {
elements.value = elements.value.map((el: FlowElement) => {
if (isNode(el)) el.class = el.class === 'dark' ? 'light' : 'dark'
return el
})
}
</script>
<template>
<Flow
class="vue-flow-basic-example"
:elements="elements"
:default-zoom="1.5"
:min-zoom="0.2"
:max-zoom="4"
@elements-remove="onElementsRemove"
@connect="onConnect"
@node-drag-stop="onNodeDragStop"
@node-click="onElementClick"
@elementClick="onElementClick"
@load="onLoad"
>
<MiniMap />
<Controls />
<Background color="#aaa" :gap="8" />
<div class="absolute right-[10px] top-[10px] z-4">
<button class="button" @click="resetTransform">reset transform</button>
<button class="button" @click="updatePos">change pos</button>
<button class="button" @click="toggleClassnames">toggle classnames</button>
<button class="button" @click="logToObject">toObject</button>
</div>
</Flow>
</template>
<style>
.vue-flow--node .light {
@apply bg-white;
}
.vue-flow--node .dark {
@apply bg-black;
}
</style>

View File

@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { ConnectionLineComponentProps } from '@braks/vue-flow'
interface ConnectionLineProps extends ConnectionLineComponentProps {
sourceX: number
sourceY: number
targetX: number
targetY: number
}
const props = defineProps<ConnectionLineProps>()
</script>
<template>
<g>
<path
class="animated"
fill="none"
stroke="#222"
:stroke-width="1.5"
:d="`M${props.sourceX},${props.sourceY} C ${props.sourceX} ${props.targetY} ${props.sourceX} ${props.targetY} ${props.targetX},${props.targetY}`"
/>
<circle :cx="props.targetX" :cy="props.targetY" fill="#fff" :r="3" stroke="#222" :stroke-width="1.5" />
</g>
</template>

View File

@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { Flow, removeElements, addEdge, Background, BackgroundVariant, Elements, Connection, Edge } from '@braks/vue-flow'
import CustomConnectionLine from './CustomConnectionLine.vue'
const elements = ref<Elements>([
{
id: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
},
])
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
</script>
<template>
<Flow :elements="elements" @elements-remove="onElementsRemove" @connect="onConnect">
<template #custom-connection-line="props">
<CustomConnectionLine v-bind="props" />
</template>
<Background :variant="BackgroundVariant.Lines" />
</Flow>
</template>

View File

@@ -0,0 +1,38 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import { Handle, Position, Connection, Edge, NodeProps } from '@braks/vue-flow'
interface ColorSelectorNodeProps extends NodeProps {
data: {
color: string
onChange: (event: any) => void
}
type: string
selected?: boolean
connectable?: boolean
xPos?: number
yPos?: number
targetPosition?: Position
sourcePosition?: Position
dragging?: boolean
}
const props = defineProps<ColorSelectorNodeProps>()
const targetHandleStyle: CSSProperties = { background: '#555' }
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: '10px' }
const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: '10px', top: 'auto' }
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params)
</script>
<template>
<div class="py-1 px-1 flex flex-col gap-1 justify-center items-center">
<Handle type="target" :position="Position.Left" :style="targetHandleStyle" :on-connect="onConnect" />
<div class="text-md font-bold">Custom Color Picker Node</div>
<div :style="{ color: data.color }" class="text-md font-semibold">
{{ data.color }}
</div>
<input class="nodrag" type="color" :value="data.color" @input="props.data.onChange" />
<Handle id="a" type="source" :position="Position.Right" :style="sourceHandleStyleA" />
<Handle id="b" type="source" :position="Position.Right" :style="sourceHandleStyleB" />
</div>
</template>

View File

@@ -0,0 +1,132 @@
<script lang="ts" setup>
import {
Flow,
isEdge,
removeElements,
addEdge,
MiniMap,
Controls,
Node,
FlowElement,
FlowInstance,
Elements,
Position,
SnapGrid,
Connection,
ConnectionMode,
Edge,
} from '@braks/vue-flow'
import ColorPickerNode from './ColorPickerNode.vue'
const elements = ref<Elements>([])
const bgColor = ref('#1A192B')
const connectionLineStyle = { stroke: '#fff' }
const snapGrid: SnapGrid = [16, 16]
const nodeTypes = {
selectorNode: ColorPickerNode,
}
const onLoad = (flowInstance: FlowInstance) => {
flowInstance.fitView()
console.log('flow loaded:', flowInstance)
}
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node)
const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element)
const nodeStroke = (n: Node): string => {
if (n.type === 'input') return '#0041d0'
if (n.type === 'selectorNode') return bgColor.value
if (n.type === 'output') return '#ff0072'
return '#eee'
}
const nodeColor = (n: Node): string => {
if (n.type === 'selectorNode') return bgColor.value
return '#fff'
}
onMounted(() => {
const onChange = (event: Event) => {
elements.value = elements.value.map((e: FlowElement) => {
if (isEdge(e) || e.id !== '2') {
return e
}
const color = (event.target as HTMLInputElement).value
bgColor.value = color
return {
...e,
data: {
...e.data,
color,
},
}
})
}
elements.value = [
{
id: '1',
type: 'input',
data: { label: 'An input node' },
position: { x: 0, y: 50 },
sourcePosition: Position.Right,
},
{
id: '2',
type: 'selectorNode',
data: { onChange, color: bgColor.value },
style: { border: '1px solid #777', padding: '10px' },
position: { x: 250, y: 50 },
},
{
id: '3',
type: 'output',
data: { label: 'Output A' },
position: { x: 650, y: 25 },
targetPosition: Position.Left,
},
{
id: '4',
type: 'output',
data: { label: 'Output B' },
position: { x: 650, y: 100 },
targetPosition: Position.Left,
},
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } },
]
})
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) =>
(elements.value = addEdge(
{
...params,
animated: true,
style: { stroke: '#fff' },
} as Edge,
elements.value,
))
</script>
<template>
<Flow
:elements="elements"
:style="`background: ${bgColor}`"
:node-types="nodeTypes"
:connection-mode="ConnectionMode.Loose"
:connection-line-style="connectionLineStyle"
:snap-to-grid="true"
:snap-grid="snapGrid"
:default-zoom="1.5"
@element-click="onElementClick"
@elements-remove="onElementsRemove"
@connect="onConnect"
@node-drag-stop="onNodeDragStop"
@load="onLoad"
>
<MiniMap :node-stroke-color="nodeStroke" :node-color="nodeColor" />
<Controls />
</Flow>
</template>

View File

@@ -0,0 +1,70 @@
<script lang="ts" setup>
import {
Flow,
addEdge,
removeElements,
Controls,
MiniMap,
Background,
FlowInstance,
Elements,
Connection,
Edge,
ElementId,
} from '@braks/vue-flow'
import Sidebar from './Sidebar.vue'
const flowInstance = ref<FlowInstance>()
const elements = ref<Elements>([
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
])
let id = 0
const getId = (): ElementId => `dndnode_${id++}`
const onDragOver = (event: DragEvent) => {
event.preventDefault()
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onLoad = (instance: FlowInstance) => (flowInstance.value = instance)
const onDrop = (event: DragEvent) => {
event.preventDefault()
if (flowInstance.value) {
console.log(event.dataTransfer?.getData('application/vueflow'))
const type = event.dataTransfer?.getData('application/vueflow')
const position = flowInstance.value.project({ x: event.clientX - 400, y: event.clientY - 40 })
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` },
}
elements.value.push(newNode)
}
}
</script>
<template>
<div class="flex flex-col md:flex-row h-full">
<div class="flex-1 h-full" @drop="onDrop">
<Flow :elements="elements" @elements-remove="onElementsRemove" @load="onLoad" @connect="onConnect" @dragover="onDragOver">
<Controls />
<MiniMap />
<Background color="#aaa" :gap="8" />
</Flow>
</div>
<Sidebar />
</div>
</template>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup>
const onDragStart = (event: DragEvent, nodeType: string) => {
if (event.dataTransfer) {
event.dataTransfer.setData('application/vueflow', nodeType)
event.dataTransfer.effectAllowed = 'move'
}
}
</script>
<template>
<aside class="md:(w-[30%] max-w-[200px])">
<div class="mb-6">You can drag these nodes to the pane on the left.</div>
<div
class="droppable-node vue-flow__node-input"
:draggable="true"
@dragstart="(event: DragEvent) => onDragStart(event, 'input')"
>
Input Node
</div>
<div
class="droppable-node vue-flow__node-default"
:draggable="true"
@dragstart="(event: DragEvent) => onDragStart(event, 'default')"
>
Default Node
</div>
<div
class="droppable-node vue-flow__node-output"
:draggable="true"
@dragstart="(event: DragEvent) => onDragStart(event, 'output')"
>
Output Node
</div>
</aside>
</template>
<style scoped>
aside {
border-right: 1px solid #eee;
padding: 15px 10px;
font-size: 12px;
background: #fcfcfc;
}
.droppable-node {
@apply mb-6;
cursor: grab;
}
</style>

View File

@@ -0,0 +1,30 @@
<script lang="ts" setup>
import {
Flow,
removeElements,
addEdge,
MiniMap,
Controls,
Background,
FlowInstance,
Connection,
Edge,
Elements,
} from '@braks/vue-flow'
import { getElements } from './utils'
const onLoad = (flowInstance: FlowInstance) => {
flowInstance.fitView()
console.log(flowInstance.getElements())
}
const elements = ref<Elements>(getElements())
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
</script>
<template>
<Flow :elements="elements" :min-zoom="0.2" @load="onLoad" @elements-remove="onElementsRemove" @connect="onConnect">
<MiniMap />
<Controls />
</Flow>
</template>

View File

@@ -0,0 +1,106 @@
import { ElementId, Elements, Position } from '@braks/vue-flow'
const nodeWidth = 80
const nodeGapWidth = nodeWidth * 2
const nodeStyle = { width: `${nodeWidth}px`, fontSize: '11px', color: 'white' }
const sourceTargetPositions = [
{ source: Position.Bottom, target: Position.Top },
{ source: Position.Right, target: Position.Left },
]
const nodeColors = [
['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'],
['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8'],
]
const edgeTypes = ['default', 'step', 'smoothstep', 'straight']
const offsets = [
{
x: 0,
y: -nodeGapWidth,
},
{
x: nodeGapWidth,
y: -nodeGapWidth,
},
{
x: nodeGapWidth,
y: 0,
},
{
x: nodeGapWidth,
y: nodeGapWidth,
},
{
x: 0,
y: nodeGapWidth,
},
{
x: -nodeGapWidth,
y: nodeGapWidth,
},
{
x: -nodeGapWidth,
y: 0,
},
{
x: -nodeGapWidth,
y: -nodeGapWidth,
},
]
let id = 0
const getNodeId = (): ElementId => (id++).toString()
export function getElements(): Elements {
const initialElements = []
for (let sourceTargetIndex = 0; sourceTargetIndex < sourceTargetPositions.length; sourceTargetIndex++) {
const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex]
for (let edgeTypeIndex = 0; edgeTypeIndex < edgeTypes.length; edgeTypeIndex++) {
const currEdgeType = edgeTypes[edgeTypeIndex]
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
const currOffset = offsets[offsetIndex]
const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] }
const sourcePosition = {
x: offsetIndex * nodeWidth * 4,
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300,
}
const sourceId = getNodeId()
const sourceData = { label: `Source ${sourceId}` }
const sourceNode = {
id: sourceId,
style,
data: sourceData,
position: sourcePosition,
sourcePosition: currSourceTargetPos.source,
targetPosition: currSourceTargetPos.target,
}
const targetId = getNodeId()
const targetData = { label: `Target ${targetId}` }
const targetPosition = {
x: sourcePosition.x + currOffset.x,
y: sourcePosition.y + currOffset.y,
}
const targetNode = {
id: targetId,
style,
data: targetData,
position: targetPosition,
sourcePosition: currSourceTargetPos.source,
targetPosition: currSourceTargetPos.target,
}
initialElements.push(sourceNode)
initialElements.push(targetNode)
initialElements.push({ id: `${sourceId}-${targetId}`, source: sourceId, target: targetId, type: currEdgeType })
}
}
}
return initialElements
}

View File

@@ -0,0 +1,80 @@
<script lang="ts" setup>
import {
useHooks,
useStore,
getEdgeCenter,
getBezierPath,
getMarkerEnd,
ArrowHeadType,
EdgeProps,
ElementId,
Position,
} from '@braks/vue-flow'
interface CustomEdgeProps<T = any> extends EdgeProps<T> {
id: ElementId
sourceX: number
sourceY: number
targetX: number
targetY: number
sourcePosition: Position
targetPosition: Position
arrowHeadType?: ArrowHeadType
markerEndId?: string
data?: T
}
const props = defineProps<CustomEdgeProps>()
const store = useStore()
const hooks = useHooks()
const onEdgeClick = (evt: Event, id: string) => {
const edge = store.edges.find((edge) => edge.id === id)
if (edge) {
hooks.elementsRemove.trigger([edge])
}
evt.stopPropagation()
alert(`remove ${id}`)
}
const foreignObjectSize = 40
const edgePath = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId))
const center = computed(() =>
getEdgeCenter({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
}),
)
</script>
<template>
<path :id="props.id" :style="props.style" class="vue-flow__edge-path" :d="edgePath" :marker-end="markerEnd" />
<foreignObject
:width="foreignObjectSize"
:height="foreignObjectSize"
:x="center[0] - foreignObjectSize / 2"
:y="center[1] - foreignObjectSize / 2"
class="edgebutton-foreignobject"
requiredExtensions="http://www.w3.org/1999/xhtml"
>
<body class="p-1">
<button class="edgebutton bg-gray-500 p-[4px] h-full w-full" @click="(event) => onEdgeClick(event, props.id)">×</button>
</body>
</foreignObject>
</template>
<style scoped>
body {
background: unset;
}
</style>

View File

@@ -0,0 +1,55 @@
<script lang="ts" setup>
import {
Flow,
addEdge,
Background,
Connection,
Controls,
Edge,
EdgeType,
Elements,
MiniMap,
FlowInstance,
removeElements,
} from '@braks/vue-flow'
import ButtonEdge from './ButtonEdge.vue'
const edgeTypes = {
buttonedge: ButtonEdge as EdgeType,
}
const elements = ref<Elements>([
{
id: 'ewb-1',
type: 'input',
data: { label: 'Input 1' },
position: { x: 250, y: 0 },
},
{ id: 'ewb-2', data: { label: 'Node 2' }, position: { x: 250, y: 300 } },
{
id: 'edge-1-2',
source: 'ewb-1',
target: 'ewb-2',
type: 'buttonedge',
},
])
const onLoad = (flowInstance: FlowInstance) => flowInstance.fitView()
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) =>
(elements.value = addEdge({ ...params, type: 'buttonedge' } as Edge, elements.value))
</script>
<template>
<Flow
key="edge-with-button"
:elements="elements"
:snap-to-grid="true"
:edge-types="edgeTypes"
@load="onLoad"
@elementsRemove="onElementsRemove"
@connect="onConnect"
>
<MiniMap />
<Controls />
<Background />
</Flow>
</template>

View File

@@ -0,0 +1,41 @@
<script lang="ts" setup>
import { ArrowHeadType, ElementId, getBezierPath, getMarkerEnd, Position, EdgeProps } from '@braks/vue-flow'
interface CustomEdgeProps<T = any> extends EdgeProps<T> {
source: ElementId
target: ElementId
sourceHandleId?: ElementId
targetHandleId?: ElementId
id: ElementId
sourceX: number
sourceY: number
targetX: number
targetY: number
sourcePosition: Position
targetPosition: Position
arrowHeadType?: ArrowHeadType
markerEndId?: string
data?: T
}
const props = defineProps<CustomEdgeProps>()
const edgePath = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId))
</script>
<template>
<path :id="props.id" class="vue-flow__edge-path" :d="edgePath" :marker-end="markerEnd" />
<text>
<textPath :href="`#${props.id}`" :style="{ fontSize: '12px' }" startOffset="50%" text-anchor="middle">
{{ props.data.text }}
</textPath>
</text>
</template>

View File

@@ -0,0 +1,65 @@
<script lang="ts" setup>
import {
getBezierPath,
getMarkerEnd,
ArrowHeadType,
EdgeProps,
ElementId,
getEdgeCenter,
Position,
EdgeText,
} from '@braks/vue-flow'
interface CustomEdgeProps<T = any> extends EdgeProps<T> {
source: ElementId
target: ElementId
sourceHandleId?: ElementId
targetHandleId?: ElementId
id: ElementId
sourceX: number
sourceY: number
targetX: number
targetY: number
sourcePosition: Position
targetPosition: Position
arrowHeadType?: ArrowHeadType
markerEndId?: string
data?: T
}
const props = defineProps<CustomEdgeProps>()
const edgePath = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId))
const center = computed(() =>
getEdgeCenter({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
}),
)
</script>
<template>
<path :id="props.id" class="vue-flow__edge-path" :d="edgePath" :marker-end="markerEnd" />
<EdgeText
:x="center[0]"
:y="center[1]"
:label="props.data?.text"
:label-style="{ fill: 'white' }"
:label-show-bg="true"
:label-bg-style="{ fill: 'red' }"
:label-bg-padding="[2, 4]"
:label-bg-border-radius="2"
@click="() => console.log(props.data)"
/>
</template>

View File

@@ -0,0 +1,10 @@
<script lang="ts" setup>
interface CustomLabelProps {
text: string
}
const props = defineProps<CustomLabelProps>()
</script>
<template>
<tspan dy="10" x="0">{{ props.text }}</tspan>
</template>

View File

@@ -0,0 +1,106 @@
<script lang="ts" setup>
import {
Flow,
MiniMap,
Controls,
Background,
Elements,
FlowInstance,
FlowElement,
removeElements,
Connection,
Edge,
addEdge,
ArrowHeadType,
} from '@braks/vue-flow'
import CustomEdge from './CustomEdge.vue'
import CustomEdge2 from './CustomEdge2.vue'
import CustomLabel from './CustomLabel.vue'
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Input 1' }, position: { x: 250, y: 0 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
{ id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 250, y: 200 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } },
{ id: '3a', data: { label: 'Node 3a' }, position: { x: 150, y: 300 } },
{ id: '5', data: { label: 'Node 5' }, position: { x: 250, y: 400 } },
{ id: '6', type: 'output', data: { label: 'Output 6' }, position: { x: 50, y: 550 } },
{ id: '7', type: 'output', data: { label: 'Output 7' }, position: { x: 250, y: 550 } },
{ id: '8', type: 'output', data: { label: 'Output 8' }, position: { x: 525, y: 600 } },
{ id: '9', type: 'output', data: { label: 'Output 9' }, position: { x: 675, y: 500 } },
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', class: 'normal-edge' },
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
{ id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' },
{ id: 'e3-3a', source: '3', target: '3a', type: 'straight', label: 'label only edge', style: { stroke: 'none' } },
{ id: 'e3-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } },
{
id: 'e5-6',
source: '5',
target: '6',
label: {
component: CustomLabel,
props: {
text: 'custom label text',
},
},
labelStyle: { fill: 'red', fontWeight: 700 },
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e5-7',
source: '5',
target: '7',
label: 'label with styled bg',
labelBgPadding: [8, 4],
labelBgBorderRadius: 4,
labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 },
arrowHeadType: ArrowHeadType.ArrowClosed,
},
{
id: 'e5-8',
source: '5',
target: '8',
type: 'custom',
data: { text: 'custom edge' },
arrowHeadType: ArrowHeadType.ArrowClosed,
},
{
id: 'e5-9',
source: '5',
target: '9',
type: 'custom2',
data: { text: 'custom edge 2' },
},
]
const edgeTypes: Record<string, any> = {
custom: CustomEdge,
custom2: CustomEdge2,
}
const elements = ref<Elements>(initialElements)
const onLoad = (flowInstance: FlowInstance) => flowInstance.fitView()
const onNodeDragStop = (node: Node) => console.log('drag stop', node)
const onElementClick = (element: FlowElement) => console.log('click', element)
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
</script>
<template>
<Flow
:elements="elements"
:snap-to-grid="true"
:edge-types="edgeTypes"
@element-click="onElementClick"
@elements-remove="onElementsRemove"
@connect="onConnect"
@node-drag-stop="onNodeDragStop"
@load="onLoad"
>
<MiniMap />
<Controls />
<Background />
</Flow>
</template>

View File

@@ -0,0 +1,54 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import {
Flow,
MiniMap,
Controls,
Background,
BackgroundVariant,
Connection,
Edge,
ElementId,
Elements,
FlowElement,
Node,
FlowInstance,
addEdge,
removeElements,
} from '@braks/vue-flow'
const elements = ref<Elements>([])
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onLoad = (flowInstance: FlowInstance) => console.log('flow loaded:', flowInstance)
const onElementClick = (element: FlowElement) => console.log('click', element)
const onNodeDragStop = (node: Node) => console.log('drag stop', node)
const buttonStyle: CSSProperties = { position: 'absolute', left: '10px', top: '10px', zIndex: 4 }
const addRandomNode = () => {
const nodeId: ElementId = (elements.value.length + 1).toString()
const newNode: Node = {
id: nodeId,
data: { label: `Node: ${nodeId}` },
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight },
} as Node
elements.value = [...elements.value, newNode]
}
</script>
<template>
<Flow
:elements="elements"
@load="onLoad"
@element-click="onElementClick"
@elements-remove="onElementsRemove"
@connect="(p) => onConnect(p)"
@node-drag-stop="onNodeDragStop"
>
<MiniMap />
<Controls />
<Background :variant="BackgroundVariant.Lines" />
<button class="button" type="button" :style="buttonStyle" @click="addRandomNode">add node</button>
</Flow>
</template>

View File

@@ -0,0 +1,16 @@
<template>
<header class="flex items-center py-4 px-8 text-white h-[92px]">
<div class="flex flex-col">
<a class="logo text-xl" href="https://github.com/bcakmakoglu/vue-flow"> Vue Flow </a>
<span class="text-xs text-gray-800 mt-2"
>An open source library by <a href="https://github.com/bcakmakoglu/bcakmakoglu">Braks</a> <br />
Based on <a href="https://reactflow.dev">React Flow</a>
</span>
</div>
<div class="flex-1">Docs</div>
<div class="flex-1">
<router-link to="/examples"> Examples </router-link>
</div>
<div class="flex-1"><a href="https://github.com/bcakmakoglu/vue-flow">Github</a></div>
</header>
</template>

View File

@@ -0,0 +1,39 @@
<script lang="ts" setup>
import { Flow, MiniMap, Controls, Connection, Edge, Elements, addEdge } from '@braks/vue-flow'
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4' },
]
const elements = ref<Elements>(initialElements)
const isHidden = ref<boolean>(false)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
watchEffect(() => {
elements.value = elements.value.map((e) => {
e.isHidden = isHidden.value
return e
})
})
</script>
<template>
<Flow :elements="elements" @connect="onConnect">
<MiniMap />
<Controls />
<div :style="{ position: 'absolute', left: '10px', top: '10px', zIndex: 4 }">
<div>
<label for="ishidden">
isHidden
<input id="ishidden" v-model="isHidden" type="checkbox" class="vue-flow__ishidden" />
</label>
</div>
</div>
</Flow>
</template>

View File

@@ -0,0 +1,152 @@
<script lang="ts" setup>
import {
Flow,
addEdge,
MiniMap,
Controls,
Elements,
Node,
FlowElement,
Connection,
Edge,
PanOnScrollMode,
FlowTransform,
} from '@braks/vue-flow'
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
]
const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node)
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node)
const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element)
const onPaneClick = (event: MouseEvent) => console.log('onPaneClick', event)
const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event)
const onPaneContextMenu = (event: MouseEvent) => console.log('onPaneContextMenu', event)
const onMoveEnd = (flowTranasform?: FlowTransform) => console.log('onMoveEnd', flowTranasform)
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const isSelectable = ref(false)
const isDraggable = ref(false)
const isConnectable = ref(false)
const zoomOnScroll = ref(false)
const zoomOnPinch = ref(false)
const panOnScroll = ref(false)
const panOnScrollMode = ref<PanOnScrollMode>(PanOnScrollMode.Free)
const zoomOnDoubleClick = ref(false)
const paneMoveable = ref(true)
const captureZoomClick = ref(false)
const captureZoomScroll = ref(false)
const captureElementClick = ref(false)
</script>
<template>
<Flow
:elements="elements"
:elements-selectable="isSelectable"
:nodes-connectable="isConnectable"
:nodes-draggable="isDraggable"
:zoom-on-scroll="zoomOnScroll"
:zoom-on-pinch="zoomOnPinch"
:pan-on-scroll="panOnScroll"
:pan-on-scroll-mode="panOnScrollMode"
:zoom-on-double-click="zoomOnDoubleClick"
:pane-moveable="paneMoveable"
@connect="onConnect"
@element-click="captureElementClick ? onElementClick : undefined"
@node-drag-start="onNodeDragStart"
@node-drag-stop="onNodeDragStop"
@pane-click="captureZoomClick ? onPaneClick : undefined"
@pane-scroll="captureZoomScroll ? onPaneScroll : undefined"
@pane-context-menu="captureZoomClick ? onPaneContextMenu : undefined"
@move-end="onMoveEnd"
>
<MiniMap />
<Controls />
<div :style="{ position: 'absolute', left: 10, top: 10, zIndex: 4 }">
<div>
<label for="draggable">
nodesDraggable
<input id="draggable" v-model="isDraggable" type="checkbox" class="vue-flow__draggable" />
</label>
</div>
<div>
<label for="connectable">
nodesConnectable
<input id="connectable" v-model="isConnectable" type="checkbox" class="vue-flow__connectable" />
</label>
</div>
<div>
<label for="selectable">
elementsSelectable
<input id="selectable" v-model="isSelectable" type="checkbox" class="vue-flow__selectable" />
</label>
</div>
<div>
<label for="zoomonscroll">
zoomOnScroll
<input id="zoomonscroll" v-model="zoomOnScroll" type="checkbox" class="vue-flow__zoomonscroll" />
</label>
</div>
<div>
<label for="zoomonpinch">
zoomOnPinch
<input id="zoomonpinch" v-model="zoomOnPinch" type="checkbox" class="vue-flow__zoomonpinch" />
</label>
</div>
<div>
<label for="panonscroll">
panOnScroll
<input id="panonscroll" v-model="panOnScroll" type="checkbox" class="vue-flow__panonscroll" />
</label>
</div>
<div>
<label>
panOnScrollMode
<select id="panonscrollmode" v-model="panOnScrollMode" class="vue-flow__panonscrollmode">
<option value="free">free</option>
<option value="horizontal">horizontal</option>
<option value="vertical">vertical</option>
</select>
</label>
</div>
<div>
<label for="zoomondbl">
zoomOnDoubleClick
<input id="zoomondbl" v-model="zoomOnDoubleClick" type="checkbox" class="vue-flow__zoomondbl" />
</label>
</div>
<div>
<label for="panemoveable">
paneMoveable
<input id="panemoveable" v-model="paneMoveable" type="checkbox" class="vue-flow__panemoveable" />
</label>
</div>
<div>
<label for="capturezoompaneclick">
capture onPaneClick
<input id="capturezoompaneclick" v-model="captureZoomClick" type="checkbox" class="vue-flow__capturezoompaneclick" />
</label>
</div>
<div>
<label for="capturezoompanescroll">
capture onPaneScroll
<input id="capturezoompanescroll" v-model="captureZoomScroll" type="checkbox" class="vue-flow__capturezoompanescroll" />
</label>
</div>
<div>
<label for="captureelementclick">
capture onElementClick
<input id="captureelementclick" v-model="captureElementClick" type="checkbox" class="vue-flow__captureelementclick" />
</label>
</div>
</div>
</Flow>
</template>

View File

@@ -0,0 +1,92 @@
<script lang="ts" setup>
import dagre from 'dagre'
import {
Flow,
Controls,
addEdge,
ConnectionMode,
Connection,
Edge,
Elements,
isNode,
NodeExtent,
Position,
removeElements,
} from '@braks/vue-flow'
import initialElements from './initial-elements'
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
const nodeExtent: NodeExtent = [
[0, 0],
[1000, 1000],
]
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge({ ...params, animated: true }, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
// todo changing elements not properly updating flowchart...
const onLayout = (direction: string) => {
const isHorizontal = direction === 'LR'
dagreGraph.setGraph({ rankdir: direction })
elements.value.forEach((el) => {
if (isNode(el)) {
dagreGraph.setNode(el.id, { width: 150, height: 50 })
} else {
dagreGraph.setEdge(el.source, el.target)
}
})
dagre.layout(dagreGraph)
elements.value = elements.value.map((el) => {
if (isNode(el)) {
const nodeWithPosition = dagreGraph.node(el.id)
el.targetPosition = isHorizontal ? Position.Left : Position.Top
el.sourcePosition = isHorizontal ? Position.Right : Position.Bottom
el.position = { x: nodeWithPosition.x, y: nodeWithPosition.y }
}
return el
})
console.log(elements.value)
}
</script>
<template>
<div class="layoutflow">
<Flow
:elements="elements"
:node-extent="nodeExtent"
:connection-mode="ConnectionMode.Loose"
@connect="onConnect"
@clements-remove="onElementsRemove"
@load="() => onLayout('TB')"
>
<Controls />
</Flow>
<div class="controls">
<button class="button" :style="{ marginRight: 10 }" @click="() => onLayout('TB')">vertical layout</button>
<button class="button" @click="() => onLayout('LR')">horizontal layout</button>
</div>
</div>
</template>
<style>
.layoutflow {
flex-grow: 1;
position: relative;
}
.layoutflow .controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 10;
}
.controls button {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,71 @@
import { Elements, XYPosition } from '@braks/vue-flow'
const position: XYPosition = { x: 0, y: 0 }
const elements: Elements = [
{
id: '1',
type: 'input',
data: { label: 'input' },
position,
},
{
id: '2',
data: { label: 'node 2' },
position,
},
{
id: '2a',
data: { label: 'node 2a' },
position,
},
{
id: '2b',
data: { label: 'node 2b' },
position,
},
{
id: '2c',
data: { label: 'node 2c' },
position,
},
{
id: '2d',
data: { label: 'node 2d' },
position,
},
{
id: '3',
data: { label: 'node 3' },
position,
},
{
id: '4',
data: { label: 'node 4' },
position,
},
{
id: '5',
data: { label: 'node 5' },
position,
},
{
id: '6',
type: 'output',
data: { label: 'output' },
position,
},
{ id: '7', type: 'output', data: { label: 'output' }, position: { x: 400, y: 450 } },
{ id: 'e12', source: '1', target: '2', type: 'smoothstep', animated: true },
{ id: 'e13', source: '1', target: '3', type: 'smoothstep', animated: true },
{ id: 'e22a', source: '2', target: '2a', type: 'smoothstep', animated: true },
{ id: 'e22b', source: '2', target: '2b', type: 'smoothstep', animated: true },
{ id: 'e22c', source: '2', target: '2c', type: 'smoothstep', animated: true },
{ id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep', animated: true },
{ id: 'e45', source: '4', target: '5', type: 'smoothstep', animated: true },
{ id: 'e56', source: '5', target: '6', type: 'smoothstep', animated: true },
{ id: 'e57', source: '5', target: '7', type: 'smoothstep', animated: true },
]
export default elements

View File

@@ -0,0 +1,20 @@
<script lang="ts" setup>
import { Flow, Background, Connection, Elements, Edge, removeElements, addEdge } from '@braks/vue-flow'
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, class: 'light' },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, class: 'light' },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, class: 'light' },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
]
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
</script>
<template>
<Flow :elements="elements" @elements-remove="onElementsRemove" @connect="onConnect">
<Background />
</Flow>
</template>

View File

@@ -0,0 +1,24 @@
<script lang="ts" setup>
import Flow from './Flow.vue'
</script>
<template>
<div class="vue-flow__example-multiflows flex-1">
<Flow />
<Flow />
</div>
</template>
<style>
.vue-flow__example-multiflows {
display: flex;
height: 100%;
}
.vue-flow__example-multiflows .vue-flow {
width: 100%;
height: 100%;
}
.vue-flow__example-multiflows .vue-flow:first-child {
border-right: 2px solid #333;
}
</style>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import { Flow, addEdge, Connection, Edge, Elements, isEdge, FlowInstance, Position } from '@braks/vue-flow'
const initialElements: Elements = [
{
id: '1',
sourcePosition: Position.Right,
type: 'input',
data: { label: 'Input' },
position: { x: 0, y: 80 },
},
{
id: '2',
type: 'output',
sourcePosition: Position.Right,
targetPosition: Position.Left,
data: { label: 'A Node' },
position: { x: 250, y: 0 },
},
{ id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true },
]
const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 }
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onLoad = (flowInstance: FlowInstance) => flowInstance.fitView()
const changeType = () => {
elements.value = elements.value.map((el) => {
if (isEdge(el) || el.type === 'input') return el
return {
...el,
type: el.type === 'default' ? 'output' : 'default',
}
})
}
</script>
<template>
<Flow :elements="elements" @connect="onConnect" @load="onLoad">
<button class="button mt-2" :style="buttonStyle" @click="changeType">change type</button>
</Flow>
</template>

View File

@@ -0,0 +1,11 @@
<script lang="ts" setup>
import { NodeProps } from '@braks/vue-flow'
interface NodeAProps extends NodeProps {
nodeStyles: Record<string, any>
}
const props = defineProps<NodeAProps>()
</script>
<template>
<div :style="props.nodeStyles">A</div>
</template>

View File

@@ -0,0 +1,11 @@
<script lang="ts" setup>
import { NodeProps } from '@braks/vue-flow'
interface NodeBPro extends NodeProps {
nodeStyles: Record<string, any>
}
const props = defineProps<NodeBPro>()
</script>
<template>
<div :style="props.nodeStyles">B</div>
</template>

View File

@@ -0,0 +1,60 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import { Flow, Elements, Position, NodeType, Connection, Edge, addEdge } from '@braks/vue-flow'
import NodeA from './NodeA.vue'
import NodeB from './NodeB.vue'
const initialElements: Elements = [
{
id: '1',
sourcePosition: Position.Right,
type: 'input',
data: { label: 'Input' },
position: { x: 0, y: 80 },
},
{
id: '2',
type: 'a',
sourcePosition: Position.Right,
targetPosition: Position.Left,
data: { label: 'A Node' },
position: { x: 250, y: 0 },
},
]
const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 }
const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' }
type NodeTypesObject = {
[key: string]: Record<string, NodeType>
}
const nodeTypesObjects: NodeTypesObject = {
a: {
a: NodeA as NodeType,
},
b: {
b: NodeB as NodeType,
},
}
const elements = ref(initialElements)
const nodeTypesId = ref('a')
const changeType = () => {
const id = nodeTypesId.value === 'a' ? 'b' : 'a'
elements.value[1].type = id
nodeTypesId.value = id
}
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
</script>
<template>
<Flow :elements="elements" :node-types="nodeTypesObjects[nodeTypesId]" :node-types-id="nodeTypesId" @connect="onConnect">
<template #node-a>
<NodeA :node-styles="nodeStyles" />
</template>
<template #node-b>
<NodeB :node-styles="nodeStyles" />
</template>
<button class="button mt-2" :style="buttonStyle" @click="changeType">change type</button>
</Flow>
</template>

View File

@@ -0,0 +1,172 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import {
Flow,
removeElements,
addEdge,
MiniMap,
Controls,
Background,
isNode,
Node,
Elements,
FlowElement,
FlowInstance,
FlowTransform,
SnapGrid,
ArrowHeadType,
Connection,
Edge,
} from '@braks/vue-flow'
const onNodeDragStart = (node: Node) => console.log('drag start', node)
const onNodeDrag = (node: Node) => console.log('drag', node)
const onNodeDragStop = (node: Node) => console.log('drag stop', node)
const onNodeDoubleClick = (node: Node) => console.log('node double click', node)
const onPaneClick = (event: MouseEvent) => console.log('pane click', event)
const onPaneScroll = (event?: MouseEvent) => console.log('pane scroll', event)
const onPaneContextMenu = (event: MouseEvent) => console.log('pane context menu', event)
const onSelectionDrag = (nodes: Node[]) => console.log('selection drag', nodes)
const onSelectionDragStart = (nodes: Node[]) => console.log('selection drag start', nodes)
const onSelectionDragStop = (nodes: Node[]) => console.log('selection drag stop', nodes)
const onSelectionContextMenu = (event: MouseEvent, nodes: Node[]) => {
event.preventDefault()
console.log('selection context menu', nodes)
}
const onElementClick = (element: FlowElement) => console.log(`${isNode(element) ? 'node' : 'edge'} click:`, element)
const onSelectionChange = (elements: Elements | null) => console.log('selection change', elements)
const onLoad = (flowInstance: FlowInstance) => {
console.log('flow loaded:', flowInstance)
flowInstance.fitView()
}
const onMoveEnd = (transform?: FlowTransform) => console.log('zoom/move end', transform)
const onEdgeContextMenu = (edge: Edge) => console.log('edge context menu', edge)
const onEdgeMouseEnter = (edge: Edge) => console.log('edge mouse enter', edge)
const onEdgeMouseMove = (edge: Edge) => console.log('edge mouse move', edge)
const onEdgeMouseLeave = (edge: Edge) => console.log('edge mouse leave', edge)
const onEdgeDoubleClick = (edge: Edge) => console.log('edge double click', edge)
const initialElements: Elements = [
{
id: '1',
type: 'input',
data: {
label: 'Welcome to <strong>Vue Flow!</strong>',
},
position: { x: 250, y: 0 },
},
{
id: '2',
data: {
label: 'This is a <strong>default node</strong>',
},
position: { x: 100, y: 100 },
},
{
id: '3',
data: {
label: 'This one has a <strong>custom style</strong>',
},
position: { x: 400, y: 100 },
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
},
{
id: '4',
position: { x: 250, y: 200 },
data: {
label: `You can find the docs on
<a href="https://github.com/bcakmakoglu/vue-flow" target="_blank" rel="noopener noreferrer">
Github
</a>`,
},
},
{
id: '5',
data: {
label: 'Or check out the other <strong>examples</strong>',
},
position: { x: 250, y: 325 },
},
{
id: '6',
type: 'output',
data: {
label: 'An <strong>output node</strong>',
},
position: { x: 100, y: 480 },
},
{ id: '7', type: 'output', data: { label: 'Another output node' }, position: { x: 400, y: 450 } },
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' },
{ id: 'e4-5', source: '4', target: '5', arrowHeadType: ArrowHeadType.Arrow, label: 'edge with arrow head' },
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
{
id: 'e5-7',
source: '5',
target: '7',
type: 'step',
style: { stroke: '#f6ab6c' },
label: 'a step edge',
animated: true,
labelStyle: { fill: '#f6ab6c', fontWeight: 700 },
},
]
const connectionLineStyle: CSSProperties = { stroke: '#ddd' }
const snapGrid: SnapGrid = [16, 16]
const nodeStrokeColor = (n: Node): string => {
if (n.style?.background) return n.style.background as string
if (n.type === 'input') return '#0041d0'
if (n.type === 'output') return '#ff0072'
if (n.type === 'default') return '#1a192b'
return '#eee'
}
const nodeColor = (n: Node): string => {
if (n.style?.background) return n.style.background as string
return '#fff'
}
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
</script>
<template>
<Flow
:elements="elements"
:connection-line-style="connectionLineStyle"
:snap-to-grid="true"
:snap-grid="snapGrid"
@element-click="onElementClick"
@elements-remove="onElementsRemove"
@eonnect="onConnect"
@pane-click="onPaneClick"
@pane-scroll="onPaneScroll"
@pane-contex-menu="onPaneContextMenu"
@node-drag-start="onNodeDragStart"
@node-drag="onNodeDrag"
@node-drag-stop="onNodeDragStop"
@node-double-click="onNodeDoubleClick"
@selection-drag-start="onSelectionDragStart"
@selection-drag="onSelectionDrag"
@selection-drag-stop="onSelectionDragStop"
@selection-context-menu="onSelectionContextMenu"
@selection-change="onSelectionChange"
@move-end="onMoveEnd"
@load="onLoad"
@edge-context-menu="onEdgeContextMenu"
@edge-mouse-enter="onEdgeMouseEnter"
@edge-mouse-move="onEdgeMouseMove"
@edge-mouse-leave="onEdgeMouseLeave"
@edge-double-click="onEdgeDoubleClick"
>
<MiniMap :node-stroke-color="nodeStrokeColor" :node-color="nodeColor" :node-border-radius="2" />
<Controls />
<Background color="#aaa" gap="20" />
</Flow>
</template>

View File

@@ -0,0 +1,97 @@
<script lang="ts" setup>
import {
Flow,
addEdge,
removeElements,
Controls,
FlowInstance,
FlowElement,
Connection,
Edge,
Elements,
ConnectionMode,
useStore,
} from '@braks/vue-flow'
import Sidebar from './Sidebar.vue'
const onElementClick = (element: FlowElement) => console.log('click', element)
const onLoad = (flowInstance: FlowInstance) => console.log('flow loaded:', flowInstance)
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
]
useStore()
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
</script>
<template>
<div class="providerflow">
<Sidebar />
<div class="vue-flow-wrapper">
<Flow
:elements="elements"
:connection-mode="ConnectionMode.Loose"
@element-click="onElementClick"
@connect="onConnect"
@elements-remove="onElementsRemove"
@load="onLoad"
>
<Controls />
</Flow>
</div>
</div>
</template>
<style>
.providerflow {
flex-direction: column;
display: flex;
height: 100%;
width: 100%;
}
.providerflow aside {
border-right: 1px solid #eee;
padding: 15px 10px;
font-size: 12px;
background: #fcfcfc;
}
.providerflow aside .description {
margin-bottom: 10px;
}
.providerflow aside .title {
font-weight: 700;
margin-bottom: 5px;
}
.providerflow aside .transform {
margin-bottom: 20px;
}
.providerflow .vue-flow-wrapper {
flex-grow: 1;
height: 100%;
}
.providerflow .selectall {
margin-top: 10px;
}
@media screen and (min-width: 768px) {
.providerflow {
flex-direction: row;
}
.providerflow aside {
max-width: 250px;
}
}
</style>

View File

@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { useStore } from '@braks/vue-flow'
const store = useStore()
const nodes = computed(() => store.nodes)
const transform = computed(() => store.transform)
const selectAll = () => {
store.selectedElements = nodes.value
store.unsetUserSelection()
}
</script>
<template>
<aside>
<div class="description">This is an example of how you can access the internal state outside of the Vue Flow component.</div>
<div class="title">Zoom & pan transform</div>
<div class="transform">{{ [transform[0].toFixed(2), transform[1].toFixed(2), transform[2].toFixed(2)] }}</div>
<div class="title">Nodes</div>
<div v-for="node of nodes" :key="node.id">
Node {{ node.id }} - x: {{ node.__rf.position.x.toFixed(2) }}, y: {{ node.__rf.position.y.toFixed(2) }}
</div>
<div class="selectall">
<button class="button" @click="selectAll">select all nodes</button>
</div>
</aside>
</template>

View File

@@ -0,0 +1,60 @@
<script lang="ts" setup>
import localforage from 'localforage'
import { useZoomPanHelper, FlowInstance, FlowExportObject, Node } from '@braks/vue-flow'
localforage.config({
name: 'vue-flow',
storeName: 'flows',
})
const flowKey = 'example-flow'
const getNodeId = () => `randomnode_${+new Date()}`
const { transform } = useZoomPanHelper()
type ControlsProps = {
flowInstance?: FlowInstance
}
const props = defineProps<ControlsProps>()
const emit = defineEmits(['restore', 'add'])
const onSave = () => {
if (props.flowInstance) {
const flow = props.flowInstance.toObject()
localforage.setItem(flowKey, flow)
}
}
const onRestore = () => {
const restoreFlow = async () => {
const flow: FlowExportObject | null = await localforage.getItem(flowKey)
console.log(flow)
if (flow) {
const [x = 0, y = 0] = flow.position
emit('restore', flow.elements ?? [])
transform({ x, y, zoom: flow.zoom || 0 })
}
}
restoreFlow()
}
const onAdd = () => {
const newNode = {
id: `random_node-${getNodeId()}`,
data: { label: 'Added node' },
position: { x: Math.random() * window.innerWidth - 100, y: Math.random() * window.innerHeight },
} as Node
emit('add', newNode)
}
</script>
<template>
<div class="save__controls">
<button class="button" @click="onSave">save</button>
<button class="button" @click="onRestore">restore</button>
<button class="button" @click="onAdd">add node</button>
</div>
</template>

View File

@@ -0,0 +1,37 @@
<script lang="ts" setup>
import { Flow, addEdge, Connection, Node, Edge, Elements, FlowInstance, removeElements } from '@braks/vue-flow'
import Controls from './Controls.vue'
const initialElements: Elements = [
{ id: '1', data: { label: 'Node 1' }, position: { x: 100, y: 100 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } },
{ id: 'e1-2', source: '1', target: '2' },
]
const elements = ref(initialElements)
const flowInstance = ref()
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onLoad = (instance: FlowInstance) => (flowInstance.value = instance)
const onRestore = (els: Elements) => (elements.value = els)
const onAdd = (el: Node) => (elements.value = elements.value.concat(el))
</script>
<template>
<Flow :elements="elements" @elements-remove="onElementsRemove" @connect="onConnect" @load="onLoad">
<Controls :flow-instance="flowInstance" @restore="onRestore" @add="onAdd" />
</Flow>
</template>
<style>
.save__controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 4;
font-size: 12px;
}
.save__controls button {
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,126 @@
<script lang="ts" setup>
const examples = [
{
path: '/',
label: 'Overview',
},
{
path: '/basic',
label: 'Basic',
},
{
path: '/custom-connectionline',
label: 'Custom Connectionline',
},
{
path: '/custom-node',
label: 'Custom Node',
},
{
path: '/drag-n-drop',
label: 'Drag and Drop',
},
{
path: '/edges',
label: 'Edges',
},
{
path: '/button-edge',
label: 'Edge with Button',
},
{
path: '/edge-types',
label: 'Edge Types',
},
{
path: '/empty',
label: 'Empty',
},
{
path: '/hidden',
label: 'Hidden',
},
{
path: '/interaction',
label: 'Interaction',
},
{
path: '/layouting',
label: 'Layouting',
},
{
path: '/multi-flows',
label: 'Multi Flows',
},
{
path: '/node-type-change',
label: 'Node Type Change',
},
{
path: '/node-types-id-change',
label: 'Node Types ID Change',
},
{
path: '/provider',
label: 'Provider',
},
{
path: '/save-restore',
label: 'Save and Restore',
},
{
path: '/stress',
label: 'Stress',
},
{
path: '/switch',
label: 'Switch',
},
{
path: '/unidirectional',
label: 'Unidirectional',
},
{
path: '/updatable-edge',
label: 'Updatable Edge',
},
{
path: '/update-node',
label: 'Update Node',
},
{
path: '/update-node-internals',
label: 'Update Node Internals',
},
{
path: '/validation',
label: 'Validation',
},
{
path: '/zoom-pan-helper',
label: 'Zoom Pan Helper',
},
]
</script>
<template>
<aside>
<div class="flex flex-col justify-center items-start">
<router-link v-for="(example, i) of examples" :key="`example-link-${i}`" class="example-link" :to="example.path">
{{ example.label }}
</router-link>
</div>
<slot></slot>
</aside>
</template>
<style>
.example-link {
@apply text-lg text-white hover:text-black py-2 underline;
}
aside {
border-right: 1px solid #eee;
padding: 15px 10px;
height: 100%; /* Full-height: remove this if you want "auto" height */
width: 280px; /* Set the width of the sidebar */
overflow-x: hidden; /* Disable horizontal scroll */
}
</style>

View File

@@ -0,0 +1,90 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import {
Flow,
removeElements,
addEdge,
MiniMap,
isNode,
Controls,
Background,
FlowInstance,
Elements,
Connection,
Edge,
} from '@braks/vue-flow'
function getElements(xElements = 10, yElements = 10): Elements {
const initialElements = []
let nodeId = 1
let recentNodeId = null
for (let y = 0; y < yElements; y++) {
for (let x = 0; x < xElements; x++) {
const position = { x: x * 100, y: y * 50 }
const data = { label: `Node ${nodeId}` }
const node = {
id: nodeId.toString(),
style: { width: 50, fontSize: 11 },
data,
position,
}
initialElements.push(node)
if (recentNodeId && nodeId <= xElements * yElements) {
initialElements.push({ id: `${x}-${y}`, source: recentNodeId.toString(), target: nodeId.toString() })
}
recentNodeId = nodeId
nodeId++
}
}
return initialElements
}
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 }
const onLoad = (flowInstance: FlowInstance) => {
flowInstance.fitView()
console.log(flowInstance.getElements())
}
const initialElements: Elements = getElements(10, 10)
const elements = ref(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const updatePos = () => {
elements.value = elements.value.map((el) => {
if (isNode(el)) {
return {
...el,
position: {
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
},
}
}
return el
})
}
const updateElements = () => {
const grid = Math.ceil(Math.random() * 10)
elements.value = getElements(grid, grid)
}
</script>
<template>
<Flow :elements="elements" @load="onLoad" @elementsRemove="onElementsRemove" @connect="onConnect">
<MiniMap />
<Controls />
<Background />
<div :style="buttonWrapperStyles">
<button style="margin-right: 5px" @click="updatePos">change pos</button>
<button @click="updateElements">update elements</button>
</div>
</Flow>
</template>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup>
import { Flow, removeElements, addEdge, Node, FlowElement, Elements, Connection, Edge } from '@braks/vue-flow'
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node)
const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element)
const elementsA: Elements = [
{ id: '1a', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, class: 'light' },
{ id: '2a', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, class: 'light' },
{ id: '3a', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, class: 'light' },
{ id: '4a', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, class: 'light' },
{ id: 'e1-2', source: '1a', target: '2a' },
{ id: 'e1-3', source: '1a', target: '3a' },
]
const elementsB: Elements = [
{ id: 'inputb', type: 'input', data: { label: 'Input' }, position: { x: 300, y: 5 }, class: 'light' },
{ id: '1b', data: { label: 'Node 1' }, position: { x: 0, y: 100 }, class: 'light' },
{ id: '2b', data: { label: 'Node 2' }, position: { x: 200, y: 100 }, class: 'light' },
{ id: '3b', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, class: 'light' },
{ id: '4b', data: { label: 'Node 4' }, position: { x: 600, y: 100 }, class: 'light' },
{ id: 'e1b', source: 'inputb', target: '1b' },
{ id: 'e2b', source: 'inputb', target: '2b' },
{ id: 'e3b', source: 'inputb', target: '3b' },
{ id: 'e4b', source: 'inputb', target: '4b' },
]
const elements = ref(elementsA)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
</script>
<template>
<Flow
:elements="elements"
@element-click="onElementClick"
@elements-remove="onElementsRemove"
@connect="onConnect"
@node-drag-stop="onNodeDragStop"
>
<div :style="{ position: 'absolute', right: '10px', top: '10px', zIndex: 4 }">
<button class="button" style="margin-right: 5px" @click="() => (elements = elementsA)">flow a</button>
<button class="button" @click="() => (elements = elementsB)">flow b</button>
</div>
</Flow>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import { Handle, NodeProps, Position } from '@braks/vue-flow'
interface CustomNodeProps extends NodeProps {
id: string
}
const props = defineProps<CustomNodeProps>()
const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' }
</script>
<template>
<div :style="nodeStyles">
<div>node {{ props.id }}</div>
<Handle id="left" type="source" :position="Position.Left" />
<Handle id="right" type="source" :position="Position.Right" />
<Handle id="top" type="source" :position="Position.Top" />
<Handle id="bottom" type="source" :position="Position.Bottom" />
</div>
</template>

View File

@@ -0,0 +1,205 @@
<script lang="ts" setup>
import {
Flow,
NodeType,
addEdge,
useZoomPanHelper,
Elements,
Connection,
Edge,
ElementId,
Node,
ConnectionLineType,
ConnectionMode,
updateEdge,
ArrowHeadType,
} from '@braks/vue-flow'
import CustomNode from './CustomNode.vue'
const initialElements: Elements = [
{
id: '00',
type: 'custom',
position: { x: 300, y: 250 },
},
{
id: '01',
type: 'custom',
position: { x: 100, y: 50 },
},
{
id: '02',
type: 'custom',
position: { x: 500, y: 50 },
},
{
id: '03',
type: 'custom',
position: { x: 500, y: 500 },
},
{
id: '04',
type: 'custom',
position: { x: 100, y: 500 },
},
{
id: '10',
type: 'custom',
position: { x: 300, y: 5 },
},
{
id: '20',
type: 'custom',
position: { x: 600, y: 250 },
},
{
id: '30',
type: 'custom',
position: { x: 300, y: 600 },
},
{
id: '40',
type: 'custom',
position: { x: 5, y: 250 },
},
{
id: 'e0-1a',
source: '00',
target: '01',
sourceHandle: 'left',
targetHandle: 'bottom',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-1b',
source: '00',
target: '01',
sourceHandle: 'top',
targetHandle: 'right',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-2a',
source: '00',
target: '02',
sourceHandle: 'top',
targetHandle: 'left',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-2b',
source: '00',
target: '02',
sourceHandle: 'right',
targetHandle: 'bottom',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-3a',
source: '00',
target: '03',
sourceHandle: 'right',
targetHandle: 'top',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-3b',
source: '00',
target: '03',
sourceHandle: 'bottom',
targetHandle: 'left',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-4a',
source: '00',
target: '04',
sourceHandle: 'bottom',
targetHandle: 'right',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-4b',
source: '00',
target: '04',
sourceHandle: 'left',
targetHandle: 'top',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-10',
source: '00',
target: '10',
sourceHandle: 'top',
targetHandle: 'bottom',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-20',
source: '00',
target: '20',
sourceHandle: 'right',
targetHandle: 'left',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-30',
source: '00',
target: '30',
sourceHandle: 'bottom',
targetHandle: 'top',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
{
id: 'e0-40',
source: '00',
target: '40',
sourceHandle: 'left',
targetHandle: 'right',
type: 'smoothstep',
arrowHeadType: ArrowHeadType.Arrow,
},
]
const nodeTypes: Record<string, NodeType> = {
custom: CustomNode as NodeType,
}
let id = 4
const getId = (): ElementId => `${id++}`
const elements = ref(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge({ ...params, type: 'smoothstep' }, elements.value))
const { project } = useZoomPanHelper()
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
(elements.value = updateEdge(oldEdge, newConnection, elements.value))
const onPaneClick = (evt: MouseEvent) =>
(elements.value = elements.value.concat({
id: getId(),
position: project({ x: evt.clientX, y: evt.clientY - 40 }),
type: 'custom',
} as Node))
</script>
<template>
<Flow
:elements="elements"
:node-types="nodeTypes"
:connection-line-type="ConnectionLineType.SmoothStep"
:connection-mode="ConnectionMode.Loose"
@connect="onConnect"
@pane-click="onPaneClick"
@edge-pdate="onEdgeUpdate"
/>
</template>

View File

@@ -0,0 +1,89 @@
<script lang="ts" setup>
import {
Flow,
Controls,
updateEdge,
addEdge,
Elements,
FlowInstance,
Connection,
Edge,
removeElements,
FlowEvents,
ConnectionMode,
} from '@braks/vue-flow'
const initialElements: Elements = [
{
id: '1',
type: 'input',
data: {
label: 'Node <strong>A</strong>',
},
position: { x: 250, y: 0 },
},
{
id: '2',
data: {
label: 'Node <strong>B</strong>',
},
position: { x: 100, y: 100 },
},
{
id: '3',
data: {
label: 'Node <strong>C</strong>',
},
position: { x: 400, y: 100 },
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
},
{ id: 'e1-2', source: '1', target: '2', label: 'Updateable edge' },
]
const elements = ref(initialElements)
const onLoad = (flowInstance: FlowInstance) => flowInstance.fitView()
const onEdgeUpdateStart = (edge: Edge) => console.log('start update', edge)
const onEdgeUpdateEnd = (edge: Edge) => console.log('end update', edge)
const onEdgeUpdate = ({ edge, connection }: FlowEvents['edgeUpdate']) =>
(elements.value = updateEdge(edge, connection, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
</script>
<template>
<Flow
:elements="elements"
:snap-to-grid="true"
:connection-mode="ConnectionMode.Loose"
@load="onLoad"
@edge-update="onEdgeUpdate"
@connect="onConnect"
@edge-update-start="onEdgeUpdateStart"
@elements-remove="onElementsRemove"
@edge-update-end="onEdgeUpdateEnd"
>
<Controls />
</Flow>
</template>
<style>
.updatenode__controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 4;
font-size: 12px;
}
.updatenode__controls label {
display: block;
}
.updatenode__bglabel {
margin-top: 10px;
}
.updatenode__checkboxwrapper {
margin-top: 10px;
display: flex;
align-items: center;
}
</style>

View File

@@ -0,0 +1,73 @@
<script lang="ts" setup>
import { Flow, Elements } from '@braks/vue-flow'
const initialElements: Elements = [
{ id: '1', data: { label: '-' }, position: { x: 100, y: 100 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } },
{ id: 'e1-2', source: '1', target: '2' },
]
const elements = ref<Elements>(initialElements)
const nodeName = ref<string>('Node 1')
const nodeBg = ref<string>('#eee')
const nodeHidden = ref<boolean>(false)
const updateNode = () => {
elements.value = elements.value.map((el) => {
if (el.id === '1') {
// it's important that you create a new object here in order to notify react flow about the change
el.data = {
...el.data,
label: nodeName.value,
}
el.style = { backgroundColor: nodeBg.value }
el.isHidden = nodeHidden.value
}
return el
})
}
watchEffect(() => {
updateNode()
})
</script>
<template>
<Flow :elements="elements" :default-zoom="1.5" :min-zoom="0.2" :max-zoom="4">
<div class="updatenode__controls p-4 bg-gray-300 rounded-xl">
<label>label:</label>
<input v-model="nodeName" />
<label class="updatenode__bglabel">background:</label>
<input v-model="nodeBg" type="color" />
<div class="updatenode__checkboxwrapper">
<label>hidden:</label>
<input v-model="nodeHidden" type="checkbox" />
</div>
</div>
</Flow>
</template>
<style>
.updatenode__controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 4;
font-size: 12px;
}
.updatenode__controls label {
display: block;
}
.updatenode__bglabel {
margin-top: 10px;
}
.updatenode__checkboxwrapper {
margin-top: 10px;
display: flex;
align-items: center;
}
</style>

View File

@@ -0,0 +1,25 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import { Handle, Position, NodeProps } from '@braks/vue-flow'
interface CustomNodeProps extends NodeProps {
data: any
}
const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' }
const props = defineProps<CustomNodeProps>()
</script>
<template>
<div :style="nodeStyles">
<Handle type="target" :position="Position.Left" />
<div>output handle count: {{ props.data.handleCount }}</div>
<Handle
v-for="i of props.data.handleCount"
:id="`handle-${i}`"
:key="`handle-${i}`"
type="source"
:position="Position.Right"
:style="{ top: 10 * i + props.data.handlePosition * 10 }"
/>
</div>
</template>

View File

@@ -0,0 +1,83 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import {
Flow,
NodeType,
addEdge,
useZoomPanHelper,
Node,
Elements,
Connection,
Edge,
ElementId,
Position,
isEdge,
FlowInstance,
} from '@braks/vue-flow'
import CustomNode from './CustomNode.vue'
const initialHandleCount = 1
const initialElements: Elements = [
{
id: '1',
type: 'custom',
data: { label: 'Node 1', handleCount: initialHandleCount, handlePosition: 0 },
position: { x: 250, y: 5 },
},
]
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: '10px', top: '10px', zIndex: 10 }
const nodeTypes: Record<string, NodeType> = {
custom: CustomNode as NodeType,
}
let id = 5
const getId = (): ElementId => `${id++}`
const elements = ref<Elements>(initialElements)
const flowInstance = ref<FlowInstance>()
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const { project } = useZoomPanHelper()
const onPaneClick = (evt: MouseEvent) =>
(elements.value = elements.value.concat({
id: getId(),
position: project({ x: evt.clientX, y: evt.clientY - 40 }),
data: { label: 'new node' },
targetPosition: Position.Left,
sourcePosition: Position.Right,
} as Node))
const toggleHandleCount = () =>
(elements.value = elements.value.map((el) => {
if (isEdge(el)) {
return el
}
return { ...el, data: { ...el.data, handleCount: el.data?.handleCount === 1 ? 2 : 1 } }
}))
const toggleHandlePosition = () =>
(elements.value = elements.value.map((el) => {
if (isEdge(el)) return el
return { ...el, data: { ...el.data, handlePosition: el.data?.handlePosition === 0 ? 1 : 0 } }
}))
const onLoad = (instance: FlowInstance) => {
instance.fitView()
flowInstance.value = instance
}
const updateNodeInternals = () => flowInstance.value?.updateNodeInternals('1')
</script>
<template>
<Flow :elements="elements" :node-types="nodeTypes" @connect="onConnect" @pane-click="onPaneClick" @load="onLoad">
<div :style="buttonWrapperStyles">
<button class="button" @click="toggleHandleCount">toggle handle count</button>
<button class="button" @click="toggleHandlePosition">toggle handle position</button>
<button class="button" @click="updateNodeInternals">update node internals</button>
</div>
</Flow>
</template>

View File

@@ -0,0 +1,9 @@
<script lang="ts" setup>
import { Position, Handle, Connection } from '@braks/vue-flow'
const isValidConnection = (connection: Connection) => connection.target === 'B'
</script>
<template>
<div>Only connectable with B</div>
<Handle type="source" :position="Position.Right" :is-valid-connection="isValidConnection" />
</template>

View File

@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { Position, Handle, Connection, NodeProps } from '@braks/vue-flow'
interface CustomNodeProps extends NodeProps {
id: string
}
const props = defineProps<CustomNodeProps>()
const isValidConnection = (connection: Connection) => connection.target === 'B'
</script>
<template>
<Handle type="target" :position="Position.Left" :is-valid-connection="isValidConnection" />
<div>{{ props.id }}</div>
<Handle type="source" :position="Position.Right" :is-valid-connection="isValidConnection" />
</template>

View File

@@ -0,0 +1,84 @@
<script lang="ts" setup>
import {
Flow,
addEdge,
Handle,
Connection,
Position,
Elements,
Edge,
OnConnectStartParams,
NodeProps,
FlowInstance,
NodeType,
} from '@braks/vue-flow'
import CustomInput from './CustomInput.vue'
import CustomNode from './CustomNode.vue'
const initialElements: Elements = [
{ id: '0', type: 'custominput', position: { x: 0, y: 150 } },
{ id: 'A', type: 'customnode', position: { x: 250, y: 0 } },
{ id: 'B', type: 'customnode', position: { x: 250, y: 150 } },
{ id: 'C', type: 'customnode', position: { x: 250, y: 300 } },
]
const onLoad = (reactFlowInstance: FlowInstance) => reactFlowInstance.fitView()
const onConnectStart = ({ nodeId, handleType }: OnConnectStartParams) => console.log('on connect start', { nodeId, handleType })
const onConnectStop = (event: MouseEvent) => console.log('on connect stop', event)
const onConnectEnd = (event: MouseEvent) => console.log('on connect end', event)
const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => {
console.log('on connect', params)
elements.value = addEdge(params, elements.value)
}
const nodeTypes: Record<string, NodeType> = {
custominput: CustomInput as NodeType,
customnode: CustomNode as NodeType,
}
</script>
<template>
<Flow
:elements="elements"
:select-nodes-on-drag="false"
class="validationflow"
:node-types="nodeTypes"
@connect="onConnect"
@oad="onLoad"
@connect-start="onConnectStart"
@connect-stop="onConnectStop"
@connect-end="onConnectEnd"
/>
</template>
<style>
.validationflow .vue-flow__node {
width: 150px;
border-radius: 5px;
padding: 10px;
color: #555;
border: 1px solid #ddd;
text-align: center;
font-size: 12px;
}
.validationflow .vue-flow__node-customnode {
background: #e6e6e9;
border: 1px solid #ddd;
}
.vue-flow__node-custominput .vue-flow__handle {
background: #e6e6e9;
}
.validationflow .vue-flow__node-custominput {
background: #fff;
}
.validationflow .vue-flow__handle-connecting {
background: #ff6060;
}
.validationflow .vue-flow__handle-valid {
background: #55dd99;
}
</style>

View File

@@ -0,0 +1,51 @@
<script lang="ts" setup>
import {
Flow,
removeElements,
addEdge,
Background,
MiniMap,
useZoomPanHelper,
Elements,
ElementId,
Connection,
Edge,
Node,
} from '@braks/vue-flow'
const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, class: 'light' },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, class: 'light' },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, class: 'light' },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
]
let id = 5
const getId = (): ElementId => `${id++}`
const { project } = useZoomPanHelper()
const elements = ref(initialElements)
const onPaneClick = (evt: MouseEvent) => {
const projectedPosition = project({ x: evt.clientX, y: evt.clientY - 40 })
elements.value = elements.value.concat({
id: getId(),
position: projectedPosition,
data: {
label: `${projectedPosition.x}-${projectedPosition.y}`,
},
} as Node)
}
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
</script>
<template>
<Flow :elements="elements" @elements-remove="onElementsRemove" @connect="onConnect" @pane-click="onPaneClick">
<Background />
<MiniMap />
</Flow>
</template>

View File

@@ -0,0 +1,108 @@
#root{
text-transform: uppercase;
font-family: 'JetBrains Mono', monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
html,
body,
#root{
position: relative;
height: 100vh;
margin: 0;
color: #111;
overflow: hidden;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
background-image: url('./polygon-scatter.svg');
}
.backround-img {
background-position: center;
background-size: cover;
background-repeat: no-repeat;
background-image: url('./polygon-scatter.svg');
}
#root {
display: flex;
flex-direction: column;
}
header {
font-weight: 700;
}
.logo {
text-decoration: none;
display: block;
line-height: 1;
}
header a:hover {
color: #c9c9c9;
}
header select {
margin-left: 1em;
}
.overview-example__add {
display: none;
}
.vue-flow__node a {
font-weight: 700;
color: #111;
}
.vue-flow__node.dark-node {
background: #0041d0;
color: #f8f8f8;
}
.vue-flow__node.dark {
background: #557;
color: #f8f8f8;
}
.vue-flow__node-selectorNode {
font-size: 14px;
background: #f0f2f3;
border: 1px solid #555;
border-radius: 5px;
text-align: center;
}
.vue-flow__node-selectorNode .vue-flow__handle {
border-color: #f0f2f3;
}
@media screen and (min-width: 768px) {
.overview-example__add {
display: block;
}
}
.edgebutton {
border-radius: 999px;
cursor: pointer;
}
.edgebutton:hover {
box-shadow: 0 0 0 2px pink , 0 0 0 4px #f05f75;
}
#vue-flow-docs {
display: flex;
margin:0;
background: #fff;
}
.button {
@apply bg-gray-200 hover:bg-gray-300 focus:outline-none font-bold mr-2 border-2 p-2 rounded-xl;
}

View File

View File

@@ -0,0 +1 @@
<svg id="visual" viewBox="0 0 960 540" width="960" height="540" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><rect width="960" height="540" fill="#31475e"></rect><g><g transform="translate(57 45)"><path d="M0 -106.9L83.6 -66.7L104.2 23.8L46.4 96.3L-46.4 96.3L-104.2 23.8L-83.6 -66.7Z" fill="#3fb984"></path></g><g transform="translate(708 88)"><path d="M0 -66L51.6 -41.2L64.3 14.7L28.6 59.5L-28.6 59.5L-64.3 14.7L-51.6 -41.2Z" fill="#3fb984"></path></g><g transform="translate(823 510)"><path d="M0 -77L60.2 -48L75.1 17.1L33.4 69.4L-33.4 69.4L-75.1 17.1L-60.2 -48Z" fill="#3fb984"></path></g><g transform="translate(896 280)"><path d="M0 -45L35.2 -28.1L43.9 10L19.5 40.5L-19.5 40.5L-43.9 10L-35.2 -28.1Z" fill="#3fb984"></path></g><g transform="translate(287 458)"><path d="M0 -58L45.3 -36.2L56.5 12.9L25.2 52.3L-25.2 52.3L-56.5 12.9L-45.3 -36.2Z" fill="#3fb984"></path></g><g transform="translate(12 380)"><path d="M0 -101L79 -63L98.5 22.5L43.8 91L-43.8 91L-98.5 22.5L-79 -63Z" fill="#3fb984"></path></g><g transform="translate(609 440)"><path d="M0 -60L46.9 -37.4L58.5 13.4L26 54.1L-26 54.1L-58.5 13.4L-46.9 -37.4Z" fill="#3fb984"></path></g><g transform="translate(457 42)"><path d="M0 -81L63.3 -50.5L79 18L35.1 73L-35.1 73L-79 18L-63.3 -50.5Z" fill="#3fb984"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

50
examples-app/src/auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,50 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const EffectScope: typeof import('vue')['EffectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
}
export {}

8
examples-app/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

15
examples-app/src/main.ts Normal file
View File

@@ -0,0 +1,15 @@
import 'virtual:windi.css'
import { createApp } from 'vue'
import './assets/index.css'
import { createPinia } from 'pinia'
import { DraggablePlugin } from '@braks/revue-draggable'
import App from './App.vue'
import { router } from './router'
const app = createApp(App)
app.config.performance = true
app.use(router)
app.use(DraggablePlugin)
app.use(createPinia())
app.mount('#root')

113
examples-app/src/router.ts Normal file
View File

@@ -0,0 +1,113 @@
import { createRouter, createWebHashHistory, RouterOptions } from 'vue-router'
export const routes: RouterOptions['routes'] = [
{
path: '/',
redirect: '/overview',
},
{
path: '/basic',
component: () => import('./Basic/Basic.vue'),
},
{
path: '/custom-connectionline',
component: () => import('./CustomConnectionLine/CustomConnectionLine.vue'),
},
{
path: '/custom-node',
component: () => import('./CustomNode/CustomNode.vue'),
},
{
path: '/drag-n-drop',
component: () => import('./DragNDrop/DnD.vue'),
},
{
path: '/edges',
component: () => import('./Edges/EdgesExample.vue'),
},
{
path: '/button-edge',
component: () => import('./EdgeWithButton/EdgeWithButton.vue'),
},
{
path: '/edge-types',
component: () => import('./EdgeTypes/EdgeTypesExample.vue'),
},
{
path: '/empty',
component: () => import('./Empty/EmptyExample.vue'),
},
{
path: '/hidden',
component: () => import('./Hidden/HiddenExample.vue'),
},
{
path: '/interaction',
component: () => import('./Interaction/InteractionExample.vue'),
},
{
path: '/layouting',
component: () => import('./Layouting/LayoutingExample.vue'),
},
{
path: '/multi-flows',
component: () => import('./MultiFlows/MultiFlowsExample.vue'),
},
{
path: '/node-type-change',
component: () => import('./NodeTypeChange/NodeTypeChangeExample.vue'),
},
{
path: '/node-types-id-change',
component: () => import('./NodeTypesIdChange/NodeTypesIdChangeExample.vue'),
},
{
path: '/overview',
component: () => import('./Overview/Overview.vue'),
},
{
path: '/provider',
component: () => import('./Provider/ProviderExample.vue'),
},
{
path: '/save-restore',
component: () => import('./SaveRestore/SaveRestoreExample.vue'),
},
{
path: '/stress',
component: () => import('./Stress/StressExample.vue'),
},
{
path: '/switch',
component: () => import('./Switch/SwitchExample.vue'),
},
{
path: '/unidirectional',
component: () => import('./Unidirectional/UnidirectionalExample.vue'),
},
{
path: '/updateable-edge',
component: () => import('./UpdatableEdge/UpdatableEdgeExample.vue'),
},
{
path: '/update-node',
component: () => import('./UpdateNode/UpdateNodeExample.vue'),
},
{
path: '/update-node-internals',
component: () => import('./UpdateNodeInternals/UpdateNodeInternalsExample.vue'),
},
{
path: '/validation',
component: () => import('./Validation/ValidationExample.vue'),
},
{
path: '/zoom-pan-helper',
component: () => import('./ZoomPanHelper/ZoomPanHelperExample.vue'),
},
]
export const router = createRouter({
history: createWebHashHistory(),
routes,
})

View File

@@ -0,0 +1,16 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@@ -0,0 +1,27 @@
import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import WindiCSS from 'vite-plugin-windicss'
// https://vitejs.dev/config/
export default defineConfig({
alias: {
vue: resolve('./node_modules/vue'),
},
resolve: {
dedupe: ['vue'],
preserveSymlinks: false,
},
build: {
emptyOutDir: true,
},
plugins: [
vue(),
AutoImport({
imports: ['vue'],
dts: 'src/auto-imports.d.ts',
}),
WindiCSS(),
],
})

1791
examples-app/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -118,6 +118,7 @@ const edgePos = computed(() =>
</script>
<template>
<g
v-if="!props.edge.isHidden && isVisible(edgePos)"
:class="[
'vue-flow__edge',
`vue-flow__edge-${props.edge.type || 'default'}`,
@@ -136,7 +137,6 @@ const edgePos = computed(() =>
@mouseleave="onEdgeMouseLeave"
>
<slot
v-if="!props.edge.isHidden && isVisible(edgePos)"
v-bind="{
id: props.edge.id,
source: props.edge.source,

View File

@@ -32,9 +32,7 @@ const edges = computed(() => store.edges.filter((edge) => !edge.isHidden))
<template>
<svg :width="store.dimensions.width" :height="store.dimensions.height" class="vue-flow__edges">
<MarkerDefinitions :color="props.arrowHeadColor" />
<g
:transform="store.transform.length && `translate(${store.transform[0]},${store.transform[1]}) scale(${store.transform[2]})`"
>
<g :transform="`translate(${store.transform[0]},${store.transform[1]}) scale(${store.transform[2]})`">
<template v-for="edge of edges" :key="edge.id">
<Edge
:edge="edge"

View File

@@ -1,5 +1,4 @@
<script lang="ts" setup>
import { onMounted } from 'vue'
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
import { get } from '@vueuse/core'
import { pointer, select } from 'd3-selection'
@@ -40,7 +39,9 @@ const hooks = useHooks()
const zoomPaneEl = templateRef<HTMLDivElement>('zoomPane', null)
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k
(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,
@@ -52,7 +53,6 @@ const clampedX = clamp(props.defaultPosition[0], store.translateExtent[0][0], st
const clampedY = clamp(props.defaultPosition[1], store.translateExtent[0][1], store.translateExtent[1][1])
const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom)
const transform = ref({ x: clampedX, y: clampedY, zoom: clampedZoom })
const d3Zoom = ref(zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent))
const d3Selection = ref()
@@ -93,10 +93,12 @@ until(zoomPaneEl)
d3z.on('zoom', null)
} else {
d3z.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const flowTransform = eventToFlowTransform(event.transform)
transform.value = flowTransform
if (viewChanged(transform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform)
transform.value = flowTransform
hooks.move.trigger(flowTransform)
hooks.move.trigger(flowTransform)
}
})
}
})
@@ -190,7 +192,6 @@ store.dimensions = {
width: width.value,
height: height.value,
}
store.transform = [transform.value.x, transform.value.y, transform.value.zoom]
watch(width, (val) => (store.dimensions.width = val))
watch(height, (val) => (store.dimensions.height = val))