chore(lint): Update eslint rules

* Remove semi
This commit is contained in:
Braks
2021-10-20 22:39:54 +02:00
parent 5ed1094dfe
commit 77eac0c12a
74 changed files with 2007 additions and 1827 deletions
+14 -15
View File
@@ -3,12 +3,12 @@ const baseRules = {
'@typescript-eslint/ban-ts-ignore': 0, '@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/ban-ts-comment': 0, '@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-empty-function': 0, '@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'no-use-before-define': 0, 'no-use-before-define': 0,
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['warn'], '@typescript-eslint/no-unused-vars': ['warn'],
indent: ['warn', 2, { SwitchCase: 1, flatTernaryExpressions: true }], indent: ['warn', 2, { SwitchCase: 1, flatTernaryExpressions: true }],
quotes: ['error', 'single', { avoidEscape: true }], quotes: ['error', 'single', { avoidEscape: true }],
semi: ['error', 'always'],
'import/no-mutable-exports': 0, 'import/no-mutable-exports': 0,
'no-cond-assign': [2], 'no-cond-assign': [2],
'no-console': 'off', 'no-console': 'off',
@@ -16,28 +16,27 @@ const baseRules = {
'@typescript/camelcase': 0, '@typescript/camelcase': 0,
'vue/no-v-html': 'off', 'vue/no-v-html': 'off',
'vue/attribute-hyphenation': 0, 'vue/attribute-hyphenation': 0,
'prettier/prettier': [ 'no-undef': 'off',
'warn', 'prettier/prettier': ['error', {}, { usePrettierrc: true }]
{ }
singleQuote: true,
trailingComma: 'none',
semi: true,
bracketSpacing: true,
vueIndentScriptAndStyle: false,
printWidth: 130
}
]
};
module.exports = { module.exports = {
root: true, root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
env: { env: {
node: true, node: true,
commonjs: true, commonjs: true,
es6: true, es6: true,
browser: true browser: true
}, },
extends: ['plugin:vue/vue3-recommended', 'eslint:recommended', '@vue/typescript/recommended', 'plugin:prettier/recommended'], extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', '@vue/typescript/recommended', 'plugin:prettier/recommended'],
plugins: [], plugins: [],
rules: baseRules rules: baseRules
}; }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",
"semi": true, "semi": false,
"bracketSpacing": true, "bracketSpacing": true,
"vueIndentScriptAndStyle": false, "vueIndentScriptAndStyle": false,
"printWidth": 130 "printWidth": 130
+7 -7
View File
@@ -1,6 +1,6 @@
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue'
import Header from './Header'; import Header from './Header'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router'
export default defineComponent({ export default defineComponent({
name: 'App', name: 'App',
@@ -8,13 +8,13 @@ export default defineComponent({
Header Header
}, },
setup() { setup() {
const route = useRoute(); const route = useRoute()
const key = computed(() => route.fullPath); const key = computed(() => route.fullPath)
return () => ( return () => (
<> <>
<Header /> <Header />
<router-view ref="view" key={key.value} /> <router-view ref="view" key={key.value} />
</> </>
); )
} }
}); })
+6 -6
View File
@@ -1,9 +1,9 @@
const onDragStart = (event: DragEvent, nodeType: string) => { const onDragStart = (event: DragEvent, nodeType: string) => {
if (event.dataTransfer) { if (event.dataTransfer) {
event.dataTransfer.setData('application/revueflow', nodeType); event.dataTransfer.setData('application/revueflow', nodeType)
event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.effectAllowed = 'move'
} }
}; }
const Sidebar = () => { const Sidebar = () => {
return ( return (
@@ -19,7 +19,7 @@ const Sidebar = () => {
Output Node Output Node
</div> </div>
</aside> </aside>
); )
}; }
export default Sidebar; export default Sidebar
+13 -13
View File
@@ -11,22 +11,22 @@ import RevueFlow, {
Connection, Connection,
Edge, Edge,
Elements Elements
} from '../../src'; } from '../../src'
import { getElements } from './utils'; import { getElements } from './utils'
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue'
const onLoad = (reactFlowInstance: OnLoadParams) => { const onLoad = (reactFlowInstance: OnLoadParams) => {
reactFlowInstance.fitView(); reactFlowInstance.fitView()
console.log(reactFlowInstance.getElements()); console.log(reactFlowInstance.getElements())
}; }
const initialElements = getElements(); const initialElements = getElements()
const EdgeTypesFlow = defineComponent({ const EdgeTypesFlow = defineComponent({
setup() { setup() {
const elements = ref<Elements>(initialElements); const elements = ref<Elements>(initialElements)
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value)); const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value)); const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
return () => ( return () => (
<RevueFlow <RevueFlow
@@ -36,8 +36,8 @@ const EdgeTypesFlow = defineComponent({
onConnect={onConnect} onConnect={onConnect}
minZoom={0.2} minZoom={0.2}
></RevueFlow> ></RevueFlow>
); )
} }
}); })
export default EdgeTypesFlow; export default EdgeTypesFlow
+27 -27
View File
@@ -1,18 +1,18 @@
import { ElementId, Elements, Position } from '../../src'; import { ElementId, Elements, Position } from '../../src'
const nodeWidth = 80; const nodeWidth = 80
const nodeGapWidth = nodeWidth * 2; const nodeGapWidth = nodeWidth * 2
const nodeStyle = { width: nodeWidth + 'px', fontSize: '11px', color: 'white' }; const nodeStyle = { width: nodeWidth + 'px', fontSize: '11px', color: 'white' }
const sourceTargetPositions = [ const sourceTargetPositions = [
{ source: Position.Bottom, target: Position.Top }, { source: Position.Bottom, target: Position.Top },
{ source: Position.Right, target: Position.Left } { source: Position.Right, target: Position.Left }
]; ]
const nodeColors = [ const nodeColors = [
['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'], ['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'],
['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8'] ['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8']
]; ]
const edgeTypes = ['default', 'step', 'smoothstep', 'straight']; const edgeTypes = ['default', 'step', 'smoothstep', 'straight']
const offsets = [ const offsets = [
{ {
x: 0, x: 0,
@@ -46,30 +46,30 @@ const offsets = [
x: -nodeGapWidth, x: -nodeGapWidth,
y: -nodeGapWidth y: -nodeGapWidth
} }
]; ]
let id = 0; let id = 0
const getNodeId = (): ElementId => (id++).toString(); const getNodeId = (): ElementId => (id++).toString()
export function getElements(): Elements { export function getElements(): Elements {
const initialElements = []; const initialElements = []
for (let sourceTargetIndex = 0; sourceTargetIndex < sourceTargetPositions.length; sourceTargetIndex++) { for (let sourceTargetIndex = 0; sourceTargetIndex < sourceTargetPositions.length; sourceTargetIndex++) {
const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex]; const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex]
for (let edgeTypeIndex = 0; edgeTypeIndex < edgeTypes.length; edgeTypeIndex++) { for (let edgeTypeIndex = 0; edgeTypeIndex < edgeTypes.length; edgeTypeIndex++) {
const currEdgeType = edgeTypes[edgeTypeIndex]; const currEdgeType = edgeTypes[edgeTypeIndex]
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) { for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
const currOffset = offsets[offsetIndex]; const currOffset = offsets[offsetIndex]
const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] }; const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] }
const sourcePosition = { const sourcePosition = {
x: offsetIndex * nodeWidth * 4, x: offsetIndex * nodeWidth * 4,
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300 y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300
}; }
const sourceId = getNodeId(); const sourceId = getNodeId()
const sourceData = { label: `Source ${sourceId}` }; const sourceData = { label: `Source ${sourceId}` }
const sourceNode = { const sourceNode = {
id: sourceId, id: sourceId,
style, style,
@@ -77,14 +77,14 @@ export function getElements(): Elements {
position: sourcePosition, position: sourcePosition,
sourcePosition: currSourceTargetPos.source, sourcePosition: currSourceTargetPos.source,
targetPosition: currSourceTargetPos.target targetPosition: currSourceTargetPos.target
}; }
const targetId = getNodeId(); const targetId = getNodeId()
const targetData = { label: `Target ${targetId}` }; const targetData = { label: `Target ${targetId}` }
const targetPosition = { const targetPosition = {
x: sourcePosition.x + currOffset.x, x: sourcePosition.x + currOffset.x,
y: sourcePosition.y + currOffset.y y: sourcePosition.y + currOffset.y
}; }
const targetNode = { const targetNode = {
id: targetId, id: targetId,
style, style,
@@ -92,15 +92,15 @@ export function getElements(): Elements {
position: targetPosition, position: targetPosition,
sourcePosition: currSourceTargetPos.source, sourcePosition: currSourceTargetPos.source,
targetPosition: currSourceTargetPos.target targetPosition: currSourceTargetPos.target
}; }
initialElements.push(sourceNode); initialElements.push(sourceNode)
initialElements.push(targetNode); initialElements.push(targetNode)
initialElements.push({ id: `${sourceId}-${targetId}`, source: sourceId, target: targetId, type: currEdgeType }); initialElements.push({ id: `${sourceId}-${targetId}`, source: sourceId, target: targetId, type: currEdgeType })
} }
} }
} }
return initialElements as Elements; return initialElements as Elements
} }
+17 -17
View File
@@ -1,25 +1,25 @@
import { computed, defineComponent, inject } from 'vue'; import { computed, defineComponent, inject } from 'vue'
import { getBezierPath, getEdgeCenter, getMarkerEnd, RevueFlowStore } from '../../src'; import { getBezierPath, getEdgeCenter, getMarkerEnd, RevueFlowStore } from '../../src'
import { DefaultEdgeProps } from '../../src/components/Edges/utils'; import { DefaultEdgeProps } from '../../src/components/Edges/utils'
import { RevueFlowHooks } from '../../src/hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../src/hooks/RevueFlowHooks'
export default defineComponent({ export default defineComponent({
props: { props: {
...DefaultEdgeProps ...DefaultEdgeProps
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const onEdgeClick = (evt: Event, id: string) => { const onEdgeClick = (evt: Event, id: string) => {
const edge = store.edges.find((edge) => edge.id === id); const edge = store.edges.find((edge) => edge.id === id)
if (edge) { if (edge) {
hooks.elementsRemove.trigger([edge]); hooks.elementsRemove.trigger([edge])
} }
evt.stopPropagation(); evt.stopPropagation()
alert(`remove ${id}`); alert(`remove ${id}`)
}; }
const foreignObjectSize = 40; const foreignObjectSize = 40
const edgePath = computed(() => const edgePath = computed(() =>
getBezierPath({ getBezierPath({
@@ -30,8 +30,8 @@ export default defineComponent({
targetY: props.targetY, targetY: props.targetY,
targetPosition: props.targetPosition targetPosition: props.targetPosition
}) })
); )
const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId)); const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId))
const center = computed(() => const center = computed(() =>
getEdgeCenter({ getEdgeCenter({
sourceX: props.sourceX, sourceX: props.sourceX,
@@ -39,7 +39,7 @@ export default defineComponent({
targetX: props.targetX, targetX: props.targetX,
targetY: props.targetY targetY: props.targetY
}) })
); )
return () => ( return () => (
<> <>
@@ -59,6 +59,6 @@ export default defineComponent({
</body> </body>
</foreignObject> </foreignObject>
</> </>
); )
} }
}); })
+8 -8
View File
@@ -1,6 +1,6 @@
import { getBezierPath, getMarkerEnd } from '../../src'; import { getBezierPath, getMarkerEnd } from '../../src'
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue'
import { DefaultEdgeProps } from '../../src/components/Edges/utils'; import { DefaultEdgeProps } from '../../src/components/Edges/utils'
const CustomEdge = defineComponent({ const CustomEdge = defineComponent({
props: { props: {
@@ -16,8 +16,8 @@ const CustomEdge = defineComponent({
targetY: props.targetY, targetY: props.targetY,
targetPosition: props.targetPosition targetPosition: props.targetPosition
}) })
); )
const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId)); const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId))
return () => ( return () => (
<> <>
@@ -28,8 +28,8 @@ const CustomEdge = defineComponent({
</textPath> </textPath>
</text> </text>
</> </>
); )
} }
}); })
export default CustomEdge; export default CustomEdge
+9 -9
View File
@@ -1,6 +1,6 @@
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue'
import { EdgeText, getBezierPath, getEdgeCenter, getMarkerEnd } from '../../src'; import { EdgeText, getBezierPath, getEdgeCenter, getMarkerEnd } from '../../src'
import { DefaultEdgeProps } from '../../src/components/Edges/utils'; import { DefaultEdgeProps } from '../../src/components/Edges/utils'
const CustomEdge = defineComponent({ const CustomEdge = defineComponent({
props: { props: {
@@ -16,8 +16,8 @@ const CustomEdge = defineComponent({
targetY: props.targetY, targetY: props.targetY,
targetPosition: props.targetPosition targetPosition: props.targetPosition
}) })
); )
const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId)); const markerEnd = computed(() => getMarkerEnd(props.arrowHeadType, props.markerEndId))
const center = computed(() => const center = computed(() =>
getEdgeCenter({ getEdgeCenter({
sourceX: props.sourceX, sourceX: props.sourceX,
@@ -25,7 +25,7 @@ const CustomEdge = defineComponent({
targetX: props.targetX, targetX: props.targetX,
targetY: props.targetY targetY: props.targetY
}) })
); )
return () => ( return () => (
<> <>
@@ -42,8 +42,8 @@ const CustomEdge = defineComponent({
onClick={() => console.log(props.data)} onClick={() => console.log(props.data)}
/> />
</> </>
); )
} }
}); })
export default CustomEdge; export default CustomEdge
+15 -15
View File
@@ -1,6 +1,6 @@
import { ref } from 'vue'; import { ref } from 'vue'
import CustomEdge from './CustomEdge'; import CustomEdge from './CustomEdge'
import CustomEdge2 from './CustomEdge2'; import CustomEdge2 from './CustomEdge2'
import RevueFlow, { import RevueFlow, {
Elements, Elements,
FlowElement, FlowElement,
@@ -13,7 +13,7 @@ import RevueFlow, {
Connection, Connection,
Edge, Edge,
removeElements removeElements
} from '../../src'; } from '../../src'
const initialElements = [ const initialElements = [
{ id: '1', type: 'input', data: { label: 'Input 1' }, position: { x: 250, y: 0 } }, { id: '1', type: 'input', data: { label: 'Input 1' }, position: { x: 250, y: 0 } },
@@ -73,21 +73,21 @@ const initialElements = [
type: 'custom2', type: 'custom2',
data: { text: 'custom edge 2' } data: { text: 'custom edge 2' }
} }
]; ]
const edgeTypes: Record<string, any> = { const edgeTypes: Record<string, any> = {
custom: CustomEdge, custom: CustomEdge,
custom2: CustomEdge2 custom2: CustomEdge2
}; }
const EdgesFlow = () => { const EdgesFlow = () => {
const elements = ref<Elements>(initialElements as Elements); const elements = ref<Elements>(initialElements as Elements)
const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView()
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node)
const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element)
const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value)); const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value)); const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
return ( return (
<RevueFlow <RevueFlow
@@ -104,7 +104,7 @@ const EdgesFlow = () => {
<Controls /> <Controls />
<Background /> <Background />
</RevueFlow> </RevueFlow>
); )
}; }
export default EdgesFlow; export default EdgesFlow
+16 -17
View File
@@ -12,32 +12,31 @@ import RevueFlow, {
BackgroundVariant, BackgroundVariant,
Connection, Connection,
Edge Edge
} from '../../src'; } from '../../src'
import { CSSProperties, defineComponent, ref } from 'vue'; import { CSSProperties, defineComponent, ref } from 'vue'
const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance); const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance)
const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element)
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node)
const buttonStyle: CSSProperties = { position: 'absolute', left: '10px', top: '10px', zIndex: 4 }; const buttonStyle: CSSProperties = { position: 'absolute', left: '10px', top: '10px', zIndex: 4 }
const EmptyFlow = defineComponent({ const EmptyFlow = defineComponent({
setup() { setup() {
{ {
const elements = ref<Elements>([]); const elements = ref<Elements>([])
const onElementsRemove = (elementsToRemove: Elements) => const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value))
(elements.value = removeElements(elementsToRemove, elements.value)); const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value));
const addRandomNode = () => { const addRandomNode = () => {
const nodeId: ElementId = (elements.value.length + 1).toString(); const nodeId: ElementId = (elements.value.length + 1).toString()
const newNode: Node = { const newNode: Node = {
id: nodeId, id: nodeId,
data: { label: `Node: ${nodeId}` }, data: { label: `Node: ${nodeId}` },
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight } position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }
} as Node; } as Node
elements.value = [...elements.value, newNode]; elements.value = [...elements.value, newNode]
}; }
return () => ( return () => (
<RevueFlow <RevueFlow
@@ -57,9 +56,9 @@ const EmptyFlow = defineComponent({
add node add node
</button> </button>
</RevueFlow> </RevueFlow>
); )
} }
} }
}); })
export default EmptyFlow; export default EmptyFlow
+10 -10
View File
@@ -1,14 +1,14 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import { routes } from './router'; import { routes } from './router'
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router'
const Header = defineComponent({ const Header = defineComponent({
setup() { setup() {
const router = useRouter(); const router = useRouter()
const route = useRoute(); const route = useRoute()
const onChange = async (event: any) => { const onChange = async (event: any) => {
await router.push(event.target.value); await router.push(event.target.value)
}; }
return () => ( return () => (
<header> <header>
@@ -25,8 +25,8 @@ const Header = defineComponent({
)} )}
</select> </select>
</header> </header>
); )
} }
}); })
export default Header; export default Header
+17 -17
View File
@@ -1,5 +1,5 @@
import RevueFlow, { addEdge, MiniMap, Controls, Connection, Edge, Elements } from '../../src'; import RevueFlow, { addEdge, MiniMap, Controls, Connection, Edge, Elements } from '../../src'
import { defineComponent, ref, watchEffect } from 'vue'; import { defineComponent, ref, watchEffect } from 'vue'
const initialElements: Elements = [ const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
@@ -9,26 +9,26 @@ const initialElements: Elements = [
{ id: 'e1-2', source: '1', target: '2' }, { id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' }, { id: 'e1-3', source: '1', target: '3' },
{ id: 'e3-4', source: '3', target: '4' } { id: 'e3-4', source: '3', target: '4' }
] as Elements; ] as Elements
const HiddenFlow = defineComponent({ const HiddenFlow = defineComponent({
setup() { setup() {
const elements = ref<Elements>(initialElements); const elements = ref<Elements>(initialElements)
const isHidden = ref<boolean>(false); const isHidden = ref<boolean>(false)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value)); const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
watchEffect(() => { watchEffect(() => {
console.log(isHidden.value); console.log(isHidden.value)
elements.value = elements.value.map((e) => { elements.value = elements.value.map((e) => {
e.isHidden = isHidden.value; e.isHidden = isHidden.value
return e; return e
}); })
}); })
const toggle = (event) => { const toggle = (event) => {
console.log(event.target); console.log(event.target)
isHidden.value = event.target?.checked; isHidden.value = event.target?.checked
}; }
return () => ( return () => (
<RevueFlow v-model={elements.value} onConnect={onConnect}> <RevueFlow v-model={elements.value} onConnect={onConnect}>
@@ -44,8 +44,8 @@ const HiddenFlow = defineComponent({
</div> </div>
</div> </div>
</RevueFlow> </RevueFlow>
); )
} }
}); })
export default HiddenFlow; export default HiddenFlow
+27 -27
View File
@@ -9,8 +9,8 @@ import RevueFlow, {
Edge, Edge,
PanOnScrollMode, PanOnScrollMode,
FlowTransform FlowTransform
} from '../../src'; } from '../../src'
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue'
const initialElements: Elements = [ const initialElements: Elements = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
@@ -19,33 +19,33 @@ const initialElements: Elements = [
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' } { id: 'e1-3', source: '1', target: '3' }
] as Elements; ] as Elements
const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node); const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node)
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node)
const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element)
const onPaneClick = (event: MouseEvent) => console.log('onPaneClick', event); const onPaneClick = (event: MouseEvent) => console.log('onPaneClick', event)
const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event); const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event)
const onPaneContextMenu = (event: MouseEvent) => console.log('onPaneContextMenu', event); const onPaneContextMenu = (event: MouseEvent) => console.log('onPaneContextMenu', event)
const onMoveEnd = (flowTranasform?: FlowTransform) => console.log('onMoveEnd', flowTranasform); const onMoveEnd = (flowTranasform?: FlowTransform) => console.log('onMoveEnd', flowTranasform)
const InteractionFlow = defineComponent({ const InteractionFlow = defineComponent({
setup() { setup() {
const elements = ref<Elements>(initialElements); const elements = ref<Elements>(initialElements)
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value)); const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
const isSelectable = ref(false); const isSelectable = ref(false)
const isDraggable = ref(false); const isDraggable = ref(false)
const isConnectable = ref(false); const isConnectable = ref(false)
const zoomOnScroll = ref(false); const zoomOnScroll = ref(false)
const zoomOnPinch = ref(false); const zoomOnPinch = ref(false)
const panOnScroll = ref(false); const panOnScroll = ref(false)
const panOnScrollMode = ref<PanOnScrollMode>(PanOnScrollMode.Free); const panOnScrollMode = ref<PanOnScrollMode>(PanOnScrollMode.Free)
const zoomOnDoubleClick = ref(false); const zoomOnDoubleClick = ref(false)
const paneMoveable = ref(true); const paneMoveable = ref(true)
const captureZoomClick = ref(false); const captureZoomClick = ref(false)
const captureZoomScroll = ref(false); const captureZoomScroll = ref(false)
const captureElementClick = ref(false); const captureElementClick = ref(false)
return () => ( return () => (
<RevueFlow <RevueFlow
@@ -165,8 +165,8 @@ const InteractionFlow = defineComponent({
</div> </div>
</div> </div>
</RevueFlow> </RevueFlow>
); )
} }
}); })
export default InteractionFlow; export default InteractionFlow
+12 -12
View File
@@ -1,14 +1,14 @@
import { createApp } from 'vue'; import { createApp } from 'vue'
import './index.css'; import './index.css'
import App from './App'; import App from './App'
import { router } from './router'; import { router } from './router'
import { createPinia } from 'pinia'; import { createPinia } from 'pinia'
import { DraggablePlugin } from '@braks/revue-draggable'; import { DraggablePlugin } from '@braks/revue-draggable'
const app = createApp(App); const app = createApp(App)
app.config.performance = true; app.config.performance = true
app.use(router); app.use(router)
app.use(DraggablePlugin); app.use(DraggablePlugin)
app.use(createPinia()); app.use(createPinia())
app.mount('#root'); app.mount('#root')
+3 -3
View File
@@ -1,4 +1,4 @@
import { createRouter, createWebHashHistory, RouterOptions } from 'vue-router'; import { createRouter, createWebHashHistory, RouterOptions } from 'vue-router'
export const routes: RouterOptions['routes'] = [ export const routes: RouterOptions['routes'] = [
{ {
@@ -45,9 +45,9 @@ export const routes: RouterOptions['routes'] = [
path: '/interaction', path: '/interaction',
component: () => import('./Interaction') component: () => import('./Interaction')
} }
]; ]
export const router = createRouter({ export const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes routes
}); })
+1
View File
@@ -76,6 +76,7 @@
"rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-typescript2": "^0.30.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"unplugin-auto-import": "^0.4.12",
"vite": "^2.3.8", "vite": "^2.3.8",
"vite-svg-loader": "^2.1.0", "vite-svg-loader": "^2.1.0",
"vue": "^3.2.0-beta.8", "vue": "^3.2.0-beta.8",
+1 -1
View File
@@ -1,3 +1,3 @@
module.exports = { module.exports = {
plugins: [require('autoprefixer'), require('postcss-nested')] plugins: [require('autoprefixer'), require('postcss-nested')]
}; }
+2 -2
View File
@@ -1,3 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
require('esbuild-register'); require('esbuild-register')
module.exports = require('./rollup.ts'); module.exports = require('./rollup.ts')
+21 -22
View File
@@ -1,39 +1,38 @@
import typescript from 'rollup-plugin-typescript2'; import typescript from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'; import { terser } from 'rollup-plugin-terser'
import dts from 'rollup-plugin-dts'; import dts from 'rollup-plugin-dts'
import { OutputOptions, RollupOptions } from 'rollup'; import commonjs from '@rollup/plugin-commonjs'
import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'
import resolve from '@rollup/plugin-node-resolve'; import babel from '@rollup/plugin-babel'
import babel from '@rollup/plugin-babel';
// @ts-ignore // @ts-ignore
import svg from 'rollup-plugin-svg'; import svg from 'rollup-plugin-svg'
import postcss from 'rollup-plugin-postcss'; import postcss from 'rollup-plugin-postcss'
// @ts-ignore // @ts-ignore
import { DEFAULT_EXTENSIONS as DEFAULT_BABEL_EXTENSIONS } from '@babel/core'; import { DEFAULT_EXTENSIONS as DEFAULT_BABEL_EXTENSIONS } from '@babel/core'
const configs: RollupOptions[] = []; const configs: any[] = []
const activePackages = [ const activePackages = [
{ {
display: 'Revue-Flow', display: 'Revue-Flow',
external: ['@vueuse/core'] external: ['@vueuse/core']
} }
]; ]
// @ts-ignore // @ts-ignore
for (const { external, iife } of activePackages) { for (const { external, iife } of activePackages) {
const iifeGlobals = { const iifeGlobals = {
vue: 'Vue', vue: 'Vue',
'@vueuse/core': 'VueUse' '@vueuse/core': 'VueUse'
}; }
const iifeName = 'RevueFlow'; const iifeName = 'RevueFlow'
const functionNames = ['revue-flow']; const functionNames = ['revue-flow']
for (const fn of functionNames) { for (const fn of functionNames) {
const input = 'src/index.ts'; const input = 'src/index.ts'
const output: OutputOptions[] = [ const output: any[] = [
{ {
file: `dist/${fn}.cjs.js`, file: `dist/${fn}.cjs.js`,
format: 'cjs' format: 'cjs'
@@ -42,7 +41,7 @@ for (const { external, iife } of activePackages) {
file: `dist/${fn}.esm.js`, file: `dist/${fn}.esm.js`,
format: 'es' format: 'es'
} }
]; ]
if (iife !== false) { if (iife !== false) {
output.push( output.push(
@@ -67,7 +66,7 @@ for (const { external, iife } of activePackages) {
}) })
] ]
} }
); )
} }
configs.push({ configs.push({
@@ -95,7 +94,7 @@ for (const { external, iife } of activePackages) {
}) })
], ],
external: ['vue', ...(external || [])] external: ['vue', ...(external || [])]
}); })
configs.push({ configs.push({
input, input,
@@ -105,8 +104,8 @@ for (const { external, iife } of activePackages) {
}, },
plugins: [dts()], plugins: [dts()],
external: ['vue', ...(external || []), /\.css$/] external: ['vue', ...(external || []), /\.css$/]
}); })
} }
} }
export default configs; export default configs
+21 -21
View File
@@ -1,18 +1,18 @@
import { BackgroundVariant, RevueFlowStore } from '../../types'; import { BackgroundVariant, RevueFlowStore } from '../../types'
import { createGridDotsPath, createGridLinesPath } from './utils'; import { createGridDotsPath, createGridLinesPath } from './utils'
import { computed, defineComponent, HTMLAttributes, inject, PropType } from 'vue'; import { computed, defineComponent, HTMLAttributes, inject, PropType } from 'vue'
export interface BackgroundProps extends HTMLAttributes { export interface BackgroundProps extends HTMLAttributes {
variant?: BackgroundVariant; variant?: BackgroundVariant
gap?: number; gap?: number
color?: string; color?: string
size?: number; size?: number
} }
const defaultColors = { const defaultColors = {
[BackgroundVariant.Dots]: '#81818a', [BackgroundVariant.Dots]: '#81818a',
[BackgroundVariant.Lines]: '#eee' [BackgroundVariant.Lines]: '#eee'
}; }
const Background = defineComponent({ const Background = defineComponent({
name: 'Background', name: 'Background',
@@ -39,23 +39,23 @@ const Background = defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const transform = computed(() => store.transform); const transform = computed(() => store.transform)
// when there are multiple flows on a page we need to make sure that every background gets its own pattern. // when there are multiple flows on a page we need to make sure that every background gets its own pattern.
const patternId = `pattern-${Math.floor(Math.random() * 100000)}`; const patternId = `pattern-${Math.floor(Math.random() * 100000)}`
const bgClasses = ['revue-flow__background']; const bgClasses = ['revue-flow__background']
const scaledGap = computed(() => props.gap && props.gap * transform.value[2]); const scaledGap = computed(() => props.gap && props.gap * transform.value[2])
const xOffset = computed(() => scaledGap.value && transform.value[0] % scaledGap.value); const xOffset = computed(() => scaledGap.value && transform.value[0] % scaledGap.value)
const yOffset = computed(() => scaledGap.value && transform.value[1] % scaledGap.value); const yOffset = computed(() => scaledGap.value && transform.value[1] % scaledGap.value)
const isLines = computed(() => props.variant === BackgroundVariant.Lines); const isLines = computed(() => props.variant === BackgroundVariant.Lines)
const bgColor = computed(() => (props.color ? props.color : defaultColors[props.variant || BackgroundVariant.Dots])); const bgColor = computed(() => (props.color ? props.color : defaultColors[props.variant || BackgroundVariant.Dots]))
const path = computed(() => const path = computed(() =>
isLines.value isLines.value
? scaledGap.value && props.size && createGridLinesPath(scaledGap.value, props.size, bgColor.value) ? scaledGap.value && props.size && createGridLinesPath(scaledGap.value, props.size, bgColor.value)
: createGridDotsPath(props.size || 0.4 * transform.value[2], bgColor.value) : createGridDotsPath(props.size || 0.4 * transform.value[2], bgColor.value)
); )
return () => ( return () => (
<svg <svg
@@ -77,8 +77,8 @@ const Background = defineComponent({
</pattern> </pattern>
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} /> <rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
</svg> </svg>
); )
} }
}); })
export default Background; export default Background
@@ -1,7 +1,7 @@
export const createGridLinesPath = (size: number, strokeWidth: number, stroke: string) => { export const createGridLinesPath = (size: number, strokeWidth: number, stroke: string) => {
return <path stroke={stroke} stroke-width={strokeWidth} d={`M${size / 2} 0 V${size} M0 ${size / 2} H${size}`} />; return <path stroke={stroke} stroke-width={strokeWidth} d={`M${size / 2} 0 V${size} M0 ${size / 2} H${size}`} />
}; }
export const createGridDotsPath = (size: number, fill: string) => { export const createGridDotsPath = (size: number, fill: string) => {
return <circle cx={size} cy={size} r={size} fill={fill} />; return <circle cx={size} cy={size} r={size} fill={fill} />
}; }
+44 -44
View File
@@ -1,24 +1,24 @@
import { defineComponent, HTMLAttributes, inject, onMounted, PropType, ref } from 'vue'; import { defineComponent, HTMLAttributes, inject, onMounted, PropType, ref } from 'vue'
import useZoomPanHelper from '../../hooks/useZoomPanHelper'; import useZoomPanHelper from '../../hooks/useZoomPanHelper'
import { FitViewParams, RevueFlowStore, ZoomPanHelperFunctions } from '../../types'; import { FitViewParams, RevueFlowStore, ZoomPanHelperFunctions } from '../../types'
import PlusIcon from '../../../assets/icons/plus.svg'; import PlusIcon from '../../../assets/icons/plus.svg'
import MinusIcon from '../../../assets/icons/minus.svg'; import MinusIcon from '../../../assets/icons/minus.svg'
import Fitview from '../../../assets/icons/fitview.svg'; import Fitview from '../../../assets/icons/fitview.svg'
import Lock from '../../../assets/icons/lock.svg'; import Lock from '../../../assets/icons/lock.svg'
import Unlock from '../../../assets/icons/unlock.svg'; import Unlock from '../../../assets/icons/unlock.svg'
export interface ControlProps extends HTMLAttributes { export interface ControlProps extends HTMLAttributes {
showZoom?: boolean; showZoom?: boolean
showFitView?: boolean; showFitView?: boolean
showInteractive?: boolean; showInteractive?: boolean
fitViewParams?: FitViewParams; fitViewParams?: FitViewParams
onZoomIn?: () => void; onZoomIn?: () => void
onZoomOut?: () => void; onZoomOut?: () => void
onFitView?: () => void; onFitView?: () => void
onInteractiveChange?: (interactiveStatus: boolean) => void; onInteractiveChange?: (interactiveStatus: boolean) => void
} }
export type ControlButtonProps = HTMLButtonElement; export type ControlButtonProps = HTMLButtonElement
export const ControlButton = defineComponent({ export const ControlButton = defineComponent({
props: { props: {
@@ -38,9 +38,9 @@ export const ControlButton = defineComponent({
<button class={['revue-flow__controls-button']} {...props}> <button class={['revue-flow__controls-button']} {...props}>
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</button> </button>
); )
} }
}); })
const Controls = defineComponent({ const Controls = defineComponent({
name: 'Controls', name: 'Controls',
@@ -87,41 +87,41 @@ const Controls = defineComponent({
} }
}, },
setup(props, { slots }) { setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const isVisible = ref<boolean>(false); const isVisible = ref<boolean>(false)
const zoomHelper = ref<ZoomPanHelperFunctions>(); const zoomHelper = ref<ZoomPanHelperFunctions>()
const { onReady } = useZoomPanHelper(); const { onReady } = useZoomPanHelper()
onReady((helper) => { onReady((helper) => {
zoomHelper.value = helper; zoomHelper.value = helper
}); })
const isInteractive = store.nodesDraggable && store.nodesConnectable && store.elementsSelectable; const isInteractive = store.nodesDraggable && store.nodesConnectable && store.elementsSelectable
const mapClasses = ['revue-flow__controls']; const mapClasses = ['revue-flow__controls']
const onZoomInHandler = () => { const onZoomInHandler = () => {
zoomHelper.value?.zoomIn?.(); zoomHelper.value?.zoomIn?.()
props.onZoomIn?.(); props.onZoomIn?.()
}; }
const onZoomOutHandler = () => { const onZoomOutHandler = () => {
zoomHelper.value?.zoomOut?.(); zoomHelper.value?.zoomOut?.()
props.onZoomOut?.(); props.onZoomOut?.()
}; }
const onFitViewHandler = () => { const onFitViewHandler = () => {
zoomHelper.value?.fitView?.(props.fitViewParams); zoomHelper.value?.fitView?.(props.fitViewParams)
props.onFitView?.(); props.onFitView?.()
}; }
const onInteractiveChangeHandler = () => { const onInteractiveChangeHandler = () => {
store.setInteractive?.(!isInteractive); store.setInteractive?.(!isInteractive)
props.onInteractiveChange?.(!isInteractive); props.onInteractiveChange?.(!isInteractive)
}; }
onMounted(() => { onMounted(() => {
isVisible.value = true; isVisible.value = true
}); })
return () => ( return () => (
<div class={mapClasses}> <div class={mapClasses}>
@@ -147,8 +147,8 @@ const Controls = defineComponent({
)} )}
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</div> </div>
); )
} }
}); })
export default Controls; export default Controls
@@ -1,15 +1,15 @@
import { computed, defineComponent, PropType } from 'vue'; import { computed, defineComponent, PropType } from 'vue'
interface MiniMapNodeProps { interface MiniMapNodeProps {
x: number; x: number
y: number; y: number
width: number; width: number
height: number; height: number
borderRadius: number; borderRadius: number
color: string; color: string
shapeRendering: string; shapeRendering: string
strokeColor: string; strokeColor: string
strokeWidth: number; strokeWidth: number
} }
const MiniMapNode = defineComponent({ const MiniMapNode = defineComponent({
@@ -62,8 +62,8 @@ const MiniMapNode = defineComponent({
} }
}, },
setup(props, { attrs }: { attrs: Record<string, any> }) { setup(props, { attrs }: { attrs: Record<string, any> }) {
const styles = attrs.style || {}; const styles = attrs.style || {}
const fill = computed(() => (props.color || styles.value.background || styles.value.backgroundColor) as string); const fill = computed(() => (props.color || styles.value.background || styles.value.backgroundColor) as string)
return () => ( return () => (
<rect <rect
@@ -79,8 +79,8 @@ const MiniMapNode = defineComponent({
stroke-width={props.strokeWidth} stroke-width={props.strokeWidth}
shape-rendering={props.shapeRendering} shape-rendering={props.shapeRendering}
/> />
); )
} }
}); })
export default MiniMapNode; export default MiniMapNode
+41 -39
View File
@@ -1,23 +1,23 @@
import { getRectOfNodes, getBoundsofRects } from '../../utils/graph'; import { getRectOfNodes, getBoundsofRects } from '../../utils/graph'
import { Node, Rect, RevueFlowStore } from '../../types'; import { Node, Rect, RevueFlowStore } from '../../types'
import MiniMapNode from './MiniMapNode'; import MiniMapNode from './MiniMapNode'
import { computed, defineComponent, HTMLAttributes, inject, PropType } from 'vue'; import { computed, defineComponent, HTMLAttributes, inject, PropType } from 'vue'
type StringFunc = (node: Node) => string; type StringFunc = (node: Node) => string
export interface MiniMapProps extends HTMLAttributes { export interface MiniMapProps extends HTMLAttributes {
nodeColor?: string | StringFunc; nodeColor?: string | StringFunc
nodeStrokeColor?: string | StringFunc; nodeStrokeColor?: string | StringFunc
nodeClassName?: string | StringFunc; nodeClassName?: string | StringFunc
nodeBorderRadius?: number; nodeBorderRadius?: number
nodeStrokeWidth?: number; nodeStrokeWidth?: number
maskColor?: string; maskColor?: string
} }
declare const window: any; declare const window: any
const defaultWidth = 200; const defaultWidth = 200
const defaultHeight = 150; const defaultHeight = 150
const MiniMap = defineComponent({ const MiniMap = defineComponent({
name: 'MiniMap', name: 'MiniMap',
@@ -54,37 +54,39 @@ const MiniMap = defineComponent({
} }
}, },
setup(props, { attrs }: { attrs: Record<string, any> }) { setup(props, { attrs }: { attrs: Record<string, any> }) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const transform = computed(() => store.transform); const transform = computed(() => store.transform)
const elementWidth = computed(() => (attrs.style?.width || defaultWidth)! as number); const elementWidth = computed(() => (attrs.style?.width || defaultWidth)! as number)
const elementHeight = computed(() => (attrs.style?.height || defaultHeight)! as number); const elementHeight = computed(() => (attrs.style?.height || defaultHeight)! as number)
const nodeColorFunc = computed(() => (props.nodeColor instanceof Function ? props.nodeColor : () => props.nodeColor) as StringFunc); const nodeColorFunc = computed(
() => (props.nodeColor instanceof Function ? props.nodeColor : () => props.nodeColor) as StringFunc
)
const nodeStrokeColorFunc = computed( const nodeStrokeColorFunc = computed(
() => (props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor) as StringFunc () => (props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor) as StringFunc
); )
const nodeClassNameFunc = computed( const nodeClassNameFunc = computed(
() => (props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName) as StringFunc () => (props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName) as StringFunc
); )
const hasNodes = computed(() => store.nodes && store.nodes.length); const hasNodes = computed(() => store.nodes && store.nodes.length)
const bb = computed(() => getRectOfNodes(store.nodes)); const bb = computed(() => getRectOfNodes(store.nodes))
const viewBB = computed<Rect>(() => ({ const viewBB = computed<Rect>(() => ({
x: -transform.value[0] / transform.value[2], x: -transform.value[0] / transform.value[2],
y: -transform.value[1] / transform.value[2], y: -transform.value[1] / transform.value[2],
width: store.width / transform.value[2], width: store.width / transform.value[2],
height: store.height / transform.value[2] height: store.height / transform.value[2]
})); }))
const boundingRect = computed(() => (hasNodes.value ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value)); const boundingRect = computed(() => (hasNodes.value ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value))
const scaledWidth = computed(() => boundingRect.value.width / elementWidth.value); const scaledWidth = computed(() => boundingRect.value.width / elementWidth.value)
const scaledHeight = computed(() => boundingRect.value.height / elementHeight.value); const scaledHeight = computed(() => boundingRect.value.height / elementHeight.value)
const viewScale = computed(() => Math.max(scaledWidth.value, scaledHeight.value)); const viewScale = computed(() => Math.max(scaledWidth.value, scaledHeight.value))
const viewWidth = computed(() => viewScale.value * elementWidth.value); const viewWidth = computed(() => viewScale.value * elementWidth.value)
const viewHeight = computed(() => viewScale.value * elementHeight.value); const viewHeight = computed(() => viewScale.value * elementHeight.value)
const offset = computed(() => 5 * viewScale.value); const offset = computed(() => 5 * viewScale.value)
const x = computed(() => boundingRect.value.x - (viewWidth.value - boundingRect.value.width) / 2 - offset.value); const x = computed(() => boundingRect.value.x - (viewWidth.value - boundingRect.value.width) / 2 - offset.value)
const y = computed(() => boundingRect.value.y - (viewHeight.value - boundingRect.value.height) / 2 - offset.value); const y = computed(() => boundingRect.value.y - (viewHeight.value - boundingRect.value.height) / 2 - offset.value)
const width = computed(() => viewWidth.value + offset.value * 2); const width = computed(() => viewWidth.value + offset.value * 2)
const height = computed(() => viewHeight.value + offset.value * 2); const height = computed(() => viewHeight.value + offset.value * 2)
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
return () => ( return () => (
<svg <svg
@@ -121,8 +123,8 @@ const MiniMap = defineComponent({
fill-rule="evenodd" fill-rule="evenodd"
/> />
</svg> </svg>
); )
} }
}); })
export default MiniMap; export default MiniMap
+3 -3
View File
@@ -1,6 +1,6 @@
// These components are not used by revue Flow directly // These components are not used by revue Flow directly
// but the user can add them as children of a revue Flow component // but the user can add them as children of a revue Flow component
export { default as MiniMap } from './MiniMap'; export { default as MiniMap } from './MiniMap'
export { default as Controls, ControlButton } from './Controls'; export { default as Controls, ControlButton } from './Controls'
export { default as Background } from './Background'; export { default as Background } from './Background'
+156
View File
@@ -0,0 +1,156 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
const computed: typeof import('vue')['computed']
const computedInject: typeof import('@vueuse/core')['computedInject']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const customRef: typeof import('vue')['customRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const extendRef: typeof import('@vueuse/core')['extendRef']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const syncRef: typeof import('@vueuse/core')['syncRef']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useDark: typeof import('@vueuse/core')['useDark']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementSize: typeof import('@vueuse/core')['useElementSize']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNow: typeof import('@vueuse/core')['useNow']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}
export {}
+28 -28
View File
@@ -1,13 +1,13 @@
import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue'; import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue'
import { getBezierPath } from '../Edges/BezierEdge'; import { getBezierPath } from '../Edges/BezierEdge'
import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge'
import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineComponent, RevueFlowStore } from '../../types'; import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineComponent, RevueFlowStore } from '../../types'
interface ConnectionLineProps { interface ConnectionLineProps {
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties
customConnectionLine?: ConnectionLineComponent; customConnectionLine?: ConnectionLineComponent
} }
export default defineComponent({ export default defineComponent({
@@ -29,35 +29,35 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const sourceNode = ref<Node | null>(store.nodes.find((n) => n.id === store.connectionNodeId) || null); const sourceNode = ref<Node | null>(store.nodes.find((n) => n.id === store.connectionNodeId) || null)
const nodesConnectable = computed(() => store.nodesConnectable); const nodesConnectable = computed(() => store.nodesConnectable)
const sourceHandle = computed(() => const sourceHandle = computed(() =>
store.connectionHandleId && store.connectionHandleType store.connectionHandleId && store.connectionHandleType
? sourceNode.value?.__rf.handleBounds[store.connectionHandleType].find( ? sourceNode.value?.__rf.handleBounds[store.connectionHandleType].find(
(d: HandleElement) => d.id === store.connectionHandleId (d: HandleElement) => d.id === store.connectionHandleId
) )
: store.connectionHandleType && sourceNode.value?.__rf.handleBounds[store.connectionHandleType][0] : store.connectionHandleType && sourceNode.value?.__rf.handleBounds[store.connectionHandleType][0]
); )
const sourceHandleX = computed(() => const sourceHandleX = computed(() =>
sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (sourceNode.value?.__rf.width as number) / 2 sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (sourceNode.value?.__rf.width as number) / 2
); )
const sourceHandleY = computed(() => const sourceHandleY = computed(() =>
sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : sourceNode.value?.__rf.height sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : sourceNode.value?.__rf.height
); )
const sourceX = computed(() => sourceNode.value?.__rf.position.x + sourceHandleX.value); const sourceX = computed(() => sourceNode.value?.__rf.position.x + sourceHandleX.value)
const sourceY = computed(() => sourceNode.value?.__rf.position.y + sourceHandleY.value); const sourceY = computed(() => sourceNode.value?.__rf.position.y + sourceHandleY.value)
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2]); const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2]); const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
const isRightOrLeft = computed( const isRightOrLeft = computed(
() => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right () => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right
); )
const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top)); const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top))
let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`); let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`)
if (props.connectionLineType === ConnectionLineType.Bezier) { if (props.connectionLineType === ConnectionLineType.Bezier) {
dAttr = computed(() => dAttr = computed(() =>
@@ -69,7 +69,7 @@ export default defineComponent({
targetY: targetY.value, targetY: targetY.value,
targetPosition: targetPosition.value targetPosition: targetPosition.value
}) })
); )
} else if (props.connectionLineType === ConnectionLineType.Step) { } else if (props.connectionLineType === ConnectionLineType.Step) {
dAttr = computed(() => dAttr = computed(() =>
getSmoothStepPath({ getSmoothStepPath({
@@ -81,7 +81,7 @@ export default defineComponent({
targetPosition: targetPosition.value, targetPosition: targetPosition.value,
borderRadius: 0 borderRadius: 0
}) })
); )
} else if (props.connectionLineType === ConnectionLineType.SmoothStep) { } else if (props.connectionLineType === ConnectionLineType.SmoothStep) {
dAttr = computed(() => dAttr = computed(() =>
getSmoothStepPath({ getSmoothStepPath({
@@ -92,7 +92,7 @@ export default defineComponent({
targetY: targetY.value, targetY: targetY.value,
targetPosition: targetPosition.value targetPosition: targetPosition.value
}) })
); )
} }
if (props.customConnectionLine) { if (props.customConnectionLine) {
@@ -110,7 +110,7 @@ export default defineComponent({
connectionLineStyle: props.connectionLineStyle connectionLineStyle: props.connectionLineStyle
})} })}
</g> </g>
); )
} }
return () => return () =>
@@ -120,6 +120,6 @@ export default defineComponent({
</g> </g>
) : ( ) : (
'' ''
); )
} }
}); })
+29 -29
View File
@@ -1,18 +1,18 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import EdgeText from './EdgeText'; import EdgeText from './EdgeText'
import { getMarkerEnd, getCenter, GetCenterParams, DefaultEdgeProps } from './utils'; import { getMarkerEnd, getCenter, GetCenterParams, DefaultEdgeProps } from './utils'
import { ArrowHeadType, EdgeType, Position } from '../../types'; import { ArrowHeadType, EdgeType, Position } from '../../types'
import { reactify } from '@vueuse/core'; import { reactify } from '@vueuse/core'
interface GetBezierPathParams { interface GetBezierPathParams {
sourceX: number; sourceX: number
sourceY: number; sourceY: number
sourcePosition?: Position; sourcePosition?: Position
targetX: number; targetX: number
targetY: number; targetY: number
targetPosition?: Position; targetPosition?: Position
centerX?: number; centerX?: number
centerY?: number; centerY?: number
} }
export function getBezierPath({ export function getBezierPath({
@@ -25,23 +25,23 @@ export function getBezierPath({
centerX, centerX,
centerY centerY
}: GetBezierPathParams): string { }: GetBezierPathParams): string {
const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }); const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY })
const leftAndRight = [Position.Left, Position.Right]; const leftAndRight = [Position.Left, Position.Right]
const cX = typeof centerX !== 'undefined' ? centerX : _centerX; const cX = typeof centerX !== 'undefined' ? centerX : _centerX
const cY = typeof centerY !== 'undefined' ? centerY : _centerY; const cY = typeof centerY !== 'undefined' ? centerY : _centerY
let path = `M${sourceX},${sourceY} C${sourceX},${cY} ${targetX},${cY} ${targetX},${targetY}`; let path = `M${sourceX},${sourceY} C${sourceX},${cY} ${targetX},${cY} ${targetX},${targetY}`
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
path = `M${sourceX},${sourceY} C${cX},${sourceY} ${cX},${targetY} ${targetX},${targetY}`; path = `M${sourceX},${sourceY} C${cX},${sourceY} ${cX},${targetY} ${targetX},${targetY}`
} else if (leftAndRight.includes(targetPosition)) { } else if (leftAndRight.includes(targetPosition)) {
path = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`; path = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`
} else if (leftAndRight.includes(sourcePosition)) { } else if (leftAndRight.includes(sourcePosition)) {
path = `M${sourceX},${sourceY} C${targetX},${sourceY} ${targetX},${sourceY} ${targetX},${targetY}`; path = `M${sourceX},${sourceY} C${targetX},${sourceY} ${targetX},${sourceY} ${targetX},${targetY}`
} }
return path; return path
} }
export default defineComponent({ export default defineComponent({
@@ -58,8 +58,8 @@ export default defineComponent({
targetY: targetY, targetY: targetY,
sourcePosition: sourcePosition, sourcePosition: sourcePosition,
targetPosition: targetPosition targetPosition: targetPosition
}); })
}); })
const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetBezierPathParams) => { const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetBezierPathParams) => {
return getBezierPath({ return getBezierPath({
sourceX: sourceX, sourceX: sourceX,
@@ -68,10 +68,10 @@ export default defineComponent({
targetY: targetY, targetY: targetY,
targetPosition: targetPosition, targetPosition: targetPosition,
sourcePosition: sourcePosition sourcePosition: sourcePosition
}); })
}); })
const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId)); const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId))
return () => ( return () => (
<> <>
@@ -94,6 +94,6 @@ export default defineComponent({
/> />
) : null} ) : null}
</> </>
); )
} }
}) as EdgeType; }) as EdgeType
+69 -69
View File
@@ -1,24 +1,24 @@
import { computed, defineComponent, h, inject, PropType, ref, watchPostEffect } from 'vue'; import { computed, defineComponent, h, inject, PropType, ref, watchPostEffect } from 'vue'
import EdgeAnchor from './EdgeAnchor'; import EdgeAnchor from './EdgeAnchor'
import { ConnectionMode, Edge, Elements, Node, Position, RevueFlowStore, Transform } from '../../types'; import { ConnectionMode, Edge, Elements, Node, Position, RevueFlowStore, Transform } from '../../types'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../container/EdgeRenderer/utils'; import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../container/EdgeRenderer/utils'
import { isEdge } from '../../utils/graph'; import { isEdge } from '../../utils/graph'
import { onMouseDown } from '../Handle/handler'; import { onMouseDown } from '../Handle/handler'
import { controlledRef } from '@vueuse/core'; import { controlledRef } from '@vueuse/core'
interface EdgeProps { interface EdgeProps {
edge: Edge; edge: Edge
nodes: Node[]; nodes: Node[]
selectedElements: Elements | null; selectedElements: Elements | null
elementsSelectable: boolean; elementsSelectable: boolean
transform: Transform; transform: Transform
width: number; width: number
height: number; height: number
onlyRenderVisibleElements: boolean; onlyRenderVisibleElements: boolean
connectionMode?: ConnectionMode; connectionMode?: ConnectionMode
markerEndId?: string; markerEndId?: string
edgeUpdaterRadius?: number; edgeUpdaterRadius?: number
} }
export default defineComponent({ export default defineComponent({
@@ -54,41 +54,41 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const nodes = controlledRef(getSourceTargetNodes(props.edge, store.nodes), { const nodes = controlledRef(getSourceTargetNodes(props.edge, store.nodes), {
onBeforeChange: (value, oldValue) => { onBeforeChange: (value, oldValue) => {
if (JSON.stringify(value) === JSON.stringify(oldValue)) return false; if (JSON.stringify(value) === JSON.stringify(oldValue)) return false
} }
}); })
watchPostEffect(() => { watchPostEffect(() => {
nodes.value = getSourceTargetNodes(props.edge, store.nodes); nodes.value = getSourceTargetNodes(props.edge, store.nodes)
}); })
hooks.connect.on((connection) => { hooks.connect.on((connection) => {
hooks.edgeUpdate.trigger({ edge: props.edge, connection }); hooks.edgeUpdate.trigger({ edge: props.edge, connection })
}); })
if (!nodes.value.sourceNode) { if (!nodes.value.sourceNode) {
console.warn(`couldn't create edge for source id: ${props.edge.source}; edge id: ${props.edge.id}`); console.warn(`couldn't create edge for source id: ${props.edge.source}; edge id: ${props.edge.id}`)
} }
if (!nodes.value.targetNode) { if (!nodes.value.targetNode) {
console.warn(`couldn't create edge for target id: ${props.edge.target}; edge id: ${props.edge.id}`); console.warn(`couldn't create edge for target id: ${props.edge.target}; edge id: ${props.edge.id}`)
} }
// when connection type is loose we can define all handles as sources // when connection type is loose we can define all handles as sources
const targetNodeHandles = const targetNodeHandles =
props.connectionMode === ConnectionMode.Strict props.connectionMode === ConnectionMode.Strict
? nodes.value.targetNode?.__rf.handleBounds.target ? nodes.value.targetNode?.__rf.handleBounds.target
: nodes.value.targetNode?.__rf.handleBounds.target || nodes.value.targetNode?.__rf.handleBounds.source; : nodes.value.targetNode?.__rf.handleBounds.target || nodes.value.targetNode?.__rf.handleBounds.source
const sourceHandle = const sourceHandle =
nodes.value.sourceNode && getHandle(nodes.value.sourceNode.__rf.handleBounds.source, props.edge.sourceHandle || null); nodes.value.sourceNode && getHandle(nodes.value.sourceNode.__rf.handleBounds.source, props.edge.sourceHandle || null)
const targetHandle = getHandle(targetNodeHandles, props.edge.targetHandle || null); const targetHandle = getHandle(targetNodeHandles, props.edge.targetHandle || null)
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom; const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom
const targetPosition = targetHandle ? targetHandle.position : Position.Top; const targetPosition = targetHandle ? targetHandle.position : Position.Top
const isVisible = ({ sourceX, sourceY, targetX, targetY }: Record<string, number>) => { const isVisible = ({ sourceX, sourceY, targetX, targetY }: Record<string, number>) => {
return props.onlyRenderVisibleElements return props.onlyRenderVisibleElements
@@ -99,10 +99,10 @@ export default defineComponent({
height: store.height || 0, height: store.height || 0,
transform: store.transform transform: store.transform
}) })
: true; : true
}; }
const isSelected = computed(() => store.selectedElements?.some((elm) => isEdge(elm) && elm.id === props.edge.id) || false); const isSelected = computed(() => store.selectedElements?.some((elm) => isEdge(elm) && elm.id === props.edge.id) || false)
const edgeElement = () => { const edgeElement = () => {
const el: Edge = { const el: Edge = {
@@ -110,51 +110,51 @@ export default defineComponent({
source: props.edge.source, source: props.edge.source,
target: props.edge.target, target: props.edge.target,
type: props.edge.type type: props.edge.type
}; }
if (props.edge.sourceHandle) { if (props.edge.sourceHandle) {
el.sourceHandle = props.edge.sourceHandle; el.sourceHandle = props.edge.sourceHandle
} }
if (props.edge.targetHandle) { if (props.edge.targetHandle) {
el.targetHandle = props.edge.targetHandle; el.targetHandle = props.edge.targetHandle
} }
if (typeof props.edge.data !== 'undefined') { if (typeof props.edge.data !== 'undefined') {
el.data = props.edge.data; el.data = props.edge.data
} }
return el; return el
}; }
const onEdgeClick = (event: MouseEvent) => { const onEdgeClick = (event: MouseEvent) => {
if (store.elementsSelectable) { if (store.elementsSelectable) {
store.unsetNodesSelection(); store.unsetNodesSelection()
store.addSelectedElements(edgeElement() as any); store.addSelectedElements(edgeElement() as any)
} }
hooks.edgeClick.trigger({ event, edge: edgeElement() }); hooks.edgeClick.trigger({ event, edge: edgeElement() })
}; }
const onEdgeContextMenu = (event: MouseEvent) => const onEdgeContextMenu = (event: MouseEvent) =>
hooks.edgeContextMenu.trigger({ hooks.edgeContextMenu.trigger({
event, event,
edge: edgeElement() edge: edgeElement()
}); })
const onEdgeMouseEnter = (event: MouseEvent) => hooks.edgeMouseEnter.trigger({ event, edge: edgeElement() }); const onEdgeMouseEnter = (event: MouseEvent) => hooks.edgeMouseEnter.trigger({ event, edge: edgeElement() })
const onEdgeMouseMove = (event: MouseEvent) => hooks.edgeMouseMove.trigger({ event, edge: edgeElement() }); const onEdgeMouseMove = (event: MouseEvent) => hooks.edgeMouseMove.trigger({ event, edge: edgeElement() })
const onEdgeMouseLeave = (event: MouseEvent) => hooks.edgeMouseLeave.trigger({ event, edge: edgeElement() }); const onEdgeMouseLeave = (event: MouseEvent) => hooks.edgeMouseLeave.trigger({ event, edge: edgeElement() })
const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => { const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
const nodeId = isSourceHandle ? props.edge.target : props.edge.source; const nodeId = isSourceHandle ? props.edge.target : props.edge.source
const handleId = isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle; const handleId = isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle
const isValidConnection = () => true; const isValidConnection = () => true
const isTarget = isSourceHandle; const isTarget = isSourceHandle
hooks.edgeUpdateStart.trigger({ event, edge: edgeElement() }); hooks.edgeUpdateStart.trigger({ event, edge: edgeElement() })
onMouseDown( onMouseDown(
event, event,
store, store,
@@ -164,20 +164,20 @@ export default defineComponent({
isTarget, isTarget,
isValidConnection, isValidConnection,
isSourceHandle ? 'target' : 'source' isSourceHandle ? 'target' : 'source'
); )
}; }
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => { const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => {
handleEdgeUpdater(event, true); handleEdgeUpdater(event, true)
}; }
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => { const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => {
handleEdgeUpdater(event, false); handleEdgeUpdater(event, false)
}; }
const updating = ref<boolean>(false); const updating = ref<boolean>(false)
const onEdgeUpdaterMouseEnter = () => (updating.value = true); const onEdgeUpdaterMouseEnter = () => (updating.value = true)
const onEdgeUpdaterMouseOut = () => (updating.value = false); const onEdgeUpdaterMouseOut = () => (updating.value = false)
return () => { return () => {
const { targetX, targetY, sourceX, sourceY } = getEdgePositions( const { targetX, targetY, sourceX, sourceY } = getEdgePositions(
@@ -187,7 +187,7 @@ export default defineComponent({
nodes.value.targetNode as Node, nodes.value.targetNode as Node,
targetHandle, targetHandle,
targetPosition targetPosition
); )
return !props.edge.isHidden && isVisible({ targetX, targetY, sourceX, sourceY }) ? ( return !props.edge.isHidden && isVisible({ targetX, targetY, sourceX, sourceY }) ? (
<g <g
class={[ class={[
@@ -242,7 +242,7 @@ export default defineComponent({
</g> </g>
) : ( ) : (
'' ''
); )
}; }
} }
}); })
+16 -16
View File
@@ -1,23 +1,23 @@
import { Position } from '../../types'; import { Position } from '../../types'
import { defineComponent, HTMLAttributes, PropType } from 'vue'; import { defineComponent, HTMLAttributes, PropType } from 'vue'
const shiftX = (x: number, shift: number, position: Position): number => { const shiftX = (x: number, shift: number, position: Position): number => {
if (position === Position.Left) return x - shift; if (position === Position.Left) return x - shift
if (position === Position.Right) return x + shift; if (position === Position.Right) return x + shift
return x; return x
}; }
const shiftY = (y: number, shift: number, position: Position): number => { const shiftY = (y: number, shift: number, position: Position): number => {
if (position === Position.Top) return y - shift; if (position === Position.Top) return y - shift
if (position === Position.Bottom) return y + shift; if (position === Position.Bottom) return y + shift
return y; return y
}; }
export interface EdgeAnchorProps extends HTMLAttributes { export interface EdgeAnchorProps extends HTMLAttributes {
position: Position; position: Position
centerX: number; centerX: number
centerY: number; centerY: number
radius?: number; radius?: number
} }
export default defineComponent({ export default defineComponent({
@@ -50,6 +50,6 @@ export default defineComponent({
stroke="transparent" stroke="transparent"
fill="transparent" fill="transparent"
/> />
); )
} }
}); })
+9 -9
View File
@@ -1,5 +1,5 @@
import { EdgeTextProps, Rect } from '../../types'; import { EdgeTextProps, Rect } from '../../types'
import { defineComponent, PropType, ref, watchEffect } from 'vue'; import { defineComponent, PropType, ref, watchEffect } from 'vue'
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -37,11 +37,11 @@ export default defineComponent({
} }
}, },
setup(props, { slots }) { setup(props, { slots }) {
const edgeRef = ref<SVGTextElement | null>(null); const edgeRef = ref<SVGTextElement | null>(null)
const edgeTextBox = ref<Rect>({ x: 0, y: 0, width: 0, height: 0 }); const edgeTextBox = ref<Rect>({ x: 0, y: 0, width: 0, height: 0 })
watchEffect(() => { watchEffect(() => {
const textBbox = edgeRef.value?.getBBox(); const textBbox = edgeRef.value?.getBBox()
if (textBbox) { if (textBbox) {
edgeTextBox.value = { edgeTextBox.value = {
@@ -49,9 +49,9 @@ export default defineComponent({
y: textBbox.y, y: textBbox.y,
width: textBbox.width, width: textBbox.width,
height: textBbox.height height: textBbox.height
}; }
} }
}); })
return () => ( return () => (
<g <g
@@ -75,6 +75,6 @@ export default defineComponent({
</text> </text>
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</g> </g>
); )
} }
}); })
+54 -55
View File
@@ -1,33 +1,33 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import EdgeText from './EdgeText'; import EdgeText from './EdgeText'
import { getMarkerEnd, getCenter, EdgeSmoothProps, GetCenterParams } from './utils'; import { getMarkerEnd, getCenter, EdgeSmoothProps, GetCenterParams } from './utils'
import { ArrowHeadType, EdgeType, Position } from '../../types'; import { ArrowHeadType, EdgeType, Position } from '../../types'
import { reactify } from '@vueuse/core'; import { reactify } from '@vueuse/core'
import { getBezierPath } from './BezierEdge'; import { getBezierPath } from './BezierEdge'
// These are some helper methods for drawing the round corners // These are some helper methods for drawing the round corners
// The name indicates the direction of the path. "bottomLeftCorner" goes // The name indicates the direction of the path. "bottomLeftCorner" goes
// from bottom to the left and "leftBottomCorner" goes from left to the bottom. // from bottom to the left and "leftBottomCorner" goes from left to the bottom.
// We have to consider the direction of the paths because of the animated lines. // We have to consider the direction of the paths because of the animated lines.
const bottomLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x + size},${y}`; const bottomLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x + size},${y}`
const leftBottomCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y - size}`; const leftBottomCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y - size}`
const bottomRightCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x - size},${y}`; const bottomRightCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x - size},${y}`
const rightBottomCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y - size}`; const rightBottomCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y - size}`
const leftTopCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}`; const leftTopCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}`
const topLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}`; const topLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}`
const topRightCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}`; const topRightCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}`
const rightTopCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}`; const rightTopCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}`
interface GetSmoothStepPathParams { interface GetSmoothStepPathParams {
sourceX: number; sourceX: number
sourceY: number; sourceY: number
sourcePosition?: Position; sourcePosition?: Position
targetX: number; targetX: number
targetY: number; targetY: number
targetPosition?: Position; targetPosition?: Position
borderRadius?: number; borderRadius?: number
centerX?: number; centerX?: number
centerY?: number; centerY?: number
} }
export function getSmoothStepPath({ export function getSmoothStepPath({
@@ -41,56 +41,55 @@ export function getSmoothStepPath({
centerX, centerX,
centerY centerY
}: GetSmoothStepPathParams): string { }: GetSmoothStepPathParams): string {
const [_centerX, _centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY }); const [_centerX, _centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY })
const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX)); const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX))
const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY)); const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY))
const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY); const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY)
const leftAndRight = [Position.Left, Position.Right]; const leftAndRight = [Position.Left, Position.Right]
const cX = typeof centerX !== 'undefined' ? centerX : _centerX; const cX = typeof centerX !== 'undefined' ? centerX : _centerX
const cY = typeof centerY !== 'undefined' ? centerY : _centerY; const cY = typeof centerY !== 'undefined' ? centerY : _centerY
let firstCornerPath; let firstCornerPath
let secondCornerPath; let secondCornerPath
if (sourceX <= targetX) { if (sourceX <= targetX) {
firstCornerPath = sourceY <= targetY ? bottomLeftCorner(sourceX, cY, cornerSize) : topLeftCorner(sourceX, cY, cornerSize); firstCornerPath = sourceY <= targetY ? bottomLeftCorner(sourceX, cY, cornerSize) : topLeftCorner(sourceX, cY, cornerSize)
secondCornerPath = sourceY <= targetY ? rightTopCorner(targetX, cY, cornerSize) : rightBottomCorner(targetX, cY, cornerSize); secondCornerPath = sourceY <= targetY ? rightTopCorner(targetX, cY, cornerSize) : rightBottomCorner(targetX, cY, cornerSize)
} else { } else {
firstCornerPath = sourceY < targetY ? bottomRightCorner(sourceX, cY, cornerSize) : topRightCorner(sourceX, cY, cornerSize); firstCornerPath = sourceY < targetY ? bottomRightCorner(sourceX, cY, cornerSize) : topRightCorner(sourceX, cY, cornerSize)
secondCornerPath = sourceY < targetY ? leftTopCorner(targetX, cY, cornerSize) : leftBottomCorner(targetX, cY, cornerSize); secondCornerPath = sourceY < targetY ? leftTopCorner(targetX, cY, cornerSize) : leftBottomCorner(targetX, cY, cornerSize)
} }
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
if (sourceX <= targetX) { if (sourceX <= targetX) {
firstCornerPath = sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize); firstCornerPath = sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize)
secondCornerPath = sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize); secondCornerPath = sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize)
} else if (sourcePosition === Position.Right && targetPosition === Position.Left) { } else if (sourcePosition === Position.Right && targetPosition === Position.Left) {
// and sourceX > targetX // and sourceX > targetX
firstCornerPath = sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize); firstCornerPath = sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize)
secondCornerPath = secondCornerPath = sourceY <= targetY ? bottomRightCorner(cX, targetY, cornerSize) : topRightCorner(cX, targetY, cornerSize)
sourceY <= targetY ? bottomRightCorner(cX, targetY, cornerSize) : topRightCorner(cX, targetY, cornerSize);
} }
} else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) { } else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) {
if (sourceX <= targetX) { if (sourceX <= targetX) {
firstCornerPath = firstCornerPath =
sourceY <= targetY ? rightTopCorner(targetX, sourceY, cornerSize) : rightBottomCorner(targetX, sourceY, cornerSize); sourceY <= targetY ? rightTopCorner(targetX, sourceY, cornerSize) : rightBottomCorner(targetX, sourceY, cornerSize)
} else { } else {
firstCornerPath = firstCornerPath =
sourceY <= targetY ? leftTopCorner(targetX, sourceY, cornerSize) : leftBottomCorner(targetX, sourceY, cornerSize); sourceY <= targetY ? leftTopCorner(targetX, sourceY, cornerSize) : leftBottomCorner(targetX, sourceY, cornerSize)
} }
secondCornerPath = ''; secondCornerPath = ''
} else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { } else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
if (sourceX <= targetX) { if (sourceX <= targetX) {
firstCornerPath = firstCornerPath =
sourceY <= targetY ? bottomLeftCorner(sourceX, targetY, cornerSize) : topLeftCorner(sourceX, targetY, cornerSize); sourceY <= targetY ? bottomLeftCorner(sourceX, targetY, cornerSize) : topLeftCorner(sourceX, targetY, cornerSize)
} else { } else {
firstCornerPath = firstCornerPath =
sourceY <= targetY ? bottomRightCorner(sourceX, targetY, cornerSize) : topRightCorner(sourceX, targetY, cornerSize); sourceY <= targetY ? bottomRightCorner(sourceX, targetY, cornerSize) : topRightCorner(sourceX, targetY, cornerSize)
} }
secondCornerPath = ''; secondCornerPath = ''
} }
return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}`; return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}`
} }
export default defineComponent({ export default defineComponent({
@@ -107,8 +106,8 @@ export default defineComponent({
targetY: targetY, targetY: targetY,
sourcePosition: sourcePosition, sourcePosition: sourcePosition,
targetPosition: targetPosition targetPosition: targetPosition
}); })
}); })
const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetSmoothStepPathParams) => { const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetSmoothStepPathParams) => {
return getBezierPath({ return getBezierPath({
sourceX: sourceX, sourceX: sourceX,
@@ -117,10 +116,10 @@ export default defineComponent({
targetY: targetY, targetY: targetY,
targetPosition: targetPosition, targetPosition: targetPosition,
sourcePosition: sourcePosition sourcePosition: sourcePosition
}); })
}); })
const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId)); const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId))
return () => ( return () => (
<> <>
@@ -143,6 +142,6 @@ export default defineComponent({
/> />
) : null} ) : null}
</> </>
); )
} }
}) as unknown as EdgeType; }) as unknown as EdgeType
+6 -6
View File
@@ -1,7 +1,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import SmoothStepEdge from './SmoothStepEdge'; import SmoothStepEdge from './SmoothStepEdge'
import { EdgeSmoothProps } from './utils'; import { EdgeSmoothProps } from './utils'
import { EdgeType } from '../../types'; import { EdgeType } from '../../types'
export default defineComponent({ export default defineComponent({
components: { SmoothStepEdge }, components: { SmoothStepEdge },
@@ -10,6 +10,6 @@ export default defineComponent({
...EdgeSmoothProps ...EdgeSmoothProps
}, },
setup(props) { setup(props) {
return () => <SmoothStepEdge {...props} borderRadius={0} />; return () => <SmoothStepEdge {...props} borderRadius={0} />
} }
}) as EdgeType; }) as EdgeType
+14 -14
View File
@@ -1,8 +1,8 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import EdgeText from './EdgeText'; import EdgeText from './EdgeText'
import { getMarkerEnd, DefaultEdgeProps } from './utils'; import { getMarkerEnd, DefaultEdgeProps } from './utils'
import { reactify } from '@vueuse/core'; import { reactify } from '@vueuse/core'
import { ArrowHeadType, EdgeType } from '../../types'; import { ArrowHeadType, EdgeType } from '../../types'
export default defineComponent({ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
@@ -11,15 +11,15 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const centerY = reactify((targetY: number, sourceY: number) => { const centerY = reactify((targetY: number, sourceY: number) => {
const yOffset = Math.abs(targetY - sourceY) / 2; const yOffset = Math.abs(targetY - sourceY) / 2
return targetY < sourceY ? targetY + yOffset : targetY - yOffset; return targetY < sourceY ? targetY + yOffset : targetY - yOffset
}); })
const centerX = reactify((targetX: number, sourceX: number) => { const centerX = reactify((targetX: number, sourceX: number) => {
const xOffset = Math.abs(targetX - sourceX) / 2; const xOffset = Math.abs(targetX - sourceX) / 2
return targetX < sourceX ? targetX + xOffset : targetX - xOffset; return targetX < sourceX ? targetX + xOffset : targetX - xOffset
}); })
const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId)); const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId))
return () => ( return () => (
<> <>
@@ -42,6 +42,6 @@ export default defineComponent({
/> />
) : null} ) : null}
</> </>
); )
} }
}) as unknown as EdgeType; }) as unknown as EdgeType
+4 -4
View File
@@ -1,4 +1,4 @@
export { default as BezierEdge } from './BezierEdge'; export { default as BezierEdge } from './BezierEdge'
export { default as StepEdge } from './StepEdge'; export { default as StepEdge } from './StepEdge'
export { default as SmoothStepEdge } from './SmoothStepEdge'; export { default as SmoothStepEdge } from './SmoothStepEdge'
export { default as StraightEdge } from './StraightEdge'; export { default as StraightEdge } from './StraightEdge'
+30 -30
View File
@@ -1,24 +1,24 @@
import { ArrowHeadType, Position, EdgeProps, EdgeSmoothStepProps } from '../../types'; import { ArrowHeadType, Position, EdgeProps, EdgeSmoothStepProps } from '../../types'
import { PropType } from 'vue'; import { PropType } from 'vue'
export const getMarkerEnd = (arrowHeadType?: ArrowHeadType, markerEndId?: string): string => { export const getMarkerEnd = (arrowHeadType?: ArrowHeadType, markerEndId?: string): string => {
if (typeof markerEndId !== 'undefined' && markerEndId) { if (typeof markerEndId !== 'undefined' && markerEndId) {
return `url(#${markerEndId})`; return `url(#${markerEndId})`
} }
return typeof arrowHeadType !== 'undefined' ? `url(#revue-flow__${arrowHeadType})` : 'none'; return typeof arrowHeadType !== 'undefined' ? `url(#revue-flow__${arrowHeadType})` : 'none'
};
export interface GetCenterParams {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
sourcePosition?: Position;
targetPosition?: Position;
} }
const LeftOrRight = [Position.Left, Position.Right]; export interface GetCenterParams {
sourceX: number
sourceY: number
targetX: number
targetY: number
sourcePosition?: Position
targetPosition?: Position
}
const LeftOrRight = [Position.Left, Position.Right]
export const getCenter = ({ export const getCenter = ({
sourceX, sourceX,
@@ -28,31 +28,31 @@ export const getCenter = ({
sourcePosition = Position.Bottom, sourcePosition = Position.Bottom,
targetPosition = Position.Top targetPosition = Position.Top
}: GetCenterParams): [number, number, number, number] => { }: GetCenterParams): [number, number, number, number] => {
const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition); const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition)
const targetIsLeftOrRight = LeftOrRight.includes(targetPosition); const targetIsLeftOrRight = LeftOrRight.includes(targetPosition)
// we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom) // we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom)
// a mixed edge is when one the source is on the left and the target is on the top for example. // a mixed edge is when one the source is on the left and the target is on the top for example.
const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight); const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight)
if (mixedEdge) { if (mixedEdge) {
const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0; const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0
const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset; const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset
const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY); const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY)
const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset; const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset
return [centerX, centerY, xOffset, yOffset]; return [centerX, centerY, xOffset, yOffset]
} }
const xOffset = Math.abs(targetX - sourceX) / 2; const xOffset = Math.abs(targetX - sourceX) / 2
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset
const yOffset = Math.abs(targetY - sourceY) / 2; const yOffset = Math.abs(targetY - sourceY) / 2
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset
return [centerX, centerY, xOffset, yOffset]; return [centerX, centerY, xOffset, yOffset]
}; }
export const DefaultEdgeProps = { export const DefaultEdgeProps = {
id: { id: {
@@ -165,7 +165,7 @@ export const DefaultEdgeProps = {
required: false, required: false,
default: undefined default: undefined
} }
}; }
export const EdgeSmoothProps = { export const EdgeSmoothProps = {
...DefaultEdgeProps, ...DefaultEdgeProps,
@@ -174,4 +174,4 @@ export const EdgeSmoothProps = {
required: false, required: false,
default: 5 default: 5
} }
}; }
+61 -61
View File
@@ -1,15 +1,15 @@
import { getHostForElement } from '../../utils'; import { getHostForElement } from '../../utils'
import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '../../types'; import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '../../types'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
export type ValidConnectionFunc = (connection: Connection) => boolean; export type ValidConnectionFunc = (connection: Connection) => boolean
type Result = { type Result = {
elementBelow: Element | null; elementBelow: Element | null
isValid: boolean; isValid: boolean
connection: Connection; connection: Connection
isHoveringHandle: boolean; isHoveringHandle: boolean
}; }
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 } // checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
function checkElementBelowIsValid( function checkElementBelowIsValid(
@@ -21,27 +21,27 @@ function checkElementBelowIsValid(
isValidConnection: ValidConnectionFunc, isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot doc: Document | ShadowRoot
) { ) {
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY); const elementBelow = doc.elementFromPoint(event.clientX, event.clientY)
const elementBelowIsTarget = elementBelow?.classList.contains('target') || false; const elementBelowIsTarget = elementBelow?.classList.contains('target') || false
const elementBelowIsSource = elementBelow?.classList.contains('source') || false; const elementBelowIsSource = elementBelow?.classList.contains('source') || false
const result: Result = { const result: Result = {
elementBelow, elementBelow,
isValid: false, isValid: false,
connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, connection: { source: null, target: null, sourceHandle: null, targetHandle: null },
isHoveringHandle: false isHoveringHandle: false
}; }
if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) { if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) {
result.isHoveringHandle = true; result.isHoveringHandle = true
// in strict mode we don't allow target to target or source to source connections // in strict mode we don't allow target to target or source to source connections
const isValid = const isValid =
connectionMode === ConnectionMode.Strict ? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget) : true; connectionMode === ConnectionMode.Strict ? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget) : true
if (isValid) { if (isValid) {
const elementBelowNodeId = elementBelow.getAttribute('data-nodeid'); const elementBelowNodeId = elementBelow.getAttribute('data-nodeid')
const elementBelowHandleId = elementBelow.getAttribute('data-handleid'); const elementBelowHandleId = elementBelow.getAttribute('data-handleid')
const connection: Connection = isTarget const connection: Connection = isTarget
? { ? {
source: elementBelowNodeId, source: elementBelowNodeId,
@@ -54,30 +54,30 @@ function checkElementBelowIsValid(
sourceHandle: handleId, sourceHandle: handleId,
target: elementBelowNodeId, target: elementBelowNodeId,
targetHandle: elementBelowHandleId targetHandle: elementBelowHandleId
}; }
result.connection = connection; result.connection = connection
result.isValid = isValidConnection(connection); result.isValid = isValidConnection(connection)
} }
} }
return result; return result
} }
function resetRecentHandle(hoveredHandle: Element): void { function resetRecentHandle(hoveredHandle: Element): void {
hoveredHandle?.classList.remove('revue-flow__handle-valid'); hoveredHandle?.classList.remove('revue-flow__handle-valid')
hoveredHandle?.classList.remove('revue-flow__handle-connecting'); hoveredHandle?.classList.remove('revue-flow__handle-connecting')
} }
export function onMouseDown( export function onMouseDown(
event: MouseEvent, event: MouseEvent,
store: RevueFlowStore, store: RevueFlowStore,
hooks: { hooks: {
connectStart: RevueFlowHooks['connectStart']; connectStart: RevueFlowHooks['connectStart']
connectStop: RevueFlowHooks['connectStop']; connectStop: RevueFlowHooks['connectStop']
connectEnd: RevueFlowHooks['connectEnd']; connectEnd: RevueFlowHooks['connectEnd']
connect: RevueFlowHooks['connect']; connect: RevueFlowHooks['connect']
edgeUpdateEnd: RevueFlowHooks['edgeUpdateEnd']; edgeUpdateEnd: RevueFlowHooks['edgeUpdateEnd']
}, },
handleId: ElementId | null, handleId: ElementId | null,
nodeId: ElementId, nodeId: ElementId,
@@ -85,39 +85,39 @@ export function onMouseDown(
isValidConnection: ValidConnectionFunc, isValidConnection: ValidConnectionFunc,
elementEdgeUpdaterType?: HandleType elementEdgeUpdaterType?: HandleType
): void { ): void {
const revueFlowNode = (event.target as Element).closest('.revue-flow'); const revueFlowNode = (event.target as Element).closest('.revue-flow')
// when revue-flow is used inside a shadow root we can't use document // when revue-flow is used inside a shadow root we can't use document
const doc = getHostForElement(event.target as HTMLElement); const doc = getHostForElement(event.target as HTMLElement)
if (!doc) { if (!doc) {
return; return
} }
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY); const elementBelow = doc.elementFromPoint(event.clientX, event.clientY)
const elementBelowIsTarget = elementBelow?.classList.contains('target'); const elementBelowIsTarget = elementBelow?.classList.contains('target')
const elementBelowIsSource = elementBelow?.classList.contains('source'); const elementBelowIsSource = elementBelow?.classList.contains('source')
if (!revueFlowNode || (!elementBelowIsTarget && !elementBelowIsSource && !elementEdgeUpdaterType)) { if (!revueFlowNode || (!elementBelowIsTarget && !elementBelowIsSource && !elementEdgeUpdaterType)) {
return; return
} }
const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source'; const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source'
const containerBounds = revueFlowNode.getBoundingClientRect(); const containerBounds = revueFlowNode.getBoundingClientRect()
let recentHoveredHandle: Element; let recentHoveredHandle: Element
store.connectionPosition.x = event.clientX - containerBounds.left; store.connectionPosition.x = event.clientX - containerBounds.left
store.connectionPosition.y = event.clientY - containerBounds.top; store.connectionPosition.y = event.clientY - containerBounds.top
store.setConnectionNodeId({ store.setConnectionNodeId({
connectionNodeId: nodeId, connectionNodeId: nodeId,
connectionHandleId: handleId, connectionHandleId: handleId,
connectionHandleType: handleType connectionHandleType: handleType
}); })
hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } }); hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } })
function onMouseMove(event: MouseEvent) { function onMouseMove(event: MouseEvent) {
store.connectionPosition.x = event.clientX - containerBounds.left; store.connectionPosition.x = event.clientX - containerBounds.left
store.connectionPosition.y = event.clientY - containerBounds.top; store.connectionPosition.y = event.clientY - containerBounds.top
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid( const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
event, event,
@@ -127,18 +127,18 @@ export function onMouseDown(
handleId, handleId,
isValidConnection, isValidConnection,
doc doc
); )
if (!isHoveringHandle) { if (!isHoveringHandle) {
return resetRecentHandle(recentHoveredHandle); return resetRecentHandle(recentHoveredHandle)
} }
const isOwnHandle = connection.source === connection.target; const isOwnHandle = connection.source === connection.target
if (!isOwnHandle && elementBelow) { if (!isOwnHandle && elementBelow) {
recentHoveredHandle = elementBelow; recentHoveredHandle = elementBelow
elementBelow.classList.add('revue-flow__handle-connecting'); elementBelow.classList.add('revue-flow__handle-connecting')
elementBelow.classList.toggle('revue-flow__handle-valid', isValid); elementBelow.classList.toggle('revue-flow__handle-valid', isValid)
} }
} }
@@ -151,27 +151,27 @@ export function onMouseDown(
handleId, handleId,
isValidConnection, isValidConnection,
doc doc
); )
hooks.connectStop.trigger(event); hooks.connectStop.trigger(event)
if (isValid) { if (isValid) {
hooks.connect.trigger(connection); hooks.connect.trigger(connection)
} }
hooks.connectEnd.trigger(event); hooks.connectEnd.trigger(event)
if (elementEdgeUpdaterType) { if (elementEdgeUpdaterType) {
hooks.edgeUpdateEnd.trigger({ event } as any); hooks.edgeUpdateEnd.trigger({ event } as any)
} }
resetRecentHandle(recentHoveredHandle); resetRecentHandle(recentHoveredHandle)
store.setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null }); store.setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null })
doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject); doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject)
doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject); doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject)
} }
doc.addEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject); doc.addEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject)
doc.addEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject); doc.addEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject)
} }
+11 -11
View File
@@ -1,9 +1,9 @@
import { ElementId, Position, RevueFlowStore } from '../../types'; import { ElementId, Position, RevueFlowStore } from '../../types'
import { onMouseDown, ValidConnectionFunc } from './handler'; import { onMouseDown, ValidConnectionFunc } from './handler'
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, inject, PropType } from 'vue'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
const alwaysValid = () => true; const alwaysValid = () => true
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -34,12 +34,12 @@ export default defineComponent({
} }
}, },
setup(props, { slots }) { setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const nodeId = inject<ElementId>('NodeIdContext')!; const nodeId = inject<ElementId>('NodeIdContext')!
const onMouseDownHandler = (event: MouseEvent) => const onMouseDownHandler = (event: MouseEvent) =>
onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection); onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection)
return () => ( return () => (
<div <div
@@ -60,6 +60,6 @@ export default defineComponent({
> >
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</div> </div>
); )
} }
}); })
+5 -5
View File
@@ -1,6 +1,6 @@
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue'
import Handle from '../../components/Handle'; import Handle from '../../components/Handle'
import { NodeProps, NodeType, Position } from '../../types'; import { NodeProps, NodeType, Position } from '../../types'
export default defineComponent({ export default defineComponent({
name: 'DefaultNode', name: 'DefaultNode',
@@ -35,6 +35,6 @@ export default defineComponent({
{props.data?.label} {props.data?.label}
<Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} /> <Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} />
</> </>
); )
} }
}) as NodeType; }) as NodeType
+5 -5
View File
@@ -1,6 +1,6 @@
import Handle from '../../components/Handle'; import Handle from '../../components/Handle'
import { NodeProps, NodeType, Position } from '../../types'; import { NodeProps, NodeType, Position } from '../../types'
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'InputNode', name: 'InputNode',
@@ -29,6 +29,6 @@ export default defineComponent({
{props.data?.label} {props.data?.label}
<Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} /> <Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} />
</> </>
); )
} }
}) as NodeType; }) as NodeType
+61 -61
View File
@@ -1,14 +1,14 @@
import { computed, defineComponent, h, inject, onMounted, PropType, provide } from 'vue'; import { computed, defineComponent, h, inject, onMounted, PropType, provide } from 'vue'
import { templateRef, useResizeObserver } from '@vueuse/core'; import { templateRef, useResizeObserver } from '@vueuse/core'
import { DraggableCore, DraggableEventListener } from '@braks/revue-draggable'; import { DraggableCore, DraggableEventListener } from '@braks/revue-draggable'
import { Node, NodeDimensionUpdate, NodeType, RevueFlowStore } from '../../types'; import { Node, NodeDimensionUpdate, NodeType, RevueFlowStore } from '../../types'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
interface NodeProps { interface NodeProps {
nodeTypes: Record<string, NodeType>; nodeTypes: Record<string, NodeType>
snapToGrid: boolean; snapToGrid: boolean
snapGrid: [number, number]; snapGrid: [number, number]
selectNodesOnDrag: boolean; selectNodesOnDrag: boolean
} }
export default defineComponent({ export default defineComponent({
@@ -39,22 +39,22 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
provide('NodeIdContext', props.node.id); provide('NodeIdContext', props.node.id)
const nodeElement = templateRef<HTMLDivElement>('nodeElement', null); const nodeElement = templateRef<HTMLDivElement>('nodeElement', null)
const selected = computed(() => store.selectedElements?.some(({ id }) => id === props.node.id) || false); const selected = computed(() => store.selectedElements?.some(({ id }) => id === props.node.id) || false)
const isDraggable = computed( const isDraggable = computed(
() => props.node.draggable || (store.nodesDraggable && typeof props.node.draggable === 'undefined') () => props.node.draggable || (store.nodesDraggable && typeof props.node.draggable === 'undefined')
); )
const isSelectable = computed( const isSelectable = computed(
() => props.node.selectable || (store.elementsSelectable && typeof props.node.selectable === 'undefined') () => props.node.selectable || (store.elementsSelectable && typeof props.node.selectable === 'undefined')
); )
const isConnectable = computed( const isConnectable = computed(
() => props.node.connectable || (store.nodesConnectable && typeof props.node.connectable === 'undefined') () => props.node.connectable || (store.nodesConnectable && typeof props.node.connectable === 'undefined')
); )
const node = () => const node = () =>
({ ({
@@ -62,72 +62,72 @@ export default defineComponent({
type: props.node.type, type: props.node.type,
position: { x: props.node.__rf.position.x, y: props.node.__rf.position.y }, position: { x: props.node.__rf.position.x, y: props.node.__rf.position.y },
data: props.node.data data: props.node.data
} as Node); } as Node)
const onMouseEnterHandler = () => { const onMouseEnterHandler = () => {
if (props.node.__rf.isDragging) { if (props.node.__rf.isDragging) {
return; return
} }
return (event: MouseEvent) => hooks.nodeMouseEnter.trigger({ event, node: node() }); return (event: MouseEvent) => hooks.nodeMouseEnter.trigger({ event, node: node() })
}; }
const onMouseMoveHandler = () => { const onMouseMoveHandler = () => {
if (props.node.__rf.isDragging) { if (props.node.__rf.isDragging) {
return; return
} }
return (event: MouseEvent) => hooks.nodeMouseMove.trigger({ event, node: node() }); return (event: MouseEvent) => hooks.nodeMouseMove.trigger({ event, node: node() })
}; }
const onMouseLeaveHandler = () => { const onMouseLeaveHandler = () => {
if (props.node.__rf.isDragging) { if (props.node.__rf.isDragging) {
return; return
} }
return (event: MouseEvent) => hooks.nodeMouseLeave.trigger({ event, node: node() }); return (event: MouseEvent) => hooks.nodeMouseLeave.trigger({ event, node: node() })
}; }
const onContextMenuHandler = () => { const onContextMenuHandler = () => {
return (event: MouseEvent) => hooks.nodeContextMenu.trigger({ event, node: node() }); return (event: MouseEvent) => hooks.nodeContextMenu.trigger({ event, node: node() })
}; }
const onSelectNodeHandler = (event: MouseEvent) => { const onSelectNodeHandler = (event: MouseEvent) => {
if (!isDraggable.value) { if (!isDraggable.value) {
const n = node(); const n = node()
if (isSelectable.value) { if (isSelectable.value) {
store.unsetNodesSelection(); store.unsetNodesSelection()
if (!selected.value) { if (!selected.value) {
store.addSelectedElements([n]); store.addSelectedElements([n])
} }
} }
hooks.nodeClick.trigger({ event, node: n }); hooks.nodeClick.trigger({ event, node: n })
} }
}; }
const onDragStart: DraggableEventListener = ({ event }) => { const onDragStart: DraggableEventListener = ({ event }) => {
const n = node(); const n = node()
hooks.nodeDragStart.trigger({ event, node: n }); hooks.nodeDragStart.trigger({ event, node: n })
if (props.selectNodesOnDrag && isSelectable.value) { if (props.selectNodesOnDrag && isSelectable.value) {
store.unsetNodesSelection(); store.unsetNodesSelection()
if (!selected.value) { if (!selected.value) {
store.addSelectedElements([n]); store.addSelectedElements([n])
} }
} else if (!props.selectNodesOnDrag && !selected.value && isSelectable.value) { } else if (!props.selectNodesOnDrag && !selected.value && isSelectable.value) {
store.unsetNodesSelection(); store.unsetNodesSelection()
store.addSelectedElements([]); store.addSelectedElements([])
} }
}; }
const onDrag: DraggableEventListener = ({ event, data }) => { const onDrag: DraggableEventListener = ({ event, data }) => {
const n = node(); const n = node()
n.position.x += data.deltaX; n.position.x += data.deltaX
n.position.y += data.deltaY; n.position.y += data.deltaY
hooks.nodeDrag.trigger({ event, node: n }); hooks.nodeDrag.trigger({ event, node: n })
store?.updateNodePosDiff({ store?.updateNodePosDiff({
id: props.node.id as string, id: props.node.id as string,
@@ -136,39 +136,39 @@ export default defineComponent({
y: data.deltaY y: data.deltaY
}, },
isDragging: true isDragging: true
}); })
}; }
const onDragStop: DraggableEventListener = ({ event }) => { const onDragStop: DraggableEventListener = ({ event }) => {
const n = node(); const n = node()
// onDragStop also gets called when user just clicks on a node. // onDragStop also gets called when user just clicks on a node.
// Because of that we set dragging to true inside the onDrag handler and handle the click here // Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!props.node.__rf.isDragging) { if (!props.node.__rf.isDragging) {
if (isSelectable.value && !props.selectNodesOnDrag && !selected.value) { if (isSelectable.value && !props.selectNodesOnDrag && !selected.value) {
store.addSelectedElements([n]); store.addSelectedElements([n])
} }
hooks.nodeClick.trigger({ event, node: n }); hooks.nodeClick.trigger({ event, node: n })
return; return
} }
store.updateNodePosDiff({ store.updateNodePosDiff({
id: n.id || '', id: n.id || '',
isDragging: false isDragging: false
}); })
hooks.nodeDragStop.trigger({ event, node: n }); hooks.nodeDragStop.trigger({ event, node: n })
}; }
useResizeObserver(nodeElement, (entries) => { useResizeObserver(nodeElement, (entries) => {
const updates: NodeDimensionUpdate[] = entries.map((entry) => ({ const updates: NodeDimensionUpdate[] = entries.map((entry) => ({
id: entry.target.getAttribute('data-id') || '', id: entry.target.getAttribute('data-id') || '',
nodeElement: entry.target as HTMLDivElement nodeElement: entry.target as HTMLDivElement
})); }))
store.updateNodeDimensions(updates); store.updateNodeDimensions(updates)
}); })
onMounted(() => { onMounted(() => {
store.updateNodeDimensions([ store.updateNodeDimensions([
@@ -177,8 +177,8 @@ export default defineComponent({
nodeElement: nodeElement.value, nodeElement: nodeElement.value,
forceUpdate: true forceUpdate: true
} }
]); ])
}); })
return () => return () =>
props.node.isHidden ? ( props.node.isHidden ? (
@@ -231,6 +231,6 @@ export default defineComponent({
})} })}
</div> </div>
</DraggableCore> </DraggableCore>
); )
} }
}); })
+5 -5
View File
@@ -1,6 +1,6 @@
import Handle from '../../components/Handle'; import Handle from '../../components/Handle'
import { NodeProps, NodeType, Position } from '../../types'; import { NodeProps, NodeType, Position } from '../../types'
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'OutputNode', name: 'OutputNode',
@@ -29,6 +29,6 @@ export default defineComponent({
{props.data?.label} {props.data?.label}
<Handle type="source" position={props.targetPosition} isConnectable={props.isConnectable} /> <Handle type="source" position={props.targetPosition} isConnectable={props.isConnectable} />
</> </>
); )
} }
}) as NodeType; }) as NodeType
+15 -15
View File
@@ -1,14 +1,14 @@
import { HandleElement, Position } from '../../types'; import { HandleElement, Position } from '../../types'
import { getDimensions } from '../../utils'; import { getDimensions } from '../../utils'
export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => { export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
const bounds = nodeElement.getBoundingClientRect(); const bounds = nodeElement.getBoundingClientRect()
return { return {
source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale), source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale),
target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale) target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale)
}; }
}; }
export const getHandleBoundsByHandleType = ( export const getHandleBoundsByHandleType = (
selector: string, selector: string,
@@ -16,19 +16,19 @@ export const getHandleBoundsByHandleType = (
parentBounds: ClientRect | DOMRect, parentBounds: ClientRect | DOMRect,
k: number k: number
): HandleElement[] | null => { ): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector); const handles = nodeElement.querySelectorAll(selector)
if (!handles || !handles.length) { if (!handles || !handles.length) {
return null; return null
} }
const handlesArray = Array.from(handles) as HTMLDivElement[]; const handlesArray = Array.from(handles) as HTMLDivElement[]
return handlesArray.map((handle): HandleElement => { return handlesArray.map((handle): HandleElement => {
const bounds = handle.getBoundingClientRect(); const bounds = handle.getBoundingClientRect()
const dimensions = getDimensions(handle); const dimensions = getDimensions(handle)
const handleId = handle.getAttribute('data-handleid'); const handleId = handle.getAttribute('data-handleid')
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position; const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position
return { return {
id: handleId, id: handleId,
@@ -36,6 +36,6 @@ export const getHandleBoundsByHandleType = (
x: (bounds.left - parentBounds.left) / k, x: (bounds.left - parentBounds.left) / k,
y: (bounds.top - parentBounds.top) / k, y: (bounds.top - parentBounds.top) / k,
...dimensions ...dimensions
}; }
}); })
}; }
+33 -33
View File
@@ -3,56 +3,56 @@
* The nodes selection rectangle gets displayed when a user * The nodes selection rectangle gets displayed when a user
* made a selectio with on or several nodes * made a selectio with on or several nodes
*/ */
import { computed, defineComponent, inject } from 'vue'; import { computed, defineComponent, inject } from 'vue'
import { Draggable, DraggableEventListener } from '@braks/revue-draggable'; import { Draggable, DraggableEventListener } from '@braks/revue-draggable'
import { isNode } from '../../utils/graph'; import { isNode } from '../../utils/graph'
import { Node, RevueFlowStore } from '../../types'; import { Node, RevueFlowStore } from '../../types'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
export interface NodesSelectionProps { export interface NodesSelectionProps {
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void; onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void
onSelectionDrag?: (event: MouseEvent, nodes: Node[]) => void; onSelectionDrag?: (event: MouseEvent, nodes: Node[]) => void
onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void; onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void; onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void
} }
const NodesSelection = defineComponent({ const NodesSelection = defineComponent({
setup() { setup() {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const grid = computed(() => (store.snapToGrid ? store.snapGrid : [1, 1])! as [number, number]); const grid = computed(() => (store.snapToGrid ? store.snapGrid : [1, 1])! as [number, number])
const transform = computed(() => store.transform); const transform = computed(() => store.transform)
const selectedNodes = computed(() => const selectedNodes = computed(() =>
store.selectedElements store.selectedElements
? store.selectedElements.filter(isNode).map((selectedNode) => { ? store.selectedElements.filter(isNode).map((selectedNode) => {
const matchingNode = store.nodes.find((node) => node.id === selectedNode.id); const matchingNode = store.nodes.find((node) => node.id === selectedNode.id)
return { return {
...matchingNode, ...matchingNode,
position: matchingNode?.__rf.position position: matchingNode?.__rf.position
} as Node; } as Node
}) })
: [] : []
); )
const style = computed(() => ({ const style = computed(() => ({
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})` transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
})); }))
const innerStyle = computed(() => ({ const innerStyle = computed(() => ({
width: `${store.selectedNodesBbox.width}px`, width: `${store.selectedNodesBbox.width}px`,
height: `${store.selectedNodesBbox.height}px`, height: `${store.selectedNodesBbox.height}px`,
top: `${store.selectedNodesBbox.y}px`, top: `${store.selectedNodesBbox.y}px`,
left: `${store.selectedNodesBbox.x}px` left: `${store.selectedNodesBbox.x}px`
})); }))
const onStart: DraggableEventListener = ({ event }) => { const onStart: DraggableEventListener = ({ event }) => {
hooks.selectionDragStart.trigger({ event, nodes: selectedNodes.value }); hooks.selectionDragStart.trigger({ event, nodes: selectedNodes.value })
}; }
const onDrag: DraggableEventListener = ({ event, data }) => { const onDrag: DraggableEventListener = ({ event, data }) => {
hooks.selectionDrag.trigger({ event, nodes: selectedNodes.value }); hooks.selectionDrag.trigger({ event, nodes: selectedNodes.value })
store.updateNodePosDiff({ store.updateNodePosDiff({
diff: { diff: {
@@ -60,24 +60,24 @@ const NodesSelection = defineComponent({
y: data.deltaY y: data.deltaY
}, },
isDragging: true isDragging: true
}); })
}; }
const onStop: DraggableEventListener = ({ event }) => { const onStop: DraggableEventListener = ({ event }) => {
store.updateNodePosDiff({ store.updateNodePosDiff({
isDragging: false isDragging: false
}); })
hooks.selectionDragStop.trigger({ event, nodes: selectedNodes.value }); hooks.selectionDragStop.trigger({ event, nodes: selectedNodes.value })
}; }
const onContextMenu = (event: MouseEvent) => { const onContextMenu = (event: MouseEvent) => {
const selectedNodes: Node[] = store.selectedElements const selectedNodes: Node[] = store.selectedElements
? store.selectedElements.filter(isNode).map((selectedNode) => store.nodes.find((node) => node.id === selectedNode.id)) ? store.selectedElements.filter(isNode).map((selectedNode) => store.nodes.find((node) => node.id === selectedNode.id))
: []; : []
hooks.selectionContextMenu.trigger({ event, nodes: selectedNodes }); hooks.selectionContextMenu.trigger({ event, nodes: selectedNodes })
}; }
return () => { return () => {
return !store.selectedElements || store.selectionActive ? ( return !store.selectedElements || store.selectionActive ? (
@@ -95,9 +95,9 @@ const NodesSelection = defineComponent({
<div class="revue-flow__nodesselection-rect" onContextmenu={onContextMenu} style={innerStyle.value} /> <div class="revue-flow__nodesselection-rect" onContextmenu={onContextMenu} style={innerStyle.value} />
</Draggable> </Draggable>
</div> </div>
); )
}; }
} }
}); })
export default NodesSelection; export default NodesSelection
+33 -33
View File
@@ -1,22 +1,22 @@
/** /**
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift * The user selection rectangle gets displayed when a user drags the mouse while pressing shift
*/ */
import { RevueFlowStore, XYPosition } from '../../types'; import { RevueFlowStore, XYPosition } from '../../types'
import { computed, defineComponent, inject } from 'vue'; import { computed, defineComponent, inject } from 'vue'
import { templateRef, useEventListener } from '@vueuse/core'; import { templateRef, useEventListener } from '@vueuse/core'
function getMousePosition(event: MouseEvent): XYPosition | void { function getMousePosition(event: MouseEvent): XYPosition | void {
const revueFlowNode = (event.target as Element).closest('.revue-flow'); const revueFlowNode = (event.target as Element).closest('.revue-flow')
if (!revueFlowNode) { if (!revueFlowNode) {
return; return
} }
const containerBounds = revueFlowNode.getBoundingClientRect(); const containerBounds = revueFlowNode.getBoundingClientRect()
return { return {
x: event.clientX - containerBounds.left, x: event.clientX - containerBounds.left,
y: event.clientY - containerBounds.top y: event.clientY - containerBounds.top
}; }
} }
const SelectionRect = (props: { width: number; height: number; x: number; y: number }) => { const SelectionRect = (props: { width: number; height: number; x: number; y: number }) => {
@@ -29,51 +29,51 @@ const SelectionRect = (props: { width: number; height: number; x: number; y: num
transform: `translate(${props.x}px, ${props.y}px)` transform: `translate(${props.x}px, ${props.y}px)`
}} }}
/> />
); )
}; }
export default defineComponent({ export default defineComponent({
components: { SelectionRect }, components: { SelectionRect },
setup() { setup() {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const el = templateRef('user-selection', null); const el = templateRef('user-selection', null)
const shouldRender = computed(() => store.selectionActive || store.elementsSelectable); const shouldRender = computed(() => store.selectionActive || store.elementsSelectable)
const onMouseDown = (event: MouseEvent) => { const onMouseDown = (event: MouseEvent) => {
const mousePos = getMousePosition(event); const mousePos = getMousePosition(event)
if (!mousePos) { if (!mousePos) {
return; return
} }
store.setUserSelection(mousePos); store.setUserSelection(mousePos)
}; }
const onMouseMove = (event: MouseEvent) => { const onMouseMove = (event: MouseEvent) => {
if (!store.selectionActive) { if (!store.selectionActive) {
return; return
} }
const mousePos = getMousePosition(event); const mousePos = getMousePosition(event)
if (!mousePos) { if (!mousePos) {
return; return
} }
store.updateUserSelection(mousePos); store.updateUserSelection(mousePos)
}; }
const onMouseUp = () => { const onMouseUp = () => {
store.unsetUserSelection(); store.unsetUserSelection()
}; }
const onMouseLeave = () => { const onMouseLeave = () => {
store.unsetUserSelection(); store.unsetUserSelection()
store.unsetNodesSelection(); store.unsetNodesSelection()
}; }
useEventListener(el, 'mousedown', onMouseDown); useEventListener(el, 'mousedown', onMouseDown)
useEventListener(el, 'mousemove', onMouseMove); useEventListener(el, 'mousemove', onMouseMove)
useEventListener(el, 'click', onMouseUp); useEventListener(el, 'click', onMouseUp)
useEventListener(el, 'mouseup', onMouseUp); useEventListener(el, 'mouseup', onMouseUp)
useEventListener(el, 'mouseleave', onMouseLeave); useEventListener(el, 'mouseleave', onMouseLeave)
return () => return () =>
shouldRender.value ? ( shouldRender.value ? (
@@ -91,6 +91,6 @@ export default defineComponent({
</div> </div>
) : ( ) : (
'' ''
); )
} }
}); })
+4 -4
View File
@@ -1,7 +1,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
interface MarkerProps { interface MarkerProps {
id: string; id: string
} }
export default defineComponent({ export default defineComponent({
@@ -25,6 +25,6 @@ export default defineComponent({
> >
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</marker> </marker>
); )
} }
}); })
@@ -1,8 +1,8 @@
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue'
import Marker from './Marker'; import Marker from './Marker'
interface MarkerDefinitionsProps { interface MarkerDefinitionsProps {
color: string; color: string
} }
export default defineComponent({ export default defineComponent({
@@ -38,6 +38,6 @@ export default defineComponent({
/> />
</Marker> </Marker>
</defs> </defs>
); )
} }
}); })
+17 -17
View File
@@ -1,19 +1,19 @@
import { CSSProperties, defineComponent, inject, PropType } from 'vue'; import { CSSProperties, defineComponent, inject, PropType } from 'vue'
import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types'; import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types'
import ConnectionLine from '../../components/ConnectionLine'; import ConnectionLine from '../../components/ConnectionLine'
import MarkerDefinitions from './MarkerDefinitions'; import MarkerDefinitions from './MarkerDefinitions'
import Edge from '../../components/Edges/Edge'; import Edge from '../../components/Edges/Edge'
interface EdgeRendererProps { interface EdgeRendererProps {
edgeTypes: any; edgeTypes: any
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties
connectionLineComponent?: ConnectionLineComponent; connectionLineComponent?: ConnectionLineComponent
connectionMode?: ConnectionMode; connectionMode?: ConnectionMode
arrowHeadColor: string; arrowHeadColor: string
markerEndId?: string; markerEndId?: string
onlyRenderVisibleElements: boolean; onlyRenderVisibleElements: boolean
edgeUpdaterRadius?: number; edgeUpdaterRadius?: number
} }
export default defineComponent({ export default defineComponent({
@@ -70,7 +70,7 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
return () => ( return () => (
<svg width={store.width} height={store.height} class="revue-flow__edges"> <svg width={store.width} height={store.height} class="revue-flow__edges">
@@ -94,6 +94,6 @@ export default defineComponent({
)} )}
</g> </g>
</svg> </svg>
); )
} }
}); })
+51 -51
View File
@@ -1,6 +1,6 @@
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'; import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'
import { rectToBox } from '../../utils/graph'; import { rectToBox } from '../../utils/graph'
import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '../../types'; import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '../../types'
export function createEdgeTypes(edgeTypes: Record<string, EdgeType>): Record<string, EdgeType> { export function createEdgeTypes(edgeTypes: Record<string, EdgeType>): Record<string, EdgeType> {
const standardTypes: Record<string, EdgeType> = { const standardTypes: Record<string, EdgeType> = {
@@ -8,75 +8,75 @@ export function createEdgeTypes(edgeTypes: Record<string, EdgeType>): Record<str
straight: edgeTypes.bezier || StraightEdge, straight: edgeTypes.bezier || StraightEdge,
step: edgeTypes.step || StepEdge, step: edgeTypes.step || StepEdge,
smoothstep: edgeTypes.step || SmoothStepEdge smoothstep: edgeTypes.step || SmoothStepEdge
}; }
const wrappedTypes = {} as Record<string, EdgeType>; const wrappedTypes = {} as Record<string, EdgeType>
const specialTypes: Record<string, EdgeType> = Object.keys(edgeTypes) const specialTypes: Record<string, EdgeType> = Object.keys(edgeTypes)
.filter((k) => !['default', 'bezier'].includes(k)) .filter((k) => !['default', 'bezier'].includes(k))
.reduce((res, key) => { .reduce((res, key) => {
res[key] = edgeTypes[key] || BezierEdge; res[key] = edgeTypes[key] || BezierEdge
return res; return res
}, wrappedTypes); }, wrappedTypes)
return { return {
...standardTypes, ...standardTypes,
...specialTypes ...specialTypes
}; }
} }
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition { export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
const x = (handle?.x || 0) + node.__rf.position.x; const x = (handle?.x || 0) + node.__rf.position.x
const y = (handle?.y || 0) + node.__rf.position.y; const y = (handle?.y || 0) + node.__rf.position.y
const width = handle?.width || node.__rf.width; const width = handle?.width || node.__rf.width
const height = handle?.height || node.__rf.height; const height = handle?.height || node.__rf.height
switch (position) { switch (position) {
case Position.Top: case Position.Top:
return { return {
x: x + width / 2, x: x + width / 2,
y y
}; }
case Position.Right: case Position.Right:
return { return {
x: x + width, x: x + width,
y: y + height / 2 y: y + height / 2
}; }
case Position.Bottom: case Position.Bottom:
return { return {
x: x + width / 2, x: x + width / 2,
y: y + height y: y + height
}; }
case Position.Left: case Position.Left:
return { return {
x, x,
y: y + height / 2 y: y + height / 2
}; }
} }
} }
export function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleElement | null { export function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleElement | null {
if (!bounds) { if (!bounds) {
return null; return null
} }
// there is no handleId when there are no multiple handles/ handles with ids // there is no handleId when there are no multiple handles/ handles with ids
// so we just pick the first one // so we just pick the first one
let handle = null; let handle = null
if (bounds.length === 1 || !handleId) { if (bounds.length === 1 || !handleId) {
handle = bounds[0]; handle = bounds[0]
} else if (handleId) { } else if (handleId) {
handle = bounds.find((d) => d.id === handleId); handle = bounds.find((d) => d.id === handleId)
} }
return typeof handle === 'undefined' ? null : handle; return typeof handle === 'undefined' ? null : handle
} }
interface EdgePositions { interface EdgePositions {
sourceX: number; sourceX: number
sourceY: number; sourceY: number
targetX: number; targetX: number
targetY: number; targetY: number
} }
export const getEdgePositions = ( export const getEdgePositions = (
@@ -87,23 +87,23 @@ export const getEdgePositions = (
targetHandle: HandleElement | unknown, targetHandle: HandleElement | unknown,
targetPosition: Position targetPosition: Position
): EdgePositions => { ): EdgePositions => {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle); const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle)
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle); const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle)
return { return {
sourceX: sourceHandlePos.x, sourceX: sourceHandlePos.x,
sourceY: sourceHandlePos.y, sourceY: sourceHandlePos.y,
targetX: targetHandlePos.x, targetX: targetHandlePos.x,
targetY: targetHandlePos.y targetY: targetHandlePos.y
}; }
}; }
interface IsEdgeVisibleParams { interface IsEdgeVisibleParams {
sourcePos: XYPosition; sourcePos: XYPosition
targetPos: XYPosition; targetPos: XYPosition
width: number; width: number
height: number; height: number
transform: Transform; transform: Transform
} }
export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }: IsEdgeVisibleParams): boolean { export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }: IsEdgeVisibleParams): boolean {
@@ -112,14 +112,14 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
y: Math.min(sourcePos.y, targetPos.y), y: Math.min(sourcePos.y, targetPos.y),
x2: Math.max(sourcePos.x, targetPos.x), x2: Math.max(sourcePos.x, targetPos.x),
y2: Math.max(sourcePos.y, targetPos.y) y2: Math.max(sourcePos.y, targetPos.y)
}; }
if (edgeBox.x === edgeBox.x2) { if (edgeBox.x === edgeBox.x2) {
edgeBox.x2 += 1; edgeBox.x2 += 1
} }
if (edgeBox.y === edgeBox.y2) { if (edgeBox.y === edgeBox.y2) {
edgeBox.y2 += 1; edgeBox.y2 += 1
} }
const viewBox = rectToBox({ const viewBox = rectToBox({
@@ -127,31 +127,31 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
y: (0 - transform[1]) / transform[2], y: (0 - transform[1]) / transform[2],
width: width / transform[2], width: width / transform[2],
height: height / transform[2] height: height / transform[2]
}); })
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x)); const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x))
const yOverlap = Math.max(0, Math.min(viewBox.y2, edgeBox.y2) - Math.max(viewBox.y, edgeBox.y)); const yOverlap = Math.max(0, Math.min(viewBox.y2, edgeBox.y2) - Math.max(viewBox.y, edgeBox.y))
const overlappingArea = Math.ceil(xOverlap * yOverlap); const overlappingArea = Math.ceil(xOverlap * yOverlap)
return overlappingArea > 0; return overlappingArea > 0
} }
type SourceTargetNode = { type SourceTargetNode = {
sourceNode: Node | null; sourceNode: Node | null
targetNode: Node | null; targetNode: Node | null
}; }
export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => { export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => {
return nodes.reduce( return nodes.reduce(
(res, node) => { (res, node) => {
if (node.id === edge.source) { if (node.id === edge.source) {
res.sourceNode = node; res.sourceNode = node
} }
if (node.id === edge.target) { if (node.id === edge.target) {
res.targetNode = node; res.targetNode = node
} }
return res; return res
}, },
{ sourceNode: null, targetNode: null } as SourceTargetNode { sourceNode: null, targetNode: null } as SourceTargetNode
); )
}; }
+22 -22
View File
@@ -1,11 +1,11 @@
import { computed, defineComponent, inject, PropType } from 'vue'; import { computed, defineComponent, inject, PropType } from 'vue'
import ZoomPane from '../ZoomPane'; import ZoomPane from '../ZoomPane'
import UserSelection from '../../components/UserSelection'; import UserSelection from '../../components/UserSelection'
import NodesSelection from '../../components/NodesSelection'; import NodesSelection from '../../components/NodesSelection'
import { RevueFlowStore, GraphViewProps } from '../../types'; import { RevueFlowStore, GraphViewProps } from '../../types'
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'
import useKeyPress from '../../hooks/useKeyPress'; import useKeyPress from '../../hooks/useKeyPress'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
type FlowRendererProps = Omit< type FlowRendererProps = Omit<
GraphViewProps, GraphViewProps,
@@ -18,7 +18,7 @@ type FlowRendererProps = Omit<
| 'arrowHeadColor' | 'arrowHeadColor'
| 'onlyRenderVisibleElements' | 'onlyRenderVisibleElements'
| 'selectNodesOnDrag' | 'selectNodesOnDrag'
>; >
export default defineComponent({ export default defineComponent({
name: 'FlowRenderer', name: 'FlowRenderer',
@@ -101,28 +101,28 @@ export default defineComponent({
} }
}, },
setup(props, { slots }) { setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const keyPressed = useKeyPress(props.selectionKeyCode); const keyPressed = useKeyPress(props.selectionKeyCode)
useGlobalKeyHandler({ useGlobalKeyHandler({
store, store,
onElementsRemove: hooks.elementsRemove.trigger, onElementsRemove: hooks.elementsRemove.trigger,
deleteKeyCode: props.deleteKeyCode || '', deleteKeyCode: props.deleteKeyCode || '',
multiSelectionKeyCode: props.multiSelectionKeyCode || '' multiSelectionKeyCode: props.multiSelectionKeyCode || ''
}); })
const onClick = (event: MouseEvent) => { const onClick = (event: MouseEvent) => {
hooks.paneClick.trigger(event); hooks.paneClick.trigger(event)
store.unsetNodesSelection(); store.unsetNodesSelection()
store.resetSelectedElements(); store.resetSelectedElements()
}; }
const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event); const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event)
const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event); const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event)
const selectionActive = computed(() => store.nodesSelectionActive); const selectionActive = computed(() => store.nodesSelectionActive)
return () => ( return () => (
<ZoomPane <ZoomPane
@@ -145,6 +145,6 @@ export default defineComponent({
{selectionActive.value ? <NodesSelection /> : ''} {selectionActive.value ? <NodesSelection /> : ''}
<div class="revue-flow__pane" onClick={onClick} onContextmenu={onContextMenu} onWheel={onWheel} /> <div class="revue-flow__pane" onClick={onClick} onContextmenu={onContextMenu} onWheel={onWheel} />
</ZoomPane> </ZoomPane>
); )
} }
}); })
+29 -29
View File
@@ -1,12 +1,12 @@
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, inject, PropType } from 'vue'
import FlowRenderer from '../FlowRenderer'; import FlowRenderer from '../FlowRenderer'
import NodeRenderer from '../NodeRenderer'; import NodeRenderer from '../NodeRenderer'
import EdgeRenderer from '../EdgeRenderer'; import EdgeRenderer from '../EdgeRenderer'
import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph'; import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph'
import { GraphViewProps, RevueFlowStore } from '../../types'; import { GraphViewProps, RevueFlowStore } from '../../types'
import useZoomPanHelper from '../../hooks/useZoomPanHelper'; import useZoomPanHelper from '../../hooks/useZoomPanHelper'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
import { tryOnMounted } from '@vueuse/core'; import { tryOnMounted } from '@vueuse/core'
export default defineComponent({ export default defineComponent({
name: 'GraphView', name: 'GraphView',
@@ -177,10 +177,10 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const { onReady } = useZoomPanHelper(); const { onReady } = useZoomPanHelper()
onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView }) => { onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView }) => {
hooks.load.trigger({ hooks.load.trigger({
fitView: (params = { padding: 0.1 }) => fitView(params), fitView: (params = { padding: 0.1 }) => fitView(params),
@@ -191,52 +191,52 @@ export default defineComponent({
project: onLoadProject(store), project: onLoadProject(store),
getElements: onLoadGetElements(store), getElements: onLoadGetElements(store),
toObject: onLoadToObject(store) toObject: onLoadToObject(store)
}); })
}); })
const init = () => { const init = () => {
if (typeof props.snapToGrid !== 'undefined') { if (typeof props.snapToGrid !== 'undefined') {
store.snapToGrid = props.snapToGrid; store.snapToGrid = props.snapToGrid
} }
if (typeof props.snapGrid !== 'undefined') { if (typeof props.snapGrid !== 'undefined') {
store.snapGrid = props.snapGrid; store.snapGrid = props.snapGrid
} }
if (typeof props.minZoom !== 'undefined') { if (typeof props.minZoom !== 'undefined') {
store.setMinZoom(props.minZoom); store.setMinZoom(props.minZoom)
} }
if (typeof props.maxZoom !== 'undefined') { if (typeof props.maxZoom !== 'undefined') {
store.setMaxZoom(props.maxZoom); store.setMaxZoom(props.maxZoom)
} }
if (typeof props.translateExtent !== 'undefined') { if (typeof props.translateExtent !== 'undefined') {
store.setTranslateExtent(props.translateExtent); store.setTranslateExtent(props.translateExtent)
} }
if (typeof props.nodeExtent !== 'undefined') { if (typeof props.nodeExtent !== 'undefined') {
store.setNodeExtent(props.nodeExtent); store.setNodeExtent(props.nodeExtent)
} }
if (typeof props.nodesConnectable !== 'undefined') { if (typeof props.nodesConnectable !== 'undefined') {
store.nodesConnectable = !!props.nodesConnectable; store.nodesConnectable = !!props.nodesConnectable
} }
if (typeof props.elementsSelectable !== 'undefined') { if (typeof props.elementsSelectable !== 'undefined') {
store.elementsSelectable = !!props.elementsSelectable; store.elementsSelectable = !!props.elementsSelectable
} }
if (typeof props.nodesDraggable !== 'undefined') { if (typeof props.nodesDraggable !== 'undefined') {
store.nodesDraggable = !!props.nodesDraggable; store.nodesDraggable = !!props.nodesDraggable
} }
if (typeof props.connectionMode !== 'undefined') { if (typeof props.connectionMode !== 'undefined') {
store.connectionMode = props.connectionMode; store.connectionMode = props.connectionMode
} }
}; }
tryOnMounted(() => { tryOnMounted(() => {
init(); init()
}); })
return () => ( return () => (
<FlowRenderer <FlowRenderer
@@ -275,6 +275,6 @@ export default defineComponent({
edgeUpdaterRadius={props.edgeUpdaterRadius} edgeUpdaterRadius={props.edgeUpdaterRadius}
/> />
</FlowRenderer> </FlowRenderer>
); )
} }
}); })
+34 -37
View File
@@ -1,14 +1,14 @@
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, inject, PropType } from 'vue'
import Node from '../../components/Nodes/Node'; import Node from '../../components/Nodes/Node'
import { getNodesInside } from '../../utils/graph'; import { getNodesInside } from '../../utils/graph'
import { NodeType, RevueFlowStore } from '../../types'; import { NodeType, RevueFlowStore } from '../../types'
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: Record<string, NodeType>; nodeTypes: Record<string, NodeType>
selectNodesOnDrag: boolean; selectNodesOnDrag: boolean
snapToGrid: boolean; snapToGrid: boolean
snapGrid: [number, number]; snapGrid: [number, number]
onlyRenderVisibleElements: boolean; onlyRenderVisibleElements: boolean
} }
export default defineComponent({ export default defineComponent({
@@ -42,7 +42,7 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const getNodes = () => { const getNodes = () => {
return props.onlyRenderVisibleElements return props.onlyRenderVisibleElements
@@ -58,30 +58,8 @@ export default defineComponent({
store.transform, store.transform,
true true
) )
: store.nodes; : store.nodes
}; }
const visibleNodes = () =>
getNodes().map((node, i) => {
const nodeType = node.type || 'default';
if (props.nodeTypes) {
const type = props.nodeTypes[nodeType] || props.nodeTypes.default;
if (!props.nodeTypes[nodeType]) {
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`);
}
return (
<Node
node={node}
snapGrid={props.snapGrid}
snapToGrid={props.snapToGrid}
selectNodesOnDrag={props.selectNodesOnDrag}
type={type}
key={node.id + i}
/>
);
}
});
return () => ( return () => (
<div <div
@@ -90,8 +68,27 @@ export default defineComponent({
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})` transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
}} }}
> >
{visibleNodes()} {getNodes().map((node, i) => {
const nodeType = node.type || 'default'
if (props.nodeTypes) {
const type = props.nodeTypes[nodeType] || props.nodeTypes.default
if (!props.nodeTypes[nodeType]) {
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`)
}
return (
<Node
node={node}
snapGrid={props.snapGrid}
snapToGrid={props.snapToGrid}
selectNodesOnDrag={props.selectNodesOnDrag}
type={type}
key={node.id + i}
/>
)
}
})}
</div> </div>
); )
} }
}); })
+10 -10
View File
@@ -1,26 +1,26 @@
import DefaultNode from '../../components/Nodes/DefaultNode'; import DefaultNode from '../../components/Nodes/DefaultNode'
import InputNode from '../../components/Nodes/InputNode'; import InputNode from '../../components/Nodes/InputNode'
import OutputNode from '../../components/Nodes/OutputNode'; import OutputNode from '../../components/Nodes/OutputNode'
import { NodeType } from '../../types'; import { NodeType } from '../../types'
export function createNodeTypes(nodeTypes: Record<string, NodeType>): Record<string, NodeType> { export function createNodeTypes(nodeTypes: Record<string, NodeType>): Record<string, NodeType> {
const standardTypes: Record<string, NodeType> = { const standardTypes: Record<string, NodeType> = {
input: nodeTypes.input || InputNode, input: nodeTypes.input || InputNode,
default: nodeTypes.default || DefaultNode, default: nodeTypes.default || DefaultNode,
output: nodeTypes.output || OutputNode output: nodeTypes.output || OutputNode
}; }
const wrappedTypes = {} as NodeType; const wrappedTypes = {} as NodeType
const specialTypes: NodeType = Object.keys(nodeTypes) const specialTypes: NodeType = Object.keys(nodeTypes)
.filter((k) => !['input', 'default', 'output'].includes(k)) .filter((k) => !['input', 'default', 'output'].includes(k))
.reduce((res, key) => { .reduce((res, key) => {
res[key] = nodeTypes[key] || DefaultNode; res[key] = nodeTypes[key] || DefaultNode
return res; return res
}, wrappedTypes); }, wrappedTypes)
return { return {
...standardTypes, ...standardTypes,
...specialTypes ...specialTypes
}; }
} }
+31 -32
View File
@@ -1,31 +1,30 @@
import { defineComponent, onBeforeUnmount, onUpdated, PropType, provide, watch } from 'vue'; import { PropType } from 'vue'
import { useVModel } from '@vueuse/core'; import '../../style.css'
import '../../style.css'; import '../../theme-default.css'
import '../../theme-default.css'; import GraphView from '../GraphView'
import GraphView from '../GraphView'; import DefaultNode from '../../components/Nodes/DefaultNode'
import DefaultNode from '../../components/Nodes/DefaultNode'; import InputNode from '../../components/Nodes/InputNode'
import InputNode from '../../components/Nodes/InputNode'; import OutputNode from '../../components/Nodes/OutputNode'
import OutputNode from '../../components/Nodes/OutputNode'; import { createNodeTypes } from '../NodeRenderer/utils'
import { createNodeTypes } from '../NodeRenderer/utils'; import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'; import { createEdgeTypes } from '../EdgeRenderer/utils'
import { createEdgeTypes } from '../EdgeRenderer/utils'; import { ConnectionMode, ConnectionLineType, PanOnScrollMode, RevueFlowStore, RevueFlowProps } from '../../types'
import { ConnectionMode, ConnectionLineType, PanOnScrollMode, RevueFlowStore, RevueFlowProps } from '../../types'; import { initialState } from '../../store'
import { initialState } from '../../store'; import configureStore from '../../store/configure-store'
import configureStore from '../../store/configure-store'; import { RevueFlowHooks, useRevueFlow } from '../../hooks/RevueFlowHooks'
import { RevueFlowHooks, useRevueFlow } from '../../hooks/RevueFlowHooks';
const defaultNodeTypes = { const defaultNodeTypes = {
input: InputNode, input: InputNode,
default: DefaultNode, default: DefaultNode,
output: OutputNode output: OutputNode
}; }
const defaultEdgeTypes = { const defaultEdgeTypes = {
default: BezierEdge, default: BezierEdge,
straight: StraightEdge, straight: StraightEdge,
step: StepEdge, step: StepEdge,
smoothstep: SmoothStepEdge smoothstep: SmoothStepEdge
}; }
export default defineComponent({ export default defineComponent({
name: 'RevueFlow', name: 'RevueFlow',
@@ -220,25 +219,25 @@ export default defineComponent({
}, },
emits: Object.keys(useRevueFlow()), emits: Object.keys(useRevueFlow()),
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const store = configureStore(initialState)(); const store = configureStore(initialState)()
provide<RevueFlowStore>('store', store); provide<RevueFlowStore>('store', store)
const hooks = useRevueFlow().bind(emit); const hooks = useRevueFlow().bind(emit)
provide<RevueFlowHooks>('hooks', hooks); provide<RevueFlowHooks>('hooks', hooks)
const elements = useVModel(props, 'modelValue', emit); const elements = useVModel(props, 'modelValue', emit)
store.setElements(elements.value); store.setElements(elements.value)
watch(elements.value, () => { watch(elements.value, () => {
store.setElements(elements.value); store.setElements(elements.value)
}); })
onUpdated(() => { onUpdated(() => {
store.setElements(elements.value); store.setElements(elements.value)
}); })
onBeforeUnmount(() => { onBeforeUnmount(() => {
store.$reset(); store.$reset()
store.setElements([]); store.setElements([])
}); })
return () => ( return () => (
<div class="revue-flow"> <div class="revue-flow">
@@ -279,6 +278,6 @@ export default defineComponent({
/> />
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</div> </div>
); )
} }
}); })
+91 -91
View File
@@ -1,28 +1,28 @@
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'; import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
import { select, pointer } from 'd3-selection'; import { select, pointer } from 'd3-selection'
import { clamp } from '../../utils'; import { clamp } from '../../utils'
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types'; import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types'
import { defineComponent, inject, PropType, ref, watchPostEffect } from 'vue'; import { defineComponent, inject, PropType, ref, watchPostEffect } from 'vue'
import useKeyPress from '../../hooks/useKeyPress'; import useKeyPress from '../../hooks/useKeyPress'
import useResizeHandler from '../../hooks/useResizeHandler'; import useResizeHandler from '../../hooks/useResizeHandler'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
import { templateRef } from '@vueuse/core'; import { templateRef } from '@vueuse/core'
import { ZoomBehavior } from 'd3'; import { ZoomBehavior } from 'd3'
interface ZoomPaneProps { interface ZoomPaneProps {
elementsSelectable?: boolean; elementsSelectable?: boolean
zoomOnScroll?: boolean; zoomOnScroll?: boolean
zoomOnPinch?: boolean; zoomOnPinch?: boolean
panOnScroll?: boolean; panOnScroll?: boolean
panOnScrollSpeed?: number; panOnScrollSpeed?: number
panOnScrollMode?: PanOnScrollMode; panOnScrollMode?: PanOnScrollMode
zoomOnDoubleClick?: boolean; zoomOnDoubleClick?: boolean
paneMoveable?: boolean; paneMoveable?: boolean
defaultPosition?: [number, number]; defaultPosition?: [number, number]
defaultZoom?: number; defaultZoom?: number
translateExtent?: TranslateExtent; translateExtent?: TranslateExtent
zoomActivationKeyCode?: KeyCode; zoomActivationKeyCode?: KeyCode
selectionKeyCode?: KeyCode; selectionKeyCode?: KeyCode
} }
export default defineComponent({ export default defineComponent({
@@ -95,35 +95,35 @@ export default defineComponent({
} }
}, },
setup(props, { slots }) { setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!; const hooks = inject<RevueFlowHooks>('hooks')!
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null); const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null)
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 }); const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 })
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean => const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k; prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({ const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
x: eventTransform.x, x: eventTransform.x,
y: eventTransform.y, y: eventTransform.y,
zoom: eventTransform.k zoom: eventTransform.k
}); })
const init = () => { const init = () => {
const { onReady } = useResizeHandler(zoomPane, store.updateSize); const { onReady } = useResizeHandler(zoomPane, store.updateSize)
onReady(() => { onReady(() => {
if (props.defaultPosition && props.defaultZoom) { if (props.defaultPosition && props.defaultZoom) {
const currentTranslateExtent = const currentTranslateExtent =
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent; typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent); const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent)
const selection = select(zoomPane.value as Element).call(d3ZoomInstance); const selection = select(zoomPane.value as Element).call(d3ZoomInstance)
const clampedX = clamp(props.defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0]); const clampedX = clamp(props.defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0])
const clampedY = clamp(props.defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1]); const clampedY = clamp(props.defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1])
const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom); const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom)
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom); const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom)
d3ZoomInstance.transform(selection, updatedTransform); d3ZoomInstance.transform(selection, updatedTransform)
store.initD3Zoom({ store.initD3Zoom({
d3Zoom: d3ZoomInstance, d3Zoom: d3ZoomInstance,
@@ -131,106 +131,106 @@ export default defineComponent({
d3ZoomHandler: selection.on('wheel.zoom'), d3ZoomHandler: selection.on('wheel.zoom'),
// we need to pass transform because zoom handler is not registered when we set the initial transform // we need to pass transform because zoom handler is not registered when we set the initial transform
transform: [clampedX, clampedY, clampedZoom] transform: [clampedX, clampedY, clampedZoom]
}); })
applyZoomHandlers(d3ZoomInstance); applyZoomHandlers(d3ZoomInstance)
applyZoomFilter(d3ZoomInstance); applyZoomFilter(d3ZoomInstance)
} }
}); })
const applyZoomHandlers = (d3Zoom: ZoomBehavior<Element, unknown>) => { const applyZoomHandlers = (d3Zoom: ZoomBehavior<Element, unknown>) => {
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(prevTransform.value, event.transform)) { if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform); const flowTransform = eventToFlowTransform(event.transform)
prevTransform.value = flowTransform; prevTransform.value = flowTransform
hooks.moveStart.trigger(flowTransform); hooks.moveStart.trigger(flowTransform)
} }
}); })
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(prevTransform.value, event.transform)) { if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform); const flowTransform = eventToFlowTransform(event.transform)
prevTransform.value = flowTransform; prevTransform.value = flowTransform
hooks.moveEnd.trigger(flowTransform); hooks.moveEnd.trigger(flowTransform)
} }
}); })
useKeyPress(props.selectionKeyCode, (keyPress) => { useKeyPress(props.selectionKeyCode, (keyPress) => {
if (keyPress) { if (keyPress) {
d3Zoom.on('zoom', null); d3Zoom.on('zoom', null)
} else { } else {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
store.transform = [event.transform.x, event.transform.y, event.transform.k]; store.transform = [event.transform.x, event.transform.y, event.transform.k]
hooks.move.trigger(eventToFlowTransform(event.transform)); hooks.move.trigger(eventToFlowTransform(event.transform))
}); })
} }
}); })
useKeyPress(props.zoomActivationKeyCode, (keyPress) => { useKeyPress(props.zoomActivationKeyCode, (keyPress) => {
if (props.panOnScroll && keyPress) { if (props.panOnScroll && keyPress) {
store.d3Selection store.d3Selection
?.on('wheel', (event: WheelEvent) => { ?.on('wheel', (event: WheelEvent) => {
event.preventDefault(); event.preventDefault()
event.stopImmediatePropagation(); event.stopImmediatePropagation()
const currentZoom = store.d3Selection?.property('__zoom').k || 1; const currentZoom = store.d3Selection?.property('__zoom').k || 1
if (event.ctrlKey && props.zoomOnPinch) { if (event.ctrlKey && props.zoomOnPinch) {
const point = pointer(event); const point = pointer(event)
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10; const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10
const zoom = currentZoom * Math.pow(2, pinchDelta); const zoom = currentZoom * Math.pow(2, pinchDelta)
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point); if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point)
return; return
} }
// increase scroll speed in firefox // increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0 // firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1; const deltaNormalize = event.deltaMode === 1 ? 20 : 1
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize; const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize; const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
if (store.d3Selection && props.panOnScrollSpeed) if (store.d3Selection && props.panOnScrollSpeed)
store.d3Zoom?.translateBy( store.d3Zoom?.translateBy(
store.d3Selection, store.d3Selection,
-(deltaX / currentZoom) * props.panOnScrollSpeed, -(deltaX / currentZoom) * props.panOnScrollSpeed,
-(deltaY / currentZoom) * props.panOnScrollSpeed -(deltaY / currentZoom) * props.panOnScrollSpeed
); )
}) })
.on('wheel.zoom', null); .on('wheel.zoom', null)
} else if (typeof store.d3ZoomHandler !== 'undefined') { } else if (typeof store.d3ZoomHandler !== 'undefined') {
store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler); store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler)
} }
}); })
}; }
watchPostEffect(() => store.d3Zoom && applyZoomFilter(store.d3Zoom)); watchPostEffect(() => store.d3Zoom && applyZoomFilter(store.d3Zoom))
const applyZoomFilter = (d3Zoom: ZoomBehavior<Element, unknown>) => { const applyZoomFilter = (d3Zoom: ZoomBehavior<Element, unknown>) => {
const keyPress = useKeyPress(props.selectionKeyCode); const keyPress = useKeyPress(props.selectionKeyCode)
d3Zoom.filter((event: MouseEvent) => { d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = props.zoomOnScroll; const zoomScroll = props.zoomOnScroll
const pinchZoom = props.zoomOnPinch && event.ctrlKey; const pinchZoom = props.zoomOnPinch && event.ctrlKey
// if all interactions are disabled, we prevent all zoom events // if all interactions are disabled, we prevent all zoom events
if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) { if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) {
return false; return false
} }
// during a selection we prevent all other interactions // during a selection we prevent all other interactions
if (keyPress.value) { if (keyPress.value) {
return false; return false
} }
// if zoom on double click is disabled, we prevent the double click event // if zoom on double click is disabled, we prevent the double click event
if (!props.zoomOnDoubleClick && event.type === 'dblclick') { if (!props.zoomOnDoubleClick && event.type === 'dblclick') {
return false; return false
} }
if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') { if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') {
return false; return false
} }
// when the target element is a node, we still allow zooming // when the target element is a node, we still allow zooming
@@ -238,40 +238,40 @@ export default defineComponent({
((event.target as Element).closest('.revue-flow__node') || (event.target as Element).closest('.revue-flow__edge')) && ((event.target as Element).closest('.revue-flow__node') || (event.target as Element).closest('.revue-flow__edge')) &&
event.type !== 'wheel' event.type !== 'wheel'
) { ) {
return false; return false
} }
// when the target element is a node selection, we still allow zooming // when the target element is a node selection, we still allow zooming
if ((event.target as Element).closest('.revue-flow__nodesselection') && event.type !== 'wheel') { if ((event.target as Element).closest('.revue-flow__nodesselection') && event.type !== 'wheel') {
return false; return false
} }
if (!props.zoomOnPinch && event.ctrlKey && event.type === 'wheel') { if (!props.zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
return false; return false
} }
// when there is no scroll handling enabled, we prevent all wheel events // when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !props.panOnScroll && !pinchZoom && event.type === 'wheel') { if (!zoomScroll && !props.panOnScroll && !pinchZoom && event.type === 'wheel') {
return false; return false
} }
// if the pane is not movable, we prevent dragging it with mousestart or touchstart // if the pane is not movable, we prevent dragging it with mousestart or touchstart
if (!props.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) { if (!props.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) {
return false; return false
} }
// default filter for d3-zoom // default filter for d3-zoom
return (!event.ctrlKey || event.type === 'wheel') && !event.button; return (!event.ctrlKey || event.type === 'wheel') && !event.button
}); })
}; }
}; }
init(); init()
return () => ( return () => (
<div ref="zoom-pane" class="revue-flow__renderer revue-flow__zoompane"> <div ref="zoom-pane" class="revue-flow__renderer revue-flow__zoompane">
{slots.default ? slots.default() : ''} {slots.default ? slots.default() : ''}
</div> </div>
); )
} }
}); })
+2 -2
View File
@@ -1,2 +1,2 @@
declare const __REVUE_FLOW_VERSION__: string; declare const __REVUE_FLOW_VERSION__: string
declare const __ENV__: string; declare const __ENV__: string
+49 -49
View File
@@ -1,47 +1,47 @@
// global hooks // global hooks
import { Connection, Edge, Elements, FlowTransform, Node, OnConnectStartParams, OnLoadParams } from '../types'; import { Connection, Edge, Elements, FlowTransform, Node, OnConnectStartParams, OnLoadParams } from '../types'
import { createEventHook, EventHook } from '@vueuse/core'; import { createEventHook, EventHook } from '@vueuse/core'
export interface RevueFlowHooks { export interface RevueFlowHooks {
elementClick: EventHook<{ event: MouseEvent; element: Node | Edge }>; elementClick: EventHook<{ event: MouseEvent; element: Node | Edge }>
elementsRemove: EventHook<Elements>; elementsRemove: EventHook<Elements>
nodeDoubleClick: EventHook<{ event: MouseEvent; node: Node }>; nodeDoubleClick: EventHook<{ event: MouseEvent; node: Node }>
nodeClick: EventHook<{ event: MouseEvent; node: Node }>; nodeClick: EventHook<{ event: MouseEvent; node: Node }>
nodeMouseEnter: EventHook<{ event: MouseEvent; node: Node }>; nodeMouseEnter: EventHook<{ event: MouseEvent; node: Node }>
nodeMouseMove: EventHook<{ event: MouseEvent; node: Node }>; nodeMouseMove: EventHook<{ event: MouseEvent; node: Node }>
nodeMouseLeave: EventHook<{ event: MouseEvent; node: Node }>; nodeMouseLeave: EventHook<{ event: MouseEvent; node: Node }>
nodeContextMenu: EventHook<{ event: MouseEvent; node: Node }>; nodeContextMenu: EventHook<{ event: MouseEvent; node: Node }>
nodeDragStart: EventHook<{ event: MouseEvent; node: Node }>; nodeDragStart: EventHook<{ event: MouseEvent; node: Node }>
nodeDrag: EventHook<{ event: MouseEvent; node: Node }>; nodeDrag: EventHook<{ event: MouseEvent; node: Node }>
nodeDragStop: EventHook<{ event: MouseEvent; node: Node }>; nodeDragStop: EventHook<{ event: MouseEvent; node: Node }>
connect: EventHook<Connection>; connect: EventHook<Connection>
connectStart: EventHook<{ connectStart: EventHook<{
event: MouseEvent; event: MouseEvent
params: OnConnectStartParams; params: OnConnectStartParams
}>; }>
connectStop: EventHook<MouseEvent>; connectStop: EventHook<MouseEvent>
connectEnd: EventHook<MouseEvent>; connectEnd: EventHook<MouseEvent>
load: EventHook<OnLoadParams>; load: EventHook<OnLoadParams>
move: EventHook<FlowTransform | undefined>; move: EventHook<FlowTransform | undefined>
moveStart: EventHook<FlowTransform | undefined>; moveStart: EventHook<FlowTransform | undefined>
moveEnd: EventHook<FlowTransform | undefined>; moveEnd: EventHook<FlowTransform | undefined>
selectionChange: EventHook<Elements | null>; selectionChange: EventHook<Elements | null>
selectionDragStart: EventHook<{ event: MouseEvent; nodes: Node[] }>; selectionDragStart: EventHook<{ event: MouseEvent; nodes: Node[] }>
selectionDrag: EventHook<{ event: MouseEvent; nodes: Node[] }>; selectionDrag: EventHook<{ event: MouseEvent; nodes: Node[] }>
selectionDragStop: EventHook<{ event: MouseEvent; nodes: Node[] }>; selectionDragStop: EventHook<{ event: MouseEvent; nodes: Node[] }>
selectionContextMenu: EventHook<{ event: MouseEvent; nodes: Node[] }>; selectionContextMenu: EventHook<{ event: MouseEvent; nodes: Node[] }>
paneScroll: EventHook<WheelEvent | undefined>; paneScroll: EventHook<WheelEvent | undefined>
paneClick: EventHook<MouseEvent>; paneClick: EventHook<MouseEvent>
paneContextMenu: EventHook<MouseEvent>; paneContextMenu: EventHook<MouseEvent>
edgeUpdate: EventHook<{ edge: Edge; connection: Connection }>; edgeUpdate: EventHook<{ edge: Edge; connection: Connection }>
edgeContextMenu: EventHook<{ event: MouseEvent; edge: Edge }>; edgeContextMenu: EventHook<{ event: MouseEvent; edge: Edge }>
edgeMouseEnter: EventHook<{ event: MouseEvent; edge: Edge }>; edgeMouseEnter: EventHook<{ event: MouseEvent; edge: Edge }>
edgeMouseMove: EventHook<{ event: MouseEvent; edge: Edge }>; edgeMouseMove: EventHook<{ event: MouseEvent; edge: Edge }>
edgeMouseLeave: EventHook<{ event: MouseEvent; edge: Edge }>; edgeMouseLeave: EventHook<{ event: MouseEvent; edge: Edge }>
edgeDoubleClick: EventHook<{ event: MouseEvent; edge: Edge }>; edgeDoubleClick: EventHook<{ event: MouseEvent; edge: Edge }>
edgeClick: EventHook<{ event: MouseEvent; edge: Edge }>; edgeClick: EventHook<{ event: MouseEvent; edge: Edge }>
edgeUpdateStart: EventHook<{ event: MouseEvent; edge: Edge }>; edgeUpdateStart: EventHook<{ event: MouseEvent; edge: Edge }>
edgeUpdateEnd: EventHook<{ event: MouseEvent; edge: Edge }>; edgeUpdateEnd: EventHook<{ event: MouseEvent; edge: Edge }>
} }
export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, ...args: any[]) => void) => RevueFlowHooks } => { export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, ...args: any[]) => void) => RevueFlowHooks } => {
@@ -59,8 +59,8 @@ export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string,
nodeDragStop: createEventHook<{ event: MouseEvent; node: Node }>(), nodeDragStop: createEventHook<{ event: MouseEvent; node: Node }>(),
connect: createEventHook<Connection>(), connect: createEventHook<Connection>(),
connectStart: createEventHook<{ connectStart: createEventHook<{
event: MouseEvent; event: MouseEvent
params: OnConnectStartParams; params: OnConnectStartParams
}>(), }>(),
connectStop: createEventHook<MouseEvent>(), connectStop: createEventHook<MouseEvent>(),
connectEnd: createEventHook<MouseEvent>(), connectEnd: createEventHook<MouseEvent>(),
@@ -85,20 +85,20 @@ export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string,
edgeClick: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeClick: createEventHook<{ event: MouseEvent; edge: Edge }>(),
edgeUpdateStart: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeUpdateStart: createEventHook<{ event: MouseEvent; edge: Edge }>(),
edgeUpdateEnd: createEventHook<{ event: MouseEvent; edge: Edge }>() edgeUpdateEnd: createEventHook<{ event: MouseEvent; edge: Edge }>()
}; }
const bind = (emit: (event: string, args: any) => void) => { const bind = (emit: (event: string, args: any) => void) => {
for (const [key, value] of Object.entries(eventHooks)) { for (const [key, value] of Object.entries(eventHooks)) {
value.on((data: any) => { value.on((data: any) => {
emit(key, data); emit(key, data)
}); })
} }
return eventHooks; return eventHooks
}; }
return { return {
...eventHooks, ...eventHooks,
bind bind
}; }
}; }
+22 -22
View File
@@ -1,37 +1,37 @@
import useKeyPress from './useKeyPress'; import useKeyPress from './useKeyPress'
import { isNode, getConnectedEdges } from '../utils/graph'; import { isNode, getConnectedEdges } from '../utils/graph'
import { Elements, KeyCode, ElementId, FlowElement, RevueFlowStore, Edge } from '../types'; import { Elements, KeyCode, ElementId, FlowElement, RevueFlowStore, Edge } from '../types'
import { computed, watch } from 'vue'; import { computed, watch } from 'vue'
interface HookParams { interface HookParams {
store: RevueFlowStore; store: RevueFlowStore
deleteKeyCode: KeyCode; deleteKeyCode: KeyCode
multiSelectionKeyCode: KeyCode; multiSelectionKeyCode: KeyCode
onElementsRemove?: (elements: Elements) => void; onElementsRemove?: (elements: Elements) => void
} }
export default ({ store, deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: HookParams): void => { export default ({ store, deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: HookParams): void => {
const deleteKeyPressed = useKeyPress(deleteKeyCode); const deleteKeyPressed = useKeyPress(deleteKeyCode)
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode)
const selectedElements = computed(() => store.selectedElements || []); const selectedElements = computed(() => store.selectedElements || [])
const edges = computed(() => store.edges); const edges = computed(() => store.edges)
watch(selectedElements, () => { watch(selectedElements, () => {
if (onElementsRemove && deleteKeyPressed.value && selectedElements.value.length > 0) { if (onElementsRemove && deleteKeyPressed.value && selectedElements.value.length > 0) {
const selectedNodes = selectedElements.value.filter(isNode); const selectedNodes = selectedElements.value.filter(isNode)
const connectedEdges = getConnectedEdges(selectedNodes, edges.value as Edge[]); const connectedEdges = getConnectedEdges(selectedNodes, edges.value as Edge[])
const elementsToRemove = [...selectedElements.value, ...connectedEdges].reduce( const elementsToRemove = [...selectedElements.value, ...connectedEdges].reduce(
(res, item) => res.set(item.id, item), (res, item) => res.set(item.id, item),
new Map<ElementId, FlowElement>() new Map<ElementId, FlowElement>()
); )
onElementsRemove(Array.from(elementsToRemove.values())); onElementsRemove(Array.from(elementsToRemove.values()))
store.unsetNodesSelection(); store.unsetNodesSelection()
store.resetSelectedElements(); store.resetSelectedElements()
} }
}); })
watch(multiSelectionKeyPressed, () => { watch(multiSelectionKeyPressed, () => {
store.multiSelectionActive = multiSelectionKeyPressed.value; store.multiSelectionActive = multiSelectionKeyPressed.value
}); })
}; }
+20 -20
View File
@@ -1,47 +1,47 @@
import { Ref } from 'vue'; import { Ref } from 'vue'
import { KeyCode } from '../types'; import { KeyCode } from '../types'
import { controlledRef, onKeyStroke, useEventListener } from '@vueuse/core'; import { controlledRef, onKeyStroke, useEventListener } from '@vueuse/core'
export default (keyCode?: KeyCode, cb?: (keyPress: boolean) => void): Ref<boolean> => { export default (keyCode?: KeyCode, cb?: (keyPress: boolean) => void): Ref<boolean> => {
const keyPressed = controlledRef<boolean>(false, { const keyPressed = controlledRef<boolean>(false, {
onBeforeChange(val, oldVal) { onBeforeChange(val, oldVal) {
if (val === oldVal) return false; if (val === oldVal) return false
}, },
onChanged() { onChanged() {
if (cb && typeof cb === 'function') cb(keyPressed.value); if (cb && typeof cb === 'function') cb(keyPressed.value)
} }
}); })
onKeyStroke( onKeyStroke(
(event) => event.key === keyCode || event.keyCode === keyCode, (event) => event.key === keyCode || event.keyCode === keyCode,
(e) => { (e) => {
e.preventDefault(); e.preventDefault()
keyPressed.value = true; keyPressed.value = true
}, },
{ eventName: 'keypress' } { eventName: 'keypress' }
); )
onKeyStroke( onKeyStroke(
(event) => event.key === keyCode || event.keyCode === keyCode, (event) => event.key === keyCode || event.keyCode === keyCode,
(e) => { (e) => {
e.preventDefault(); e.preventDefault()
keyPressed.value = true; keyPressed.value = true
}, },
{ eventName: 'keydown' } { eventName: 'keydown' }
); )
onKeyStroke( onKeyStroke(
(event) => event.key === keyCode || event.keyCode === keyCode, (event) => event.key === keyCode || event.keyCode === keyCode,
(e) => { (e) => {
e.preventDefault(); e.preventDefault()
keyPressed.value = false; keyPressed.value = false
}, },
{ eventName: 'keyup' } { eventName: 'keyup' }
); )
useEventListener(window, 'blur', () => { useEventListener(window, 'blur', () => {
keyPressed.value = false; keyPressed.value = false
}); })
if (cb && typeof cb === 'function') cb(keyPressed.value); if (cb && typeof cb === 'function') cb(keyPressed.value)
return keyPressed; return keyPressed
}; }
+16 -16
View File
@@ -1,33 +1,33 @@
import { createEventHook, MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core'; import { createEventHook, MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core'
import { getDimensions } from '../utils'; import { getDimensions } from '../utils'
import { Dimensions } from '../types'; import { Dimensions } from '../types'
export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimensions) => any) => { export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimensions) => any) => {
const resizeHandlerHook = createEventHook(); const resizeHandlerHook = createEventHook()
const updateDimensions = () => { const updateDimensions = () => {
if (!unrefElement(rendererNode)) { if (!unrefElement(rendererNode)) {
return; return
} }
const size = getDimensions(unrefElement(rendererNode)); const size = getDimensions(unrefElement(rendererNode))
if (size.height === 0 || size.width === 0) { if (size.height === 0 || size.width === 0) {
console.log('The revue Flow parent container needs a width and a height to render the graph.'); console.log('The revue Flow parent container needs a width and a height to render the graph.')
} }
updateSize(size); updateSize(size)
}; }
until(rendererNode) until(rendererNode)
.toBeTruthy() .toBeTruthy()
.then(() => { .then(() => {
updateDimensions(); updateDimensions()
useEventListener(window, 'resize', updateDimensions); useEventListener(window, 'resize', updateDimensions)
useResizeObserver(unrefElement(rendererNode), () => updateDimensions()); useResizeObserver(unrefElement(rendererNode), () => updateDimensions())
resizeHandlerHook.trigger(true); resizeHandlerHook.trigger(true)
}); })
return { return {
onReady: resizeHandlerHook.on onReady: resizeHandlerHook.on
}; }
}; }
+7 -7
View File
@@ -1,16 +1,16 @@
import { ElementId, RevueFlowStore, UpdateNodeInternals } from '../types'; import { ElementId, RevueFlowStore, UpdateNodeInternals } from '../types'
import { inject } from 'vue'; import { inject } from 'vue'
function useUpdateNodeInternals(): UpdateNodeInternals { function useUpdateNodeInternals(): UpdateNodeInternals {
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
return (id: ElementId) => { return (id: ElementId) => {
const nodeElement: HTMLDivElement | null = document.querySelector(`.revue-flow__node[data-id="${id}"]`); const nodeElement: HTMLDivElement | null = document.querySelector(`.revue-flow__node[data-id="${id}"]`)
if (nodeElement) { if (nodeElement) {
store.updateNodeDimensions([{ id, nodeElement, forceUpdate: true }]); store.updateNodeDimensions([{ id, nodeElement, forceUpdate: true }])
} }
}; }
} }
export default useUpdateNodeInternals; export default useUpdateNodeInternals
+41 -41
View File
@@ -1,20 +1,20 @@
import { zoomIdentity } from 'd3-zoom'; import { zoomIdentity } from 'd3-zoom'
import { computed, effectScope, inject } from 'vue'; import { computed, effectScope, inject } from 'vue'
import { createEventHook, EventHookOn, until } from '@vueuse/core'; import { createEventHook, EventHookOn, until } from '@vueuse/core'
import { Selection, ZoomBehavior } from 'd3'; import { Selection, ZoomBehavior } from 'd3'
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph'; import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph'
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition, RevueFlowStore } from '../types'; import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition, RevueFlowStore } from '../types'
const DEFAULT_PADDING = 0.1; const DEFAULT_PADDING = 0.1
const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } => { const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } => {
const zoomPanHelperEvent = createEventHook<ZoomPanHelperFunctions>(); const zoomPanHelperEvent = createEventHook<ZoomPanHelperFunctions>()
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!
const scope = effectScope(); const scope = effectScope()
scope.run(() => { scope.run(() => {
const d3Zoom = computed(() => store.d3Zoom); const d3Zoom = computed(() => store.d3Zoom)
const d3Selection = computed(() => store.d3Selection); const d3Selection = computed(() => store.d3Selection)
const createZoomPanHelper = ( const createZoomPanHelper = (
d3Zoom: ZoomBehavior<Element, unknown>, d3Zoom: ZoomBehavior<Element, unknown>,
@@ -24,18 +24,18 @@ const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } =>
zoomOut: () => d3Zoom?.scaleBy(d3Selection, 1 / 1.2), zoomOut: () => d3Zoom?.scaleBy(d3Selection, 1 / 1.2),
zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel), zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel),
transform: (transform: FlowTransform) => { transform: (transform: FlowTransform) => {
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom); const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom)
d3Zoom.transform(d3Selection, nextTransform); d3Zoom.transform(d3Selection, nextTransform)
}, },
fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {
const { nodes, width, height, minZoom, maxZoom } = store; const { nodes, width, height, minZoom, maxZoom } = store
if (!nodes.length) { if (!nodes.length) {
return; return
} }
const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden)); const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden))
const [x, y, zoom] = getTransformForBounds( const [x, y, zoom] = getTransformForBounds(
bounds, bounds,
width, width,
@@ -43,49 +43,49 @@ const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } =>
options.minZoom || minZoom, options.minZoom || minZoom,
options.maxZoom || maxZoom, options.maxZoom || maxZoom,
options.padding || DEFAULT_PADDING options.padding || DEFAULT_PADDING
); )
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom)
d3Zoom?.transform(d3Selection, transform); d3Zoom?.transform(d3Selection, transform)
}, },
setCenter: (x: number, y: number, zoom?: number) => { setCenter: (x: number, y: number, zoom?: number) => {
const { width, height, maxZoom } = store; const { width, height, maxZoom } = store
const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom; const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom
const centerX = width / 2 - x * nextZoom; const centerX = width / 2 - x * nextZoom
const centerY = height / 2 - y * nextZoom; const centerY = height / 2 - y * nextZoom
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom); const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom)
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(d3Selection, transform)
}, },
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
const { width, height, minZoom, maxZoom } = store; const { width, height, minZoom, maxZoom } = store
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding); const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding)
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom)
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(d3Selection, transform)
}, },
project: (position: XYPosition) => { project: (position: XYPosition) => {
const { transform, snapToGrid, snapGrid } = store; const { transform, snapToGrid, snapGrid } = store
return pointToRendererPoint(position, transform, snapToGrid, snapGrid); return pointToRendererPoint(position, transform, snapToGrid, snapGrid)
} }
}); })
until(d3Zoom) until(d3Zoom)
.toBeTruthy() .toBeTruthy()
.then(() => { .then(() => {
if (d3Zoom.value && d3Selection.value) { if (d3Zoom.value && d3Selection.value) {
zoomPanHelperEvent.trigger(createZoomPanHelper(d3Zoom.value, d3Selection.value)); zoomPanHelperEvent.trigger(createZoomPanHelper(d3Zoom.value, d3Selection.value))
} else { } else {
throw Error('D3 could not be initialized.'); throw Error('D3 could not be initialized.')
} }
scope.stop(); scope.stop()
}); })
}); })
return { return {
onReady: zoomPanHelperEvent.on onReady: zoomPanHelperEvent.on
}; }
}; }
export default useZoomPanHelper; export default useZoomPanHelper
+11 -11
View File
@@ -1,9 +1,9 @@
export { default as default } from './container/RevueFlow'; export { default as default } from './container/RevueFlow'
export { default as Handle } from './components/Handle'; export { default as Handle } from './components/Handle'
export { default as EdgeText } from './components/Edges/EdgeText'; export { default as EdgeText } from './components/Edges/EdgeText'
export { getBezierPath } from './components/Edges/BezierEdge'; export { getBezierPath } from './components/Edges/BezierEdge'
export { getSmoothStepPath } from './components/Edges/SmoothStepEdge'; export { getSmoothStepPath } from './components/Edges/SmoothStepEdge'
export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils'; export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils'
export { export {
isNode, isNode,
@@ -16,9 +16,9 @@ export {
updateEdge, updateEdge,
getTransformForBounds, getTransformForBounds,
getRectOfNodes getRectOfNodes
} from './utils/graph'; } from './utils/graph'
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper'; export { default as useZoomPanHelper } from './hooks/useZoomPanHelper'
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals'; export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals'
export * from './additional-components'; export * from './additional-components'
export * from './types'; export * from './types'
+2 -2
View File
@@ -1,4 +1,4 @@
import Vue, { VNode } from 'vue'; import Vue, { VNode } from 'vue'
declare global { declare global {
namespace JSX { namespace JSX {
@@ -9,7 +9,7 @@ declare global {
interface ElementClass extends Vue {} interface ElementClass extends Vue {}
interface IntrinsicAttributes { interface IntrinsicAttributes {
[prop: string]: any; [prop: string]: any
} }
} }
} }
+3 -3
View File
@@ -1,6 +1,6 @@
declare module '*.vue' { declare module '*.vue' {
import { DefineComponent } from 'vue'; import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>; const component: DefineComponent<{}, {}, any>
export default component; export default component
} }
+91 -91
View File
@@ -1,20 +1,20 @@
import { setActivePinia, createPinia, defineStore, StoreDefinition } from 'pinia'; import { setActivePinia, createPinia, defineStore, StoreDefinition } from 'pinia'
import isEqual from 'fast-deep-equal'; import isEqual from 'fast-deep-equal'
import { Edge, Node, NodeDiffUpdate, RevueFlowState, RevueFlowActions, XYPosition } from '../types'; import { Edge, Node, NodeDiffUpdate, RevueFlowState, RevueFlowActions, XYPosition } from '../types'
import { getConnectedEdges, getNodesInside, getRectOfNodes, isEdge, isNode, parseEdge, parseNode } from '../utils/graph'; import { getConnectedEdges, getNodesInside, getRectOfNodes, isEdge, isNode, parseEdge, parseNode } from '../utils/graph'
import { clampPosition, getDimensions } from '../utils'; import { clampPosition, getDimensions } from '../utils'
import { getHandleBounds } from '../components/Nodes/utils'; import { getHandleBounds } from '../components/Nodes/utils'
type NextElements = { type NextElements = {
nextNodes: Node[]; nextNodes: Node[]
nextEdges: Edge[]; nextEdges: Edge[]
}; }
const pinia = createPinia(); const pinia = createPinia()
export default function configureStore( export default function configureStore(
preloadedState: RevueFlowState preloadedState: RevueFlowState
): StoreDefinition<string, RevueFlowState, any, RevueFlowActions> { ): StoreDefinition<string, RevueFlowState, any, RevueFlowActions> {
setActivePinia(pinia); setActivePinia(pinia)
return defineStore({ return defineStore({
id: 'revue-flow-' + Math.random(), id: 'revue-flow-' + Math.random(),
@@ -24,66 +24,66 @@ export default function configureStore(
getters: {}, getters: {},
actions: { actions: {
setElements(elements) { setElements(elements) {
const propElements = elements; const propElements = elements
const nextElements: NextElements = { const nextElements: NextElements = {
nextNodes: [], nextNodes: [],
nextEdges: [] nextEdges: []
}; }
const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => { const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => {
if (isNode(propElement)) { if (isNode(propElement)) {
const storeNode = this.nodes.find((node) => node.id === propElement.id); const storeNode = this.nodes.find((node) => node.id === propElement.id)
if (storeNode) { if (storeNode) {
const updatedNode: Node = { const updatedNode: Node = {
...storeNode, ...storeNode,
...propElement ...propElement
}; }
if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) { if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) {
updatedNode.__rf.position = propElement.position; updatedNode.__rf.position = propElement.position
} }
if (typeof propElement.type !== 'undefined' && propElement.type !== storeNode.type) { if (typeof propElement.type !== 'undefined' && propElement.type !== storeNode.type) {
// we reset the elements dimensions here in order to force a re-calculation of the bounds. // we reset the elements dimensions here in order to force a re-calculation of the bounds.
// When the type of a node changes it is possible that the number or positions of handles changes too. // When the type of a node changes it is possible that the number or positions of handles changes too.
updatedNode.__rf.width = null; updatedNode.__rf.width = null
} }
res.nextNodes.push(updatedNode); res.nextNodes.push(updatedNode)
} else { } else {
res.nextNodes.push(parseNode(propElement, this.nodeExtent)); res.nextNodes.push(parseNode(propElement, this.nodeExtent))
} }
} else if (isEdge(propElement)) { } else if (isEdge(propElement)) {
const storeEdge = this.edges.find((se) => se.id === propElement.id); const storeEdge = this.edges.find((se) => se.id === propElement.id)
if (storeEdge) { if (storeEdge) {
res.nextEdges.push({ res.nextEdges.push({
...storeEdge, ...storeEdge,
...propElement ...propElement
}); })
} else { } else {
res.nextEdges.push(parseEdge(propElement)); res.nextEdges.push(parseEdge(propElement))
} }
} }
return res; return res
}, nextElements); }, nextElements)
this.nodes = nextNodes; this.nodes = nextNodes
this.edges = nextEdges; this.edges = nextEdges
}, },
updateNodeDimensions(updates) { updateNodeDimensions(updates) {
this.nodes = this.nodes.map((node) => { this.nodes = this.nodes.map((node) => {
const update = updates.find((u) => u.id === node.id); const update = updates.find((u) => u.id === node.id)
if (update) { if (update) {
const dimensions = getDimensions(update.nodeElement); const dimensions = getDimensions(update.nodeElement)
const doUpdate = const doUpdate =
dimensions.width && dimensions.width &&
dimensions.height && dimensions.height &&
(node.__rf.width !== dimensions.width || node.__rf.height !== dimensions.height || update.forceUpdate); (node.__rf.width !== dimensions.width || node.__rf.height !== dimensions.height || update.forceUpdate)
if (doUpdate) { if (doUpdate) {
const handleBounds = getHandleBounds(update.nodeElement, this.transform[2]); const handleBounds = getHandleBounds(update.nodeElement, this.transform[2])
return { return {
...node, ...node,
@@ -92,23 +92,23 @@ export default function configureStore(
...dimensions, ...dimensions,
handleBounds handleBounds
} }
}; }
} }
} }
return node; return node
}); })
}, },
updateNodePos(payload) { updateNodePos(payload) {
const { id, pos } = payload; const { id, pos } = payload
let position: XYPosition = pos; let position: XYPosition = pos
if (this.snapToGrid) { if (this.snapToGrid) {
const [gridSizeX, gridSizeY] = this.snapGrid; const [gridSizeX, gridSizeY] = this.snapGrid
position = { position = {
x: gridSizeX * Math.round(pos.x / gridSizeX), x: gridSizeX * Math.round(pos.x / gridSizeX),
y: gridSizeY * Math.round(pos.y / gridSizeY) y: gridSizeY * Math.round(pos.y / gridSizeY)
}; }
} }
this.nodes = this.nodes.map((node) => { this.nodes = this.nodes.map((node) => {
@@ -119,14 +119,14 @@ export default function configureStore(
...node.__rf, ...node.__rf,
position position
} }
}; }
} }
return node; return node
}); })
}, },
updateNodePosDiff(payload: NodeDiffUpdate) { updateNodePosDiff(payload: NodeDiffUpdate) {
const { id, diff, isDragging } = payload; const { id, diff, isDragging } = payload
this.nodes = this.nodes.map((node) => { this.nodes = this.nodes.map((node) => {
if (id === node.id || this.selectedElements?.find((sNode) => sNode.id === node.id)) { if (id === node.id || this.selectedElements?.find((sNode) => sNode.id === node.id)) {
@@ -136,23 +136,23 @@ export default function configureStore(
...node.__rf, ...node.__rf,
isDragging isDragging
} }
}; }
if (diff) { if (diff) {
updatedNode.__rf.position = { updatedNode.__rf.position = {
x: node.__rf.position.x + diff.x, x: node.__rf.position.x + diff.x,
y: node.__rf.position.y + diff.y y: node.__rf.position.y + diff.y
}; }
} }
return updatedNode; return updatedNode
} }
return node; return node
}); })
}, },
setUserSelection(mousePos) { setUserSelection(mousePos) {
this.selectionActive = true; this.selectionActive = true
this.userSelectionRect = { this.userSelectionRect = {
width: 0, width: 0,
height: 0, height: 0,
@@ -161,11 +161,11 @@ export default function configureStore(
x: mousePos.x, x: mousePos.x,
y: mousePos.y, y: mousePos.y,
draw: true draw: true
}; }
}, },
updateUserSelection(mousePos) { updateUserSelection(mousePos) {
const startX = this.userSelectionRect.startX || 0; const startX = this.userSelectionRect.startX || 0
const startY = this.userSelectionRect.startY || 0; const startY = this.userSelectionRect.startY || 0
const nextUserSelectRect: RevueFlowState['userSelectionRect'] = { const nextUserSelectRect: RevueFlowState['userSelectionRect'] = {
...this.userSelectionRect, ...this.userSelectionRect,
@@ -173,59 +173,59 @@ export default function configureStore(
y: mousePos.y < startY ? mousePos.y : this.userSelectionRect.y, y: mousePos.y < startY ? mousePos.y : this.userSelectionRect.y,
width: Math.abs(mousePos.x - startX), width: Math.abs(mousePos.x - startX),
height: Math.abs(mousePos.y - startY) height: Math.abs(mousePos.y - startY)
}; }
const selectedNodes = getNodesInside(this.nodes, nextUserSelectRect, this.transform); const selectedNodes = getNodesInside(this.nodes, nextUserSelectRect, this.transform)
const selectedEdges = getConnectedEdges(selectedNodes, this.edges); const selectedEdges = getConnectedEdges(selectedNodes, this.edges)
const nextSelectedElements = [...selectedNodes, ...selectedEdges]; const nextSelectedElements = [...selectedNodes, ...selectedEdges]
this.userSelectionRect = nextUserSelectRect; this.userSelectionRect = nextUserSelectRect
this.selectedElements = nextSelectedElements; this.selectedElements = nextSelectedElements
}, },
unsetUserSelection() { unsetUserSelection() {
const selectedNodes = this.selectedElements?.filter((node) => isNode(node) && node.__rf) as Node[]; const selectedNodes = this.selectedElements?.filter((node) => isNode(node) && node.__rf) as Node[]
this.selectionActive = false; this.selectionActive = false
this.userSelectionRect.draw = false; this.userSelectionRect.draw = false
if (!selectedNodes || selectedNodes.length === 0) { if (!selectedNodes || selectedNodes.length === 0) {
this.selectedElements = null; this.selectedElements = null
this.nodesSelectionActive = false; this.nodesSelectionActive = false
} else { } else {
this.selectedNodesBbox = getRectOfNodes(selectedNodes); this.selectedNodesBbox = getRectOfNodes(selectedNodes)
this.nodesSelectionActive = true; this.nodesSelectionActive = true
} }
}, },
addSelectedElements(elements) { addSelectedElements(elements) {
const selectedElementsArr = Array.isArray(elements) ? elements : [elements]; const selectedElementsArr = Array.isArray(elements) ? elements : [elements]
const selectedElementsUpdated = !isEqual(selectedElementsArr, this.selectedElements); const selectedElementsUpdated = !isEqual(selectedElementsArr, this.selectedElements)
this.selectedElements = selectedElementsUpdated ? selectedElementsArr : this.selectedElements; this.selectedElements = selectedElementsUpdated ? selectedElementsArr : this.selectedElements
}, },
initD3Zoom(payload) { initD3Zoom(payload) {
const { d3Zoom, d3Selection, d3ZoomHandler, transform } = payload; const { d3Zoom, d3Selection, d3ZoomHandler, transform } = payload
this.d3Zoom = d3Zoom; this.d3Zoom = d3Zoom
this.d3Selection = d3Selection; this.d3Selection = d3Selection
this.d3ZoomHandler = d3ZoomHandler; this.d3ZoomHandler = d3ZoomHandler
this.transform = transform; this.transform = transform
}, },
setMinZoom(minZoom) { setMinZoom(minZoom) {
this.d3Zoom?.scaleExtent([minZoom, this.maxZoom]); this.d3Zoom?.scaleExtent([minZoom, this.maxZoom])
this.minZoom = minZoom; this.minZoom = minZoom
}, },
setMaxZoom(maxZoom) { setMaxZoom(maxZoom) {
this.d3Zoom?.scaleExtent([this.minZoom, maxZoom]); this.d3Zoom?.scaleExtent([this.minZoom, maxZoom])
this.maxZoom = maxZoom; this.maxZoom = maxZoom
}, },
setTranslateExtent(translateExtent) { setTranslateExtent(translateExtent) {
this.d3Zoom?.translateExtent(translateExtent); this.d3Zoom?.translateExtent(translateExtent)
this.translateExtent = translateExtent; this.translateExtent = translateExtent
}, },
setNodeExtent(nodeExtent) { setNodeExtent(nodeExtent) {
this.nodeExtent = nodeExtent; this.nodeExtent = nodeExtent
this.nodes = this.nodes.map((node) => { this.nodes = this.nodes.map((node) => {
return { return {
...node, ...node,
@@ -233,29 +233,29 @@ export default function configureStore(
...node.__rf, ...node.__rf,
position: clampPosition(node.__rf.position, nodeExtent) position: clampPosition(node.__rf.position, nodeExtent)
} }
}; }
}); })
}, },
resetSelectedElements() { resetSelectedElements() {
this.selectedElements = null; this.selectedElements = null
}, },
unsetNodesSelection() { unsetNodesSelection() {
this.nodesSelectionActive = false; this.nodesSelectionActive = false
}, },
updateSize(size) { updateSize(size) {
this.height = size.height; this.height = size.height
this.width = size.width; this.width = size.width
}, },
setConnectionNodeId(payload) { setConnectionNodeId(payload) {
this.connectionNodeId = payload.connectionNodeId; this.connectionNodeId = payload.connectionNodeId
this.connectionHandleId = payload.connectionHandleId; this.connectionHandleId = payload.connectionHandleId
this.connectionHandleType = payload.connectionHandleType; this.connectionHandleType = payload.connectionHandleType
}, },
setInteractive(isInteractive) { setInteractive(isInteractive) {
this.nodesDraggable = isInteractive; this.nodesDraggable = isInteractive
this.nodesConnectable = isInteractive; this.nodesConnectable = isInteractive
this.elementsSelectable = isInteractive; this.elementsSelectable = isInteractive
} }
} }
}); })
} }
+2 -2
View File
@@ -1,4 +1,4 @@
import { RevueFlowState, ConnectionMode } from '../types'; import { RevueFlowState, ConnectionMode } from '../types'
export const initialState: RevueFlowState = { export const initialState: RevueFlowState = {
width: 0, width: 0,
@@ -52,4 +52,4 @@ export const initialState: RevueFlowState = {
multiSelectionActive: false, multiSelectionActive: false,
revueFlowVersion: typeof __REVUE_FLOW_VERSION__ !== 'undefined' ? __REVUE_FLOW_VERSION__ : '-' revueFlowVersion: typeof __REVUE_FLOW_VERSION__ !== 'undefined' ? __REVUE_FLOW_VERSION__ : '-'
}; }
+287 -287
View File
@@ -1,14 +1,14 @@
import { Selection as D3Selection, ZoomBehavior } from 'd3'; import { Selection as D3Selection, ZoomBehavior } from 'd3'
import { CSSProperties, DefineComponent, HTMLAttributes, VNode } from 'vue'; import { CSSProperties, DefineComponent, HTMLAttributes, VNode } from 'vue'
import { Store } from 'pinia'; import { Store } from 'pinia'
export type ElementId = string; export type ElementId = string
export type FlowElement<T = any> = Node<T> | Edge<T>; export type FlowElement<T = any> = Node<T> | Edge<T>
export type Elements<T = any> = Array<FlowElement<T>>; export type Elements<T = any> = Array<FlowElement<T>>
export type Transform = [number, number, number]; export type Transform = [number, number, number]
export enum Position { export enum Position {
Left = 'left', Left = 'left',
@@ -18,44 +18,44 @@ export enum Position {
} }
export interface XYPosition { export interface XYPosition {
x: number; x: number
y: number; y: number
} }
export interface Dimensions { export interface Dimensions {
width: number; width: number
height: number; height: number
} }
export interface Rect extends Dimensions, XYPosition {} export interface Rect extends Dimensions, XYPosition {}
export interface Box extends XYPosition { export interface Box extends XYPosition {
x2: number; x2: number
y2: number; y2: number
} }
export type SnapGrid = [number, number]; export type SnapGrid = [number, number]
export interface Node<T = any> { export interface Node<T = any> {
id: ElementId; id: ElementId
position: XYPosition; position: XYPosition
type?: string; type?: string
__rf: { __rf: {
position: XYPosition; position: XYPosition
isDragging?: boolean; isDragging?: boolean
width: number | null; width: number | null
height: number | null; height: number | null
handleBounds?: any; handleBounds?: any
}; }
data?: T; data?: T
style?: any; style?: any
className?: string; className?: string
targetPosition?: Position; targetPosition?: Position
sourcePosition?: Position; sourcePosition?: Position
isHidden?: boolean; isHidden?: boolean
draggable?: boolean; draggable?: boolean
selectable?: boolean; selectable?: boolean
connectable?: boolean; connectable?: boolean
} }
export enum ArrowHeadType { export enum ArrowHeadType {
@@ -64,24 +64,24 @@ export enum ArrowHeadType {
} }
export interface Edge<T = any> { export interface Edge<T = any> {
id: ElementId; id: ElementId
type?: string; type?: string
source: ElementId; source: ElementId
target: ElementId; target: ElementId
sourceHandle?: ElementId | null; sourceHandle?: ElementId | null
targetHandle?: ElementId | null; targetHandle?: ElementId | null
label?: string | VNode; label?: string | VNode
labelStyle?: any; labelStyle?: any
labelShowBg?: boolean; labelShowBg?: boolean
labelBgStyle?: any; labelBgStyle?: any
labelBgPadding?: [number, number]; labelBgPadding?: [number, number]
labelBgBorderRadius?: number; labelBgBorderRadius?: number
style?: any; style?: any
animated?: boolean; animated?: boolean
arrowHeadType?: ArrowHeadType; arrowHeadType?: ArrowHeadType
isHidden?: boolean; isHidden?: boolean
data?: T; data?: T
className?: string; className?: string
} }
export enum BackgroundVariant { export enum BackgroundVariant {
@@ -89,107 +89,107 @@ export enum BackgroundVariant {
Dots = 'dots' Dots = 'dots'
} }
export type HandleType = 'source' | 'target'; export type HandleType = 'source' | 'target'
export type NodeType = DefineComponent<NodeProps>; export type NodeType = DefineComponent<NodeProps>
export type EdgeType = DefineComponent<EdgeSmoothStepProps>; export type EdgeType = DefineComponent<EdgeSmoothStepProps>
export interface SelectionRect extends Rect { export interface SelectionRect extends Rect {
startX: number; startX: number
startY: number; startY: number
draw: boolean; draw: boolean
} }
export interface EdgeProps<T = any> { export interface EdgeProps<T = any> {
id: ElementId; id: ElementId
source: ElementId; source: ElementId
target: ElementId; target: ElementId
sourceX: number; sourceX: number
sourceY: number; sourceY: number
targetX: number; targetX: number
targetY: number; targetY: number
selected?: boolean; selected?: boolean
animated?: boolean; animated?: boolean
sourcePosition: Position; sourcePosition: Position
targetPosition: Position; targetPosition: Position
label?: string | VNode; label?: string | VNode
labelStyle?: any; labelStyle?: any
labelShowBg?: boolean; labelShowBg?: boolean
labelBgStyle?: any; labelBgStyle?: any
labelBgPadding?: [number, number]; labelBgPadding?: [number, number]
labelBgBorderRadius?: number; labelBgBorderRadius?: number
style?: any; style?: any
arrowHeadType?: ArrowHeadType; arrowHeadType?: ArrowHeadType
markerEndId?: string; markerEndId?: string
data?: T; data?: T
sourceHandleId?: ElementId | null; sourceHandleId?: ElementId | null
targetHandleId?: ElementId | null; targetHandleId?: ElementId | null
} }
export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> { export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> {
borderRadius?: number; borderRadius?: number
} }
export interface EdgeTextProps extends HTMLAttributes { export interface EdgeTextProps extends HTMLAttributes {
x: number; x: number
y: number; y: number
label?: string | VNode; label?: string | VNode
labelStyle?: any; labelStyle?: any
labelShowBg?: boolean; labelShowBg?: boolean
labelBgStyle?: any; labelBgStyle?: any
labelBgPadding?: [number, number]; labelBgPadding?: [number, number]
labelBgBorderRadius?: number; labelBgBorderRadius?: number
} }
export interface NodeProps<T = any> { export interface NodeProps<T = any> {
id: ElementId; id: ElementId
type: string; type: string
data: T; data: T
selected: boolean; selected: boolean
isConnectable: boolean; isConnectable: boolean
xPos?: number; xPos?: number
yPos?: number; yPos?: number
targetPosition?: Position; targetPosition?: Position
sourcePosition?: Position; sourcePosition?: Position
isDragging?: boolean; isDragging?: boolean
} }
export type FitViewParams = { export type FitViewParams = {
padding?: number; padding?: number
includeHiddenNodes?: boolean; includeHiddenNodes?: boolean
minZoom?: number; minZoom?: number
maxZoom?: number; maxZoom?: number
}; }
export type FlowExportObject<T = any> = { export type FlowExportObject<T = any> = {
elements: Elements<T>; elements: Elements<T>
position: [number, number]; position: [number, number]
zoom: number; zoom: number
}; }
export type FitViewFunc = (fitViewOptions?: FitViewParams) => void; export type FitViewFunc = (fitViewOptions?: FitViewParams) => void
export type ProjectFunc = (position: XYPosition) => XYPosition; export type ProjectFunc = (position: XYPosition) => XYPosition
export type ToObjectFunc<T = any> = () => FlowExportObject<T>; export type ToObjectFunc<T = any> = () => FlowExportObject<T>
export type OnLoadParams<T = any> = { export type OnLoadParams<T = any> = {
zoomIn: () => void; zoomIn: () => void
zoomOut: () => void; zoomOut: () => void
zoomTo: (zoomLevel: number) => void; zoomTo: (zoomLevel: number) => void
fitView: FitViewFunc; fitView: FitViewFunc
project: ProjectFunc; project: ProjectFunc
getElements: () => Elements<T>; getElements: () => Elements<T>
setTransform: (transform: FlowTransform) => void; setTransform: (transform: FlowTransform) => void
toObject: ToObjectFunc<T>; toObject: ToObjectFunc<T>
}; }
export type OnLoadFunc<T = any> = (params: OnLoadParams<T>) => void; export type OnLoadFunc<T = any> = (params: OnLoadParams<T>) => void
export interface Connection { export interface Connection {
source: ElementId | null; source: ElementId | null
target: ElementId | null; target: ElementId | null
sourceHandle: ElementId | null; sourceHandle: ElementId | null
targetHandle: ElementId | null; targetHandle: ElementId | null
} }
export enum ConnectionMode { export enum ConnectionMode {
@@ -205,69 +205,69 @@ export enum ConnectionLineType {
} }
export type ConnectionLineComponentProps = { export type ConnectionLineComponentProps = {
sourceX: number; sourceX: number
sourceY: number; sourceY: number
sourcePosition?: Position; sourcePosition?: Position
targetX: number; targetX: number
targetY: number; targetY: number
targetPosition?: Position; targetPosition?: Position
connectionLineStyle?: any; connectionLineStyle?: any
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType
}; }
export type ConnectionLineComponent = DefineComponent<ConnectionLineComponentProps>; export type ConnectionLineComponent = DefineComponent<ConnectionLineComponentProps>
export type OnConnectFunc = (connection: Connection) => void; export type OnConnectFunc = (connection: Connection) => void
export type OnConnectStartParams = { export type OnConnectStartParams = {
nodeId: ElementId | null; nodeId: ElementId | null
handleId: ElementId | null; handleId: ElementId | null
handleType: HandleType | null; handleType: HandleType | null
}; }
export type OnConnectStartFunc = (event: MouseEvent, params: OnConnectStartParams) => void; export type OnConnectStartFunc = (event: MouseEvent, params: OnConnectStartParams) => void
export type OnConnectStopFunc = (event: MouseEvent) => void; export type OnConnectStopFunc = (event: MouseEvent) => void
export type OnConnectEndFunc = (event: MouseEvent) => void; export type OnConnectEndFunc = (event: MouseEvent) => void
export type SetConnectionId = { export type SetConnectionId = {
connectionNodeId: ElementId | null; connectionNodeId: ElementId | null
connectionHandleId: ElementId | null; connectionHandleId: ElementId | null
connectionHandleType: HandleType | null; connectionHandleType: HandleType | null
}; }
export interface HandleElement extends XYPosition, Dimensions { export interface HandleElement extends XYPosition, Dimensions {
id?: ElementId | null; id?: ElementId | null
position: Position; position: Position
} }
export interface HandleProps { export interface HandleProps {
type: HandleType; type: HandleType
position: Position; position: Position
isConnectable?: boolean; isConnectable?: boolean
onConnect?: OnConnectFunc; onConnect?: OnConnectFunc
isValidConnection?: (connection: Connection) => boolean; isValidConnection?: (connection: Connection) => boolean
id?: ElementId; id?: ElementId
} }
export type NodePosUpdate = { export type NodePosUpdate = {
id: ElementId; id: ElementId
pos: XYPosition; pos: XYPosition
}; }
export type NodeDiffUpdate = { export type NodeDiffUpdate = {
id?: ElementId; id?: ElementId
diff?: XYPosition; diff?: XYPosition
isDragging?: boolean; isDragging?: boolean
}; }
export type FlowTransform = { export type FlowTransform = {
x: number; x: number
y: number; y: number
zoom: number; zoom: number
}; }
export type TranslateExtent = [[number, number], [number, number]]; export type TranslateExtent = [[number, number], [number, number]]
export type NodeExtent = TranslateExtent; export type NodeExtent = TranslateExtent
export type KeyCode = number | string; export type KeyCode = number | string
export enum PanOnScrollMode { export enum PanOnScrollMode {
Free = 'free', Free = 'free',
@@ -276,150 +276,150 @@ export enum PanOnScrollMode {
} }
export interface ZoomPanHelperFunctions { export interface ZoomPanHelperFunctions {
zoomIn: () => void; zoomIn: () => void
zoomOut: () => void; zoomOut: () => void
zoomTo: (zoomLevel: number) => void; zoomTo: (zoomLevel: number) => void
transform: (transform: FlowTransform) => void; transform: (transform: FlowTransform) => void
fitView: FitViewFunc; fitView: FitViewFunc
setCenter: (x: number, y: number, zoom?: number) => void; setCenter: (x: number, y: number, zoom?: number) => void
fitBounds: (bounds: Rect, padding?: number) => void; fitBounds: (bounds: Rect, padding?: number) => void
project: (position: XYPosition) => XYPosition; project: (position: XYPosition) => XYPosition
} }
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void; export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void
export type NodeDimensionUpdate = { export type NodeDimensionUpdate = {
id: ElementId; id: ElementId
nodeElement: HTMLDivElement; nodeElement: HTMLDivElement
forceUpdate?: boolean; forceUpdate?: boolean
}; }
export type InitD3ZoomPayload = { export type InitD3ZoomPayload = {
d3Zoom: ZoomBehavior<Element, unknown>; d3Zoom: ZoomBehavior<Element, unknown>
d3Selection: D3Selection<Element, unknown, null, undefined>; d3Selection: D3Selection<Element, unknown, null, undefined>
d3ZoomHandler: ((this: Element, event: any, d: unknown) => void) | undefined; d3ZoomHandler: ((this: Element, event: any, d: unknown) => void) | undefined
transform: Transform; transform: Transform
}; }
export interface RevueFlowState { export interface RevueFlowState {
width: number; width: number
height: number; height: number
transform: Transform; transform: Transform
nodes: Node[]; nodes: Node[]
edges: Edge[]; edges: Edge[]
selectedElements: Elements | null; selectedElements: Elements | null
selectedNodesBbox: Rect; selectedNodesBbox: Rect
d3Zoom: ZoomBehavior<Element, unknown> | null; d3Zoom: ZoomBehavior<Element, unknown> | null
d3Selection: D3Selection<Element, unknown, null, undefined> | null; d3Selection: D3Selection<Element, unknown, null, undefined> | null
d3ZoomHandler: ((this: Element, event: any, d: unknown) => void) | undefined; d3ZoomHandler: ((this: Element, event: any, d: unknown) => void) | undefined
minZoom: number; minZoom: number
maxZoom: number; maxZoom: number
translateExtent: TranslateExtent; translateExtent: TranslateExtent
nodeExtent: NodeExtent; nodeExtent: NodeExtent
nodesSelectionActive: boolean; nodesSelectionActive: boolean
selectionActive: boolean; selectionActive: boolean
userSelectionRect: SelectionRect; userSelectionRect: SelectionRect
connectionNodeId: ElementId | null; connectionNodeId: ElementId | null
connectionHandleId: ElementId | null; connectionHandleId: ElementId | null
connectionHandleType: HandleType | null; connectionHandleType: HandleType | null
connectionPosition: XYPosition; connectionPosition: XYPosition
connectionMode: ConnectionMode; connectionMode: ConnectionMode
snapToGrid: boolean; snapToGrid: boolean
snapGrid: SnapGrid; snapGrid: SnapGrid
nodesDraggable: boolean; nodesDraggable: boolean
nodesConnectable: boolean; nodesConnectable: boolean
elementsSelectable: boolean; elementsSelectable: boolean
multiSelectionActive: boolean; multiSelectionActive: boolean
revueFlowVersion: string; revueFlowVersion: string
onConnect?: OnConnectFunc; onConnect?: OnConnectFunc
onConnectStart?: OnConnectStartFunc; onConnectStart?: OnConnectStartFunc
onConnectStop?: OnConnectStopFunc; onConnectStop?: OnConnectStopFunc
onConnectEnd?: OnConnectEndFunc; onConnectEnd?: OnConnectEndFunc
} }
export type RevueFlowActions = { export type RevueFlowActions = {
setElements: (elements: Elements) => void; setElements: (elements: Elements) => void
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void; updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void
updateNodePos: (payload: NodePosUpdate) => void; updateNodePos: (payload: NodePosUpdate) => void
updateNodePosDiff: (payload: NodeDiffUpdate) => void; updateNodePosDiff: (payload: NodeDiffUpdate) => void
setUserSelection: (mousePos: XYPosition) => void; setUserSelection: (mousePos: XYPosition) => void
updateUserSelection: (mousePos: XYPosition) => void; updateUserSelection: (mousePos: XYPosition) => void
unsetUserSelection: () => void; unsetUserSelection: () => void
addSelectedElements: (elements: Elements) => void; addSelectedElements: (elements: Elements) => void
initD3Zoom: (payload: InitD3ZoomPayload) => void; initD3Zoom: (payload: InitD3ZoomPayload) => void
setMinZoom: (zoom: number) => void; setMinZoom: (zoom: number) => void
setMaxZoom: (zoom: number) => void; setMaxZoom: (zoom: number) => void
setTranslateExtent: (translateExtent: TranslateExtent) => void; setTranslateExtent: (translateExtent: TranslateExtent) => void
setNodeExtent: (nodeExtent: NodeExtent) => void; setNodeExtent: (nodeExtent: NodeExtent) => void
resetSelectedElements: () => void; resetSelectedElements: () => void
unsetNodesSelection: () => void; unsetNodesSelection: () => void
updateSize: (size: Dimensions) => void; updateSize: (size: Dimensions) => void
setConnectionNodeId: (payload: SetConnectionId) => void; setConnectionNodeId: (payload: SetConnectionId) => void
setInteractive: (isInteractive: boolean) => void; setInteractive: (isInteractive: boolean) => void
}; }
export type RevueFlowStore = Store<string, RevueFlowState, any, RevueFlowActions>; export type RevueFlowStore = Store<string, RevueFlowState, any, RevueFlowActions>
export type UpdateNodeInternals = (nodeId: ElementId) => void; export type UpdateNodeInternals = (nodeId: ElementId) => void
export interface RevueFlowProps extends Omit<HTMLAttributes, 'onLoad'> { export interface RevueFlowProps extends Omit<HTMLAttributes, 'onLoad'> {
modelValue: Elements; modelValue: Elements
nodeTypes?: Record<string, NodeType>; nodeTypes?: Record<string, NodeType>
edgeTypes?: Record<string, EdgeType>; edgeTypes?: Record<string, EdgeType>
connectionMode?: ConnectionMode; connectionMode?: ConnectionMode
connectionLineType?: ConnectionLineType; connectionLineType?: ConnectionLineType
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties
connectionLineComponent?: ConnectionLineComponent; connectionLineComponent?: ConnectionLineComponent
deleteKeyCode?: KeyCode; deleteKeyCode?: KeyCode
selectionKeyCode?: KeyCode; selectionKeyCode?: KeyCode
multiSelectionKeyCode?: KeyCode; multiSelectionKeyCode?: KeyCode
zoomActivationKeyCode?: KeyCode; zoomActivationKeyCode?: KeyCode
snapToGrid?: boolean; snapToGrid?: boolean
snapGrid?: [number, number]; snapGrid?: [number, number]
onlyRenderVisibleElements?: boolean; onlyRenderVisibleElements?: boolean
nodesDraggable?: boolean; nodesDraggable?: boolean
nodesConnectable?: boolean; nodesConnectable?: boolean
elementsSelectable?: boolean; elementsSelectable?: boolean
selectNodesOnDrag?: boolean; selectNodesOnDrag?: boolean
paneMoveable?: boolean; paneMoveable?: boolean
minZoom?: number; minZoom?: number
maxZoom?: number; maxZoom?: number
defaultZoom?: number; defaultZoom?: number
defaultPosition?: [number, number]; defaultPosition?: [number, number]
translateExtent?: TranslateExtent; translateExtent?: TranslateExtent
nodeExtent?: NodeExtent; nodeExtent?: NodeExtent
arrowHeadColor?: string; arrowHeadColor?: string
markerEndId?: string; markerEndId?: string
zoomOnScroll?: boolean; zoomOnScroll?: boolean
zoomOnPinch?: boolean; zoomOnPinch?: boolean
panOnScroll?: boolean; panOnScroll?: boolean
panOnScrollSpeed?: number; panOnScrollSpeed?: number
panOnScrollMode?: PanOnScrollMode; panOnScrollMode?: PanOnScrollMode
zoomOnDoubleClick?: boolean; zoomOnDoubleClick?: boolean
edgeUpdaterRadius?: number; edgeUpdaterRadius?: number
nodeTypesId?: string; nodeTypesId?: string
edgeTypesId?: string; edgeTypesId?: string
} }
export interface GraphViewProps extends Required<RevueFlowProps> { export interface GraphViewProps extends Required<RevueFlowProps> {
selectionKeyCode: KeyCode; selectionKeyCode: KeyCode
deleteKeyCode: KeyCode; deleteKeyCode: KeyCode
multiSelectionKeyCode: KeyCode; multiSelectionKeyCode: KeyCode
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType
snapToGrid: boolean; snapToGrid: boolean
snapGrid: [number, number]; snapGrid: [number, number]
onlyRenderVisibleElements: boolean; onlyRenderVisibleElements: boolean
defaultZoom: number; defaultZoom: number
defaultPosition: [number, number]; defaultPosition: [number, number]
arrowHeadColor: string; arrowHeadColor: string
selectNodesOnDrag: boolean; selectNodesOnDrag: boolean
} }
+91 -91
View File
@@ -1,4 +1,4 @@
import { clampPosition, clamp } from './index'; import { clampPosition, clamp } from './index'
import { import {
ElementId, ElementId,
@@ -13,47 +13,47 @@ import {
FlowExportObject, FlowExportObject,
NodeExtent, NodeExtent,
RevueFlowStore RevueFlowStore
} from '../types'; } from '../types'
export const isEdge = (element: Node | Connection | Edge): element is Edge => export const isEdge = (element: Node | Connection | Edge): element is Edge =>
'id' in element && 'source' in element && 'target' in element; 'id' in element && 'source' in element && 'target' in element
export const isNode = (element: Node | Connection | Edge): element is Node => export const isNode = (element: Node | Connection | Edge): element is Node =>
'id' in element && !('source' in element) && !('target' in element); 'id' in element && !('source' in element) && !('target' in element)
export const getOutgoers = (node: Node, elements: Elements): Node[] => { export const getOutgoers = (node: Node, elements: Elements): Node[] => {
if (!isNode(node)) { if (!isNode(node)) {
return []; return []
} }
const outgoerIds = elements.filter((e) => isEdge(e) && e.source === node.id).map((e) => (e as Edge).target); const outgoerIds = elements.filter((e) => isEdge(e) && e.source === node.id).map((e) => (e as Edge).target)
return elements.filter((e) => outgoerIds.includes(e.id)) as Node[]; return elements.filter((e) => outgoerIds.includes(e.id)) as Node[]
}; }
export const getIncomers = (node: Node, elements: Elements): Node[] => { export const getIncomers = (node: Node, elements: Elements): Node[] => {
if (!isNode(node)) { if (!isNode(node)) {
return []; return []
} }
const incomersIds = elements.filter((e) => isEdge(e) && e.target === node.id).map((e) => (e as Edge).source); const incomersIds = elements.filter((e) => isEdge(e) && e.target === node.id).map((e) => (e as Edge).source)
return elements.filter((e) => incomersIds.includes(e.id)) as Node[]; return elements.filter((e) => incomersIds.includes(e.id)) as Node[]
}; }
export const removeElements = (elementsToRemove: Elements, elements: Elements): Elements => { export const removeElements = (elementsToRemove: Elements, elements: Elements): Elements => {
const nodeIdsToRemove = elementsToRemove.map((n) => n.id); const nodeIdsToRemove = elementsToRemove.map((n) => n.id)
return elements.filter((element) => { return elements.filter((element) => {
const edgeElement = element as Edge; const edgeElement = element as Edge
return !( return !(
nodeIdsToRemove.includes(element.id) || nodeIdsToRemove.includes(element.id) ||
nodeIdsToRemove.includes(edgeElement.target) || nodeIdsToRemove.includes(edgeElement.target) ||
nodeIdsToRemove.includes(edgeElement.source) nodeIdsToRemove.includes(edgeElement.source)
); )
}); })
}; }
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId => const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): ElementId =>
`revueflow__edge-${source}${sourceHandle}-${target}${targetHandle}`; `revueflow__edge-${source}${sourceHandle}-${target}${targetHandle}`
const connectionExists = (edge: Edge, elements: Elements) => { const connectionExists = (edge: Edge, elements: Elements) => {
return elements.some( return elements.some(
@@ -63,43 +63,43 @@ const connectionExists = (edge: Edge, elements: Elements) => {
el.target === edge.target && el.target === edge.target &&
(el.sourceHandle === edge.sourceHandle || (!el.sourceHandle && !edge.sourceHandle)) && (el.sourceHandle === edge.sourceHandle || (!el.sourceHandle && !edge.sourceHandle)) &&
(el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle)) (el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle))
); )
}; }
export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => { export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => {
if (!edgeParams.source || !edgeParams.target) { if (!edgeParams.source || !edgeParams.target) {
console.log("Can't create edge. An edge needs a source and a target."); console.log("Can't create edge. An edge needs a source and a target.")
return elements; return elements
} }
let edge: Edge; let edge: Edge
if (isEdge(edgeParams)) { if (isEdge(edgeParams)) {
edge = { ...edgeParams }; edge = { ...edgeParams }
} else { } else {
edge = { edge = {
...edgeParams, ...edgeParams,
id: getEdgeId(edgeParams) id: getEdgeId(edgeParams)
} as Edge; } as Edge
} }
if (connectionExists(edge, elements)) { if (connectionExists(edge, elements)) {
return elements; return elements
} }
return elements.concat(edge); return elements.concat(edge)
}; }
export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements => { export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements => {
if (!newConnection.source || !newConnection.target) { if (!newConnection.source || !newConnection.target) {
console.warn("Can't create new edge. An edge needs a source and a target."); console.warn("Can't create new edge. An edge needs a source and a target.")
return elements; return elements
} }
const foundEdge = elements.find((e) => isEdge(e) && e.id === oldEdge.id) as Edge; const foundEdge = elements.find((e) => isEdge(e) && e.id === oldEdge.id) as Edge
if (!foundEdge) { if (!foundEdge) {
console.warn(`The old edge with id=${oldEdge.id} does not exist.`); console.warn(`The old edge with id=${oldEdge.id} does not exist.`)
return elements; return elements
} }
// Remove old edge and create the new edge with parameters of old edge. // Remove old edge and create the new edge with parameters of old edge.
@@ -110,10 +110,10 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: E
target: newConnection.target, target: newConnection.target,
sourceHandle: newConnection.sourceHandle, sourceHandle: newConnection.sourceHandle,
targetHandle: newConnection.targetHandle targetHandle: newConnection.targetHandle
} as Edge; } as Edge
return elements.filter((e) => e.id !== oldEdge.id).concat(edge); return elements.filter((e) => e.id !== oldEdge.id).concat(edge)
}; }
export const pointToRendererPoint = ( export const pointToRendererPoint = (
{ x, y }: XYPosition, { x, y }: XYPosition,
@@ -124,23 +124,23 @@ export const pointToRendererPoint = (
const position: XYPosition = { const position: XYPosition = {
x: (x - tx) / tScale, x: (x - tx) / tScale,
y: (y - ty) / tScale y: (y - ty) / tScale
}; }
if (snapToGrid) { if (snapToGrid) {
return { return {
x: snapX * Math.round(position.x / snapX), x: snapX * Math.round(position.x / snapX),
y: snapY * Math.round(position.y / snapY) y: snapY * Math.round(position.y / snapY)
}; }
} }
return position; return position
}; }
export const onLoadProject = (currentStore: RevueFlowStore) => { export const onLoadProject = (currentStore: RevueFlowStore) => {
return (position: XYPosition): XYPosition => { return (position: XYPosition): XYPosition => {
return pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid); return pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid)
}; }
}; }
export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => { export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => {
return { return {
@@ -154,8 +154,8 @@ export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => {
handleBounds: {}, handleBounds: {},
isDragging: false isDragging: false
} }
}; }
}; }
export const parseEdge = (edge: Edge): Edge => { export const parseEdge = (edge: Edge): Edge => {
return { return {
@@ -166,32 +166,32 @@ export const parseEdge = (edge: Edge): Edge => {
targetHandle: edge.targetHandle ? edge.targetHandle.toString() : null, targetHandle: edge.targetHandle ? edge.targetHandle.toString() : null,
id: edge.id.toString(), id: edge.id.toString(),
type: edge.type || 'default' type: edge.type || 'default'
}; }
}; }
const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({ const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
x: Math.min(box1.x, box2.x), x: Math.min(box1.x, box2.x),
y: Math.min(box1.y, box2.y), y: Math.min(box1.y, box2.y),
x2: Math.max(box1.x2, box2.x2), x2: Math.max(box1.x2, box2.x2),
y2: Math.max(box1.y2, box2.y2) y2: Math.max(box1.y2, box2.y2)
}); })
export const rectToBox = ({ x, y, width, height }: Rect): Box => ({ export const rectToBox = ({ x, y, width, height }: Rect): Box => ({
x, x,
y, y,
x2: x + width, x2: x + width,
y2: y + height y2: y + height
}); })
export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
x, x,
y, y,
width: x2 - x, width: x2 - x,
height: y2 - y height: y2 - y
}); })
export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect => export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect =>
boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2))); boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)))
export const getRectOfNodes = (nodes: Node[]): Rect => { export const getRectOfNodes = (nodes: Node[]): Rect => {
const box = nodes.reduce( const box = nodes.reduce(
@@ -205,15 +205,15 @@ export const getRectOfNodes = (nodes: Node[]): Rect => {
} as any) } as any)
), ),
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }
); )
return boxToRect(box); return boxToRect(box)
}; }
export const graphPosToZoomedPos = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => ({ export const graphPosToZoomedPos = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => ({
x: x * tScale + tx, x: x * tScale + tx,
y: y * tScale + ty y: y * tScale + ty
}); })
export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false): Node[] => { export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false): Node[] => {
const rBox = rectToBox({ const rBox = rectToBox({
@@ -221,54 +221,54 @@ export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Tran
y: (rect.y - ty) / tScale, y: (rect.y - ty) / tScale,
width: rect.width / tScale, width: rect.width / tScale,
height: rect.height / tScale height: rect.height / tScale
}); })
return nodes.filter(({ __rf: { position, width, height, isDragging } }) => { return nodes.filter(({ __rf: { position, width, height, isDragging } }) => {
const nBox = rectToBox({ ...position, width, height } as any); const nBox = rectToBox({ ...position, width, height } as any)
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x)); const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x))
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y)); const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y))
const overlappingArea = Math.ceil(xOverlap * yOverlap); const overlappingArea = Math.ceil(xOverlap * yOverlap)
if (width === null || height === null || isDragging) { if (width === null || height === null || isDragging) {
// nodes are initialized with width and height = null // nodes are initialized with width and height = null
return true; return true
} }
if (partially) { if (partially) {
return overlappingArea > 0; return overlappingArea > 0
} }
const area = width * height; const area = width * height
return overlappingArea >= area; return overlappingArea >= area
}); })
}; }
export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => { export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
const nodeIds = nodes.map((node) => node.id); const nodeIds = nodes.map((node) => node.id)
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target)); return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target))
}; }
const parseElements = (nodes: Node[], edges: Edge[]): Elements => { const parseElements = (nodes: Node[], edges: Edge[]): Elements => {
return [ return [
...nodes.map((node) => { ...nodes.map((node) => {
const n = { ...node }; const n = { ...node }
n.position = n.__rf.position; n.position = n.__rf.position
n.__rf = {} as any; n.__rf = {} as any
return n; return n
}), }),
...edges.map((e) => ({ ...e })) ...edges.map((e) => ({ ...e }))
]; ]
}; }
export const onLoadGetElements = (currentStore: RevueFlowStore) => { export const onLoadGetElements = (currentStore: RevueFlowStore) => {
return (): Elements => { return (): Elements => {
return parseElements(currentStore.nodes || [], currentStore.edges || []); return parseElements(currentStore.nodes || [], currentStore.edges || [])
}; }
}; }
export const onLoadToObject = (currentStore: RevueFlowStore) => { export const onLoadToObject = (currentStore: RevueFlowStore) => {
return (): FlowExportObject => { return (): FlowExportObject => {
@@ -276,9 +276,9 @@ export const onLoadToObject = (currentStore: RevueFlowStore) => {
elements: parseElements(currentStore.nodes || [], currentStore.edges || []), elements: parseElements(currentStore.nodes || [], currentStore.edges || []),
position: [currentStore.transform[0], currentStore.transform[1]], position: [currentStore.transform[0], currentStore.transform[1]],
zoom: currentStore.transform[2] zoom: currentStore.transform[2]
}; }
}; }
}; }
export const getTransformForBounds = ( export const getTransformForBounds = (
bounds: Rect, bounds: Rect,
@@ -288,14 +288,14 @@ export const getTransformForBounds = (
maxZoom: number, maxZoom: number,
padding = 0.1 padding = 0.1
): Transform => { ): Transform => {
const xZoom = width / (bounds.width * (1 + padding)); const xZoom = width / (bounds.width * (1 + padding))
const yZoom = height / (bounds.height * (1 + padding)); const yZoom = height / (bounds.height * (1 + padding))
const zoom = Math.min(xZoom, yZoom); const zoom = Math.min(xZoom, yZoom)
const clampedZoom = clamp(zoom, minZoom, maxZoom); const clampedZoom = clamp(zoom, minZoom, maxZoom)
const boundsCenterX = bounds.x + bounds.width / 2; const boundsCenterX = bounds.x + bounds.width / 2
const boundsCenterY = bounds.y + bounds.height / 2; const boundsCenterY = bounds.y + bounds.height / 2
const x = width / 2 - boundsCenterX * clampedZoom; const x = width / 2 - boundsCenterX * clampedZoom
const y = height / 2 - boundsCenterY * clampedZoom; const y = height / 2 - boundsCenterY * clampedZoom
return [x, y, clampedZoom]; return [x, y, clampedZoom]
}; }
+8 -8
View File
@@ -1,21 +1,21 @@
import { Dimensions, XYPosition, NodeExtent } from '../types'; import { Dimensions, XYPosition, NodeExtent } from '../types'
export const isInputDOMNode = (e: KeyboardEvent | MouseEvent): boolean => { export const isInputDOMNode = (e: KeyboardEvent | MouseEvent): boolean => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement
return ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName) || target.hasAttribute('contentEditable'); return ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName) || target.hasAttribute('contentEditable')
}; }
export const getDimensions = (node: HTMLDivElement): Dimensions => ({ export const getDimensions = (node: HTMLDivElement): Dimensions => ({
width: node.offsetWidth, width: node.offsetWidth,
height: node.offsetHeight height: node.offsetHeight
}); })
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max); export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max)
export const clampPosition = (position: XYPosition, extent: NodeExtent): XYPosition => ({ export const clampPosition = (position: XYPosition, extent: NodeExtent): XYPosition => ({
x: clamp(position.x, extent[0][0], extent[1][0]), x: clamp(position.x, extent[0][0], extent[1][0]),
y: clamp(position.y, extent[0][1], extent[1][1]) y: clamp(position.y, extent[0][1], extent[1][1])
}); })
export const getHostForElement = (element: HTMLElement): Document | ShadowRoot => export const getHostForElement = (element: HTMLElement): Document | ShadowRoot =>
(element.getRootNode() as Document | ShadowRoot) || window.document; (element.getRootNode() as Document | ShadowRoot) || window.document
+6 -6
View File
@@ -1,7 +1,7 @@
import { InjectionKey } from 'vue'; import { InjectionKey } from 'vue'
import { RevueFlowStore } from '../types'; import { RevueFlowStore } from '../types'
import { RevueFlowHooks } from '../hooks/RevueFlowHooks'; import { RevueFlowHooks } from '../hooks/RevueFlowHooks'
export const StoreKey: InjectionKey<RevueFlowStore> = Symbol('store'); export const StoreKey: InjectionKey<RevueFlowStore> = Symbol('store')
export const HooksKey: InjectionKey<RevueFlowHooks> = Symbol('hooks'); export const HooksKey: InjectionKey<RevueFlowHooks> = Symbol('hooks')
export const NodeIdContextKey: InjectionKey<string> = Symbol('NodeIdContext'); export const NodeIdContextKey: InjectionKey<string> = Symbol('NodeIdContext')
+35 -21
View File
@@ -1,28 +1,42 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "baseUrl": ".",
"module": "esnext", "module": "ESNext",
"moduleResolution": "node", "target": "es2016",
"lib": [
"DOM",
"ESNext"
],
"strict": true, "strict": true,
"jsx": "preserve",
"jsxFactory": "",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["es6", "dom", "es2016", "es2017", "es2020"], "incremental": false,
"types": ["@types/node"], "skipLibCheck": true,
"noImplicitAny": true, "moduleResolution": "node",
"strictNullChecks": true, "resolveJsonModule": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "strictNullChecks": true,
"noImplicitReturns": false, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "jsx": "preserve",
"allowSyntheticDefaultImports": true, "types": [
"vite/client",
"@types/node"
],
"paths": {
"~/*": [
"src/*"
]
}
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "include": [
"exclude": ["node_modules", "build", "dist", "example"] "src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"exclude": [
"node_modules",
"build",
"dist",
"example"
]
} }
+21 -6
View File
@@ -1,12 +1,27 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'; import { resolve } from 'path'
import vueJsx from '@vitejs/plugin-vue-jsx'; import vue from '@vitejs/plugin-vue'
import svgLoader from 'vite-svg-loader'; import vueJsx from '@vitejs/plugin-vue-jsx'
import svgLoader from 'vite-svg-loader'
import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
resolve: {
alias: {
'~/': `${resolve(__dirname, 'src')}/`
}
},
build: { build: {
outDir: 'build' outDir: 'build'
}, },
plugins: [vue(), vueJsx(), svgLoader()] plugins: [
}); vue(),
vueJsx(),
svgLoader(), // https://github.com/antfu/unplugin-auto-import
AutoImport({
imports: ['vue', '@vueuse/core'],
dts: 'src/auto-imports.d.ts'
})
]
})