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 { Node, EdgeLayouted, Edge, EdgeEvents } from '$lib/types';
|
||||||
import type { SvelteFlowStore } from '$lib/store/types';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
edge,
|
edge,
|
||||||
@@ -104,7 +105,6 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- TODO: aria-label, describedby -->
|
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
<svg style:z-index={zIndex} class="svelte-flow__edge-wrapper">
|
<svg style:z-index={zIndex} class="svelte-flow__edge-wrapper">
|
||||||
@@ -136,6 +136,7 @@
|
|||||||
: ariaLabel
|
: ariaLabel
|
||||||
? ariaLabel
|
? ariaLabel
|
||||||
: `Edge from ${source} to ${target}`}
|
: `Edge from ${source} to ${target}`}
|
||||||
|
aria-describedby={focusable ? `${ARIA_EDGE_DESC_KEY}-${store.flowId}` : undefined}
|
||||||
role={focusable ? 'button' : 'img'}
|
role={focusable ? 'button' : 'img'}
|
||||||
onkeydown={focusable ? onkeydown : undefined}
|
onkeydown={focusable ? onkeydown : undefined}
|
||||||
tabindex={focusable ? 0 : undefined}
|
tabindex={focusable ? 0 : undefined}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import type { ConnectableContext, NodeWrapperProps } from './types';
|
import type { ConnectableContext, NodeWrapperProps } from './types';
|
||||||
import type { Node, Edge, NodeEvents } from '$lib/types';
|
import type { Node, Edge, NodeEvents } from '$lib/types';
|
||||||
import { toPxString } from '$lib/utils';
|
import { toPxString } from '$lib/utils';
|
||||||
|
import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
store = $bindable(),
|
store = $bindable(),
|
||||||
@@ -183,12 +184,9 @@
|
|||||||
// prevent default scrolling behavior on arrow key press when node is moved
|
// prevent default scrolling behavior on arrow key press when node is moved
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// TODO: aria live message
|
store.ariaLiveMessage = `Moved selected node ${event.key
|
||||||
// store.setState({
|
.replace('Arrow', '')
|
||||||
// ariaLiveMessage: `Moved selected node ${event.key
|
.toLowerCase()}. New position, x: ${node.internals.positionAbsolute.x}, y: ${node.internals.positionAbsolute.y}`;
|
||||||
// .replace('Arrow', '')
|
|
||||||
// .toLowerCase()}. New position, x: ${~~internals.positionAbsolute.x}, y: ${~~internals.positionAbsolute.y}`,
|
|
||||||
// });
|
|
||||||
|
|
||||||
store.moveSelectedNodes(arrowKeyDiffs[event.key], event.shiftKey ? 4 : 1);
|
store.moveSelectedNodes(arrowKeyDiffs[event.key], event.shiftKey ? 4 : 1);
|
||||||
}
|
}
|
||||||
@@ -250,6 +248,9 @@
|
|||||||
onkeydown={focusable ? onKeyDown : undefined}
|
onkeydown={focusable ? onKeyDown : undefined}
|
||||||
tabIndex={focusable ? 0 : undefined}
|
tabIndex={focusable ? 0 : undefined}
|
||||||
role={focusable ? 'button' : undefined}
|
role={focusable ? 'button' : undefined}
|
||||||
|
aria-describedby={store.disableKeyboardA11y
|
||||||
|
? undefined
|
||||||
|
: `${ARIA_NODE_DESC_KEY}-${store.flowId}`}
|
||||||
>
|
>
|
||||||
<NodeComponent
|
<NodeComponent
|
||||||
{data}
|
{data}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
import type { SvelteFlowProps } from './types';
|
import type { SvelteFlowProps } from './types';
|
||||||
import { type ProviderContext, type StoreContext } from '$lib/store/types';
|
import { type ProviderContext, type StoreContext } from '$lib/store/types';
|
||||||
import Wrapper from './Wrapper.svelte';
|
import Wrapper from './Wrapper.svelte';
|
||||||
|
import { A11yDescriptions } from '$lib/components/A11yDescriptions';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
width,
|
width,
|
||||||
@@ -193,5 +194,6 @@
|
|||||||
</Pane>
|
</Pane>
|
||||||
</Zoom>
|
</Zoom>
|
||||||
<Attribution {proOptions} position={attributionPosition} />
|
<Attribution {proOptions} position={attributionPosition} />
|
||||||
|
<A11yDescriptions {store} />
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
|
|||||||
panActivationKeyPressed: boolean = $state(false);
|
panActivationKeyPressed: boolean = $state(false);
|
||||||
zoomActivationKeyPressed: boolean = $state(false);
|
zoomActivationKeyPressed: boolean = $state(false);
|
||||||
selectionRectMode: string | null = $state(null);
|
selectionRectMode: string | null = $state(null);
|
||||||
|
ariaLiveMessage = $state<string>('');
|
||||||
selectionMode: SelectionMode = $derived(signals.props.selectionMode ?? SelectionMode.Partial);
|
selectionMode: SelectionMode = $derived(signals.props.selectionMode ?? SelectionMode.Partial);
|
||||||
|
|
||||||
nodeTypes: NodeTypes = $derived({ ...initialNodeTypes, ...signals.props.nodeTypes });
|
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._connection = initialConnection;
|
||||||
this.clickConnectStartHandle = null;
|
this.clickConnectStartHandle = null;
|
||||||
this.viewport = signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 };
|
this.viewport = signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 };
|
||||||
|
this.ariaLiveMessage = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new SvelteFlowStore();
|
return new SvelteFlowStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user