diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 168bcff6..58123d57 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -25,9 +25,10 @@ const initialNodes: Node[] = [ { id: '1', type: 'input', - data: { label: 'Node 1' }, + data: { label: 'Focusable Node' }, position: { x: 250, y: 5 }, className: 'light', + role: null, }, { id: '2', diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 5c0ce08b..272f9de9 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -39,6 +39,7 @@ export function NodeWrapper({ nodeTypes, nodeClickDistance, onError, + role, }: NodeWrapperProps) { const { node, internals, isParent } = useStore((s) => { const node = s.nodeLookup.get(id)! as InternalNode; @@ -154,7 +155,6 @@ export function NodeWrapper({ }); } }; - return (
({ onDoubleClick={onDoubleClickHandler} onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} - role={isFocusable ? 'button' : undefined} + role={node.role === null ? undefined : node.role || (isFocusable ? 'button' : undefined)} aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`} aria-label={node.ariaLabel} > diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 8dc213d7..9be41de7 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -1,5 +1,12 @@ import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; -import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, InternalNodeBase } from '@xyflow/system'; +import type { + CoordinateExtent, + NodeBase, + OnError, + NodeProps as NodePropsBase, + InternalNodeBase, + NodeRole, +} from '@xyflow/system'; import { NodeTypes } from './general'; @@ -58,6 +65,7 @@ export type NodeWrapperProps = { nodeExtent?: CoordinateExtent; onError?: OnError; nodeClickDistance?: number; + role?: NodeRole; }; /** diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 687f81ba..f90b367d 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -73,6 +73,13 @@ export type NodeBase< */ origin?: NodeOrigin; handles?: NodeHandle[]; + /** + * The ARIA role attribute for the node element, used for accessibility. + * Common values for nodes might be 'button', 'group', 'listitem', etc. + * When not specified, focusable nodes default to 'button' role. + * @default "button" (for focusable nodes) + */ + role?: NodeRole; measured?: { width?: number; height?: number; @@ -180,3 +187,4 @@ export type Align = 'center' | 'start' | 'end'; export type NodeLookup = Map; export type ParentLookup = Map>; +export type NodeRole = 'button' | 'group' | 'listitem' | 'application' | 'region' | 'none' | null;