From f7dc75b0f3db8fda0ea6ee0bc6219fa3c2e599cd Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Mon, 1 Aug 2022 13:58:18 +0200 Subject: [PATCH] Feat: basic keyboard controls and better WAI-ARIA defaults (#2333) * feat(nodes): focusable and moveable with keys * refactor(key-handling): cleanup, handle selections * feat(edges): selectable with keys * refactor(nodes-edges): cleanup keyboard controls and aria- attrs * refactor(nodes): node needs to be selected for arrow keys * refactor(minimap): create const for labelledby closes #1033 --- examples/nextjs/pages/CustomNode/index.tsx | 3 +- examples/nextjs/pages/Edges/index.tsx | 2 +- examples/nextjs/pages/NestedNodes/index.tsx | 3 +- examples/nextjs/pages/Subflow/index.tsx | 6 +- examples/nextjs/pages/Switch/index.tsx | 18 +- examples/nextjs/pages/UpdateNode/index.tsx | 5 +- packages/background/src/Background.tsx | 71 +- .../src/components/A11yDescriptions/index.tsx | 26 + .../core/src/components/Attribution/index.tsx | 2 +- .../core/src/components/Edges/wrapEdge.tsx | 44 +- packages/core/src/components/Nodes/utils.ts | 4 +- .../core/src/components/Nodes/wrapNode.tsx | 41 +- .../src/components/NodesSelection/index.tsx | 24 +- .../core/src/container/EdgeRenderer/index.tsx | 5 +- .../core/src/container/FlowRenderer/index.tsx | 8 +- .../core/src/container/GraphView/index.tsx | 11 +- .../core/src/container/NodeRenderer/index.tsx | 5 + .../core/src/container/ReactFlow/index.tsx | 9 +- .../core/src/container/ZoomPane/index.tsx | 2 +- packages/core/src/hooks/useDrag/index.ts | 18 +- packages/core/src/hooks/useDrag/utils.ts | 46 +- .../core/src/hooks/useGlobalKeyHandler.ts | 46 +- .../core/src/hooks/useUpdateNodePositions.ts | 37 + packages/core/src/store/index.ts | 2 +- packages/core/src/theme-default.css | 20 +- packages/core/src/types/component-props.ts | 1 + packages/core/src/types/edges.ts | 3 + packages/core/src/types/general.ts | 2 +- packages/core/src/types/nodes.ts | 4 + packages/core/src/utils/index.ts | 3 + packages/minimap/src/MiniMap.tsx | 19 +- yarn.lock | 12631 +++++++++------- 32 files changed, 7921 insertions(+), 5200 deletions(-) create mode 100644 packages/core/src/components/A11yDescriptions/index.tsx create mode 100644 packages/core/src/hooks/useUpdateNodePositions.ts diff --git a/examples/nextjs/pages/CustomNode/index.tsx b/examples/nextjs/pages/CustomNode/index.tsx index acc1b90c..3883ddc2 100644 --- a/examples/nextjs/pages/CustomNode/index.tsx +++ b/examples/nextjs/pages/CustomNode/index.tsx @@ -29,6 +29,7 @@ const initBgColor = '#1A192B'; const connectionLineStyle = { stroke: '#fff' }; const snapGrid: SnapGrid = [16, 16]; +const defaultViewport = { x: 0, y: 0, zoom: 1.5 }; const nodeTypes = { selectorNode: ColorSelectorNode, @@ -141,7 +142,7 @@ const CustomNodeFlow = () => { connectionLineStyle={connectionLineStyle} snapToGrid={true} snapGrid={snapGrid} - defaultZoom={1.5} + defaultViewport={defaultViewport} fitView > { const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = useCallback( (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), - [] + [setEdges] ); return ( diff --git a/examples/nextjs/pages/NestedNodes/index.tsx b/examples/nextjs/pages/NestedNodes/index.tsx index b5489bc9..8cbedace 100644 --- a/examples/nextjs/pages/NestedNodes/index.tsx +++ b/examples/nextjs/pages/NestedNodes/index.tsx @@ -158,8 +158,7 @@ const NestedFlow = () => { onEdgeClick={onEdgeClick} onConnect={onConnect} onNodeDragStop={onNodeDragStop} - className='react-flow-basic-example' - defaultZoom={1.5} + className="react-flow-basic-example" minZoom={0.2} maxZoom={4} onlyRenderVisibleElements={false} diff --git a/examples/nextjs/pages/Subflow/index.tsx b/examples/nextjs/pages/Subflow/index.tsx index 9a6083fb..6c4bfca5 100644 --- a/examples/nextjs/pages/Subflow/index.tsx +++ b/examples/nextjs/pages/Subflow/index.tsx @@ -24,6 +24,8 @@ const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); +const defaultViewport = { x: 0, y: 0, zoom: 1.5 }; + const initialNodes: Node[] = [ { id: '1', @@ -206,8 +208,8 @@ const Subflow = () => { onConnect={onConnect} onNodeDrag={onNodeDrag} onNodeDragStop={onNodeDragStop} - className='react-flow-basic-example' - defaultZoom={1.5} + className="react-flow-basic-example" + defaultViewport={defaultViewport} minZoom={0.2} maxZoom={4} onlyRenderVisibleElements={false} diff --git a/examples/nextjs/pages/Switch/index.tsx b/examples/nextjs/pages/Switch/index.tsx index 4b76376e..874dcfad 100644 --- a/examples/nextjs/pages/Switch/index.tsx +++ b/examples/nextjs/pages/Switch/index.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent } from 'react'; +import React, { MouseEvent, useCallback } from 'react'; import ReactFlow, { addEdge, Node, @@ -19,12 +19,14 @@ const nodesA: Node[] = [ data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light', + ariaLabel: 'Input Node 1', }, { id: '2a', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light', + ariaLabel: 'Default Node 2', }, { id: '3a', @@ -41,7 +43,7 @@ const nodesA: Node[] = [ ]; const edgesA: Edge[] = [ - { id: 'e1-2', source: '1a', target: '2a' }, + { id: 'e1-2', source: '1a', target: '2a', ariaLabel: null }, { id: 'e1-3', source: '1a', target: '3a' }, ]; @@ -52,18 +54,21 @@ const nodesB: Node[] = [ data: { label: 'Input' }, position: { x: 300, y: 5 }, className: 'light', + ariaLabel: 'Input Node', }, { id: '1b', data: { label: 'Node 1' }, position: { x: 0, y: 100 }, className: 'light', + ariaLabel: 'Node with id 1', }, { id: '2b', data: { label: 'Node 2' }, position: { x: 200, y: 100 }, className: 'light', + ariaLabel: 'Node with id 2', }, { id: '3b', @@ -80,7 +85,7 @@ const nodesB: Node[] = [ ]; const edgesB: Edge[] = [ - { id: 'e1b', source: 'inputb', target: '1b' }, + { id: 'e1b', source: 'inputb', target: '1b', ariaLabel: 'edge to connect' }, { id: 'e2b', source: 'inputb', target: '2b' }, { id: 'e3b', source: 'inputb', target: '3b' }, { id: 'e4b', source: 'inputb', target: '4b' }, @@ -90,8 +95,10 @@ const BasicFlow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(nodesA); const [edges, setEdges, onEdgesChange] = useEdgesState(edgesA); - const onConnect = (params: Connection | Edge) => - setEdges((eds) => addEdge(params, eds)); + const onConnect = useCallback( + (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), + [setEdges] + ); return ( { onNodeClick={onNodeClick} onConnect={onConnect} onNodeDragStop={onNodeDragStop} + disableKeyboardA11y >