add aria labels

This commit is contained in:
peterkogo
2025-04-17 18:06:33 +02:00
parent aaed8c721e
commit 473ddc5b61
6 changed files with 48 additions and 7 deletions

View File

@@ -0,0 +1,30 @@
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
import type { SvelteFlowStore } from '$lib/store/types';
import type { Node, Edge } from '$lib/types';
import { ARIA_EDGE_DESC_KEY, ARIA_LIVE_MESSAGE, ARIA_NODE_DESC_KEY } from '.';
let { store }: { store: SvelteFlowStore<NodeType, EdgeType> } = $props();
</script>
<div id={`${ARIA_NODE_DESC_KEY}-${store.flowId}`} style="display: none;">
Press enter or space to select a node.
{#if !store.disableKeyboardA11y}
You can then use the arrow keys to move the node around.
{/if}
Press delete to remove it and escape to cancel.{' '}
</div>
<div id={`${ARIA_EDGE_DESC_KEY}-${store.flowId}`} style="display: none;">
Press enter or space to select an edge. You can then press delete to remove it or escape to
cancel.
</div>
{#if !store.disableKeyboardA11y}
<div
id={`${ARIA_LIVE_MESSAGE}-${store.flowId}`}
aria-live="assertive"
aria-atomic="true"
style="position: absolute; width: 1px; height: 1px; margin: -1px; border: 0; padding: 0; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); clip-path: inset(100%);"
>
{store.ariaLiveMessage}
</div>
{/if}

View File

@@ -0,0 +1,5 @@
export { default as A11yDescriptions } from './A11yDescriptions.svelte';
export const ARIA_NODE_DESC_KEY = 'svelte-flow__node-desc';
export const ARIA_EDGE_DESC_KEY = 'svelte-flow__edge-desc';
export const ARIA_LIVE_MESSAGE = 'svelte-flow__aria-live';

View File

@@ -7,6 +7,7 @@
import type { Node, EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types';
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
const {
edge,
@@ -104,7 +105,6 @@
};
</script>
<!-- TODO: aria-label, describedby -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
{#if !hidden}
<svg style:z-index={zIndex} class="svelte-flow__edge-wrapper">
@@ -136,6 +136,7 @@
: ariaLabel
? ariaLabel
: `Edge from ${source} to ${target}`}
aria-describedby={focusable ? `${ARIA_EDGE_DESC_KEY}-${store.flowId}` : undefined}
role={focusable ? 'button' : 'img'}
onkeydown={focusable ? onkeydown : undefined}
tabindex={focusable ? 0 : undefined}

View File

@@ -15,6 +15,7 @@
import type { ConnectableContext, NodeWrapperProps } from './types';
import type { Node, Edge, NodeEvents } from '$lib/types';
import { toPxString } from '$lib/utils';
import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
let {
store = $bindable(),
@@ -183,12 +184,9 @@
// prevent default scrolling behavior on arrow key press when node is moved
event.preventDefault();
// TODO: aria live message
// store.setState({
// ariaLiveMessage: `Moved selected node ${event.key
// .replace('Arrow', '')
// .toLowerCase()}. New position, x: ${~~internals.positionAbsolute.x}, y: ${~~internals.positionAbsolute.y}`,
// });
store.ariaLiveMessage = `Moved selected node ${event.key
.replace('Arrow', '')
.toLowerCase()}. New position, x: ${node.internals.positionAbsolute.x}, y: ${node.internals.positionAbsolute.y}`;
store.moveSelectedNodes(arrowKeyDiffs[event.key], event.shiftKey ? 4 : 1);
}
@@ -250,6 +248,9 @@
onkeydown={focusable ? onKeyDown : undefined}
tabIndex={focusable ? 0 : undefined}
role={focusable ? 'button' : undefined}
aria-describedby={store.disableKeyboardA11y
? undefined
: `${ARIA_NODE_DESC_KEY}-${store.flowId}`}
>
<NodeComponent
{data}

View File

@@ -18,6 +18,7 @@
import type { SvelteFlowProps } from './types';
import { type ProviderContext, type StoreContext } from '$lib/store/types';
import Wrapper from './Wrapper.svelte';
import { A11yDescriptions } from '$lib/components/A11yDescriptions';
let {
width,
@@ -193,5 +194,6 @@
</Pane>
</Zoom>
<Attribution {proOptions} position={attributionPosition} />
<A11yDescriptions {store} />
{@render children?.()}
</Wrapper>

View File

@@ -232,6 +232,7 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
panActivationKeyPressed: boolean = $state(false);
zoomActivationKeyPressed: boolean = $state(false);
selectionRectMode: string | null = $state(null);
ariaLiveMessage = $state<string>('');
selectionMode: SelectionMode = $derived(signals.props.selectionMode ?? SelectionMode.Partial);
nodeTypes: NodeTypes = $derived({ ...initialNodeTypes, ...signals.props.nodeTypes });
@@ -374,6 +375,7 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
this._connection = initialConnection;
this.clickConnectStartHandle = null;
this.viewport = signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 };
this.ariaLiveMessage = '';
}
}
return new SvelteFlowStore();