Merge branch 'main' into feat/packages

This commit is contained in:
moklick
2023-03-28 16:27:18 +02:00
61 changed files with 2513 additions and 1150 deletions
+26 -22
View File
@@ -54,6 +54,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
rfId,
ariaLabel,
isFocusable,
isUpdatable,
pathOptions,
interactionWidth,
}: WrapEdgeProps): JSX.Element | null => {
@@ -137,7 +138,6 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
const inactive = !elementsSelectable && !onClick;
const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined';
const onKeyDown = (event: KeyboardEvent) => {
if (elementSelectionKeys.includes(event.key) && elementsSelectable) {
@@ -204,28 +204,32 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
interactionWidth={interactionWidth}
/>
)}
{handleEdgeUpdate && (
{isUpdatable && (
<>
<EdgeAnchor
position={sourcePosition}
centerX={sourceX}
centerY={sourceY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterSourceMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="source"
/>
<EdgeAnchor
position={targetPosition}
centerX={targetX}
centerY={targetY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterTargetMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="target"
/>
{(isUpdatable === 'source' || isUpdatable === true) && (
<EdgeAnchor
position={sourcePosition}
centerX={sourceX}
centerY={sourceY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterSourceMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="source"
/>
)}
{(isUpdatable === 'target' || isUpdatable === true) && (
<EdgeAnchor
position={targetPosition}
centerX={targetX}
centerY={targetY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterTargetMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="target"
/>
)}
</>
)}
</g>
@@ -95,10 +95,17 @@ export function handlePointerDown({
setState({
connectionPosition,
connectionStatus: null,
// connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
connectionNodeId: nodeId,
connectionHandleId: handleId,
connectionHandleType: handleType,
connectionStatus: null,
connectionStartHandle: {
nodeId,
handleId,
type: handleType,
},
connectionEndHandle: null,
});
onConnectStart?.(event, { nodeId, handleId, handleType });
@@ -145,6 +152,7 @@ export function handlePointerDown({
)
: connectionPosition,
connectionStatus: getConnectionStatus(!!prevClosestHandle, isValid),
connectionEndHandle: result.endHandle,
});
if (!prevClosestHandle && !isValid && !handleDomNode) {
+56 -25
View File
@@ -1,15 +1,15 @@
import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import { errorMessages, Position, type HandleProps, type Connection } from '@reactflow/system';
import { errorMessages, Position, type HandleProps, type Connection, type HandleType } from '@reactflow/system';
import { getHostForElement, isMouseEvent } from '@reactflow/utils';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext';
import { handlePointerDown } from './handler';
import { addEdge } from '../../utils/';
import { type ReactFlowState } from '../../types';
import { isValidHandle } from './utils';
import { addEdge } from '../../utils';
import type { ReactFlowState } from '../../types';
const alwaysValid = () => true;
@@ -21,6 +21,23 @@ const selector = (s: ReactFlowState) => ({
noPanClassName: s.noPanClassName,
});
const connectingSelector =
(nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => {
const {
connectionStartHandle: startHandle,
connectionEndHandle: endHandle,
connectionClickStartHandle: clickHandle,
} = state;
return {
connecting:
(startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type) ||
(endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type),
clickConnecting:
clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
};
};
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
(
{
@@ -28,6 +45,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
position = Position.Top,
isValidConnection,
isConnectable = true,
isConnectableStart = true,
isConnectableEnd = true,
id,
onConnect,
children,
@@ -38,19 +57,16 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
},
ref
) => {
const store = useStoreApi();
const nodeId = useNodeId();
if (!nodeId) {
store.getState().onError?.('010', errorMessages['010']());
return null;
}
const { connectionStartHandle, connectOnClick, noPanClassName } = useStore(selector, shallow);
const handleId = id || null;
const isTarget = type === 'target';
const store = useStoreApi();
const nodeId = useNodeId();
const { connectOnClick, noPanClassName } = useStore(selector, shallow);
const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type));
if (!nodeId) {
store.getState().onError?.('010', errorMessages['error010']());
}
const onConnectExtended = (params: Connection) => {
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
@@ -69,9 +85,16 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
};
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
if (!nodeId) {
return;
}
const isMouseTriggered = isMouseEvent(event.nativeEvent);
if ((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered) {
if (
isConnectableStart &&
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
) {
handlePointerDown({
event,
handleId,
@@ -95,12 +118,18 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
const {
onClickConnectStart,
onClickConnectEnd,
connectionClickStartHandle,
connectionMode,
isValidConnection: isValidConnectionStore,
} = store.getState();
if (!connectionStartHandle) {
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
return;
}
if (!connectionClickStartHandle) {
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
return;
}
@@ -114,9 +143,9 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
type,
},
connectionMode,
connectionStartHandle.nodeId,
connectionStartHandle.handleId || null,
connectionStartHandle.type,
connectionClickStartHandle.nodeId,
connectionClickStartHandle.handleId || null,
connectionClickStartHandle.type,
isValidConnectionHandler,
doc
);
@@ -127,7 +156,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
onClickConnectEnd?.(event as unknown as MouseEvent);
store.setState({ connectionStartHandle: null });
store.setState({ connectionClickStartHandle: null });
};
return (
@@ -146,10 +175,12 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
source: !isTarget,
target: isTarget,
connectable: isConnectable,
connecting:
connectionStartHandle?.nodeId === nodeId &&
connectionStartHandle?.handleId === handleId &&
connectionStartHandle?.type === type,
connectablestart: isConnectableStart,
connectableend: isConnectableEnd,
connecting: clickConnecting,
// this class is used to style the handle when the user is connecting
connectionindicator:
isConnectable && ((isConnectableStart && !connecting) || (isConnectableEnd && connecting)),
},
])}
onMouseDown={onPointerDown}
+15 -4
View File
@@ -1,12 +1,12 @@
import { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
import {
internalsSymbol,
ConnectionMode,
ConnectionStatus,
type ConnectingHandle,
type Connection,
type HandleType,
type XYPosition,
type NodeHandleBounds,
type XYPosition,
} from '@reactflow/system';
import { getEventPosition } from '@reactflow/utils';
@@ -67,6 +67,7 @@ type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection;
endHandle: ConnectingHandle | null;
};
const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null };
@@ -78,7 +79,7 @@ export function isValidHandle(
connectionMode: ConnectionMode,
fromNodeId: string,
fromHandleId: string | null,
fromType: string,
fromType: HandleType,
isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot
) {
@@ -94,12 +95,15 @@ export function isValidHandle(
handleDomNode: handleToCheck,
isValid: false,
connection: nullConnection,
endHandle: null,
};
if (handleToCheck) {
const handleType = getHandleType(undefined, handleToCheck);
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
const handleId = handleToCheck.getAttribute('data-handleid');
const connectable = handleToCheck.classList.contains('connectable');
const connectableEnd = handleToCheck.classList.contains('connectableend');
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
@@ -110,14 +114,21 @@ export function isValidHandle(
result.connection = connection;
const isConnectable = connectable && connectableEnd;
// in strict mode we don't allow target to target or source to source connections
const isValid =
handleToCheck.classList.contains('connectable') &&
isConnectable &&
(connectionMode === ConnectionMode.Strict
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId);
if (isValid) {
result.endHandle = {
nodeId: handleNodeId as string,
handleId,
type: handleType as HandleType,
};
result.isValid = isValidConnection(connection);
}
}
@@ -21,6 +21,7 @@ type StoreUpdaterProps = Pick<
| 'nodesConnectable'
| 'nodesFocusable'
| 'edgesFocusable'
| 'edgesUpdatable'
| 'minZoom'
| 'maxZoom'
| 'nodeExtent'
@@ -99,6 +100,7 @@ const StoreUpdater = ({
nodesConnectable,
nodesFocusable,
edgesFocusable,
edgesUpdatable,
elevateNodesOnSelect,
minZoom,
maxZoom,
@@ -163,6 +165,7 @@ const StoreUpdater = ({
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState);
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState);
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
@@ -43,7 +43,7 @@ export function useMarkerSymbol(type: MarkerType) {
const symbolExists = Object.prototype.hasOwnProperty.call(MarkerSymbols, type);
if (!symbolExists) {
store.getState().onError?.('009', errorMessages['009'](type));
store.getState().onError?.('009', errorMessages['error009'](type));
return null;
}
@@ -38,6 +38,7 @@ type EdgeRendererProps = Pick<
const selector = (s: ReactFlowState) => ({
nodesConnectable: s.nodesConnectable,
edgesFocusable: s.edgesFocusable,
edgesUpdatable: s.edgesUpdatable,
elementsSelectable: s.elementsSelectable,
width: s.width,
height: s.height,
@@ -65,10 +66,8 @@ const EdgeRenderer = ({
onEdgeUpdateEnd,
children,
}: EdgeRendererProps) => {
const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(
selector,
shallow
);
const { edgesFocusable, edgesUpdatable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } =
useStore(selector, shallow);
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
if (!width) {
@@ -98,7 +97,7 @@ const EdgeRenderer = ({
let edgeType = edge.type || 'default';
if (!edgeTypes[edgeType]) {
onError?.('011', errorMessages['011'](edgeType));
onError?.('011', errorMessages['error011'](edgeType));
edgeType = 'default';
}
@@ -113,9 +112,12 @@ const EdgeRenderer = ({
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
const isUpdatable =
typeof onEdgeUpdate !== 'undefined' &&
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
if (!sourceHandle || !targetHandle) {
onError?.('008', errorMessages['008'](sourceHandle, edge));
onError?.('008', errorMessages['error008'](sourceHandle, edge));
return null;
}
@@ -172,6 +174,7 @@ const EdgeRenderer = ({
rfId={rfId}
ariaLabel={edge.ariaLabel}
isFocusable={isFocusable}
isUpdatable={isUpdatable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
interactionWidth={edge.interactionWidth}
/>
@@ -75,7 +75,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
let nodeType = node.type || 'default';
if (!props.nodeTypes[nodeType]) {
onError?.('003', errorMessages['003'](nodeType));
onError?.('003', errorMessages['error003'](nodeType));
nodeType = 'default';
}
@@ -119,6 +119,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
nodesFocusable,
nodeOrigin = initNodeOrigin,
edgesFocusable,
edgesUpdatable,
elementsSelectable,
defaultViewport = initDefaultViewport,
minZoom = 0.5,
@@ -270,6 +271,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
nodesConnectable={nodesConnectable}
nodesFocusable={nodesFocusable}
edgesFocusable={edgesFocusable}
edgesUpdatable={edgesUpdatable}
elementsSelectable={elementsSelectable}
elevateNodesOnSelect={elevateNodesOnSelect}
minZoom={minZoom}
@@ -17,7 +17,7 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any
if (process.env.NODE_ENV === 'development') {
const typeKeys = Object.keys(nodeOrEdgeTypes);
if (shallow(typesKeysRef.current, typeKeys)) {
devWarn('002', errorMessages['002']());
devWarn('002', errorMessages['error002']());
}
typesKeysRef.current = typeKeys;
@@ -139,7 +139,7 @@ const ZoomPane = ({
-(deltaX / currentZoom) * panOnScrollSpeed,
-(deltaY / currentZoom) * panOnScrollSpeed
);
});
}, { passive: false });
} else if (typeof d3ZoomHandler !== 'undefined') {
d3Selection.on('wheel.zoom', function (event: any, d: any) {
if (!preventScrolling || isWrappedWithClass(event, noWheelClassName)) {
@@ -148,7 +148,7 @@ const ZoomPane = ({
event.preventDefault();
d3ZoomHandler.call(this, event, d);
});
}, { passive: false });
}
}
}, [
+1 -1
View File
@@ -103,7 +103,7 @@ export function calcNextPosition(
]
: currentExtent;
} else {
onError?.('005', errorMessages['005']());
onError?.('005', errorMessages['error005']());
currentExtent = nodeExtent;
}
+15 -4
View File
@@ -3,16 +3,27 @@ import { internalsSymbol } from '@reactflow/system';
import { useStore } from './useStore';
import type { ReactFlowState } from '../types';
const selector = (s: ReactFlowState) => {
export type UseNodesInitializedOptions = {
includeHiddenNodes?: boolean;
};
const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => {
if (s.nodeInternals.size === 0) {
return false;
}
return s.getNodes().every((n) => n[internalsSymbol]?.handleBounds !== undefined);
return s
.getNodes()
.filter((n) => (options.includeHiddenNodes ? true : !n.hidden))
.every((n) => n[internalsSymbol]?.handleBounds !== undefined);
};
function useNodesInitialized(): boolean {
const initialized = useStore(selector);
const defaultOptions = {
includeHiddenNodes: false,
};
function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
const initialized = useStore(selector(options));
return initialized;
}
+1 -1
View File
@@ -18,7 +18,7 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
const size = getDimensions(rendererNode.current);
if (size.height === 0 || size.width === 0) {
store.getState().onError?.('004', errorMessages['004']());
store.getState().onError?.('004', errorMessages['error004']());
}
store.setState({ width: size.width || 500, height: size.height || 500 });
+1 -1
View File
@@ -5,7 +5,7 @@ import { errorMessages } from '@reactflow/system';
import StoreContext from '../contexts/RFStoreContext';
import type { ReactFlowState } from '../types';
const zustandErrorMessage = errorMessages['001']();
const zustandErrorMessage = errorMessages['error001']();
type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never;
+3 -3
View File
@@ -19,9 +19,9 @@ export { default as useViewport } from './hooks/useViewport';
export { default as useKeyPress } from './hooks/useKeyPress';
export * from './hooks/useNodesEdgesState';
export { useStore, useStoreApi } from './hooks/useStore';
export { default as useOnViewportChange } from './hooks/useOnViewportChange';
export { default as useOnSelectionChange } from './hooks/useOnSelectionChange';
export { default as useNodesInitialized } from './hooks/useNodesInitialized';
export { default as useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/useOnViewportChange';
export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange';
export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized';
export { default as useGetPointerPosition } from './hooks/useGetPointerPosition';
export { useNodeId } from './contexts/NodeIdContext';
export * from '@reactflow/edge-utils';
+4 -2
View File
@@ -35,8 +35,8 @@ const createRFStore = () =>
return Array.from(get().nodeInternals.values());
},
setEdges: (edges: Edge[]) => {
const { defaultEdgeOptions = null } = get();
set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges });
const { defaultEdgeOptions = {} } = get();
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
},
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
const hasDefaultNodes = typeof nodes !== 'undefined';
@@ -299,6 +299,8 @@ const createRFStore = () =>
connectionHandleId: initialState.connectionHandleId,
connectionHandleType: initialState.connectionHandleType,
connectionStatus: initialState.connectionStatus,
connectionStartHandle: initialState.connectionStartHandle,
connectionEndHandle: initialState.connectionEndHandle,
}),
reset: () => set({ ...initialState }),
}));
+3
View File
@@ -42,6 +42,7 @@ const initialState: ReactFlowStore = {
nodesConnectable: true,
nodesFocusable: true,
edgesFocusable: true,
edgesUpdatable: true,
elementsSelectable: true,
elevateNodesOnSelect: true,
fitViewOnInit: false,
@@ -51,6 +52,8 @@ const initialState: ReactFlowStore = {
multiSelectionActive: false,
connectionStartHandle: null,
connectionEndHandle: null,
connectionClickStartHandle: null,
connectOnClick: true,
ariaLiveMessage: '',
+1 -1
View File
@@ -144,7 +144,7 @@
min-width: 5px;
min-height: 5px;
&.connectable {
&.connectionindicator {
pointer-events: all;
cursor: crosshair;
}
@@ -114,6 +114,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
nodesFocusable?: boolean;
nodeOrigin?: NodeOrigin;
edgesFocusable?: boolean;
edgesUpdatable?: boolean;
initNodeOrigin?: NodeOrigin;
elementsSelectable?: boolean;
selectNodesOnDrag?: boolean;
+4
View File
@@ -25,11 +25,14 @@ export type EdgeLabelOptions = {
labelBgBorderRadius?: number;
};
export type EdgeUpdatable = boolean | HandleType;
export type DefaultEdge<EdgeData = any> = BaseEdge<EdgeData> & {
style?: CSSProperties;
className?: string;
sourceNode?: Node;
targetNode?: Node;
updatable?: EdgeUpdatable;
} & EdgeLabelOptions;
type SmoothStepEdgeType<T> = DefaultEdge<T> & {
@@ -68,6 +71,7 @@ export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandl
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
rfId?: string;
isFocusable: boolean;
isUpdatable: EdgeUpdatable;
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
};
+5 -2
View File
@@ -13,7 +13,7 @@ import {
OnViewportChange,
SelectionRect,
SnapGrid,
StartHandle,
ConnectingHandle,
Transform,
XYPosition,
} from '@reactflow/system';
@@ -80,12 +80,15 @@ export type ReactFlowStore = {
nodesConnectable: boolean;
nodesFocusable: boolean;
edgesFocusable: boolean;
edgesUpdatable: boolean;
elementsSelectable: boolean;
elevateNodesOnSelect: boolean;
multiSelectionActive: boolean;
connectionStartHandle: StartHandle | null;
connectionStartHandle: ConnectingHandle | null;
connectionEndHandle: ConnectingHandle | null;
connectionClickStartHandle: ConnectingHandle | null;
onNodeDragStart?: NodeDragHandler;
onNodeDrag?: NodeDragHandler;