refactor: back to jsx 🥇

This commit is contained in:
Braks
2021-07-24 21:08:34 +02:00
parent a6f7678c0f
commit 1d542aba65
31 changed files with 666 additions and 558 deletions

View File

@@ -14,7 +14,7 @@
import { defineComponent, PropType } from 'vue';
import { ConnectionLineComponentProps } from '../../src';
const ConnectionLine = defineComponent({
export default defineComponent({
props: {
sourceX: {
type: Number as PropType<ConnectionLineComponentProps['sourceX']>,
@@ -34,6 +34,4 @@ const ConnectionLine = defineComponent({
}
}
});
export default ConnectionLine;
</script>

View File

@@ -1,9 +1,9 @@
<template>
<RevueFlow
v-model="elements"
:connectionLineComponent="ConnectionLine"
:onElementsRemove="onElementsRemove"
:onConnect="onConnect"
:connection-line-component="ConnectionLine"
@elementsRemove="onElementsRemove"
@connect="onConnect"
>
<Background :variant="bgVariant" />
</RevueFlow>
@@ -14,10 +14,17 @@ import RevueFlow, { removeElements, addEdge, Background, BackgroundVariant, Elem
import ConnectionLine from './ConnectionLine.vue';
import { defineComponent, ref } from 'vue';
const ConnectionLineFlow = defineComponent({
export default defineComponent({
components: { RevueFlow, Background },
setup() {
const elements = ref<Elements>([{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }]);
const elements = ref<Elements>([
{
id: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 }
}
] as Elements);
const onElementsRemove = (elementsToRemove: Elements) =>
(elements.value = removeElements(elementsToRemove, elements.value as Elements));
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value as Elements));
@@ -31,6 +38,4 @@ const ConnectionLineFlow = defineComponent({
};
}
});
export default ConnectionLineFlow;
</script>

View File

@@ -116,7 +116,7 @@ const CustomNodeFlow = defineComponent({
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }
];
] as Elements;
});
const onElementsRemove = (elementsToRemove: Elements) =>

View File

@@ -1,14 +1,7 @@
<template>
<div class="dndflow">
<div class="revueflow-wrapper" @drop="onDrop">
<RevueFlow
v-model="elements"
:onConnect="onConnect"
:onElementsRemove="onElementsRemove"
:onLoad="onLoad"
@dragover="onDragOver"
@click="onDrop"
>
<RevueFlow v-model="elements" @elementsRemove="onElementsRemove" @load="onLoad" @connect="onConnect" @dragover="onDragOver">
<Controls />
</RevueFlow>
</div>
@@ -43,7 +36,7 @@ const DnDFlow = defineComponent({
data: { label: 'input node' },
position: { x: 250, y: 5 }
}
]);
] as Elements);
let id = 0;
const getId = (): ElementId => `dndnode_${id++}`;
@@ -67,12 +60,12 @@ const DnDFlow = defineComponent({
console.log(event.dataTransfer?.getData('application/revueflow'));
const type = event.dataTransfer?.getData('application/revueflow');
const position = revueFlowInstance.value.project({ x: event.clientX, y: event.clientY - 40 });
const newNode: Node = {
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` }
};
} as Node;
elements.value.push(newNode);
}

View File

@@ -28,7 +28,7 @@
"lint": "yarn lint:js"
},
"dependencies": {
"@braks/revue-draggable": "^0.2.4",
"@braks/revue-draggable": "^0.2.5",
"@types/d3": "^7.0.0",
"@vueuse/core": "^5.1.4",
"d3": "^7.0.0",

View File

@@ -90,7 +90,7 @@ const Controls = defineComponent({
const store = inject<RevueFlowStore>('store')!;
const isVisible = ref<boolean>(false);
const zoomHelper = ref<ZoomPanHelperFunctions>();
const { onReady } = useZoomPanHelper(store);
const { onReady } = useZoomPanHelper();
onReady((helper) => {
zoomHelper.value = helper;

View File

@@ -1,23 +1,4 @@
<template>
<g v-show="sourceNode && nodesConnectable" class="revue-flow__connection">
<g v-if="CustomConnectionLineComponent" class="revue-flow__connection">
<component
:is="CustomConnectionLineComponent"
:sourceX="sourceX"
:sourceY="sourceY"
:sourcePosition="sourceHandle.position"
:targetX="targetX"
:targetY="targetY"
:targetPosition="targetPosition"
:connectionLineType="connectionLineType"
:connectionLineStyle="connectionLineStyle"
/>
</g>
<path v-else class="revue-flow__connection-path" :d="dAttr" :style="connectionLineStyle" />
</g>
</template>
<script lang="ts">
import { ref, defineComponent, CSSProperties, PropType, computed, inject } from 'vue';
import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue';
import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
@@ -26,23 +7,23 @@ import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineCompon
interface ConnectionLineProps {
connectionLineType: ConnectionLineType;
connectionLineStyle?: CSSProperties;
CustomConnectionLineComponent?: ConnectionLineComponent;
customConnectionLine?: ConnectionLineComponent;
}
const ConnectionLine = defineComponent({
export default defineComponent({
props: {
connectionLineType: {
type: String as PropType<ConnectionLineProps['connectionLineType']>,
required: false,
default: ConnectionLineType.Bezier
},
connectionLineStyle: {
type: Object as PropType<ConnectionLineProps['connectionLineStyle']>,
required: false,
default: () => {}
},
CustomConnectionLineComponent: {
type: Object as PropType<ConnectionLineProps['CustomConnectionLineComponent']>,
connectionLineType: {
type: String as PropType<ConnectionLineProps['connectionLineType']>,
required: false,
default: ConnectionLineType.Bezier
},
customConnectionLine: {
type: Object as PropType<ConnectionLineProps['customConnectionLine']>,
required: false,
default: undefined
}
@@ -114,20 +95,31 @@ const ConnectionLine = defineComponent({
);
}
return {
dAttr,
targetX,
targetY,
sourceX,
sourceY,
sourceHandle,
sourceNode,
sourceHandleX,
sourceHandleY,
nodesConnectable
};
if (props.customConnectionLine) {
return () => (
<g class="revue-flow__connection">
{props.customConnectionLine &&
h(props.customConnectionLine, {
sourceX: sourceX.value,
sourceY: sourceY.value,
sourcePosition: sourceHandle.value?.position,
targetX: targetX.value,
targetY: targetY.value,
targetPosition: targetPosition.value,
connectionLineType: props.connectionLineType,
connectionLineStyle: props.connectionLineStyle
})}
</g>
);
}
return () =>
nodesConnectable.value && sourceNode.value ? (
<g class="revue-flow__connection">
<path d={dAttr.value} class="revue-flow__connection-path" style={props.connectionLineStyle} />
</g>
) : (
''
);
}
});
export default ConnectionLine;
</script>

View File

@@ -121,7 +121,7 @@ const BezierEdge = defineComponent({
style: {
type: Object as PropType<CSSProperties>,
required: false,
default: () => ({})
default: () => {}
}
},
setup(props) {

View File

@@ -0,0 +1,255 @@
import { EdgeAnchor } from './EdgeAnchor';
import { ConnectionMode, Edge, Elements, Node, Position, RevueFlowStore, Transform } from '../../types';
import { computed, defineComponent, h, inject, PropType, ref } from 'vue';
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../container/EdgeRenderer/utils';
import { isEdge } from '../../utils/graph';
import { onMouseDown } from '../Handle/handler';
interface EdgeProps {
edge: Edge;
nodes: Node[];
selectedElements: Elements | null;
elementsSelectable: boolean;
transform: Transform;
width: number;
height: number;
onlyRenderVisibleElements: boolean;
connectionMode?: ConnectionMode;
markerEndId?: string;
edgeUpdaterRadius?: number;
}
export default defineComponent({
components: { EdgeAnchor },
props: {
edge: {
type: Object as PropType<EdgeProps['edge']>,
required: true
},
markerEndId: {
type: Object as PropType<EdgeProps['markerEndId']>,
required: false,
default: undefined
},
edgeUpdaterRadius: {
type: Number as PropType<EdgeProps['edgeUpdaterRadius']>,
required: false,
default: undefined
},
type: {
type: Object,
required: true
},
onlyRenderVisibleElements: {
type: Boolean as PropType<EdgeProps['onlyRenderVisibleElements']>,
required: false,
default: false
},
connectionMode: {
type: String as PropType<EdgeProps['connectionMode']>,
required: false,
default: undefined
}
},
setup(props) {
const store = inject<RevueFlowStore>('store')!;
const hooks = inject<RevueFlowHooks>('hooks')!;
const nodes = computed(() => getSourceTargetNodes(props.edge, store.nodes));
hooks.connect.on((connection) => {
hooks.edgeUpdate.trigger({ edge: props.edge, connection });
});
if (!nodes.value.sourceNode) {
console.warn(`couldn't create edge for source id: ${props.edge.source}; edge id: ${props.edge.id}`);
}
if (!nodes.value.targetNode) {
console.warn(`couldn't create edge for target id: ${props.edge.target}; edge id: ${props.edge.id}`);
}
const targetNodeBounds = computed(() => nodes.value.targetNode?.__rf.handleBounds);
// when connection type is loose we can define all handles as sources
const targetNodeHandles = computed(() =>
props.connectionMode === ConnectionMode.Strict
? targetNodeBounds.value?.target
: targetNodeBounds.value?.target || targetNodeBounds.value?.source
);
const sourceHandle = computed(
() => nodes.value.sourceNode && getHandle(nodes.value.sourceNode.__rf.handleBounds.source, props.edge.sourceHandle || null)
);
const targetHandle = computed(() => getHandle(targetNodeHandles.value, props.edge.targetHandle || null));
const sourcePosition = computed(() => (sourceHandle.value ? sourceHandle.value.position : Position.Bottom));
const targetPosition = computed(() => (targetHandle.value ? targetHandle.value.position : Position.Top));
const edgePositions = computed<{
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
}>(() => {
return getEdgePositions(
nodes.value.sourceNode as Node,
sourceHandle.value,
sourcePosition.value,
nodes.value.targetNode as Node,
targetHandle.value,
targetPosition.value
);
});
const isVisible = computed(() => {
return props.onlyRenderVisibleElements
? edgePositions.value &&
isEdgeVisible({
sourcePos: { x: edgePositions.value?.sourceX, y: edgePositions.value?.sourceY },
targetPos: { x: edgePositions.value?.targetX, y: edgePositions.value?.targetY },
width: store.width || 0,
height: store.height || 0,
transform: store.transform
})
: true;
});
const isSelected = computed(() => store.selectedElements?.some((elm) => isEdge(elm) && elm.id === props.edge.id) || false);
const updating = ref<boolean>(false);
const inactive = computed(() => !store.elementsSelectable);
const edgeClasses = computed(() => [
'revue-flow__edge',
`revue-flow__edge-${props.type}`,
{ selected: isSelected.value, animated: props.edge.animated, inactive: inactive.value, updating: updating.value }
]);
const edgeElement = computed<Edge>(() => {
const el: Edge = {
id: props.edge.id || '',
source: props.edge.source,
target: props.edge.target,
type: props.edge.type
};
if (props.edge.sourceHandle) {
el.sourceHandle = props.edge.sourceHandle;
}
if (props.edge.targetHandle) {
el.targetHandle = props.edge.targetHandle;
}
if (typeof props.edge.data !== 'undefined') {
el.data = props.edge.data;
}
return el;
});
const onEdgeClick = (event: MouseEvent) => {
if (store.elementsSelectable) {
store.unsetNodesSelection();
store.addSelectedElements(edgeElement.value as any);
}
hooks.edgeClick.trigger({ event, edge: edgeElement.value });
};
const onEdgeContextMenu = (event: MouseEvent) =>
hooks.edgeContextMenu.trigger({
event,
edge: edgeElement.value
});
const onEdgeMouseEnter = (event: MouseEvent) => hooks.edgeMouseEnter.trigger({ event, edge: edgeElement.value });
const onEdgeMouseMove = (event: MouseEvent) => hooks.edgeMouseMove.trigger({ event, edge: edgeElement.value });
const onEdgeMouseLeave = (event: MouseEvent) => hooks.edgeMouseLeave.trigger({ event, edge: edgeElement.value });
const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
const nodeId = isSourceHandle ? props.edge.target : props.edge.source;
const handleId = isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle;
const isValidConnection = () => true;
const isTarget = isSourceHandle;
hooks.edgeUpdateStart.trigger({ event, edge: edgeElement.value });
onMouseDown(
event,
store,
hooks,
handleId as string,
nodeId,
isTarget,
isValidConnection,
isSourceHandle ? 'target' : 'source'
);
};
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => {
handleEdgeUpdater(event, true);
};
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => {
handleEdgeUpdater(event, false);
};
const onEdgeUpdaterMouseEnter = () => (updating.value = true);
const onEdgeUpdaterMouseOut = () => (updating.value = false);
return () =>
isVisible.value ? (
<g
class={edgeClasses.value}
onClick={onEdgeClick}
onContextmenu={onEdgeContextMenu}
onMouseenter={onEdgeMouseEnter}
onMousemove={onEdgeMouseMove}
onMouseleave={onEdgeMouseLeave}
>
{h(props.type, {
id: props.edge.id,
source: props.edge.source,
target: props.edge.target,
selected: isSelected,
animated: props.edge.animated,
label: props.edge.label,
labelStyle: props.edge.labelStyle,
labelShowBg: props.edge.labelShowBg,
labelBgStyle: props.edge.labelBgStyle,
labelBgPadding: props.edge.labelBgPadding,
labelBgBorderRadius: props.edge.labelBgBorderRadius,
data: props.edge.data,
style: props.edge.style,
arrowHeadType: props.edge.arrowHeadType,
sourceX: edgePositions.value.sourceX,
sourceY: edgePositions.value.sourceY,
targetX: edgePositions.value.targetX,
targetY: edgePositions.value.targetY,
sourcePosition: sourcePosition.value,
targetPosition: targetPosition.value,
markerEndId: props.markerEndId,
sourceHandleId: props.edge.sourceHandle,
targetHandleId: props.edge.targetHandle
}, {})}
<g onMousedown={onEdgeUpdaterSourceMouseDown} onMouseenter={onEdgeUpdaterMouseEnter} onMouseout={onEdgeUpdaterMouseOut}>
<EdgeAnchor
position={sourcePosition.value}
centerX={edgePositions.value.sourceX}
centerY={edgePositions.value.sourceY}
radius={props.edgeUpdaterRadius}
/>
</g>
<g onMousedown={onEdgeUpdaterTargetMouseDown} onMouseenter={onEdgeUpdaterMouseEnter} onMouseout={onEdgeUpdaterMouseOut}>
<EdgeAnchor
position={sourcePosition.value}
centerX={edgePositions.value.sourceX}
centerY={edgePositions.value.sourceY}
radius={props.edgeUpdaterRadius}
/>
</g>
</g>
) : (
''
);
}
});

View File

@@ -144,8 +144,6 @@ export default defineComponent({
targetY: number;
}>(() => {
return (
nodes.value.sourceNode &&
nodes.value.targetNode &&
getEdgePositions(
nodes.value.sourceNode,
sourceHandle.value,

View File

@@ -1,70 +0,0 @@
import { ElementId, Position, RevueFlowStore } from '../../types';
import { onMouseDown, ValidConnectionFunc } from './handler';
import { computed, defineComponent, h, inject, PropType } from 'vue';
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
const alwaysValid = () => true;
export default defineComponent({
props: {
type: {
type: String,
required: false,
default: 'source'
},
position: {
type: String as PropType<Position>,
required: false,
default: Position.Top
},
isValidConnection: {
type: Function as PropType<ValidConnectionFunc>,
required: false,
default: alwaysValid
},
isConnectable: {
type: Boolean,
required: false,
default: true
},
id: {
type: String,
required: false,
default: undefined
}
},
setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!;
const hooks = inject<RevueFlowHooks>('hooks')!;
const nodeId = inject<ElementId>('NodeIdContext') as ElementId;
const isTarget = computed(() => props.type === 'target');
const onMouseDownHandler = (event: MouseEvent) => {
onMouseDown(event, store, hooks, props.id || '', nodeId, isTarget.value, props.isValidConnection);
};
const handleClasses = computed(() => [
'revue-flow__handle',
`revue-flow__handle-${props.position}`,
'nodrag',
{
source: !isTarget.value,
target: isTarget.value,
connectable: props.isConnectable
}
]);
return () =>
h(
'div',
{
dataHandleid: props.id,
dataNodeid: nodeId,
dataHandlepos: props.position,
class: handleClasses.value,
onMouseDown: onMouseDownHandler
},
slots.default?.()
);
}
});

View File

@@ -1,11 +1,8 @@
import { getHostForElement } from '../../utils';
import { ElementId, XYPosition, ConnectionMode, SetConnectionId, Connection, HandleType, RevueFlowStore } from '../../types';
import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '../../types';
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
export type ValidConnectionFunc = (connection: Connection) => boolean;
export type SetSourceIdFunc = (params: SetConnectionId) => void;
export type SetPosition = (pos: XYPosition) => void;
type Result = {
elementBelow: Element | null;

View File

@@ -5,7 +5,7 @@ import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
const alwaysValid = () => true;
const Handle = defineComponent({
export default defineComponent({
props: {
type: {
type: String,
@@ -41,21 +41,18 @@ const Handle = defineComponent({
setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!;
const hooks = inject<RevueFlowHooks>('hooks')!;
const nodeId = inject<ElementId>('NodeIdContext') as ElementId;
const isTarget = computed(() => props.type === 'target');
const nodeId = inject<ElementId>('NodeIdContext')!;
const onMouseDownHandler = (event: MouseEvent) => {
console.log('mousedown');
onMouseDown(event, store, hooks, props.id as string, nodeId, isTarget.value, props.isValidConnection);
};
const onMouseDownHandler = (event: MouseEvent) =>
onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection);
const handleClasses = computed(() => [
'revue-flow__handle',
`revue-flow__handle-${props.position}`,
'nodrag',
{
source: !isTarget.value,
target: isTarget.value,
source: props.type !== 'target',
target: props.type === 'target',
connectable: props.isConnectable
}
]);
@@ -73,5 +70,3 @@ const Handle = defineComponent({
);
}
});
export default Handle;

View File

@@ -57,7 +57,7 @@ export default defineComponent({
required: true
},
type: {
type: Object,
type: [Object, String],
required: true
},
snapToGrid: {

View File

@@ -38,9 +38,6 @@ export default defineComponent({
const store = inject<RevueFlowStore>('store')!;
const el = templateRef('user-selection', null);
const shouldRender = computed(() => store.selectionActive || store.elementsSelectable);
if (!shouldRender.value) {
return null;
}
const onMouseDown = (event: MouseEvent) => {
const mousePos = getMousePosition(event);
@@ -52,12 +49,10 @@ export default defineComponent({
};
const onMouseMove = (event: MouseEvent) => {
if (!store.selectionActive) {
return;
}
const mousePos = getMousePosition(event);
if (!mousePos) {
return;
}
@@ -80,19 +75,22 @@ export default defineComponent({
useEventListener(el, 'mouseup', onMouseUp);
useEventListener(el, 'mouseleave', onMouseLeave);
return () => (
<div class="revue-flow__selectionpane" ref="user-selection">
{store?.userSelectionRect.draw ? (
<SelectionRect
width={store.userSelectionRect.width}
height={store.userSelectionRect.height}
x={store.userSelectionRect.x}
y={store.userSelectionRect.y}
/>
) : (
''
)}
</div>
);
return () =>
shouldRender.value ? (
<div class="revue-flow__selectionpane" ref="user-selection">
{store?.userSelectionRect.draw ? (
<SelectionRect
width={store.userSelectionRect.width}
height={store.userSelectionRect.height}
x={store.userSelectionRect.x}
y={store.userSelectionRect.y}
/>
) : (
''
)}
</div>
) : (
''
);
}
});

View File

@@ -0,0 +1,22 @@
import { FunctionalComponent } from 'vue';
interface MarkerProps {
id: string;
}
export default ((props: MarkerProps, { slots }) => {
return () => (
<marker
id={props.id}
class="revue-flow__arrowhead"
markerWidth="12.5"
markerHeight="12.5"
viewBox="-10 -10 20 20"
orient="auto"
refX="0"
refY="0"
>
{slots.default ? slots.default() : ''}
</marker>
);
}) as FunctionalComponent<MarkerProps>;

View File

@@ -1,30 +0,0 @@
<template>
<marker
:id="id"
class="revue-flow__arrowhead"
markerWidth="12.5"
markerHeight="12.5"
viewBox="-10 -10 20 20"
orient="auto"
refX="0"
refY="0"
>
<slot></slot>
</marker>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
interface MarkerProps {
id: string;
}
export default defineComponent({
props: {
id: {
type: String as PropType<MarkerProps['id']>,
required: true
}
}
});
</script>

View File

@@ -0,0 +1,42 @@
import { defineComponent, PropType } from 'vue';
import Marker from './Marker';
interface MarkerDefinitionsProps {
color: string;
}
export default defineComponent({
components: { Marker },
props: {
color: {
type: String as PropType<MarkerDefinitionsProps['color']>,
required: true
}
},
setup(props) {
return () => (
<defs>
<Marker id="revue-flow__arrowclosed">
<polyline
stroke={props.color}
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
fill={props.color}
points="-5,-4 0,0 -5,4 -5,-4"
/>
</Marker>
<Marker id="revue-flow__arrow">
<polyline
stroke={props.color}
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
fill="none"
points="-5,-4 0,0 -5,4"
/>
</Marker>
</defs>
);
}
});

View File

@@ -1,45 +0,0 @@
<template>
<defs>
<Marker id="revue-flow__arrowclosed">
<polyline
:stroke="color"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
:fill="color"
points="-5,-4 0,0 -5,4 -5,-4"
/>
</Marker>
<Marker id="revue-flow__arrow">
<polyline
:stroke="color"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
fill="none"
points="-5,-4 0,0 -5,4"
/>
</Marker>
</defs>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import Marker from './Marker.vue';
interface MarkerDefinitionsProps {
color: string;
}
const MarkerDefinitions = defineComponent({
name: 'MarkerDefinitions',
components: { Marker },
props: {
color: {
type: String as PropType<MarkerDefinitionsProps['color']>,
required: true
}
}
});
export default MarkerDefinitions;
</script>

View File

@@ -1,30 +1,8 @@
<template>
<svg :width="width" :height="height" class="revue-flow__edges">
<MarkerDefinitions :color="arrowHeadColor" />
<g :transform="transformStyle">
<template v-for="edge of edges" :key="edge.id">
<Edge
:edge="edge"
:marker-end-id="markerEndId"
:onlyRenderVisibleElements="onlyRenderVisibleElements"
:type="edgeTypes[edge.type]"
/>
<ConnectionLine
v-if="renderConnectionLine"
:connectionLineStyle="connectionLineStyle"
:connectionLineType="connectionLineType"
:CustomConnectionLineComponent="connectionLineComponent"
/>
</template>
</g>
</svg>
</template>
<script lang="ts">
import { computed, CSSProperties, defineComponent, inject, PropType } from 'vue';
import Edge from '../../components/Edges/Edge.vue';
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue';
import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types';
import MarkerDefinitions from './MarkerDefinitions.vue';
import ConnectionLine from '../../components/ConnectionLine';
import MarkerDefinitions from './MarkerDefinitions';
import Edge from '../../components/Edges/Edge';
interface EdgeRendererProps {
edgeTypes: any;
@@ -38,7 +16,7 @@ interface EdgeRendererProps {
edgeUpdaterRadius?: number;
}
const EdgeRenderer = defineComponent({
export default defineComponent({
name: 'EdgeRenderer',
components: {
Edge,
@@ -48,8 +26,7 @@ const EdgeRenderer = defineComponent({
props: {
edgeTypes: {
type: Object,
required: false,
default: undefined
required: true
},
connectionLineType: {
type: String as PropType<EdgeRendererProps['connectionLineType']>,
@@ -92,27 +69,36 @@ const EdgeRenderer = defineComponent({
default: undefined
}
},
setup() {
const store = inject<RevueFlowStore>('store')!;
const edges = computed(() => store.edges);
const transform = computed(() => store.transform);
setup(props) {
const store = inject<RevueFlowStore>('store');
const transform = computed(() => store?.transform);
const transformStyle = computed(() => {
return `translate(${transform.value?.[0]},${transform.value?.[1]}) scale(${transform.value?.[2]})`;
});
const renderConnectionLine = computed(() => store.connectionNodeId && store.connectionHandleType);
const width = computed(() => store.width);
const height = computed(() => store.height);
const renderConnectionLine = computed(() => store?.connectionNodeId && store?.connectionHandleType);
return {
transform,
transformStyle,
renderConnectionLine,
edges,
width,
height
};
return () => (
<svg width={store?.width} height={store?.height} class="revue-flow__edges">
<MarkerDefinitions color={props.arrowHeadColor as string} />
<g transform={transformStyle.value}>
{store?.edges.map((edge) => (
<Edge
key={edge.id}
edge={edge}
type={props.edgeTypes[edge.type || 'default']}
onlyRenderVisibleElements={props.onlyRenderVisibleElements}
markerEndId={props.markerEndId}
/>
))}
{renderConnectionLine.value && (
<ConnectionLine
connectionLineStyle={props.connectionLineStyle}
connectionLineType={props.connectionLineType}
customConnectionLine={props.connectionLineComponent}
/>
)}
</g>
</svg>
);
}
});
export default EdgeRenderer;
</script>

View File

@@ -1,26 +1,3 @@
<template>
<ZoomPane
:selectionKeyPressed="keyPressed"
:elementsSelectable="elementsSelectable"
:zoomOnScroll="zoomOnScroll"
:zoomOnPinch="zoomOnPinch"
:panOnScroll="panOnScroll"
:panOnScrollSpeed="panOnScrollSpeed"
:panOnScrollMode="panOnScrollMode"
:zoomOnDoubleClick="zoomOnDoubleClick"
:paneMoveable="paneMoveable"
:defaultPosition="defaultPosition"
:defaultZoom="defaultZoom"
:translateExtent="translateExtent"
:zoomActivationKeyCode="zoomActivationKeyCode"
>
<slot></slot>
<UserSelection v-if="keyPressed" />
<NodesSelection v-if="selectionActive" />
<div class="revue-flow__pane" @click="onClick" @onContextmenu="onContextMenu" @wheel="onWheel" />
</ZoomPane>
</template>
<script lang="ts">
import { computed, defineComponent, inject, PropType } from 'vue';
import ZoomPane from '../ZoomPane';
import UserSelection from '../../components/UserSelection';
@@ -43,7 +20,7 @@ type FlowRendererProps = Omit<
| 'selectNodesOnDrag'
>;
const FlowRenderer = defineComponent({
export default defineComponent({
name: 'FlowRenderer',
components: { UserSelection, ZoomPane, NodesSelection },
props: {
@@ -123,7 +100,7 @@ const FlowRenderer = defineComponent({
default: undefined
}
},
setup(props) {
setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!;
const hooks = inject<RevueFlowHooks>('hooks')!;
const keyPressed = useKeyPress(props.selectionKeyCode);
@@ -147,15 +124,27 @@ const FlowRenderer = defineComponent({
const selectionActive = computed(() => store.nodesSelectionActive);
return {
onClick,
onContextMenu,
onWheel,
keyPressed,
selectionActive
};
return () => (
<ZoomPane
selectionKeyPressed={keyPressed.value}
elementsSelectable={props.elementsSelectable}
zoomOnScroll={props.zoomOnScroll}
zoomOnPinch={props.zoomOnPinch}
panOnScroll={props.panOnScroll}
panOnScrollSpeed={props.panOnScrollSpeed}
panOnScrollMode={props.panOnScrollMode}
zoomOnDoubleClick={props.zoomOnDoubleClick}
paneMoveable={props.paneMoveable}
defaultPosition={props.defaultPosition}
defaultZoom={props.defaultZoom}
translateExtent={props.translateExtent}
zoomActivationKeyCode={props.zoomActivationKeyCode}
>
{slots.default ? slots.default() : ''}
{keyPressed.value ? <UserSelection /> : ''}
{selectionActive.value ? <NodesSelection /> : ''}
<div class="revue-flow__pane" onClick={onClick} onContextmenu={onContextMenu} onWheel={onWheel} />
</ZoomPane>
);
}
});
export default FlowRenderer;
</script>

View File

@@ -1,52 +1,52 @@
<template>
<FlowRenderer
:deleteKeyCode="deleteKeyCode"
:selectionKeyCode="selectionKeyCode"
:multiSelectionKeyCode="multiSelectionKeyCode"
:zoomActivationKeyCode="zoomActivationKeyCode"
:elementsSelectable="elementsSelectable"
:zoomOnScroll="zoomOnScroll"
:zoomOnPinch="zoomOnPinch"
:zoomOnDoubleClick="zoomOnDoubleClick"
:panOnScroll="panOnScroll"
:panOnScrollSpeed="panOnScrollSpeed"
:panOnScrollMode="panOnScrollMode"
:paneMoveable="paneMoveable"
:defaultPosition="defaultPosition"
:defaultZoom="defaultZoom"
:translateExtent="translateExtent"
:delete-key-code="deleteKeyCode"
:selection-key-code="selectionKeyCode"
:multi-selection-key-code="multiSelectionKeyCode"
:zoom-activation-key-code="zoomActivationKeyCode"
:elements-selectable="elementsSelectable"
:zoom-on-scroll="zoomOnScroll"
:zoom-on-pinch="zoomOnPinch"
:zoom-on-double-click="zoomOnDoubleClick"
:pan-on-scroll="panOnScroll"
:pan-on-scroll-speed="panOnScrollSpeed"
:pan-on-scroll-mode="panOnScrollMode"
:pane-moveable="paneMoveable"
:default-position="defaultPosition"
:default-zoom="defaultZoom"
:translate-extent="translateExtent"
>
<NodeRenderer
:nodeTypes="nodeTypes"
:selectNodesOnDrag="selectNodesOnDrag"
:snapToGrid="snapToGrid"
:snapGrid="snapGrid"
:onlyRenderVisibleElements="onlyRenderVisibleElements"
:node-types="nodeTypes"
:select-nodes-on-drag="selectNodesOnDrag"
:snap-to-grid="snapToGrid"
:snap-grid="snapGrid"
:only-render-visible-elements="onlyRenderVisibleElements"
/>
<EdgeRenderer
:edgeTypes="edgeTypes"
:connectionLineType="connectionLineType"
:connectionLineStyle="connectionLineStyle"
:connectionLineComponent="connectionLineComponent"
:connectionMode="connectionMode"
:arrowHeadColor="arrowHeadColor"
:markerEndId="markerEndId"
:onlyRenderVisibleElements="onlyRenderVisibleElements"
:edgeUpdaterRadius="edgeUpdaterRadius"
:edge-types="edgeTypes"
:connection-line-type="connectionLineType"
:connection-line-style="connectionLineStyle"
:connection-line-component="connectionLineComponent"
:connection-mode="connectionMode"
:arrow-head-color="arrowHeadColor"
:marker-end-id="markerEndId"
:only-render-visible-elements="onlyRenderVisibleElements"
:edge-updater-radius="edgeUpdaterRadius"
/>
</FlowRenderer>
</template>
<script lang="ts">
import { defineComponent, inject, PropType, CSSProperties, watchEffect } from 'vue';
import FlowRenderer from '../FlowRenderer/FlowRenderer.vue';
import NodeRenderer from '../NodeRenderer/NodeRenderer.vue';
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue';
import FlowRenderer from '../FlowRenderer';
import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer';
import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph';
import { GraphViewProps, RevueFlowStore } from '../../types';
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
const GraphView = defineComponent({
export default defineComponent({
name: 'GraphView',
components: { NodeRenderer, EdgeRenderer, FlowRenderer },
props: {
@@ -220,20 +220,18 @@ const GraphView = defineComponent({
const store = inject<RevueFlowStore>('store')!;
const hooks = inject<RevueFlowHooks>('hooks')!;
const { onReady } = useZoomPanHelper(store);
onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView, initialized }) => {
if (initialized) {
hooks.load.trigger({
fitView: (params = { padding: 0.1 }) => fitView(params),
zoomIn,
zoomOut,
zoomTo,
setTransform: transform,
project: onLoadProject(store),
getElements: onLoadGetElements(store),
toObject: onLoadToObject(store)
});
}
const { onReady } = useZoomPanHelper();
onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView }) => {
hooks.load.trigger({
fitView: (params = { padding: 0.1 }) => fitView(params),
zoomIn,
zoomOut,
zoomTo,
setTransform: transform,
project: onLoadProject(store),
getElements: onLoadGetElements(store),
toObject: onLoadToObject(store)
});
});
const init = () => {
@@ -281,6 +279,4 @@ const GraphView = defineComponent({
});
}
});
export default GraphView;
</script>

View File

@@ -1,21 +1,7 @@
<template>
<div class="revue-flow__nodes" :style="transformStyle">
<template v-for="(node, i) of visibleNodes" :key="`node-${i}`">
<Node
:node="node"
:snap-grid="snapGrid"
:snap-to-grid="snapToGrid"
:select-nodes-on-drag="selectNodesOnDrag"
:type="nodeTypes[node.type]"
/>
</template>
</div>
</template>
<script lang="ts">
import { getNodesInside } from '../../utils/graph';
import { NodeTypesType, RevueFlowStore } from '../../types';
import { computed, defineComponent, inject, PropType } from 'vue';
import Node from '../../components/Nodes/Node.vue';
import { getNodesInside } from '../../utils/graph';
import { NodeTypesType, RevueFlowStore } from '../../types';
interface NodeRendererProps {
nodeTypes: NodeTypesType;
@@ -25,7 +11,7 @@ interface NodeRendererProps {
onlyRenderVisibleElements: boolean;
}
const NodeRenderer = defineComponent({
export default defineComponent({
name: 'NodeRenderer',
components: { Node },
props: {
@@ -79,12 +65,28 @@ const NodeRenderer = defineComponent({
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
}));
return {
transformStyle,
visibleNodes
};
return () => (
<div class="revue-flow__nodes" style={transformStyle.value}>
{visibleNodes.value?.map((node) => {
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}
/>
);
}
})}
</div>
);
}
});
export default NodeRenderer;
</script>

View File

@@ -1,39 +1,39 @@
<template>
<div class="revue-flow">
<GraphView
:nodeTypes="nodeTypesParsed"
:edgeTypes="edgeTypesParsed"
:connectionMode="connectionMode"
:connectionLineType="connectionLineType"
:connectionLineStyle="connectionLineStyle"
:connectionLineComponent="connectionLineComponent"
:selectionKeyCode="selectionKeyCode"
:deleteKeyCode="deleteKeyCode"
:multiSelectionKeyCode="multiSelectionKeyCode"
:zoomActivationKeyCode="zoomActivationKeyCode"
:snapToGrid="snapToGrid"
:snapGrid="snapGrid"
:onlyRenderVisibleElements="onlyRenderVisibleElements"
:nodesDraggable="nodesDraggable"
:nodesConnectable="nodesConnectable"
:elementsSelectable="elementsSelectable"
:selectNodesOnDrag="selectNodesOnDrag"
:minZoom="minZoom"
:maxZoom="maxZoom"
:defaultZoom="defaultZoom"
:defaultPosition="defaultPosition"
:translateExtent="translateExtent"
:nodeExtent="nodeExtent"
:arrowHeadColor="arrowHeadColor"
:markerEndId="markerEndId"
:zoomOnScroll="zoomOnScroll"
:zoomOnPinch="zoomOnPinch"
:zoomOnDoubleClick="zoomOnDoubleClick"
:panOnScroll="panOnScroll"
:panOnScrollSpeed="panOnScrollSpeed"
:panOnScrollMode="panOnScrollMode"
:paneMoveable="paneMoveable"
:edgeUpdaterRadius="edgeUpdaterRadius"
:node-types="nodeTypesParsed"
:edge-types="edgeTypesParsed"
:connection-mode="connectionMode"
:connection-line-type="connectionLineType"
:connection-line-style="connectionLineStyle"
:connection-line-component="connectionLineComponent"
:selection-key-code="selectionKeyCode"
:delete-key-code="deleteKeyCode"
:multi-selection-key-code="multiSelectionKeyCode"
:zoom-activation-key-code="zoomActivationKeyCode"
:snap-to-grid="snapToGrid"
:snap-grid="snapGrid"
:only-render-visible-elements="onlyRenderVisibleElements"
:nodes-draggable="nodesDraggable"
:nodes-connectable="nodesConnectable"
:elements-selectable="elementsSelectable"
:select-nodes-on-drag="selectNodesOnDrag"
:min-zoom="minZoom"
:max-zoom="maxZoom"
:default-zoom="defaultZoom"
:default-position="defaultPosition"
:translate-extent="translateExtent"
:node-extent="nodeExtent"
:arrow-head-color="arrowHeadColor"
:marker-end-id="markerEndId"
:zoom-on-scroll="zoomOnScroll"
:zoom-on-pinch="zoomOnPinch"
:zoom-on-double-click="zoomOnDoubleClick"
:pan-on-scroll="panOnScroll"
:pan-on-scroll-speed="panOnScrollSpeed"
:pan-on-scroll-mode="panOnScrollMode"
:pane-moveable="paneMoveable"
:edge-updater-radius="edgeUpdaterRadius"
/>
<slot></slot>
</div>
@@ -68,8 +68,7 @@ const defaultEdgeTypes = {
smoothstep: SmoothStepEdge
};
const eventHooks = useRevueFlow();
const RevueFlow = defineComponent({
export default defineComponent({
name: 'RevueFlow',
components: { GraphView },
props: {
@@ -254,12 +253,12 @@ const RevueFlow = defineComponent({
default: 10
}
},
emits: Object.keys(eventHooks),
emits: Object.keys(useRevueFlow()),
setup(props, { emit }) {
const store = configureStore(initialState)();
provide<RevueFlowStore>('store', store);
eventHooks.bind(emit);
provide<RevueFlowHooks>('hooks', eventHooks);
const hooks = useRevueFlow().bind(emit);
provide<RevueFlowHooks>('hooks', hooks);
const elements = useVModel(props, 'modelValue', emit);
store.setElements(elements.value);
@@ -289,5 +288,4 @@ const RevueFlow = defineComponent({
};
}
});
export default RevueFlow;
</script>

View File

@@ -1,11 +1,12 @@
import { zoom, zoomIdentity } from 'd3-zoom';
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom';
import { select, pointer } from 'd3-selection';
import { clamp } from '../../utils';
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types';
import { defineComponent, inject, PropType, ref, watchEffect } from 'vue';
import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler';
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
import { templateRef, whenever } from '@vueuse/core';
interface ZoomPaneProps {
selectionKeyPressed: boolean;
@@ -20,38 +21,12 @@ interface ZoomPaneProps {
defaultPosition?: [number, number];
defaultZoom?: number;
translateExtent?: TranslateExtent;
onMove?: (flowTransform?: FlowTransform) => void;
onMoveStart?: (flowTransform?: FlowTransform) => void;
onMoveEnd?: (flowTransform?: FlowTransform) => void;
zoomActivationKeyCode?: KeyCode;
}
const viewChanged = (prevTransform: FlowTransform, eventTransform: any): boolean =>
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k;
const eventToFlowTransform = (eventTransform: any): FlowTransform => ({
x: eventTransform.x,
y: eventTransform.y,
zoom: eventTransform.k
});
const ZoomPane = defineComponent({
export default defineComponent({
name: 'ZoomPane',
props: {
onMove: {
type: Function as unknown as PropType<ZoomPaneProps['onMove']>,
required: false,
default: () => {}
},
onMoveStart: {
type: Function as unknown as PropType<ZoomPaneProps['onMoveStart']>,
required: false,
default: () => {}
},
onMoveEnd: {
type: Function as unknown as PropType<ZoomPaneProps['onMoveEnd']>,
required: false,
default: () => {}
},
zoomOnScroll: {
type: Boolean as PropType<ZoomPaneProps['zoomOnScroll']>,
required: false,
@@ -85,12 +60,12 @@ const ZoomPane = defineComponent({
selectionKeyPressed: {
type: Boolean as PropType<ZoomPaneProps['selectionKeyPressed']>,
required: false,
default: undefined
default: false
},
elementsSelectable: {
type: Boolean as PropType<ZoomPaneProps['elementsSelectable']>,
required: false,
default: undefined
default: true
},
paneMoveable: {
type: Boolean as PropType<ZoomPaneProps['paneMoveable']>,
@@ -120,16 +95,23 @@ const ZoomPane = defineComponent({
},
setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!;
const zoomPane = ref<HTMLDivElement | null>(null);
const hooks = inject<RevueFlowHooks>('hooks')!;
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null);
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 });
const zoomActivationKeyPressed = useKeyPress(props.zoomActivationKeyCode);
watchEffect(() => {
if (zoomPane.value) useResizeHandler(zoomPane.value, store.updateSize);
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k;
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
x: eventTransform.x,
y: eventTransform.y,
zoom: eventTransform.k
});
watchEffect(() => {
if (zoomPane.value && props.defaultPosition && props.defaultZoom) {
const { onReady } = useResizeHandler(zoomPane, store.updateSize);
onReady(() => {
if (props.defaultPosition && props.defaultZoom) {
const currentTranslateExtent =
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent;
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent);
@@ -152,43 +134,41 @@ const ZoomPane = defineComponent({
}
});
watchEffect(() => {
if (store.d3Selection && store.d3Zoom) {
if (props.panOnScroll && zoomActivationKeyPressed) {
store.d3Selection
.on('wheel', (event: any) => {
event.preventDefault();
event.stopImmediatePropagation();
whenever(zoomActivationKeyPressed, () => {
if (props.panOnScroll) {
store.d3Selection
?.on('wheel', (event: WheelEvent) => {
event.preventDefault();
event.stopImmediatePropagation();
const currentZoom = store.d3Selection?.property('__zoom').k || 1;
const currentZoom = store.d3Selection?.property('__zoom').k || 1;
if (event.ctrlKey && props.zoomOnPinch) {
const point = pointer(event);
// 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 zoom = currentZoom * Math.pow(2, pinchDelta);
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point);
if (event.ctrlKey && props.zoomOnPinch) {
const point = pointer(event);
// 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 zoom = currentZoom * Math.pow(2, pinchDelta);
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point);
return;
}
return;
}
// increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
// increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
if (store.d3Selection && props.panOnScrollSpeed)
store.d3Zoom?.translateBy(
store.d3Selection,
-(deltaX / currentZoom) * props.panOnScrollSpeed,
-(deltaY / currentZoom) * props.panOnScrollSpeed
);
})
.on('wheel.zoom', null);
} else if (typeof store.d3ZoomHandler !== 'undefined') {
store.d3Selection.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler);
}
if (store.d3Selection && props.panOnScrollSpeed)
store.d3Zoom?.translateBy(
store.d3Selection,
-(deltaX / currentZoom) * props.panOnScrollSpeed,
-(deltaY / currentZoom) * props.panOnScrollSpeed
);
})
.on('wheel.zoom', null);
} else if (typeof store.d3ZoomHandler !== 'undefined') {
store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler);
}
});
@@ -197,13 +177,9 @@ const ZoomPane = defineComponent({
if (props.selectionKeyPressed) {
store.d3Zoom.on('zoom', null);
} else {
store.d3Zoom.on('zoom', (event: any) => {
store.d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
store.transform = [event.transform.x, event.transform.y, event.transform.k];
if (props.onMove) {
const flowTransform = eventToFlowTransform(event.transform);
props.onMove(flowTransform);
}
hooks.move.trigger(eventToFlowTransform(event.transform));
});
}
}
@@ -211,41 +187,33 @@ const ZoomPane = defineComponent({
watchEffect(() => {
if (store.d3Zoom) {
if (props.onMoveStart) {
store.d3Zoom.on('start', (event: any) => {
if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.value = flowTransform;
store.d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.value = flowTransform;
if (props.onMoveStart) props.onMoveStart(flowTransform);
}
});
} else {
store.d3Zoom.on('start', null);
}
hooks.moveStart.trigger(flowTransform);
}
});
}
});
watchEffect(() => {
if (store.d3Zoom) {
if (props.onMoveEnd) {
store.d3Zoom.on('end', (event: any) => {
if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.value = flowTransform;
store.d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.value = flowTransform;
if (props.onMoveEnd) props.onMoveEnd(flowTransform);
}
});
} else {
store.d3Zoom.on('end', null);
}
hooks.moveEnd.trigger(flowTransform);
}
});
}
});
watchEffect(() => {
if (store.d3Zoom) {
store.d3Zoom.filter((event: any) => {
store.d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = props.zoomOnScroll;
const pinchZoom = props.zoomOnPinch && event.ctrlKey;
@@ -264,20 +232,20 @@ const ZoomPane = defineComponent({
return false;
}
if (event.target.closest('.nowheel') && event.type === 'wheel') {
if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') {
return false;
}
// when the target element is a node, we still allow zooming
if (
(event.target.closest('.revue-flow__node') || event.target.closest('.revue-flow__edge')) &&
((event.target as Element).closest('.revue-flow__node') || (event.target as Element).closest('.revue-flow__edge')) &&
event.type !== 'wheel'
) {
return false;
}
// when the target element is a node selection, we still allow zooming
if (event.target.closest('.revue-flow__nodesselection') && event.type !== 'wheel') {
if ((event.target as Element).closest('.revue-flow__nodesselection') && event.type !== 'wheel') {
return false;
}
@@ -302,11 +270,9 @@ const ZoomPane = defineComponent({
});
return () => (
<div class="revue-flow__renderer revue-flow__zoompane" ref={zoomPane}>
<div class="revue-flow__renderer revue-flow__zoompane" ref="zoom-pane">
{slots.default ? slots.default() : ''}
</div>
);
}
});
export default ZoomPane;

View File

@@ -44,7 +44,7 @@ export interface RevueFlowHooks {
edgeUpdateEnd: EventHook<{ event: MouseEvent; edge: Edge }>;
}
export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, ...args: any[]) => void) => void } => {
export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, ...args: any[]) => void) => RevueFlowHooks } => {
const eventHooks: RevueFlowHooks = {
elementClick: createEventHook<{ event: MouseEvent; element: Node | Edge }>(),
elementsRemove: createEventHook<Elements>(),
@@ -93,6 +93,8 @@ export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string,
emit(key, data);
});
}
return eventHooks;
};
return {

View File

@@ -1,10 +1,11 @@
import { createEventHook, MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core';
import { getDimensions } from '../utils';
import { Dimensions } from '../types';
import { MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core';
export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimensions) => any) => {
const resizeHandlerHook = createEventHook();
const updateDimensions = () => {
if (!rendererNode) {
if (!unrefElement(rendererNode)) {
return;
}
@@ -23,5 +24,10 @@ export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimen
updateDimensions();
useEventListener(window, 'resize', updateDimensions);
useResizeObserver(unrefElement(rendererNode), () => updateDimensions());
resizeHandlerHook.trigger(true);
});
return {
onReady: resizeHandlerHook.on
};
};

View File

@@ -65,8 +65,7 @@ const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } =>
project: (position: XYPosition) => {
const { transform, snapToGrid, snapGrid } = store;
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
},
initialized: true
}
});
until(d3Zoom)

15
src/shims-tsx.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// @ts-ignore
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

View File

@@ -666,7 +666,6 @@ export interface ZoomPanHelperFunctions {
setCenter: (x: number, y: number, zoom?: number) => void;
fitBounds: (bounds: Rect, padding?: number) => void;
project: (position: XYPosition) => XYPosition;
initialized: boolean;
}
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void;

View File

@@ -262,10 +262,10 @@
"@babel/helper-validator-identifier" "^7.14.5"
to-fast-properties "^2.0.0"
"@braks/revue-draggable@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@braks/revue-draggable/-/revue-draggable-0.2.4.tgz#a3af0c27f32a1845f4182ae579ce80cc88fd9019"
integrity sha512-8hDMuO7fl5KCshmg87aK3bQt371f3jPOZ35ip4gdcalP2fEnusVv8+AjWP1DHR2lObDTjovn30LzCK4Ily08LA==
"@braks/revue-draggable@^0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@braks/revue-draggable/-/revue-draggable-0.2.5.tgz#28a94ee4633aab25bcee28877511e0c9ad262f59"
integrity sha512-iKGLFXaurfEqoyPbkANv8ZuO1229krVvUQDDYN9le5WlVssFWcQtA3hNvBD2TPuwil226qXv9SqXGp5kgDhAsA==
dependencies:
"@vueuse/core" "^5.1.3"
vue-demi latest