Merge pull request #2136 from wbkd/refactor/d3-drag-handle-selector
Refactor/d3 drag handle selector
This commit is contained in:
@@ -26,9 +26,13 @@
|
|||||||
|
|
||||||
Cypress.Commands.add('drag', (selector, { x, y }) => {
|
Cypress.Commands.add('drag', (selector, { x, y }) => {
|
||||||
return cy
|
return cy
|
||||||
.get(selector)
|
.window()
|
||||||
.trigger('mousedown', { which: 1 })
|
.then((window) =>
|
||||||
.trigger('mousemove', { clientX: x, clientY: y })
|
cy
|
||||||
.wait(50)
|
.get(selector)
|
||||||
.trigger('mouseup', { force: true });
|
.trigger('mousedown', { which: 1, view: window })
|
||||||
|
.trigger('mousemove', { clientX: x, clientY: y, force: true })
|
||||||
|
.wait(50)
|
||||||
|
.trigger('mouseup', { view: window, force: tru })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,18 +5,17 @@ import { ReactFlowState, Node } from '../../types';
|
|||||||
|
|
||||||
function useMemoizedMouseHandler(
|
function useMemoizedMouseHandler(
|
||||||
id: string,
|
id: string,
|
||||||
dragging: boolean,
|
|
||||||
getState: GetState<ReactFlowState>,
|
getState: GetState<ReactFlowState>,
|
||||||
handler?: (event: MouseEvent, node: Node) => void
|
handler?: (event: MouseEvent, node: Node) => void
|
||||||
) {
|
) {
|
||||||
const memoizedHandler = useCallback(
|
const memoizedHandler = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
if (typeof handler !== 'undefined' && !dragging) {
|
if (typeof handler !== 'undefined') {
|
||||||
const node = getState().nodeInternals.get(id)!;
|
const node = getState().nodeInternals.get(id)!;
|
||||||
handler(event, { ...node });
|
handler(event, { ...node });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handler, dragging, id]
|
[handler, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return memoizedHandler;
|
return memoizedHandler;
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react';
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
memo,
|
||||||
|
ComponentType,
|
||||||
|
CSSProperties,
|
||||||
|
useMemo,
|
||||||
|
MouseEvent,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
@@ -41,7 +51,6 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
hidden,
|
hidden,
|
||||||
dragging,
|
|
||||||
resizeObserver,
|
resizeObserver,
|
||||||
dragHandle,
|
dragHandle,
|
||||||
zIndex,
|
zIndex,
|
||||||
@@ -49,6 +58,8 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
noPanClassName,
|
noPanClassName,
|
||||||
noDragClassName,
|
noDragClassName,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
|
const draggingRef = useRef<boolean>(false);
|
||||||
|
const [dragging, setDragging] = useState<boolean>(false);
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { addSelectedNodes, unselectNodesAndEdges, updateNodePosition, updateNodeDimensions } = useStore(
|
const { addSelectedNodes, unselectNodesAndEdges, updateNodePosition, updateNodeDimensions } = useStore(
|
||||||
selector,
|
selector,
|
||||||
@@ -69,11 +80,11 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
[zIndex, xPos, yPos, hasPointerEvents, style]
|
[zIndex, xPos, yPos, hasPointerEvents, style]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseEnterHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseEnter);
|
const onMouseEnterHandler = useMemoizedMouseHandler(id, store.getState, onMouseEnter);
|
||||||
const onMouseMoveHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseMove);
|
const onMouseMoveHandler = useMemoizedMouseHandler(id, store.getState, onMouseMove);
|
||||||
const onMouseLeaveHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseLeave);
|
const onMouseLeaveHandler = useMemoizedMouseHandler(id, store.getState, onMouseLeave);
|
||||||
const onContextMenuHandler = useMemoizedMouseHandler(id, false, store.getState, onContextMenu);
|
const onContextMenuHandler = useMemoizedMouseHandler(id, store.getState, onContextMenu);
|
||||||
const onNodeDoubleClickHandler = useMemoizedMouseHandler(id, false, store.getState, onNodeDoubleClick);
|
const onNodeDoubleClickHandler = useMemoizedMouseHandler(id, store.getState, onNodeDoubleClick);
|
||||||
|
|
||||||
const onSelectNodeHandler = useCallback(
|
const onSelectNodeHandler = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
@@ -123,13 +134,14 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
|
|
||||||
const onDrag = useCallback(
|
const onDrag = useCallback(
|
||||||
(event: UseDragEvent, dragPos: UseDragData) => {
|
(event: UseDragEvent, dragPos: UseDragData) => {
|
||||||
updateNodePosition({ id, dragging: true, diff: { x: dragPos.dx, y: dragPos.dy } });
|
updateNodePosition({ id, diff: { x: dragPos.dx, y: dragPos.dy } });
|
||||||
|
setDragging(true);
|
||||||
|
draggingRef.current = true;
|
||||||
|
|
||||||
if (onNodeDrag) {
|
if (onNodeDrag) {
|
||||||
const node = store.getState().nodeInternals.get(id)!;
|
const node = store.getState().nodeInternals.get(id)!;
|
||||||
onNodeDrag(event.sourceEvent as MouseEvent, {
|
onNodeDrag(event.sourceEvent as MouseEvent, {
|
||||||
...node,
|
...node,
|
||||||
dragging: true,
|
|
||||||
position: {
|
position: {
|
||||||
x: node.position.x + dragPos.dx,
|
x: node.position.x + dragPos.dx,
|
||||||
y: node.position.y + dragPos.dy,
|
y: node.position.y + dragPos.dy,
|
||||||
@@ -146,15 +158,13 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
|
|
||||||
const onDragStop = useCallback(
|
const onDragStop = useCallback(
|
||||||
(event: UseDragEvent) => {
|
(event: UseDragEvent) => {
|
||||||
// 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
|
|
||||||
let node;
|
let node;
|
||||||
|
|
||||||
if (onClick || onNodeDragStop) {
|
if (onClick || onNodeDragStop) {
|
||||||
node = store.getState().nodeInternals.get(id)!;
|
node = store.getState().nodeInternals.get(id)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dragging) {
|
if (!draggingRef.current) {
|
||||||
if (isSelectable && !selectNodesOnDrag && !selected) {
|
if (isSelectable && !selectNodesOnDrag && !selected) {
|
||||||
addSelectedNodes([id]);
|
addSelectedNodes([id]);
|
||||||
}
|
}
|
||||||
@@ -165,17 +175,14 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
draggingRef.current = false;
|
||||||
updateNodePosition({
|
setDragging(false);
|
||||||
id,
|
|
||||||
dragging: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (onNodeDragStop && node) {
|
if (onNodeDragStop && node) {
|
||||||
onNodeDragStop(event.sourceEvent as MouseEvent, { ...node, dragging: false });
|
onNodeDragStop(event.sourceEvent as MouseEvent, { ...node });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[id, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, dragging, selected]
|
[id, onNodeDragStop, onClick]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ function NodesSelection({
|
|||||||
x: data.dx,
|
x: data.dx,
|
||||||
y: data.dy,
|
y: data.dy,
|
||||||
},
|
},
|
||||||
dragging: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onSelectionDrag?.(event.sourceEvent, selectedNodes);
|
onSelectionDrag?.(event.sourceEvent, selectedNodes);
|
||||||
@@ -82,10 +81,6 @@ function NodesSelection({
|
|||||||
|
|
||||||
const onStop = useCallback(
|
const onStop = useCallback(
|
||||||
(event: UseDragEvent) => {
|
(event: UseDragEvent) => {
|
||||||
updateNodePosition({
|
|
||||||
dragging: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
onSelectionDragStop?.(event.sourceEvent, selectedNodes);
|
onSelectionDragStop?.(event.sourceEvent, selectedNodes);
|
||||||
},
|
},
|
||||||
[selectedNodes, onSelectionDragStop]
|
[selectedNodes, onSelectionDragStop]
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
hidden={node.hidden}
|
hidden={node.hidden}
|
||||||
xPos={node.positionAbsolute?.x ?? 0}
|
xPos={node.positionAbsolute?.x ?? 0}
|
||||||
yPos={node.positionAbsolute?.y ?? 0}
|
yPos={node.positionAbsolute?.y ?? 0}
|
||||||
dragging={!!node.dragging}
|
|
||||||
selectNodesOnDrag={props.selectNodesOnDrag}
|
selectNodesOnDrag={props.selectNodesOnDrag}
|
||||||
onClick={props.onNodeClick}
|
onClick={props.onNodeClick}
|
||||||
onMouseEnter={props.onNodeMouseEnter}
|
onMouseEnter={props.onNodeMouseEnter}
|
||||||
|
|||||||
+31
-5
@@ -16,7 +16,6 @@ type UseDragParams = {
|
|||||||
nodeRef: RefObject<Element>;
|
nodeRef: RefObject<Element>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
noDragClassName?: string;
|
noDragClassName?: string;
|
||||||
// @TODO: implement handleSelector functionality
|
|
||||||
handleSelector?: string;
|
handleSelector?: string;
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
};
|
};
|
||||||
@@ -42,7 +41,27 @@ function getParentNodePosition(nodeInternals: NodeInternals, nodeId?: string): X
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function useDrag({ onStart, onDrag, onStop, nodeRef, disabled = false, noDragClassName, nodeId }: UseDragParams) {
|
function selectorExistsTargetToNode(target: Element, selector: string, nodeRef: RefObject<Element>): boolean {
|
||||||
|
let current = target;
|
||||||
|
do {
|
||||||
|
if (current?.matches(selector)) return true;
|
||||||
|
if (current === nodeRef.current) return false;
|
||||||
|
current = current.parentElement as Element;
|
||||||
|
} while (current);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useDrag({
|
||||||
|
onStart,
|
||||||
|
onDrag,
|
||||||
|
onStop,
|
||||||
|
nodeRef,
|
||||||
|
disabled = false,
|
||||||
|
noDragClassName,
|
||||||
|
handleSelector,
|
||||||
|
nodeId,
|
||||||
|
}: UseDragParams) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const startPos = useRef<XYPosition>({ x: 0, y: 0 });
|
const startPos = useRef<XYPosition>({ x: 0, y: 0 });
|
||||||
const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null });
|
const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null });
|
||||||
@@ -93,8 +112,15 @@ function useDrag({ onStart, onDrag, onStop, nodeRef, disabled = false, noDragCla
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('end', onStop)
|
.on('end', (event) => {
|
||||||
.filter((event: any) => !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName));
|
onStop(event);
|
||||||
|
})
|
||||||
|
.filter((event: any) => {
|
||||||
|
const filter = !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName);
|
||||||
|
return handleSelector
|
||||||
|
? selectorExistsTargetToNode(event.target, handleSelector, nodeRef) && filter
|
||||||
|
: filter;
|
||||||
|
});
|
||||||
|
|
||||||
selection.call(dragHandler);
|
selection.call(dragHandler);
|
||||||
|
|
||||||
@@ -103,7 +129,7 @@ function useDrag({ onStart, onDrag, onStop, nodeRef, disabled = false, noDragCla
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [disabled, noDragClassName, nodeId]);
|
}, [onStart, onDrag, onStop, nodeRef, disabled, noDragClassName, handleSelector, nodeId]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-7
@@ -96,7 +96,7 @@ const createStore = () =>
|
|||||||
onNodesChange?.(changes);
|
onNodesChange?.(changes);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => {
|
updateNodePosition: ({ id, diff }: NodeDiffUpdate) => {
|
||||||
const { onNodesChange, nodeExtent, nodeInternals, hasDefaultNodes, snapGrid, snapToGrid } = get();
|
const { onNodesChange, nodeExtent, nodeInternals, hasDefaultNodes, snapGrid, snapToGrid } = get();
|
||||||
|
|
||||||
if (hasDefaultNodes || onNodesChange) {
|
if (hasDefaultNodes || onNodesChange) {
|
||||||
@@ -105,14 +105,10 @@ const createStore = () =>
|
|||||||
nodeInternals.forEach((node) => {
|
nodeInternals.forEach((node) => {
|
||||||
if (node.selected) {
|
if (node.selected) {
|
||||||
if (!node.parentNode || !isParentSelected(node, nodeInternals)) {
|
if (!node.parentNode || !isParentSelected(node, nodeInternals)) {
|
||||||
changes.push(
|
changes.push(createPositionChange({ node, diff, nodeExtent, nodeInternals, snapToGrid, snapGrid }));
|
||||||
createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals, snapToGrid, snapGrid })
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (node.id === id) {
|
} else if (node.id === id) {
|
||||||
changes.push(
|
changes.push(createPositionChange({ node, diff, nodeExtent, nodeInternals, snapToGrid, snapGrid }));
|
||||||
createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals, snapToGrid, snapGrid })
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -44,7 +44,7 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
|
|||||||
const parentNodes: ParentNodes = {};
|
const parentNodes: ParentNodes = {};
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
const z = isNumeric(node.zIndex) ? node.zIndex : node.dragging || node.selected ? 1000 : 0;
|
const z = isNumeric(node.zIndex) ? node.zIndex : node.selected ? 1000 : 0;
|
||||||
const currInternals = nodeInternals.get(node.id);
|
const currInternals = nodeInternals.get(node.id);
|
||||||
|
|
||||||
const internals: Node = {
|
const internals: Node = {
|
||||||
@@ -115,7 +115,6 @@ type CreatePostionChangeParams = {
|
|||||||
nodeExtent: CoordinateExtent;
|
nodeExtent: CoordinateExtent;
|
||||||
nodeInternals: NodeInternals;
|
nodeInternals: NodeInternals;
|
||||||
diff?: XYPosition;
|
diff?: XYPosition;
|
||||||
dragging?: boolean;
|
|
||||||
snapToGrid?: boolean;
|
snapToGrid?: boolean;
|
||||||
snapGrid?: SnapGrid;
|
snapGrid?: SnapGrid;
|
||||||
};
|
};
|
||||||
@@ -123,7 +122,6 @@ type CreatePostionChangeParams = {
|
|||||||
export function createPositionChange({
|
export function createPositionChange({
|
||||||
node,
|
node,
|
||||||
diff,
|
diff,
|
||||||
dragging,
|
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
nodeInternals,
|
nodeInternals,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
@@ -132,7 +130,6 @@ export function createPositionChange({
|
|||||||
const change: NodePositionChange = {
|
const change: NodePositionChange = {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
type: 'position',
|
type: 'position',
|
||||||
dragging: !!dragging,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (diff) {
|
if (diff) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export type NodePositionChange = {
|
|||||||
id: string;
|
id: string;
|
||||||
type: 'position';
|
type: 'position';
|
||||||
position?: XYPosition;
|
position?: XYPosition;
|
||||||
dragging?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeSelectionChange = {
|
export type NodeSelectionChange = {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export interface Node<T = any> {
|
|||||||
sourcePosition?: Position;
|
sourcePosition?: Position;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
dragging?: boolean;
|
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
connectable?: boolean;
|
connectable?: boolean;
|
||||||
@@ -79,7 +78,6 @@ export interface WrapNodeProps<T = any> {
|
|||||||
sourcePosition: Position;
|
sourcePosition: Position;
|
||||||
targetPosition: Position;
|
targetPosition: Position;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
dragging: boolean;
|
|
||||||
resizeObserver: ResizeObserver | null;
|
resizeObserver: ResizeObserver | null;
|
||||||
dragHandle?: string;
|
dragHandle?: string;
|
||||||
zIndex: number;
|
zIndex: number;
|
||||||
@@ -96,7 +94,6 @@ export type NodeHandleBounds = {
|
|||||||
export type NodeDiffUpdate = {
|
export type NodeDiffUpdate = {
|
||||||
id?: string;
|
id?: string;
|
||||||
diff?: XYPosition;
|
diff?: XYPosition;
|
||||||
dragging?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeDimensionUpdate = {
|
export type NodeDimensionUpdate = {
|
||||||
|
|||||||
@@ -69,10 +69,6 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
updateItem.position = currentChange.position;
|
updateItem.position = currentChange.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof currentChange.dragging !== 'undefined') {
|
|
||||||
updateItem.dragging = currentChange.dragging;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateItem.expandParent) {
|
if (updateItem.expandParent) {
|
||||||
handleParentExpand(res, updateItem);
|
handleParentExpand(res, updateItem);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -176,7 +176,7 @@ export const getNodesInside = (
|
|||||||
const visibleNodes: Node[] = [];
|
const visibleNodes: Node[] = [];
|
||||||
|
|
||||||
nodeInternals.forEach((node) => {
|
nodeInternals.forEach((node) => {
|
||||||
const { positionAbsolute, width, height, dragging, selectable = true } = node;
|
const { positionAbsolute, width, height, selectable = true } = node;
|
||||||
|
|
||||||
if (excludeNonSelectableNodes && !selectable) {
|
if (excludeNonSelectableNodes && !selectable) {
|
||||||
return false;
|
return false;
|
||||||
@@ -187,7 +187,7 @@ export const getNodesInside = (
|
|||||||
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);
|
||||||
const notInitialized =
|
const notInitialized =
|
||||||
typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null || dragging;
|
typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null;
|
||||||
|
|
||||||
const partiallyVisible = partially && overlappingArea > 0;
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
const area = (width || 0) * (height || 0);
|
const area = (width || 0) * (height || 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user