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
This commit is contained in:
Moritz Klack
2022-08-01 13:58:18 +02:00
committed by GitHub
parent 8eb1053aed
commit f7dc75b0f3
32 changed files with 7921 additions and 5200 deletions
@@ -40,7 +40,8 @@ interface EdgeRendererProps {
edgeUpdaterRadius?: number;
noPanClassName?: string;
elevateEdgesOnSelect: boolean;
rfId?: string;
rfId: string;
disableKeyboardA11y: boolean;
}
const selector = (s: ReactFlowState) => ({
@@ -182,6 +183,8 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
onEdgeUpdateStart={props.onEdgeUpdateStart}
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
rfId={props.rfId}
ariaLabel={edge.ariaLabel}
disableKeyboardA11y={props.disableKeyboardA11y}
/>
);
})}
@@ -22,6 +22,7 @@ export type FlowRendererProps = Omit<
| 'onlyRenderVisibleElements'
| 'selectNodesOnDrag'
| 'defaultMarkerColor'
| 'rfId'
> & {
children: ReactNode;
};
@@ -56,6 +57,7 @@ const FlowRenderer = ({
onSelectionContextMenu,
noWheelClassName,
noPanClassName,
disableKeyboardA11y,
}: FlowRendererProps) => {
const store = useStoreApi();
const nodesSelectionActive = useStore(selector);
@@ -95,7 +97,11 @@ const FlowRenderer = ({
{children}
<UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && (
<NodesSelection onSelectionContextMenu={onSelectionContextMenu} noPanClassName={noPanClassName} />
<NodesSelection
onSelectionContextMenu={onSelectionContextMenu}
noPanClassName={noPanClassName}
disableKeyboardA11y={disableKeyboardA11y}
/>
)}
<div
className="react-flow__pane react-flow__container"
@@ -22,6 +22,8 @@ export interface GraphViewProps
noWheelClassName: string;
noPanClassName: string;
defaultViewport: Viewport;
rfId: string;
disableKeyboardA11y: boolean;
}
const GraphView = ({
@@ -79,7 +81,8 @@ const GraphView = ({
noWheelClassName,
noPanClassName,
elevateEdgesOnSelect,
id,
disableKeyboardA11y,
rfId,
}: GraphViewProps) => {
useOnInitHandler(onInit);
@@ -112,6 +115,7 @@ const GraphView = ({
noDragClassName={noDragClassName}
noWheelClassName={noWheelClassName}
noPanClassName={noPanClassName}
disableKeyboardA11y={disableKeyboardA11y}
>
<ViewportWrapper>
<EdgeRenderer
@@ -134,7 +138,8 @@ const GraphView = ({
defaultMarkerColor={defaultMarkerColor}
noPanClassName={noPanClassName}
elevateEdgesOnSelect={!!elevateEdgesOnSelect}
rfId={id}
disableKeyboardA11y={disableKeyboardA11y}
rfId={rfId}
/>
<NodeRenderer
nodeTypes={nodeTypes}
@@ -148,6 +153,8 @@ const GraphView = ({
onlyRenderVisibleElements={onlyRenderVisibleElements}
noPanClassName={noPanClassName}
noDragClassName={noDragClassName}
disableKeyboardA11y={disableKeyboardA11y}
rfId={rfId}
/>
</ViewportWrapper>
</FlowRenderer>
@@ -18,6 +18,8 @@ interface NodeRendererProps {
onlyRenderVisibleElements: boolean;
noPanClassName: string;
noDragClassName: string;
rfId: string;
disableKeyboardA11y: boolean;
}
const selector = (s: ReactFlowState) => ({
@@ -110,6 +112,9 @@ const NodeRenderer = (props: NodeRendererProps) => {
noDragClassName={props.noDragClassName}
noPanClassName={props.noPanClassName}
initialized={!!node.width && !!node.height}
rfId={props.rfId}
disableKeyboardA11y={props.disableKeyboardA11y}
ariaLabel={node.ariaLabel}
/>
);
})}
@@ -1,4 +1,4 @@
import React, { forwardRef } from 'react';
import React, { forwardRef, useId } from 'react';
import cc from 'classcat';
import Attribution from '../../components/Attribution';
@@ -29,6 +29,7 @@ import GraphView from '../GraphView';
import { createNodeTypes } from '../NodeRenderer/utils';
import { useNodeOrEdgeTypes } from './utils';
import Wrapper from './Wrapper';
import A11yDescriptions from '../../components/A11yDescriptions';
const defaultNodeTypes: NodeTypes = {
input: InputNode,
@@ -141,12 +142,14 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
proOptions,
defaultEdgeOptions,
elevateEdgesOnSelect = false,
disableKeyboardA11y = false,
...rest
},
ref
) => {
const nodeTypesWrapped = useNodeOrEdgeTypes(nodeTypes, createNodeTypes) as NodeTypesWrapped;
const edgeTypesWrapped = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes) as EdgeTypesWrapped;
const rfId = useId();
return (
<div {...rest} ref={ref} className={cc(['react-flow', className])}>
@@ -205,7 +208,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
noWheelClassName={noWheelClassName}
noPanClassName={noPanClassName}
elevateEdgesOnSelect={elevateEdgesOnSelect}
id={rest?.id}
rfId={rfId}
disableKeyboardA11y={disableKeyboardA11y}
/>
<StoreUpdater
nodes={nodes}
@@ -245,6 +249,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
{children}
<Attribution proOptions={proOptions} position={attributionPosition} />
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} />
</Wrapper>
</div>
);
@@ -12,7 +12,7 @@ import { FlowRendererProps } from '../FlowRenderer';
type ZoomPaneProps = Omit<
FlowRendererProps,
'deleteKeyCode' | 'selectionKeyCode' | 'multiSelectionKeyCode' | 'noDragClassName'
'deleteKeyCode' | 'selectionKeyCode' | 'multiSelectionKeyCode' | 'noDragClassName' | 'disableKeyboardA11y'
> & { selectionKeyPressed: boolean };
const viewChanged = (prevViewport: Viewport, eventViewport: any): boolean =>