chore(docs): update example styles (#1469)

* chore(docs): update basic example styles

* chore(docs): update confirm delete example styles

* chore(docs): update dnd example styles

* chore(docs): update hidden example

* chore(docs): update update node example

* chore(docs): update node toolbar example styles

* chore(docs): update transition example styles

* chore(docs): cleanup basic example els

* chore(docs): cleanup examples
This commit is contained in:
Braks
2024-06-10 23:36:55 +02:00
committed by braks
parent 5dc8711a6b
commit d71de0a92d
34 changed files with 528 additions and 244 deletions

View File

@@ -8,10 +8,12 @@ import { initialEdges, initialNodes } from './initial-elements.js'
import Icon from './Icon.vue'
/**
* 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
* `useVueFlow` provides:
* 1. a set of methods to interact with the VueFlow instance (like `fitView`, `setViewport`, `addEdges`, etc)
* 2. a set of event-hooks to listen to VueFlow events (like `onInit`, `onNodeDragStop`, `onConnect`, etc)
* 3. the internal state of the VueFlow instance (like `nodes`, `edges`, `viewport`, etc)
*/
const { onPaneReady, onNodeDragStop, onConnect, addEdges, setViewport, toObject } = useVueFlow()
const { onInit, onNodeDragStop, onConnect, addEdges, setViewport, toObject } = useVueFlow()
const nodes = ref(initialNodes)
@@ -24,10 +26,11 @@ const dark = ref(false)
* 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
* Any event that is available as `@event-name` on the VueFlow component is also available as `onEventName` on the composable and vice versa
*
* onPaneReady is called when viewpane & nodes have visible dimensions
* onInit is called when the VueFlow viewport is initialized
*/
onPaneReady(({ fitView }) => {
fitView()
onInit((vueFlowInstance) => {
// instance is the same as the return of `useVueFlow`
vueFlowInstance.fitView()
})
/**
@@ -39,8 +42,8 @@ onPaneReady(({ fitView }) => {
* 3. the node that initiated the drag
* 4. any intersections with other nodes
*/
onNodeDragStop(({ event, nodes, node, intersections }) => {
console.log('Node Drag Stop', { event, nodes, node, intersections })
onNodeDragStop(({ event, nodes, node }) => {
console.log('Node Drag Stop', { event, nodes, node })
})
/**
@@ -94,7 +97,7 @@ function toggleDarkMode() {
:nodes="nodes"
:edges="edges"
:class="{ dark }"
class="basicflow"
class="basic-flow"
:default-viewport="{ zoom: 1.5 }"
:min-zoom="0.2"
:max-zoom="4"
@@ -103,7 +106,7 @@ function toggleDarkMode() {
<MiniMap />
<Controls position="top-right">
<Controls position="top-left">
<ControlButton title="Reset Transform" @click="resetTransform">
<Icon name="reset" />
</ControlButton>

View File

@@ -1,24 +1,69 @@
import { MarkerType } from '@vue-flow/core'
export const initialNodes = [
{ 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: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 0 },
class: 'light',
},
{
id: '2',
type: 'output',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
class: 'light',
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 100 },
class: 'light',
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 150, y: 200 },
class: 'light',
},
{
id: '5',
type: 'output',
data: { label: 'Node 5' },
position: { x: 300, y: 300 },
class: 'light',
},
]
export const initialEdges = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', label: 'edge with arrowhead', source: '1', target: '3', markerEnd: MarkerType.ArrowClosed },
{
id: 'e1-2',
source: '1',
target: '2',
animated: true,
},
{
id: 'e1-3',
source: '1',
target: '3',
label: 'edge with arrowhead',
markerEnd: MarkerType.ArrowClosed,
},
{
id: 'e4-5',
type: 'step',
label: 'step-edge',
source: '4',
target: '5',
label: 'Node 2',
style: { stroke: 'orange' },
labelBgStyle: { fill: 'orange' },
},
{ id: 'e3-4', type: 'smoothstep', label: 'smoothstep-edge', source: '3', target: '4' },
{
id: 'e3-4',
type: 'smoothstep',
source: '3',
target: '4',
label: 'smoothstep-edge',
},
]

View File

@@ -1,48 +1,52 @@
.basicflow.dark {
background: #000000;
.basic-flow.dark {
background: #2d3748;
color: #FFFFFB;
}
.basicflow.dark .vue-flow__node {
background: hsl(0, 0%, 10%);
.basic-flow.dark .vue-flow__node {
background: #4a5568;
color: #FFFFFB;
}
.basicflow.dark .vue-flow__node.selected {
.basic-flow.dark .vue-flow__node.selected {
background: hsl(0, 0%, 20%);
border: 1px solid hotpink;
box-shadow: 0 0 0 2px #2563eb;
}
.basicflow .vue-flow__controls {
.basic-flow .vue-flow__controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.basicflow.dark .vue-flow__controls {
.basic-flow.dark .vue-flow__controls {
border: 1px solid #FFFFFB;
}
.basicflow .vue-flow__controls .vue-flow__controls-button {
.basic-flow .vue-flow__controls .vue-flow__controls-button {
border: none;
border-right: 1px solid #eee;
}
.basic-flow .vue-flow__controls .vue-flow__controls-button svg {
height: 100%;
width: 100%;
}
.basicflow.dark .vue-flow__controls .vue-flow__controls-button {
.basic-flow.dark .vue-flow__controls .vue-flow__controls-button {
background: hsl(0, 0%, 20%);
fill: #FFFFFB;
border: none;
}
.basicflow.dark .vue-flow__controls .vue-flow__controls-button:hover {
.basic-flow.dark .vue-flow__controls .vue-flow__controls-button:hover {
background: hsl(0, 0%, 30%);
}
.basicflow.dark .vue-flow__edge-textbg {
.basic-flow.dark .vue-flow__edge-textbg {
fill: #292524;
}
.basicflow.dark .vue-flow__edge-text {
.basic-flow.dark .vue-flow__edge-text {
fill: #FFFFFB;
}

View File

@@ -1,5 +1,5 @@
<script setup>
import { ref } from 'vue'
import { h, ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { useDialog } from './useDialog'
@@ -10,16 +10,26 @@ const { onConnect, addEdges, onNodesChange, onEdgesChange, applyNodeChanges, app
const dialog = useDialog()
const nodes = 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: '1', type: 'input', data: { label: 'Click me and' }, position: { x: 0, y: 0 } },
{ id: '2', data: { label: `press 'Backspace' to delete me` }, position: { x: 0, y: 100 } },
])
const edges = ref([
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
])
const edges = ref([{ id: 'e1-2', source: '1', target: '2' }])
function dialogMsg(id) {
return h(
'span',
{
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '8px',
},
},
[`Are you sure?`, h('br'), h('span', `[ELEMENT_ID: ${id}]`)],
)
}
onConnect(addEdges)
@@ -28,7 +38,7 @@ onNodesChange(async (changes) => {
for (const change of changes) {
if (change.type === 'remove') {
const isConfirmed = await dialog.confirm(`Do you really want to delete this node: ${change.id}?`)
const isConfirmed = await dialog.confirm(dialogMsg(change.id))
if (isConfirmed) {
nextChanges.push(change)
@@ -46,7 +56,7 @@ onEdgesChange(async (changes) => {
for (const change of changes) {
if (change.type === 'remove') {
const isConfirmed = await dialog.confirm(`Do you really want to delete this edge: ${change.id}?`)
const isConfirmed = await dialog.confirm(dialogMsg(change.id))
if (isConfirmed) {
nextChanges.push(change)
@@ -61,7 +71,7 @@ onEdgesChange(async (changes) => {
</script>
<template>
<VueFlow :nodes="nodes" :edges="edges" :apply-default="false" fit-view-on-init class="vue-flow-basic-example">
<VueFlow :nodes="nodes" :edges="edges" :apply-default="false" fit-view-on-init class="confirm-flow">
<Background />
<Dialog />

View File

@@ -17,9 +17,17 @@ function cancel() {
<template>
<div v-if="isVisible" class="dialog-overlay">
<div class="dialog">
<p>{{ message }}</p>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path
fill="#e53e3e"
d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z"
/>
</svg>
<div class="dialog-actions">
<p v-if="typeof message === 'string'">{{ message }}</p>
<component :is="message" v-else />
<div class="actions">
<button @click="confirm">Confirm</button>
<button @click="cancel">Cancel</button>
</div>
@@ -42,16 +50,34 @@ function cancel() {
}
.dialog {
background: white;
background: #2d3748;
padding: 20px;
border-radius: 5px;
text-align: center;
color: white;
}
.dialog-actions {
.dialog .actions {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 8px;
}
.dialog .actions button {
background: #4a5568;
color: white;
border: none;
padding: 8px 16px;
border-radius: 5px;
cursor: pointer;
}
.dialog .actions button:first-of-type:hover {
background: #2563eb;
}
.dialog .actions button:last-of-type:hover {
background: #e53e3e;
}
</style>

View File

@@ -1,5 +1,8 @@
import { ref } from 'vue'
/**
* In a real world example you would want to avoid creating refs in a global scope like this
*/
const isVisible = ref(false)
const message = ref('')
let resolveCallback

View File

@@ -6,17 +6,17 @@ import ConnectionLine from './SnappableConnectionLine.vue'
const nodes = ref([
{
id: '1',
label: 'Node 1',
data: { label: 'Node 1' },
position: { x: 0, y: 0 },
},
{
id: '2',
label: 'Node 2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
},
{
id: '3',
label: 'Node 3',
data: { label: 'Node 3' },
position: { x: 200, y: 0 },
},
])

View File

@@ -7,7 +7,7 @@ const nodes = ref([
{
id: '1',
type: 'input',
label: 'Node 1',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
},
])

View File

@@ -21,8 +21,6 @@ const nodes = ref([
},
])
const colorSelectorData = toRef(() => nodes.value[0].data)
const edges = ref([
{
id: 'e1a-2',
@@ -30,19 +28,21 @@ const edges = ref([
sourceHandle: 'a',
target: '2',
animated: true,
style: () => ({
stroke: colorSelectorData.value?.color,
}),
style: {
stroke: presets.ayame,
},
},
])
const colorSelectorData = toRef(() => nodes.value[0].data)
// minimap stroke color functions
function nodeStroke(n) {
switch (n.type) {
case 'input':
return '#0041d0'
case 'color-selector':
return presets.sumi
return n.data.color
case 'output':
return '#ff0072'
default:
@@ -52,7 +52,7 @@ function nodeStroke(n) {
function nodeColor(n) {
if (n.type === 'color-selector') {
return colorSelectorData.value?.color
return n.data.color
}
return '#fff'
@@ -66,11 +66,10 @@ function nodeColor(n) {
class="custom-node-flow"
:class="[colorSelectorData?.isGradient ? 'animated-bg-gradient' : '']"
:style="{ backgroundColor: colorSelectorData?.color }"
:default-viewport="{ zoom: 1.5 }"
fit-view-on-init
>
<template #node-color-selector="{ id }">
<ColorSelectorNode :id="id" />
<template #node-color-selector="props">
<ColorSelectorNode :id="props.id" :data="props.data" />
</template>
<template #node-output>

View File

@@ -7,12 +7,23 @@ const props = defineProps({
type: String,
required: true,
},
data: {
type: Object,
required: true,
},
})
const { updateNodeData } = useVueFlow()
const { updateNodeData, getConnectedEdges } = useVueFlow()
function onSelect(color) {
updateNodeData(props.id, { color, isGradient: false })
const connectedEdges = getConnectedEdges(props.id)
for (const edge of connectedEdges) {
edge.style = {
stroke: color,
}
}
}
function onGradient() {
@@ -28,6 +39,7 @@ function onGradient() {
v-for="{ name: colorName, value: color } of colors"
:key="colorName"
:title="colorName"
:class="{ selected: color === data.color }"
:style="{ backgroundColor: color }"
type="button"
@click="onSelect(color)"

View File

@@ -9,6 +9,10 @@ const nodesData = useNodesData(() => connections.value[0]?.source)
</script>
<template>
<Handle type="target" :position="Position.Left" :style="{ backgroundColor: nodesData.data?.color, filter: 'invert(100%)' }" />
<Handle
type="target"
:position="Position.Left"
:style="{ height: '16px', width: '6px', backgroundColor: nodesData.data?.color, filter: 'invert(100%)' }"
/>
{{ nodesData.data?.isGradient ? 'GRADIENT' : nodesData.data?.color }}
</template>

View File

@@ -1,8 +1,14 @@
.custom-node-flow .vue-flow__edges {
.vue-flow__edges {
filter: invert(100%);
}
.custom-node-flow .vue-flow__node-color-selector {
.vue-flow__handle {
height: 24px;
width: 8px;
border-radius: 4px;
}
.vue-flow__node-color-selector {
border: 1px solid #777;
padding: 10px;
border-radius: 10px;
@@ -15,7 +21,7 @@
max-width: 250px;
}
.custom-node-flow .vue-flow__node-color-selector .color-selector {
.vue-flow__node-color-selector .color-selector {
display: flex;
flex-direction: row;
flex-wrap: wrap;
@@ -25,20 +31,30 @@
gap: 4px
}
.custom-node-flow .vue-flow__node-color-selector .color-selector button {
border: 1px solid #777;
.vue-flow__node-color-selector .color-selector button {
border: none;
cursor: pointer;
padding: 5px;
width: 25px;
height: 25px;
border-radius: 25px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.custom-node-flow .vue-flow__node-color-selector .color-selector button:hover {
-webkit-box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
transform: scale(105%);
transition: 250ms all ease;
.vue-flow__node-color-selector .color-selector button:hover {
box-shadow: 0 0 0 2px #2563eb;
transition: box-shadow 0.2s;
}
.vue-flow__node-color-selector .color-selector button.selected {
box-shadow: 0 0 0 2px #2563eb;
}
.vue-flow__node-color-selector .vue-flow__handle {
background-color: #ec4899;
height: 24px;
width: 8px;
border-radius: 4px;
}
.animated-bg-gradient {

View File

@@ -15,14 +15,16 @@ onConnect(addEdges)
</script>
<template>
<div class="dndflow" @drop="onDrop">
<div class="dnd-flow" @drop="onDrop">
<VueFlow :nodes="nodes" @dragover="onDragOver" @dragleave="onDragLeave">
<DropzoneBackground
:style="{
backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
transition: 'background-color 0.2s ease',
}"
/>
>
<p v-if="isDragOver">Drop here</p>
</DropzoneBackground>
</VueFlow>
<Sidebar />

View File

@@ -3,9 +3,11 @@ import { Background } from '@vue-flow/background'
</script>
<template>
<div style="height: 100%; width: 100%">
<Background :size="2" :gap="20" pattern-color="#BDBDBD">
<div class="dropzone-background">
<Background :size="2" :gap="20" pattern-color="#BDBDBD" />
<div class="overlay">
<slot />
</Background>
</div>
</div>
</template>

View File

@@ -1,51 +1,70 @@
.dndflow {
.dnd-flow {
flex-direction: column;
display: flex;
height: 100%;
}
.dndflow aside {
.dnd-flow 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);
-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 > * {
.dnd-flow 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);
-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 {
.dnd-flow aside .description {
margin-bottom: 10px;
}
.dndflow .vue-flow-wrapper {
.dnd-flow .vue-flow-wrapper {
flex-grow: 1;
height: 100%;
}
@media screen and (min-width: 640px) {
.dndflow {
.dnd-flow {
flex-direction: row;
}
.dndflow aside {
.dnd-flow aside {
min-width: 25%;
}
}
@media screen and (max-width: 639px) {
.dndflow aside .nodes {
.dnd-flow aside .nodes {
display: flex;
flex-direction: row;
gap: 5px;
}
}
.dropzone-background {
position: relative;
height: 100%;
width: 100%
}
.dropzone-background .overlay {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
pointer-events: none;
}

View File

@@ -89,7 +89,7 @@ export default function useDragAndDrop() {
id: nodeId,
type: draggedType.value,
position,
label: `[${nodeId}]`,
data: { label: nodeId },
}
/**

View File

@@ -4,10 +4,27 @@ import { Background } from '@vue-flow/background'
import { Panel, VueFlow } from '@vue-flow/core'
const nodes = 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: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 100 },
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 400, y: 200 },
},
])
const edges = ref([
@@ -30,7 +47,19 @@ watch(isHidden, () => {
<Panel position="top-right">
<button type="button" @click="isHidden = !isHidden">
{{ isHidden ? 'Show' : 'Hide' }}
<svg v-if="isHidden" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12 9a3 3 0 0 0-3 3a3 3 0 0 0 3 3a3 3 0 0 0 3-3a3 3 0 0 0-3-3m0 8a5 5 0 0 1-5-5a5 5 0 0 1 5-5a5 5 0 0 1 5 5a5 5 0 0 1-5 5m0-12.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5"
/>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M11.83 9L15 12.16V12a3 3 0 0 0-3-3zm-4.3.8l1.55 1.55c-.05.21-.08.42-.08.65a3 3 0 0 0 3 3c.22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53a5 5 0 0 1-5-5c0-.79.2-1.53.53-2.2M2 4.27l2.28 2.28l.45.45C3.08 8.3 1.78 10 1 12c1.73 4.39 6 7.5 11 7.5c1.55 0 3.03-.3 4.38-.84l.43.42L19.73 22L21 20.73L3.27 3M12 7a5 5 0 0 1 5 5c0 .64-.13 1.26-.36 1.82l2.93 2.93c1.5-1.25 2.7-2.89 3.43-4.75c-1.73-4.39-6-7.5-11-7.5c-1.4 0-2.74.25-4 .7l2.17 2.15C10.74 7.13 11.35 7 12 7"
/>
</svg>
</button>
</Panel>
</VueFlow>

View File

@@ -4,20 +4,38 @@ import { VueFlow } from '@vue-flow/core'
import InteractionControls from './InteractionControls.vue'
const nodes = 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: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 100 },
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 400, y: 200 },
},
])
const edges = ref([
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4' },
])
</script>
<template>
<VueFlow :nodes="nodes" :edges="edges" class="interactionflow" fit-view-on-init>
<VueFlow :nodes="nodes" :edges="edges" class="interaction-flow" fit-view-on-init>
<InteractionControls />
</VueFlow>
</template>

View File

@@ -6,21 +6,41 @@ import { VueFlow, useVueFlow } from '@vue-flow/core'
* 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, updateNode } = useVueFlow()
const { onNodeDrag, getIntersectingNodes, isNodeIntersecting: __, updateNode } = useVueFlow()
const nodes = ref([
{ 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 } },
{
id: '1',
data: { label: 'Node 1' },
position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 400, y: 400 },
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 0 },
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 0, y: 400 },
},
{
id: '5',
style: { borderColor: 'red' },
data: { label: 'Drag me over another node' },
position: { x: 200, y: 200 },
},
])
onNodeDrag(({ intersections }) => {
const intersectionIds = intersections.map((intersection) => intersection.id)
onNodeDrag(({ node }) => {
const intersectionIds = getIntersectingNodes(node).map((intersection) => intersection.id)
for (const node of getNodes.value) {
for (const node of nodes.value) {
const isIntersecting = intersectionIds.includes(node.id)
updateNode(node.id, { class: isIntersecting ? 'intersecting' : '' })

View File

@@ -4,10 +4,31 @@ import { Background } from '@vue-flow/background'
import { Panel, VueFlow } from '@vue-flow/core'
const nodes = 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: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
class: 'light',
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
class: 'light',
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 100 },
class: 'light',
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 400, y: 200 },
class: 'light',
},
])
const edges = ref([

View File

@@ -6,56 +6,66 @@ import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
const nodes = ref([
{ id: '1', type: 'input', label: 'node', position: { x: 250, y: 0 } },
{
id: '1',
type: 'input',
data: { label: 'node' },
position: { x: 250, y: 0 },
},
{
id: '2',
label: 'parent node',
data: { 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',
data: { label: 'child node' },
position: { x: 10, y: 50 },
parentNode: '2',
},
{
id: '4',
label: 'parent node',
data: { 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',
data: { label: 'child node' },
position: { x: 15, y: 65 },
extent: 'parent',
parentNode: '4',
},
{
id: '4b',
label: 'nested parent node',
data: { 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',
data: { label: 'nested child node' },
position: { x: 20, y: 40 },
parentNode: '4b',
},
{
id: '4b2',
label: 'nested child node',
data: { label: 'nested child node' },
position: { x: 100, y: 100 },
parentNode: '4b',
},
{ id: '4c', label: 'child node', position: { x: 200, y: 65 }, parentNode: '4' },
{
id: '4c',
data: { label: 'child node' },
position: { x: 200, y: 65 },
parentNode: '4',
},
{
id: '999',
type: 'input',
label: 'Drag me to extend area!',
data: { label: 'Drag me to extend area!' },
position: { x: 20, y: 100 },
class: 'light',
expandParent: true,

View File

@@ -7,7 +7,7 @@ const nodes = ref([
{
id: '1',
type: 'resizable',
label: 'NodeResizer',
data: { label: 'NodeResizer' },
position: { x: 0, y: 0 },
style: { background: '#fff', border: '2px solid black' },
},
@@ -17,7 +17,7 @@ const nodes = ref([
<template>
<VueFlow :nodes="nodes" fit-view-on-init>
<template #node-resizable="resizableNodeProps">
<ResizableNode :label="resizableNodeProps.label" />
<ResizableNode :data="resizableNodeProps.data" />
</template>
</VueFlow>
</template>

View File

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

View File

@@ -3,61 +3,83 @@ import { ref } from 'vue'
import { Position, VueFlow } from '@vue-flow/core'
import ToolbarNode from './ToolbarNode.vue'
const defaultNodeStyle = {
border: '1px solid #10b981',
background: '#ef467e',
color: 'white',
borderRadius: '99px',
}
const nodes = ref([
{
id: '1',
type: 'toolbar',
label: 'toolbar top',
data: { toolbarPosition: Position.Top },
type: 'menu',
data: { label: 'toolbar top', toolbarPosition: Position.Top },
position: { x: 200, y: 0 },
style: defaultNodeStyle,
},
{
id: '2',
type: 'toolbar',
label: 'toolbar right',
data: { toolbarPosition: Position.Right },
type: 'menu',
data: { label: 'toolbar right', toolbarPosition: Position.Right },
position: { x: -50, y: 100 },
style: defaultNodeStyle,
},
{
id: '3',
type: 'toolbar',
label: 'toolbar bottom',
data: { toolbarPosition: Position.Bottom },
type: 'menu',
data: { label: 'toolbar bottom', toolbarPosition: Position.Bottom },
position: { x: 0, y: 200 },
style: defaultNodeStyle,
},
{
id: '4',
type: 'toolbar',
label: 'toolbar left',
data: { toolbarPosition: Position.Left },
type: 'menu',
data: { label: 'toolbar left', toolbarPosition: Position.Left },
position: { x: 200, y: 300 },
style: defaultNodeStyle,
},
{
id: '5',
type: 'toolbar',
label: 'toolbar always open',
data: { toolbarPosition: Position.Top, toolbarVisible: true },
type: 'menu',
data: { label: 'toolbar always open', toolbarPosition: Position.Top, toolbarVisible: true },
position: { x: 0, y: -100 },
style: defaultNodeStyle,
},
])
</script>
<template>
<VueFlow :nodes="nodes" fit-view-on-init class="vue-flow-basic-example">
<template #node-toolbar="nodeProps">
<ToolbarNode :data="nodeProps.data" :label="nodeProps.label" />
<VueFlow :nodes="nodes" fit-view-on-init>
<template #node-menu="props">
<ToolbarNode :id="props.id" :data="props.data" />
</template>
</VueFlow>
</template>
<style>
.vue-flow__node-toolbar {
display: flex;
gap: 0.5rem;
align-items: center;
background-color: #2d3748;
padding: 8px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.vue-flow__node-toolbar button {
background: #4a5568;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
cursor: pointer;
}
.vue-flow__node-toolbar button.selected {
background: #2563eb;
}
.vue-flow__node-toolbar button:hover {
background: #2563eb;
}
.vue-flow__node-menu {
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.vue-flow__node-menu.selected {
box-shadow: 0 0 0 2px #2563eb;
}
</style>

View File

@@ -1,23 +1,29 @@
<script setup>
import { Handle, Position } from '@vue-flow/core'
import { Handle, Position, useVueFlow } from '@vue-flow/core'
import { NodeToolbar } from '@vue-flow/node-toolbar'
defineProps(['data', 'label'])
const props = defineProps(['id', 'data'])
const actions = ['👎', '✋', '👍']
const { updateNodeData } = useVueFlow()
</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 :is-visible="data.toolbarVisible" :position="data.toolbarPosition">
<button
v-for="action of actions"
:key="action"
type="button"
:class="{ selected: action === data.action }"
@click="updateNodeData(props.id, { action })"
>
{{ action }}
</button>
</NodeToolbar>
<div :style="{ padding: '10px 20px' }">
{{ label }}
<div>
{{ data.label }}
</div>
<Handle type="target" :position="Position.Left" />

View File

@@ -4,7 +4,7 @@ import { VueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import SaveRestoreControls from './Controls.vue'
const nodes = ref([{ id: '1', label: 'Node 1', position: { x: 100, y: 100 } }])
const nodes = ref([{ id: '1', data: { label: 'Node 1' }, position: { x: 100, y: 100 } }])
const edges = ref([])
</script>

View File

@@ -10,7 +10,7 @@ export function getElements(xElements = 10, yElements = 10) {
const node = {
id: nodeId.toString(),
style: { width: `50px`, fontSize: `11px`, zIndex: 1 },
label: `Node ${nodeId}`,
data: { label: `Node ${nodeId}` },
class: 'light',
position,
}

View File

@@ -7,24 +7,21 @@ import TeleportableNode from './TeleportableNode.vue'
const nodes = ref([
{
id: '1',
label: 'Click to teleport',
type: 'teleportable',
position: { x: 125, y: 0 },
data: {},
data: { label: 'Click to teleport' },
},
{
id: '2',
label: 'Click to teleport',
type: 'teleportable',
position: { x: 350, y: 200 },
data: {},
data: { label: 'Click to teleport' },
},
{
id: '3',
label: 'Click to teleport',
type: 'teleportable',
position: { x: 0, y: 200 },
data: {},
data: { label: 'Click to teleport' },
},
])

View File

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

View File

@@ -9,17 +9,17 @@ const nodes = ref([
{
id: '1',
type: 'input',
label: 'Node <strong>A</strong>',
data: { label: 'Node <strong>A</strong>' },
position: { x: 250, y: 0 },
},
{
id: '2',
label: 'Node <strong>B</strong>',
data: { label: 'Node <strong>B</strong>' },
position: { x: 100, y: 100 },
},
{
id: '3',
label: 'Node <strong>C</strong>',
data: { label: 'Node <strong>C</strong>' },
position: { x: 400, y: 100 },
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
},

View File

@@ -1,44 +1,42 @@
<script setup>
import { reactive, ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { ref } from 'vue'
import { Panel, VueFlow, useVueFlow } from '@vue-flow/core'
const { onPaneReady, findNode, updateNode } = useVueFlow()
const { updateNode } = useVueFlow()
const opts = reactive({
bg: '#eeeeee',
label: 'Node 1',
hidden: false,
})
const bgColor = ref('#eeeeee')
const label = ref('Node 1')
const nodes = ref([
{ id: '1', label: opts.label, style: { backgroundColor: opts.bg }, hidden: opts.hidden, position: { x: 100, y: 100 } },
{ id: '2', label: 'Node 2', position: { x: 100, y: 200 } },
{
id: '1',
data: { label: label.value },
style: { backgroundColor: bgColor.value },
position: { x: 100, y: 100 },
},
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } },
])
const edges = ref([{ id: 'e1-2', source: '1', target: '2' }])
onPaneReady(({ fitView }) => {
fitView()
})
function handleUpdate() {
updateNode(nodes.value[0].id, { label: opts.label, style: { backgroundColor: opts.bg }, hidden: opts.hidden })
updateNode('1', { data: { label: label.value }, style: { backgroundColor: bgColor.value } })
}
</script>
<template>
<VueFlow :nodes="nodes" :edges="edges">
<div class="updatenode__controls">
<label>label:</label>
<input v-model="opts.label" @input="handleUpdate" />
<label class="updatenode__bglabel">background:</label>
<input v-model="opts.bg" type="color" @input="handleUpdate" />
<div class="updatenode__checkboxwrapper">
<label>hidden:</label>
<input v-model="opts.hidden" type="checkbox" @change="handleUpdate" />
<VueFlow :nodes="nodes" :edges="edges" fit-view-on-init>
<Panel position="top-right">
<div class="field">
<label for="label">Label:</label>
<input id="label" v-model="label" @input="handleUpdate" />
</div>
</div>
<div class="field">
<label for="bgColor">Background color:</label>
<input id="bgColor" v-model="bgColor" type="color" @input="handleUpdate" />
</div>
</Panel>
</VueFlow>
</template>

View File

@@ -1,30 +1,24 @@
.updatenode__controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 4;
font-size: 11px;
background-color: lightgray;
border-radius: 10px;
.vue-flow__panel {
background-color: #2d3748;
color: white;
border-radius: 8px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.updatenode__controls label {
.vue-flow__panel .field {
display: flex;
gap: 8px;
align-items: center;
}
.vue-flow__panel label {
display: blocK;
}
.updatenode__controls input {
.vue-flow__panel 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

@@ -7,15 +7,30 @@ import CustomNode from './CustomNode.vue'
const { addEdges } = useVueFlow()
const nodes = ref([
{ id: '0', type: 'custominput', position: { x: 0, y: 150 }, isValidTargetPos: (connection) => connection.target === 'B' },
{
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' },
{
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 edges = ref([])

View File

@@ -209,7 +209,7 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
{ text: 'Math Operation Flow', link: '/examples/math' },
{ text: 'Screenshot', link: '/examples/screenshot' },
{ text: 'Confirm Delete', link: '/examples/confirm' },
{ text: 'Node Visibility', link: '/examples/hidden' },
{ text: 'Hidden', link: '/examples/hidden' },
{ text: 'Node Intersections', link: '/examples/intersection' },
{ text: 'Multiple Flows', link: '/examples/multi' },
{ text: 'Pinia Store', link: '/examples/pinia' },