docs: replace codesandbox embed with repl (#122)
* docs: Animations * docs: add confetti gun * docs: update basic example on home page * docs(deps): Add stackblitz sdk to deps * chore: update yarn.lock * docs: use vue repl * docs: remove stackblitz sdk * docs: basic repl example * docs: use repl for examples/index.md (basic example) * docs: pass ext to repl * docs: add copy plugin * docs: use import map instead of dynamic imports * docs: use repl for custom node example * docs: hide repl errors * docs: use repl for custom connectionline example * docs: use repl for edges example * docs: exclude repl from ssr * docs: use repl for nested example * docs: use repl for stress example * docs: rename customNode to custom-node * docs: use repl for update-edge example * docs: use repl for update-node example * docs: use repl for validation example * docs: use repl for save-restore example * docs: scale down minimap in repl examples * docs: use repl for dnd example * docs: use repl for empty example * docs: use repl for hidden example * docs: use repl for interaction example * docs: use repl for multi example * docs: add pinia example with stackblitz * docs: update basic example * docs: update examples * docs: remove transition from intro * docs: update features * update: README.md * docs: scope css
This commit is contained in:
17
README.md
17
README.md
@@ -22,16 +22,16 @@ the [examples](https://vueflow.dev/examples).
|
||||
|
||||
* [🎮 Quickstart](#-quickstart)
|
||||
|
||||
+ [▸ Vue 2](#-vue-2)
|
||||
+ [🪴 Vue 2](#-vue-2)
|
||||
|
||||
* [🧪 Development](#-development)
|
||||
|
||||
+ [🐳 Dev Container](#-dev-container)
|
||||
|
||||
* [⭐ Stargazers](#-stargazers)
|
||||
|
||||
* [💝 Special Thanks](#-special-thanks)
|
||||
|
||||
* [⭐ Stargazers](#-stargazers)
|
||||
|
||||
## ⭐️ Features
|
||||
|
||||
- 👶 __Easy setup__: Get started hassle-free - Built-in zoom- & pan features, element dragging, selection and much more
|
||||
@@ -50,6 +50,8 @@ the [examples](https://vueflow.dev/examples).
|
||||
|
||||
- 🕹 Controls: Control zoom behavior from a panel on the bottom left
|
||||
|
||||
- 🤖 And (many) more to come...
|
||||
|
||||
- 🦾 __Reliable__: Fully written in TypeScript
|
||||
|
||||
## 🛠 Setup
|
||||
@@ -147,15 +149,14 @@ This project is built with
|
||||
|
||||
- [React Flow](https://reactflow.dev/)
|
||||
- Vue flow is heavily based on [webkids'](https://webkid.io/) [react flow](https://reactflow.dev/). I wholeheartedly thank
|
||||
them for their amazing work! Without them, Vue Flow would not exist.
|
||||
Please consider [donating](https://github.com/sponsors/wbkd) to them!
|
||||
them for their amazing work! Without them Vue Flow would not exist.
|
||||
Please consider [donating](https://github.com/sponsors/wbkd) to them.
|
||||
|
||||
- [D3.js](https://d3js.org/)
|
||||
- D3 makes all the zoom and pan actions in the graph possible.
|
||||
Check it out to see if you can find other uses for it too!
|
||||
- D3 makes all the zoom and pan actions in Vue Flow possible.
|
||||
|
||||
- [VueUse](https://vueuse.org/)
|
||||
- VueUse is a collection of helpful utilities/composables that have been battle-tested.
|
||||
- VueUse is a collection of essential vue composition utilities
|
||||
|
||||
## ⭐ Stargazers
|
||||
|
||||
|
||||
107
docs/components/Repl.vue
Normal file
107
docs/components/Repl.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script lang="ts" setup>
|
||||
import { Repl, ReplStore } from '@vue/repl'
|
||||
import pkg from '../package.json'
|
||||
import '@vue/repl/style.css'
|
||||
import { exampleImports } from './examples'
|
||||
|
||||
const props = defineProps<{ example: keyof typeof exampleImports; mainFile?: string; dependencies?: Record<string, string> }>()
|
||||
|
||||
let css = `@import 'https://cdn.jsdelivr.net/npm/@braks/vue-flow@${pkg.dependencies['@braks/vue-flow']}/dist/style.css';
|
||||
@import 'https://cdn.jsdelivr.net/npm/@braks/vue-flow@${pkg.dependencies['@braks/vue-flow']}/dist/theme-default.css';
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
text-transform: uppercase;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.vue-flow__minimap {
|
||||
transform: scale(50%)
|
||||
transform-origin: bottom right;
|
||||
}`
|
||||
|
||||
const store = new ReplStore({
|
||||
showOutput: true,
|
||||
outputMode: 'preview',
|
||||
})
|
||||
|
||||
watchEffect(
|
||||
() => {
|
||||
document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`)
|
||||
},
|
||||
{ flush: 'post' },
|
||||
)
|
||||
|
||||
const files: any = {}
|
||||
const imports = exampleImports[props.example]
|
||||
|
||||
for (const example of Object.keys(imports)) {
|
||||
if (example.includes('css')) {
|
||||
css += `${imports[example as keyof typeof imports]}`
|
||||
} else {
|
||||
files[example] = imports[example as keyof typeof imports]
|
||||
}
|
||||
}
|
||||
|
||||
await store.setFiles(
|
||||
{
|
||||
...files,
|
||||
'main.css': css,
|
||||
},
|
||||
props.mainFile ?? 'App.vue',
|
||||
)
|
||||
|
||||
// pre-set import map
|
||||
store.setImportMap({
|
||||
imports: {
|
||||
'@braks/vue-flow': `${location.origin}/vue-flow.es.js`,
|
||||
},
|
||||
})
|
||||
|
||||
store.setVueVersion('3.2.25')
|
||||
|
||||
const sfcOptions = {
|
||||
script: {
|
||||
reactivityTransform: true,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Repl
|
||||
:clear-console="true"
|
||||
:auto-resize="true"
|
||||
:store="store"
|
||||
:show-compile-output="false"
|
||||
:show-import-map="false"
|
||||
:sfc-options="sfcOptions"
|
||||
@keydown.ctrl.s.prevent
|
||||
@keydown.meta.s.prevent
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.file-selector {
|
||||
@apply scrollbar scrollbar-thin scrollbar-thumb-rounded scrollbar-thumb-green-500 scrollbar-track-black;
|
||||
}
|
||||
|
||||
.vue-repl {
|
||||
@apply rounded-lg border-1 border-solid dark:border-gray-200/10 border-gray-200;
|
||||
|
||||
height: calc(var(--vh) - var(--navbar-height));
|
||||
}
|
||||
|
||||
.msg.err {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
84
docs/components/examples/basic/App.vue
Normal file
84
docs/components/examples/basic/App.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup>
|
||||
import { Background, Controls, MiniMap, VueFlow, isNode, useVueFlow } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
import { initialElements } from './initial-elements.js'
|
||||
|
||||
/**
|
||||
* useVueFlow provides all event handlers and store properties
|
||||
* You can pass the composable an object that has the same properties as the VueFlow component props
|
||||
*/
|
||||
const { onPaneReady, onNodeDragStop, onConnect, instance, addEdges } = useVueFlow()
|
||||
|
||||
/**
|
||||
* Our elements
|
||||
*/
|
||||
const elements = ref(initialElements)
|
||||
|
||||
/**
|
||||
* This is a Vue Flow event-hook which can be listened to from anywhere you call the composable, instead of only on the main component
|
||||
*
|
||||
* onPaneReady is called when viewpane & nodes have visible dimensions
|
||||
*/
|
||||
onPaneReady(({ fitView }) => {
|
||||
fitView()
|
||||
})
|
||||
|
||||
onNodeDragStop((e) => console.log('drag stop', e))
|
||||
|
||||
/**
|
||||
* onConnect is called when a new connection is created.
|
||||
* You can add additional properties to your new edge (like a type or label) or block the creation altogether
|
||||
*/
|
||||
onConnect((params) => addEdges([params]))
|
||||
|
||||
const dark = ref(false)
|
||||
|
||||
/**
|
||||
* To update node properties you can simply use your elements v-model and mutate the elements directly
|
||||
* Changes should always be reflected on the graph reactively, without the need to overwrite the elements
|
||||
*/
|
||||
const updatePos = () =>
|
||||
elements.value.forEach((el) => {
|
||||
if (isNode(el)) {
|
||||
el.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* toObject transforms your current graph data to an easily persist-able object
|
||||
*/
|
||||
const logToObject = () => console.log(instance.value?.toObject())
|
||||
|
||||
/**
|
||||
* Resets the current viewpane transformation (zoom & pan)
|
||||
*/
|
||||
const resetTransform = () => instance.value?.setTransform({ x: 0, y: 0, zoom: 1 })
|
||||
|
||||
const toggleClass = () => {
|
||||
dark.value = !dark.value
|
||||
elements.value.forEach((el) => (el.class = dark.value ? 'dark' : 'light'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements" class="basicflow" :default-zoom="1.5" :min-zoom="0.2" :max-zoom="4">
|
||||
<Background pattern-color="#aaa" gap="8" />
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div class="controls">
|
||||
<button style="background-color: #113285; color: white" @click="resetTransform">reset transform</button>
|
||||
<button style="background-color: #6f3381; color: white" @click="updatePos">update positions</button>
|
||||
<button
|
||||
:style="{ backgroundColor: dark ? '#FFFFFB' : '#1C1C1C', color: dark ? '#1C1C1C' : '#FFFFFB' }"
|
||||
@click="toggleClass"
|
||||
>
|
||||
toggle {{ dark ? 'light' : 'dark' }}
|
||||
</button>
|
||||
<button @click="logToObject">log toObject</button>
|
||||
</div>
|
||||
</VueFlow>
|
||||
</template>
|
||||
3
docs/components/examples/basic/index.ts
Normal file
3
docs/components/examples/basic/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as BasicApp } from './App.vue?raw'
|
||||
export { default as BasicElements } from './initial-elements.js?raw'
|
||||
export { default as BasicCSS } from './style.css'
|
||||
25
docs/components/examples/basic/initial-elements.js
Normal file
25
docs/components/examples/basic/initial-elements.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { MarkerType } from '@braks/vue-flow'
|
||||
|
||||
/**
|
||||
* You can pass elements together as a v-model value
|
||||
* or split them up into nodes and edges and pass them to the `nodes` and `edges` props of Vue Flow (or useVueFlow composable)
|
||||
*/
|
||||
export const initialElements = [
|
||||
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
|
||||
{ id: '2', type: 'output', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
|
||||
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 }, class: 'light' },
|
||||
{ id: '4', label: 'Node 4', position: { x: 150, y: 200 }, class: 'light' },
|
||||
{ id: '5', type: 'output', label: 'Node 5', position: { x: 300, y: 300 }, class: 'light' },
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', label: 'edge with arrowhead', source: '1', target: '3', markerEnd: MarkerType.Arrow },
|
||||
{
|
||||
id: 'e4-5',
|
||||
type: 'step',
|
||||
label: 'step-edge',
|
||||
source: '4',
|
||||
target: '5',
|
||||
style: { stroke: 'orange' },
|
||||
labelBgStyle: { fill: 'orange' },
|
||||
},
|
||||
{ id: 'e3-4', type: 'smoothstep', label: 'smoothstep-edge', source: '3', target: '4' },
|
||||
]
|
||||
30
docs/components/examples/basic/style.css
Normal file
30
docs/components/examples/basic/style.css
Normal file
@@ -0,0 +1,30 @@
|
||||
.basicflow .vue-flow__node.dark {
|
||||
background: #1C1C1C;
|
||||
color: #FFFFFB;
|
||||
}
|
||||
|
||||
.basicflow .controls {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.basicflow .controls button {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
-webkit-box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.basicflow .controls button:hover {
|
||||
opacity: 0.8;
|
||||
transform: scale(105%);
|
||||
transition: 250ms all ease;
|
||||
}
|
||||
24
docs/components/examples/connectionline/App.vue
Normal file
24
docs/components/examples/connectionline/App.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { Background, BackgroundVariant, VueFlow } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
import CustomConnectionLine from './CustomConnectionLine.vue'
|
||||
|
||||
const elements = ref([
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'Node 1',
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements">
|
||||
<template #connection-line="props">
|
||||
<CustomConnectionLine v-bind="props" />
|
||||
</template>
|
||||
|
||||
<Background :variant="BackgroundVariant.Lines" />
|
||||
</VueFlow>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
sourceX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
sourceY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<g>
|
||||
<path
|
||||
class="animated"
|
||||
fill="none"
|
||||
stroke="#6F3381"
|
||||
:stroke-width="2.5"
|
||||
:d="`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`"
|
||||
/>
|
||||
<circle :cx="targetX" :cy="targetY" fill="#fff" :r="5" stroke="#6F3381" :stroke-width="1.5" />
|
||||
</g>
|
||||
</template>
|
||||
2
docs/components/examples/connectionline/index.ts
Normal file
2
docs/components/examples/connectionline/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as CustomConnectionLineApp } from './App.vue?raw'
|
||||
export { default as CustomConnectionLine } from './CustomConnectionLine.vue?raw'
|
||||
82
docs/components/examples/custom-node/App.vue
Normal file
82
docs/components/examples/custom-node/App.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script setup>
|
||||
import { Background, ConnectionMode, MiniMap, Position, VueFlow } from '@braks/vue-flow'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
import ColorSelectorNode from './CustomNode.vue'
|
||||
import { presets } from './presets.js'
|
||||
|
||||
const elements = ref([])
|
||||
|
||||
const bgColor = ref(presets.ayame)
|
||||
const bgName = ref('AYAME')
|
||||
|
||||
const connectionLineStyle = { stroke: '#fff' }
|
||||
|
||||
const snapGrid = [16, 16]
|
||||
|
||||
const nodeStroke = (n) => {
|
||||
if (n.type === 'input') return '#0041d0'
|
||||
if (n.type === 'custom') return presets.sumi
|
||||
if (n.type === 'output') return '#ff0072'
|
||||
return '#eee'
|
||||
}
|
||||
const nodeColor = (n) => {
|
||||
if (n.type === 'custom') return bgColor.value
|
||||
return '#fff'
|
||||
}
|
||||
|
||||
// output labels
|
||||
const outputColorLabel = () => h('div', {}, bgColor.value)
|
||||
const outputNameLabel = () => h('div', {}, bgName.value)
|
||||
|
||||
const onChange = (color) => {
|
||||
bgColor.value = color.value
|
||||
bgName.value = color.name
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
elements.value = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
data: { color: bgColor },
|
||||
position: { x: 0, y: 50 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'output',
|
||||
label: outputNameLabel,
|
||||
position: { x: 350, y: 25 },
|
||||
targetPosition: Position.Left,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'output',
|
||||
label: outputColorLabel,
|
||||
position: { x: 350, y: 175 },
|
||||
targetPosition: Position.Left,
|
||||
},
|
||||
|
||||
{ id: 'e1a-2', source: '1', sourceHandle: 'a', target: '2', animated: true, style: { stroke: '#fff' } },
|
||||
{ id: 'e1b-3', source: '1', sourceHandle: 'b', target: '3', animated: true, style: { stroke: '#fff' } },
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow
|
||||
v-model="elements"
|
||||
:style="{ backgroundColor: bgColor }"
|
||||
:connection-mode="ConnectionMode.Loose"
|
||||
:connection-line-style="connectionLineStyle"
|
||||
:snap-to-grid="true"
|
||||
:snap-grid="snapGrid"
|
||||
:default-zoom="1.5"
|
||||
:fit-view-on-init="true"
|
||||
>
|
||||
<template #node-custom="props">
|
||||
<ColorSelectorNode v-bind="props" @change="onChange" />
|
||||
</template>
|
||||
<Background :size="0.7" pattern-color="black" />
|
||||
<MiniMap :node-stroke-color="nodeStroke" :node-color="nodeColor" />
|
||||
</VueFlow>
|
||||
</template>
|
||||
64
docs/components/examples/custom-node/CustomNode.vue
Normal file
64
docs/components/examples/custom-node/CustomNode.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup>
|
||||
import { Handle, Position } from '@braks/vue-flow'
|
||||
import { computed } from 'vue'
|
||||
import { presets } from './presets.js'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const targetHandleStyle = { background: '#555' }
|
||||
const sourceHandleStyleA = { ...targetHandleStyle, top: '10px' }
|
||||
const sourceHandleStyleB = { ...targetHandleStyle, bottom: '10px', top: 'auto' }
|
||||
|
||||
const onConnect = (params) => console.log('handle onConnect', params)
|
||||
|
||||
const onSelect = (color) => {
|
||||
emit('change', color)
|
||||
}
|
||||
|
||||
const colors = computed(() => {
|
||||
return Object.keys(presets).map((color) => {
|
||||
return {
|
||||
name: color,
|
||||
value: presets[color],
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const selectedColor = computed(() => {
|
||||
return colors.value.find((color) => color.value === props.data.color)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="selectedColor">
|
||||
Select a color
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; max-width: 90%; margin: auto; gap: 3px"
|
||||
>
|
||||
<template v-for="color of colors" :key="color.name">
|
||||
<button
|
||||
:title="color.name"
|
||||
:style="{ backgroundColor: color.value }"
|
||||
style="padding: 3px; width: 25px; height: 25px"
|
||||
type="button"
|
||||
@click="onSelect(color)"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
<Handle id="a" type="source" :position="Position.Right" :style="sourceHandleStyleA" />
|
||||
<Handle id="b" type="source" :position="Position.Right" :style="sourceHandleStyleB" />
|
||||
</template>
|
||||
4
docs/components/examples/custom-node/index.ts
Normal file
4
docs/components/examples/custom-node/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as CustomNodeApp } from './App.vue?raw'
|
||||
export { default as CustomNode } from './CustomNode.vue?raw'
|
||||
export { default as CustomNodeCSS } from './style.css'
|
||||
export { default as ColorPresets } from './presets.js?raw'
|
||||
23
docs/components/examples/custom-node/presets.js
Normal file
23
docs/components/examples/custom-node/presets.js
Normal file
@@ -0,0 +1,23 @@
|
||||
export const presets = {
|
||||
byakuroku: '#A8D8B9',
|
||||
mizu: '#81C7D4',
|
||||
asagi: '#33A6B8',
|
||||
ukon: '#EFBB24',
|
||||
mushikuri: '#D9CD90',
|
||||
hiwa: '#BEC23F',
|
||||
ichigo: '#B5495B',
|
||||
kurenai: '#CB1B45',
|
||||
syojyohi: '#E83015',
|
||||
konjyo: '#113285',
|
||||
fuji: '#8B81C3',
|
||||
ayame: '#6F3381',
|
||||
gofun: '#FFFFFB',
|
||||
torinoko: '#DAC9A6',
|
||||
kurotsurubami: '#0B1013',
|
||||
sumi: '#1C1C1C',
|
||||
ohni: '#F05E1C',
|
||||
kokikuchinashi: '#FB9966',
|
||||
beniukon: '#E98B2A',
|
||||
sakura: '#FEDFE1',
|
||||
toki: '#EEA9A9',
|
||||
}
|
||||
12
docs/components/examples/custom-node/style.css
Normal file
12
docs/components/examples/custom-node/style.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.vue-flow__node-custom {
|
||||
border: 1px solid #777;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: whitesmoke;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
max-width: 250px;
|
||||
}
|
||||
47
docs/components/examples/dnd/App.vue
Normal file
47
docs/components/examples/dnd/App.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
import { VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||
import Sidebar from './Sidebar.vue'
|
||||
|
||||
let id = 0
|
||||
const getId = () => `dndnode_${id++}`
|
||||
|
||||
const { instance, onConnect, nodes, edges, addEdges, addNodes, viewport } = useVueFlow({
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'input node',
|
||||
position: { x: 250, y: 25 },
|
||||
},
|
||||
],
|
||||
})
|
||||
const onDragOver = (event) => {
|
||||
event.preventDefault()
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
onConnect((params) => addEdges([params]))
|
||||
|
||||
const onDrop = (event) => {
|
||||
if (instance.value) {
|
||||
const type = event.dataTransfer?.getData('application/vueflow')
|
||||
const position = instance.value.project({ x: event.clientX - 40, y: event.clientY - 18 })
|
||||
const newNode = {
|
||||
id: getId(),
|
||||
type,
|
||||
position,
|
||||
label: `${type} node`,
|
||||
}
|
||||
addNodes([newNode])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dndflow" @drop="onDrop">
|
||||
<VueFlow @dragover="onDragOver" />
|
||||
<Sidebar />
|
||||
</div>
|
||||
</template>
|
||||
19
docs/components/examples/dnd/Sidebar.vue
Normal file
19
docs/components/examples/dnd/Sidebar.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
const onDragStart = (event, nodeType) => {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.setData('application/vueflow', nodeType)
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside>
|
||||
<div class="description">You can drag these nodes to the pane.</div>
|
||||
<div class="nodes">
|
||||
<div class="vue-flow__node-input" :draggable="true" @dragstart="onDragStart($event, 'input')">Input Node</div>
|
||||
<div class="vue-flow__node-default" :draggable="true" @dragstart="onDragStart($event, 'default')">Default Node</div>
|
||||
<div class="vue-flow__node-output" :draggable="true" @dragstart="onDragStart($event, 'output')">Output Node</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
3
docs/components/examples/dnd/index.ts
Normal file
3
docs/components/examples/dnd/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as DndApp } from './App.vue?raw'
|
||||
export { default as DndSidebar } from './Sidebar.vue?raw'
|
||||
export { default as DndCSS } from './style.css'
|
||||
51
docs/components/examples/dnd/style.css
Normal file
51
docs/components/examples/dnd/style.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.dndflow {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dndflow aside {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
border-right: 1px solid #eee;
|
||||
padding: 15px 10px;
|
||||
font-size: 12px;
|
||||
background: rgba(16, 185, 129, 0.75);
|
||||
-webkit-box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
|
||||
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.dndflow aside .nodes > * {
|
||||
margin-bottom: 10px;
|
||||
cursor: grab;
|
||||
font-weight: 500;
|
||||
-webkit-box-shadow: 5px 5px 10px 2px rgba(0,0,0,0.25);
|
||||
box-shadow: 5px 5px 10px 2px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
.dndflow aside .description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dndflow .vue-flow-wrapper {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
.dndflow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dndflow aside {
|
||||
min-width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 639px) {
|
||||
.dndflow aside .nodes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
74
docs/components/examples/edges/App.vue
Normal file
74
docs/components/examples/edges/App.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
import { Background, Controls, MarkerType, MiniMap, VueFlow } from '@braks/vue-flow'
|
||||
import { h, ref } from 'vue'
|
||||
import CustomEdge from './CustomEdge.vue'
|
||||
import CustomEdge2 from './CustomEdge2.vue'
|
||||
import CustomEdgeLabel from './CustomEdgeLabel.vue'
|
||||
|
||||
const elements = ref([
|
||||
{ id: '1', type: 'input', label: 'Input 1', position: { x: 250, y: 0 } },
|
||||
{ id: '2', label: 'Node 2', position: { x: 150, y: 100 } },
|
||||
{ id: '2a', label: 'Node 2a', position: { x: 0, y: 180 } },
|
||||
{ id: '3', label: 'Node 3', position: { x: 250, y: 200 } },
|
||||
{ id: '4', label: 'Node 4', position: { x: 400, y: 300 } },
|
||||
{ id: '3a', label: 'Node 3a', position: { x: 150, y: 300 } },
|
||||
{ id: '5', label: 'Node 5', position: { x: 250, y: 400 } },
|
||||
{ id: '6', type: 'output', label: 'Output 6', position: { x: 50, y: 550 } },
|
||||
{ id: '7', type: 'output', label: 'Output 7', position: { x: 250, y: 550 } },
|
||||
{ id: '8', type: 'output', label: 'Output 8', position: { x: 525, y: 600 } },
|
||||
{ id: '9', type: 'output', label: 'Output 9', position: { x: 675, y: 500 } },
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', class: 'normal-edge' },
|
||||
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
|
||||
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
|
||||
{ id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' },
|
||||
{ id: 'e3-3a', source: '3', target: '3a', type: 'straight', label: 'label only edge', style: { stroke: 'none' } },
|
||||
{ id: 'e3-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } },
|
||||
{
|
||||
id: 'e5-6',
|
||||
source: '5',
|
||||
target: '6',
|
||||
label: () => h(CustomEdgeLabel, { label: 'custom label text' }),
|
||||
labelStyle: { fill: 'red', fontWeight: 700 },
|
||||
markerEnd: MarkerType.Arrow,
|
||||
},
|
||||
{
|
||||
id: 'e5-7',
|
||||
source: '5',
|
||||
target: '7',
|
||||
label: 'label with styled bg',
|
||||
labelBgPadding: [8, 4],
|
||||
labelBgBorderRadius: 4,
|
||||
labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 },
|
||||
markerEnd: MarkerType.ArrowClosed,
|
||||
},
|
||||
{
|
||||
id: 'e5-8',
|
||||
source: '5',
|
||||
target: '8',
|
||||
type: 'custom',
|
||||
data: { text: 'custom edge' },
|
||||
markerEnd: MarkerType.ArrowClosed,
|
||||
},
|
||||
{
|
||||
id: 'e5-9',
|
||||
source: '5',
|
||||
target: '9',
|
||||
type: 'custom2',
|
||||
data: { text: 'custom edge 2' },
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements" :fit-view-on-init="true" :snap-to-grid="true">
|
||||
<template #edge-custom="props">
|
||||
<CustomEdge v-bind="props" />
|
||||
</template>
|
||||
<template #edge-custom2="props">
|
||||
<CustomEdge2 v-bind="props" />
|
||||
</template>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</VueFlow>
|
||||
</template>
|
||||
100
docs/components/examples/edges/CustomEdge.vue
Normal file
100
docs/components/examples/edges/CustomEdge.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import { getBezierPath, getEdgeCenter, useVueFlow } from '@braks/vue-flow'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sourceX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
sourceY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
sourcePosition: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
targetPosition: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
markerEnd: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
const { applyEdgeChanges } = useVueFlow()
|
||||
|
||||
const foreignObjectSize = 40
|
||||
|
||||
const onClick = (evt, id) => {
|
||||
applyEdgeChanges([{ type: 'remove', id }])
|
||||
evt.stopPropagation()
|
||||
}
|
||||
|
||||
const edgePath = computed(() =>
|
||||
getBezierPath({
|
||||
sourceX: props.sourceX,
|
||||
sourceY: props.sourceY,
|
||||
sourcePosition: props.sourcePosition,
|
||||
targetX: props.targetX,
|
||||
targetY: props.targetY,
|
||||
targetPosition: props.targetPosition,
|
||||
}),
|
||||
)
|
||||
|
||||
const center = computed(() =>
|
||||
getEdgeCenter({
|
||||
sourceX: props.sourceX,
|
||||
sourceY: props.sourceY,
|
||||
targetX: props.targetX,
|
||||
targetY: props.targetY,
|
||||
}),
|
||||
)
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<path :id="id" :style="style" class="vue-flow__edge-path" :d="edgePath" :marker-end="markerEnd" />
|
||||
<foreignObject
|
||||
:width="foreignObjectSize"
|
||||
:height="foreignObjectSize"
|
||||
:x="center[0] - foreignObjectSize / 2"
|
||||
:y="center[1] - foreignObjectSize / 2"
|
||||
class="edgebutton-foreignobject"
|
||||
requiredExtensions="http://www.w3.org/1999/xhtml"
|
||||
>
|
||||
<body style="display: flex; align-items: center; justify-content: center">
|
||||
<div>
|
||||
<button ref="btn" class="edgebutton animated-text-gradient" @click="(event) => onClick(event, id)">×</button>
|
||||
</div>
|
||||
</body>
|
||||
</foreignObject>
|
||||
</template>
|
||||
97
docs/components/examples/edges/CustomEdge2.vue
Normal file
97
docs/components/examples/edges/CustomEdge2.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<script setup>
|
||||
import { EdgeText, getBezierPath, getEdgeCenter } from '@braks/vue-flow'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sourceX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
sourceY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
sourcePosition: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
targetPosition: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
markerEnd: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
sourceHandleId: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
targetHandleId: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
const edgePath = computed(() =>
|
||||
getBezierPath({
|
||||
sourceX: props.sourceX,
|
||||
sourceY: props.sourceY,
|
||||
sourcePosition: props.sourcePosition,
|
||||
targetX: props.targetX,
|
||||
targetY: props.targetY,
|
||||
targetPosition: props.targetPosition,
|
||||
}),
|
||||
)
|
||||
|
||||
const center = computed(() =>
|
||||
getEdgeCenter({
|
||||
sourceX: props.sourceX,
|
||||
sourceY: props.sourceY,
|
||||
targetX: props.targetX,
|
||||
targetY: props.targetY,
|
||||
}),
|
||||
)
|
||||
const onClick = () => console.log(props.data)
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<path :id="props.id" class="vue-flow__edge-path" :d="edgePath" :marker-end="props.markerEnd" />
|
||||
<EdgeText
|
||||
:x="center[0]"
|
||||
:y="center[1]"
|
||||
:label="props.data?.text"
|
||||
:label-style="{ fill: 'white' }"
|
||||
:label-show-bg="true"
|
||||
:label-bg-style="{ fill: 'red' }"
|
||||
:label-bg-padding="[2, 4]"
|
||||
:label-bg-border-radius="2"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
12
docs/components/examples/edges/CustomEdgeLabel.vue
Normal file
12
docs/components/examples/edges/CustomEdgeLabel.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tspan dy="10" x="0">{{ props.label }}</tspan>
|
||||
</template>
|
||||
5
docs/components/examples/edges/index.ts
Normal file
5
docs/components/examples/edges/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { default as EdgesApp } from './App.vue?raw'
|
||||
export { default as CustomEdge } from './CustomEdge.vue?raw'
|
||||
export { default as CustomEdge2 } from './CustomEdge2.vue?raw'
|
||||
export { default as CustomEdgeLabel } from './CustomEdgeLabel.vue?raw'
|
||||
export { default as EdgeCSS } from './style.css'
|
||||
34
docs/components/examples/edges/style.css
Normal file
34
docs/components/examples/edges/style.css
Normal file
@@ -0,0 +1,34 @@
|
||||
.edgebutton {
|
||||
color: whitesmoke;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edgebutton:hover {
|
||||
box-shadow: 0 0 0 2px pink, 0 0 0 4px #f05f75;
|
||||
}
|
||||
|
||||
.animated-text-gradient {
|
||||
background: linear-gradient(122deg, #6f3381, #81c7d4, #fedfe1, #fffffb);
|
||||
background-size: 800% 800%;
|
||||
|
||||
-webkit-animation: textgradient 4s ease infinite;
|
||||
-moz-animation: textgradient 4s ease infinite;
|
||||
animation: textgradient 4s ease infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes textgradient {
|
||||
0%{background-position:0% 22%}
|
||||
50%{background-position:100% 79%}
|
||||
100%{background-position:0% 22%}
|
||||
}
|
||||
@-moz-keyframes textgradient {
|
||||
0%{background-position:0% 22%}
|
||||
50%{background-position:100% 79%}
|
||||
100%{background-position:0% 22%}
|
||||
}
|
||||
@keyframes textgradient {
|
||||
0%{background-position:0% 22%}
|
||||
50%{background-position:100% 79%}
|
||||
100%{background-position:0% 22%}
|
||||
}
|
||||
31
docs/components/examples/empty/App.vue
Normal file
31
docs/components/examples/empty/App.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
import { Background, BackgroundVariant, Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||
|
||||
const { nodes, addNodes, edges, addEdges, onConnect, onPaneReady, onNodeDragStop, dimensions } = useVueFlow()
|
||||
|
||||
onConnect((params) => addEdges([params]))
|
||||
onPaneReady((flowInstance) => console.log('flow loaded:', flowInstance))
|
||||
onNodeDragStop((node) => console.log('drag stop', node))
|
||||
|
||||
const addRandomNode = () => {
|
||||
const nodeId = (nodes.value.length + 1).toString()
|
||||
const newNode = {
|
||||
id: nodeId,
|
||||
label: `Node: ${nodeId}`,
|
||||
position: { x: Math.random() * dimensions.value.width, y: Math.random() * dimensions.value.height },
|
||||
}
|
||||
addNodes([newNode])
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background :variant="BackgroundVariant.Lines" />
|
||||
|
||||
<button type="button" :style="{ position: 'absolute', left: '10px', top: '10px', zIndex: 4 }" @click="addRandomNode">
|
||||
add node
|
||||
</button>
|
||||
</VueFlow>
|
||||
</template>
|
||||
1
docs/components/examples/empty/index.ts
Normal file
1
docs/components/examples/empty/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as EmptyApp } from './App.vue?raw'
|
||||
41
docs/components/examples/hidden/App.vue
Normal file
41
docs/components/examples/hidden/App.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
const isHidden = ref(false)
|
||||
|
||||
const { nodes, edges } = useVueFlow({
|
||||
nodes: [
|
||||
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 } },
|
||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 } },
|
||||
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 } },
|
||||
{ id: '4', label: 'Node 4', position: { x: 400, y: 200 } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e3-4', source: '3', target: '4' },
|
||||
],
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
nodes.value.forEach((n) => (n.hidden = isHidden.value))
|
||||
edges.value.forEach((e) => (e.hidden = isHidden.value))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div :style="{ position: 'absolute', left: '10px', top: '10px', zIndex: 4 }">
|
||||
<div>
|
||||
<label for="ishidden">
|
||||
hidden
|
||||
<input id="ishidden" v-model="isHidden" type="checkbox" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</VueFlow>
|
||||
</template>
|
||||
1
docs/components/examples/hidden/index.ts
Normal file
1
docs/components/examples/hidden/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as HiddenApp } from './App.vue?raw'
|
||||
87
docs/components/examples/index.ts
Normal file
87
docs/components/examples/index.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { BasicApp, BasicCSS, BasicElements } from './basic'
|
||||
import { ColorPresets, CustomNode, CustomNodeApp, CustomNodeCSS } from './custom-node'
|
||||
import { CustomConnectionLine, CustomConnectionLineApp } from './connectionline'
|
||||
import { CustomEdge, CustomEdge2, CustomEdgeLabel, EdgeCSS, EdgesApp } from './edges'
|
||||
import { NestedApp } from './nested'
|
||||
import { StressApp, StressCSS, StressUtils } from './stress'
|
||||
import { UpdateEdgeApp } from './update-edge'
|
||||
import { UpdateNodeApp, UpdateNodeCSS } from './update-node'
|
||||
import { ValidationApp, ValidationCSS, ValidationCustomInput, ValidationCustomNode } from './validation'
|
||||
import { SaveRestoreApp, SaveRestoreCSS, SaveRestoreControls } from './save-restore'
|
||||
import { DndApp, DndCSS, DndSidebar } from './dnd'
|
||||
import { EmptyApp } from './empty'
|
||||
import { HiddenApp } from './hidden'
|
||||
import { InteractionApp, InteractionCSS, InteractionControls } from './interaction'
|
||||
import { MultiApp, MultiCSS, MultiFlow } from './multi'
|
||||
|
||||
export const exampleImports = {
|
||||
basic: {
|
||||
'App.vue': BasicApp,
|
||||
'initial-elements.js': BasicElements,
|
||||
'style.css': BasicCSS,
|
||||
},
|
||||
customNode: {
|
||||
'App.vue': CustomNodeApp,
|
||||
'CustomNode.vue': CustomNode,
|
||||
'style.css': CustomNodeCSS,
|
||||
'presets.js': ColorPresets,
|
||||
},
|
||||
connectionline: {
|
||||
'App.vue': CustomConnectionLineApp,
|
||||
'CustomConnectionLine.vue': CustomConnectionLine,
|
||||
},
|
||||
edges: {
|
||||
'App.vue': EdgesApp,
|
||||
'CustomEdge.vue': CustomEdge,
|
||||
'CustomEdge2.vue': CustomEdge2,
|
||||
'CustomEdgeLabel.vue': CustomEdgeLabel,
|
||||
'style.css': EdgeCSS,
|
||||
},
|
||||
nested: {
|
||||
'App.vue': NestedApp,
|
||||
},
|
||||
stress: {
|
||||
'App.vue': StressApp,
|
||||
'utils.js': StressUtils,
|
||||
'style.css': StressCSS,
|
||||
},
|
||||
updateEdge: {
|
||||
'App.vue': UpdateEdgeApp,
|
||||
},
|
||||
updateNode: {
|
||||
'App.vue': UpdateNodeApp,
|
||||
'style.css': UpdateNodeCSS,
|
||||
},
|
||||
validation: {
|
||||
'App.vue': ValidationApp,
|
||||
'CustomInput.vue': ValidationCustomInput,
|
||||
'CustomNode.vue': ValidationCustomNode,
|
||||
'style.css': ValidationCSS,
|
||||
},
|
||||
saveRestore: {
|
||||
'App.vue': SaveRestoreApp,
|
||||
'Controls.vue': SaveRestoreControls,
|
||||
'style.css': SaveRestoreCSS,
|
||||
},
|
||||
dnd: {
|
||||
'App.vue': DndApp,
|
||||
'Sidebar.vue': DndSidebar,
|
||||
'style.css': DndCSS,
|
||||
},
|
||||
empty: {
|
||||
'App.vue': EmptyApp,
|
||||
},
|
||||
hidden: {
|
||||
'App.vue': HiddenApp,
|
||||
},
|
||||
interaction: {
|
||||
'App.vue': InteractionApp,
|
||||
'InteractionControls.vue': InteractionControls,
|
||||
'style.css': InteractionCSS,
|
||||
},
|
||||
multi: {
|
||||
'App.vue': MultiApp,
|
||||
'Flow.vue': MultiFlow,
|
||||
'style.css': MultiCSS,
|
||||
},
|
||||
}
|
||||
20
docs/components/examples/interaction/App.vue
Normal file
20
docs/components/examples/interaction/App.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
import { VueFlow } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
import InteractionControls from './InteractionControls.vue'
|
||||
|
||||
const elements = ref([
|
||||
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 } },
|
||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 } },
|
||||
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 } },
|
||||
{ id: '4', label: 'Node 4', position: { x: 400, y: 200 } },
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow class="interactionflow" v-model="elements" :fit-view-on-init="true">
|
||||
<InteractionControls />
|
||||
</VueFlow>
|
||||
</template>
|
||||
110
docs/components/examples/interaction/InteractionControls.vue
Normal file
110
docs/components/examples/interaction/InteractionControls.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup>
|
||||
import { useVueFlow } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const {
|
||||
nodesDraggable,
|
||||
nodesConnectable,
|
||||
elementsSelectable,
|
||||
zoomOnScroll,
|
||||
zoomOnDoubleClick,
|
||||
zoomOnPinch,
|
||||
panOnScroll,
|
||||
panOnScrollMode,
|
||||
panOnDrag,
|
||||
onConnect,
|
||||
onNodeDragStop,
|
||||
onPaneClick,
|
||||
onPaneScroll,
|
||||
onPaneContextMenu,
|
||||
onNodeDragStart,
|
||||
onMoveEnd,
|
||||
addEdges,
|
||||
} = useVueFlow()
|
||||
|
||||
const captureZoomClick = ref(false)
|
||||
const captureZoomScroll = ref(false)
|
||||
|
||||
onConnect((params) => addEdges([params]))
|
||||
onNodeDragStart((e) => console.log('drag start', e))
|
||||
onNodeDragStop((e) => console.log('drag stop', e))
|
||||
onPaneClick((event) => captureZoomClick.value && console.log('pane click', event))
|
||||
onPaneScroll((event) => captureZoomScroll.value && console.log('pane scroll', event))
|
||||
onPaneContextMenu((event) => captureZoomClick.value && console.log('pane ctx menu', event))
|
||||
onMoveEnd((flowTransform) => console.log('move end', flowTransform))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<label class="label" for="draggable">
|
||||
nodesDraggable
|
||||
<input id="draggable" v-model="nodesDraggable" type="checkbox" class="vue-flow__draggable" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="connectable">
|
||||
nodesConnectable
|
||||
<input id="connectable" v-model="nodesConnectable" type="checkbox" class="vue-flow__connectable" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="selectable">
|
||||
elementsSelectable
|
||||
<input id="selectable" v-model="elementsSelectable" type="checkbox" class="vue-flow__selectable" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="zoomonscroll">
|
||||
zoomOnScroll
|
||||
<input id="zoomonscroll" v-model="zoomOnScroll" type="checkbox" class="vue-flow__zoomonscroll" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="zoomonpinch">
|
||||
zoomOnPinch
|
||||
<input id="zoomonpinch" v-model="zoomOnPinch" type="checkbox" class="vue-flow__zoomonpinch" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="panonscroll">
|
||||
panOnScroll
|
||||
<input id="panonscroll" v-model="panOnScroll" type="checkbox" class="vue-flow__panonscroll" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">
|
||||
panOnScrollMode
|
||||
<select id="panonscrollmode" v-model="panOnScrollMode" class="vue-flow__panonscrollmode">
|
||||
<option value="free">free</option>
|
||||
<option value="horizontal">horizontal</option>
|
||||
<option value="vertical">vertical</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="zoomondbl">
|
||||
zoomOnDoubleClick
|
||||
<input id="zoomondbl" v-model="zoomOnDoubleClick" type="checkbox" class="vue-flow__zoomondbl" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="panemoveable">
|
||||
paneMovable
|
||||
<input id="panemoveable" v-model="panOnDrag" type="checkbox" class="vue-flow__panemoveable" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="capturezoompaneclick">
|
||||
capture onPaneClick
|
||||
<input id="capturezoompaneclick" v-model="captureZoomClick" type="checkbox" class="vue-flow__capturezoompaneclick" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="capturezoompanescroll">
|
||||
capture onPaneScroll
|
||||
<input id="capturezoompanescroll" v-model="captureZoomScroll" type="checkbox" class="vue-flow__capturezoompanescroll" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
3
docs/components/examples/interaction/index.ts
Normal file
3
docs/components/examples/interaction/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as InteractionApp } from './App.vue?raw'
|
||||
export { default as InteractionControls } from './InteractionControls.vue?raw'
|
||||
export { default as InteractionCSS } from './style.css'
|
||||
22
docs/components/examples/interaction/style.css
Normal file
22
docs/components/examples/interaction/style.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.interactionflow .controls {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 4;
|
||||
font-size: 11px;
|
||||
background-color: lightgray;
|
||||
border-bottom-right-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.interactionflow .controls .label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.interactionflow .controls .label input {
|
||||
cursor: pointer;
|
||||
}
|
||||
10
docs/components/examples/multi/App.vue
Normal file
10
docs/components/examples/multi/App.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script setup>
|
||||
import Flow from './Flow.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiflows">
|
||||
<Flow />
|
||||
<Flow />
|
||||
</div>
|
||||
</template>
|
||||
36
docs/components/examples/multi/Flow.vue
Normal file
36
docs/components/examples/multi/Flow.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import { Background, VueFlow, isNode } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const elements = ref([
|
||||
{ id: '1', type: 'input', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
|
||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
|
||||
{ id: '3', label: 'Node 3', position: { x: 400, y: 100 }, class: 'light' },
|
||||
{ id: '4', label: 'Node 4', position: { x: 400, y: 200 }, class: 'light' },
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
])
|
||||
|
||||
const toggleClass = () => elements.value.forEach((el) => (el.class = el.class === 'light' ? 'dark' : 'light'))
|
||||
|
||||
const updatePos = () =>
|
||||
elements.value.forEach((el) => {
|
||||
if (isNode(el)) {
|
||||
el.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements" :fit-view-on-init="true">
|
||||
<Background />
|
||||
|
||||
<div style="position: absolute; right: 10px; top: 10px; z-index: 4">
|
||||
<button style="margin-right: 5px" @click="updatePos">update positions</button>
|
||||
<button style="margin-right: 5px" @click="toggleClass">toggle class</button>
|
||||
</div>
|
||||
</VueFlow>
|
||||
</template>
|
||||
3
docs/components/examples/multi/index.ts
Normal file
3
docs/components/examples/multi/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as MultiApp } from './App.vue?raw'
|
||||
export { default as MultiFlow } from './Flow.vue?raw'
|
||||
export { default as MultiCSS } from './style.css'
|
||||
18
docs/components/examples/multi/style.css
Normal file
18
docs/components/examples/multi/style.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.vue-flow__node.dark {
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.multiflows {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.multiflows .vue-flow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.multiflows .vue-flow:first-child {
|
||||
border-right: 2px solid #333;
|
||||
}
|
||||
98
docs/components/examples/nested/App.vue
Normal file
98
docs/components/examples/nested/App.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script setup>
|
||||
import { Background, ConnectionMode, Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const { onConnect, nodes, edges, addEdges, addNodes } = useVueFlow({
|
||||
fitViewOnInit: true,
|
||||
connectionMode: ConnectionMode.Loose,
|
||||
nodes: [
|
||||
{ id: '1', type: 'input', label: 'node', position: { x: 250, y: 0 }, class: 'light' },
|
||||
{
|
||||
id: '2',
|
||||
label: 'parent node',
|
||||
position: { x: 100, y: 100 },
|
||||
class: 'light',
|
||||
style: { backgroundColor: 'rgba(16, 185, 129, 0.5)', width: '200px', height: '200px' },
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
label: 'child node',
|
||||
position: { x: 10, y: 50 },
|
||||
parentNode: '2',
|
||||
},
|
||||
{ id: '3', label: 'node', position: { x: 350, y: 100 }, class: 'light' },
|
||||
{
|
||||
id: '4',
|
||||
label: 'parent node',
|
||||
position: { x: 320, y: 300 },
|
||||
class: 'light',
|
||||
style: { backgroundColor: 'rgba(16, 185, 129, 0.5)', width: '300px', height: '300px' },
|
||||
},
|
||||
{
|
||||
id: '4a',
|
||||
label: 'child node',
|
||||
position: { x: 15, y: 65 },
|
||||
class: 'light',
|
||||
extent: 'parent',
|
||||
parentNode: '4',
|
||||
},
|
||||
{
|
||||
id: '4b',
|
||||
label: 'nested parent node',
|
||||
position: { x: 15, y: 120 },
|
||||
class: 'light',
|
||||
style: { backgroundColor: 'rgba(139, 92, 246, 0.5)', height: '150px', width: '270px' },
|
||||
parentNode: '4',
|
||||
},
|
||||
{
|
||||
id: '4b1',
|
||||
label: 'nested child node',
|
||||
position: { x: 20, y: 40 },
|
||||
class: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
{
|
||||
id: '4b2',
|
||||
label: 'nested child node',
|
||||
position: { x: 100, y: 100 },
|
||||
class: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e2a-4a', source: '2a', target: '4a' },
|
||||
{ id: 'e3-4', source: '3', target: '4' },
|
||||
{ id: 'e3-4b', source: '3', target: '4b' },
|
||||
{ id: 'e4a-4b1', source: '4a', target: '4b1' },
|
||||
{ id: 'e4a-4b2', source: '4a', target: '4b2' },
|
||||
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
||||
],
|
||||
})
|
||||
|
||||
onConnect((params) => addEdges([params]))
|
||||
|
||||
onMounted(() => {
|
||||
// add nodes to parent
|
||||
addNodes([
|
||||
{
|
||||
id: '999',
|
||||
type: 'input',
|
||||
label: 'Drag me to extend area!',
|
||||
position: { x: 20, y: 100 },
|
||||
class: 'light',
|
||||
expandParent: true,
|
||||
parentNode: '2',
|
||||
},
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</VueFlow>
|
||||
</template>
|
||||
1
docs/components/examples/nested/index.ts
Normal file
1
docs/components/examples/nested/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as NestedApp } from './App.vue?raw'
|
||||
23
docs/components/examples/pinia/PiniaExample.vue
Normal file
23
docs/components/examples/pinia/PiniaExample.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import sdk from '@stackblitz/sdk'
|
||||
|
||||
const el = ref()
|
||||
|
||||
onMounted(() => {
|
||||
sdk.embedProjectId(el.value, 'vitejs-vite-jvtrur', {
|
||||
height: 750,
|
||||
forceEmbedLayout: true,
|
||||
openFile: 'src/App.vue',
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el" class="outline-none"></div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.application {
|
||||
@apply h-[75vh];
|
||||
}
|
||||
</style>
|
||||
13
docs/components/examples/save-restore/App.vue
Normal file
13
docs/components/examples/save-restore/App.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { VueFlow } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
import Controls from './Controls.vue'
|
||||
|
||||
const elements = ref([{ id: '1', label: 'Node 1', position: { x: 100, y: 100 } }])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements">
|
||||
<Controls />
|
||||
</VueFlow>
|
||||
</template>
|
||||
40
docs/components/examples/save-restore/Controls.vue
Normal file
40
docs/components/examples/save-restore/Controls.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import { useVueFlow } from '@braks/vue-flow'
|
||||
|
||||
const flowKey = 'example-flow'
|
||||
|
||||
const { nodes, addNodes, setNodes, setEdges, instance, dimensions } = useVueFlow()
|
||||
|
||||
const onSave = () => {
|
||||
localStorage.setItem(flowKey, JSON.stringify(instance.value?.toObject()))
|
||||
}
|
||||
|
||||
const onRestore = () => {
|
||||
const flow = JSON.parse(localStorage.getItem(flowKey))
|
||||
|
||||
if (flow) {
|
||||
const [x = 0, y = 0] = flow.position
|
||||
setNodes(flow.nodes)
|
||||
setEdges(flow.edges)
|
||||
instance.value.setTransform({ x, y, zoom: flow.zoom || 0 })
|
||||
}
|
||||
}
|
||||
|
||||
const onAdd = () => {
|
||||
const id = nodes.value.length + 1
|
||||
const newNode = {
|
||||
id: `random_node-${id}`,
|
||||
label: `Node ${id}`,
|
||||
position: { x: Math.random() * dimensions.value.width, y: Math.random() * dimensions.value.height },
|
||||
}
|
||||
addNodes([newNode])
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="save__controls">
|
||||
<button style="background-color: #33a6b8" @click="onSave">save</button>
|
||||
<button style="background-color: #113285" @click="onRestore">restore</button>
|
||||
<button style="background-color: #6F3381" @click="onAdd">add node</button>
|
||||
</div>
|
||||
</template>
|
||||
3
docs/components/examples/save-restore/index.ts
Normal file
3
docs/components/examples/save-restore/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as SaveRestoreApp } from './App.vue?raw'
|
||||
export { default as SaveRestoreControls } from './Controls.vue?raw'
|
||||
export { default as SaveRestoreCSS } from './style.css'
|
||||
25
docs/components/examples/save-restore/style.css
Normal file
25
docs/components/examples/save-restore/style.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.save__controls {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 4;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.save__controls button {
|
||||
margin-left: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
-webkit-box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
|
||||
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.save__controls button:hover {
|
||||
opacity: 0.8;
|
||||
transform: scale(105%);
|
||||
transition: 250ms all ease-in-out;
|
||||
}
|
||||
38
docs/components/examples/stress/App.vue
Normal file
38
docs/components/examples/stress/App.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { VueFlow, isNode, useVueFlow } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
import { getElements } from './utils.js'
|
||||
|
||||
const { nodes, edges } = getElements(10, 10)
|
||||
const elements = ref([...nodes, ...edges])
|
||||
|
||||
const { onPaneReady } = useVueFlow()
|
||||
|
||||
onPaneReady((i) => {
|
||||
i.fitView({
|
||||
padding: 0.2,
|
||||
})
|
||||
console.log(i.getElements())
|
||||
})
|
||||
|
||||
const toggleClass = () => elements.value.forEach((el) => (el.class = el.class === 'light' ? 'dark' : 'light'))
|
||||
|
||||
const updatePos = () =>
|
||||
elements.value.forEach((el) => {
|
||||
if (isNode(el)) {
|
||||
el.position = {
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow v-model="elements">
|
||||
<div style="position: absolute; right: 10px; top: 10px; z-index: 4">
|
||||
<button style="margin-right: 5px" @click="updatePos">update positions</button>
|
||||
<button style="margin-right: 5px" @click="toggleClass">toggle class</button>
|
||||
</div>
|
||||
</VueFlow>
|
||||
</template>
|
||||
3
docs/components/examples/stress/index.ts
Normal file
3
docs/components/examples/stress/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as StressApp } from './App.vue?raw'
|
||||
export { default as StressUtils } from './utils.js?raw'
|
||||
export { default as StressCSS } from './style.css'
|
||||
4
docs/components/examples/stress/style.css
Normal file
4
docs/components/examples/stress/style.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.vue-flow__node.dark {
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
32
docs/components/examples/stress/utils.js
Normal file
32
docs/components/examples/stress/utils.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export function getElements(xElements = 10, yElements = 10) {
|
||||
const initialNodes = []
|
||||
const initialEdges = []
|
||||
let nodeId = 1
|
||||
let recentNodeId = null
|
||||
|
||||
for (let y = 0; y < yElements; y++) {
|
||||
for (let x = 0; x < xElements; x++) {
|
||||
const position = { x: x * 75, y: y * 75 }
|
||||
const node = {
|
||||
id: nodeId.toString(),
|
||||
style: { width: `50px`, fontSize: `11px` },
|
||||
label: `Node ${nodeId}`,
|
||||
class: 'light',
|
||||
position,
|
||||
}
|
||||
initialNodes.push(node)
|
||||
|
||||
if (recentNodeId && nodeId <= xElements * yElements) {
|
||||
initialEdges.push({ id: `${x}-${y}`, source: recentNodeId.toString(), target: nodeId.toString() })
|
||||
}
|
||||
|
||||
recentNodeId = nodeId
|
||||
nodeId++
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodes: initialNodes,
|
||||
edges: initialEdges,
|
||||
}
|
||||
}
|
||||
47
docs/components/examples/update-edge/App.vue
Normal file
47
docs/components/examples/update-edge/App.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
import { ConnectionMode, Controls, VueFlow, addEdge, updateEdge } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const elements = ref([
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'Node <strong>A</strong>',
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Node <strong>B</strong>',
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: 'Node <strong>C</strong>',
|
||||
position: { x: 400, y: 100 },
|
||||
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
|
||||
},
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'Updateable edge', updatable: true },
|
||||
])
|
||||
|
||||
const onLoad = (flowInstance) => flowInstance.fitView()
|
||||
const onEdgeUpdateStart = (edge) => console.log('start update', edge)
|
||||
const onEdgeUpdateEnd = (edge) => console.log('end update', edge)
|
||||
const onEdgeUpdate = ({ edge, connection }) => {
|
||||
elements.value = updateEdge(edge, connection, elements.value)
|
||||
}
|
||||
const onConnect = (params) => (elements.value = addEdge(params, elements.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow
|
||||
v-model="elements"
|
||||
:connection-mode="ConnectionMode.Loose"
|
||||
@pane-ready="onLoad"
|
||||
@edge-update="onEdgeUpdate"
|
||||
@connect="onConnect"
|
||||
@edge-update-start="onEdgeUpdateStart"
|
||||
@edge-update-end="onEdgeUpdateEnd"
|
||||
>
|
||||
<Controls />
|
||||
</VueFlow>
|
||||
</template>
|
||||
1
docs/components/examples/update-edge/index.ts
Normal file
1
docs/components/examples/update-edge/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as UpdateEdgeApp } from './App.vue?raw'
|
||||
48
docs/components/examples/update-node/App.vue
Normal file
48
docs/components/examples/update-node/App.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const defaultLabel = '-'
|
||||
const { onPaneReady, getNode } = useVueFlow({
|
||||
nodes: [
|
||||
{ id: '1', label: defaultLabel, position: { x: 100, y: 100 } },
|
||||
{ id: '2', label: 'Node 2', position: { x: 100, y: 200 } },
|
||||
],
|
||||
edges: [{ id: 'e1-2', source: '1', target: '2' }],
|
||||
})
|
||||
|
||||
const opts = reactive({
|
||||
bg: '#eeeeee',
|
||||
label: 'Node 1',
|
||||
hidden: false,
|
||||
})
|
||||
|
||||
const updateNode = () => {
|
||||
const node = getNode.value('1')
|
||||
node.label = opts.label.trim() !== '' ? opts.label : defaultLabel
|
||||
node.style = { backgroundColor: opts.bg }
|
||||
node.hidden = opts.hidden
|
||||
}
|
||||
|
||||
onPaneReady(({ fitView }) => {
|
||||
fitView()
|
||||
updateNode()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow>
|
||||
<div class="updatenode__controls">
|
||||
<label>label:</label>
|
||||
<input v-model="opts.label" @input="updateNode" />
|
||||
|
||||
<label class="updatenode__bglabel">background:</label>
|
||||
<input v-model="opts.bg" type="color" @input="updateNode" />
|
||||
|
||||
<div class="updatenode__checkboxwrapper">
|
||||
<label>hidden:</label>
|
||||
<input v-model="opts.hidden" type="checkbox" @change="updateNode" />
|
||||
</div>
|
||||
</div>
|
||||
</VueFlow>
|
||||
</template>
|
||||
2
docs/components/examples/update-node/index.ts
Normal file
2
docs/components/examples/update-node/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as UpdateNodeApp } from './App.vue?raw'
|
||||
export { default as UpdateNodeCSS } from './style.css'
|
||||
30
docs/components/examples/update-node/style.css
Normal file
30
docs/components/examples/update-node/style.css
Normal file
@@ -0,0 +1,30 @@
|
||||
.updatenode__controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 4;
|
||||
font-size: 11px;
|
||||
background-color: lightgray;
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.updatenode__controls label {
|
||||
display: blocK;
|
||||
}
|
||||
|
||||
.updatenode__controls input {
|
||||
padding: 2px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.updatenode__bglabel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.updatenode__checkboxwrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
47
docs/components/examples/validation/App.vue
Normal file
47
docs/components/examples/validation/App.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
import { VueFlow, addEdge } from '@braks/vue-flow'
|
||||
import { ref } from 'vue'
|
||||
import CustomInput from './CustomInput.vue'
|
||||
import CustomNode from './CustomNode.vue'
|
||||
|
||||
const elements = ref([
|
||||
{ id: '0', type: 'custominput', position: { x: 0, y: 150 }, isValidTargetPos: (connection) => connection.target === 'B' },
|
||||
{
|
||||
id: 'A',
|
||||
type: 'custom',
|
||||
position: { x: 250, y: 0 },
|
||||
isValidSourcePos: () => false,
|
||||
},
|
||||
{ id: 'B', type: 'custom', position: { x: 250, y: 150 }, isValidSourcePos: (connection) => connection.target === 'B' },
|
||||
{ id: 'C', type: 'custom', position: { x: 250, y: 300 }, isValidSourcePos: (connection) => connection.target === 'B' },
|
||||
])
|
||||
|
||||
const onLoad = (flowInstance) => flowInstance.fitView()
|
||||
const onConnectStart = ({ nodeId, handleType }) => console.log('on connect start', { nodeId, handleType })
|
||||
const onConnectStop = (event) => console.log('on connect stop', event)
|
||||
const onConnectEnd = (event) => console.log('on connect end', event)
|
||||
|
||||
const onConnect = (params) => {
|
||||
console.log('on connect', params)
|
||||
addEdge(params, elements.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFlow
|
||||
class="validationflow"
|
||||
v-model="elements"
|
||||
@connect="onConnect"
|
||||
@pane-ready="onLoad"
|
||||
@connect-start="onConnectStart"
|
||||
@connect-stop="onConnectStop"
|
||||
@connect-end="onConnectEnd"
|
||||
>
|
||||
<template #node-custominput="props">
|
||||
<CustomInput v-bind="props" />
|
||||
</template>
|
||||
<template #node-custom="props">
|
||||
<CustomNode v-bind="props" />
|
||||
</template>
|
||||
</VueFlow>
|
||||
</template>
|
||||
21
docs/components/examples/validation/CustomInput.vue
Normal file
21
docs/components/examples/validation/CustomInput.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { Handle, Position } from '@braks/vue-flow'
|
||||
|
||||
const props = defineProps({
|
||||
isValidTargetPos: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Only connectable with B</div>
|
||||
<Handle type="source" :position="Position.Right" :is-valid-connection="props.isValidTargetPos" />
|
||||
</template>
|
||||
25
docs/components/examples/validation/CustomNode.vue
Normal file
25
docs/components/examples/validation/CustomNode.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import { Handle, Position } from '@braks/vue-flow'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isValidSourcePos: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Handle type="target" :position="Position.Left" :is-valid-connection="props.isValidSourcePos" />
|
||||
<div>{{ props.id }}</div>
|
||||
</template>
|
||||
4
docs/components/examples/validation/index.ts
Normal file
4
docs/components/examples/validation/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as ValidationApp } from './App.vue?raw'
|
||||
export { default as ValidationCustomInput } from './CustomInput.vue?raw'
|
||||
export { default as ValidationCustomNode } from './CustomNode.vue?raw'
|
||||
export { default as ValidationCSS } from './style.css'
|
||||
31
docs/components/examples/validation/style.css
Normal file
31
docs/components/examples/validation/style.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.validationflow .vue-flow__node {
|
||||
width: 150px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
color: #555;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.validationflow .vue-flow__node-customnode {
|
||||
background: #e6e6e9;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.validationflow .vue-flow__node-custominput .vue-flow__handle {
|
||||
background: #e6e6e9;
|
||||
}
|
||||
|
||||
.validationflow .vue-flow__node-custominput {
|
||||
background: #fff;
|
||||
|
||||
}
|
||||
|
||||
.validationflow .vue-flow__handle-connecting {
|
||||
background: #ff6060;
|
||||
}
|
||||
|
||||
.validationflow .vue-flow__handle-valid {
|
||||
background: #55dd99;
|
||||
}
|
||||
@@ -28,18 +28,31 @@ watch([breakpoints.sm, breakpoints.md, breakpoints.lg, breakpoints.xl, breakpoin
|
||||
<div
|
||||
class="flex flex-col divide-y divide-gray-500 md:divide-y-0 gap-12 md:gap-24 lg:gap-36 max-w-9/12 md:max-w-11/12 lg:max-w-9/12 m-auto py-12 md:py-24 text-center md:text-left"
|
||||
>
|
||||
<div class="flex flex-col md:flex-row gap-12 md:gap-24">
|
||||
<Basic @pane="onLoad" />
|
||||
</div>
|
||||
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
|
||||
<RGB @pane="onLoad" />
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row flex-unwrap gap-12 md:gap-24">
|
||||
<Nested @pane="onLoad" />
|
||||
</div>
|
||||
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
|
||||
<Additional @pane="onLoad" />
|
||||
<div ref="basic">
|
||||
<XyzTransition :appear-visible="true" xyz="fade left ease-out-back">
|
||||
<div class="flex flex-col md:flex-row gap-12 md:gap-24">
|
||||
<Basic @pane="onLoad" />
|
||||
</div>
|
||||
</XyzTransition>
|
||||
</div>
|
||||
|
||||
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
|
||||
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
|
||||
<RGB @pane="onLoad" />
|
||||
</div>
|
||||
</XyzTransition>
|
||||
|
||||
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
|
||||
<div class="flex flex-col md:flex-row flex-unwrap gap-12 md:gap-24">
|
||||
<Nested @pane="onLoad" />
|
||||
</div>
|
||||
</XyzTransition>
|
||||
|
||||
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
|
||||
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
|
||||
<Additional @pane="onLoad" />
|
||||
</div>
|
||||
</XyzTransition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,9 +18,11 @@ onMounted(() => {
|
||||
magnetic: false,
|
||||
dotColor: '#10b981',
|
||||
radius: 8,
|
||||
focusableElementsOffsetX: 2,
|
||||
focusableElementsOffsetY: 2,
|
||||
focusableElementsOffsetX: 5,
|
||||
focusableElementsOffsetY: 4,
|
||||
mode: 'bouncy',
|
||||
focusableElements:
|
||||
'[data-blobity], a:not([data-no-blobity]), button:not([data-no-blobity]), [data-blobity-tooltip], .back-to-top, .intro',
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { GraphEdge, useVueFlow, VueFlow, Background, Controls, ClassFunc } from '@braks/vue-flow'
|
||||
import { Background, ClassFunc, ConnectionLineType, Controls, GraphEdge, useVueFlow, VueFlow } from "@braks/vue-flow";
|
||||
import Cross from "~icons/mdi/window-close";
|
||||
|
||||
const getClass: ClassFunc = (el) => {
|
||||
const classes = ['font-semibold', '!border-2', 'transition-colors', 'duration-300', 'ease-in-out']
|
||||
@@ -13,26 +14,31 @@ const getClass: ClassFunc = (el) => {
|
||||
|
||||
const emit = defineEmits(['pane'])
|
||||
|
||||
const { onPaneReady } = useVueFlow({
|
||||
const { onPaneReady, onConnect, addEdges, viewport } = useVueFlow({
|
||||
connectionLineType: ConnectionLineType.SmoothStep,
|
||||
connectionLineStyle: {
|
||||
strokeDasharray: 5,
|
||||
animation: 'dashdraw 0.5s linear infinite',
|
||||
},
|
||||
modelValue: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'input',
|
||||
label: 'Start',
|
||||
position: { x: 250, y: 5 },
|
||||
class: getClass,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'default',
|
||||
label: 'Waypoint',
|
||||
position: { x: 100, y: 100 },
|
||||
class: getClass,
|
||||
},
|
||||
{ id: '3', label: 'default', position: { x: 400, y: 100 }, class: getClass },
|
||||
{ id: '3', label: 'Waypoint', position: { x: 400, y: 100 }, class: getClass },
|
||||
{
|
||||
id: '4',
|
||||
type: 'output',
|
||||
label: 'output',
|
||||
label: 'End',
|
||||
position: { x: 250, y: 225 },
|
||||
class: getClass,
|
||||
},
|
||||
@@ -93,14 +99,21 @@ const { onPaneReady } = useVueFlow({
|
||||
})
|
||||
|
||||
onPaneReady((i) => emit('pane', i))
|
||||
onConnect((param) => {
|
||||
addEdges([{
|
||||
...param,
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
}])
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="md:max-w-1/3 flex flex-col justify-center">
|
||||
<div class="flex flex-col gap-2 items-center md:items-start">
|
||||
<h1>Batteries-included</h1>
|
||||
<div class="flex flex-col items-center md:items-start">
|
||||
<h1>Interactive Graphs</h1>
|
||||
<p>
|
||||
Vue Flow comes with several built-in features including zooming, panning, zoom & pan controls, single & multi-selections, element dragging,
|
||||
node and edge components and event handlers.
|
||||
Vue Flow comes with built-in features like zoom & pan and dedicated controls, single & multi-selections, draggable elements,
|
||||
customizable nodes and edges and a bunch of event handlers.
|
||||
</p>
|
||||
<router-link class="button max-w-max" to="/guide/"> Documentation </router-link>
|
||||
</div>
|
||||
@@ -109,8 +122,12 @@ onPaneReady((i) => emit('pane', i))
|
||||
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase border-1 border-secondary overflow-hidden"
|
||||
>
|
||||
<VueFlow class="basic">
|
||||
<Controls class="md:!left-auto md:!right-[10px]" />
|
||||
<Background pattern-color="#aaa" :gap="16" />
|
||||
<Controls class="md:(!left-auto !right-[10px])" />
|
||||
<Background pattern-color="#aaa" :gap="60">
|
||||
<template #pattern>
|
||||
<Cross :style="{ fontSize: `${8 * viewport.zoom || 1}px` }" class="text-[#10b981] opacity-50" />
|
||||
</template>
|
||||
</Background>
|
||||
</VueFlow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { Handle, Position, VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
||||
import BoxNode from '../nodes/Box.vue'
|
||||
import confetti from 'canvas-confetti'
|
||||
import colors from 'windicss/colors'
|
||||
import { cheer, fireworks } from './confetti'
|
||||
import Heart from '~icons/mdi/heart'
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
const dark = useDark({
|
||||
selector: 'html',
|
||||
})
|
||||
|
||||
const initialEdges = [
|
||||
{
|
||||
id: 'eintro-examples',
|
||||
@@ -35,7 +41,7 @@ const initialEdges = [
|
||||
style: { strokeWidth: 2, stroke: '#0ea5e9' },
|
||||
},
|
||||
]
|
||||
const { dimensions, instance, onPaneReady, getNodes, getNode, getEdge, updateEdge, edges, setEdges } = useVueFlow({
|
||||
const { dimensions, instance, onNodeClick, onPaneReady, getNodes, getNode, getEdge, updateEdge, edges, setEdges } = useVueFlow({
|
||||
nodes: [
|
||||
{ id: 'intro', type: 'box', position: { x: 0, y: 0 } },
|
||||
{ id: 'examples', type: 'box', position: { x: -50, y: 400 } },
|
||||
@@ -47,10 +53,54 @@ const { dimensions, instance, onPaneReady, getNodes, getNode, getEdge, updateEdg
|
||||
nodesDraggable: false,
|
||||
panOnDrag: false,
|
||||
zoomOnScroll: false,
|
||||
zoomOnDoubleClick: false,
|
||||
zoomOnPinch: false,
|
||||
})
|
||||
|
||||
const clickInterval = ref()
|
||||
const clicks = ref(0)
|
||||
const disabled = ref(false)
|
||||
|
||||
const confettiColors = Object.values(colors).flatMap((color) => {
|
||||
if (typeof color === 'string') return color
|
||||
else return Object.values(color).flatMap((c) => c)
|
||||
})
|
||||
|
||||
onNodeClick(async ({ node }) => {
|
||||
if (node.id === 'intro') {
|
||||
if (disabled.value) return
|
||||
|
||||
if (clickInterval.value) {
|
||||
clearInterval(clickInterval.value)
|
||||
}
|
||||
|
||||
clickInterval.value = setInterval(() => {
|
||||
clearInterval(clickInterval.value)
|
||||
clicks.value = 0
|
||||
}, 1000)
|
||||
|
||||
clicks.value++
|
||||
if (clicks.value < 10) {
|
||||
confetti({
|
||||
startVelocity: 25,
|
||||
spread: 360,
|
||||
angle: 270,
|
||||
origin: { y: 0.1 },
|
||||
colors: confettiColors,
|
||||
})
|
||||
} else if (clicks.value < 20) {
|
||||
await cheer(['#10b981', dark.value ? '#ffffff' : '#000000'])
|
||||
} else if (clicks.value === 20) {
|
||||
disabled.value = true
|
||||
await fireworks(confettiColors)
|
||||
disabled.value = false
|
||||
} else {
|
||||
clicks.value = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const init = ref(false)
|
||||
const edgeId = ref('eintro-documentation')
|
||||
const el = templateRef<HTMLDivElement>('el', null)
|
||||
|
||||
onPaneReady(({ fitView }) => {
|
||||
@@ -83,40 +133,34 @@ onPaneReady(({ fitView }) => {
|
||||
}
|
||||
})
|
||||
|
||||
nextTick(() => {
|
||||
setEdges(() => {
|
||||
return [
|
||||
{
|
||||
id: 'eintro-examples',
|
||||
type: 'smoothstep',
|
||||
sourceHandle: 'a',
|
||||
source: 'intro',
|
||||
target: 'examples',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2, stroke: '#8b5cf6' },
|
||||
},
|
||||
{
|
||||
id: 'eexamples-documentation',
|
||||
type: 'smoothstep',
|
||||
source: 'examples',
|
||||
target: 'documentation',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2, stroke: '#f97316' },
|
||||
},
|
||||
{
|
||||
id: 'edocumentation-acknowledgement',
|
||||
type: 'smoothstep',
|
||||
source: 'documentation',
|
||||
target: 'acknowledgement',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2, stroke: '#0ea5e9' },
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
fitView({
|
||||
duration: 1500,
|
||||
})
|
||||
setEdges(() => {
|
||||
return [
|
||||
{
|
||||
id: 'eintro-examples',
|
||||
type: 'smoothstep',
|
||||
sourceHandle: 'a',
|
||||
source: 'intro',
|
||||
target: 'examples',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2, stroke: '#8b5cf6' },
|
||||
},
|
||||
{
|
||||
id: 'eexamples-documentation',
|
||||
type: 'smoothstep',
|
||||
source: 'examples',
|
||||
target: 'documentation',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2, stroke: '#f97316' },
|
||||
},
|
||||
{
|
||||
id: 'edocumentation-acknowledgement',
|
||||
type: 'smoothstep',
|
||||
source: 'documentation',
|
||||
target: 'acknowledgement',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2, stroke: '#0ea5e9' },
|
||||
},
|
||||
]
|
||||
})
|
||||
} else {
|
||||
getNodes.value.forEach((node) => {
|
||||
@@ -143,24 +187,16 @@ onPaneReady(({ fitView }) => {
|
||||
}
|
||||
})
|
||||
|
||||
nextTick(() => {
|
||||
setEdges(initialEdges)
|
||||
fitView({
|
||||
duration: 1500,
|
||||
})
|
||||
})
|
||||
setEdges(initialEdges)
|
||||
}
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
if (!init.value) init.value = true
|
||||
else instance.value?.fitView()
|
||||
},
|
||||
init.value ? 5 : 1505,
|
||||
)
|
||||
nextTick(() => {
|
||||
fitView()
|
||||
if (!init.value) init.value = true
|
||||
})
|
||||
}
|
||||
|
||||
useResizeObserver(el, setNodes)
|
||||
useResizeObserver(el, useDebounceFn(setNodes, 5))
|
||||
})
|
||||
|
||||
const scrollTo = () => {
|
||||
@@ -175,34 +211,35 @@ const scrollTo = () => {
|
||||
<VueFlow ref="el" class="dark:bg-black bg-white transition-colors duration-200 ease-in-out">
|
||||
<template #node-box="props">
|
||||
<template v-if="props.id === 'intro'">
|
||||
<div class="max-w-[500px]">
|
||||
<BoxNode class="intro">
|
||||
<div class="box max-w-[500px]">
|
||||
<div class="intro px-4 py-2 shadow-lg rounded-md border-2 border-solid border-black">
|
||||
<div class="font-mono flex flex-col gap-4 p-4 items-center text-center">
|
||||
<h1 class="text-2xl lg:text-4xl">Vue Flow</h1>
|
||||
<h2 class="text-lg lg:text-xl font-normal !border-0">
|
||||
The customizable Vue 3 component bringing interactivity to flowcharts and graphs.
|
||||
</h2>
|
||||
</div>
|
||||
</BoxNode>
|
||||
<Handle id="a" type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="props.id === 'documentation'">
|
||||
<div class="flex">
|
||||
<router-link class="link group bg-orange-500" to="/guide/"> Read The Documentation </router-link>
|
||||
<router-link class="link group bg-[#f15a16]" to="/guide/"> Read The Documentation </router-link>
|
||||
</div>
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<Handle class="block md:hidden" type="source" :position="Position.Bottom" />
|
||||
</template>
|
||||
<template v-else-if="props.id === 'examples'">
|
||||
<div class="flex">
|
||||
<router-link class="link group bg-purple-500" to="/examples/"> Check The Examples </router-link>
|
||||
<router-link class="link group bg-[#ef467e]" to="/examples/"> Check The Examples </router-link>
|
||||
</div>
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
<Handle class="block md:hidden" type="source" :position="Position.Bottom" />
|
||||
</template>
|
||||
<template v-else-if="props.id === 'acknowledgement'">
|
||||
<div class="flex" @click="scrollTo">
|
||||
<div class="link group bg-sky-500"><Heart class="text-red-500" /> Acknowledgement</div>
|
||||
<button class="link group bg-sky-500"><Heart class="text-red-500" /> Acknowledgement</button>
|
||||
</div>
|
||||
<Handle type="target" :position="Position.Top" />
|
||||
</template>
|
||||
|
||||
@@ -72,7 +72,7 @@ onPaneReady((i) => emit('pane', i))
|
||||
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase border-1 border-green-500 overflow-hidden"
|
||||
>
|
||||
<VueFlow class="nested">
|
||||
<Controls class="md:!left-auto md:!right-[10px]" />
|
||||
<Controls class="md:(!left-auto !right-[10px])" />
|
||||
<Background pattern-color="#aaa" class="!bg-gray-800" :gap="18" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
|
||||
69
docs/components/home/flows/confetti.ts
Normal file
69
docs/components/home/flows/confetti.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import confetti from 'canvas-confetti'
|
||||
|
||||
export const fireworks = (colors?: string[]) => {
|
||||
return new Promise((resolve) => {
|
||||
const duration = 15 * 1000
|
||||
const animationEnd = Date.now() + duration
|
||||
const defaults = { startVelocity: 60, spread: 360, ticks: 20, zIndex: 0 }
|
||||
|
||||
function randomInRange(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min
|
||||
}
|
||||
|
||||
const interval: any = setInterval(function () {
|
||||
const timeLeft = animationEnd - Date.now()
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(interval)
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration)
|
||||
// since particles fall down, start a bit higher than random
|
||||
confetti(
|
||||
Object.assign({}, defaults, {
|
||||
particleCount,
|
||||
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
|
||||
colors,
|
||||
}),
|
||||
)
|
||||
confetti(
|
||||
Object.assign({}, defaults, {
|
||||
particleCount,
|
||||
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
|
||||
colors,
|
||||
}),
|
||||
)
|
||||
}, 250)
|
||||
})
|
||||
}
|
||||
|
||||
export const cheer = (colors: string[]) => {
|
||||
return new Promise((resolve) => {
|
||||
const end = Date.now() + 2 * 1000
|
||||
|
||||
function frame() {
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 60,
|
||||
spread: 75,
|
||||
origin: { x: 0 },
|
||||
colors,
|
||||
})
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
spread: 75,
|
||||
origin: { x: 1 },
|
||||
colors,
|
||||
})
|
||||
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame)
|
||||
} else {
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
frame()
|
||||
})
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Handle, Position } from '@braks/vue-flow'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 py-2 shadow-lg rounded-md border-2 border-solid border-black">
|
||||
<slot />
|
||||
<Handle id="a" type="source" :position="Position.Bottom" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,11 +8,17 @@
|
||||
"lint": "eslint --ext \".js,.jsx,.ts,.tsx\" --fix --ignore-path .gitignore ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@braks/vue-flow": "^0.4.6",
|
||||
"blobity": "^0.1.7"
|
||||
"@animxyz/core": "^0.6.6",
|
||||
"@animxyz/vue3": "^0.6.6",
|
||||
"@braks/vue-flow": "^0.4.11",
|
||||
"@stackblitz/sdk": "^1.6.0",
|
||||
"@vue/repl": "^1.1.2",
|
||||
"blobity": "^0.1.7",
|
||||
"canvas-confetti": "^1.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.1.22",
|
||||
"@types/canvas-confetti": "^1.4.2",
|
||||
"@types/node": "^17.0.23",
|
||||
"@vuepress/bundler-vite": "^2.0.0-beta.37",
|
||||
"@vuepress/plugin-docsearch": "^2.0.0-beta.37",
|
||||
|
||||
14
docs/src/.vuepress/components.d.ts
vendored
14
docs/src/.vuepress/components.d.ts
vendored
@@ -6,17 +6,29 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Acknowledgement: typeof import('./../../components/home/Acknowledgement.vue')['default']
|
||||
Additional: typeof import('./../../components/home/flows/Additional.vue')['default']
|
||||
App: typeof import('./../../components/examples/basic/App.vue')['default']
|
||||
Banner: typeof import('./../../components/home/Banner.vue')['default']
|
||||
Basic: typeof import('./../../components/home/flows/Basic.vue')['default']
|
||||
Box: typeof import('./../../components/home/nodes/Box.vue')['default']
|
||||
Controls: typeof import('./../../components/examples/save-restore/Controls.vue')['default']
|
||||
Custom: typeof import('./../../components/home/edges/Custom.vue')['default']
|
||||
CustomConnectionLine: typeof import('./../../components/examples/connectionline/CustomConnectionLine.vue')['default']
|
||||
CustomEdge: typeof import('./../../components/examples/edges/CustomEdge.vue')['default']
|
||||
CustomEdge2: typeof import('./../../components/examples/edges/CustomEdge2.vue')['default']
|
||||
CustomEdgeLabel: typeof import('./../../components/examples/edges/CustomEdgeLabel.vue')['default']
|
||||
CustomInput: typeof import('./../../components/examples/validation/CustomInput.vue')['default']
|
||||
CustomNode: typeof import('./../../components/examples/custom-node/CustomNode.vue')['default']
|
||||
Features: typeof import('./../../components/home/Features.vue')['default']
|
||||
Flow: typeof import('./../../components/examples/multi/Flow.vue')['default']
|
||||
Home: typeof import('./../../components/home/Home.vue')['default']
|
||||
Input: typeof import('./../../components/home/nodes/Input.vue')['default']
|
||||
InteractionControls: typeof import('./../../components/examples/interaction/InteractionControls.vue')['default']
|
||||
Intro: typeof import('./../../components/home/flows/Intro.vue')['default']
|
||||
Nested: typeof import('./../../components/home/flows/Nested.vue')['default']
|
||||
Output: typeof import('./../../components/home/nodes/Output.vue')['default']
|
||||
PiniaExample: typeof import('./../../components/examples/pinia/PiniaExample.vue')['default']
|
||||
Repl: typeof import('./../../components/Repl.vue')['default']
|
||||
RGB: typeof import('./../../components/home/flows/RGB.vue')['default']
|
||||
Sidebar: typeof import('./../../components/examples/dnd/Sidebar.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import Components from 'unplugin-vue-components/vite'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import head from './head'
|
||||
import { useVueFlow } from '@braks/vue-flow'
|
||||
import { copyVueFlowPlugin } from "./copy-plugin";
|
||||
|
||||
config({ path: resolve(__dirname, '.env') })
|
||||
|
||||
@@ -23,7 +24,11 @@ export default defineUserConfig<DefaultThemeOptions>({
|
||||
bundler: '@vuepress/bundler-vite',
|
||||
bundlerConfig: {
|
||||
viteOptions: {
|
||||
optimizeDeps: {
|
||||
exclude: ['@animxyz/vue3'],
|
||||
},
|
||||
plugins: [
|
||||
copyVueFlowPlugin(),
|
||||
AutoImport({
|
||||
imports: ['vue', '@vueuse/core'],
|
||||
dts: resolve(__dirname, './auto-imports.d.ts'),
|
||||
@@ -109,6 +114,7 @@ export default defineUserConfig<DefaultThemeOptions>({
|
||||
'/examples/hidden',
|
||||
'/examples/interaction',
|
||||
'/examples/multi',
|
||||
'/examples/pinia',
|
||||
'/examples/stress',
|
||||
],
|
||||
},
|
||||
|
||||
27
docs/src/.vuepress/copy-plugin.ts
Normal file
27
docs/src/.vuepress/copy-plugin.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Plugin } from 'vite'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export function copyVueFlowPlugin(): Plugin {
|
||||
return {
|
||||
name: 'copy-vue-flow',
|
||||
generateBundle() {
|
||||
console.log('building')
|
||||
const filePath = resolve(
|
||||
__dirname,
|
||||
'../../../node_modules/@braks/vue-flow/dist/vue-flow.es.js'
|
||||
)
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error(
|
||||
`@braks/vue-flow/dist/vue-flow.es.js not built. ` +
|
||||
`Run "yarn build" first.`
|
||||
)
|
||||
}
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: 'vue-flow.es.js',
|
||||
source: readFileSync(filePath, 'utf-8'),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
6
docs/src/.vuepress/theme/clientAppEnhance.ts
Normal file
6
docs/src/.vuepress/theme/clientAppEnhance.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineClientAppEnhance } from '@vuepress/client'
|
||||
import VueAnimXyz from '@animxyz/vue3'
|
||||
|
||||
export default defineClientAppEnhance(({ app }) => {
|
||||
app.use(VueAnimXyz)
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import { resolve } from 'path'
|
||||
import { Theme } from 'vuepress'
|
||||
import { path } from '@vuepress/utils'
|
||||
|
||||
export default {
|
||||
name: 'vuepress-theme-local',
|
||||
@@ -7,4 +8,5 @@ export default {
|
||||
layouts: {
|
||||
Layout: resolve(__dirname, 'layouts/default.vue'),
|
||||
},
|
||||
clientAppEnhanceFiles: path.resolve(__dirname, './clientAppEnhance.ts'),
|
||||
} as Theme
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import 'virtual:windi.css'
|
||||
import '@animxyz/core'
|
||||
import '@braks/vue-flow/dist/style.css'
|
||||
import '@braks/vue-flow/dist/theme-default.css'
|
||||
import '../../assets/index.css'
|
||||
|
||||
@@ -6,33 +6,14 @@ pageClass: examples
|
||||
# Custom Connection Line
|
||||
|
||||
If the default connection lines aren't to your liking, or you want to expand on the existing
|
||||
functionality you can pass a custom connection line component to Vue Flow.
|
||||
functionality you can use your own custom connection line.
|
||||
|
||||
Simply pass a component in the designated template slot, and you're good to go.
|
||||
|
||||
```vue:no-line-numbers
|
||||
<template>
|
||||
<VueFlow>
|
||||
<template #connection-line="props">
|
||||
<MyCustomConnectionLine v-bind="props" />
|
||||
</template>
|
||||
</VueFlow>
|
||||
</template>
|
||||
```
|
||||
|
||||
You can see a working example in the sandbox.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-custom-connection-line-0okgze?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Custom Connection Line"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-custom-connection-line-0okgze?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Custom Connection Line"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="connectionline"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -5,24 +5,15 @@ pageClass: examples
|
||||
|
||||
# Custom Node
|
||||
|
||||
One of the key features of Vue Flow is implementing custom elements (nodes / edges) that allow you to expand on the basic node functionality (dragging, selection etc).
|
||||
One of the key features of Vue Flow is implementing custom elements (nodes / edges) that allow you to expand on the basic functionalities (dragging, selecting etc).
|
||||
|
||||
You can display any content and functionality inside a custom node. More documentation about how to set
|
||||
up a custom node can be found on the [custom nodes page](/guide/node.html#custom-nodes/).
|
||||
|
||||
You can see a working example in the sandbox below.
|
||||
up a custom node can be found on the [custom nodes page](/guide/node.html#custom-nodes).
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-custom-node-example-wznb3q?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Custom Node Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-custom-node-example-wznb3q?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Custom Node Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="customNode"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -16,16 +16,9 @@ This example shows another key feature of Vue Flow. You can initialize the Flow
|
||||
is actually mounted. This can be achieved by using the [`useVueFlow`](/guide/composables.html#usevueflow) composable.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-drag-drop-tpbm7d?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Drag & Drop"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-drag-drop-tpbm7d?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Drag & Drop"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="dnd"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -9,16 +9,9 @@ Vue Flow comes with four pre-defined edge types - bezier-, step-, smoothstep and
|
||||
In addition to the built-in edge types you can create your own custom edges. You can find more information on edge types [here](/guide/edge.html#default-edge-types).
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-edges-3jbddk?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Edges"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-edges-3jbddk?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Edges"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="edges"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -9,16 +9,9 @@ Similar to the drag and drop example, we can also add nodes to an empty graph on
|
||||
which triggers a push into our elements / nodes array.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-empty-example-bcxxdv?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Empty Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-empty-example-bcxxdv?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Empty Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="empty"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -13,16 +13,9 @@ If you set the `onlyRenderVisibleElements` prop to `true`, hidden elements will
|
||||
css. This behavior is disabled by default, meaning all elements are rendered whether they are hidden or not.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-hidden-example-nrpjl7?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Hidden Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-hidden-example-nrpjl7?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Hidden Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="hidden"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -11,16 +11,9 @@ On the bottom left you see the viewport-controls and on the bottom right the min
|
||||
You can also see the built-in node (default, input, output) and edge (bezier, straight, step, smoothstep) types.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-basic-example-3hq147?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FBasic.vue&moduleview=1&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Basic Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-basic-example-3hq147?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FBasic.vue&moduleview=1&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Basic Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="basic"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -10,16 +10,9 @@ This includes enabling/disabling zoom-scroll, pan-scroll, dragging etc.
|
||||
In the sandbox below you can find an example showing you how to toggle interaction options.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-interaction-example-pqzf7p?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Hidden Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-interaction-example-pqzf7p?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Hidden Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="interaction"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -10,16 +10,9 @@ Vue Flow is not limited to a single instance on a page.
|
||||
You can use as many component instances as you like.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-multi-flows-example-y4yn9u?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FMultiFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Multi Flows Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-multi-flows-example-y4yn9u?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FMultiFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Multi Flows Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="multi"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -12,16 +12,9 @@ You can also define the boundaries of a node, i.e. if the node can be dragged ou
|
||||
or even let Vue Flow extend the area of a node to fit all it's children.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/compassionate-goldberg-h5dsnq?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Nested Nodes Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/compassionate-goldberg-h5dsnq?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Nested Nodes Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="nested"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
15
docs/src/examples/pinia.md
Normal file
15
docs/src/examples/pinia.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
pageClass: examples
|
||||
|
||||
---
|
||||
|
||||
# Pinia
|
||||
|
||||
Using your existing storage implementation is not an issue.
|
||||
You can store your elements in whatever store you're already using, mutate them there and pass the result to Vue Flow.
|
||||
|
||||
In this example we use [pinia](https://pinia.vuejs.org/) to store our elements, update their positions and toggle classes.
|
||||
|
||||
<div class="mt-6">
|
||||
<PiniaExample />
|
||||
</div>
|
||||
@@ -5,27 +5,21 @@ pageClass: examples
|
||||
|
||||
# Save & Restore
|
||||
|
||||
::: warning BREAKING CHANGE
|
||||
__Changes from 0.3.x to 0.4.x__
|
||||
The "built-in" storage feature has been fully removed.
|
||||
There is no built-in persistent storage feature, however you can use your own storage implementation.
|
||||
|
||||
::: tip State Management Libraries
|
||||
Check the [pinia](/examples/pinia) example to see how to use Vue Flow with a state management library.
|
||||
:::
|
||||
|
||||
There is no built-in storage feature, however creating a save & restore feature is simple.
|
||||
This example demonstrates save & restore functionality using the [`LocalStorage`](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage) of the browser.
|
||||
|
||||
This example demonstrates save & restore functionality using the `LocalStorage` of the browser.
|
||||
You are of course free to implement your own logic (for example fetching the data from an API that's connected to a database).
|
||||
You can extend on this basic idea however you like, i.e. fetch data that you transform into elements which are then passed to Vue Flow or
|
||||
some other business logic you would like to handle.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-save-restore-example-1u7u6t?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Save & Restore Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-save-restore-example-1u7u6t?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Save & Restore Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="saveRestore"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -10,16 +10,9 @@ Vue Flow is built to be fast but there are limits.
|
||||
Try out how many nodes you can get to render before your browser crashes.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-stress-zyr01z?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Stress"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-stress-zyr01z?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Stress"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="stress"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -14,16 +14,9 @@ for specific edges by using the `updatable` attribute.
|
||||
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/eager-tree-c7igf6?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Updatable Edge Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/eager-tree-c7igf6?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Updatable Edge Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="updateEdge"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -14,16 +14,9 @@ That means you can manipulate any property of your original nodes, and it will t
|
||||
You can of course also access the nodes directly from the state and change their properties from there.
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-update-node-example-q5hjp3?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Update Node Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-update-node-example-q5hjp3?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Update Node Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="updateNode"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -31,16 +31,9 @@ const nodes = [
|
||||
```
|
||||
|
||||
<div class="mt-6">
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-validation-example-zxbyus?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=dark"
|
||||
class="hidden dark:block bg-black h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Validation Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<iframe src="https://codesandbox.io/embed/vue-flow-validation-example-zxbyus?eslint=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FFlow.vue&theme=light"
|
||||
class="block dark:hidden h-full w-full min-h-[75vh]"
|
||||
title="Vue Flow: Validation Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
||||
<client-only>
|
||||
<Suspense>
|
||||
<Repl example="validation"></Repl>
|
||||
</Suspense>
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
@@ -13,4 +13,6 @@ footer: MIT Licensed | Copyright © 2021-present Burak Cakmakoglu
|
||||
|
||||
<Features />
|
||||
|
||||
<Acknowledgement />
|
||||
<XyzTransition :appear-visible="true" xyz="fade down ease-out-back">
|
||||
<Acknowledgement />
|
||||
</XyzTransition>
|
||||
|
||||
@@ -15,7 +15,7 @@ interface CustomEdgeProps<T = any> extends EdgeProps<T> {
|
||||
}
|
||||
|
||||
const props = defineProps<CustomEdgeProps>()
|
||||
const { id: storeId, applyEdgeChanges, store, getEdges } = useVueFlow()
|
||||
const { id: storeId, applyEdgeChanges, getEdges } = useVueFlow()
|
||||
|
||||
const onClick = (evt: Event, id: string) => {
|
||||
applyEdgeChanges([{ type: 'remove', id }])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Connection, Edge, Elements, FlowEvents, FlowInstance, FlowTransform, Node, SnapGrid } from '@braks/vue-flow'
|
||||
import { Background, Controls, MarkerType, MiniMap, VueFlow, addEdge } from '@braks/vue-flow'
|
||||
import { Background, Controls, MarkerType, MiniMap, VueFlow, addEdge } from '@braks/vue-flow/src/index'
|
||||
|
||||
const onNodeDragStart = (e: FlowEvents['nodeDragStart']) => console.log('drag start', e)
|
||||
const onNodeDrag = (e: FlowEvents['nodeDrag']) => console.log('drag', e)
|
||||
@@ -66,7 +66,7 @@ const initialElements: Elements = [
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' },
|
||||
{ id: 'e4-5', source: '4', target: '5', markerEnd: MarkerType.Arrow, label: 'edge with arrow head' },
|
||||
{ id: 'e4-5', source: '4', target: '5', markerEnd: { type: MarkerType.ArrowClosed }, label: 'edge with arrowhead' },
|
||||
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
|
||||
{
|
||||
id: 'e5-7',
|
||||
|
||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
},
|
||||
plugins: [
|
||||
vue({
|
||||
reactivityTransform: './src/',
|
||||
reactivityTransform: true,
|
||||
}),
|
||||
vueTypes(),
|
||||
svgLoader(),
|
||||
|
||||
@@ -108,7 +108,6 @@ export default {
|
||||
v-for="node of getNodes"
|
||||
:id="node.id"
|
||||
:key="node.id"
|
||||
v-memo="[node.computedPosition, node.dimensions]"
|
||||
:position="node.computedPosition"
|
||||
:dimensions="node.dimensions"
|
||||
:style="node.style"
|
||||
|
||||
@@ -6,10 +6,9 @@ import type { GraphNode, NodeComponent, SnapGrid } from '../../types'
|
||||
import { NodeId } from '../../context'
|
||||
import { getHandleBounds, getXYZPos } from '../../utils'
|
||||
|
||||
const { id, type, name, node, parentNode, draggable, selectable, connectable, snapGrid } = defineProps<{
|
||||
const { id, type, name, node, draggable, selectable, connectable, snapGrid } = defineProps<{
|
||||
id: string
|
||||
node: GraphNode
|
||||
parentNode?: GraphNode
|
||||
draggable: boolean
|
||||
selectable: boolean
|
||||
connectable: boolean
|
||||
@@ -30,7 +29,6 @@ const {
|
||||
updateNodePosition,
|
||||
updateNodeDimensions,
|
||||
getNode,
|
||||
getNodeTypes,
|
||||
addSelectedNodes,
|
||||
} = $(useVueFlow())
|
||||
|
||||
|
||||
90
yarn.lock
90
yarn.lock
@@ -132,6 +132,19 @@
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "^0.3.0"
|
||||
|
||||
"@animxyz/core@^0.6.6":
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@animxyz/core/-/core-0.6.6.tgz#2c5eae54c7a6b98c3be79ae16fc1ec68991f67cc"
|
||||
integrity sha512-NtAA/G0Gq3hzAiL6yuE/4U8IgHMPUl3MxbWUbhO443T9UCsf9rBY94P5aK79Zd+/529FeoNdDphIOcOZLsI2sA==
|
||||
|
||||
"@animxyz/vue3@^0.6.6":
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@animxyz/vue3/-/vue3-0.6.6.tgz#473b788d07842db7dd78cf317cc7a6b4bc9fbc89"
|
||||
integrity sha512-ljHz97eNJMBLA2qVX3UDFHQbwIVKFUbKb6w6QbKUrkITY8oMwBz08ZYTYJguSUERXbpazftMSiaHsgRVnEbV/Q==
|
||||
dependencies:
|
||||
"@animxyz/core" "^0.6.6"
|
||||
clsx "^1.1.1"
|
||||
|
||||
"@antfu/eslint-config-basic@0.23.0":
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@antfu/eslint-config-basic/-/eslint-config-basic-0.23.0.tgz#9763f9900afd553bf2a74bd3b5398662f2bae62e"
|
||||
@@ -1435,6 +1448,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
|
||||
|
||||
"@stackblitz/sdk@^1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@stackblitz/sdk/-/sdk-1.6.0.tgz#b53ff39058943b7db5a1aeb640498fac8b1cbe57"
|
||||
integrity sha512-PPB9m1zywojv6sOGSiHv+tvnb4Zc00Jce9yjUG0BzTymUdFf9AP+gKJRlc0iq9vbEEEXfYv1bgKa5KTn4gAzbQ==
|
||||
|
||||
"@surma/rollup-plugin-off-main-thread@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
|
||||
@@ -1474,6 +1492,11 @@
|
||||
"@types/node" "*"
|
||||
"@types/responselike" "*"
|
||||
|
||||
"@types/canvas-confetti@^1.4.2":
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz#35c99fc904492fdcc6515c742509e04f3527211c"
|
||||
integrity sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg==
|
||||
|
||||
"@types/d3-array@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.2.tgz#71c35bca8366a40d1b8fce9279afa4a77fb0065d"
|
||||
@@ -1898,6 +1921,14 @@
|
||||
"@typescript-eslint/types" "5.23.0"
|
||||
"@typescript-eslint/visitor-keys" "5.23.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz#e78f1484bca7e484c48782075219c82c6b77a09f"
|
||||
integrity sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.25.0"
|
||||
"@typescript-eslint/visitor-keys" "5.25.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
|
||||
@@ -1912,6 +1943,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
|
||||
integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
|
||||
|
||||
"@typescript-eslint/types@5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.25.0.tgz#dee51b1855788b24a2eceeae54e4adb89b088dd8"
|
||||
integrity sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
|
||||
@@ -1925,7 +1961,20 @@
|
||||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.23.0", "@typescript-eslint/utils@^5.22.0":
|
||||
"@typescript-eslint/typescript-estree@5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz#a7ab40d32eb944e3fb5b4e3646e81b1bcdd63e00"
|
||||
integrity sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.25.0"
|
||||
"@typescript-eslint/visitor-keys" "5.25.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
|
||||
integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
|
||||
@@ -1937,6 +1986,18 @@
|
||||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
"@typescript-eslint/utils@^5.22.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.25.0.tgz#272751fd737733294b4ab95e16c7f2d4a75c2049"
|
||||
integrity sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@typescript-eslint/scope-manager" "5.25.0"
|
||||
"@typescript-eslint/types" "5.25.0"
|
||||
"@typescript-eslint/typescript-estree" "5.25.0"
|
||||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.23.0":
|
||||
version "5.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
|
||||
@@ -1945,6 +2006,14 @@
|
||||
"@typescript-eslint/types" "5.23.0"
|
||||
eslint-visitor-keys "^3.0.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.25.0":
|
||||
version "5.25.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz#33aa5fdcc5cedb9f4c8828c6a019d58548d4474b"
|
||||
integrity sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.25.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@vitejs/plugin-vue@^2.2.0", "@vitejs/plugin-vue@^2.3.1":
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.1.tgz#5f286b8d3515381c6d5c8fa8eee5e6335f727e14"
|
||||
@@ -2079,6 +2148,11 @@
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
"@vue/repl@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue/repl/-/repl-1.1.2.tgz#1a8e4ac1697d21bb355650f276a2f0390dbb51cf"
|
||||
integrity sha512-IYcoKZgwdn6spiVwGuWeXAbqAv3Nhjc/0dKEJ3BT5U6kV7KLLCwnuizjOvV8P+yFmtsfNkWbmWv9/56Pv5rH/w==
|
||||
|
||||
"@vue/runtime-core@3.2.31":
|
||||
version "3.2.31"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
|
||||
@@ -2974,6 +3048,11 @@ caniuse-lite@^1.0.30001317:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606"
|
||||
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
|
||||
|
||||
canvas-confetti@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.5.1.tgz#bf5b8622ef3bcd347378a972fc4194a89cfe0c9b"
|
||||
integrity sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
@@ -3154,6 +3233,11 @@ clone@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
|
||||
|
||||
clsx@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||
|
||||
code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
@@ -4907,7 +4991,7 @@ globals@^13.6.0, globals@^13.9.0:
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
globby@^11.0.1, globby@^11.0.4:
|
||||
globby@^11.0.1, globby@^11.0.4, globby@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
|
||||
@@ -7514,7 +7598,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.0.0:
|
||||
semver@^7.0.0, semver@^7.3.7:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
|
||||
Reference in New Issue
Block a user