docs: move examples dir out of components dir

This commit is contained in:
braks
2023-12-12 21:40:56 +01:00
committed by Braks
parent 7ba258c53b
commit bb2bf72012
89 changed files with 20 additions and 34 deletions

View File

@@ -3,7 +3,7 @@ import type { SFCOptions } from '@vue/repl'
import { ReplStore, Repl as VueRepl } from '@vue/repl'
import { useVueFlow } from '@vue-flow/core'
import '@vue/repl/style.css'
import { exampleImports } from './examples'
import { exampleImports } from '../examples'
const props = defineProps<{ example: keyof typeof exampleImports; mainFile?: string; dependencies?: Record<string, string> }>()

View File

@@ -1,130 +0,0 @@
<script setup>
import { Panel, VueFlow, isNode, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
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, addEdges, setTransform, toObject } = 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
*/
function updatePos() {
return 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
*/
function logToObject() {
return console.log(toObject())
}
/**
* Resets the current viewpane transformation (zoom & pan)
*/
function resetTransform() {
return setTransform({ x: 0, y: 0, zoom: 1 })
}
function toggleClass() {
return (dark.value = !dark.value)
}
</script>
<template>
<VueFlow v-model="elements" :class="{ dark }" class="basicflow" :default-viewport="{ zoom: 1.5 }" :min-zoom="0.2" :max-zoom="4">
<Background :pattern-color="dark ? '#FFFFFB' : '#aaa'" gap="8" />
<MiniMap />
<Controls />
<Panel position="top-right" class="controls">
<button style="background-color: #113285; color: white" title="Reset Transform" @click="resetTransform">
<svg width="16" height="16" viewBox="0 0 32 32">
<path fill="#FFFFFB" d="M18 28A12 12 0 1 0 6 16v6.2l-3.6-3.6L1 20l6 6l6-6l-1.4-1.4L8 22.2V16a10 10 0 1 1 10 10Z" />
</svg>
</button>
<button style="background-color: #6f3381" title="Shuffle Node Positions" @click="updatePos">
<svg width="16" height="16" viewBox="0 0 24 24">
<path
fill="#FFFFFB"
d="M14 20v-2h2.6l-3.2-3.2l1.425-1.425L18 16.55V14h2v6Zm-8.6 0L4 18.6L16.6 6H14V4h6v6h-2V7.4Zm3.775-9.425L4 5.4L5.4 4l5.175 5.175Z"
/>
</svg>
</button>
<button
:style="{ backgroundColor: dark ? '#FFFFFB' : '#292524', color: dark ? '#292524' : '#FFFFFB' }"
@click="toggleClass"
>
<template v-if="dark">
<svg width="16" height="16" viewBox="0 0 24 24">
<path
fill="#292524"
d="M12 17q-2.075 0-3.537-1.463Q7 14.075 7 12t1.463-3.538Q9.925 7 12 7t3.538 1.462Q17 9.925 17 12q0 2.075-1.462 3.537Q14.075 17 12 17ZM2 13q-.425 0-.712-.288Q1 12.425 1 12t.288-.713Q1.575 11 2 11h2q.425 0 .713.287Q5 11.575 5 12t-.287.712Q4.425 13 4 13Zm18 0q-.425 0-.712-.288Q19 12.425 19 12t.288-.713Q19.575 11 20 11h2q.425 0 .712.287q.288.288.288.713t-.288.712Q22.425 13 22 13Zm-8-8q-.425 0-.712-.288Q11 4.425 11 4V2q0-.425.288-.713Q11.575 1 12 1t.713.287Q13 1.575 13 2v2q0 .425-.287.712Q12.425 5 12 5Zm0 18q-.425 0-.712-.288Q11 22.425 11 22v-2q0-.425.288-.712Q11.575 19 12 19t.713.288Q13 19.575 13 20v2q0 .425-.287.712Q12.425 23 12 23ZM5.65 7.05L4.575 6q-.3-.275-.288-.7q.013-.425.288-.725q.3-.3.725-.3t.7.3L7.05 5.65q.275.3.275.7q0 .4-.275.7q-.275.3-.687.287q-.413-.012-.713-.287ZM18 19.425l-1.05-1.075q-.275-.3-.275-.712q0-.413.275-.688q.275-.3.688-.287q.412.012.712.287L19.425 18q.3.275.288.7q-.013.425-.288.725q-.3.3-.725.3t-.7-.3ZM16.95 7.05q-.3-.275-.287-.688q.012-.412.287-.712L18 4.575q.275-.3.7-.288q.425.013.725.288q.3.3.3.725t-.3.7L18.35 7.05q-.3.275-.7.275q-.4 0-.7-.275ZM4.575 19.425q-.3-.3-.3-.725t.3-.7l1.075-1.05q.3-.275.713-.275q.412 0 .687.275q.3.275.288.688q-.013.412-.288.712L6 19.425q-.275.3-.7.287q-.425-.012-.725-.287Z"
/>
</svg>
</template>
<template v-else>
<svg width="16" height="16" viewBox="0 0 24 24">
<path
fill="#FFFFFB"
d="M12 21q-3.75 0-6.375-2.625T3 12q0-3.75 2.625-6.375T12 3q.35 0 .688.025q.337.025.662.075q-1.025.725-1.637 1.887Q11.1 6.15 11.1 7.5q0 2.25 1.575 3.825Q14.25 12.9 16.5 12.9q1.375 0 2.525-.613q1.15-.612 1.875-1.637q.05.325.075.662Q21 11.65 21 12q0 3.75-2.625 6.375T12 21Z"
/>
</svg>
</template>
</button>
<button title="Log `toObject`" @click="logToObject">
<svg width="16" height="16" viewBox="0 0 24 24">
<path
fill="#292524"
d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59l3.99-4Z"
/>
</svg>
</button>
</Panel>
</VueFlow>
</template>

View File

@@ -1,3 +0,0 @@
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

@@ -1,25 +0,0 @@
import { MarkerType } from '@vue-flow/core'
/**
* 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: 0 }, 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.ArrowClosed },
{
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

@@ -1,47 +0,0 @@
.basicflow.dark {
background: #57534e;
color: #FFFFFB;
}
.basicflow.dark .vue-flow__node {
background: #292524;
color: #FFFFFB;
}
.basicflow.dark .vue-flow__controls .vue-flow__controls-button {
background: #292524;
fill: #FFFFFB;
border-color: #FFFFFB;
}
.basicflow.dark .vue-flow__edge-textbg {
fill: #292524;
}
.basicflow.dark .vue-flow__edge-text {
fill: #FFFFFB;
}
.basicflow .controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.basicflow .controls button {
padding: 4px;
border-radius: 5px;
font-weight: 600;
-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;
display: flex;
justify-content: center;
align-items: center;
}
.basicflow .controls button:hover {
transform: scale(102%);
transition: 250ms all ease;
}

View File

@@ -1,38 +0,0 @@
<script setup>
import { VueFlow } from '@vue-flow/core'
import { ref } from 'vue'
import ConnectionLine from './SnappableConnectionLine.vue'
const elements = ref([
{
id: '1',
label: 'Node 1',
position: { x: 0, y: 0 },
},
{
id: '2',
label: 'Node 2',
position: { x: 100, y: 100 },
},
{
id: '3',
label: 'Node 3',
position: { x: 200, y: 0 },
},
])
</script>
<template>
<VueFlow v-model="elements" :connection-radius="30" auto-connect fit-view-on-init>
<template #connection-line="{ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }">
<ConnectionLine
:source-x="sourceX"
:source-y="sourceY"
:target-x="targetX"
:target-y="targetY"
:source-position="sourcePosition"
:target-position="targetPosition"
/>
</template>
</VueFlow>
</template>

View File

@@ -1,152 +0,0 @@
<script setup>
import { connectionExists, getBezierPath, useVueFlow } from '@vue-flow/core'
import { computed, reactive, ref, watch } from 'vue'
const props = defineProps({
sourceX: {
type: Number,
required: true,
},
sourceY: {
type: Number,
required: true,
},
targetX: {
type: Number,
required: true,
},
targetY: {
type: Number,
required: true,
},
targetPosition: {
type: String,
reuire: true,
},
sourcePosition: {
type: String,
required: true,
},
})
const { getNodes, connectionStartHandle, onConnectEnd, addEdges, edges } = useVueFlow()
const closest = reactive({
node: null,
handle: null,
startHandle: connectionStartHandle.value,
})
const canSnap = ref(false)
const HIGHLIGHT_COLOR = '#f59e0b'
const SNAP_HIGHLIGHT_COLOR = '#10b981'
const MIN_DISTANCE = 75
const SNAP_DISTANCE = 30
watch([() => props.targetY, () => props.targetX], (_, __, onCleanup) => {
const closestNode = getNodes.value.reduce(
(res, n) => {
if (n.id !== connectionStartHandle.value?.nodeId) {
const dx = props.targetX - (n.computedPosition.x + n.dimensions.width / 2)
const dy = props.targetY - (n.computedPosition.y + n.dimensions.height / 2)
const d = Math.sqrt(dx * dx + dy * dy)
if (d < res.distance && d < MIN_DISTANCE) {
res.distance = d
res.node = n
}
}
return res
},
{
distance: Number.MAX_VALUE,
node: null,
},
)
if (!closestNode.node) {
return
}
canSnap.value = closestNode.distance < SNAP_DISTANCE
const type = connectionStartHandle.value.type === 'source' ? 'target' : 'source'
const closestHandle = closestNode.node.handleBounds[type]?.reduce((prev, curr) => {
const prevDistance = Math.sqrt((prev.x - props.targetX) ** 2 + (prev.y - props.targetY) ** 2)
const currDistance = Math.sqrt((curr.x - props.targetX) ** 2 + (curr.y - props.targetY) ** 2)
return prevDistance < currDistance ? prev : curr
})
if (
connectionExists(
{
source: connectionStartHandle.value.nodeId,
sourceHandle: connectionStartHandle.value.handleId,
target: closestNode.node.id,
targetHandle: closestHandle.id,
},
edges.value,
)
) {
return
}
if (closestHandle) {
const el = document.querySelector(`[data-handleid='${closestHandle.id}']`)
const prevStyle = el.style.backgroundColor
el.style.backgroundColor = canSnap.value ? SNAP_HIGHLIGHT_COLOR : HIGHLIGHT_COLOR
closest.node = closestNode.node
closest.handle = closestHandle
onCleanup(() => {
el.style.backgroundColor = prevStyle
closest.node = null
closest.handle = null
})
}
})
const path = computed(() => getBezierPath(props))
onConnectEnd(() => {
if (closest.startHandle && closest.handle && closest.node) {
if (canSnap.value) {
addEdges([
{
sourceHandle: closest.startHandle.handleId,
source: closest.startHandle.nodeId,
target: closest.node.id,
targetHandle: closest.handle.id,
},
])
}
}
})
const strokeColor = computed(() => {
if (canSnap.value) {
return SNAP_HIGHLIGHT_COLOR
}
if (closest.node) {
return HIGHLIGHT_COLOR
}
return '#222'
})
</script>
<template>
<g>
<path :d="path[0]" class="vue-flow__connection-path" />
<circle :cx="targetX" :cy="targetY" fill="#fff" :stroke="strokeColor" :r="3" :stroke-width="1.5" />
</g>
</template>

View File

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

View File

@@ -1,22 +0,0 @@
<script setup>
import { VueFlow } from '@vue-flow/core'
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="{ sourceX, sourceY, targetX, targetY }">
<CustomConnectionLine :source-x="sourceX" :source-y="sourceY" :target-x="targetX" :target-y="targetY" />
</template>
</VueFlow>
</template>

View File

@@ -1,34 +0,0 @@
<script setup>
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="vue-flow__connection 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="4" stroke="#6F3381" :stroke-width="1.5" />
</g>
</template>

View File

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

View File

@@ -1,129 +0,0 @@
<script setup>
import { MiniMap } from '@vue-flow/minimap'
import { Position, VueFlow, useVueFlow } from '@vue-flow/core'
import { h, onMounted, ref } from 'vue'
import ColorSelectorNode from './CustomNode.vue'
import { presets } from './presets.js'
const { findNode } = useVueFlow()
const elements = ref([])
const gradient = ref(false)
const bgColor = ref(presets.ayame)
const bgName = ref('AYAME')
const connectionLineStyle = { stroke: '#fff' }
// minimap stroke color functions
function nodeStroke(n) {
if (n.type === 'input') {
return '#0041d0'
}
if (n.type === 'custom') {
return presets.sumi
}
if (n.type === 'output') {
return '#ff0072'
}
return '#eee'
}
function nodeColor(n) {
if (n.type === 'custom') {
return bgColor.value
}
return '#fff'
}
// output labels
function outputColorLabel() {
return h('div', {}, bgColor.value)
}
function outputNameLabel() {
return h('div', {}, bgName.value)
}
function onChange(color) {
gradient.value = false
bgColor.value = color.value
bgName.value = color.name
findNode('3').hidden = false
}
function onGradient() {
gradient.value = true
bgColor.value = null
bgName.value = 'gradient'
findNode('3').hidden = true
}
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: 200 },
targetPosition: Position.Left,
},
{
id: 'e1a-2',
source: '1',
sourceHandle: 'a',
target: '2',
animated: true,
style: () => ({
stroke: bgColor.value,
filter: 'invert(100%)',
}),
},
{
id: 'e1b-3',
source: '1',
sourceHandle: 'b',
target: '3',
animated: true,
style: () => ({
stroke: bgColor.value,
filter: 'invert(100%)',
}),
},
]
})
</script>
<template>
<VueFlow
v-model="elements"
class="customnodeflow"
:class="[gradient ? 'animated-bg-gradient' : '']"
:style="{ backgroundColor: bgColor }"
:connection-line-style="connectionLineStyle"
:default-viewport="{ zoom: 1.5 }"
fit-view-on-init
>
<template #node-custom="{ data }">
<ColorSelectorNode :data="data" @change="onChange" @gradient="onGradient" />
</template>
<MiniMap :node-stroke-color="nodeStroke" :node-color="nodeColor" />
</VueFlow>
</template>

View File

@@ -1,49 +0,0 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
import { computed } from 'vue'
import { colors } from './presets.js'
const props = defineProps({
data: {
type: Object,
required: true,
},
})
const emit = defineEmits(['change', 'gradient'])
function onSelect(color) {
emit('change', color)
}
function onGradient() {
emit('gradient')
}
const sourceHandleStyleA = computed(() => ({ backgroundColor: props.data.color, filter: 'invert(100%)', top: '10px' }))
const sourceHandleStyleB = computed(() => ({
backgroundColor: props.data.color,
filter: 'invert(100%)',
bottom: '10px',
top: 'auto',
}))
</script>
<template>
<div>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 }" type="button" @click="onSelect(color)" />
</template>
<button class="animated-bg-gradient" title="gradient" type="button" @click="onGradient" />
</div>
<Handle id="a" type="source" :position="Position.Right" :style="sourceHandleStyleA" />
<Handle id="b" type="source" :position="Position.Right" :style="sourceHandleStyleB" />
</template>

View File

@@ -1,4 +0,0 @@
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

@@ -1,30 +0,0 @@
export const presets = {
sumi: '#1C1C1C',
gofun: '#FFFFFB',
byakuroku: '#A8D8B9',
mizu: '#81C7D4',
asagi: '#33A6B8',
ukon: '#EFBB24',
mushikuri: '#D9CD90',
hiwa: '#BEC23F',
ichigo: '#B5495B',
kurenai: '#CB1B45',
syojyohi: '#E83015',
konjyo: '#113285',
fuji: '#8B81C3',
ayame: '#6F3381',
torinoko: '#DAC9A6',
kurotsurubami: '#0B1013',
ohni: '#F05E1C',
kokikuchinashi: '#FB9966',
beniukon: '#E98B2A',
sakura: '#FEDFE1',
toki: '#EEA9A9',
}
export const colors = Object.keys(presets).map((color) => {
return {
name: color,
value: presets[color],
}
})

View File

@@ -1,73 +0,0 @@
.customnodeflow .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;
}
.customnodeflow button {
padding: 5px;
width: 25px;
height: 25px;
border-radius: 25px;
-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;
}
.customnodeflow button:hover {
opacity: 0.9;
transform: scale(105%);
transition: 250ms all ease;
}
.animated-bg-gradient {
background: linear-gradient(122deg, #6f3381, #81c7d4, #fedfe1, #fffffb);
background-size: 800% 800%;
-webkit-animation: gradient 4s ease infinite;
-moz-animation: gradient 4s ease infinite;
animation: gradient 4s ease infinite;
}
@-webkit-keyframes gradient {
0% {
background-position: 0% 22%
}
50% {
background-position: 100% 79%
}
100% {
background-position: 0% 22%
}
}
@-moz-keyframes gradient {
0% {
background-position: 0% 22%
}
50% {
background-position: 100% 79%
}
100% {
background-position: 0% 22%
}
}
@keyframes gradient {
0% {
background-position: 0% 22%
}
50% {
background-position: 100% 79%
}
100% {
background-position: 0% 22%
}
}

View File

@@ -1,74 +0,0 @@
<script setup>
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { nextTick, watch } from 'vue'
import Sidebar from './Sidebar.vue'
let id = 0
function getId() {
return `dndnode_${id++}`
}
const { findNode, onConnect, addEdges, addNodes, project, vueFlowRef } = useVueFlow({
nodes: [
{
id: '1',
type: 'input',
label: 'input node',
position: { x: 250, y: 25 },
},
],
})
function onDragOver(event) {
event.preventDefault()
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
onConnect((params) => addEdges(params))
function onDrop(event) {
const type = event.dataTransfer?.getData('application/vueflow')
const { left, top } = vueFlowRef.value.getBoundingClientRect()
const position = project({
x: event.clientX - left,
y: event.clientY - top,
})
const newNode = {
id: getId(),
type,
position,
label: `${type} node`,
}
addNodes([newNode])
// align node position after drop, so it's centered to the mouse
nextTick(() => {
const node = findNode(newNode.id)
const stop = watch(
() => node.dimensions,
(dimensions) => {
if (dimensions.width > 0 && dimensions.height > 0) {
node.position = { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 }
stop()
}
},
{ deep: true, flush: 'post' },
)
})
}
</script>
<template>
<div class="dndflow" @drop="onDrop">
<VueFlow @dragover="onDragOver" />
<Sidebar />
</div>
</template>

View File

@@ -1,22 +0,0 @@
<script setup>
function 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

@@ -1,3 +0,0 @@
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

@@ -1,51 +0,0 @@
.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

@@ -1,75 +0,0 @@
<script setup>
import { Background } from '@vue-flow/background'
import { MarkerType, VueFlow } from '@vue-flow/core'
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: 'Start', position: { x: 50, y: 0 }, style: { borderColor: '#10b981' } },
{ 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: 175, y: 300 } },
{ id: '5', label: 'Node 5', position: { x: 200, y: 400 } },
{ id: '6', type: 'output', label: 'Output 6', position: { x: 0, y: 350 } },
{ id: '7', type: 'output', label: 'Output 7', position: { x: 50, y: 600 } },
{ id: '8', type: 'output', label: 'Output 8', position: { x: 350, y: 600 } },
{ id: '9', type: 'output', label: 'Output 9', position: { x: 550, y: 400 } },
{ 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: '#10b981' } },
{
id: 'e2a-6',
source: '2a',
target: '6',
label: () => h(CustomEdgeLabel, { label: 'custom label text' }),
labelStyle: { fill: '#10b981', fontWeight: 700 },
markerEnd: MarkerType.Arrow,
},
{
id: 'e5-7',
source: '5',
target: '7',
label: 'label with 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: 'e4-9',
source: '4',
target: '9',
type: 'custom2',
data: { text: 'styled custom edge label' },
},
])
</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>
<Background />
</VueFlow>
</template>

View File

@@ -1,76 +0,0 @@
<script setup>
import { BaseEdge, EdgeLabelRenderer, getBezierPath, useVueFlow } from '@vue-flow/core'
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 { removeEdges } = useVueFlow()
const path = computed(() => getBezierPath(props))
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<!-- You can use the `BaseEdge` component to create your own custom edge more easily -->
<BaseEdge :id="id" :style="style" :path="path[0]" :marker-end="markerEnd" />
<!-- Use the `EdgeLabelRenderer` to escape the SVG world of edges and render your own custom label in a `<div>` ctx -->
<EdgeLabelRenderer>
<div
:style="{
pointerEvents: 'all',
position: 'absolute',
transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`,
}"
class="nodrag nopan"
>
<button class="edgebutton" @click="removeEdges(id)">×</button>
</div>
</EdgeLabelRenderer>
</template>

View File

@@ -1,80 +0,0 @@
<script setup>
import { BaseEdge, getBezierPath } from '@vue-flow/core'
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 path = computed(() => getBezierPath(props))
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<BaseEdge
:id="id"
:style="style"
:path="path[0]"
:marker-end="markerEnd"
:label="data.text"
:label-x="path[1]"
:label-y="path[2]"
:label-style="{ fill: 'white' }"
:label-show-bg="true"
:label-bg-style="{ fill: 'red' }"
:label-bg-padding="[2, 4]"
:label-bg-border-radius="2"
/>
</template>

View File

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

View File

@@ -1,5 +0,0 @@
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

@@ -1,10 +0,0 @@
.edgebutton {
border-radius: 999px;
cursor: pointer;
}
.edgebutton:hover {
transform: scale(110%);
transition: all ease 500ms;
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.5), 0 0 0 4px #10b981;
}

View File

@@ -1,33 +0,0 @@
<script setup>
import { Panel, VueFlow, useVueFlow } from '@vue-flow/core'
import { Background, BackgroundVariant } from '@vue-flow/background'
import { MiniMap } from '@vue-flow/minimap'
const { nodes, addNodes, addEdges, onConnect, dimensions } = useVueFlow()
onConnect((params) => addEdges(params))
function 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 />
<Background :variant="BackgroundVariant.Lines" />
<Panel position="top-right">
<button type="button" @click="addRandomNode">add node</button>
</Panel>
</VueFlow>
</template>

View File

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

View File

@@ -1,41 +0,0 @@
<script setup>
import { Background } from '@vue-flow/background'
import { Panel, VueFlow, useVueFlow } from '@vue-flow/core'
import { ref, watch } 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' },
],
})
watch(isHidden, () => {
nodes.value.forEach((n) => (n.hidden = isHidden.value))
edges.value.forEach((e) => (e.hidden = isHidden.value))
})
</script>
<template>
<VueFlow fit-view-on-init>
<Background />
<Panel position="top-right">
<div>
<label for="ishidden">
hidden
<input id="ishidden" v-model="isHidden" type="checkbox" />
</label>
</div>
</Panel>
</VueFlow>
</template>

View File

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

View File

@@ -1,32 +0,0 @@
<script setup>
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
import { ref } from 'vue'
import { initialElements } from './initial-elements.js'
const { onConnect, addEdges } = useVueFlow()
const elements = ref(initialElements)
onConnect((params) => addEdges(params))
</script>
<template>
<VueFlow
v-model="elements"
class="basicflow"
:default-edge-options="{ type: 'smoothstep' }"
:default-viewport="{ zoom: 1.5 }"
:min-zoom="0.2"
:max-zoom="4"
fit-view-on-init
>
<Background pattern-color="#aaa" gap="8" />
<MiniMap />
<Controls />
</VueFlow>
</template>

View File

@@ -1,2 +0,0 @@
export { default as HorizontalApp } from './App.vue?raw'
export { default as HorizontalElements } from './initial-elements.js?raw'

View File

@@ -1,38 +0,0 @@
import { MarkerType, Position } from '@vue-flow/core'
export const initialElements = [
{
id: '1',
type: 'input',
label: 'Node 1',
position: { x: 0, y: 50 },
sourcePosition: Position.Right,
},
{
id: '2',
type: 'output',
label: 'Node 2',
position: { x: 250, y: 0 },
targetPosition: Position.Left,
},
{ id: '3', label: 'Node 3', position: { x: 250, y: 100 }, sourcePosition: Position.Right, targetPosition: Position.Left },
{ id: '4', label: 'Node 4', position: { x: 500, y: 150 }, sourcePosition: Position.Right, targetPosition: Position.Left },
{
id: '5',
type: 'output',
label: 'Node 5',
position: { x: 750, y: 50 },
sourcePosition: Position.Right,
targetPosition: Position.Left,
},
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3', markerEnd: MarkerType.Arrow },
{
id: 'e4-5',
source: '4',
target: '5',
style: { stroke: 'orange' },
labelBgStyle: { fill: 'orange' },
},
{ id: 'e3-4', source: '3', target: '4' },
]

View File

@@ -1,132 +0,0 @@
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'
import { HorizontalApp, HorizontalElements } from './horizontal'
import { TeleportApp, TeleportCSS, TeleportSidebar, TeleportableNode, TeleportableUseTransition } from './teleport'
import { TransitionApp, TransitionCSS, TransitionEdge } from './transition'
import { IntersectionApp, IntersectionCSS, IntersectionElements } from './intersection'
import { SnapToHandleApp, SnappableConnectionLine } from './connection-radius'
import { NodeResizerApp, ResizableNode } from './node-resizer'
import { ToolbarApp, ToolbarNode } from './node-toolbar'
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,
},
horizontal: {
'App.vue': HorizontalApp,
'initial-elements.js': HorizontalElements,
},
teleport: {
'App.vue': TeleportApp,
'Sidebar.vue': TeleportSidebar,
'TeleportableNode.vue': TeleportableNode,
'useTeleport.js': TeleportableUseTransition,
'style.css': TeleportCSS,
},
transition: {
'App.vue': TransitionApp,
'TransitionEdge.vue': TransitionEdge,
'style.css': TransitionCSS,
'additionalImports': {
'@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@9.3.0/index.mjs',
'@vueuse/shared': 'https://cdn.jsdelivr.net/npm/@vueuse/shared@9.3.0/index.mjs',
'vue-demi': 'https://cdn.jsdelivr.net/npm/vue-demi@0.13.11/lib/index.mjs',
},
},
intersection: {
'App.vue': IntersectionApp,
'initial-elements.js': IntersectionElements,
'style.css': IntersectionCSS,
},
snappable: {
'App.vue': SnapToHandleApp,
'SnappableConnectionLine.vue': SnappableConnectionLine,
},
resizable: {
'App.vue': NodeResizerApp,
'ResizableNode.vue': ResizableNode,
},
toolbar: {
'App.vue': ToolbarApp,
'ToolbarNode.vue': ToolbarNode,
},
}

View File

@@ -1,20 +0,0 @@
<script setup>
import { VueFlow } from '@vue-flow/core'
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 v-model="elements" class="interactionflow" fit-view-on-init>
<InteractionControls />
</VueFlow>
</template>

View File

@@ -1,117 +0,0 @@
<script setup>
import { useVueFlow } from '@vue-flow/core'
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

@@ -1,3 +0,0 @@
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

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

View File

@@ -1,27 +0,0 @@
<script setup>
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { ref } from 'vue'
import { initialElements } from './initial-elements.js'
/**
* You can either use `getIntersectingNodes` to check if a given node intersects with others
* or `isNodeIntersecting` to check if a node is intersecting with a given area
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onNodeDrag, getIntersectingNodes, isNodeIntersecting, getNodes } = useVueFlow()
const elements = ref(initialElements)
onNodeDrag(({ intersections }) => {
const intersectionIds = intersections.map((intersection) => intersection.id)
getNodes.value.forEach((n) => {
const isIntersecting = intersectionIds.includes(n.id)
n.class = isIntersecting ? 'intersecting' : ''
})
})
</script>
<template>
<VueFlow v-model="elements" fit-view-on-init :default-viewport="{ zoom: 1.5 }" :min-zoom="0.2" :max-zoom="4" />
</template>

View File

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

View File

@@ -1,7 +0,0 @@
export const initialElements = [
{ id: '1', label: 'Node 1', position: { x: 0, y: 0 } },
{ id: '2', label: 'Node 2', position: { x: 400, y: 400 } },
{ id: '3', label: 'Node 3', position: { x: 400, y: 0 } },
{ id: '4', label: 'Node 4', position: { x: 0, y: 400 } },
{ id: '5', style: { borderColor: 'red' }, label: 'Drag me over another node', position: { x: 200, y: 200 } },
]

View File

@@ -1,3 +0,0 @@
.vue-flow__node.intersecting {
background-color: yellow;
}

View File

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

View File

@@ -1,40 +0,0 @@
<script setup>
import { Background } from '@vue-flow/background'
import { Panel, VueFlow, isNode } from '@vue-flow/core'
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' },
])
function toggleClass() {
return elements.value.forEach((el) => (el.class = el.class === 'light' ? 'dark' : 'light'))
}
function updatePos() {
return 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>
<Background />
<Panel position="top-right">
<button style="margin-right: 5px" @click="updatePos">update positions</button>
<button @click="toggleClass">toggle class</button>
</Panel>
</VueFlow>
</template>

View File

@@ -1,3 +0,0 @@
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

@@ -1,18 +0,0 @@
.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

@@ -1,97 +0,0 @@
<script setup>
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
import { onMounted } from 'vue'
const { onConnect, addEdges, addNodes } = useVueFlow({
fitViewOnInit: true,
// set this to true so edges get elevated when selected, defaults to false
elevateEdgesOnSelect: true,
nodes: [
{ id: '1', type: 'input', label: 'node', position: { x: 250, y: 0 } },
{
id: '2',
label: 'parent node',
position: { x: 100, y: 100 },
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: '4',
label: 'parent node',
position: { x: 320, y: 175 },
style: { backgroundColor: 'rgba(16, 185, 129, 0.5)', width: '400px', height: '300px' },
},
{
id: '4a',
label: 'child node',
position: { x: 15, y: 65 },
extent: 'parent',
parentNode: '4',
},
{
id: '4b',
label: 'nested parent node',
position: { x: 15, y: 120 },
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 },
parentNode: '4b',
},
{
id: '4b2',
label: 'nested child node',
position: { x: 100, y: 100 },
parentNode: '4b',
},
{ id: '4c', label: 'child node', position: { x: 200, y: 65 }, parentNode: '4' },
],
edges: [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-4', source: '1', target: '4' },
{ id: 'e1-4c', source: '1', target: '4c' },
{ id: 'e2a-4a', source: '2a', target: '4a' },
{ 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

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

View File

@@ -1,23 +0,0 @@
<script setup>
import { VueFlow } from '@vue-flow/core'
import { ref } from 'vue'
import ResizableNode from './ResizableNode.vue'
const elements = ref([
{
id: '1',
type: 'resizable',
label: 'NodeResizer',
position: { x: 0, y: 0 },
style: { background: '#fff', border: '2px solid black' },
},
])
</script>
<template>
<VueFlow v-model="elements" fit-view-on-init>
<template #node-resizable="resizableNodeProps">
<ResizableNode :label="resizableNodeProps.label" />
</template>
</VueFlow>
</template>

View File

@@ -1,14 +0,0 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
import { NodeResizer } from '@vue-flow/node-resizer'
defineProps(['label'])
</script>
<template>
<NodeResizer min-width="100" min-height="30" />
<Handle type="target" :position="Position.Left" />
<div style="padding: 10px">{{ label }}</div>
<Handle type="source" :position="Position.Right" />
</template>

View File

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

View File

@@ -1 +0,0 @@
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/node-resizer@latest/dist/style.css';

View File

@@ -1,63 +0,0 @@
<script setup>
import { Position, VueFlow } from '@vue-flow/core'
import { ref } from 'vue'
import ToolbarNode from './ToolbarNode.vue'
const defaultNodeStyle = {
border: '1px solid #10b981',
background: '#ef467e',
color: 'white',
borderRadius: '99px',
}
const elements = ref([
{
id: '1',
type: 'toolbar',
label: 'toolbar top',
data: { toolbarPosition: Position.Top },
position: { x: 200, y: 0 },
style: defaultNodeStyle,
},
{
id: '2',
type: 'toolbar',
label: 'toolbar right',
data: { toolbarPosition: Position.Right },
position: { x: -50, y: 100 },
style: defaultNodeStyle,
},
{
id: '3',
type: 'toolbar',
label: 'toolbar bottom',
data: { toolbarPosition: Position.Bottom },
position: { x: 0, y: 200 },
style: defaultNodeStyle,
},
{
id: '4',
type: 'toolbar',
label: 'toolbar left',
data: { toolbarPosition: Position.Left },
position: { x: 200, y: 300 },
style: defaultNodeStyle,
},
{
id: '5',
type: 'toolbar',
label: 'toolbar always open',
data: { toolbarPosition: Position.Top, toolbarVisible: true },
position: { x: 0, y: -100 },
style: defaultNodeStyle,
},
])
</script>
<template>
<VueFlow v-model="elements" fit-view-on-init class="vue-flow-basic-example">
<template #node-toolbar="nodeProps">
<ToolbarNode :data="nodeProps.data" :label="nodeProps.label" />
</template>
</VueFlow>
</template>

View File

@@ -1,25 +0,0 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
import { NodeToolbar } from '@vue-flow/node-toolbar'
defineProps(['data', 'label'])
</script>
<template>
<NodeToolbar
style="display: flex; gap: 0.5rem; align-items: center"
:is-visible="data.toolbarVisible"
:position="data.toolbarPosition"
>
<button>Action1</button>
<button>Action2</button>
<button>Action3</button>
</NodeToolbar>
<div :style="{ padding: '10px 20px' }">
{{ label }}
</div>
<Handle type="target" :position="Position.Left" />
<Handle type="source" :position="Position.Right" />
</template>

View File

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

View File

@@ -1,17 +0,0 @@
<script lang="ts" setup>
import sdk from '@stackblitz/sdk'
const el = ref<HTMLDivElement>()
onMounted(() => {
sdk.embedProjectId(el.value!, 'vitejs-vite-ujrwcm', {
forceEmbedLayout: true,
openFile: 'src/App.vue',
height: 750,
})
})
</script>
<template>
<div ref="el" class="outline-none"></div>
</template>

View File

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

View File

@@ -1,42 +0,0 @@
<script setup>
import { Panel, useVueFlow } from '@vue-flow/core'
const flowKey = 'example-flow'
const { nodes, addNodes, setNodes, setEdges, dimensions, setTransform, toObject } = useVueFlow()
function onSave() {
localStorage.setItem(flowKey, JSON.stringify(toObject()))
}
function onRestore() {
const flow = JSON.parse(localStorage.getItem(flowKey))
if (flow) {
const [x = 0, y = 0] = flow.position
setNodes(flow.nodes)
setEdges(flow.edges)
setTransform({ x, y, zoom: flow.zoom || 0 })
}
}
function 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>
<Panel position="top-right" class="save-restore-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>
</Panel>
</template>

View File

@@ -1,3 +0,0 @@
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

@@ -1,20 +0,0 @@
.save-restore-controls {
font-size: 12px;
}
.save-restore-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-restore-controls button:hover {
transform: scale(105%);
transition: 250ms all ease-in-out;
}

View File

@@ -1,51 +0,0 @@
<script setup>
import { Panel, VueFlow, isNode, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { MiniMap } from '@vue-flow/minimap'
import { nextTick, ref } from 'vue'
import { getElements } from './utils.js'
const { nodes, edges } = getElements(15, 15)
const elements = ref([...nodes, ...edges])
const { onPaneReady, dimensions, fitView } = useVueFlow()
onPaneReady(({ fitView, getElements }) => {
fitView({ padding: 0.2 })
console.log(getElements.value)
})
function toggleClass() {
return elements.value.forEach((el) => (el.class = el.class === 'light' ? 'dark' : 'light'))
}
function updatePos() {
elements.value.forEach((el) => {
if (isNode(el)) {
el.position = {
x: Math.random() * 10 * dimensions.value.width,
y: Math.random() * 10 * dimensions.value.height,
}
}
})
nextTick(() => {
fitView({ duration: 1000, padding: 0.5 })
})
}
</script>
<template>
<VueFlow v-model="elements" :min-zoom="0.1">
<MiniMap />
<Background />
<Panel position="top-right">
<button style="margin-right: 5px" @click="updatePos">update positions</button>
<button @click="toggleClass">toggle class</button>
</Panel>
</VueFlow>
</template>

View File

@@ -1,3 +0,0 @@
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

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

View File

@@ -1,42 +0,0 @@
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`, zIndex: 1 },
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(),
style: (edge) => {
if (!edge.sourceNode.selected && !edge.targetNode.selected) {
return
}
return { stroke: '#10b981', strokeWidth: 3 }
},
})
}
recentNodeId = nodeId
nodeId++
}
}
return {
nodes: initialNodes,
edges: initialEdges,
}
}

View File

@@ -1,47 +0,0 @@
<script setup>
import { VueFlow } from '@vue-flow/core'
import { ref } from 'vue'
import Sidebar from './Sidebar.vue'
import TeleportableNode from './TeleportableNode.vue'
const elements = ref([
{
id: '1',
label: 'Click to teleport',
type: 'teleportable',
position: { x: 125, y: 0 },
data: {},
},
{
id: '2',
label: 'Click to teleport',
type: 'teleportable',
position: { x: 350, y: 200 },
data: {},
},
{
id: '3',
label: 'Click to teleport',
type: 'teleportable',
position: { x: 0, y: 200 },
data: {},
},
{
id: 'e1-2',
source: '1',
target: '2',
},
])
</script>
<template>
<div class="teleportflow">
<VueFlow v-model="elements" fit-view-on-init>
<template #node-teleportable="{ id }">
<TeleportableNode :id="id" />
</template>
</VueFlow>
<Sidebar />
</div>
</template>

View File

@@ -1,11 +0,0 @@
<script setup>
// ...
</script>
<template>
<aside>
<div class="description">Teleport destination</div>
<div id="port" class="port"></div>
</aside>
</template>

View File

@@ -1,45 +0,0 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
import { useTeleport } from './useTeleport.js'
const props = defineProps({
id: {
type: String,
required: true,
},
})
const { animation, transition, teleport, onClick } = useTeleport(props.id)
function changeAnimation() {
animation.value = animation.value === 'fade' ? 'shrink' : 'fade'
}
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<teleport :disabled="!teleport" :to="teleport">
<transition :name="animation">
<div v-if="!transition" class="teleportable">
<Handle type="target" :position="Position.Top" />
[Node {{ id }}]
<div class="buttons">
<div v-if="teleport !== '#port'" class="button" @click.prevent="onClick('#port')">Teleport To Sidebar</div>
<div v-if="teleport !== null" class="button" @click.prevent="onClick(null)">Teleport To Main Graph</div>
<div class="button" @click.prevent="changeAnimation">Animation: {{ animation }}</div>
</div>
<Handle type="source" :position="Position.Bottom" />
</div>
</transition>
</teleport>
</template>

View File

@@ -1,5 +0,0 @@
export { default as TeleportApp } from './App.vue?raw'
export { default as TeleportSidebar } from './Sidebar.vue?raw'
export { default as TeleportableNode } from './TeleportableNode.vue?raw'
export { default as TeleportableUseTransition } from './useTeleport.js?raw'
export { default as TeleportCSS } from './style.css'

View File

@@ -1,125 +0,0 @@
.teleportflow {
flex-direction: column;
display: flex;
height: 100%;
}
.teleportflow aside {
display: flex;
flex-direction: column;
gap: 5px;
color: white;
font-weight: 700;
border-right: 1px solid #eee;
padding: 10px 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);
}
.teleportflow aside .port > * {
position: relative;
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);
}
.teleportflow aside .description {
margin-bottom: 10px;
}
.teleportflow .vue-flow-wrapper {
flex-grow: 1;
height: 100%;
}
@media screen and (min-width: 640px) {
.teleportflow {
flex-direction: row;
}
.teleportflow aside {
min-width: 25%;
}
}
@media screen and (max-width: 639px) {
.teleportflow aside .port {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 5px;
}
}
.teleportable {
padding: 10px;
background: white;
border: 1px solid black;
border-radius: 10px;
color: black
}
.buttons {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 5px;
gap: 5px;
}
.buttons .button {
background-color: whitesmoke;
cursor: pointer;
padding: 5px 10px;
border: 1px solid black;
border-radius: 10px;
color: black;
font-weight: 700;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}
.buttons .button:hover {
background: black;
color: white;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.shrink-leave-active {
animation: shrink 0.5s;
}
.shrink-enter-active {
animation: grow 0.5s;
}
@keyframes grow {
from {
transform: scale(0.1);
}
to {
transform: scale(1);
}
}
@keyframes shrink {
from {
transform: scale(1);
}
to {
transform: scale(0.1);
}
}

View File

@@ -1,107 +0,0 @@
import { getConnectedEdges, useVueFlow } from '@vue-flow/core'
import { nextTick, ref } from 'vue'
/**
* Utility composable for specifying animations
*
* Animations that resize a node need to call the `updateNodeDimensions` function from store to update node handle positions
* Otherwise edges do not connect properly
*/
export function useTeleport(id) {
const animation = ref('fade')
const transition = ref(false)
const teleport = ref(null)
const { updateNodeInternals, findNode, edges } = useVueFlow()
/**
* specify a selector to teleport to
*
* teleported elements still behave like they're at their position before,
* i.e. if they emit events, they will still emit them up their regular tree
*/
const fade = (destination, onFinish) => {
setTimeout(() => {
// teleport to destination or disable teleport
teleport.value = destination
setTimeout(() => {
transition.value = false
// if destination is null, defer hiding edges until node is teleported back
if (!destination) {
onFinish()
}
}, 500)
}, 500)
}
const shrink = (destination, onFinish) => {
setTimeout(() => {
// teleport to destination or disable teleport
teleport.value = destination
setTimeout(() => {
transition.value = false
setTimeout(() => {
// if destination is null, defer hiding edges until node is teleported back
if (!destination) {
updateNodeInternals([id])
nextTick(() => {
onFinish()
})
}
}, 500)
}, 500)
}, 500)
}
/**
* specify a selector to teleport to
*
* teleported elements still behave like they're at their position before,
* i.e. if they emit events, they will still emit them up their regular tree
*/
const onClick = (destination) => {
const node = findNode(id)
transition.value = true
// save current teleport destination to data of node
node.data.destination = destination
// hide connected edges when teleporting
const connectedEdges = getConnectedEdges([node], edges.value)
// if destination is not null, hide edges immediately
// check if nodes connected to edge are teleported and hide edge if one of them is
if (destination) {
connectedEdges.forEach(
(edge) => (edge.hidden = !!findNode(edge.source).data.destination || !!findNode(edge.target).data.destination),
)
}
const onFinish = () => {
connectedEdges.forEach(
(edge) => (edge.hidden = !!findNode(edge.source).data.destination || !!findNode(edge.target).data.destination),
)
}
switch (animation.value) {
case 'fade':
fade(destination, onFinish)
break
case 'shrink':
shrink(destination, onFinish)
}
}
return {
animation,
transition,
teleport,
onClick,
}
}

View File

@@ -1,40 +0,0 @@
<script setup>
import { ref } from 'vue'
import { Position, VueFlow, useVueFlow } from '@vue-flow/core'
import TransitionEdge from './TransitionEdge.vue'
const elements = ref([
{
id: '1',
type: 'input',
label: 'Dblclick me',
position: { x: 0, y: 0 },
sourcePosition: Position.Right,
},
{
id: '2',
type: 'output',
label: 'Dblclick me',
position: { x: 1000, y: 1000 },
targetPosition: Position.Left,
},
{ id: 'e1-2', type: 'custom', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
])
const { onPaneReady } = useVueFlow()
onPaneReady((i) => {
i.fitView({
nodes: ['1'],
})
})
</script>
<template>
<VueFlow v-model="elements" :style="{ backgroundColor: '#1A192B' }">
<template #edge-custom="props">
<TransitionEdge v-bind="props" />
</template>
</VueFlow>
</template>

View File

@@ -1,168 +0,0 @@
<script setup>
import { TransitionPresets, useDebounceFn, useTransition, watchDebounced } from '@vueuse/core'
import { getBezierPath, useVueFlow } from '@vue-flow/core'
import { computed, ref } from 'vue'
const props = defineProps({
id: {
type: String,
required: true,
},
source: {
type: String,
required: true,
},
target: {
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 curve = ref()
const dot = ref()
const transform = ref({ x: 0, y: 0 })
const showDot = ref(false)
const { onNodeDoubleClick, fitBounds, fitView } = useVueFlow()
const path = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
const debouncedFitBounds = useDebounceFn(fitBounds, 1, { maxWait: 1 })
onNodeDoubleClick(({ node }) => {
const isSource = props.source === node.id
const isTarget = props.target === node.id
if (!showDot.value && (isSource || isTarget)) {
showDot.value = true
let totalLength = curve.value.getTotalLength()
const initialPos = ref(isSource ? 0 : totalLength)
let stopHandle
const output = useTransition(initialPos, {
duration: Math.floor(totalLength / 2 / 100) * 1000,
transition: TransitionPresets.easeOutCubic,
onFinished: () => {
stopHandle?.()
showDot.value = false
fitView({
nodes: [isSource ? props.target : props.source],
duration: 500,
})
},
})
transform.value = curve.value.getPointAtLength(output.value)
debouncedFitBounds(
{
width: 100,
height: 200,
x: transform.value.x - 100,
y: transform.value.y - 100,
},
{ duration: 500 },
)
setTimeout(() => {
initialPos.value = isSource ? totalLength : 0
stopHandle = watchDebounced(
output,
(next) => {
if (!showDot.value) {
return
}
const nextLength = curve.value.getTotalLength()
if (totalLength !== nextLength) {
totalLength = nextLength
initialPos.value = isSource ? totalLength : 0
}
transform.value = curve.value.getPointAtLength(next)
debouncedFitBounds({
width: 100,
height: 200,
x: transform.value.x - 100,
y: transform.value.y - 100,
})
},
{ debounce: 1 },
)
}, 500)
}
})
</script>
<script>
export default {
inheritAttrs: false,
}
</script>
<template>
<path :id="id" ref="curve" :style="style" class="vue-flow__edge-path" :d="path[0]" :marker-end="markerEnd" />
<Transition name="fade">
<circle
v-if="showDot"
ref="dot"
r="5"
cy="0"
cx="0"
:transform="`translate(${transform.x}, ${transform.y})`"
style="fill: #fdd023"
/>
</Transition>
</template>

View File

@@ -1,3 +0,0 @@
export { default as TransitionApp } from './App.vue?raw'
export { default as TransitionEdge } from './TransitionEdge.vue?raw'
export { default as TransitionCSS } from './style.css'

View File

@@ -1,9 +0,0 @@
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

View File

@@ -1,57 +0,0 @@
<script setup>
import { Background } from '@vue-flow/background'
import { VueFlow, useVueFlow } from '@vue-flow/core'
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 { updateEdge, addEdges } = useVueFlow()
function onEdgeUpdateStart(edge) {
return console.log('start update', edge)
}
function onEdgeUpdateEnd(edge) {
return console.log('end update', edge)
}
function onEdgeUpdate({ edge, connection }) {
return updateEdge(edge, connection)
}
function onConnect(params) {
return addEdges([params])
}
</script>
<template>
<VueFlow
v-model="elements"
fit-view-on-init
@edge-update="onEdgeUpdate"
@connect="onConnect"
@edge-update-start="onEdgeUpdateStart"
@edge-update-end="onEdgeUpdateEnd"
>
<Background />
</VueFlow>
</template>

View File

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

View File

@@ -1,48 +0,0 @@
<script setup>
import { VueFlow, useVueFlow } from '@vue-flow/core'
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,
})
function 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

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

View File

@@ -1,30 +0,0 @@
.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

@@ -1,50 +0,0 @@
<script setup>
import { VueFlow, addEdge } from '@vue-flow/core'
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' },
])
function onConnectStart({ nodeId, handleType }) {
return console.log('on connect start', { nodeId, handleType })
}
function onConnectEnd(event) {
return console.log('on connect end', event)
}
function onConnect(params) {
console.log('on connect', params)
addEdge(params, elements.value)
}
</script>
<template>
<VueFlow
v-model="elements"
fit-view-on-init
class="validationflow"
@connect="onConnect"
@connect-start="onConnectStart"
@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

@@ -1,21 +0,0 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
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

@@ -1,25 +0,0 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
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

@@ -1,4 +0,0 @@
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

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