feat(nodes): connect on touch device
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import ReactFlow, {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
|
Position,
|
||||||
|
Connection,
|
||||||
|
addEdge,
|
||||||
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
|
import './touch-device.css';
|
||||||
|
|
||||||
|
const initialNodes: Node[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
data: { label: 'Node 1' },
|
||||||
|
position: { x: 100, y: 100 },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
data: { label: 'Node 2' },
|
||||||
|
position: { x: 300, y: 100 },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialEdges: Edge[] = [];
|
||||||
|
|
||||||
|
const TouchDeviceFlow = () => {
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TouchDeviceFlow;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.react-flow .react-flow__handle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #9f7aea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle.connecting {
|
||||||
|
animation: bounce 1600ms infinite ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, -50%) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(0, -50%) scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import Layouting from './Layouting';
|
|||||||
import NestedNodes from './NestedNodes';
|
import NestedNodes from './NestedNodes';
|
||||||
import Hidden from './Hidden';
|
import Hidden from './Hidden';
|
||||||
import UpdatableEdge from './UpdatableEdge';
|
import UpdatableEdge from './UpdatableEdge';
|
||||||
|
import TouchDevice from './TouchDevice';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -51,6 +52,10 @@ const routes = [
|
|||||||
path: '/updatable-edge',
|
path: '/updatable-edge',
|
||||||
component: UpdatableEdge,
|
component: UpdatableEdge,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/touch-device',
|
||||||
|
component: TouchDevice,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Header = withRouter(({ history, location }) => {
|
const Header = withRouter(({ history, location }) => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type Result = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 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(
|
export function checkElementBelowIsValid(
|
||||||
event: MouseEvent,
|
event: MouseEvent,
|
||||||
connectionMode: ConnectionMode,
|
connectionMode: ConnectionMode,
|
||||||
isTarget: boolean,
|
isTarget: boolean,
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import shallow from 'zustand/shallow';
|
|||||||
import { useStore, useStoreApi } from '../../store';
|
import { useStore, useStoreApi } from '../../store';
|
||||||
import NodeIdContext from '../../contexts/NodeIdContext';
|
import NodeIdContext from '../../contexts/NodeIdContext';
|
||||||
import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
|
import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
|
||||||
import { onMouseDown } from './handler';
|
import { checkElementBelowIsValid, onMouseDown } from './handler';
|
||||||
|
import { getHostForElement } from '../../utils';
|
||||||
|
|
||||||
const alwaysValid = () => true;
|
const alwaysValid = () => true;
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
onConnectStop: s.onConnectStop,
|
onConnectStop: s.onConnectStop,
|
||||||
onConnectEnd: s.onConnectEnd,
|
onConnectEnd: s.onConnectEnd,
|
||||||
connectionMode: s.connectionMode,
|
connectionMode: s.connectionMode,
|
||||||
|
connectionStartHandle: s.connectionStartHandle,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||||
@@ -36,10 +38,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
) => {
|
) => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const nodeId = useContext(NodeIdContext) as string;
|
const nodeId = useContext(NodeIdContext) as string;
|
||||||
const { onConnectAction, onConnectStart, onConnectStop, onConnectEnd, connectionMode } = useStore(
|
const { onConnectAction, onConnectStart, onConnectStop, onConnectEnd, connectionMode, connectionStartHandle } =
|
||||||
selector,
|
useStore(selector, shallow);
|
||||||
shallow
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleId = id || null;
|
const handleId = id || null;
|
||||||
const isTarget = type === 'target';
|
const isTarget = type === 'target';
|
||||||
@@ -83,6 +83,47 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onClick = useCallback(
|
||||||
|
(event: React.MouseEvent) => {
|
||||||
|
if (!connectionStartHandle) {
|
||||||
|
onConnectStart?.(event, { nodeId, handleId, handleType: type });
|
||||||
|
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
|
||||||
|
} else {
|
||||||
|
const doc = getHostForElement(event.target as HTMLElement);
|
||||||
|
const { connection, isValid } = checkElementBelowIsValid(
|
||||||
|
event as unknown as MouseEvent,
|
||||||
|
connectionMode,
|
||||||
|
connectionStartHandle.type === 'target',
|
||||||
|
connectionStartHandle.nodeId,
|
||||||
|
connectionStartHandle.handleId || null,
|
||||||
|
isValidConnection,
|
||||||
|
doc
|
||||||
|
);
|
||||||
|
|
||||||
|
onConnectStop?.(event as unknown as MouseEvent);
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
onConnectExtended(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnectEnd?.(event as unknown as MouseEvent);
|
||||||
|
|
||||||
|
store.setState({ connectionStartHandle: null });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
connectionStartHandle,
|
||||||
|
onConnectStart,
|
||||||
|
onConnectExtended,
|
||||||
|
onConnectStop,
|
||||||
|
onConnectEnd,
|
||||||
|
isTarget,
|
||||||
|
nodeId,
|
||||||
|
handleId,
|
||||||
|
type,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const handleClasses = cc([
|
const handleClasses = cc([
|
||||||
'react-flow__handle',
|
'react-flow__handle',
|
||||||
`react-flow__handle-${position}`,
|
`react-flow__handle-${position}`,
|
||||||
@@ -92,6 +133,10 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
source: !isTarget,
|
source: !isTarget,
|
||||||
target: isTarget,
|
target: isTarget,
|
||||||
connectable: isConnectable,
|
connectable: isConnectable,
|
||||||
|
connecting:
|
||||||
|
connectionStartHandle?.nodeId === nodeId &&
|
||||||
|
connectionStartHandle?.handleId === handleId &&
|
||||||
|
connectionStartHandle?.type === type,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -102,6 +147,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
data-handlepos={position}
|
data-handlepos={position}
|
||||||
className={handleClasses}
|
className={handleClasses}
|
||||||
onMouseDown={onMouseDownHandler}
|
onMouseDown={onMouseDownHandler}
|
||||||
|
onClick={onClick}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const initialState: ReactFlowStore = {
|
|||||||
|
|
||||||
fitViewOnInit: false,
|
fitViewOnInit: false,
|
||||||
fitViewOnInitDone: false,
|
fitViewOnInitDone: false,
|
||||||
|
|
||||||
|
connectionStartHandle: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default initialState;
|
export default initialState;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
|
|||||||
import { NodeChange, EdgeChange } from './changes';
|
import { NodeChange, EdgeChange } from './changes';
|
||||||
import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes';
|
import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes';
|
||||||
import { Edge } from './edges';
|
import { Edge } from './edges';
|
||||||
import { HandleType } from './handles';
|
import { HandleType, StartHandle } from './handles';
|
||||||
|
|
||||||
export type FlowElement<T = any> = Node<T> | Edge<T>;
|
export type FlowElement<T = any> = Node<T> | Edge<T>;
|
||||||
|
|
||||||
@@ -178,6 +178,8 @@ export type ReactFlowStore = {
|
|||||||
fitViewOnInit: boolean;
|
fitViewOnInit: boolean;
|
||||||
fitViewOnInitDone: boolean;
|
fitViewOnInitDone: boolean;
|
||||||
|
|
||||||
|
connectionStartHandle: StartHandle | null;
|
||||||
|
|
||||||
onConnect?: OnConnect;
|
onConnect?: OnConnect;
|
||||||
onConnectStart?: OnConnectStart;
|
onConnectStart?: OnConnectStart;
|
||||||
onConnectStop?: OnConnectStop;
|
onConnectStop?: OnConnectStop;
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ export interface HandleElement extends XYPosition, Dimensions {
|
|||||||
position: Position;
|
position: Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StartHandle {
|
||||||
|
nodeId: string;
|
||||||
|
type: HandleType;
|
||||||
|
handleId?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HandleProps {
|
export interface HandleProps {
|
||||||
type: HandleType;
|
type: HandleType;
|
||||||
position: Position;
|
position: Position;
|
||||||
|
|||||||
Reference in New Issue
Block a user