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:
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
])
|
||||
|
||||
@@ -7,7 +7,7 @@ const nodes = ref([
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'Node 1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
])
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function useDragAndDrop() {
|
||||
id: nodeId,
|
||||
type: draggedType.value,
|
||||
position,
|
||||
label: `[${nodeId}]`,
|
||||
data: { label: nodeId },
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' : '' })
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
])
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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([])
|
||||
|
||||
Reference in New Issue
Block a user