docs: replace codesandbox embed with repl (#122)

* docs: Animations

* docs: add confetti gun

* docs: update basic example on home page

* docs(deps): Add stackblitz sdk to deps

* chore: update yarn.lock

* docs: use vue repl

* docs: remove stackblitz sdk

* docs: basic repl example

* docs: use repl for examples/index.md (basic example)

* docs: pass ext to repl

* docs: add copy plugin

* docs: use import map instead of dynamic imports

* docs: use repl for custom node example

* docs: hide repl errors

* docs: use repl for custom connectionline example

* docs: use repl for edges example

* docs: exclude repl from ssr

* docs: use repl for nested example

* docs: use repl for stress example

* docs: rename customNode to custom-node

* docs: use repl for update-edge example

* docs: use repl for update-node example

* docs: use repl for validation example

* docs: use repl for save-restore example

* docs: scale down minimap in repl examples

* docs: use repl for dnd example

* docs: use repl for empty example

* docs: use repl for hidden example

* docs: use repl for interaction example

* docs: use repl for multi example

* docs: add pinia example with stackblitz

* docs: update basic example

* docs: update examples

* docs: remove transition from intro

* docs: update features

* update: README.md

* docs: scope css
This commit is contained in:
Braks
2022-05-26 19:23:35 +02:00
parent 6494b65b3e
commit 98a893108c
95 changed files with 2342 additions and 321 deletions

View File

@@ -22,16 +22,16 @@ the [examples](https://vueflow.dev/examples).
* [🎮 Quickstart](#-quickstart)
+ [ Vue 2](#-vue-2)
+ [🪴 Vue 2](#-vue-2)
* [🧪 Development](#-development)
+ [🐳 Dev Container](#-dev-container)
* [⭐ Stargazers](#-stargazers)
* [💝 Special Thanks](#-special-thanks)
* [⭐ Stargazers](#-stargazers)
## ⭐️ Features
- 👶 __Easy setup__: Get started hassle-free - Built-in zoom- & pan features, element dragging, selection and much more
@@ -50,6 +50,8 @@ the [examples](https://vueflow.dev/examples).
- 🕹 Controls: Control zoom behavior from a panel on the bottom left
- 🤖 And (many) more to come...
- 🦾 __Reliable__: Fully written in TypeScript
## 🛠 Setup
@@ -147,15 +149,14 @@ This project is built with
- [React Flow](https://reactflow.dev/)
- Vue flow is heavily based on [webkids'](https://webkid.io/) [react flow](https://reactflow.dev/). I wholeheartedly thank
them for their amazing work! Without them, Vue Flow would not exist.
Please consider [donating](https://github.com/sponsors/wbkd) to them!
them for their amazing work! Without them Vue Flow would not exist.
Please consider [donating](https://github.com/sponsors/wbkd) to them.
- [D3.js](https://d3js.org/)
- D3 makes all the zoom and pan actions in the graph possible.
Check it out to see if you can find other uses for it too!
- D3 makes all the zoom and pan actions in Vue Flow possible.
- [VueUse](https://vueuse.org/)
- VueUse is a collection of helpful utilities/composables that have been battle-tested.
- VueUse is a collection of essential vue composition utilities
## ⭐ Stargazers

107
docs/components/Repl.vue Normal file
View File

@@ -0,0 +1,107 @@
<script lang="ts" setup>
import { Repl, ReplStore } from '@vue/repl'
import pkg from '../package.json'
import '@vue/repl/style.css'
import { exampleImports } from './examples'
const props = defineProps<{ example: keyof typeof exampleImports; mainFile?: string; dependencies?: Record<string, string> }>()
let css = `@import 'https://cdn.jsdelivr.net/npm/@braks/vue-flow@${pkg.dependencies['@braks/vue-flow']}/dist/style.css';
@import 'https://cdn.jsdelivr.net/npm/@braks/vue-flow@${pkg.dependencies['@braks/vue-flow']}/dist/theme-default.css';
html,
body,
#app {
margin: 0;
height: 100%;
}
#app {
text-transform: uppercase;
font-family: 'JetBrains Mono', monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
.vue-flow__minimap {
transform: scale(50%)
transform-origin: bottom right;
}`
const store = new ReplStore({
showOutput: true,
outputMode: 'preview',
})
watchEffect(
() => {
document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`)
},
{ flush: 'post' },
)
const files: any = {}
const imports = exampleImports[props.example]
for (const example of Object.keys(imports)) {
if (example.includes('css')) {
css += `${imports[example as keyof typeof imports]}`
} else {
files[example] = imports[example as keyof typeof imports]
}
}
await store.setFiles(
{
...files,
'main.css': css,
},
props.mainFile ?? 'App.vue',
)
// pre-set import map
store.setImportMap({
imports: {
'@braks/vue-flow': `${location.origin}/vue-flow.es.js`,
},
})
store.setVueVersion('3.2.25')
const sfcOptions = {
script: {
reactivityTransform: true,
},
}
</script>
<template>
<Repl
:clear-console="true"
:auto-resize="true"
:store="store"
:show-compile-output="false"
:show-import-map="false"
:sfc-options="sfcOptions"
@keydown.ctrl.s.prevent
@keydown.meta.s.prevent
/>
</template>
<style>
.file-selector {
@apply scrollbar scrollbar-thin scrollbar-thumb-rounded scrollbar-thumb-green-500 scrollbar-track-black;
}
.vue-repl {
@apply rounded-lg border-1 border-solid dark:border-gray-200/10 border-gray-200;
height: calc(var(--vh) - var(--navbar-height));
}
.msg.err {
display: none;
}
</style>

View File

@@ -0,0 +1,84 @@
<script setup>
import { Background, Controls, MiniMap, VueFlow, isNode, useVueFlow } from '@braks/vue-flow'
import { ref } from 'vue'
import { initialElements } from './initial-elements.js'
/**
* useVueFlow provides all event handlers and store properties
* You can pass the composable an object that has the same properties as the VueFlow component props
*/
const { onPaneReady, onNodeDragStop, onConnect, instance, addEdges } = useVueFlow()
/**
* Our elements
*/
const elements = ref(initialElements)
/**
* This is a Vue Flow event-hook which can be listened to from anywhere you call the composable, instead of only on the main component
*
* onPaneReady is called when viewpane & nodes have visible dimensions
*/
onPaneReady(({ fitView }) => {
fitView()
})
onNodeDragStop((e) => console.log('drag stop', e))
/**
* onConnect is called when a new connection is created.
* You can add additional properties to your new edge (like a type or label) or block the creation altogether
*/
onConnect((params) => addEdges([params]))
const dark = ref(false)
/**
* To update node properties you can simply use your elements v-model and mutate the elements directly
* Changes should always be reflected on the graph reactively, without the need to overwrite the elements
*/
const updatePos = () =>
elements.value.forEach((el) => {
if (isNode(el)) {
el.position = {
x: Math.random() * 400,
y: Math.random() * 400,
}
}
})
/**
* toObject transforms your current graph data to an easily persist-able object
*/
const logToObject = () => console.log(instance.value?.toObject())
/**
* Resets the current viewpane transformation (zoom & pan)
*/
const resetTransform = () => instance.value?.setTransform({ x: 0, y: 0, zoom: 1 })
const toggleClass = () => {
dark.value = !dark.value
elements.value.forEach((el) => (el.class = dark.value ? 'dark' : 'light'))
}
</script>
<template>
<VueFlow v-model="elements" class="basicflow" :default-zoom="1.5" :min-zoom="0.2" :max-zoom="4">
<Background pattern-color="#aaa" gap="8" />
<MiniMap />
<Controls />
<div class="controls">
<button style="background-color: #113285; color: white" @click="resetTransform">reset transform</button>
<button style="background-color: #6f3381; color: white" @click="updatePos">update positions</button>
<button
:style="{ backgroundColor: dark ? '#FFFFFB' : '#1C1C1C', color: dark ? '#1C1C1C' : '#FFFFFB' }"
@click="toggleClass"
>
toggle {{ dark ? 'light' : 'dark' }}
</button>
<button @click="logToObject">log toObject</button>
</div>
</VueFlow>
</template>

View File

@@ -0,0 +1,3 @@
export { default as BasicApp } from './App.vue?raw'
export { default as BasicElements } from './initial-elements.js?raw'
export { default as BasicCSS } from './style.css'

View File

@@ -0,0 +1,25 @@
import { MarkerType } from '@braks/vue-flow'
/**
* You can pass elements together as a v-model value
* or split them up into nodes and edges and pass them to the `nodes` and `edges` props of Vue Flow (or useVueFlow composable)
*/
export const initialElements = [
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', type: 'output', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 }, class: 'light' },
{ id: '4', label: 'Node 4', position: { x: 150, y: 200 }, class: 'light' },
{ id: '5', type: 'output', label: 'Node 5', position: { x: 300, y: 300 }, class: 'light' },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', label: 'edge with arrowhead', source: '1', target: '3', markerEnd: MarkerType.Arrow },
{
id: 'e4-5',
type: 'step',
label: 'step-edge',
source: '4',
target: '5',
style: { stroke: 'orange' },
labelBgStyle: { fill: 'orange' },
},
{ id: 'e3-4', type: 'smoothstep', label: 'smoothstep-edge', source: '3', target: '4' },
]

View File

@@ -0,0 +1,30 @@
.basicflow .vue-flow__node.dark {
background: #1C1C1C;
color: #FFFFFB;
}
.basicflow .controls {
position: absolute;
left: 10px;
top: 10px;
z-index: 4;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.basicflow .controls button {
padding: 5px;
border-radius: 5px;
font-weight: 500;
-webkit-box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3);
box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3);
cursor: pointer;
}
.basicflow .controls button:hover {
opacity: 0.8;
transform: scale(105%);
transition: 250ms all ease;
}

View File

@@ -0,0 +1,24 @@
<script setup>
import { Background, BackgroundVariant, VueFlow } from '@braks/vue-flow'
import { ref } from 'vue'
import CustomConnectionLine from './CustomConnectionLine.vue'
const elements = ref([
{
id: '1',
type: 'input',
label: 'Node 1',
position: { x: 250, y: 5 },
},
])
</script>
<template>
<VueFlow v-model="elements">
<template #connection-line="props">
<CustomConnectionLine v-bind="props" />
</template>
<Background :variant="BackgroundVariant.Lines" />
</VueFlow>
</template>

View File

@@ -0,0 +1,33 @@
<script setup>
const props = defineProps({
sourceX: {
type: Number,
required: true,
},
sourceY: {
type: Number,
required: true,
},
targetX: {
type: Number,
required: true,
},
targetY: {
type: Number,
required: true,
},
})
</script>
<template>
<g>
<path
class="animated"
fill="none"
stroke="#6F3381"
:stroke-width="2.5"
:d="`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`"
/>
<circle :cx="targetX" :cy="targetY" fill="#fff" :r="5" stroke="#6F3381" :stroke-width="1.5" />
</g>
</template>

View File

@@ -0,0 +1,2 @@
export { default as CustomConnectionLineApp } from './App.vue?raw'
export { default as CustomConnectionLine } from './CustomConnectionLine.vue?raw'

View File

@@ -0,0 +1,82 @@
<script setup>
import { Background, ConnectionMode, MiniMap, Position, VueFlow } from '@braks/vue-flow'
import { h, onMounted, ref } from 'vue'
import ColorSelectorNode from './CustomNode.vue'
import { presets } from './presets.js'
const elements = ref([])
const bgColor = ref(presets.ayame)
const bgName = ref('AYAME')
const connectionLineStyle = { stroke: '#fff' }
const snapGrid = [16, 16]
const nodeStroke = (n) => {
if (n.type === 'input') return '#0041d0'
if (n.type === 'custom') return presets.sumi
if (n.type === 'output') return '#ff0072'
return '#eee'
}
const nodeColor = (n) => {
if (n.type === 'custom') return bgColor.value
return '#fff'
}
// output labels
const outputColorLabel = () => h('div', {}, bgColor.value)
const outputNameLabel = () => h('div', {}, bgName.value)
const onChange = (color) => {
bgColor.value = color.value
bgName.value = color.name
}
onMounted(() => {
elements.value = [
{
id: '1',
type: 'custom',
data: { color: bgColor },
position: { x: 0, y: 50 },
},
{
id: '2',
type: 'output',
label: outputNameLabel,
position: { x: 350, y: 25 },
targetPosition: Position.Left,
},
{
id: '3',
type: 'output',
label: outputColorLabel,
position: { x: 350, y: 175 },
targetPosition: Position.Left,
},
{ id: 'e1a-2', source: '1', sourceHandle: 'a', target: '2', animated: true, style: { stroke: '#fff' } },
{ id: 'e1b-3', source: '1', sourceHandle: 'b', target: '3', animated: true, style: { stroke: '#fff' } },
]
})
</script>
<template>
<VueFlow
v-model="elements"
:style="{ backgroundColor: bgColor }"
:connection-mode="ConnectionMode.Loose"
:connection-line-style="connectionLineStyle"
:snap-to-grid="true"
:snap-grid="snapGrid"
:default-zoom="1.5"
:fit-view-on-init="true"
>
<template #node-custom="props">
<ColorSelectorNode v-bind="props" @change="onChange" />
</template>
<Background :size="0.7" pattern-color="black" />
<MiniMap :node-stroke-color="nodeStroke" :node-color="nodeColor" />
</VueFlow>
</template>

View File

@@ -0,0 +1,64 @@
<script setup>
import { Handle, Position } from '@braks/vue-flow'
import { computed } from 'vue'
import { presets } from './presets.js'
const props = defineProps({
data: {
type: Object,
required: true,
},
})
const emit = defineEmits(['change'])
const targetHandleStyle = { background: '#555' }
const sourceHandleStyleA = { ...targetHandleStyle, top: '10px' }
const sourceHandleStyleB = { ...targetHandleStyle, bottom: '10px', top: 'auto' }
const onConnect = (params) => console.log('handle onConnect', params)
const onSelect = (color) => {
emit('change', color)
}
const colors = computed(() => {
return Object.keys(presets).map((color) => {
return {
name: color,
value: presets[color],
}
})
})
const selectedColor = computed(() => {
return colors.value.find((color) => color.value === props.data.color)
})
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<div v-if="selectedColor">
Select a color
</div>
<div
style="display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; max-width: 90%; margin: auto; gap: 3px"
>
<template v-for="color of colors" :key="color.name">
<button
:title="color.name"
:style="{ backgroundColor: color.value }"
style="padding: 3px; width: 25px; height: 25px"
type="button"
@click="onSelect(color)"
></button>
</template>
</div>
<Handle id="a" type="source" :position="Position.Right" :style="sourceHandleStyleA" />
<Handle id="b" type="source" :position="Position.Right" :style="sourceHandleStyleB" />
</template>

View File

@@ -0,0 +1,4 @@
export { default as CustomNodeApp } from './App.vue?raw'
export { default as CustomNode } from './CustomNode.vue?raw'
export { default as CustomNodeCSS } from './style.css'
export { default as ColorPresets } from './presets.js?raw'

View File

@@ -0,0 +1,23 @@
export const presets = {
byakuroku: '#A8D8B9',
mizu: '#81C7D4',
asagi: '#33A6B8',
ukon: '#EFBB24',
mushikuri: '#D9CD90',
hiwa: '#BEC23F',
ichigo: '#B5495B',
kurenai: '#CB1B45',
syojyohi: '#E83015',
konjyo: '#113285',
fuji: '#8B81C3',
ayame: '#6F3381',
gofun: '#FFFFFB',
torinoko: '#DAC9A6',
kurotsurubami: '#0B1013',
sumi: '#1C1C1C',
ohni: '#F05E1C',
kokikuchinashi: '#FB9966',
beniukon: '#E98B2A',
sakura: '#FEDFE1',
toki: '#EEA9A9',
}

View File

@@ -0,0 +1,12 @@
.vue-flow__node-custom {
border: 1px solid #777;
padding: 10px;
border-radius: 10px;
background: whitesmoke;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 10px;
max-width: 250px;
}

View File

@@ -0,0 +1,47 @@
<script setup>
import { VueFlow, useVueFlow } from '@braks/vue-flow'
import Sidebar from './Sidebar.vue'
let id = 0
const getId = () => `dndnode_${id++}`
const { instance, onConnect, nodes, edges, addEdges, addNodes, viewport } = useVueFlow({
nodes: [
{
id: '1',
type: 'input',
label: 'input node',
position: { x: 250, y: 25 },
},
],
})
const onDragOver = (event) => {
event.preventDefault()
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
onConnect((params) => addEdges([params]))
const onDrop = (event) => {
if (instance.value) {
const type = event.dataTransfer?.getData('application/vueflow')
const position = instance.value.project({ x: event.clientX - 40, y: event.clientY - 18 })
const newNode = {
id: getId(),
type,
position,
label: `${type} node`,
}
addNodes([newNode])
}
}
</script>
<template>
<div class="dndflow" @drop="onDrop">
<VueFlow @dragover="onDragOver" />
<Sidebar />
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script setup>
const onDragStart = (event, nodeType) => {
if (event.dataTransfer) {
event.dataTransfer.setData('application/vueflow', nodeType)
event.dataTransfer.effectAllowed = 'move'
}
}
</script>
<template>
<aside>
<div class="description">You can drag these nodes to the pane.</div>
<div class="nodes">
<div class="vue-flow__node-input" :draggable="true" @dragstart="onDragStart($event, 'input')">Input Node</div>
<div class="vue-flow__node-default" :draggable="true" @dragstart="onDragStart($event, 'default')">Default Node</div>
<div class="vue-flow__node-output" :draggable="true" @dragstart="onDragStart($event, 'output')">Output Node</div>
</div>
</aside>
</template>

View File

@@ -0,0 +1,3 @@
export { default as DndApp } from './App.vue?raw'
export { default as DndSidebar } from './Sidebar.vue?raw'
export { default as DndCSS } from './style.css'

View File

@@ -0,0 +1,51 @@
.dndflow {
flex-direction: column;
display: flex;
height: 100%;
}
.dndflow aside {
color: white;
font-weight: 700;
border-right: 1px solid #eee;
padding: 15px 10px;
font-size: 12px;
background: rgba(16, 185, 129, 0.75);
-webkit-box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
}
.dndflow aside .nodes > * {
margin-bottom: 10px;
cursor: grab;
font-weight: 500;
-webkit-box-shadow: 5px 5px 10px 2px rgba(0,0,0,0.25);
box-shadow: 5px 5px 10px 2px rgba(0,0,0,0.25);
}
.dndflow aside .description {
margin-bottom: 10px;
}
.dndflow .vue-flow-wrapper {
flex-grow: 1;
height: 100%;
}
@media screen and (min-width: 640px) {
.dndflow {
flex-direction: row;
}
.dndflow aside {
min-width: 25%;
}
}
@media screen and (max-width: 639px) {
.dndflow aside .nodes {
display: flex;
flex-direction: row;
gap: 5px;
}
}

View File

@@ -0,0 +1,74 @@
<script setup>
import { Background, Controls, MarkerType, MiniMap, VueFlow } from '@braks/vue-flow'
import { h, ref } from 'vue'
import CustomEdge from './CustomEdge.vue'
import CustomEdge2 from './CustomEdge2.vue'
import CustomEdgeLabel from './CustomEdgeLabel.vue'
const elements = ref([
{ id: '1', type: 'input', label: 'Input 1', position: { x: 250, y: 0 } },
{ id: '2', label: 'Node 2', position: { x: 150, y: 100 } },
{ id: '2a', label: 'Node 2a', position: { x: 0, y: 180 } },
{ id: '3', label: 'Node 3', position: { x: 250, y: 200 } },
{ id: '4', label: 'Node 4', position: { x: 400, y: 300 } },
{ id: '3a', label: 'Node 3a', position: { x: 150, y: 300 } },
{ id: '5', label: 'Node 5', position: { x: 250, y: 400 } },
{ id: '6', type: 'output', label: 'Output 6', position: { x: 50, y: 550 } },
{ id: '7', type: 'output', label: 'Output 7', position: { x: 250, y: 550 } },
{ id: '8', type: 'output', label: 'Output 8', position: { x: 525, y: 600 } },
{ id: '9', type: 'output', 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: () => h(CustomEdgeLabel, { label: 'custom label text' }),
labelStyle: { fill: 'red', fontWeight: 700 },
markerEnd: MarkerType.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 },
markerEnd: MarkerType.ArrowClosed,
},
{
id: 'e5-8',
source: '5',
target: '8',
type: 'custom',
data: { text: 'custom edge' },
markerEnd: MarkerType.ArrowClosed,
},
{
id: 'e5-9',
source: '5',
target: '9',
type: 'custom2',
data: { text: 'custom edge 2' },
},
])
</script>
<template>
<VueFlow v-model="elements" :fit-view-on-init="true" :snap-to-grid="true">
<template #edge-custom="props">
<CustomEdge v-bind="props" />
</template>
<template #edge-custom2="props">
<CustomEdge2 v-bind="props" />
</template>
<MiniMap />
<Controls />
<Background />
</VueFlow>
</template>

View File

@@ -0,0 +1,100 @@
<script setup>
import { getBezierPath, getEdgeCenter, useVueFlow } from '@braks/vue-flow'
import { computed } from 'vue'
const props = defineProps({
id: {
type: String,
required: true,
},
sourceX: {
type: Number,
required: true,
},
sourceY: {
type: Number,
required: true,
},
targetX: {
type: Number,
required: true,
},
targetY: {
type: Number,
required: true,
},
sourcePosition: {
type: String,
required: true,
},
targetPosition: {
type: String,
required: true,
},
data: {
type: Object,
required: false,
},
markerEnd: {
type: String,
required: false,
},
style: {
type: Object,
required: false,
},
})
const { applyEdgeChanges } = useVueFlow()
const foreignObjectSize = 40
const onClick = (evt, id) => {
applyEdgeChanges([{ type: 'remove', id }])
evt.stopPropagation()
}
const edgePath = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
const center = computed(() =>
getEdgeCenter({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
}),
)
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<path :id="id" :style="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 style="display: flex; align-items: center; justify-content: center">
<div>
<button ref="btn" class="edgebutton animated-text-gradient" @click="(event) => onClick(event, id)">×</button>
</div>
</body>
</foreignObject>
</template>

View File

@@ -0,0 +1,97 @@
<script setup>
import { EdgeText, getBezierPath, getEdgeCenter } from '@braks/vue-flow'
import { computed } from 'vue'
const props = defineProps({
id: {
type: String,
required: true,
},
sourceX: {
type: Number,
required: true,
},
sourceY: {
type: Number,
required: true,
},
targetX: {
type: Number,
required: true,
},
targetY: {
type: Number,
required: true,
},
sourcePosition: {
type: String,
required: true,
},
targetPosition: {
type: String,
required: true,
},
data: {
type: Object,
required: false,
},
markerEnd: {
type: String,
required: false,
},
style: {
type: Object,
required: false,
},
sourceHandleId: {
type: String,
required: false,
},
targetHandleId: {
type: String,
required: false,
},
})
const edgePath = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
const center = computed(() =>
getEdgeCenter({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
}),
)
const onClick = () => console.log(props.data)
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<path :id="props.id" class="vue-flow__edge-path" :d="edgePath" :marker-end="props.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="onClick"
/>
</template>

View File

@@ -0,0 +1,12 @@
<script setup>
const props = defineProps({
label: {
type: String,
required: true,
},
})
</script>
<template>
<tspan dy="10" x="0">{{ props.label }}</tspan>
</template>

View File

@@ -0,0 +1,5 @@
export { default as EdgesApp } from './App.vue?raw'
export { default as CustomEdge } from './CustomEdge.vue?raw'
export { default as CustomEdge2 } from './CustomEdge2.vue?raw'
export { default as CustomEdgeLabel } from './CustomEdgeLabel.vue?raw'
export { default as EdgeCSS } from './style.css'

View File

@@ -0,0 +1,34 @@
.edgebutton {
color: whitesmoke;
border-radius: 999px;
cursor: pointer;
}
.edgebutton:hover {
box-shadow: 0 0 0 2px pink, 0 0 0 4px #f05f75;
}
.animated-text-gradient {
background: linear-gradient(122deg, #6f3381, #81c7d4, #fedfe1, #fffffb);
background-size: 800% 800%;
-webkit-animation: textgradient 4s ease infinite;
-moz-animation: textgradient 4s ease infinite;
animation: textgradient 4s ease infinite;
}
@-webkit-keyframes textgradient {
0%{background-position:0% 22%}
50%{background-position:100% 79%}
100%{background-position:0% 22%}
}
@-moz-keyframes textgradient {
0%{background-position:0% 22%}
50%{background-position:100% 79%}
100%{background-position:0% 22%}
}
@keyframes textgradient {
0%{background-position:0% 22%}
50%{background-position:100% 79%}
100%{background-position:0% 22%}
}

View File

@@ -0,0 +1,31 @@
<script setup>
import { Background, BackgroundVariant, Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
const { nodes, addNodes, edges, addEdges, onConnect, onPaneReady, onNodeDragStop, dimensions } = useVueFlow()
onConnect((params) => addEdges([params]))
onPaneReady((flowInstance) => console.log('flow loaded:', flowInstance))
onNodeDragStop((node) => console.log('drag stop', node))
const addRandomNode = () => {
const nodeId = (nodes.value.length + 1).toString()
const newNode = {
id: nodeId,
label: `Node: ${nodeId}`,
position: { x: Math.random() * dimensions.value.width, y: Math.random() * dimensions.value.height },
}
addNodes([newNode])
}
</script>
<template>
<VueFlow>
<MiniMap />
<Controls />
<Background :variant="BackgroundVariant.Lines" />
<button type="button" :style="{ position: 'absolute', left: '10px', top: '10px', zIndex: 4 }" @click="addRandomNode">
add node
</button>
</VueFlow>
</template>

View File

@@ -0,0 +1 @@
export { default as EmptyApp } from './App.vue?raw'

View File

@@ -0,0 +1,41 @@
<script setup>
import { Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
import { ref, watchEffect } from 'vue'
const isHidden = ref(false)
const { nodes, edges } = useVueFlow({
nodes: [
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 } },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 } },
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 } },
{ id: '4', label: 'Node 4', position: { x: 400, y: 200 } },
],
edges: [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4' },
],
})
watchEffect(() => {
nodes.value.forEach((n) => (n.hidden = isHidden.value))
edges.value.forEach((e) => (e.hidden = isHidden.value))
})
</script>
<template>
<VueFlow>
<MiniMap />
<Controls />
<div :style="{ position: 'absolute', left: '10px', top: '10px', zIndex: 4 }">
<div>
<label for="ishidden">
hidden
<input id="ishidden" v-model="isHidden" type="checkbox" />
</label>
</div>
</div>
</VueFlow>
</template>

View File

@@ -0,0 +1 @@
export { default as HiddenApp } from './App.vue?raw'

View File

@@ -0,0 +1,87 @@
import { BasicApp, BasicCSS, BasicElements } from './basic'
import { ColorPresets, CustomNode, CustomNodeApp, CustomNodeCSS } from './custom-node'
import { CustomConnectionLine, CustomConnectionLineApp } from './connectionline'
import { CustomEdge, CustomEdge2, CustomEdgeLabel, EdgeCSS, EdgesApp } from './edges'
import { NestedApp } from './nested'
import { StressApp, StressCSS, StressUtils } from './stress'
import { UpdateEdgeApp } from './update-edge'
import { UpdateNodeApp, UpdateNodeCSS } from './update-node'
import { ValidationApp, ValidationCSS, ValidationCustomInput, ValidationCustomNode } from './validation'
import { SaveRestoreApp, SaveRestoreCSS, SaveRestoreControls } from './save-restore'
import { DndApp, DndCSS, DndSidebar } from './dnd'
import { EmptyApp } from './empty'
import { HiddenApp } from './hidden'
import { InteractionApp, InteractionCSS, InteractionControls } from './interaction'
import { MultiApp, MultiCSS, MultiFlow } from './multi'
export const exampleImports = {
basic: {
'App.vue': BasicApp,
'initial-elements.js': BasicElements,
'style.css': BasicCSS,
},
customNode: {
'App.vue': CustomNodeApp,
'CustomNode.vue': CustomNode,
'style.css': CustomNodeCSS,
'presets.js': ColorPresets,
},
connectionline: {
'App.vue': CustomConnectionLineApp,
'CustomConnectionLine.vue': CustomConnectionLine,
},
edges: {
'App.vue': EdgesApp,
'CustomEdge.vue': CustomEdge,
'CustomEdge2.vue': CustomEdge2,
'CustomEdgeLabel.vue': CustomEdgeLabel,
'style.css': EdgeCSS,
},
nested: {
'App.vue': NestedApp,
},
stress: {
'App.vue': StressApp,
'utils.js': StressUtils,
'style.css': StressCSS,
},
updateEdge: {
'App.vue': UpdateEdgeApp,
},
updateNode: {
'App.vue': UpdateNodeApp,
'style.css': UpdateNodeCSS,
},
validation: {
'App.vue': ValidationApp,
'CustomInput.vue': ValidationCustomInput,
'CustomNode.vue': ValidationCustomNode,
'style.css': ValidationCSS,
},
saveRestore: {
'App.vue': SaveRestoreApp,
'Controls.vue': SaveRestoreControls,
'style.css': SaveRestoreCSS,
},
dnd: {
'App.vue': DndApp,
'Sidebar.vue': DndSidebar,
'style.css': DndCSS,
},
empty: {
'App.vue': EmptyApp,
},
hidden: {
'App.vue': HiddenApp,
},
interaction: {
'App.vue': InteractionApp,
'InteractionControls.vue': InteractionControls,
'style.css': InteractionCSS,
},
multi: {
'App.vue': MultiApp,
'Flow.vue': MultiFlow,
'style.css': MultiCSS,
},
}

View File

@@ -0,0 +1,20 @@
<script setup>
import { VueFlow } from '@braks/vue-flow'
import { ref } from 'vue'
import InteractionControls from './InteractionControls.vue'
const elements = ref([
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 } },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 } },
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 } },
{ id: '4', label: 'Node 4', position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
])
</script>
<template>
<VueFlow class="interactionflow" v-model="elements" :fit-view-on-init="true">
<InteractionControls />
</VueFlow>
</template>

View File

@@ -0,0 +1,110 @@
<script setup>
import { useVueFlow } from '@braks/vue-flow'
import { ref } from 'vue'
const {
nodesDraggable,
nodesConnectable,
elementsSelectable,
zoomOnScroll,
zoomOnDoubleClick,
zoomOnPinch,
panOnScroll,
panOnScrollMode,
panOnDrag,
onConnect,
onNodeDragStop,
onPaneClick,
onPaneScroll,
onPaneContextMenu,
onNodeDragStart,
onMoveEnd,
addEdges,
} = useVueFlow()
const captureZoomClick = ref(false)
const captureZoomScroll = ref(false)
onConnect((params) => addEdges([params]))
onNodeDragStart((e) => console.log('drag start', e))
onNodeDragStop((e) => console.log('drag stop', e))
onPaneClick((event) => captureZoomClick.value && console.log('pane click', event))
onPaneScroll((event) => captureZoomScroll.value && console.log('pane scroll', event))
onPaneContextMenu((event) => captureZoomClick.value && console.log('pane ctx menu', event))
onMoveEnd((flowTransform) => console.log('move end', flowTransform))
</script>
<template>
<div class="controls">
<div>
<label class="label" for="draggable">
nodesDraggable
<input id="draggable" v-model="nodesDraggable" type="checkbox" class="vue-flow__draggable" />
</label>
</div>
<div>
<label class="label" for="connectable">
nodesConnectable
<input id="connectable" v-model="nodesConnectable" type="checkbox" class="vue-flow__connectable" />
</label>
</div>
<div>
<label class="label" for="selectable">
elementsSelectable
<input id="selectable" v-model="elementsSelectable" type="checkbox" class="vue-flow__selectable" />
</label>
</div>
<div>
<label class="label" for="zoomonscroll">
zoomOnScroll
<input id="zoomonscroll" v-model="zoomOnScroll" type="checkbox" class="vue-flow__zoomonscroll" />
</label>
</div>
<div>
<label class="label" for="zoomonpinch">
zoomOnPinch
<input id="zoomonpinch" v-model="zoomOnPinch" type="checkbox" class="vue-flow__zoomonpinch" />
</label>
</div>
<div>
<label class="label" for="panonscroll">
panOnScroll
<input id="panonscroll" v-model="panOnScroll" type="checkbox" class="vue-flow__panonscroll" />
</label>
</div>
<div>
<label class="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 class="label" for="zoomondbl">
zoomOnDoubleClick
<input id="zoomondbl" v-model="zoomOnDoubleClick" type="checkbox" class="vue-flow__zoomondbl" />
</label>
</div>
<div>
<label class="label" for="panemoveable">
paneMovable
<input id="panemoveable" v-model="panOnDrag" type="checkbox" class="vue-flow__panemoveable" />
</label>
</div>
<div>
<label class="label" for="capturezoompaneclick">
capture onPaneClick
<input id="capturezoompaneclick" v-model="captureZoomClick" type="checkbox" class="vue-flow__capturezoompaneclick" />
</label>
</div>
<div>
<label class="label" for="capturezoompanescroll">
capture onPaneScroll
<input id="capturezoompanescroll" v-model="captureZoomScroll" type="checkbox" class="vue-flow__capturezoompanescroll" />
</label>
</div>
</div>
</template>

View File

@@ -0,0 +1,3 @@
export { default as InteractionApp } from './App.vue?raw'
export { default as InteractionControls } from './InteractionControls.vue?raw'
export { default as InteractionCSS } from './style.css'

View File

@@ -0,0 +1,22 @@
.interactionflow .controls {
position: absolute;
left: 0;
top: 0;
z-index: 4;
font-size: 11px;
background-color: lightgray;
border-bottom-right-radius: 10px;
padding: 12px;
}
.interactionflow .controls .label {
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
cursor: pointer;
}
.interactionflow .controls .label input {
cursor: pointer;
}

View File

@@ -0,0 +1,10 @@
<script setup>
import Flow from './Flow.vue'
</script>
<template>
<div class="multiflows">
<Flow />
<Flow />
</div>
</template>

View File

@@ -0,0 +1,36 @@
<script setup>
import { Background, VueFlow, isNode } from '@braks/vue-flow'
import { ref } from 'vue'
const elements = ref([
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 }, class: 'light' },
{ id: '4', 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 toggleClass = () => elements.value.forEach((el) => (el.class = el.class === 'light' ? 'dark' : 'light'))
const updatePos = () =>
elements.value.forEach((el) => {
if (isNode(el)) {
el.position = {
x: Math.random() * 400,
y: Math.random() * 400,
}
}
})
</script>
<template>
<VueFlow v-model="elements" :fit-view-on-init="true">
<Background />
<div style="position: absolute; right: 10px; top: 10px; z-index: 4">
<button style="margin-right: 5px" @click="updatePos">update positions</button>
<button style="margin-right: 5px" @click="toggleClass">toggle class</button>
</div>
</VueFlow>
</template>

View File

@@ -0,0 +1,3 @@
export { default as MultiApp } from './App.vue?raw'
export { default as MultiFlow } from './Flow.vue?raw'
export { default as MultiCSS } from './style.css'

View File

@@ -0,0 +1,18 @@
.vue-flow__node.dark {
background: #000000;
color: #ffffff;
}
.multiflows {
display: flex;
height: 100%;
}
.multiflows .vue-flow {
width: 100%;
height: 100%;
}
.multiflows .vue-flow:first-child {
border-right: 2px solid #333;
}

View File

@@ -0,0 +1,98 @@
<script setup>
import { Background, ConnectionMode, Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
import { onMounted } from 'vue'
const { onConnect, nodes, edges, addEdges, addNodes } = useVueFlow({
fitViewOnInit: true,
connectionMode: ConnectionMode.Loose,
nodes: [
{ id: '1', type: 'input', label: 'node', position: { x: 250, y: 0 }, class: 'light' },
{
id: '2',
label: 'parent node',
position: { x: 100, y: 100 },
class: 'light',
style: { backgroundColor: 'rgba(16, 185, 129, 0.5)', width: '200px', height: '200px' },
},
{
id: '2a',
label: 'child node',
position: { x: 10, y: 50 },
parentNode: '2',
},
{ id: '3', label: 'node', position: { x: 350, y: 100 }, class: 'light' },
{
id: '4',
label: 'parent node',
position: { x: 320, y: 300 },
class: 'light',
style: { backgroundColor: 'rgba(16, 185, 129, 0.5)', width: '300px', height: '300px' },
},
{
id: '4a',
label: 'child node',
position: { x: 15, y: 65 },
class: 'light',
extent: 'parent',
parentNode: '4',
},
{
id: '4b',
label: 'nested parent node',
position: { x: 15, y: 120 },
class: 'light',
style: { backgroundColor: 'rgba(139, 92, 246, 0.5)', height: '150px', width: '270px' },
parentNode: '4',
},
{
id: '4b1',
label: 'nested child node',
position: { x: 20, y: 40 },
class: 'light',
parentNode: '4b',
},
{
id: '4b2',
label: 'nested child node',
position: { x: 100, y: 100 },
class: 'light',
parentNode: '4b',
},
],
edges: [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e2a-4a', source: '2a', target: '4a' },
{ id: 'e3-4', source: '3', target: '4' },
{ id: 'e3-4b', source: '3', target: '4b' },
{ id: 'e4a-4b1', source: '4a', target: '4b1' },
{ id: 'e4a-4b2', source: '4a', target: '4b2' },
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
],
})
onConnect((params) => addEdges([params]))
onMounted(() => {
// add nodes to parent
addNodes([
{
id: '999',
type: 'input',
label: 'Drag me to extend area!',
position: { x: 20, y: 100 },
class: 'light',
expandParent: true,
parentNode: '2',
},
])
})
</script>
<template>
<VueFlow>
<MiniMap />
<Controls />
<Background />
</VueFlow>
</template>

View File

@@ -0,0 +1 @@
export { default as NestedApp } from './App.vue?raw'

View File

@@ -0,0 +1,23 @@
<script setup>
import sdk from '@stackblitz/sdk'
const el = ref()
onMounted(() => {
sdk.embedProjectId(el.value, 'vitejs-vite-jvtrur', {
height: 750,
forceEmbedLayout: true,
openFile: 'src/App.vue',
})
})
</script>
<template>
<div ref="el" class="outline-none"></div>
</template>
<style>
.application {
@apply h-[75vh];
}
</style>

View File

@@ -0,0 +1,13 @@
<script lang="ts" setup>
import { VueFlow } from '@braks/vue-flow'
import { ref } from 'vue'
import Controls from './Controls.vue'
const elements = ref([{ id: '1', label: 'Node 1', position: { x: 100, y: 100 } }])
</script>
<template>
<VueFlow v-model="elements">
<Controls />
</VueFlow>
</template>

View File

@@ -0,0 +1,40 @@
<script setup>
import { useVueFlow } from '@braks/vue-flow'
const flowKey = 'example-flow'
const { nodes, addNodes, setNodes, setEdges, instance, dimensions } = useVueFlow()
const onSave = () => {
localStorage.setItem(flowKey, JSON.stringify(instance.value?.toObject()))
}
const onRestore = () => {
const flow = JSON.parse(localStorage.getItem(flowKey))
if (flow) {
const [x = 0, y = 0] = flow.position
setNodes(flow.nodes)
setEdges(flow.edges)
instance.value.setTransform({ x, y, zoom: flow.zoom || 0 })
}
}
const onAdd = () => {
const id = nodes.value.length + 1
const newNode = {
id: `random_node-${id}`,
label: `Node ${id}`,
position: { x: Math.random() * dimensions.value.width, y: Math.random() * dimensions.value.height },
}
addNodes([newNode])
}
</script>
<template>
<div class="save__controls">
<button style="background-color: #33a6b8" @click="onSave">save</button>
<button style="background-color: #113285" @click="onRestore">restore</button>
<button style="background-color: #6F3381" @click="onAdd">add node</button>
</div>
</template>

View File

@@ -0,0 +1,3 @@
export { default as SaveRestoreApp } from './App.vue?raw'
export { default as SaveRestoreControls } from './Controls.vue?raw'
export { default as SaveRestoreCSS } from './style.css'

View File

@@ -0,0 +1,25 @@
.save__controls {
position: absolute;
left: 10px;
top: 10px;
z-index: 4;
font-size: 12px;
}
.save__controls button {
margin-left: 5px;
padding: 5px;
border-radius: 5px;
font-weight: 700;
text-transform: uppercase;
color: white;
-webkit-box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
cursor: pointer;
}
.save__controls button:hover {
opacity: 0.8;
transform: scale(105%);
transition: 250ms all ease-in-out;
}

View File

@@ -0,0 +1,38 @@
<script setup>
import { VueFlow, isNode, useVueFlow } from '@braks/vue-flow'
import { ref } from 'vue'
import { getElements } from './utils.js'
const { nodes, edges } = getElements(10, 10)
const elements = ref([...nodes, ...edges])
const { onPaneReady } = useVueFlow()
onPaneReady((i) => {
i.fitView({
padding: 0.2,
})
console.log(i.getElements())
})
const toggleClass = () => elements.value.forEach((el) => (el.class = el.class === 'light' ? 'dark' : 'light'))
const updatePos = () =>
elements.value.forEach((el) => {
if (isNode(el)) {
el.position = {
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
}
}
})
</script>
<template>
<VueFlow v-model="elements">
<div style="position: absolute; right: 10px; top: 10px; z-index: 4">
<button style="margin-right: 5px" @click="updatePos">update positions</button>
<button style="margin-right: 5px" @click="toggleClass">toggle class</button>
</div>
</VueFlow>
</template>

View File

@@ -0,0 +1,3 @@
export { default as StressApp } from './App.vue?raw'
export { default as StressUtils } from './utils.js?raw'
export { default as StressCSS } from './style.css'

View File

@@ -0,0 +1,4 @@
.vue-flow__node.dark {
background: #000000;
color: #ffffff;
}

View File

@@ -0,0 +1,32 @@
export function getElements(xElements = 10, yElements = 10) {
const initialNodes = []
const initialEdges = []
let nodeId = 1
let recentNodeId = null
for (let y = 0; y < yElements; y++) {
for (let x = 0; x < xElements; x++) {
const position = { x: x * 75, y: y * 75 }
const node = {
id: nodeId.toString(),
style: { width: `50px`, fontSize: `11px` },
label: `Node ${nodeId}`,
class: 'light',
position,
}
initialNodes.push(node)
if (recentNodeId && nodeId <= xElements * yElements) {
initialEdges.push({ id: `${x}-${y}`, source: recentNodeId.toString(), target: nodeId.toString() })
}
recentNodeId = nodeId
nodeId++
}
}
return {
nodes: initialNodes,
edges: initialEdges,
}
}

View File

@@ -0,0 +1,47 @@
<script setup>
import { ConnectionMode, Controls, VueFlow, addEdge, updateEdge } from '@braks/vue-flow'
import { ref } from 'vue'
const elements = ref([
{
id: '1',
type: 'input',
label: 'Node <strong>A</strong>',
position: { x: 250, y: 0 },
},
{
id: '2',
label: 'Node <strong>B</strong>',
position: { x: 100, y: 100 },
},
{
id: '3',
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', updatable: true },
])
const onLoad = (flowInstance) => flowInstance.fitView()
const onEdgeUpdateStart = (edge) => console.log('start update', edge)
const onEdgeUpdateEnd = (edge) => console.log('end update', edge)
const onEdgeUpdate = ({ edge, connection }) => {
elements.value = updateEdge(edge, connection, elements.value)
}
const onConnect = (params) => (elements.value = addEdge(params, elements.value))
</script>
<template>
<VueFlow
v-model="elements"
:connection-mode="ConnectionMode.Loose"
@pane-ready="onLoad"
@edge-update="onEdgeUpdate"
@connect="onConnect"
@edge-update-start="onEdgeUpdateStart"
@edge-update-end="onEdgeUpdateEnd"
>
<Controls />
</VueFlow>
</template>

View File

@@ -0,0 +1 @@
export { default as UpdateEdgeApp } from './App.vue?raw'

View File

@@ -0,0 +1,48 @@
<script setup>
import { VueFlow, useVueFlow } from '@braks/vue-flow'
import { reactive } from 'vue'
const defaultLabel = '-'
const { onPaneReady, getNode } = useVueFlow({
nodes: [
{ id: '1', label: defaultLabel, position: { x: 100, y: 100 } },
{ id: '2', label: 'Node 2', position: { x: 100, y: 200 } },
],
edges: [{ id: 'e1-2', source: '1', target: '2' }],
})
const opts = reactive({
bg: '#eeeeee',
label: 'Node 1',
hidden: false,
})
const updateNode = () => {
const node = getNode.value('1')
node.label = opts.label.trim() !== '' ? opts.label : defaultLabel
node.style = { backgroundColor: opts.bg }
node.hidden = opts.hidden
}
onPaneReady(({ fitView }) => {
fitView()
updateNode()
})
</script>
<template>
<VueFlow>
<div class="updatenode__controls">
<label>label:</label>
<input v-model="opts.label" @input="updateNode" />
<label class="updatenode__bglabel">background:</label>
<input v-model="opts.bg" type="color" @input="updateNode" />
<div class="updatenode__checkboxwrapper">
<label>hidden:</label>
<input v-model="opts.hidden" type="checkbox" @change="updateNode" />
</div>
</div>
</VueFlow>
</template>

View File

@@ -0,0 +1,2 @@
export { default as UpdateNodeApp } from './App.vue?raw'
export { default as UpdateNodeCSS } from './style.css'

View File

@@ -0,0 +1,30 @@
.updatenode__controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 4;
font-size: 11px;
background-color: lightgray;
border-radius: 10px;
padding: 8px;
}
.updatenode__controls label {
display: blocK;
}
.updatenode__controls input {
padding: 2px;
border-radius: 5px;
}
.updatenode__bglabel {
margin-top: 8px;
}
.updatenode__checkboxwrapper {
display: flex;
justify-content: center;
align-items: center;
margin-top: 8px;
}

View File

@@ -0,0 +1,47 @@
<script setup>
import { VueFlow, addEdge } from '@braks/vue-flow'
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
import CustomNode from './CustomNode.vue'
const elements = ref([
{ id: '0', type: 'custominput', position: { x: 0, y: 150 }, isValidTargetPos: (connection) => connection.target === 'B' },
{
id: 'A',
type: 'custom',
position: { x: 250, y: 0 },
isValidSourcePos: () => false,
},
{ id: 'B', type: 'custom', position: { x: 250, y: 150 }, isValidSourcePos: (connection) => connection.target === 'B' },
{ id: 'C', type: 'custom', position: { x: 250, y: 300 }, isValidSourcePos: (connection) => connection.target === 'B' },
])
const onLoad = (flowInstance) => flowInstance.fitView()
const onConnectStart = ({ nodeId, handleType }) => console.log('on connect start', { nodeId, handleType })
const onConnectStop = (event) => console.log('on connect stop', event)
const onConnectEnd = (event) => console.log('on connect end', event)
const onConnect = (params) => {
console.log('on connect', params)
addEdge(params, elements.value)
}
</script>
<template>
<VueFlow
class="validationflow"
v-model="elements"
@connect="onConnect"
@pane-ready="onLoad"
@connect-start="onConnectStart"
@connect-stop="onConnectStop"
@connect-end="onConnectEnd"
>
<template #node-custominput="props">
<CustomInput v-bind="props" />
</template>
<template #node-custom="props">
<CustomNode v-bind="props" />
</template>
</VueFlow>
</template>

View File

@@ -0,0 +1,21 @@
<script setup>
import { Handle, Position } from '@braks/vue-flow'
const props = defineProps({
isValidTargetPos: {
type: Function,
required: false,
},
})
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<div>Only connectable with B</div>
<Handle type="source" :position="Position.Right" :is-valid-connection="props.isValidTargetPos" />
</template>

View File

@@ -0,0 +1,25 @@
<script setup>
import { Handle, Position } from '@braks/vue-flow'
const props = defineProps({
id: {
type: String,
required: true,
},
isValidSourcePos: {
type: Function,
required: false,
},
})
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<Handle type="target" :position="Position.Left" :is-valid-connection="props.isValidSourcePos" />
<div>{{ props.id }}</div>
</template>

View File

@@ -0,0 +1,4 @@
export { default as ValidationApp } from './App.vue?raw'
export { default as ValidationCustomInput } from './CustomInput.vue?raw'
export { default as ValidationCustomNode } from './CustomNode.vue?raw'
export { default as ValidationCSS } from './style.css'

View File

@@ -0,0 +1,31 @@
.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;
}
.validationflow .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;
}

View File

@@ -28,18 +28,31 @@ watch([breakpoints.sm, breakpoints.md, breakpoints.lg, breakpoints.xl, breakpoin
<div
class="flex flex-col divide-y divide-gray-500 md:divide-y-0 gap-12 md:gap-24 lg:gap-36 max-w-9/12 md:max-w-11/12 lg:max-w-9/12 m-auto py-12 md:py-24 text-center md:text-left"
>
<div class="flex flex-col md:flex-row gap-12 md:gap-24">
<Basic @pane="onLoad" />
</div>
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
<RGB @pane="onLoad" />
</div>
<div class="flex flex-col md:flex-row flex-unwrap gap-12 md:gap-24">
<Nested @pane="onLoad" />
</div>
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
<Additional @pane="onLoad" />
<div ref="basic">
<XyzTransition :appear-visible="true" xyz="fade left ease-out-back">
<div class="flex flex-col md:flex-row gap-12 md:gap-24">
<Basic @pane="onLoad" />
</div>
</XyzTransition>
</div>
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
<RGB @pane="onLoad" />
</div>
</XyzTransition>
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
<div class="flex flex-col md:flex-row flex-unwrap gap-12 md:gap-24">
<Nested @pane="onLoad" />
</div>
</XyzTransition>
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
<Additional @pane="onLoad" />
</div>
</XyzTransition>
</div>
</div>
</template>

View File

@@ -18,9 +18,11 @@ onMounted(() => {
magnetic: false,
dotColor: '#10b981',
radius: 8,
focusableElementsOffsetX: 2,
focusableElementsOffsetY: 2,
focusableElementsOffsetX: 5,
focusableElementsOffsetY: 4,
mode: 'bouncy',
focusableElements:
'[data-blobity], a:not([data-no-blobity]), button:not([data-no-blobity]), [data-blobity-tooltip], .back-to-top, .intro',
})
onBeforeUnmount(() => {

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { GraphEdge, useVueFlow, VueFlow, Background, Controls, ClassFunc } from '@braks/vue-flow'
import { Background, ClassFunc, ConnectionLineType, Controls, GraphEdge, useVueFlow, VueFlow } from "@braks/vue-flow";
import Cross from "~icons/mdi/window-close";
const getClass: ClassFunc = (el) => {
const classes = ['font-semibold', '!border-2', 'transition-colors', 'duration-300', 'ease-in-out']
@@ -13,26 +14,31 @@ const getClass: ClassFunc = (el) => {
const emit = defineEmits(['pane'])
const { onPaneReady } = useVueFlow({
const { onPaneReady, onConnect, addEdges, viewport } = useVueFlow({
connectionLineType: ConnectionLineType.SmoothStep,
connectionLineStyle: {
strokeDasharray: 5,
animation: 'dashdraw 0.5s linear infinite',
},
modelValue: [
{
id: '1',
type: 'input',
label: 'input',
label: 'Start',
position: { x: 250, y: 5 },
class: getClass,
},
{
id: '2',
label: 'default',
label: 'Waypoint',
position: { x: 100, y: 100 },
class: getClass,
},
{ id: '3', label: 'default', position: { x: 400, y: 100 }, class: getClass },
{ id: '3', label: 'Waypoint', position: { x: 400, y: 100 }, class: getClass },
{
id: '4',
type: 'output',
label: 'output',
label: 'End',
position: { x: 250, y: 225 },
class: getClass,
},
@@ -93,14 +99,21 @@ const { onPaneReady } = useVueFlow({
})
onPaneReady((i) => emit('pane', i))
onConnect((param) => {
addEdges([{
...param,
type: 'smoothstep',
animated: true,
}])
})
</script>
<template>
<div class="md:max-w-1/3 flex flex-col justify-center">
<div class="flex flex-col gap-2 items-center md:items-start">
<h1>Batteries-included</h1>
<div class="flex flex-col items-center md:items-start">
<h1>Interactive Graphs</h1>
<p>
Vue Flow comes with several built-in features including zooming, panning, zoom & pan controls, single & multi-selections, element dragging,
node and edge components and event handlers.
Vue Flow comes with built-in features like zoom & pan and dedicated controls, single & multi-selections, draggable elements,
customizable nodes and edges and a bunch of event handlers.
</p>
<router-link class="button max-w-max" to="/guide/"> Documentation </router-link>
</div>
@@ -109,8 +122,12 @@ onPaneReady((i) => emit('pane', i))
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase border-1 border-secondary overflow-hidden"
>
<VueFlow class="basic">
<Controls class="md:!left-auto md:!right-[10px]" />
<Background pattern-color="#aaa" :gap="16" />
<Controls class="md:(!left-auto !right-[10px])" />
<Background pattern-color="#aaa" :gap="60">
<template #pattern>
<Cross :style="{ fontSize: `${8 * viewport.zoom || 1}px` }" class="text-[#10b981] opacity-50" />
</template>
</Background>
</VueFlow>
</div>
</template>

View File

@@ -1,11 +1,17 @@
<script lang="ts" setup>
import { Handle, Position, VueFlow, useVueFlow } from '@braks/vue-flow'
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
import BoxNode from '../nodes/Box.vue'
import confetti from 'canvas-confetti'
import colors from 'windicss/colors'
import { cheer, fireworks } from './confetti'
import Heart from '~icons/mdi/heart'
const breakpoints = useBreakpoints(breakpointsTailwind)
const dark = useDark({
selector: 'html',
})
const initialEdges = [
{
id: 'eintro-examples',
@@ -35,7 +41,7 @@ const initialEdges = [
style: { strokeWidth: 2, stroke: '#0ea5e9' },
},
]
const { dimensions, instance, onPaneReady, getNodes, getNode, getEdge, updateEdge, edges, setEdges } = useVueFlow({
const { dimensions, instance, onNodeClick, onPaneReady, getNodes, getNode, getEdge, updateEdge, edges, setEdges } = useVueFlow({
nodes: [
{ id: 'intro', type: 'box', position: { x: 0, y: 0 } },
{ id: 'examples', type: 'box', position: { x: -50, y: 400 } },
@@ -47,10 +53,54 @@ const { dimensions, instance, onPaneReady, getNodes, getNode, getEdge, updateEdg
nodesDraggable: false,
panOnDrag: false,
zoomOnScroll: false,
zoomOnDoubleClick: false,
zoomOnPinch: false,
})
const clickInterval = ref()
const clicks = ref(0)
const disabled = ref(false)
const confettiColors = Object.values(colors).flatMap((color) => {
if (typeof color === 'string') return color
else return Object.values(color).flatMap((c) => c)
})
onNodeClick(async ({ node }) => {
if (node.id === 'intro') {
if (disabled.value) return
if (clickInterval.value) {
clearInterval(clickInterval.value)
}
clickInterval.value = setInterval(() => {
clearInterval(clickInterval.value)
clicks.value = 0
}, 1000)
clicks.value++
if (clicks.value < 10) {
confetti({
startVelocity: 25,
spread: 360,
angle: 270,
origin: { y: 0.1 },
colors: confettiColors,
})
} else if (clicks.value < 20) {
await cheer(['#10b981', dark.value ? '#ffffff' : '#000000'])
} else if (clicks.value === 20) {
disabled.value = true
await fireworks(confettiColors)
disabled.value = false
} else {
clicks.value = 0
}
}
})
const init = ref(false)
const edgeId = ref('eintro-documentation')
const el = templateRef<HTMLDivElement>('el', null)
onPaneReady(({ fitView }) => {
@@ -83,40 +133,34 @@ onPaneReady(({ fitView }) => {
}
})
nextTick(() => {
setEdges(() => {
return [
{
id: 'eintro-examples',
type: 'smoothstep',
sourceHandle: 'a',
source: 'intro',
target: 'examples',
animated: true,
style: { strokeWidth: 2, stroke: '#8b5cf6' },
},
{
id: 'eexamples-documentation',
type: 'smoothstep',
source: 'examples',
target: 'documentation',
animated: true,
style: { strokeWidth: 2, stroke: '#f97316' },
},
{
id: 'edocumentation-acknowledgement',
type: 'smoothstep',
source: 'documentation',
target: 'acknowledgement',
animated: true,
style: { strokeWidth: 2, stroke: '#0ea5e9' },
},
]
})
fitView({
duration: 1500,
})
setEdges(() => {
return [
{
id: 'eintro-examples',
type: 'smoothstep',
sourceHandle: 'a',
source: 'intro',
target: 'examples',
animated: true,
style: { strokeWidth: 2, stroke: '#8b5cf6' },
},
{
id: 'eexamples-documentation',
type: 'smoothstep',
source: 'examples',
target: 'documentation',
animated: true,
style: { strokeWidth: 2, stroke: '#f97316' },
},
{
id: 'edocumentation-acknowledgement',
type: 'smoothstep',
source: 'documentation',
target: 'acknowledgement',
animated: true,
style: { strokeWidth: 2, stroke: '#0ea5e9' },
},
]
})
} else {
getNodes.value.forEach((node) => {
@@ -143,24 +187,16 @@ onPaneReady(({ fitView }) => {
}
})
nextTick(() => {
setEdges(initialEdges)
fitView({
duration: 1500,
})
})
setEdges(initialEdges)
}
setTimeout(
() => {
if (!init.value) init.value = true
else instance.value?.fitView()
},
init.value ? 5 : 1505,
)
nextTick(() => {
fitView()
if (!init.value) init.value = true
})
}
useResizeObserver(el, setNodes)
useResizeObserver(el, useDebounceFn(setNodes, 5))
})
const scrollTo = () => {
@@ -175,34 +211,35 @@ const scrollTo = () => {
<VueFlow ref="el" class="dark:bg-black bg-white transition-colors duration-200 ease-in-out">
<template #node-box="props">
<template v-if="props.id === 'intro'">
<div class="max-w-[500px]">
<BoxNode class="intro">
<div class="box max-w-[500px]">
<div class="intro px-4 py-2 shadow-lg rounded-md border-2 border-solid border-black">
<div class="font-mono flex flex-col gap-4 p-4 items-center text-center">
<h1 class="text-2xl lg:text-4xl">Vue Flow</h1>
<h2 class="text-lg lg:text-xl font-normal !border-0">
The customizable Vue 3 component bringing interactivity to flowcharts and graphs.
</h2>
</div>
</BoxNode>
<Handle id="a" type="source" :position="Position.Bottom" />
</div>
</div>
</template>
<template v-else-if="props.id === 'documentation'">
<div class="flex">
<router-link class="link group bg-orange-500" to="/guide/"> Read The Documentation </router-link>
<router-link class="link group bg-[#f15a16]" to="/guide/"> Read The Documentation </router-link>
</div>
<Handle type="target" :position="Position.Top" />
<Handle class="block md:hidden" type="source" :position="Position.Bottom" />
</template>
<template v-else-if="props.id === 'examples'">
<div class="flex">
<router-link class="link group bg-purple-500" to="/examples/"> Check The Examples </router-link>
<router-link class="link group bg-[#ef467e]" to="/examples/"> Check The Examples </router-link>
</div>
<Handle type="target" :position="Position.Top" />
<Handle class="block md:hidden" type="source" :position="Position.Bottom" />
</template>
<template v-else-if="props.id === 'acknowledgement'">
<div class="flex" @click="scrollTo">
<div class="link group bg-sky-500"><Heart class="text-red-500" /> Acknowledgement</div>
<button class="link group bg-sky-500"><Heart class="text-red-500" /> Acknowledgement</button>
</div>
<Handle type="target" :position="Position.Top" />
</template>

View File

@@ -72,7 +72,7 @@ onPaneReady((i) => emit('pane', i))
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase border-1 border-green-500 overflow-hidden"
>
<VueFlow class="nested">
<Controls class="md:!left-auto md:!right-[10px]" />
<Controls class="md:(!left-auto !right-[10px])" />
<Background pattern-color="#aaa" class="!bg-gray-800" :gap="18" />
</VueFlow>
</div>

View File

@@ -0,0 +1,69 @@
import confetti from 'canvas-confetti'
export const fireworks = (colors?: string[]) => {
return new Promise((resolve) => {
const duration = 15 * 1000
const animationEnd = Date.now() + duration
const defaults = { startVelocity: 60, spread: 360, ticks: 20, zIndex: 0 }
function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min
}
const interval: any = setInterval(function () {
const timeLeft = animationEnd - Date.now()
if (timeLeft <= 0) {
clearInterval(interval)
return resolve(true)
}
const particleCount = 50 * (timeLeft / duration)
// since particles fall down, start a bit higher than random
confetti(
Object.assign({}, defaults, {
particleCount,
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
colors,
}),
)
confetti(
Object.assign({}, defaults, {
particleCount,
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
colors,
}),
)
}, 250)
})
}
export const cheer = (colors: string[]) => {
return new Promise((resolve) => {
const end = Date.now() + 2 * 1000
function frame() {
confetti({
particleCount: 2,
angle: 60,
spread: 75,
origin: { x: 0 },
colors,
})
confetti({
particleCount: 2,
angle: 120,
spread: 75,
origin: { x: 1 },
colors,
})
if (Date.now() < end) {
requestAnimationFrame(frame)
} else {
resolve(true)
}
}
frame()
})
}

View File

@@ -1,12 +0,0 @@
<script lang="ts" setup>
import { Handle, Position } from '@braks/vue-flow'
const emit = defineEmits(['next'])
</script>
<template>
<div class="px-4 py-2 shadow-lg rounded-md border-2 border-solid border-black">
<slot />
<Handle id="a" type="source" :position="Position.Bottom" />
</div>
</template>

View File

@@ -8,11 +8,17 @@
"lint": "eslint --ext \".js,.jsx,.ts,.tsx\" --fix --ignore-path .gitignore ."
},
"dependencies": {
"@braks/vue-flow": "^0.4.6",
"blobity": "^0.1.7"
"@animxyz/core": "^0.6.6",
"@animxyz/vue3": "^0.6.6",
"@braks/vue-flow": "^0.4.11",
"@stackblitz/sdk": "^1.6.0",
"@vue/repl": "^1.1.2",
"blobity": "^0.1.7",
"canvas-confetti": "^1.5.1"
},
"devDependencies": {
"@iconify/json": "^2.1.22",
"@types/canvas-confetti": "^1.4.2",
"@types/node": "^17.0.23",
"@vuepress/bundler-vite": "^2.0.0-beta.37",
"@vuepress/plugin-docsearch": "^2.0.0-beta.37",

View File

@@ -6,17 +6,29 @@ declare module 'vue' {
export interface GlobalComponents {
Acknowledgement: typeof import('./../../components/home/Acknowledgement.vue')['default']
Additional: typeof import('./../../components/home/flows/Additional.vue')['default']
App: typeof import('./../../components/examples/basic/App.vue')['default']
Banner: typeof import('./../../components/home/Banner.vue')['default']
Basic: typeof import('./../../components/home/flows/Basic.vue')['default']
Box: typeof import('./../../components/home/nodes/Box.vue')['default']
Controls: typeof import('./../../components/examples/save-restore/Controls.vue')['default']
Custom: typeof import('./../../components/home/edges/Custom.vue')['default']
CustomConnectionLine: typeof import('./../../components/examples/connectionline/CustomConnectionLine.vue')['default']
CustomEdge: typeof import('./../../components/examples/edges/CustomEdge.vue')['default']
CustomEdge2: typeof import('./../../components/examples/edges/CustomEdge2.vue')['default']
CustomEdgeLabel: typeof import('./../../components/examples/edges/CustomEdgeLabel.vue')['default']
CustomInput: typeof import('./../../components/examples/validation/CustomInput.vue')['default']
CustomNode: typeof import('./../../components/examples/custom-node/CustomNode.vue')['default']
Features: typeof import('./../../components/home/Features.vue')['default']
Flow: typeof import('./../../components/examples/multi/Flow.vue')['default']
Home: typeof import('./../../components/home/Home.vue')['default']
Input: typeof import('./../../components/home/nodes/Input.vue')['default']
InteractionControls: typeof import('./../../components/examples/interaction/InteractionControls.vue')['default']
Intro: typeof import('./../../components/home/flows/Intro.vue')['default']
Nested: typeof import('./../../components/home/flows/Nested.vue')['default']
Output: typeof import('./../../components/home/nodes/Output.vue')['default']
PiniaExample: typeof import('./../../components/examples/pinia/PiniaExample.vue')['default']
Repl: typeof import('./../../components/Repl.vue')['default']
RGB: typeof import('./../../components/home/flows/RGB.vue')['default']
Sidebar: typeof import('./../../components/examples/dnd/Sidebar.vue')['default']
}
}

View File

@@ -10,6 +10,7 @@ import Components from 'unplugin-vue-components/vite'
import AutoImport from 'unplugin-auto-import/vite'
import head from './head'
import { useVueFlow } from '@braks/vue-flow'
import { copyVueFlowPlugin } from "./copy-plugin";
config({ path: resolve(__dirname, '.env') })
@@ -23,7 +24,11 @@ export default defineUserConfig<DefaultThemeOptions>({
bundler: '@vuepress/bundler-vite',
bundlerConfig: {
viteOptions: {
optimizeDeps: {
exclude: ['@animxyz/vue3'],
},
plugins: [
copyVueFlowPlugin(),
AutoImport({
imports: ['vue', '@vueuse/core'],
dts: resolve(__dirname, './auto-imports.d.ts'),
@@ -109,6 +114,7 @@ export default defineUserConfig<DefaultThemeOptions>({
'/examples/hidden',
'/examples/interaction',
'/examples/multi',
'/examples/pinia',
'/examples/stress',
],
},

View File

@@ -0,0 +1,27 @@
import { Plugin } from 'vite'
import { existsSync, readFileSync } from 'fs'
import { resolve } from 'path'
export function copyVueFlowPlugin(): Plugin {
return {
name: 'copy-vue-flow',
generateBundle() {
console.log('building')
const filePath = resolve(
__dirname,
'../../../node_modules/@braks/vue-flow/dist/vue-flow.es.js'
)
if (!existsSync(filePath)) {
throw new Error(
`@braks/vue-flow/dist/vue-flow.es.js not built. ` +
`Run "yarn build" first.`
)
}
this.emitFile({
type: 'asset',
fileName: 'vue-flow.es.js',
source: readFileSync(filePath, 'utf-8'),
})
}
}
}

View File

@@ -0,0 +1,6 @@
import { defineClientAppEnhance } from '@vuepress/client'
import VueAnimXyz from '@animxyz/vue3'
export default defineClientAppEnhance(({ app }) => {
app.use(VueAnimXyz)
})

View File

@@ -1,5 +1,6 @@
import { resolve } from 'path'
import { Theme } from 'vuepress'
import { path } from '@vuepress/utils'
export default {
name: 'vuepress-theme-local',
@@ -7,4 +8,5 @@ export default {
layouts: {
Layout: resolve(__dirname, 'layouts/default.vue'),
},
clientAppEnhanceFiles: path.resolve(__dirname, './clientAppEnhance.ts'),
} as Theme

View File

@@ -1,5 +1,6 @@
<script>
import 'virtual:windi.css'
import '@animxyz/core'
import '@braks/vue-flow/dist/style.css'
import '@braks/vue-flow/dist/theme-default.css'
import '../../assets/index.css'

View File

@@ -6,33 +6,14 @@ pageClass: examples
# Custom Connection Line
If the default connection lines aren't to your liking, or you want to expand on the existing
functionality you can pass a custom connection line component to Vue Flow.
functionality you can use your own custom connection line.
Simply pass a component in the designated template slot, and you're good to go.
```vue:no-line-numbers
<template>
<VueFlow>
<template #connection-line="props">
<MyCustomConnectionLine v-bind="props" />
</template>
</VueFlow>
</template>
```
You can see a working example in the sandbox.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-custom-connection-line-0okgze?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Custom Connection Line"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-custom-connection-line-0okgze?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Custom Connection Line"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="connectionline"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -5,24 +5,15 @@ pageClass: examples
# Custom Node
One of the key features of Vue Flow is implementing custom elements (nodes / edges) that allow you to expand on the basic node functionality (dragging, selection etc).
One of the key features of Vue Flow is implementing custom elements (nodes / edges) that allow you to expand on the basic functionalities (dragging, selecting etc).
You can display any content and functionality inside a custom node. More documentation about how to set
up a custom node can be found on the [custom nodes page](/guide/node.html#custom-nodes/).
You can see a working example in the sandbox below.
up a custom node can be found on the [custom nodes page](/guide/node.html#custom-nodes).
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-custom-node-example-wznb3q?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Custom Node Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-custom-node-example-wznb3q?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Custom Node Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="customNode"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -16,16 +16,9 @@ This example shows another key feature of Vue Flow. You can initialize the Flow
is actually mounted. This can be achieved by using the [`useVueFlow`](/guide/composables.html#usevueflow) composable.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-drag-drop-tpbm7d?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Drag & Drop"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-drag-drop-tpbm7d?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Drag & Drop"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="dnd"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -9,16 +9,9 @@ Vue Flow comes with four pre-defined edge types - bezier-, step-, smoothstep and
In addition to the built-in edge types you can create your own custom edges. You can find more information on edge types [here](/guide/edge.html#default-edge-types).
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-edges-3jbddk?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Edges"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-edges-3jbddk?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Edges"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="edges"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -9,16 +9,9 @@ Similar to the drag and drop example, we can also add nodes to an empty graph on
which triggers a push into our elements / nodes array.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-empty-example-bcxxdv?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Empty Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-empty-example-bcxxdv?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Empty Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="empty"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -13,16 +13,9 @@ If you set the `onlyRenderVisibleElements` prop to `true`, hidden elements will
css. This behavior is disabled by default, meaning all elements are rendered whether they are hidden or not.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-hidden-example-nrpjl7?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Hidden Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-hidden-example-nrpjl7?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Hidden Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="hidden"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -11,16 +11,9 @@ On the bottom left you see the viewport-controls and on the bottom right the min
You can also see the built-in node (default, input, output) and edge (bezier, straight, step, smoothstep) types.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-basic-example-3hq147?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FBasic.vue&moduleview=1&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Basic Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-basic-example-3hq147?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FBasic.vue&moduleview=1&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Basic Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="basic"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -10,16 +10,9 @@ This includes enabling/disabling zoom-scroll, pan-scroll, dragging etc.
In the sandbox below you can find an example showing you how to toggle interaction options.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-interaction-example-pqzf7p?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Hidden Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-interaction-example-pqzf7p?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Hidden Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="interaction"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -10,16 +10,9 @@ Vue Flow is not limited to a single instance on a page.
You can use as many component instances as you like.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-multi-flows-example-y4yn9u?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FMultiFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Multi Flows Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-multi-flows-example-y4yn9u?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FMultiFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Multi Flows Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="multi"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -12,16 +12,9 @@ You can also define the boundaries of a node, i.e. if the node can be dragged ou
or even let Vue Flow extend the area of a node to fit all it's children.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/compassionate-goldberg-h5dsnq?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Nested Nodes Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/compassionate-goldberg-h5dsnq?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Nested Nodes Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="nested"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -0,0 +1,15 @@
---
pageClass: examples
---
# Pinia
Using your existing storage implementation is not an issue.
You can store your elements in whatever store you're already using, mutate them there and pass the result to Vue Flow.
In this example we use [pinia](https://pinia.vuejs.org/) to store our elements, update their positions and toggle classes.
<div class="mt-6">
<PiniaExample />
</div>

View File

@@ -5,27 +5,21 @@ pageClass: examples
# Save & Restore
::: warning BREAKING CHANGE
__Changes from 0.3.x to 0.4.x__
The "built-in" storage feature has been fully removed.
There is no built-in persistent storage feature, however you can use your own storage implementation.
::: tip State Management Libraries
Check the [pinia](/examples/pinia) example to see how to use Vue Flow with a state management library.
:::
There is no built-in storage feature, however creating a save & restore feature is simple.
This example demonstrates save & restore functionality using the [`LocalStorage`](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage) of the browser.
This example demonstrates save & restore functionality using the `LocalStorage` of the browser.
You are of course free to implement your own logic (for example fetching the data from an API that's connected to a database).
You can extend on this basic idea however you like, i.e. fetch data that you transform into elements which are then passed to Vue Flow or
some other business logic you would like to handle.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-save-restore-example-1u7u6t?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Save & Restore Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-save-restore-example-1u7u6t?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Save & Restore Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="saveRestore"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -10,16 +10,9 @@ Vue Flow is built to be fast but there are limits.
Try out how many nodes you can get to render before your browser crashes.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-stress-zyr01z?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Stress"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-stress-zyr01z?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Stress"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="stress"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -14,16 +14,9 @@ for specific edges by using the `updatable` attribute.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/eager-tree-c7igf6?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Updatable Edge Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/eager-tree-c7igf6?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Updatable Edge Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="updateEdge"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -14,16 +14,9 @@ That means you can manipulate any property of your original nodes, and it will t
You can of course also access the nodes directly from the state and change their properties from there.
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-update-node-example-q5hjp3?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Update Node Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-update-node-example-q5hjp3?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Update Node Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="updateNode"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -31,16 +31,9 @@ const nodes = [
```
<div class="mt-6">
<iframe src="https://codesandbox.io/embed/vue-flow-validation-example-zxbyus?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
title="Vue Flow: Validation Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<iframe src="https://codesandbox.io/embed/vue-flow-validation-example-zxbyus?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
class="block dark:hidden h-full w-full min-h-[75vh]"
title="Vue Flow: Validation Example"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<client-only>
<Suspense>
<Repl example="validation"></Repl>
</Suspense>
</client-only>
</div>

View File

@@ -13,4 +13,6 @@ footer: MIT Licensed | Copyright © 2021-present Burak Cakmakoglu
<Features />
<Acknowledgement />
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
<Acknowledgement />
</XyzTransition>

View File

@@ -15,7 +15,7 @@ interface CustomEdgeProps<T = any> extends EdgeProps<T> {
}
const props = defineProps<CustomEdgeProps>()
const { id: storeId, applyEdgeChanges, store, getEdges } = useVueFlow()
const { id: storeId, applyEdgeChanges, getEdges } = useVueFlow()
const onClick = (evt: Event, id: string) => {
applyEdgeChanges([{ type: 'remove', id }])

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { Connection, Edge, Elements, FlowEvents, FlowInstance, FlowTransform, Node, SnapGrid } from '@braks/vue-flow'
import { Background, Controls, MarkerType, MiniMap, VueFlow, addEdge } from '@braks/vue-flow'
import { Background, Controls, MarkerType, MiniMap, VueFlow, addEdge } from '@braks/vue-flow/src/index'
const onNodeDragStart = (e: FlowEvents['nodeDragStart']) => console.log('drag start', e)
const onNodeDrag = (e: FlowEvents['nodeDrag']) => console.log('drag', e)
@@ -66,7 +66,7 @@ const initialElements: Elements = [
{ 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', markerEnd: MarkerType.Arrow, label: 'edge with arrow head' },
{ id: 'e4-5', source: '4', target: '5', markerEnd: { type: MarkerType.ArrowClosed }, label: 'edge with arrowhead' },
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
{
id: 'e5-7',

View File

@@ -14,7 +14,7 @@ export default defineConfig({
},
plugins: [
vue({
reactivityTransform: './src/',
reactivityTransform: true,
}),
vueTypes(),
svgLoader(),

View File

@@ -108,7 +108,6 @@ export default {
v-for="node of getNodes"
:id="node.id"
:key="node.id"
v-memo="[node.computedPosition, node.dimensions]"
:position="node.computedPosition"
:dimensions="node.dimensions"
:style="node.style"

View File

@@ -6,10 +6,9 @@ import type { GraphNode, NodeComponent, SnapGrid } from '../../types'
import { NodeId } from '../../context'
import { getHandleBounds, getXYZPos } from '../../utils'
const { id, type, name, node, parentNode, draggable, selectable, connectable, snapGrid } = defineProps<{
const { id, type, name, node, draggable, selectable, connectable, snapGrid } = defineProps<{
id: string
node: GraphNode
parentNode?: GraphNode
draggable: boolean
selectable: boolean
connectable: boolean
@@ -30,7 +29,6 @@ const {
updateNodePosition,
updateNodeDimensions,
getNode,
getNodeTypes,
addSelectedNodes,
} = $(useVueFlow())

View File

@@ -132,6 +132,19 @@
dependencies:
"@jridgewell/trace-mapping" "^0.3.0"
"@animxyz/core@^0.6.6":
version "0.6.6"
resolved "https://registry.yarnpkg.com/@animxyz/core/-/core-0.6.6.tgz#2c5eae54c7a6b98c3be79ae16fc1ec68991f67cc"
integrity sha512-NtAA/G0Gq3hzAiL6yuE/4U8IgHMPUl3MxbWUbhO443T9UCsf9rBY94P5aK79Zd+/529FeoNdDphIOcOZLsI2sA==
"@animxyz/vue3@^0.6.6":
version "0.6.6"
resolved "https://registry.yarnpkg.com/@animxyz/vue3/-/vue3-0.6.6.tgz#473b788d07842db7dd78cf317cc7a6b4bc9fbc89"
integrity sha512-ljHz97eNJMBLA2qVX3UDFHQbwIVKFUbKb6w6QbKUrkITY8oMwBz08ZYTYJguSUERXbpazftMSiaHsgRVnEbV/Q==
dependencies:
"@animxyz/core" "^0.6.6"
clsx "^1.1.1"
"@antfu/eslint-config-basic@0.23.0":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@antfu/eslint-config-basic/-/eslint-config-basic-0.23.0.tgz#9763f9900afd553bf2a74bd3b5398662f2bae62e"
@@ -1435,6 +1448,11 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
"@stackblitz/sdk@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@stackblitz/sdk/-/sdk-1.6.0.tgz#b53ff39058943b7db5a1aeb640498fac8b1cbe57"
integrity sha512-PPB9m1zywojv6sOGSiHv+tvnb4Zc00Jce9yjUG0BzTymUdFf9AP+gKJRlc0iq9vbEEEXfYv1bgKa5KTn4gAzbQ==
"@surma/rollup-plugin-off-main-thread@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
@@ -1474,6 +1492,11 @@
"@types/node" "*"
"@types/responselike" "*"
"@types/canvas-confetti@^1.4.2":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz#35c99fc904492fdcc6515c742509e04f3527211c"
integrity sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg==
"@types/d3-array@*":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.2.tgz#71c35bca8366a40d1b8fce9279afa4a77fb0065d"
@@ -1898,6 +1921,14 @@
"@typescript-eslint/types" "5.23.0"
"@typescript-eslint/visitor-keys" "5.23.0"
"@typescript-eslint/scope-manager@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz#e78f1484bca7e484c48782075219c82c6b77a09f"
integrity sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==
dependencies:
"@typescript-eslint/types" "5.25.0"
"@typescript-eslint/visitor-keys" "5.25.0"
"@typescript-eslint/type-utils@5.23.0":
version "5.23.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
@@ -1912,6 +1943,11 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
"@typescript-eslint/types@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.25.0.tgz#dee51b1855788b24a2eceeae54e4adb89b088dd8"
integrity sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==
"@typescript-eslint/typescript-estree@5.23.0":
version "5.23.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
@@ -1925,7 +1961,20 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.23.0", "@typescript-eslint/utils@^5.22.0":
"@typescript-eslint/typescript-estree@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz#a7ab40d32eb944e3fb5b4e3646e81b1bcdd63e00"
integrity sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==
dependencies:
"@typescript-eslint/types" "5.25.0"
"@typescript-eslint/visitor-keys" "5.25.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.23.0":
version "5.23.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
@@ -1937,6 +1986,18 @@
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/utils@^5.22.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.25.0.tgz#272751fd737733294b4ab95e16c7f2d4a75c2049"
integrity sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.25.0"
"@typescript-eslint/types" "5.25.0"
"@typescript-eslint/typescript-estree" "5.25.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.23.0":
version "5.23.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
@@ -1945,6 +2006,14 @@
"@typescript-eslint/types" "5.23.0"
eslint-visitor-keys "^3.0.0"
"@typescript-eslint/visitor-keys@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz#33aa5fdcc5cedb9f4c8828c6a019d58548d4474b"
integrity sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==
dependencies:
"@typescript-eslint/types" "5.25.0"
eslint-visitor-keys "^3.3.0"
"@vitejs/plugin-vue@^2.2.0", "@vitejs/plugin-vue@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.1.tgz#5f286b8d3515381c6d5c8fa8eee5e6335f727e14"
@@ -2079,6 +2148,11 @@
dependencies:
"@vue/shared" "3.2.33"
"@vue/repl@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@vue/repl/-/repl-1.1.2.tgz#1a8e4ac1697d21bb355650f276a2f0390dbb51cf"
integrity sha512-IYcoKZgwdn6spiVwGuWeXAbqAv3Nhjc/0dKEJ3BT5U6kV7KLLCwnuizjOvV8P+yFmtsfNkWbmWv9/56Pv5rH/w==
"@vue/runtime-core@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
@@ -2974,6 +3048,11 @@ caniuse-lite@^1.0.30001317:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606"
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
canvas-confetti@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.5.1.tgz#bf5b8622ef3bcd347378a972fc4194a89cfe0c9b"
integrity sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -3154,6 +3233,11 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
@@ -4907,7 +4991,7 @@ globals@^13.6.0, globals@^13.9.0:
dependencies:
type-fest "^0.20.2"
globby@^11.0.1, globby@^11.0.4:
globby@^11.0.1, globby@^11.0.4, globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -7514,7 +7598,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.0.0:
semver@^7.0.0, semver@^7.3.7:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==