add aria labels
This commit is contained in:
@@ -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}
|
||||
@@ -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';
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user