docs(guide): update node page
This commit is contained in:
+534
-247
@@ -1,9 +1,19 @@
|
||||
<script setup>
|
||||
import { VueFlow } from '@vue-flow/core';
|
||||
import LogosJavascript from '~icons/logos/javascript';
|
||||
import LogosTypescript from '~icons/logos/typescript-icon';
|
||||
import { VueFlow, Panel } from '@vue-flow/core';
|
||||
import { Background } from '@vue-flow/background';
|
||||
import Check from '~icons/mdi/check';
|
||||
import Close from '~icons/mdi/close';
|
||||
import { ref } from 'vue';
|
||||
import { ref, h } from 'vue';
|
||||
|
||||
const ScrollableNode = () => h('div', { class: 'custom-node-container' }, [
|
||||
h('ul', { class: 'nowheel' }, Array.from({ length: 100 }, (_, i) => h('li', { key: i }, `Item ${i}`)))
|
||||
]);
|
||||
|
||||
const InputFieldNode = () => h('div', { class: 'custom-node-container' }, [
|
||||
h('input', { class: 'nodrag', placeholder: 'Type something...' })
|
||||
]);
|
||||
|
||||
const defaultNode = ref([
|
||||
{
|
||||
@@ -32,66 +42,96 @@ const outputNode = ref([
|
||||
]);
|
||||
</script>
|
||||
|
||||
# Nodes
|
||||
# Introduction to Nodes
|
||||
|
||||
Nodes are the building blocks of your graph. They represent any sort of data you want to present in your graph.
|
||||
Nodes are the underlying components of your graph.
|
||||
They can be any kind of data you want to visualize in your graph, existing independently and being interconnected
|
||||
through edges to create a data map.
|
||||
|
||||
They can exist on their own but can be connected to each other with edges to create a map.
|
||||
Remember, every node is unique and thus **requires a unique id** and **an [XY-position](/typedocs/interfaces/XYPosition)**.
|
||||
|
||||
Each node <span class="font-bold text-blue-500">requires a unique id and
|
||||
an [xy-position](/typedocs/interfaces/XYPosition).</span>
|
||||
For the full list of options available for a node, check out the [Node Interface](/typedocs/interfaces/Node).
|
||||
|
||||
You can view the full options-list for a node [here](/typedocs/interfaces/Node).
|
||||
## Adding Nodes to the Graph
|
||||
|
||||
## Usage
|
||||
Nodes are generally created by adding them to the `mode-value` (using `v-model`) or to the `nodes` prop of the Vue Flow component.
|
||||
This can be done dynamically at any point in your component's lifecycle.
|
||||
|
||||
Generally you create nodes by adding them to the model-value or the nodes prop of the Vue Flow component.
|
||||
:::code-group
|
||||
|
||||
```vue
|
||||
<script>
|
||||
```vue [<LogosJavascript />]
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
|
||||
export default defineComponent({
|
||||
components: { VueFlow },
|
||||
data() {
|
||||
return {
|
||||
elements: [
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 50, y: 50 },
|
||||
label: 'Node 1',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Add an element after mount
|
||||
this.elements.push(
|
||||
{
|
||||
id: '2',
|
||||
position: { x: 150, y: 50 },
|
||||
label: 'Node 2',
|
||||
}
|
||||
)
|
||||
const elements = ref([
|
||||
{
|
||||
id: '1',
|
||||
position: {x: 50, y: 50},
|
||||
label: 'Node 1',
|
||||
}
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
elements.value.push({
|
||||
id: '2',
|
||||
label: 'Node 2',
|
||||
position: {x: 150, y: 50},
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height: 300px">
|
||||
<VueFlow v-model="elements" />
|
||||
</div>
|
||||
<VueFlow v-model="elements"/>
|
||||
</template>
|
||||
```
|
||||
|
||||
For more advanced graphs that require more state access you will want to use the useVueFlow composable.
|
||||
[useVueFlow](/typedocs/functions/useVueFlow) will provide the [`addNodes`](/typedocs/interfaces/Actions#addnodes) action,
|
||||
which you can use to add nodes directly to the state.
|
||||
```vue [<LogosTypescript />]
|
||||
|
||||
This action can be even be used outside the component that is rendering the graph, like a Sidebar or a Toolbar.
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { Elements } from '@vue-flow/core'
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
|
||||
const elements = ref<Elements>([
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 50, y: 50 },
|
||||
label: 'Node 1',
|
||||
}
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
elements.value.push({
|
||||
id: '2',
|
||||
label: 'Node 2',
|
||||
position: { x: 150, y: 50 },
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements"/>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
If you are working with more complex graphs that necessitate extensive state access, the `useVueFlow` composable should
|
||||
be employed.
|
||||
The [`addNodes`](/typedocs/interfaces/Actions#addnodes) action is available
|
||||
through [useVueFlow](/typedocs/functions/useVueFlow), allowing you to add nodes straight to the state.
|
||||
|
||||
What's more, this action isn't limited to the component rendering the graph; it can be utilized elsewhere, like in a
|
||||
Sidebar or Toolbar.
|
||||
|
||||
::: code-group
|
||||
|
||||
```vue [<LogosJavascript />]
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { VueFlow, useVueFlow } from '@vue-flow/core'
|
||||
|
||||
const initialNodes = ref([
|
||||
@@ -101,117 +141,295 @@ const initialNodes = ref([
|
||||
label: 'Node 1',
|
||||
}
|
||||
])
|
||||
const { addNodes } = useVueFlow({
|
||||
nodes: initialNodes,
|
||||
})
|
||||
const { addNodes } = useVueFlow()
|
||||
|
||||
onMounted(() => {
|
||||
// Add an element after mount
|
||||
addNodes([
|
||||
{
|
||||
id: '2',
|
||||
position: { x: 150, y: 50 },
|
||||
label: 'Node 2',
|
||||
}
|
||||
])
|
||||
})
|
||||
function generateRandomNode() {
|
||||
return {
|
||||
id: Math.random().toString(),
|
||||
position: { x: Math.random() * 500, y: Math.random() * 500 },
|
||||
label: 'Random Node',
|
||||
}
|
||||
}
|
||||
|
||||
function onAddNode() {
|
||||
// add a single node to the graph
|
||||
addNodes(generateRandomNode())
|
||||
}
|
||||
|
||||
function onAddNodes() {
|
||||
// add multiple nodes to the graph
|
||||
addNodes(Array.from({ length: 10 }, generateRandomNode))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height: 300px">
|
||||
<VueFlow />
|
||||
</div>
|
||||
<VueFlow :nodes="initialNodes" />
|
||||
|
||||
<button type="button" @click="onAddNode">Add a node</button>
|
||||
<button type="button" @click="onAddNodes">Add multiple nodes</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
## [Default Node-Types](/typedocs/types/DefaultNodeTypes)
|
||||
```vue [<LogosTypescript />]
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { Node } from '@vue-flow/core'
|
||||
import { VueFlow, useVueFlow } from '@vue-flow/core'
|
||||
|
||||
Vue Flow comes with built-in nodes that you can use right out of the box.
|
||||
These node types include `default`, `input` and `output`.
|
||||
const initialNodes = ref<Node[]>([
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 50, y: 50 },
|
||||
label: 'Node 1',
|
||||
data: {},
|
||||
}
|
||||
])
|
||||
const { addNodes } = useVueFlow()
|
||||
|
||||
function generateRandomNode() {
|
||||
return {
|
||||
id: Math.random().toString(),
|
||||
position: { x: Math.random() * 500, y: Math.random() * 500 },
|
||||
label: 'Random Node',
|
||||
data: {
|
||||
hello: 'world',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onAddNode() {
|
||||
// add a single node to the graph
|
||||
addNodes(generateRandomNode())
|
||||
}
|
||||
|
||||
function onAddNodes() {
|
||||
// add multiple nodes to the graph
|
||||
addNodes(Array.from({ length: 10 }, generateRandomNode))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow :nodes="initialNodes" />
|
||||
|
||||
<button type="button" @click="onAddNode()">Add a node</button>
|
||||
<button type="button" @click="onAddNodes()">Add multiple nodes</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## [Predefined Node-Types](/typedocs/types/DefaultNodeTypes)
|
||||
|
||||
Vue Flow provides several built-in node types that you can leverage immediately.
|
||||
The included node types are `default`, `input`, and `output`.
|
||||
|
||||
### Default Node
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded h-50">
|
||||
<VueFlow v-model="defaultNode">
|
||||
<Background />
|
||||
</VueFlow>
|
||||
</div>
|
||||
A default node includes two handles and serves as a branching junction in your map.
|
||||
|
||||
A default node comes with two handles.
|
||||
It represents a branching point in your map.
|
||||
You have the freedom to determine the location of handles in the node's definition.
|
||||
|
||||
You can specify the position of handles in the node definition.
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
import { Position } from '@vue-flow/core'
|
||||
|
||||
```js{5-6}
|
||||
const nodes = [
|
||||
const nodes = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
label: 'Default Node',
|
||||
type: 'default', // You can omit this as it's the fallback type
|
||||
targetHandle: Position.Top, // or Bottom, Left, Right,
|
||||
sourceHandle: Position.Right,
|
||||
sourceHandle: Position.Bottom, // or Top, Left, Right,
|
||||
}
|
||||
]
|
||||
])
|
||||
```
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded-lg h-50">
|
||||
<VueFlow v-model="defaultNode">
|
||||
<Background class="rounded-lg" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
### Input Node
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded h-50">
|
||||
An input node features a single handle, which is by default positioned at the bottom.
|
||||
It represents a starting point of your map.
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
import { Position } from '@vue-flow/core'
|
||||
|
||||
const nodes = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Input Node',
|
||||
type: 'input',
|
||||
sourceHandle: Position.Bottom, // or Top, Left, Right,
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded-lg h-50">
|
||||
<VueFlow v-model="inputNode">
|
||||
<Background />
|
||||
<Background class="rounded-lg" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
|
||||
An input node has a single handle, located at the bottom by default.
|
||||
It represents a starting point of your map.
|
||||
|
||||
### Output Node
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded h-50">
|
||||
An output node also possesses a single handle, although it is typically found at the top.
|
||||
This node represents a conclusion point of your map.
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
import { Position } from '@vue-flow/core'
|
||||
|
||||
const nodes = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Output Node',
|
||||
type: 'output',
|
||||
targetHandle: Position.Top, // or Bottom, Left, Right,
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded-lg h-50">
|
||||
<VueFlow v-model="outputNode">
|
||||
<Background />
|
||||
<Background class="rounded-lg" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
An output node has a single handle, located at the top by default.
|
||||
It represents an ending point of your map.
|
||||
|
||||
## Custom Nodes
|
||||
## User-Defined Nodes
|
||||
|
||||
In addition to the default node types from the previous chapter, you can define any amount of custom node-types.
|
||||
Node-types are inferred from your node's definition.
|
||||
On top of the default node types mentioned earlier, you can create as many custom node-types as you need.
|
||||
Node-types are determined from your nodes' definitions.
|
||||
|
||||
```js{5,11}
|
||||
const nodes = [
|
||||
::: code-group
|
||||
|
||||
```js [nodes <LogosJavascript />]
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const nodes = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
// this will create the node-type `custom`
|
||||
type: 'custom',
|
||||
position: { x: 50, y: 50 },
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
// this will create the node-type `special`
|
||||
type: 'special',
|
||||
position: { x: 150, y: 50 },
|
||||
}
|
||||
]
|
||||
])
|
||||
```
|
||||
|
||||
Vue Flow will now try to resolve this node-type to a component.
|
||||
First and foremost we will look for a definition in the `nodeTypes` object of the state.
|
||||
After that we will try to resolve the component to a globally registered one that matches the exact name.
|
||||
Finally, we will check if a template slot has been provided to fill the node-type.
|
||||
```vue [CustomNode.vue <LogosJavascript />]
|
||||
<script setup>
|
||||
import { Position } from '@vue-flow/core'
|
||||
|
||||
If none of these methods succeed in resolving the component the default node-type will be used as a fallback.
|
||||
const props = defineProps(['label'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<div>{{ label }}</div>
|
||||
<Handle type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```ts [nodes <LogosTypescript />]
|
||||
import { ref } from 'vue'
|
||||
import type { Node } from '@vue-flow/core'
|
||||
|
||||
// You can pass 3 optional generic arguments to the Node interface, allowing you to define:
|
||||
// 1. The data object type
|
||||
// 2. The events object type
|
||||
// 3. The possible node types
|
||||
|
||||
export interface CustomData {
|
||||
hello: string
|
||||
}
|
||||
|
||||
export interface CustomEvents {
|
||||
onCustomEvent: (event: MouseEvent) => void
|
||||
}
|
||||
|
||||
type CustomNodeTypes = 'custom' | 'special'
|
||||
|
||||
type CustomNode = Node<CustomData, CustomEvents, CustomNodeTypes>
|
||||
|
||||
export const nodes = ref<CustomNode[]>([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
// this will create the node-type `custom`
|
||||
type: 'custom',
|
||||
position: { x: 50, y: 50 },
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
// this will create the node-type `special`
|
||||
type: 'special',
|
||||
position: { x: 150, y: 50 },
|
||||
},
|
||||
|
||||
// this will throw a type error, as the type is not defined in the CustomEdgeTypes
|
||||
// regardless it would be rendered as a default edge type
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
type: 'invalid',
|
||||
position: { x: 150, y: 50 },
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
```vue [CustomNode.vue <LogosTypescript />]
|
||||
<script setup lang="ts">
|
||||
import type { NodeProps } from '@vue-flow/core'
|
||||
import { Position } from '@vue-flow/core'
|
||||
|
||||
import { CustomData, CustomEvents } from './nodes'
|
||||
|
||||
const props = defineProps<NodeProps<CustomData, CustomEvents>>()
|
||||
|
||||
console.log(props.data.hello) // 'world'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<div>{{ label }}</div>
|
||||
<Handle type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Vue Flow will then attempt to resolve this node-type to a component.
|
||||
Priority is given to a definition in the nodeTypes object of the state.
|
||||
Next, it tries to match the component to a globally registered one with the same name.
|
||||
Finally, it searches for a provided template slot to fill in the node-type.
|
||||
|
||||
If no methods produce a result in resolving the component, the default node-type is used as a fallback.
|
||||
|
||||
### Template slots
|
||||
|
||||
The easiest way to define custom nodes is, by passing them as template slots.
|
||||
Your custom node-types are dynamically resolved to slot-names, meaning a node with the type `custom`
|
||||
will expect a slot to have the name `node-custom`.
|
||||
One of the easiest ways to define custom nodes is, by passing them as template slots.
|
||||
Dynamic resolution to slot-names is done for your user-defined node-types,
|
||||
meaning a node with the type `custom` is expected to have a slot named `#node-custom`.
|
||||
|
||||
```vue{9,16}
|
||||
```vue{9,18}
|
||||
<script setup>
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
import CustomNode from './CustomNode.vue'
|
||||
@@ -225,8 +443,10 @@ const elements = ref([
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements">
|
||||
<!-- the expected slot name is `node-custom` -->
|
||||
<template #node-custom="props">
|
||||
<CustomNode v-bind="props" />
|
||||
</template>
|
||||
@@ -236,13 +456,10 @@ const elements = ref([
|
||||
|
||||
### Node-types object
|
||||
|
||||
You can also define node-types by passing an object as a prop to the VueFlow component (or as an option to the
|
||||
composable).
|
||||
Alternatively, node-types can also be defined by passing an object as a prop to the VueFlow component (or as an option to the composable).
|
||||
|
||||
::: warning
|
||||
When doing this, mark your components as raw (using the designated function from the vue library) to avoid them being
|
||||
turned into reactive objects.
|
||||
Otherwise, vue will throw a warning in the console.
|
||||
::: warning
|
||||
Take precaution to mark your components as raw (utilizing the marked function from the Vue library) to prevent their conversion into reactive objects. Otherwise, Vue will display a warning on the console.
|
||||
:::
|
||||
|
||||
```vue{6-9,26}
|
||||
@@ -269,60 +486,20 @@ const elements = ref([
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height: 300px">
|
||||
<VueFlow v-model="elements" :node-types="nodeTypes" />
|
||||
</div>
|
||||
<VueFlow v-model="elements" :node-types="nodeTypes" />
|
||||
</template>
|
||||
```
|
||||
|
||||
::: tip
|
||||
You can find a more advanced example [here](/examples/nodes/).
|
||||
[You can find a working example here](/examples/nodes/).
|
||||
:::
|
||||
|
||||
### Node Template
|
||||
## [Node Props](/typedocs/interfaces/NodeProps)
|
||||
|
||||
You can also set a template per node, which will overwrite the node-type component but will retain
|
||||
the type otherwise.
|
||||
|
||||
```vue{17}
|
||||
<script setup>
|
||||
import { markRaw } from 'vue'
|
||||
import CustomNode from './CustomNode.vue'
|
||||
import OverwriteCustomNode from './OverwriteCustomNode.vue'
|
||||
import SpecialNode from './SpecialNode.vue'
|
||||
|
||||
const nodeTypes = {
|
||||
custom: markRaw(CustomNode),
|
||||
special: markRaw(SpecialNode),
|
||||
}
|
||||
|
||||
const elements = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
type: 'custom',
|
||||
template: markRaw(OverwriteCustomNode),
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
type: 'special',
|
||||
}
|
||||
])
|
||||
</script>
|
||||
<template>
|
||||
<div style="height: 300px">
|
||||
<VueFlow v-model="elements" :node-types="nodeTypes" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### [(Custom) Node Props](/typedocs/interfaces/NodeProps)
|
||||
|
||||
Your custom nodes are wrapped so that the basic functions like dragging or selecting work.
|
||||
But you might want to extend on that functionality or implement your own business logic inside of nodes, therefore
|
||||
your nodes receive the following props:
|
||||
Your custom nodes are enclosed so that fundamental functions like dragging or selecting operate.
|
||||
But you may wish to expand on these features or implement your business logic inside nodes, thus your nodes receive the following properties:
|
||||
|
||||
| Name | Definition | Type | Optional |
|
||||
|------------------|--------------------------------------------------|------------------------------------------------------------|--------------------------------------------|
|
||||
@@ -344,129 +521,240 @@ your nodes receive the following props:
|
||||
| sourcePosition | Source handle position | [Position](/typedocs/enums/Position) | <Check class="text-[var(--vp-c-brand)]" /> |
|
||||
| dragHandle | Node drag handle class | string | <Check class="text-[var(--vp-c-brand)]" /> |
|
||||
|
||||
### (Custom) Node Events
|
||||
## [Node Events](/typedocs/types/NodeEventsHandler)
|
||||
|
||||
In addition to the event handlers that you can access through [`useVueFlow`](/guide/composables#useVueFlow/) or the Vue
|
||||
Flow component,
|
||||
you can also pass in event handlers in your initial node definition, or you can access the node events through
|
||||
the `events` prop passed
|
||||
to your node components.
|
||||
Vue Flow provides two main ways of listening to node events,
|
||||
either by using `useVueFlow` to bind listeners to the event handlers
|
||||
or by using binding listeners to the `<VueFlow>` component.
|
||||
|
||||
```vue{10-17}
|
||||
::: code-group
|
||||
|
||||
```vue [useVueFlow]
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { VueFlow, useVueFlow } from '@vue-flow/core'
|
||||
|
||||
// useVueFlow provides access to the event handlers
|
||||
const {
|
||||
onNodeDragStart,
|
||||
onNodeDrag,
|
||||
onNodeDragStop,
|
||||
onNodeClick,
|
||||
onNodeDoubleClick,
|
||||
onNodeContextMenu,
|
||||
onNodeMouseEnter,
|
||||
onNodeMouseLeave,
|
||||
onNodeMouseMove
|
||||
} = useVueFlow()
|
||||
|
||||
const elements = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
position: { x: 50, y: 50 },
|
||||
},
|
||||
])
|
||||
|
||||
// bind listeners to the event handlers
|
||||
onNodeDragStart((event) => {
|
||||
console.log('Node drag started', event)
|
||||
})
|
||||
|
||||
onNodeDrag((event) => {
|
||||
console.log('Node dragged', event)
|
||||
})
|
||||
|
||||
onNodeDragStop((event) => {
|
||||
console.log('Node drag stopped', event)
|
||||
})
|
||||
|
||||
// ... and so on
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements" />
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue [component]
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
|
||||
const elements = ref([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
type: 'custom',
|
||||
position: { x: 50, y: 50 },
|
||||
events: {
|
||||
click: () => {
|
||||
console.log('Node 1 clicked')
|
||||
},
|
||||
customEvent: () => {
|
||||
console.log('Node 1 custom event')
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
])
|
||||
</script>
|
||||
```
|
||||
|
||||
As you can see above, you can also pass in custom event handlers. These will not be called by Vue Flow but can be used
|
||||
to forward callback functions to your custom components.
|
||||
The `click` handler is part of the [`NodeEventsHandler`](/typedocs/types/NodeEventsHandler) interface, meaning it
|
||||
will be
|
||||
triggered when the node is clicked.
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import type { NodeProps, NodeEventsOn } from '@vue-flow/core'
|
||||
|
||||
// define your events
|
||||
interface CustomNodeEvents {
|
||||
click: NodeEventsOn['click']
|
||||
customEvent: (input: string) => void
|
||||
}
|
||||
|
||||
interface CustomNodeProps extends NodeProps<any, CustomNodeEvents> {
|
||||
id: string
|
||||
events: CustomNodeEvents
|
||||
}
|
||||
|
||||
const props = defineProps<CustomNodeProps>()
|
||||
|
||||
props.events.click(() => {
|
||||
console.log(`Node ${props.id} clicked`)
|
||||
})
|
||||
|
||||
// custom events are just functions, they are not hooks which you can listen to like `click`
|
||||
props.events.customEvent('custom event triggered')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Omitted for simplicty -->
|
||||
<!-- bind listeners to the event handlers -->
|
||||
<VueFlow
|
||||
v-model="elements"
|
||||
@node-drag-start="console.log('drag start', $event)"
|
||||
@node-drag="console.log('drag', $event)"
|
||||
@node-drag-stop="console.log('drag stop', $event)"
|
||||
@node-click="console.log('click', $event)"
|
||||
@node-double-click="console.log('dblclick', $event)"
|
||||
@node-contextmenu="console.log('contextmenu', $event)"
|
||||
@node-mouse-enter="console.log('mouseenter', $event)"
|
||||
@node-mouse-leave="console.log('mouseleave', $event)"
|
||||
@node-mouse-move="console.log('mousemove', $event)"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
::: tip
|
||||
To overwrite default theme styles check the [Theming section](/guide/theming).
|
||||
:::
|
||||
|
||||
### Custom Nodes
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded-lg h-50">
|
||||
<VueFlow
|
||||
v-model="defaultNode"
|
||||
@node-drag-start="console.log('drag start', $event)"
|
||||
@node-drag="console.log('drag', $event)"
|
||||
@node-drag-stop="console.log('drag stop', $event)"
|
||||
@node-click="console.log('click', $event)"
|
||||
@node-double-click="console.log('dblclick', $event)"
|
||||
@node-contextmenu="console.log('contextmenu', $event)"
|
||||
@node-mouse-enter="console.log('mouseenter', $event)"
|
||||
@node-mouse-leave="console.log('mouseleave', $event)"
|
||||
@node-mouse-move="console.log('mousemove', $event)"
|
||||
>
|
||||
<Panel position="top-center">
|
||||
<p class="text-sm">Interact to see events in browser console</p>
|
||||
</Panel>
|
||||
<Background class="rounded-lg" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
When you create a new node type you also need to implement some styling. Your custom node has no default styles.
|
||||
|
||||
## Customizing Appearance
|
||||
|
||||
::: tip
|
||||
To override the styles of the default theme, visit the [Theming section](/guide/theming).
|
||||
:::
|
||||
|
||||
### User-Defined Nodes
|
||||
|
||||
When constructing a new node type, it's necessary for you to add some styling specific to it.
|
||||
User-created nodes don't have any default styles associated and thus need custom styling.
|
||||
|
||||
```css
|
||||
.vue-flow__node-custom {
|
||||
background: #9CA8B3;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
background: #9CA8B3;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
## Scrolling inside a node
|
||||
## Implementing Scrolling within Nodes
|
||||
|
||||
Sometimes, a node might contain a large amount of content, making it difficult for users to view everything without the aid of a scroll function.
|
||||
To facilitate this scrolling ability without invoking zoom or pan behaviors on the node, Vue Flow provides the `noWheelClassName` property.
|
||||
|
||||
The `noWheelClassName` property allows you to specify a class name that, when applied to a node, will disable the default zoom-on-scroll or pan-on-scroll events on that particular node.
|
||||
|
||||
You can use the `noWheelClassName` prop to define a class name which will prevent zoom-on-scroll or pan-on-scroll behavior on
|
||||
that node.
|
||||
By default, the `noWheelClassName` is `nowheel`.
|
||||
By adding this class you can enable scrolling inside a node without triggering zoom-pan behavior.
|
||||
|
||||
## Dragging inside a node
|
||||
|
||||
You can use the `noDragClassName` prop to define a class name which will not trigger dragging behavior on
|
||||
that node.
|
||||
By default, the `noDragClassName` is `nodrag`.
|
||||
|
||||
## Dynamic handle positions / Adding handles dynamically
|
||||
|
||||
|
||||
::: info
|
||||
When using Vue Flow 1.x you don't need to call `updateNodeInternals` when adding handles dynamically.
|
||||
Handles will try to be added to the node automatically when they are mounted.
|
||||
If this does not work for you, for whatever reason, you can still follow the guide below and force Vue Flow to update
|
||||
the node internals.
|
||||
:::
|
||||
|
||||
When working with dynamic handle positions or adding handles dynamically, you need to use
|
||||
the [`updateNodeInternals`](/typedocs/types/UpdateNodeInternals) method.
|
||||
|
||||
You need to call this method otherwise your node will not respond to the new handles and edges will be
|
||||
misaligned.
|
||||
|
||||
You can either use the store action to update multiple nodes at once by passing their ids into the method,
|
||||
or you can emit the `updateNodeInternals` event from your custom node component without passing any parameters.
|
||||
|
||||
### Examples
|
||||
|
||||
- Using store action
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const listItems = ref(Array.from({ length: 100 }, (_, i) => i))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-node-container">
|
||||
<ul class="nowheel">
|
||||
<li v-for="item in listItems" :key="item">Item {{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded-lg h-50">
|
||||
<VueFlow :model-value="[{ id: '1', type: 'scrollable', label: 'Node 1', position: { x: 50, y: 50 } }]">
|
||||
<template #node-scrollable>
|
||||
<ScrollableNode />
|
||||
</template>
|
||||
<Background class="rounded-lg" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.vue-flow__node-scrollable {
|
||||
@apply bg-accent rounded px-2;
|
||||
overflow: auto;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.custom-node-container ul {
|
||||
max-height: 75px;
|
||||
}
|
||||
</style>
|
||||
|
||||
## Preventing Drag Behavior withing Nodes
|
||||
|
||||
There are certain scenarios where you might need to interact with the contents of a node without triggering a drag action on the node itself.
|
||||
This can be particularly useful when nodes contain interactive elements like input boxes, buttons, or sliders that you want your users to engage with.
|
||||
|
||||
To accomplish this, Vue Flow provides a `noDragClassName` property.
|
||||
This property allows specification of a class name, which when applied to an element within a node,
|
||||
prevents triggering a drag action on the node when the user interacts with that element.
|
||||
|
||||
By default, the `noDragClassName` is set as `nodrag`.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const inputValue = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-node-container">
|
||||
<input class="nodrag" v-model="inputValue" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
<div class="mt-4 bg-[var(--vp-code-block-bg)] rounded-lg h-50">
|
||||
<VueFlow :model-value="[{ id: '1', type: 'input-field', label: 'Node 1', position: { x: 50, y: 50 } }]">
|
||||
<template #node-input-field>
|
||||
<InputFieldNode />
|
||||
</template>
|
||||
<Background class="rounded-lg" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.vue-flow__node-input-field {
|
||||
@apply bg-primary rounded p-4;
|
||||
}
|
||||
</style>
|
||||
|
||||
## Working with Dynamic Handle Positions / Adding Handles Dynamically
|
||||
|
||||
::: tip
|
||||
In Vue Flow 1.x, there's no need to manually invoke `updateNodeInternals` when dynamically adding handles.
|
||||
Upon mounting, handles will automatically attempt to attach to the node.
|
||||
However, if for any reason this isn't happening as expected, you can stick to the guideline provided below to enforce Vue Flow to update the node internals.
|
||||
:::
|
||||
|
||||
At times, you may need to modify handle positions dynamically or programmatically add new handles to a node. In this scenario, the [`updateNodeInternals`](/typedocs/types/UpdateNodeInternals) method found in Vue Flow's API comes in handy.
|
||||
|
||||
Invoking this method is vital when dealing with dynamic handles. If not, the node might fail to recognize these new handles, resulting in misaligned edges.
|
||||
|
||||
The `updateNodeInternals` function can be deployed in one of two ways:
|
||||
|
||||
- **Using the store action:** This approach allows you to update several nodes at once by passing their IDs into the method.
|
||||
- **Emitting the `updateNodeInternals` event from your customized node component:** This doesn't require any parameters to be passed.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [store action]
|
||||
import { useVueFlow } from '@vue-flow/core'
|
||||
|
||||
const { updateNodeInternals } = useVueFlow()
|
||||
@@ -474,12 +762,9 @@ const { updateNodeInternals } = useVueFlow()
|
||||
const onSomeEvent = () => {
|
||||
updateNodeInternals(['1'])
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
- Emitting event from custom component
|
||||
|
||||
```vue
|
||||
```vue [emit event]
|
||||
<script setup>
|
||||
const emits = defineEmits(['updateNodeInternals'])
|
||||
|
||||
@@ -488,3 +773,5 @@ const onSomeEvent = () => {
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Reference in New Issue
Block a user