From 9f82b15d582196fa7e4f3ef576f4ce04cf40d6dd Mon Sep 17 00:00:00 2001 From: Abbey Yacoe Date: Tue, 3 Jun 2025 11:48:06 +0200 Subject: [PATCH] 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;