diff --git a/examples/vite-app/package.json b/examples/vite-app/package.json index a4e6eab8..67f9d7aa 100644 --- a/examples/vite-app/package.json +++ b/examples/vite-app/package.json @@ -1,7 +1,7 @@ { "name": "@reactflow/examples", "private": true, - "version": "0.0.1", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite --port 3000 --open", diff --git a/examples/vite-app/src/examples/Basic/index.tsx b/examples/vite-app/src/examples/Basic/index.tsx index 37c5d010..b59e21b3 100644 --- a/examples/vite-app/src/examples/Basic/index.tsx +++ b/examples/vite-app/src/examples/Basic/index.tsx @@ -92,7 +92,7 @@ const BasicFlow = () => { defaultEdgeOptions={defaultEdgeOptions} selectNodesOnDrag={false} > - + diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index 7c79626b..b19289e6 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactflow/background +## 11.0.1 + +### Patch Changes + +- [`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab) Thanks [@moklick](https://github.com/moklick)! - default gap = 20 + +- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]: + - @reactflow/core@11.1.0 + ## 11.0.0 ### Major Changes diff --git a/packages/background/package.json b/packages/background/package.json index 2e883352..b9a1b184 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/background", - "version": "11.0.0", + "version": "11.0.1", "description": "Background component with different variants for React Flow", "keywords": [ "react", diff --git a/packages/background/src/Background.tsx b/packages/background/src/Background.tsx index 7fc52cae..7f300565 100644 --- a/packages/background/src/Background.tsx +++ b/packages/background/src/Background.tsx @@ -22,7 +22,7 @@ const selector = (s: ReactFlowState) => ({ transform: s.transform, rfId: s.rfId function Background({ variant = BackgroundVariant.Dots, - gap = 25, + gap = 20, // only used for dots and cross size, // only used for lines and cross diff --git a/packages/controls/CHANGELOG.md b/packages/controls/CHANGELOG.md index 7e95ab8a..646ec99c 100644 --- a/packages/controls/CHANGELOG.md +++ b/packages/controls/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/controls +## 11.0.1 + +### Patch Changes + +- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]: + - @reactflow/core@11.1.0 + ## 11.0.0 ### Major Changes diff --git a/packages/controls/package.json b/packages/controls/package.json index 53bec0a6..283a310f 100644 --- a/packages/controls/package.json +++ b/packages/controls/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/controls", - "version": "11.0.0", + "version": "11.0.1", "description": "Component to control the viewport of a React Flow instance", "keywords": [ "react", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 06967953..e862ed66 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,15 @@ # @reactflow/core +## 11.1.0 + +### Minor Changes + +- [`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab) Thanks [@moklick](https://github.com/moklick)! - New props: nodesFocusable and edgesFocusable + +### Patch Changes + +- [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4) Thanks [@moklick](https://github.com/moklick)! - Make nopan class name overwritable with class name option + ## 11.0.0 ### Major Changes diff --git a/packages/core/package.json b/packages/core/package.json index d6b3c102..6b261a76 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/core", - "version": "11.0.0", + "version": "11.1.0", "description": "Core components and util functions of React Flow.", "keywords": [ "react", diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx index 4ce63466..35d7b133 100644 --- a/packages/core/src/components/Edges/wrapEdge.tsx +++ b/packages/core/src/components/Edges/wrapEdge.tsx @@ -51,7 +51,7 @@ export default (EdgeComponent: ComponentType) => { markerStart, rfId, ariaLabel, - disableKeyboardA11y, + isFocusable, pathOptions, interactionWidth, }: WrapEdgeProps): JSX.Element | null => { @@ -158,12 +158,12 @@ export default (EdgeComponent: ComponentType) => { onMouseEnter={onEdgeMouseEnter} onMouseMove={onEdgeMouseMove} onMouseLeave={onEdgeMouseLeave} - onKeyDown={disableKeyboardA11y ? undefined : onKeyDown} - tabIndex={disableKeyboardA11y ? undefined : 0} - role={disableKeyboardA11y ? undefined : 'button'} + onKeyDown={isFocusable ? onKeyDown : undefined} + tabIndex={isFocusable ? 0 : undefined} + role={isFocusable ? 'button' : undefined} data-testid={`rf__edge-${id}`} aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`} - aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_EDGE_DESC_KEY}-${rfId}`} + aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined} ref={edgeRef} > {!updating && ( diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx index 8ebb50f9..76d53d19 100644 --- a/packages/core/src/components/Nodes/wrapNode.tsx +++ b/packages/core/src/components/Nodes/wrapNode.tsx @@ -11,10 +11,10 @@ import { NodeProps, WrapNodeProps, XYPosition } from '../../types'; import { elementSelectionKeys } from '../../utils'; export const arrowKeyDiffs: Record = { - ArrowUp: { x: 0, y: -10 }, - ArrowDown: { x: 0, y: 10 }, - ArrowLeft: { x: -10, y: 0 }, - ArrowRight: { x: 10, y: 0 }, + ArrowUp: { x: 0, y: -1 }, + ArrowDown: { x: 0, y: 1 }, + ArrowLeft: { x: -1, y: 0 }, + ArrowRight: { x: 1, y: 0 }, }; export default (NodeComponent: ComponentType) => { @@ -38,6 +38,7 @@ export default (NodeComponent: ComponentType) => { isDraggable, isSelectable, isConnectable, + isFocusable, selectNodesOnDrag, sourcePosition, targetPosition, @@ -82,6 +83,7 @@ export default (NodeComponent: ComponentType) => { }; const onKeyDown = (event: KeyboardEvent) => { + const { snapGrid, snapToGrid } = store.getState(); if (elementSelectionKeys.includes(event.key) && isSelectable) { const unselect = event.key === 'Escape'; if (unselect) { @@ -92,14 +94,28 @@ export default (NodeComponent: ComponentType) => { store, unselect, }); - } else if (selected && Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) { + } else if ( + !disableKeyboardA11y && + isDraggable && + selected && + Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key) + ) { store.setState({ - ariaLiveMessage: `Moved selected node ten pixels ${event.key + ariaLiveMessage: `Moved selected node ${event.key .replace('Arrow', '') .toLowerCase()}. New position, x: ${~~xPos}, y: ${~~yPos}`, }); - updatePositions(arrowKeyDiffs[event.key]); + // by default a node moves 5px on each key press, or 20px if shift is pressed + // if snap grid is enabled, we use that for the velocity. + const xVelo = snapToGrid ? snapGrid[0] : 5; + const yVelo = snapToGrid ? snapGrid[1] : 5; + const factor = event.shiftKey ? 4 : 1; + + updatePositions({ + x: arrowKeyDiffs[event.key].x * xVelo * factor, + y: arrowKeyDiffs[event.key].y * yVelo * factor, + }); } }; @@ -151,13 +167,16 @@ export default (NodeComponent: ComponentType) => { className={cc([ 'react-flow__node', `react-flow__node-${type}`, + { + // this is overwritable by passing `nopan` as a class name + [noPanClassName]: isDraggable, + }, className, { selected, selectable: isSelectable, parent: isParent, dragging, - [noPanClassName]: isDraggable, }, ])} ref={nodeRef} @@ -176,9 +195,9 @@ export default (NodeComponent: ComponentType) => { onContextMenu={onContextMenuHandler} onClick={onSelectNodeHandler} onDoubleClick={onDoubleClickHandler} - onKeyDown={disableKeyboardA11y ? undefined : onKeyDown} - tabIndex={disableKeyboardA11y ? undefined : 0} - role={disableKeyboardA11y ? undefined : 'button'} + onKeyDown={isFocusable ? onKeyDown : undefined} + tabIndex={isFocusable ? 0 : undefined} + role={isFocusable ? 'button' : undefined} aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`} aria-label={ariaLabel} > diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index 1c0db13a..aae2e2fc 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -18,6 +18,8 @@ type StoreUpdaterProps = Pick< | 'onClickConnectEnd' | 'nodesDraggable' | 'nodesConnectable' + | 'nodesFocusable' + | 'edgesFocusable' | 'minZoom' | 'maxZoom' | 'nodeExtent' @@ -89,6 +91,8 @@ const StoreUpdater = ({ onClickConnectEnd, nodesDraggable, nodesConnectable, + nodesFocusable, + edgesFocusable, minZoom, maxZoom, nodeExtent, @@ -145,6 +149,8 @@ const StoreUpdater = ({ useDirectStoreUpdater('onClickConnectEnd', onClickConnectEnd, store.setState); useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState); useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState); + useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState); + useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState); useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState); useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState); useDirectStoreUpdater('snapGrid', snapGrid, store.setState); diff --git a/packages/core/src/container/EdgeRenderer/index.tsx b/packages/core/src/container/EdgeRenderer/index.tsx index 1ce6b4a2..93b7adaa 100644 --- a/packages/core/src/container/EdgeRenderer/index.tsx +++ b/packages/core/src/container/EdgeRenderer/index.tsx @@ -46,6 +46,7 @@ const selector = (s: ReactFlowState) => ({ connectionNodeId: s.connectionNodeId, connectionHandleType: s.connectionHandleType, nodesConnectable: s.nodesConnectable, + edgesFocusable: s.edgesFocusable, elementsSelectable: s.elementsSelectable, width: s.width, height: s.height, @@ -58,6 +59,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => { connectionNodeId, connectionHandleType, nodesConnectable, + edgesFocusable, elementsSelectable, width, height, @@ -119,6 +121,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => { const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle || null); const sourcePosition = sourceHandle?.position || Position.Bottom; const targetPosition = targetHandle?.position || Position.Top; + const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); if (!sourceHandle || !targetHandle) { devWarn( @@ -181,7 +184,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => { onEdgeUpdateEnd={props.onEdgeUpdateEnd} rfId={props.rfId} ariaLabel={edge.ariaLabel} - disableKeyboardA11y={props.disableKeyboardA11y} + isFocusable={isFocusable} pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined} interactionWidth={edge.interactionWidth} /> diff --git a/packages/core/src/container/NodeRenderer/index.tsx b/packages/core/src/container/NodeRenderer/index.tsx index c9b4190f..b576ff69 100644 --- a/packages/core/src/container/NodeRenderer/index.tsx +++ b/packages/core/src/container/NodeRenderer/index.tsx @@ -31,12 +31,16 @@ type NodeRendererProps = Pick< const selector = (s: ReactFlowState) => ({ nodesDraggable: s.nodesDraggable, nodesConnectable: s.nodesConnectable, + nodesFocusable: s.nodesFocusable, elementsSelectable: s.elementsSelectable, updateNodeDimensions: s.updateNodeDimensions, }); const NodeRenderer = (props: NodeRendererProps) => { - const { nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions } = useStore(selector, shallow); + const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions } = useStore( + selector, + shallow + ); const nodes = useVisibleNodes(props.onlyRenderVisibleElements); const resizeObserverRef = useRef(); @@ -82,6 +86,8 @@ const NodeRenderer = (props: NodeRendererProps) => { const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined')); const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined')); + const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined')); + const clampedPosition = props.nodeExtent ? clampPosition(node.positionAbsolute, props.nodeExtent) : node.positionAbsolute; @@ -122,6 +128,7 @@ const NodeRenderer = (props: NodeRendererProps) => { isDraggable={isDraggable} isSelectable={isSelectable} isConnectable={isConnectable} + isFocusable={isFocusable} resizeObserver={resizeObserver} dragHandle={node.dragHandle} zIndex={node[internalsSymbol]?.z ?? 0} diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index bda960ec..bd493eec 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -108,7 +108,9 @@ const ReactFlow = forwardRef( selectNodesOnDrag = true, nodesDraggable, nodesConnectable, + nodesFocusable, nodeOrigin = initNodeOrigin, + edgesFocusable, elementsSelectable, defaultViewport = initDefaultViewport, minZoom = 0.5, @@ -245,6 +247,8 @@ const ReactFlow = forwardRef( onClickConnectEnd={onClickConnectEnd} nodesDraggable={nodesDraggable} nodesConnectable={nodesConnectable} + nodesFocusable={nodesFocusable} + edgesFocusable={edgesFocusable} elementsSelectable={elementsSelectable} minZoom={minZoom} maxZoom={maxZoom} diff --git a/packages/core/src/hooks/useUpdateNodePositions.ts b/packages/core/src/hooks/useUpdateNodePositions.ts index a77d50a0..8a303e06 100644 --- a/packages/core/src/hooks/useUpdateNodePositions.ts +++ b/packages/core/src/hooks/useUpdateNodePositions.ts @@ -9,17 +9,19 @@ function useUpdateNodePositions() { const store = useStoreApi(); const updatePositions = useCallback((positionDiff: XYPosition) => { - const { nodeInternals, nodeExtent, updateNodePositions } = store.getState(); + const { nodeInternals, nodeExtent, updateNodePositions, snapToGrid, snapGrid } = store.getState(); const selectedNodes = Array.from(nodeInternals.values()).filter((n) => n.selected); const nodeUpdates = selectedNodes.map((n) => { if (n.positionAbsolute) { - const updatedPos = calcNextPosition( - n, - { x: n.positionAbsolute.x + positionDiff.x, y: n.positionAbsolute.y + positionDiff.y }, - nodeInternals, - nodeExtent - ); + const nextPosition = { x: n.positionAbsolute.x + positionDiff.x, y: n.positionAbsolute.y + positionDiff.y }; + + if (snapToGrid) { + nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]); + nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]); + } + + const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent); n.position = updatedPos.position; n.positionAbsolute = updatedPos.positionAbsolute; diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index b84946ef..ce08a40d 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -40,6 +40,8 @@ const initialState: ReactFlowStore = { nodesDraggable: true, nodesConnectable: true, + nodesFocusable: true, + edgesFocusable: true, elementsSelectable: true, fitViewOnInit: false, fitViewOnInitDone: false, diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index c763343e..7a10340b 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -92,7 +92,9 @@ export interface ReactFlowProps extends HTMLAttributes { onlyRenderVisibleElements?: boolean; nodesDraggable?: boolean; nodesConnectable?: boolean; + nodesFocusable?: boolean; nodeOrigin?: NodeOrigin; + edgesFocusable?: boolean; initNodeOrigin?: NodeOrigin; elementsSelectable?: boolean; selectNodesOnDrag?: boolean; diff --git a/packages/core/src/types/edges.ts b/packages/core/src/types/edges.ts index 2332dae9..71863196 100644 --- a/packages/core/src/types/edges.ts +++ b/packages/core/src/types/edges.ts @@ -33,6 +33,7 @@ type DefaultEdge = { zIndex?: number; ariaLabel?: string; interactionWidth?: number; + focusable?: boolean; }; export type SmoothStepPathOptions = { @@ -132,7 +133,7 @@ export type WrapEdgeProps = Omit, 'sourceHandle' | 'targetHandl onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge, handleType: HandleType) => void; onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void; rfId?: string; - disableKeyboardA11y: boolean; + isFocusable: boolean; pathOptions?: BezierPathOptions | SmoothStepPathOptions; }; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index ba645b43..6b4422fb 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -167,6 +167,8 @@ export type ReactFlowStore = { nodesDraggable: boolean; nodesConnectable: boolean; + nodesFocusable: boolean; + edgesFocusable: boolean; elementsSelectable: boolean; multiSelectionActive: boolean; diff --git a/packages/core/src/types/nodes.ts b/packages/core/src/types/nodes.ts index bcb884fa..a1473f71 100644 --- a/packages/core/src/types/nodes.ts +++ b/packages/core/src/types/nodes.ts @@ -31,6 +31,7 @@ export interface Node { expandParent?: boolean; positionAbsolute?: XYPosition; ariaLabel?: string; + focusable?: boolean; // only used internally [internalsSymbol]?: { @@ -73,6 +74,7 @@ export interface WrapNodeProps { initialized: boolean; isSelectable: boolean; isDraggable: boolean; + isFocusable: boolean; selectNodesOnDrag: boolean; onClick?: NodeMouseHandler; onDoubleClick?: NodeMouseHandler; diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md index cf08eb09..eb44ff49 100644 --- a/packages/minimap/CHANGELOG.md +++ b/packages/minimap/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/minimap +## 11.0.1 + +### Patch Changes + +- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]: + - @reactflow/core@11.1.0 + ## 11.0.0 ### Major Changes @@ -29,7 +36,6 @@ - @reactflow/controls - @reactflow/minimap - ### Patch Changes - Updated dependencies: diff --git a/packages/minimap/package.json b/packages/minimap/package.json index d0210b51..341d53e8 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/minimap", - "version": "11.0.0", + "version": "11.0.1", "description": "Minimap component for React Flow.", "keywords": [ "react", diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md index 2cff2539..3fa8bed1 100644 --- a/packages/reactflow/CHANGELOG.md +++ b/packages/reactflow/CHANGELOG.md @@ -1,9 +1,25 @@ # reactflow +## 11.1.0 + +### Minor Changes + +- [`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab) Thanks [@moklick](https://github.com/moklick)! - New props: nodesFocusable and edgesFocusable + +### Patch Changes + +- [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4) Thanks [@moklick](https://github.com/moklick)! - Make nopan class name overwritable with class name option + +- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]: + - @reactflow/background@11.0.1 + - @reactflow/core@11.1.0 + - @reactflow/controls@11.0.1 + - @reactflow/minimap@11.0.1 + ## 11.0.0 Finally it's here! A new version that comes with lots of improvements and the new package name `reactflow`. -From now on you can install it via `npm install reactflow`. +From now on you can install it via `npm install reactflow`. ## Major Changes diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json index 8a411979..895157e1 100644 --- a/packages/reactflow/package.json +++ b/packages/reactflow/package.json @@ -1,6 +1,6 @@ { "name": "reactflow", - "version": "11.0.0", + "version": "11.1.0", "description": "A highly customizable React library for building node-based editors and interactive flow charts", "keywords": [ "react",