diff --git a/docs/examples/index.ts b/docs/examples/index.ts index 9d332778..859496b6 100644 --- a/docs/examples/index.ts +++ b/docs/examples/index.ts @@ -19,6 +19,7 @@ import { SnapToHandleApp, SnappableConnectionLine } from './connection-radius' import { NodeResizerApp, ResizableNode } from './node-resizer' import { ToolbarApp, ToolbarNode } from './node-toolbar' import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout' +import { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math' export const exampleImports = { basic: { @@ -140,4 +141,13 @@ export const exampleImports = { '@dagrejs/dagre': 'https://cdn.jsdelivr.net/npm/@dagrejs/dagre@1.1.2/+esm', }, }, + math: { + 'App.vue': MathApp, + 'ValueNode.vue': MathValueNode, + 'OperatorNode.vue': MathOperatorNode, + 'ResultNode.vue': MathResultNode, + 'Icon.vue': MathIcon, + 'style.css': MathCSS, + 'initial-elements.js': MathElements, + }, } diff --git a/docs/examples/math/App.vue b/docs/examples/math/App.vue new file mode 100644 index 00000000..a8567738 --- /dev/null +++ b/docs/examples/math/App.vue @@ -0,0 +1,31 @@ + + + diff --git a/docs/examples/math/Icon.vue b/docs/examples/math/Icon.vue new file mode 100644 index 00000000..956ae4b0 --- /dev/null +++ b/docs/examples/math/Icon.vue @@ -0,0 +1,31 @@ + + + diff --git a/docs/examples/math/OperatorNode.vue b/docs/examples/math/OperatorNode.vue new file mode 100644 index 00000000..38d1d825 --- /dev/null +++ b/docs/examples/math/OperatorNode.vue @@ -0,0 +1,27 @@ + + + diff --git a/docs/examples/math/ResultNode.vue b/docs/examples/math/ResultNode.vue new file mode 100644 index 00000000..95ddc6ad --- /dev/null +++ b/docs/examples/math/ResultNode.vue @@ -0,0 +1,77 @@ + + + diff --git a/docs/examples/math/ValueNode.vue b/docs/examples/math/ValueNode.vue new file mode 100644 index 00000000..d14a6951 --- /dev/null +++ b/docs/examples/math/ValueNode.vue @@ -0,0 +1,22 @@ + + + diff --git a/docs/examples/math/index.ts b/docs/examples/math/index.ts new file mode 100644 index 00000000..0c6746ef --- /dev/null +++ b/docs/examples/math/index.ts @@ -0,0 +1,7 @@ +export { default as MathApp } from './App.vue?raw' +export { default as MathElements } from './initial-elements.js?raw' +export { default as MathValueNode } from './ValueNode.vue?raw' +export { default as MathOperatorNode } from './OperatorNode.vue?raw' +export { default as MathResultNode } from './ResultNode.vue?raw' +export { default as MathIcon } from './Icon.vue?raw' +export { default as MathCSS } from './style.css?inline' diff --git a/docs/examples/math/initial-elements.js b/docs/examples/math/initial-elements.js new file mode 100644 index 00000000..6dd83059 --- /dev/null +++ b/docs/examples/math/initial-elements.js @@ -0,0 +1,31 @@ +export const initialNodes = [ + { + id: '1', + position: { x: 0, y: 0 }, + type: 'value', + data: { value: 10 }, + }, + { + id: '2', + position: { x: 0, y: 100 }, + type: 'value', + data: { value: 30 }, + }, + { + id: '3', + position: { x: 300, y: 35 }, + type: 'operator', + data: { operator: '+' }, + }, + { + id: '4', + position: { x: 650, y: 15 }, + type: 'result', + }, +] + +export const initialEdges = [ + { id: 'e1-3', source: '1', target: '3', animated: true, targetHandle: 'target-a' }, + { id: 'e2-3', source: '2', target: '3', animated: true, targetHandle: 'target-b' }, + { id: 'e3-4', source: '3', target: '4', animated: true }, +] diff --git a/docs/examples/math/style.css b/docs/examples/math/style.css new file mode 100644 index 00000000..b6e40ec8 --- /dev/null +++ b/docs/examples/math/style.css @@ -0,0 +1,123 @@ +.math-flow { + background-color: #edf2f7; + height: 100%; + width: 100%; +} + +.vue-flow__handle { + height: 24px; + width: 10px; + background: #aaa; + border-radius: 4px +} + +.vue-flow__edges path { + stroke-width: 3; +} + +.vue-flow__node-value { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background-color: #f3f4f6; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.vue-flow__node-value.selected { + box-shadow: 0 0 0 2px #ec4899; +} + +.vue-flow__node-value input { + flex: 1; + padding: 8px; + border: none; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.vue-flow__node-value input:focus { + outline: none; +} + +.vue-flow__node-value .vue-flow__handle { + background-color: #ec4899; +} + +.vue-flow__node-operator { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px 24px; + background-color: #f3f4f6; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.vue-flow__node-operator.selected { + box-shadow: 0 0 0 2px #0EA5E9; +} + +.vue-flow__node-operator .buttons { + display: flex; + gap: 8px; +} + +.vue-flow__node-operator button { + border: none; + cursor: pointer; + background-color: #4a5568; + border-radius: 8px; + color: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + width: 40px; + height: 40px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; +} + +.vue-flow__node-operator button:hover { + background-color: #0EA5E9; + transition: background-color 0.2s; +} + +.vue-flow__node-operator button.selected { + background-color: #0EA5E9; +} + +.vue-flow__node-operator .vue-flow__handle[data-handleid="target-a"] { + top: 25%; +} + +.vue-flow__node-operator .vue-flow__handle[data-handleid="target-b"] { + top: 75%; +} + +.vue-flow__node-operator .vue-flow__handle { + background-color: #0EA5E9; +} + +.vue-flow__node-result { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px 24px; + background-color: #f3f4f6; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.vue-flow__node-result.selected { + box-shadow: 0 0 0 2px #5EC697; +} + +.vue-flow__node-result .calculation { + display: flex; + gap: 8px; + font-size: 24px; +} diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index a467d904..4832f6f2 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -206,6 +206,7 @@ export default defineConfigWithTheme({ { text: 'Drag & Drop', link: '/examples/dnd' }, { text: 'Interactions', link: '/examples/interaction' }, { text: 'Save & Restore', link: '/examples/save' }, + { text: 'Math Operation Flow', link: '/examples/math' }, { text: 'Screenshot', link: '/examples/screenshot' }, { text: 'Node Visibility', link: '/examples/hidden' }, { text: 'Node Intersections', link: '/examples/intersection' }, diff --git a/docs/src/examples/math.md b/docs/src/examples/math.md new file mode 100644 index 00000000..dd483ad5 --- /dev/null +++ b/docs/src/examples/math.md @@ -0,0 +1,9 @@ +# Math Operation + +This example demonstrates how to use the different composables like `useHandleConnections` or `useNodesData` to create a data flow between nodes. + +In this example we use nodes to create a simple math operation, where the user can input two numbers and select an operation to perform. + +
+ +
diff --git a/docs/src/examples/stress.md b/docs/src/examples/stress.md index 29f77d5c..03194735 100644 --- a/docs/src/examples/stress.md +++ b/docs/src/examples/stress.md @@ -1,4 +1,3 @@ - # Stress Vue Flow is built to be fast but there are limits. @@ -6,9 +5,5 @@ 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.
- - - - - +
diff --git a/docs/src/examples/teleport.md b/docs/src/examples/teleport.md index 7ef6e7bd..cb94ab1b 100644 --- a/docs/src/examples/teleport.md +++ b/docs/src/examples/teleport.md @@ -4,9 +4,5 @@ Teleport your nodes to another dom element using Vue 3 [`Teleport`](https://vuejs.org/guide/built-ins/teleport.html).
- - - - - +
diff --git a/examples/vite/router.ts b/examples/vite/router.ts index 321e8fb8..9e8a9f58 100644 --- a/examples/vite/router.ts +++ b/examples/vite/router.ts @@ -18,6 +18,10 @@ export const routes: RouterOptions['routes'] = [ path: '/snap-handle', component: () => import('./src/SnapHandle/SnapHandleExample.vue'), }, + { + path: '/math', + component: () => import('./src/Math/MathExample.vue'), + }, { path: '/node-resizer', component: () => import('./src/NodeResizer/NodeResizerExample.vue'), diff --git a/examples/vite/src/Math/Icon.vue b/examples/vite/src/Math/Icon.vue new file mode 100644 index 00000000..10043cef --- /dev/null +++ b/examples/vite/src/Math/Icon.vue @@ -0,0 +1,33 @@ + + + diff --git a/examples/vite/src/Math/MathExample.vue b/examples/vite/src/Math/MathExample.vue new file mode 100644 index 00000000..354a65c8 --- /dev/null +++ b/examples/vite/src/Math/MathExample.vue @@ -0,0 +1,31 @@ + + + diff --git a/examples/vite/src/Math/OperatorNode.vue b/examples/vite/src/Math/OperatorNode.vue new file mode 100644 index 00000000..4c42def2 --- /dev/null +++ b/examples/vite/src/Math/OperatorNode.vue @@ -0,0 +1,31 @@ + + + diff --git a/examples/vite/src/Math/ResultNode.vue b/examples/vite/src/Math/ResultNode.vue new file mode 100644 index 00000000..495e44fe --- /dev/null +++ b/examples/vite/src/Math/ResultNode.vue @@ -0,0 +1,76 @@ + + + diff --git a/examples/vite/src/Math/ValueNode.vue b/examples/vite/src/Math/ValueNode.vue new file mode 100644 index 00000000..6f282cd7 --- /dev/null +++ b/examples/vite/src/Math/ValueNode.vue @@ -0,0 +1,28 @@ + + + diff --git a/examples/vite/src/Math/initialElements.ts b/examples/vite/src/Math/initialElements.ts new file mode 100644 index 00000000..61cc5310 --- /dev/null +++ b/examples/vite/src/Math/initialElements.ts @@ -0,0 +1,34 @@ +import type { Edge, Node } from '@vue-flow/core' + +// these are some math nodes, inputs and outputs +export const initialNodes: Node[] = [ + { + id: '1', + position: { x: 0, y: 0 }, + type: 'value', + data: { value: 10 }, + }, + { + id: '2', + position: { x: 0, y: 100 }, + type: 'value', + data: { value: 30 }, + }, + { + id: '3', + position: { x: 400, y: 35 }, + type: 'operator', + data: { operator: '+' }, + }, + { + id: '4', + position: { x: 700, y: 40 }, + type: 'result', + }, +] + +export const initialEdges: Edge[] = [ + { id: 'e1-3', source: '1', target: '3', animated: true, targetHandle: 'target-a' }, + { id: 'e2-3', source: '2', target: '3', animated: true, targetHandle: 'target-b' }, + { id: 'e3-4', source: '3', target: '4', animated: true }, +] diff --git a/examples/vite/src/Math/styles.css b/examples/vite/src/Math/styles.css new file mode 100644 index 00000000..fadb4204 --- /dev/null +++ b/examples/vite/src/Math/styles.css @@ -0,0 +1,123 @@ +.math-flow { + background-color: #edf2f7; + height: 100%; + width: 100%; +} + +.vue-flow__handle { + height: 24px; + width: 10px; + background: #aaa; + border-radius: 4px +} + +.vue-flow__edges path { + stroke-width: 3; +} + +.vue-flow__node-value { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background-color: #f3f4f6; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.vue-flow__node-value.selected { + box-shadow: 0 0 0 2px #ec4899; +} + +.vue-flow__node-value input { + flex: 1; + padding: 8px; + border: none; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.vue-flow__node-value input:focus { + outline: none; +} + +.vue-flow__node-value .vue-flow__handle { + background-color: #ec4899; +} + +.vue-flow__node-operator { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 8px 16px; + background-color: #f3f4f6; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.vue-flow__node-operator.selected { + box-shadow: 0 0 0 2px #0EA5E9; +} + +.vue-flow__node-operator .buttons { + display: flex; + gap: 8px; +} + +.vue-flow__node-operator button { + border: none; + cursor: pointer; + background-color: #4a5568; + border-radius: 8px; + color: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + width: 40px; + height: 40px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; +} + +.vue-flow__node-operator button:hover { + background-color: #0EA5E9; + transition: background-color 0.2s; +} + +.vue-flow__node-operator button.selected { + background-color: #0EA5E9; +} + +.vue-flow__node-operator .vue-flow__handle[data-handleid="target-a"] { + top: 25%; +} + +.vue-flow__node-operator .vue-flow__handle[data-handleid="target-b"] { + top: 75%; +} + +.vue-flow__node-operator .vue-flow__handle { + background-color: #0EA5E9; +} + +.vue-flow__node-result { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 8px 16px; + background-color: #f3f4f6; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.vue-flow__node-result.selected { + box-shadow: 0 0 0 2px #5EC697; +} + +.vue-flow__node-result .calculation { + display: flex; + gap: 8px; + font-size: 24px; +} diff --git a/examples/vite/src/Math/types.ts b/examples/vite/src/Math/types.ts new file mode 100644 index 00000000..dda8410d --- /dev/null +++ b/examples/vite/src/Math/types.ts @@ -0,0 +1,9 @@ +export interface ValueNodeData { + value: number +} + +export type Operator = '+' | '-' | '*' | '/' + +export interface OperatorNodeData { + operator: Operator +} diff --git a/examples/vite/src/Math/utils.ts b/examples/vite/src/Math/utils.ts new file mode 100644 index 00000000..f8c1ad0e --- /dev/null +++ b/examples/vite/src/Math/utils.ts @@ -0,0 +1,8 @@ +import type { Operator } from './types' + +export const mathFunctions: Record number> = { + '+': (a: number, b: number) => a + b, + '-': (a: number, b: number) => a - b, + '*': (a: number, b: number) => a * b, + '/': (a: number, b: number) => a / b, +}