Merge pull request #2962 from wbkd/feat/edges-updatable
Feat/edges updatable
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@reactflow/background': minor
|
||||||
|
'@reactflow/core': minor
|
||||||
|
'@reactflow/minimap': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
edges: add updatable option
|
||||||
@@ -36,7 +36,7 @@ const initialNodes: Node[] = [
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
position: { x: 100, y: 100 },
|
position: { x: 75, y: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
@@ -55,9 +55,46 @@ const initialNodes: Node[] = [
|
|||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
data: {
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
Node <strong>D</strong>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
position: { x: -75, y: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
data: {
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
Node <strong>E</strong>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
position: { x: 150, y: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
data: {
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
Node <strong>F</strong>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
position: { x: 150, y: 250 },
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }];
|
const initialEdges: Edge[] = [
|
||||||
|
{ id: 'e1-3', source: '1', target: '3', label: 'This edge can only be updated from source', updatable: 'source' },
|
||||||
|
{ id: 'e2-4', source: '2', target: '4', label: 'This edge can only be updated from target', updatable: 'target' },
|
||||||
|
{ id: 'e5-6', source: '5', target: '6', label: 'This edge can be updated from both sides' },
|
||||||
|
];
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||||
const onEdgeUpdateStart = (_: ReactMouseEvent, edge: Edge, handleType: HandleType) =>
|
const onEdgeUpdateStart = (_: ReactMouseEvent, edge: Edge, handleType: HandleType) =>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
rfId,
|
rfId,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
isFocusable,
|
isFocusable,
|
||||||
|
isUpdatable,
|
||||||
pathOptions,
|
pathOptions,
|
||||||
interactionWidth,
|
interactionWidth,
|
||||||
}: WrapEdgeProps): JSX.Element | null => {
|
}: WrapEdgeProps): JSX.Element | null => {
|
||||||
@@ -138,7 +139,6 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
||||||
|
|
||||||
const inactive = !elementsSelectable && !onClick;
|
const inactive = !elementsSelectable && !onClick;
|
||||||
const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined';
|
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (elementSelectionKeys.includes(event.key) && elementsSelectable) {
|
if (elementSelectionKeys.includes(event.key) && elementsSelectable) {
|
||||||
@@ -205,28 +205,32 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
interactionWidth={interactionWidth}
|
interactionWidth={interactionWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{handleEdgeUpdate && (
|
{isUpdatable && (
|
||||||
<>
|
<>
|
||||||
<EdgeAnchor
|
{(isUpdatable === 'source' || isUpdatable === true) && (
|
||||||
position={sourcePosition}
|
<EdgeAnchor
|
||||||
centerX={sourceX}
|
position={sourcePosition}
|
||||||
centerY={sourceY}
|
centerX={sourceX}
|
||||||
radius={edgeUpdaterRadius}
|
centerY={sourceY}
|
||||||
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
radius={edgeUpdaterRadius}
|
||||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
||||||
onMouseOut={onEdgeUpdaterMouseOut}
|
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||||
type="source"
|
onMouseOut={onEdgeUpdaterMouseOut}
|
||||||
/>
|
type="source"
|
||||||
<EdgeAnchor
|
/>
|
||||||
position={targetPosition}
|
)}
|
||||||
centerX={targetX}
|
{(isUpdatable === 'target' || isUpdatable === true) && (
|
||||||
centerY={targetY}
|
<EdgeAnchor
|
||||||
radius={edgeUpdaterRadius}
|
position={targetPosition}
|
||||||
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
centerX={targetX}
|
||||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
centerY={targetY}
|
||||||
onMouseOut={onEdgeUpdaterMouseOut}
|
radius={edgeUpdaterRadius}
|
||||||
type="target"
|
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
||||||
/>
|
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||||
|
onMouseOut={onEdgeUpdaterMouseOut}
|
||||||
|
type="target"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type StoreUpdaterProps = Pick<
|
|||||||
| 'nodesConnectable'
|
| 'nodesConnectable'
|
||||||
| 'nodesFocusable'
|
| 'nodesFocusable'
|
||||||
| 'edgesFocusable'
|
| 'edgesFocusable'
|
||||||
|
| 'edgesUpdatable'
|
||||||
| 'minZoom'
|
| 'minZoom'
|
||||||
| 'maxZoom'
|
| 'maxZoom'
|
||||||
| 'nodeExtent'
|
| 'nodeExtent'
|
||||||
@@ -98,6 +99,7 @@ const StoreUpdater = ({
|
|||||||
nodesConnectable,
|
nodesConnectable,
|
||||||
nodesFocusable,
|
nodesFocusable,
|
||||||
edgesFocusable,
|
edgesFocusable,
|
||||||
|
edgesUpdatable,
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
minZoom,
|
minZoom,
|
||||||
maxZoom,
|
maxZoom,
|
||||||
@@ -162,6 +164,7 @@ const StoreUpdater = ({
|
|||||||
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
|
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
|
||||||
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
|
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
|
||||||
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
|
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
|
||||||
|
useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState);
|
||||||
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
|
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
|
||||||
useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState);
|
useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState);
|
||||||
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
|
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type EdgeRendererProps = Pick<
|
|||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
nodesConnectable: s.nodesConnectable,
|
nodesConnectable: s.nodesConnectable,
|
||||||
edgesFocusable: s.edgesFocusable,
|
edgesFocusable: s.edgesFocusable,
|
||||||
|
edgesUpdatable: s.edgesUpdatable,
|
||||||
elementsSelectable: s.elementsSelectable,
|
elementsSelectable: s.elementsSelectable,
|
||||||
width: s.width,
|
width: s.width,
|
||||||
height: s.height,
|
height: s.height,
|
||||||
@@ -66,10 +67,8 @@ const EdgeRenderer = ({
|
|||||||
onEdgeUpdateEnd,
|
onEdgeUpdateEnd,
|
||||||
children,
|
children,
|
||||||
}: EdgeRendererProps) => {
|
}: EdgeRendererProps) => {
|
||||||
const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(
|
const { edgesFocusable, edgesUpdatable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } =
|
||||||
selector,
|
useStore(selector, shallow);
|
||||||
shallow
|
|
||||||
);
|
|
||||||
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
|
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
|
||||||
|
|
||||||
if (!width) {
|
if (!width) {
|
||||||
@@ -114,6 +113,9 @@ const EdgeRenderer = ({
|
|||||||
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
||||||
const targetPosition = targetHandle?.position || Position.Top;
|
const targetPosition = targetHandle?.position || Position.Top;
|
||||||
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
||||||
|
const isUpdatable =
|
||||||
|
typeof onEdgeUpdate !== 'undefined' &&
|
||||||
|
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
|
||||||
|
|
||||||
if (!sourceHandle || !targetHandle) {
|
if (!sourceHandle || !targetHandle) {
|
||||||
onError?.('008', errorMessages['error008'](sourceHandle, edge));
|
onError?.('008', errorMessages['error008'](sourceHandle, edge));
|
||||||
@@ -173,6 +175,7 @@ const EdgeRenderer = ({
|
|||||||
rfId={rfId}
|
rfId={rfId}
|
||||||
ariaLabel={edge.ariaLabel}
|
ariaLabel={edge.ariaLabel}
|
||||||
isFocusable={isFocusable}
|
isFocusable={isFocusable}
|
||||||
|
isUpdatable={isUpdatable}
|
||||||
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
|
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
|
||||||
interactionWidth={edge.interactionWidth}
|
interactionWidth={edge.interactionWidth}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
nodesFocusable,
|
nodesFocusable,
|
||||||
nodeOrigin = initNodeOrigin,
|
nodeOrigin = initNodeOrigin,
|
||||||
edgesFocusable,
|
edgesFocusable,
|
||||||
|
edgesUpdatable,
|
||||||
elementsSelectable,
|
elementsSelectable,
|
||||||
defaultViewport = initDefaultViewport,
|
defaultViewport = initDefaultViewport,
|
||||||
minZoom = 0.5,
|
minZoom = 0.5,
|
||||||
@@ -266,6 +267,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
nodesConnectable={nodesConnectable}
|
nodesConnectable={nodesConnectable}
|
||||||
nodesFocusable={nodesFocusable}
|
nodesFocusable={nodesFocusable}
|
||||||
edgesFocusable={edgesFocusable}
|
edgesFocusable={edgesFocusable}
|
||||||
|
edgesUpdatable={edgesUpdatable}
|
||||||
elementsSelectable={elementsSelectable}
|
elementsSelectable={elementsSelectable}
|
||||||
elevateNodesOnSelect={elevateNodesOnSelect}
|
elevateNodesOnSelect={elevateNodesOnSelect}
|
||||||
minZoom={minZoom}
|
minZoom={minZoom}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const initialState: ReactFlowStore = {
|
|||||||
nodesConnectable: true,
|
nodesConnectable: true,
|
||||||
nodesFocusable: true,
|
nodesFocusable: true,
|
||||||
edgesFocusable: true,
|
edgesFocusable: true,
|
||||||
|
edgesUpdatable: true,
|
||||||
elementsSelectable: true,
|
elementsSelectable: true,
|
||||||
elevateNodesOnSelect: true,
|
elevateNodesOnSelect: true,
|
||||||
fitViewOnInit: false,
|
fitViewOnInit: false,
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
nodesFocusable?: boolean;
|
nodesFocusable?: boolean;
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
edgesFocusable?: boolean;
|
edgesFocusable?: boolean;
|
||||||
|
edgesUpdatable?: boolean;
|
||||||
initNodeOrigin?: NodeOrigin;
|
initNodeOrigin?: NodeOrigin;
|
||||||
elementsSelectable?: boolean;
|
elementsSelectable?: boolean;
|
||||||
selectNodesOnDrag?: boolean;
|
selectNodesOnDrag?: boolean;
|
||||||
|
|||||||
@@ -36,8 +36,11 @@ type DefaultEdge<T = any> = {
|
|||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
interactionWidth?: number;
|
interactionWidth?: number;
|
||||||
focusable?: boolean;
|
focusable?: boolean;
|
||||||
|
updatable?: EdgeUpdatable;
|
||||||
} & EdgeLabelOptions;
|
} & EdgeLabelOptions;
|
||||||
|
|
||||||
|
export type EdgeUpdatable = boolean | HandleType;
|
||||||
|
|
||||||
export type SmoothStepPathOptions = {
|
export type SmoothStepPathOptions = {
|
||||||
offset?: number;
|
offset?: number;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
@@ -88,6 +91,7 @@ export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandl
|
|||||||
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
|
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
|
||||||
rfId?: string;
|
rfId?: string;
|
||||||
isFocusable: boolean;
|
isFocusable: boolean;
|
||||||
|
isUpdatable: EdgeUpdatable;
|
||||||
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
|
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ export type ReactFlowStore = {
|
|||||||
nodesConnectable: boolean;
|
nodesConnectable: boolean;
|
||||||
nodesFocusable: boolean;
|
nodesFocusable: boolean;
|
||||||
edgesFocusable: boolean;
|
edgesFocusable: boolean;
|
||||||
|
edgesUpdatable: boolean;
|
||||||
elementsSelectable: boolean;
|
elementsSelectable: boolean;
|
||||||
elevateNodesOnSelect: boolean;
|
elevateNodesOnSelect: boolean;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user