Merge branch 'main' into feat/packages

This commit is contained in:
moklick
2023-03-14 11:23:32 +01:00
54 changed files with 667 additions and 264 deletions
@@ -10,6 +10,8 @@ import { EdgeAnchor } from './EdgeAnchor';
import { getMouseHandler } from './utils';
import type { EdgeProps, WrapEdgeProps } from '../../types';
const alwaysValidConnection = () => true;
export default (EdgeComponent: ComponentType<EdgeProps>) => {
const EdgeWrapper = ({
id,
@@ -93,12 +95,14 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
return;
}
const { edges, isValidConnection: isValidConnectionStore } = store.getState();
const nodeId = isSourceHandle ? target : source;
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
const handleType = isSourceHandle ? 'target' : 'source';
const isValidConnection = () => true;
const isValidConnection = isValidConnectionStore || alwaysValidConnection;
const isTarget = isSourceHandle;
const edge = store.getState().edges.find((e) => e.id === id)!;
const edge = edges.find((e) => e.id === id)!;
setUpdating(true);
onEdgeUpdateStart?.(event, edge, handleType);
+12 -6
View File
@@ -26,7 +26,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
{
type = 'source',
position = Position.Top,
isValidConnection = alwaysValid,
isValidConnection,
isConnectable = true,
id,
onConnect,
@@ -60,8 +60,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
...params,
};
if (hasDefaultEdges) {
const { edges } = store.getState();
store.setState({ edges: addEdge(edgeParams, edges) });
const { edges, setEdges } = store.getState();
setEdges(addEdge(edgeParams, edges));
}
onConnectAction?.(edgeParams);
@@ -80,7 +80,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
isTarget,
getState: store.getState,
setState: store.setState,
isValidConnection,
isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid,
});
}
@@ -92,7 +92,12 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
};
const onClick = (event: ReactMouseEvent) => {
const { onClickConnectStart, onClickConnectEnd, connectionMode } = store.getState();
const {
onClickConnectStart,
onClickConnectEnd,
connectionMode,
isValidConnection: isValidConnectionStore,
} = store.getState();
if (!connectionStartHandle) {
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
@@ -100,6 +105,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
}
const doc = getHostForElement(event.target as HTMLElement);
const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid;
const { connection, isValid } = isValidHandle(
event.nativeEvent,
{
@@ -111,7 +117,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
connectionStartHandle.nodeId,
connectionStartHandle.handleId || null,
connectionStartHandle.type,
isValidConnection,
isValidConnectionHandler,
doc
);
+3 -2
View File
@@ -112,9 +112,10 @@ export function isValidHandle(
// in strict mode we don't allow target to target or source to source connections
const isValid =
connectionMode === ConnectionMode.Strict
handleToCheck.classList.contains('connectable') &&
(connectionMode === ConnectionMode.Strict
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
: handleNodeId !== fromNodeId || handleId !== fromHandleId);
if (isValid) {
result.isValid = isValidConnection(connection);
+5 -1
View File
@@ -1,4 +1,4 @@
import { MouseEvent } from 'react';
import { MouseEvent, RefObject } from 'react';
import { StoreApi } from 'zustand';
import { getDimensions } from '@reactflow/utils';
import { Position, type HandleElement, type NodeOrigin } from '@reactflow/system';
@@ -58,6 +58,7 @@ export function handleNodeClick({
id,
store,
unselect = false,
nodeRef,
}: {
id: string;
store: {
@@ -65,6 +66,7 @@ export function handleNodeClick({
setState: StoreApi<ReactFlowState>['setState'];
};
unselect?: boolean;
nodeRef?: RefObject<HTMLDivElement>;
}) {
const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeInternals } = store.getState();
const node = nodeInternals.get(id)!;
@@ -75,5 +77,7 @@ export function handleNodeClick({
addSelectedNodes([id]);
} else if (unselect || (node.selected && multiSelectionActive)) {
unselectNodesAndEdges({ nodes: [node] });
requestAnimationFrame(() => nodeRef?.current?.blur());
}
}
@@ -74,6 +74,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
handleNodeClick({
id,
store,
nodeRef,
});
}
@@ -90,13 +91,12 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
if (elementSelectionKeys.includes(event.key) && isSelectable) {
const unselect = event.key === 'Escape';
if (unselect) {
nodeRef.current?.blur();
}
handleNodeClick({
id,
store,
unselect,
nodeRef,
});
} else if (
!disableKeyboardA11y &&
@@ -50,6 +50,7 @@ type StoreUpdaterProps = Pick<
| 'autoPanOnNodeDrag'
| 'onError'
| 'connectionRadius'
| 'isValidConnection'
> & { rfId: string };
const selector = (s: ReactFlowState) => ({
@@ -128,6 +129,7 @@ const StoreUpdater = ({
autoPanOnNodeDrag,
onError,
connectionRadius,
isValidConnection,
}: StoreUpdaterProps) => {
const {
setNodes,
@@ -185,6 +187,7 @@ const StoreUpdater = ({
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
useDirectStoreUpdater('onError', onError, store.setState);
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState);
useStoreUpdater<Node[]>(nodes, setNodes);
useStoreUpdater<Edge[]>(edges, setEdges);
@@ -75,10 +75,10 @@ export function getHandle(bounds: HandleElement[], handleId?: string | null): Ha
return null;
}
if (handleId) {
return bounds.find((d) => d.id === handleId)!;
} else if (bounds.length === 1) {
if (bounds.length === 1 || !handleId) {
return bounds[0];
} else if (handleId) {
return bounds.find((d) => d.id === handleId) || null;
}
return null;
+2 -2
View File
@@ -111,7 +111,7 @@ const Pane = memo(
return;
}
const { x, y } = getEventPosition(event, containerBounds.current);
const { x, y } = getEventPosition(event.nativeEvent, containerBounds.current);
resetSelectedElements();
@@ -138,7 +138,7 @@ const Pane = memo(
store.setState({ userSelectionActive: true, nodesSelectionActive: false });
const mousePos = getEventPosition(event, containerBounds.current);
const mousePos = getEventPosition(event.nativeEvent, containerBounds.current);
const startX = userSelectionRect.startX ?? 0;
const startY = userSelectionRect.startY ?? 0;
@@ -167,6 +167,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
autoPanOnConnect = true,
autoPanOnNodeDrag = true,
connectionRadius = 20,
isValidConnection,
onError,
style,
id,
@@ -299,6 +300,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
autoPanOnNodeDrag={autoPanOnNodeDrag}
onError={onError}
connectionRadius={connectionRadius}
isValidConnection={isValidConnection}
/>
<SelectionListener onSelectionChange={onSelectionChange} />
{children}
+1
View File
@@ -153,6 +153,7 @@ function useDrag({
handleNodeClick({
id: nodeId,
store,
nodeRef: nodeRef as RefObject<HTMLDivElement>,
});
}
+9 -2
View File
@@ -18,6 +18,9 @@ const doc = typeof document !== 'undefined' ? document : null;
export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { target: doc }): boolean => {
const [keyPressed, setKeyPressed] = useState(false);
// we need to remember if a modifier key is pressed in order to track it
const modifierPressed = useRef(false);
// we need to remember the pressed keys in order to support combinations
const pressedKeys = useRef<PressedKeys>(new Set([]));
@@ -42,7 +45,10 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = {
useEffect(() => {
if (keyCode !== null) {
const downHandler = (event: KeyboardEvent) => {
if (isInputDOMNode(event)) {
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey;
if (!modifierPressed.current && isInputDOMNode(event)) {
return false;
}
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
@@ -55,7 +61,7 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = {
};
const upHandler = (event: KeyboardEvent) => {
if (isInputDOMNode(event)) {
if (!modifierPressed.current && isInputDOMNode(event)) {
return false;
}
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
@@ -66,6 +72,7 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = {
} else {
pressedKeys.current.delete(event[keyOrCode]);
}
modifierPressed.current = false;
};
const resetHandler = () => {
+1 -1
View File
@@ -7,7 +7,6 @@ export { default as BezierEdge } from './components/Edges/BezierEdge';
export { default as SimpleBezierEdge, getSimpleBezierPath } from './components/Edges/SimpleBezierEdge';
export { default as SmoothStepEdge } from './components/Edges/SmoothStepEdge';
export { default as BaseEdge } from './components/Edges/BaseEdge';
export { default as ReactFlowProvider } from './components/ReactFlowProvider';
export { default as Panel } from './components/Panel';
export { default as EdgeLabelRenderer } from './components/EdgeLabelRenderer';
@@ -34,6 +33,7 @@ export {
rectToBox,
boxToRect,
getBoundsOfRects,
clamp,
} from '@reactflow/utils';
export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
+2 -2
View File
@@ -36,8 +36,8 @@ const createRFStore = () =>
return Array.from(get().nodeInternals.values());
},
setEdges: (edges: Edge[]) => {
const { defaultEdgeOptions = {} } = get();
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
const { defaultEdgeOptions = null } = get();
set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges });
},
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
const hasDefaultNodes = typeof nodes !== 'undefined';
+1
View File
@@ -58,6 +58,7 @@ const initialState: ReactFlowStore = {
autoPanOnNodeDrag: true,
connectionRadius: 20,
onError: devWarn,
isValidConnection: undefined,
};
export default initialState;
@@ -40,6 +40,7 @@ import type {
SelectionDragHandler,
EdgeMouseHandler,
} from '.';
import { ValidConnectionFunc } from '../components/Handle/utils';
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
nodes?: Node[];
@@ -146,6 +147,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
autoPanOnConnect?: boolean;
connectionRadius?: number;
onError?: OnError;
isValidConnection?: ValidConnectionFunc;
};
export type ReactFlowRefType = HTMLDivElement;
+3
View File
@@ -18,6 +18,7 @@ import {
SetCenter,
FitBounds,
Project,
Connection,
} from '@reactflow/system';
import type { NodeChange, EdgeChange, Node, WrapNodeProps, Edge, EdgeProps, WrapEdgeProps, ReactFlowInstance } from '.';
@@ -53,6 +54,8 @@ export type FitViewOptions = FitViewOptionsBase<Node>;
export type FitView = (fitViewOptions?: FitViewOptions) => boolean;
export type OnInit<NodeData = any, EdgeData = any> = (reactFlowInstance: ReactFlowInstance<NodeData, EdgeData>) => void;
export type IsValidConnection = (edge: Edge | Connection) => boolean;
export type ViewportHelperFunctions = {
zoomIn: ZoomInOut;
zoomOut: ZoomInOut;
+3
View File
@@ -35,6 +35,7 @@ import type {
OnEdgesDelete,
OnSelectionChangeFunc,
UnselectNodesAndEdgesParams,
IsValidConnection,
} from '.';
export type ReactFlowStore = {
@@ -123,6 +124,8 @@ export type ReactFlowStore = {
autoPanOnConnect: boolean;
autoPanOnNodeDrag: boolean;
connectionRadius: number;
isValidConnection?: IsValidConnection;
};
export type ReactFlowActions = {