From ba9df8eaf848ce9eea937ec94572b7d9c35f2140 Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Mon, 26 May 2025 17:08:29 +0200 Subject: [PATCH 01/11] feat: add role prop to nodes --- examples/react/src/examples/Basic/index.tsx | 3 ++- packages/react/src/components/NodeWrapper/index.tsx | 4 ++-- packages/react/src/types/nodes.ts | 10 +++++++++- packages/system/src/types/nodes.ts | 8 ++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) 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; From cdc08f1b5477daba51b55b30b68f9ed5285b76b0 Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Mon, 2 Jun 2025 12:10:54 +0200 Subject: [PATCH 02/11] chore: swap role for ariaRole, add svelte --- examples/react/src/examples/Basic/index.tsx | 3 +-- packages/react/src/components/NodeWrapper/index.tsx | 3 +-- packages/react/src/container/ReactFlow/index.tsx | 1 + packages/react/src/types/nodes.ts | 10 +--------- .../src/lib/components/NodeWrapper/NodeWrapper.svelte | 2 +- packages/system/src/types/nodes.ts | 5 +++-- 6 files changed, 8 insertions(+), 16 deletions(-) diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 58123d57..168bcff6 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -25,10 +25,9 @@ const initialNodes: Node[] = [ { id: '1', type: 'input', - data: { label: 'Focusable Node' }, + data: { label: 'Node 1' }, 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 272f9de9..10909bcd 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -39,7 +39,6 @@ export function NodeWrapper({ nodeTypes, nodeClickDistance, onError, - role, }: NodeWrapperProps) { const { node, internals, isParent } = useStore((s) => { const node = s.nodeLookup.get(id)! as InternalNode; @@ -192,7 +191,7 @@ export function NodeWrapper({ onDoubleClick={onDoubleClickHandler} onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} - role={node.role === null ? undefined : node.role || (isFocusable ? 'button' : undefined)} + role={node.ariaRole === null ? undefined : node.ariaRole || (isFocusable ? 'button' : undefined)} aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`} aria-label={node.ariaLabel} > diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 2247a004..bd2f1334 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -170,6 +170,7 @@ function ReactFlow( ref={ref} className={cc(['react-flow', className, colorModeClassName])} id={id} + role="application" > = { nodeExtent?: CoordinateExtent; onError?: OnError; nodeClickDistance?: number; - role?: NodeRole; }; /** diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index f053cd82..dce41767 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -252,7 +252,7 @@ : undefined} onkeydown={focusable ? onKeyDown : undefined} tabIndex={focusable ? 0 : undefined} - role={focusable ? 'button' : undefined} + role={node.ariaRole === null ? undefined : node.ariaRole || (focusable ? 'button' : undefined)} aria-describedby={store.disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${store.flowId}`} diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index f90b367d..97c7fa8d 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -79,7 +79,8 @@ export type NodeBase< * When not specified, focusable nodes default to 'button' role. * @default "button" (for focusable nodes) */ - role?: NodeRole; + + ariaRole?: AriaRole; measured?: { width?: number; height?: number; @@ -187,4 +188,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; +export type AriaRole = 'button' | 'group' | 'listitem' | 'application' | 'region' | 'none' | null; From 28b043060626fa5af8b630ef0f1b04c37e2166b3 Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Mon, 2 Jun 2025 13:19:27 +0200 Subject: [PATCH 03/11] chore: set default role to group, add aria-roledescription --- packages/react/src/components/NodeWrapper/index.tsx | 3 ++- .../src/lib/components/NodeWrapper/NodeWrapper.svelte | 3 ++- packages/system/src/types/nodes.ts | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 10909bcd..218ce700 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -191,7 +191,8 @@ export function NodeWrapper({ onDoubleClick={onDoubleClickHandler} onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} - role={node.ariaRole === null ? undefined : node.ariaRole || (isFocusable ? 'button' : undefined)} + role={node.ariaRole === null ? undefined : node.ariaRole || (isFocusable ? 'group' : undefined)} + aria-roledescription={node.ariaRoleDescription || 'node'} aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`} aria-label={node.ariaLabel} > diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index dce41767..3c7e7db9 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -252,7 +252,8 @@ : undefined} onkeydown={focusable ? onKeyDown : undefined} tabIndex={focusable ? 0 : undefined} - role={node.ariaRole === null ? undefined : node.ariaRole || (focusable ? 'button' : undefined)} + role={node.ariaRole === null ? undefined : node.ariaRole || (focusable ? 'group' : undefined)} + aria-roledescription={node.ariaRoleDescription || 'node'} aria-describedby={store.disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${store.flowId}`} diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 97c7fa8d..82446cd7 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -75,12 +75,15 @@ export type NodeBase< 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) + * @default "group" */ ariaRole?: AriaRole; + /** + * A description of the node's role, used for accessibility. + * @default "node" + */ + ariaRoleDescription?: string; measured?: { width?: number; height?: number; From cb3e80dc54a62d1f30e61676dd1968f68c87896c Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Mon, 2 Jun 2025 14:35:44 +0200 Subject: [PATCH 04/11] fix(EdgeLabel): fix a11y error in svelte --- packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte b/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte index 187567d7..5ba2ba38 100644 --- a/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte +++ b/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte @@ -36,7 +36,8 @@ style:width={toPxString(width)} style:height={toPxString(height)} style:z-index={z} - role="button" + role="group" + aria-roledescription="edge label" tabindex="-1" onclick={() => { if (selectEdgeOnClick && id) store.handleEdgeSelection(id); From 848b486b2201b650ecb3317f367a723edb2458e1 Mon Sep 17 00:00:00 2001 From: printer_scanner Date: Mon, 2 Jun 2025 14:43:06 +0200 Subject: [PATCH 05/11] Create dry-pianos-fail.md --- .changeset/dry-pianos-fail.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/dry-pianos-fail.md diff --git a/.changeset/dry-pianos-fail.md b/.changeset/dry-pianos-fail.md new file mode 100644 index 00000000..246cf930 --- /dev/null +++ b/.changeset/dry-pianos-fail.md @@ -0,0 +1,7 @@ +--- +"@xyflow/react": minor +"@xyflow/svelte": minor +"@xyflow/system": minor +--- + +feat: add `ariaRole` prop to nodes From 9f82b15d582196fa7e4f3ef576f4ce04cf40d6dd Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Tue, 3 Jun 2025 11:48:06 +0200 Subject: [PATCH 06/11] chore: add roles to edges --- .../src/components/EdgeWrapper/index.tsx | 23 ++++++++++--------- .../lib/components/EdgeLabel/EdgeLabel.svelte | 2 -- .../components/EdgeWrapper/EdgeWrapper.svelte | 3 ++- packages/system/src/types/edges.ts | 13 ++++++++++- packages/system/src/types/nodes.ts | 2 +- packages/system/src/types/utils.ts | 5 ++++ 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx index 72c5c2f8..18a8f3b8 100644 --- a/packages/react/src/components/EdgeWrapper/index.tsx +++ b/packages/react/src/components/EdgeWrapper/index.tsx @@ -136,28 +136,28 @@ export function EdgeWrapper({ const onEdgeDoubleClick = onDoubleClick ? (event: React.MouseEvent) => { - onDoubleClick(event, { ...edge }); - } + onDoubleClick(event, { ...edge }); + } : undefined; const onEdgeContextMenu = onContextMenu ? (event: React.MouseEvent) => { - onContextMenu(event, { ...edge }); - } + onContextMenu(event, { ...edge }); + } : undefined; const onEdgeMouseEnter = onMouseEnter ? (event: React.MouseEvent) => { - onMouseEnter(event, { ...edge }); - } + onMouseEnter(event, { ...edge }); + } : undefined; const onEdgeMouseMove = onMouseMove ? (event: React.MouseEvent) => { - onMouseMove(event, { ...edge }); - } + onMouseMove(event, { ...edge }); + } : undefined; const onEdgeMouseLeave = onMouseLeave ? (event: React.MouseEvent) => { - onMouseLeave(event, { ...edge }); - } + onMouseLeave(event, { ...edge }); + } : undefined; const onKeyDown = (event: KeyboardEvent) => { @@ -198,7 +198,8 @@ export function EdgeWrapper({ onMouseLeave={onEdgeMouseLeave} onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} - role={isFocusable ? 'button' : 'img'} + role={edge.ariaRole === null ? undefined : edge.ariaRole || (isFocusable ? 'group' : 'img')} + aria-roledescription={edge.ariaRoleDescription || 'edge'} data-id={id} data-testid={`rf__edge-${id}`} aria-label={ diff --git a/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte b/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte index 5ba2ba38..83ce1411 100644 --- a/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte +++ b/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte @@ -36,8 +36,6 @@ style:width={toPxString(width)} style:height={toPxString(height)} style:z-index={z} - role="group" - aria-roledescription="edge label" tabindex="-1" onclick={() => { if (selectEdgeOnClick && id) store.handleEdgeSelection(id); diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index c1c988fd..c9f77d85 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -137,7 +137,8 @@ ? ariaLabel : `Edge from ${source} to ${target}`} aria-describedby={focusable ? `${ARIA_EDGE_DESC_KEY}-${store.flowId}` : undefined} - role={focusable ? 'button' : 'img'} + role={edge.ariaRole === null ? undefined : edge.ariaRole || (focusable ? 'group' : 'img')} + aria-roledescription={edge.ariaRoleDescription || 'edge'} onkeydown={focusable ? onkeydown : undefined} tabindex={focusable ? 0 : undefined} > diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index c0568200..ec83b256 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -1,4 +1,4 @@ -import { Position } from './utils'; +import { Position, AriaRole } from './utils'; export type EdgeBase< EdgeData extends Record = Record, @@ -40,6 +40,17 @@ export type EdgeBase< * This property sets the width of that invisible path. */ interactionWidth?: number; + /** + * The ARIA role attribute for the edge, used for accessibility. + * @default "group" + */ + + ariaRole?: AriaRole; + /** + * A description of the edge's, used for accessibility. + * @default "node" + */ + ariaRoleDescription?: string; }; export type SmoothStepPathOptions = { diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 82446cd7..3f2978ad 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -1,5 +1,6 @@ import type { XYPosition, Position, CoordinateExtent, Handle } from '.'; import { Optional } from '../utils/types'; +import { AriaRole } from './utils'; /** * Framework independent node data structure. @@ -191,4 +192,3 @@ export type Align = 'center' | 'start' | 'end'; export type NodeLookup = Map; export type ParentLookup = Map>; -export type AriaRole = 'button' | 'group' | 'listitem' | 'application' | 'region' | 'none' | null; diff --git a/packages/system/src/types/utils.ts b/packages/system/src/types/utils.ts index 274bfe55..d46bc76c 100644 --- a/packages/system/src/types/utils.ts +++ b/packages/system/src/types/utils.ts @@ -56,3 +56,8 @@ export type Transform = [number, number, number]; * to represent an unbounded extent. */ export type CoordinateExtent = [[number, number], [number, number]]; + +/** + * The `AriaRole` type is used to define the role of an element in the accessibility tree. + */ +export type AriaRole = 'button' | 'group' | 'listitem' | 'application' | 'region' | 'none' | null; From 416f533622dad3113300036d34692a16bdf3f4dc Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Tue, 3 Jun 2025 12:59:43 +0200 Subject: [PATCH 07/11] fix: update default value --- packages/system/src/types/edges.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index ec83b256..a8446e00 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -48,7 +48,7 @@ export type EdgeBase< ariaRole?: AriaRole; /** * A description of the edge's, used for accessibility. - * @default "node" + * @default "edge" */ ariaRoleDescription?: string; }; From 96eba257aa4276ee8354bb213be13ea07acad041 Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Tue, 3 Jun 2025 14:41:25 +0200 Subject: [PATCH 08/11] chore: use svelte and react types for AriaRole --- packages/react/src/types/edges.ts | 14 +++++++++++++- packages/react/src/types/nodes.ts | 8 +++++++- packages/svelte/src/lib/types/edges.ts | 1 + packages/svelte/src/lib/types/nodes.ts | 7 ++++++- packages/system/src/types/edges.ts | 8 +------- packages/system/src/types/nodes.ts | 7 ------- packages/system/src/types/utils.ts | 5 ----- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index a5c22c75..a4325ebc 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -1,6 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { CSSProperties, SVGAttributes, ReactNode, MouseEvent as ReactMouseEvent, ComponentType } from 'react'; +import type { + CSSProperties, + SVGAttributes, + ReactNode, + MouseEvent as ReactMouseEvent, + ComponentType, + AriaRole, +} from 'react'; import type { EdgeBase, BezierPathOptions, @@ -176,6 +183,11 @@ export type BaseEdgeProps = Omit, 'd' | 'path' | ' * element in a separate SVG document or element. */ markerEnd?: string; + /** + * The ARIA role attribute for the edge, used for accessibility. + * @default "group" + */ + ariaRole?: AriaRole; }; /** diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 8dc213d7..d9e2671b 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -1,4 +1,4 @@ -import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; +import type { CSSProperties, MouseEvent as ReactMouseEvent, AriaRole } from 'react'; import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, InternalNodeBase } from '@xyflow/system'; import { NodeTypes } from './general'; @@ -18,6 +18,12 @@ export type Node< className?: string; resizing?: boolean; focusable?: boolean; + /** + * The ARIA role attribute for the node element, used for accessibility. + * @default "group" + */ + + ariaRole?: AriaRole; }; /** diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index c903512a..2b56f48b 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -46,6 +46,7 @@ export type BaseEdgeProps = Pick< * @example 'url(#arrow)' */ markerEnd?: string; + ariaRole?: HTMLAttributes['role']; class?: ClassValue; } & HTMLAttributes; diff --git a/packages/svelte/src/lib/types/nodes.ts b/packages/svelte/src/lib/types/nodes.ts index f861c13b..f7f65469 100644 --- a/packages/svelte/src/lib/types/nodes.ts +++ b/packages/svelte/src/lib/types/nodes.ts @@ -1,5 +1,5 @@ import type { Component } from 'svelte'; -import type { ClassValue } from 'svelte/elements'; +import type { ClassValue, HTMLAttributes } from 'svelte/elements'; import type { InternalNodeBase, NodeBase, NodeProps as NodePropsBase } from '@xyflow/system'; /** @@ -21,6 +21,11 @@ export type Node< class?: ClassValue; style?: string; focusable?: boolean; + /** + * The ARIA role attribute for the node element, used for accessibility. + * @default "group" + */ + ariaRole?: HTMLAttributes['role']; }; // @todo: currently generics for nodes are not really supported diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index a8446e00..3e69bf4c 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -1,4 +1,4 @@ -import { Position, AriaRole } from './utils'; +import { Position } from './utils'; export type EdgeBase< EdgeData extends Record = Record, @@ -40,12 +40,6 @@ export type EdgeBase< * This property sets the width of that invisible path. */ interactionWidth?: number; - /** - * The ARIA role attribute for the edge, used for accessibility. - * @default "group" - */ - - ariaRole?: AriaRole; /** * A description of the edge's, used for accessibility. * @default "edge" diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 3f2978ad..49012024 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -1,6 +1,5 @@ import type { XYPosition, Position, CoordinateExtent, Handle } from '.'; import { Optional } from '../utils/types'; -import { AriaRole } from './utils'; /** * Framework independent node data structure. @@ -74,12 +73,6 @@ export type NodeBase< */ origin?: NodeOrigin; handles?: NodeHandle[]; - /** - * The ARIA role attribute for the node element, used for accessibility. - * @default "group" - */ - - ariaRole?: AriaRole; /** * A description of the node's role, used for accessibility. * @default "node" diff --git a/packages/system/src/types/utils.ts b/packages/system/src/types/utils.ts index d46bc76c..274bfe55 100644 --- a/packages/system/src/types/utils.ts +++ b/packages/system/src/types/utils.ts @@ -56,8 +56,3 @@ export type Transform = [number, number, number]; * to represent an unbounded extent. */ export type CoordinateExtent = [[number, number], [number, number]]; - -/** - * The `AriaRole` type is used to define the role of an element in the accessibility tree. - */ -export type AriaRole = 'button' | 'group' | 'listitem' | 'application' | 'region' | 'none' | null; From 79db292c4d4ce5937ccd900f7295fb242c5f40f9 Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Tue, 3 Jun 2025 15:13:32 +0200 Subject: [PATCH 09/11] fix: move ariaRole --- packages/react/src/types/edges.ts | 10 +++++----- packages/svelte/src/lib/types/edges.ts | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index a4325ebc..83d74c2d 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -64,6 +64,11 @@ export type Edge< */ reconnectable?: boolean | HandleType; focusable?: boolean; + /** + * The ARIA role attribute for the edge, used for accessibility. + * @default "group" + */ + ariaRole?: AriaRole; }; type SmoothStepEdge = Record> = Edge< @@ -183,11 +188,6 @@ export type BaseEdgeProps = Omit, 'd' | 'path' | ' * element in a separate SVG document or element. */ markerEnd?: string; - /** - * The ARIA role attribute for the edge, used for accessibility. - * @default "group" - */ - ariaRole?: AriaRole; }; /** diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index 2b56f48b..182f1e48 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -25,6 +25,11 @@ export type Edge< style?: string; class?: ClassValue; focusable?: boolean; + /** + * The ARIA role attribute for the edge, used for accessibility. + * @default "group" + */ + ariaRole?: HTMLAttributes['role']; }; export type BaseEdgeProps = Pick< @@ -46,7 +51,6 @@ export type BaseEdgeProps = Pick< * @example 'url(#arrow)' */ markerEnd?: string; - ariaRole?: HTMLAttributes['role']; class?: ClassValue; } & HTMLAttributes; From c9838359a197c8e812128888e6ff49cecfcd2599 Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Tue, 3 Jun 2025 15:21:02 +0200 Subject: [PATCH 10/11] chore: remove null check --- packages/react/src/components/EdgeWrapper/index.tsx | 2 +- packages/react/src/components/NodeWrapper/index.tsx | 2 +- .../svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte | 2 +- .../svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx index 18a8f3b8..a5db4441 100644 --- a/packages/react/src/components/EdgeWrapper/index.tsx +++ b/packages/react/src/components/EdgeWrapper/index.tsx @@ -198,7 +198,7 @@ export function EdgeWrapper({ onMouseLeave={onEdgeMouseLeave} onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} - role={edge.ariaRole === null ? undefined : edge.ariaRole || (isFocusable ? 'group' : 'img')} + role={edge.ariaRole ?? (isFocusable ? 'group' : 'img')} aria-roledescription={edge.ariaRoleDescription || 'edge'} data-id={id} data-testid={`rf__edge-${id}`} diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index 218ce700..11f65d42 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -191,7 +191,7 @@ export function NodeWrapper({ onDoubleClick={onDoubleClickHandler} onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} - role={node.ariaRole === null ? undefined : node.ariaRole || (isFocusable ? 'group' : undefined)} + role={node.ariaRole ?? (isFocusable ? 'group' : undefined)} aria-roledescription={node.ariaRoleDescription || 'node'} aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`} aria-label={node.ariaLabel} diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index c9f77d85..c63d85e5 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -137,7 +137,7 @@ ? ariaLabel : `Edge from ${source} to ${target}`} aria-describedby={focusable ? `${ARIA_EDGE_DESC_KEY}-${store.flowId}` : undefined} - role={edge.ariaRole === null ? undefined : edge.ariaRole || (focusable ? 'group' : 'img')} + role={edge.ariaRole ?? (focusable ? 'group' : 'img')} aria-roledescription={edge.ariaRoleDescription || 'edge'} onkeydown={focusable ? onkeydown : undefined} tabindex={focusable ? 0 : undefined} diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index 3c7e7db9..e8a11de9 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -252,7 +252,7 @@ : undefined} onkeydown={focusable ? onKeyDown : undefined} tabIndex={focusable ? 0 : undefined} - role={node.ariaRole === null ? undefined : node.ariaRole || (focusable ? 'group' : undefined)} + role={node.ariaRole ?? (focusable ? 'group' : undefined)} aria-roledescription={node.ariaRoleDescription || 'node'} aria-describedby={store.disableKeyboardA11y ? undefined From d5dd91b920ac9310ba6b4f4e9e179bcdcce0683e Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Tue, 3 Jun 2025 20:54:51 +0200 Subject: [PATCH 11/11] Update dry-pianos-fail.md --- .changeset/dry-pianos-fail.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/dry-pianos-fail.md b/.changeset/dry-pianos-fail.md index 246cf930..d8c7acf4 100644 --- a/.changeset/dry-pianos-fail.md +++ b/.changeset/dry-pianos-fail.md @@ -1,7 +1,7 @@ --- "@xyflow/react": minor "@xyflow/svelte": minor -"@xyflow/system": minor +"@xyflow/system": patch --- -feat: add `ariaRole` prop to nodes +Add `ariaRole` prop to nodes and edges