feat(nodes-edges): add domAttributes

This commit is contained in:
moklick
2025-06-05 15:41:44 +02:00
parent 3e6bcf51e3
commit 09458f52ff
12 changed files with 56 additions and 19 deletions

View File

@@ -0,0 +1,7 @@
---
'@xyflow/react': minor
'@xyflow/svelte': minor
'@xyflow/system': minor
---
Add `domAttributes` option for nodes and edges

View File

@@ -24,6 +24,10 @@ const initialNodes: Node[] = [
data: { label: 'A11y Node 1' },
position: { x: 250, y: 5 },
className: 'light',
domAttributes: {
tabIndex: 10,
'aria-roledescription': 'A11y Node',
},
},
{
id: '2',

View File

@@ -199,7 +199,7 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
onKeyDown={isFocusable ? onKeyDown : undefined}
tabIndex={isFocusable ? 0 : undefined}
role={edge.ariaRole ?? (isFocusable ? 'group' : 'img')}
aria-roledescription={edge.ariaRoleDescription || 'edge'}
aria-roledescription={edge.domAttributes?.['aria-roledescription'] || 'edge'}
data-id={id}
data-testid={`rf__edge-${id}`}
aria-label={
@@ -207,6 +207,7 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
}
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
ref={edgeRef}
{...edge.domAttributes}
>
{!reconnecting && (
<EdgeComponent

View File

@@ -196,9 +196,10 @@ export function NodeWrapper<NodeType extends Node>({
onKeyDown={isFocusable ? onKeyDown : undefined}
tabIndex={isFocusable ? 0 : undefined}
role={node.ariaRole ?? (isFocusable ? 'group' : undefined)}
aria-roledescription={node.ariaRoleDescription || 'node'}
aria-roledescription={node.domAttributes?.['aria-roledescription'] || 'node'}
aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`}
aria-label={node.ariaLabel}
{...node.domAttributes}
>
<Provider value={id}>
<NodeComponent

View File

@@ -69,6 +69,10 @@ export type Edge<
* @default "group"
*/
ariaRole?: AriaRole;
/**
* General escape hatch for adding custom attributes to the edge's DOM element.
*/
domAttributes?: Omit<SVGAttributes<SVGGElement>, 'id' | 'style' | 'className' | 'role' | 'aria-label'>;
};
type SmoothStepEdge<EdgeData extends Record<string, unknown> = Record<string, unknown>> = Edge<

View File

@@ -1,4 +1,4 @@
import type { CSSProperties, MouseEvent as ReactMouseEvent, AriaRole } from 'react';
import type { CSSProperties, MouseEvent as ReactMouseEvent, AriaRole, HTMLAttributes, DOMAttributes } from 'react';
import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, InternalNodeBase } from '@xyflow/system';
import { NodeTypes } from './general';
@@ -22,8 +22,15 @@ export type Node<
* The ARIA role attribute for the node element, used for accessibility.
* @default "group"
*/
ariaRole?: AriaRole;
/**
* General escape hatch for adding custom attributes to the node's DOM element.
*/
domAttributes?: Omit<
HTMLAttributes<HTMLDivElement>,
'id' | 'style' | 'className' | 'draggable' | 'role' | 'aria-label' | keyof DOMAttributes<HTMLDivElement>
>;
};
/**

View File

@@ -138,9 +138,10 @@
: `Edge from ${source} to ${target}`}
aria-describedby={focusable ? `${ARIA_EDGE_DESC_KEY}-${store.flowId}` : undefined}
role={edge.ariaRole ?? (focusable ? 'group' : 'img')}
aria-roledescription={edge.ariaRoleDescription || 'edge'}
aria-roledescription={edge.domAttributes?.['aria-roledescription'] || 'edge'}
onkeydown={focusable ? onkeydown : undefined}
tabindex={focusable ? 0 : undefined}
{...edge.domAttributes}
>
<EdgeComponent
{id}

View File

@@ -254,10 +254,11 @@
onkeydown={focusable ? onKeyDown : undefined}
tabIndex={focusable ? 0 : undefined}
role={node.ariaRole ?? (focusable ? 'group' : undefined)}
aria-roledescription={node.ariaRoleDescription || 'node'}
aria-roledescription={node.domAttributes?.['aria-roledescription'] || 'node'}
aria-describedby={store.disableKeyboardA11y
? undefined
: `${ARIA_NODE_DESC_KEY}-${store.flowId}`}
{...node.domAttributes}
>
<NodeComponent
{data}

View File

@@ -1,4 +1,5 @@
import type { Component } from 'svelte';
import type { ClassValue, HTMLAttributes, SVGAttributes } from 'svelte/elements';
import type {
EdgeBase,
BezierPathOptions,
@@ -9,7 +10,6 @@ import type {
} from '@xyflow/system';
import type { Node } from '$lib/types';
import type { ClassValue, HTMLAttributes } from 'svelte/elements';
/**
* An `Edge` is the complete description with everything Svelte Flow needs to know in order to
@@ -30,6 +30,13 @@ export type Edge<
* @default "group"
*/
ariaRole?: HTMLAttributes<HTMLElement>['role'];
/**
* General escape hatch for adding custom attributes to the edge's DOM element.
*/
domAttributes?: Omit<
SVGAttributes<SVGGElement>,
'id' | 'style' | 'class' | 'role' | 'aria-label'
>;
};
export type BaseEdgeProps = Pick<

View File

@@ -1,5 +1,5 @@
import type { Component } from 'svelte';
import type { ClassValue, HTMLAttributes } from 'svelte/elements';
import type { ClassValue, HTMLAttributes, DOMAttributes } from 'svelte/elements';
import type { InternalNodeBase, NodeBase, NodeProps as NodePropsBase } from '@xyflow/system';
/**
@@ -25,7 +25,21 @@ export type Node<
* The ARIA role attribute for the node element, used for accessibility.
* @default "group"
*/
ariaRole?: HTMLAttributes<HTMLElement>['role'];
ariaRole?: HTMLAttributes<HTMLDivElement>['role'];
/**
* General escape hatch for adding custom attributes to the node's DOM element.
*/
domAttributes?: Omit<
HTMLAttributes<HTMLDivElement>,
| 'id'
| 'style'
| 'class'
| 'draggable'
| 'role'
| 'aria-label'
| keyof DOMAttributes<HTMLDivElement>
>;
};
// @todo: currently generics for nodes are not really supported

View File

@@ -40,11 +40,6 @@ export type EdgeBase<
* This property sets the width of that invisible path.
*/
interactionWidth?: number;
/**
* A description of the edge's, used for accessibility.
* @default "edge"
*/
ariaRoleDescription?: string;
};
export type SmoothStepPathOptions = {

View File

@@ -73,11 +73,6 @@ export type NodeBase<
*/
origin?: NodeOrigin;
handles?: NodeHandle[];
/**
* A description of the node's role, used for accessibility.
* @default "node"
*/
ariaRoleDescription?: string;
measured?: {
width?: number;
height?: number;