refactor(wrapNode): clean up event handlers closes #482
This commit is contained in:
+107
-205
@@ -16,151 +16,11 @@ import { ResizeObserver } from 'resize-observer';
|
|||||||
import { useStoreActions } from '../../store/hooks';
|
import { useStoreActions } from '../../store/hooks';
|
||||||
|
|
||||||
import { Provider } from '../../contexts/NodeIdContext';
|
import { Provider } from '../../contexts/NodeIdContext';
|
||||||
import {
|
import { Node, NodeComponentProps, WrapNodeProps } from '../../types';
|
||||||
Node,
|
|
||||||
XYPosition,
|
|
||||||
Transform,
|
|
||||||
ElementId,
|
|
||||||
NodeComponentProps,
|
|
||||||
WrapNodeProps,
|
|
||||||
Elements,
|
|
||||||
Edge,
|
|
||||||
NodePosUpdate,
|
|
||||||
} from '../../types';
|
|
||||||
|
|
||||||
import { noop } from '../../utils';
|
|
||||||
|
|
||||||
const getMouseEvent = (event: MouseEvent | TouchEvent) =>
|
const getMouseEvent = (event: MouseEvent | TouchEvent) =>
|
||||||
typeof TouchEvent !== 'undefined' && event instanceof TouchEvent ? event.touches[0] : (event as MouseEvent);
|
typeof TouchEvent !== 'undefined' && event instanceof TouchEvent ? event.touches[0] : (event as MouseEvent);
|
||||||
|
|
||||||
interface OnDragStartParams {
|
|
||||||
event: MouseEvent | TouchEvent;
|
|
||||||
id: ElementId;
|
|
||||||
type: string;
|
|
||||||
data: any;
|
|
||||||
selectNodesOnDrag: boolean;
|
|
||||||
isSelectable: boolean;
|
|
||||||
setOffset: (pos: XYPosition) => void;
|
|
||||||
transform: Transform;
|
|
||||||
position: XYPosition;
|
|
||||||
setSelectedElements: (elms: Elements | Node | Edge) => void;
|
|
||||||
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onStart = ({
|
|
||||||
event,
|
|
||||||
onNodeDragStart,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
data,
|
|
||||||
selectNodesOnDrag,
|
|
||||||
setOffset,
|
|
||||||
transform,
|
|
||||||
position,
|
|
||||||
setSelectedElements,
|
|
||||||
isSelectable,
|
|
||||||
}: OnDragStartParams): false | void => {
|
|
||||||
const startEvent = getMouseEvent(event);
|
|
||||||
|
|
||||||
const scaledClient: XYPosition = {
|
|
||||||
x: startEvent.clientX * (1 / transform[2]),
|
|
||||||
y: startEvent.clientY * (1 / transform[2]),
|
|
||||||
};
|
|
||||||
|
|
||||||
const offsetX = scaledClient.x - position.x - transform[0];
|
|
||||||
const offsetY = scaledClient.y - position.y - transform[1];
|
|
||||||
const node = { id, type, position, data };
|
|
||||||
|
|
||||||
setOffset({ x: offsetX, y: offsetY });
|
|
||||||
|
|
||||||
if (onNodeDragStart) {
|
|
||||||
onNodeDragStart(event as MouseEvent, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectNodesOnDrag && isSelectable) {
|
|
||||||
setSelectedElements({ id, type } as Node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OnDragParams {
|
|
||||||
event: MouseEvent | TouchEvent;
|
|
||||||
setDragging: (isDragging: boolean) => void;
|
|
||||||
id: ElementId;
|
|
||||||
offset: XYPosition;
|
|
||||||
transform: Transform;
|
|
||||||
updateNodePos: (params: NodePosUpdate) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDrag = ({ event, setDragging, id, offset, transform, updateNodePos }: OnDragParams): void => {
|
|
||||||
const dragEvent = getMouseEvent(event);
|
|
||||||
|
|
||||||
const scaledClient = {
|
|
||||||
x: dragEvent.clientX / transform[2],
|
|
||||||
y: dragEvent.clientY / transform[2],
|
|
||||||
};
|
|
||||||
|
|
||||||
setDragging(true);
|
|
||||||
updateNodePos({
|
|
||||||
id,
|
|
||||||
pos: {
|
|
||||||
x: scaledClient.x - transform[0] - offset.x,
|
|
||||||
y: scaledClient.y - transform[1] - offset.y,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OnDragStopParams {
|
|
||||||
event: MouseEvent;
|
|
||||||
isDragging: boolean;
|
|
||||||
setDragging: (isDragging: boolean) => void;
|
|
||||||
id: ElementId;
|
|
||||||
type: string;
|
|
||||||
position: XYPosition;
|
|
||||||
data: any;
|
|
||||||
selectNodesOnDrag: boolean;
|
|
||||||
isSelectable: boolean;
|
|
||||||
setSelectedElements: (elms: Elements | Node | Edge) => void;
|
|
||||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
|
||||||
onClick?: (event: MouseEvent, node: Node) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onStop = ({
|
|
||||||
event,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
position,
|
|
||||||
data,
|
|
||||||
isDragging,
|
|
||||||
setDragging,
|
|
||||||
selectNodesOnDrag,
|
|
||||||
isSelectable,
|
|
||||||
onNodeDragStop,
|
|
||||||
onClick,
|
|
||||||
setSelectedElements,
|
|
||||||
}: OnDragStopParams): void => {
|
|
||||||
const node = {
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
position,
|
|
||||||
data,
|
|
||||||
} as Node;
|
|
||||||
if (!isDragging && isSelectable) {
|
|
||||||
if (!selectNodesOnDrag) {
|
|
||||||
setSelectedElements({ id, type } as Node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onClick) {
|
|
||||||
return onClick(event, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDragging(false);
|
|
||||||
|
|
||||||
if (onNodeDragStop) {
|
|
||||||
onNodeDragStop(event, node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||||
const NodeWrapper = ({
|
const NodeWrapper = ({
|
||||||
id,
|
id,
|
||||||
@@ -195,60 +55,129 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
const nodeElement = useRef<HTMLDivElement>(null);
|
const nodeElement = useRef<HTMLDivElement>(null);
|
||||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
||||||
const [isDragging, setDragging] = useState(false);
|
const [isDragging, setDragging] = useState(false);
|
||||||
const position = { x: xPos, y: yPos };
|
|
||||||
const nodeClasses = cc([
|
const position = useMemo(() => ({ x: xPos, y: yPos }), [xPos, yPos]);
|
||||||
'react-flow__node',
|
const node = useMemo(() => ({ id, type, position, data }), [id, type, position, data]);
|
||||||
`react-flow__node-${type}`,
|
const nodeStyle: CSSProperties = useMemo(
|
||||||
className,
|
() => ({
|
||||||
{
|
zIndex: selected ? 10 : 3,
|
||||||
selected,
|
transform: `translate(${xPos}px,${yPos}px)`,
|
||||||
selectable: isSelectable,
|
pointerEvents: isSelectable || isDraggable || onClick ? 'all' : 'none',
|
||||||
},
|
opacity: isInitialized ? 1 : 0, // prevents jumping of nodes on start
|
||||||
]);
|
...style,
|
||||||
const node = { id, type, position, data };
|
}),
|
||||||
|
[selected, xPos, yPos, isSelectable, isDraggable, onClick, isInitialized, style]
|
||||||
|
);
|
||||||
const onMouseEnterHandler = useMemo(() => {
|
const onMouseEnterHandler = useMemo(() => {
|
||||||
if (!onMouseEnter || isDragging) {
|
if (!onMouseEnter || isDragging) {
|
||||||
return noop;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (event: MouseEvent) => onMouseEnter(event, node);
|
return (event: MouseEvent) => onMouseEnter(event, node);
|
||||||
}, [onMouseEnter, isDragging]);
|
}, [onMouseEnter, isDragging, node]);
|
||||||
|
|
||||||
const onMouseMoveHandler = useMemo(() => {
|
const onMouseMoveHandler = useMemo(() => {
|
||||||
if (!onMouseMove || isDragging) {
|
if (!onMouseMove || isDragging) {
|
||||||
return noop;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (event: MouseEvent) => onMouseMove(event, node);
|
return (event: MouseEvent) => onMouseMove(event, node);
|
||||||
}, [onMouseMove, isDragging]);
|
}, [onMouseMove, isDragging, node]);
|
||||||
|
|
||||||
const onMouseLeaveHandler = useMemo(() => {
|
const onMouseLeaveHandler = useMemo(() => {
|
||||||
if (!onMouseLeave || isDragging) {
|
if (!onMouseLeave || isDragging) {
|
||||||
return noop;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (event: MouseEvent) => onMouseLeave(event, node);
|
return (event: MouseEvent) => onMouseLeave(event, node);
|
||||||
}, [onMouseLeave, isDragging]);
|
}, [onMouseLeave, isDragging, node]);
|
||||||
|
|
||||||
const onContextMenuHandler = useMemo(() => {
|
const onContextMenuHandler = useMemo(() => {
|
||||||
if (!onContextMenu) {
|
if (!onContextMenu) {
|
||||||
return noop;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (event: MouseEvent) => onContextMenu(event, node);
|
return (event: MouseEvent) => onContextMenu(event, node);
|
||||||
}, [onContextMenu]);
|
}, [onContextMenu, node]);
|
||||||
|
|
||||||
const onSelectNodeHandler = useCallback(
|
const onSelectNodeHandler = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
if (!isDraggable && isSelectable) {
|
if (!isDraggable) {
|
||||||
setSelectedElements({ id: node.id, type: node.type } as Node);
|
if (isSelectable) {
|
||||||
}
|
setSelectedElements({ id: node.id, type: node.type } as Node);
|
||||||
|
}
|
||||||
|
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick(event, node);
|
onClick(event, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isSelectable, isDraggable, node]
|
[isSelectable, isDraggable, onClick, node]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragStart = useCallback(
|
||||||
|
(event) => {
|
||||||
|
const startEvent = getMouseEvent(event);
|
||||||
|
|
||||||
|
const scaledClientX = startEvent.clientX / transform[2];
|
||||||
|
const scaledClientY = startEvent.clientY / transform[2];
|
||||||
|
|
||||||
|
const offsetX = scaledClientX - position.x - transform[0];
|
||||||
|
const offsetY = scaledClientY - position.y - transform[1];
|
||||||
|
|
||||||
|
setOffset({ x: offsetX, y: offsetY });
|
||||||
|
|
||||||
|
if (onNodeDragStart) {
|
||||||
|
onNodeDragStart(event as MouseEvent, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectNodesOnDrag && isSelectable) {
|
||||||
|
setSelectedElements({ id: node.id, type: node.type } as Node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[node, transform, position, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDrag = useCallback(
|
||||||
|
(event) => {
|
||||||
|
const dragEvent = getMouseEvent(event);
|
||||||
|
|
||||||
|
const scaledClientX = dragEvent.clientX / transform[2];
|
||||||
|
const scaledClientY = dragEvent.clientY / transform[2];
|
||||||
|
|
||||||
|
setDragging(true);
|
||||||
|
updateNodePos({
|
||||||
|
id,
|
||||||
|
pos: {
|
||||||
|
x: scaledClientX - transform[0] - offset.x,
|
||||||
|
y: scaledClientY - transform[1] - offset.y,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[id, transform, offset]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragStop = useCallback(
|
||||||
|
(event) => {
|
||||||
|
if (!isDragging) {
|
||||||
|
if (isSelectable && !selectNodesOnDrag) {
|
||||||
|
setSelectedElements({ id: node.id, type: node.type } as Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
onClick(event as MouseEvent, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDragging(false);
|
||||||
|
|
||||||
|
if (onNodeDragStop) {
|
||||||
|
onNodeDragStop(event as MouseEvent, node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[node, isDragging, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -279,48 +208,21 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeStyle: CSSProperties = {
|
const nodeClasses = cc([
|
||||||
zIndex: selected ? 10 : 3,
|
'react-flow__node',
|
||||||
transform: `translate(${xPos}px,${yPos}px)`,
|
`react-flow__node-${type}`,
|
||||||
pointerEvents: isSelectable || isDraggable || onClick ? 'all' : 'none',
|
className,
|
||||||
opacity: isInitialized ? 1 : 0, // prevents jumping of nodes on start
|
{
|
||||||
...style,
|
selected,
|
||||||
};
|
selectable: isSelectable,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableCore
|
<DraggableCore
|
||||||
onStart={(event) =>
|
onStart={onDragStart}
|
||||||
onStart({
|
onDrag={onDrag}
|
||||||
event: event as MouseEvent,
|
onStop={onDragStop}
|
||||||
selectNodesOnDrag,
|
|
||||||
isSelectable,
|
|
||||||
onNodeDragStart,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
data,
|
|
||||||
setOffset,
|
|
||||||
transform,
|
|
||||||
position,
|
|
||||||
setSelectedElements,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onDrag={(event) => onDrag({ event: event as MouseEvent, setDragging, id, offset, transform, updateNodePos })}
|
|
||||||
onStop={(event) =>
|
|
||||||
onStop({
|
|
||||||
event: event as MouseEvent,
|
|
||||||
onNodeDragStop,
|
|
||||||
selectNodesOnDrag,
|
|
||||||
isSelectable,
|
|
||||||
onClick,
|
|
||||||
isDragging,
|
|
||||||
setDragging,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
position,
|
|
||||||
data,
|
|
||||||
setSelectedElements,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
scale={transform[2]}
|
scale={transform[2]}
|
||||||
disabled={!isDraggable}
|
disabled={!isDraggable}
|
||||||
cancel=".nodrag"
|
cancel=".nodrag"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { DraggableEvent } from 'react-draggable';
|
import { DraggableEvent } from 'react-draggable';
|
||||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
|
|
||||||
export const noop = () => {};
|
|
||||||
|
|
||||||
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
|
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
|
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
|
||||||
|
|||||||
Reference in New Issue
Block a user