chore: rename LabelConfig -> AriaLabelConfig, update dict names

This commit is contained in:
Abbey Yacoe
2025-06-03 15:42:57 +02:00
parent 65e912a755
commit 13b64afbaf
24 changed files with 94 additions and 92 deletions
+1 -1
View File
@@ -4,5 +4,5 @@
"@xyflow/system": patch "@xyflow/system": patch
--- ---
Add `labelConfig` prop for customizing UI text like aria labels and descriptions. Add `ariaLabelConfig` prop for customizing UI text like aria labels and descriptions.
+10 -10
View File
@@ -9,7 +9,7 @@ import {
Node, Node,
Edge, Edge,
OnNodeDrag, OnNodeDrag,
LabelConfig, AriaLabelConfig,
} from '@xyflow/react'; } from '@xyflow/react';
const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes); const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
@@ -44,17 +44,17 @@ const initialEdges: Edge[] = [
{ id: 'e1-3', source: '1', target: '3' }, { id: 'e1-3', source: '1', target: '3' },
]; ];
const labelConfig: Partial<LabelConfig> = { const ariaLabelConfig: Partial<AriaLabelConfig> = {
'a11yDescription.node.default': 'Custom Node Desc.', 'node.a11yDescription.default': 'Custom Node Desc.',
'a11yDescription.node.keyboardDisabled': 'Custom Keyboard Desc.', 'node.a11yDescription.keyboardDisabled': 'Custom Keyboard Desc.',
'a11yDescription.edge.default': 'Custom Edge Desc.', 'edge.a11yDescription.default': 'Custom Edge Desc.',
'a11yDescription.ariaLiveMessage': ({ direction, x, y }) => 'a11yDescription.ariaLiveMessage': ({ direction, x, y }) =>
`Custom Moved selected node ${direction}. New position, x: ${x}, y: ${y}`, `Custom Moved selected node ${direction}. New position, x: ${x}, y: ${y}`,
'controls.ariaLabel': 'Custom Controls Aria Label', 'controls.ariaLabel': 'Custom Controls Aria Label',
'controls.zoomin.title': 'Custom Zoom in', 'controls.zoomin.ariaLabel': 'Custom Zoom in',
'controls.zoomout.title': 'Custom Zoom Out', 'controls.zoomout.ariaLabel': 'Custom Zoom Out',
// 'controls.fitview.title': 'Custom Fit View', // 'controls.fitview.ariaLabel': 'Custom Fit View',
'controls.interactive.title': 'Custom Toggle Interactivity', 'controls.interactive.ariaLabel': 'Custom Toggle Interactivity',
'minimap.ariaLabel': 'Custom Aria Label', 'minimap.ariaLabel': 'Custom Aria Label',
}; };
@@ -76,7 +76,7 @@ const A11y = () => {
elevateEdgesOnSelect elevateEdgesOnSelect
elevateNodesOnSelect={false} elevateNodesOnSelect={false}
nodeDragThreshold={0} nodeDragThreshold={0}
labelConfig={labelConfig} ariaLabelConfig={ariaLabelConfig}
> >
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -25,17 +25,17 @@
bind:nodes bind:nodes
bind:edges bind:edges
fitView fitView
labelConfig={{ ariaLabelConfig={{
'a11yDescription.node.default': 'Svelte Custom Node Desc.', 'node.a11yDescription.default': 'Svelte Custom Node Desc.',
'a11yDescription.node.keyboardDisabled': 'Svelte Custom Keyboard Desc.', 'node.a11yDescription.keyboardDisabled': 'Svelte Custom Keyboard Desc.',
'a11yDescription.edge.default': 'Svelte Custom Edge Desc.', 'edge.a11yDescription.default': 'Svelte Custom Edge Desc.',
'a11yDescription.ariaLiveMessage': ({ direction, x, y }) => 'a11yDescription.ariaLiveMessage': ({ direction, x, y }) =>
`Custom Moved selected node ${direction}. New position, x: ${x}, y: ${y}`, `Custom Moved selected node ${direction}. New position, x: ${x}, y: ${y}`,
'controls.ariaLabel': 'Svelte Custom Control Aria Label', 'controls.ariaLabel': 'Svelte Custom Control Aria Label',
'controls.zoomin.title': 'Svelte Custom Zoom in', 'controls.zoomin.ariaLabel': 'Svelte Custom Zoom in',
'controls.zoomout.title': 'Svelte Custom Zoom Out', 'controls.zoomout.ariaLabel': 'Svelte Custom Zoom Out',
// 'controls.fitview.title': 'Svelte Custom Fit View', // 'controls.fitview.ariaLabel': 'Svelte Custom Fit View',
'controls.interactive.title': 'Svelte Custom Toggle Interactivity', 'controls.interactive.ariaLabel': 'Svelte Custom Toggle Interactivity',
'minimap.ariaLabel': 'Svelte Custom Minimap' 'minimap.ariaLabel': 'Svelte Custom Minimap'
}} }}
> >
@@ -19,7 +19,7 @@ const selector = (s: ReactFlowState) => ({
isInteractive: s.nodesDraggable || s.nodesConnectable || s.elementsSelectable, isInteractive: s.nodesDraggable || s.nodesConnectable || s.elementsSelectable,
minZoomReached: s.transform[2] <= s.minZoom, minZoomReached: s.transform[2] <= s.minZoom,
maxZoomReached: s.transform[2] >= s.maxZoom, maxZoomReached: s.transform[2] >= s.maxZoom,
labelConfig: s.labelConfig, ariaLabelConfig: s.ariaLabelConfig,
}); });
function ControlsComponent({ function ControlsComponent({
@@ -39,7 +39,7 @@ function ControlsComponent({
'aria-label': ariaLabel, 'aria-label': ariaLabel,
}: ControlProps) { }: ControlProps) {
const store = useStoreApi(); const store = useStoreApi();
const { isInteractive, minZoomReached, maxZoomReached, labelConfig } = useStore(selector, shallow); const { isInteractive, minZoomReached, maxZoomReached, ariaLabelConfig } = useStore(selector, shallow);
const { zoomIn, zoomOut, fitView } = useReactFlow(); const { zoomIn, zoomOut, fitView } = useReactFlow();
const onZoomInHandler = () => { const onZoomInHandler = () => {
@@ -74,15 +74,15 @@ function ControlsComponent({
position={position} position={position}
style={style} style={style}
data-testid="rf__controls" data-testid="rf__controls"
aria-label={ariaLabel ?? labelConfig['controls.ariaLabel']} aria-label={ariaLabel ?? ariaLabelConfig['controls.ariaLabel']}
> >
{showZoom && ( {showZoom && (
<> <>
<ControlButton <ControlButton
onClick={onZoomInHandler} onClick={onZoomInHandler}
className="react-flow__controls-zoomin" className="react-flow__controls-zoomin"
title={labelConfig['controls.zoomin.title']} title={ariaLabelConfig['controls.zoomin.ariaLabel']}
aria-label={labelConfig['controls.zoomin.title']} aria-label={ariaLabelConfig['controls.zoomin.ariaLabel']}
disabled={maxZoomReached} disabled={maxZoomReached}
> >
<PlusIcon /> <PlusIcon />
@@ -90,8 +90,8 @@ function ControlsComponent({
<ControlButton <ControlButton
onClick={onZoomOutHandler} onClick={onZoomOutHandler}
className="react-flow__controls-zoomout" className="react-flow__controls-zoomout"
title={labelConfig['controls.zoomout.title']} title={ariaLabelConfig['controls.zoomout.ariaLabel']}
aria-label={labelConfig['controls.zoomout.title']} aria-label={ariaLabelConfig['controls.zoomout.ariaLabel']}
disabled={minZoomReached} disabled={minZoomReached}
> >
<MinusIcon /> <MinusIcon />
@@ -102,8 +102,8 @@ function ControlsComponent({
<ControlButton <ControlButton
className="react-flow__controls-fitview" className="react-flow__controls-fitview"
onClick={onFitViewHandler} onClick={onFitViewHandler}
title={labelConfig['controls.fitview.title']} title={ariaLabelConfig['controls.fitview.ariaLabel']}
aria-label={labelConfig['controls.fitview.title']} aria-label={ariaLabelConfig['controls.fitview.ariaLabel']}
> >
<FitViewIcon /> <FitViewIcon />
</ControlButton> </ControlButton>
@@ -112,8 +112,8 @@ function ControlsComponent({
<ControlButton <ControlButton
className="react-flow__controls-interactive" className="react-flow__controls-interactive"
onClick={onToggleInteractivity} onClick={onToggleInteractivity}
title={labelConfig['controls.interactive.title']} title={ariaLabelConfig['controls.interactive.ariaLabel']}
aria-label={labelConfig['controls.interactive.title']} aria-label={ariaLabelConfig['controls.interactive.ariaLabel']}
> >
{isInteractive ? <UnlockIcon /> : <LockIcon />} {isInteractive ? <UnlockIcon /> : <LockIcon />}
</ControlButton> </ControlButton>
@@ -36,7 +36,7 @@ const selector = (s: ReactFlowState) => {
translateExtent: s.translateExtent, translateExtent: s.translateExtent,
flowWidth: s.width, flowWidth: s.width,
flowHeight: s.height, flowHeight: s.height,
labelConfig: s.labelConfig, ariaLabelConfig: s.ariaLabelConfig,
}; };
}; };
@@ -70,7 +70,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
}: MiniMapProps<NodeType>) { }: MiniMapProps<NodeType>) {
const store = useStoreApi<NodeType>(); const store = useStoreApi<NodeType>();
const svg = useRef<SVGSVGElement>(null); const svg = useRef<SVGSVGElement>(null);
const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight, labelConfig } = useStore( const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight, ariaLabelConfig } = useStore(
selector, selector,
shallow shallow
); );
@@ -133,7 +133,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
}, []) }, [])
: undefined; : undefined;
const _ariaLabel = ariaLabel ?? labelConfig['minimap.ariaLabel']; const _ariaLabel = ariaLabel ?? ariaLabelConfig['minimap.ariaLabel'];
return ( return (
<Panel <Panel
@@ -21,7 +21,7 @@ export const ARIA_EDGE_DESC_KEY = 'react-flow__edge-desc';
export const ARIA_LIVE_MESSAGE = 'react-flow__aria-live'; export const ARIA_LIVE_MESSAGE = 'react-flow__aria-live';
const ariaLiveSelector = (s: ReactFlowState) => s.ariaLiveMessage; const ariaLiveSelector = (s: ReactFlowState) => s.ariaLiveMessage;
const labelConfigSelector = (s: ReactFlowState) => s.labelConfig; const ariaLabelConfigSelector = (s: ReactFlowState) => s.ariaLabelConfig;
function AriaLiveMessage({ rfId }: { rfId: string }) { function AriaLiveMessage({ rfId }: { rfId: string }) {
const ariaLiveMessage = useStore(ariaLiveSelector); const ariaLiveMessage = useStore(ariaLiveSelector);
@@ -34,17 +34,17 @@ function AriaLiveMessage({ rfId }: { rfId: string }) {
} }
export function A11yDescriptions({ rfId, disableKeyboardA11y }: { rfId: string; disableKeyboardA11y: boolean }) { export function A11yDescriptions({ rfId, disableKeyboardA11y }: { rfId: string; disableKeyboardA11y: boolean }) {
const labelConfig = useStore(labelConfigSelector); const ariaLabelConfig = useStore(ariaLabelConfigSelector);
return ( return (
<> <>
<div id={`${ARIA_NODE_DESC_KEY}-${rfId}`} style={style}> <div id={`${ARIA_NODE_DESC_KEY}-${rfId}`} style={style}>
{disableKeyboardA11y {disableKeyboardA11y
? labelConfig['a11yDescription.node.default'] ? ariaLabelConfig['node.a11yDescription.default']
: labelConfig['a11yDescription.node.keyboardDisabled']} : ariaLabelConfig['node.a11yDescription.keyboardDisabled']}
</div> </div>
<div id={`${ARIA_EDGE_DESC_KEY}-${rfId}`} style={style}> <div id={`${ARIA_EDGE_DESC_KEY}-${rfId}`} style={style}>
{labelConfig['a11yDescription.edge.default']} {ariaLabelConfig['edge.a11yDescription.default']}
</div> </div>
{!disableKeyboardA11y && <AriaLiveMessage rfId={rfId} />} {!disableKeyboardA11y && <AriaLiveMessage rfId={rfId} />}
</> </>
@@ -142,10 +142,10 @@ export function NodeWrapper<NodeType extends Node>({
// 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();
const { labelConfig } = store.getState(); const { ariaLabelConfig } = store.getState();
store.setState({ store.setState({
ariaLiveMessage: labelConfig['a11yDescription.ariaLiveMessage']({ ariaLiveMessage: ariaLabelConfig['a11yDescription.ariaLiveMessage']({
direction: event.key.replace('Arrow', '').toLowerCase(), direction: event.key.replace('Arrow', '').toLowerCase(),
x: ~~internals.positionAbsolute.x, x: ~~internals.positionAbsolute.x,
y: ~~internals.positionAbsolute.y, y: ~~internals.positionAbsolute.y,
@@ -5,7 +5,7 @@
*/ */
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { infiniteExtent, type CoordinateExtent, mergeLabelConfig, LabelConfig } from '@xyflow/system'; import { infiniteExtent, type CoordinateExtent, mergeAriaLabelConfig, AriaLabelConfig } from '@xyflow/system';
import { useStore, useStoreApi } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types'; import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types';
@@ -68,7 +68,7 @@ const reactFlowFieldsToTrack = [
'debug', 'debug',
'autoPanSpeed', 'autoPanSpeed',
'paneClickDistance', 'paneClickDistance',
'labelConfig', 'ariaLabelConfig',
] as const; ] as const;
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
@@ -158,8 +158,8 @@ export function StoreUpdater<NodeType extends Node = Node, EdgeType extends Edge
else if (fieldName === 'fitView') store.setState({ fitViewQueued: fieldValue as boolean }); else if (fieldName === 'fitView') store.setState({ fitViewQueued: fieldValue as boolean });
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOptions: fieldValue as FitViewOptions }); else if (fieldName === 'fitViewOptions') store.setState({ fitViewOptions: fieldValue as FitViewOptions });
if (fieldName === 'labelConfig') { if (fieldName === 'ariaLabelConfig') {
store.setState({ labelConfig: mergeLabelConfig(fieldValue as LabelConfig) }); store.setState({ ariaLabelConfig: mergeAriaLabelConfig(fieldValue as AriaLabelConfig) });
} }
// General case // General case
else store.setState({ [fieldName]: fieldValue }); else store.setState({ [fieldName]: fieldValue });
@@ -145,7 +145,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
colorMode = 'light', colorMode = 'light',
debug, debug,
onScroll, onScroll,
labelConfig, ariaLabelConfig,
...rest ...rest
}: ReactFlowProps<NodeType, EdgeType>, }: ReactFlowProps<NodeType, EdgeType>,
ref: ForwardedRef<HTMLDivElement> ref: ForwardedRef<HTMLDivElement>
@@ -306,7 +306,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
onBeforeDelete={onBeforeDelete} onBeforeDelete={onBeforeDelete}
paneClickDistance={paneClickDistance} paneClickDistance={paneClickDistance}
debug={debug} debug={debug}
labelConfig={labelConfig} ariaLabelConfig={ariaLabelConfig}
/> />
<SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} /> <SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
{children} {children}
+1 -1
View File
@@ -108,7 +108,7 @@ export {
type NoConnection, type NoConnection,
type NodeConnection, type NodeConnection,
type OnReconnect, type OnReconnect,
type LabelConfig, type AriaLabelConfig,
} from '@xyflow/system'; } from '@xyflow/system';
// we need this workaround to prevent a duplicate identifier error // we need this workaround to prevent a duplicate identifier error
+2 -2
View File
@@ -10,7 +10,7 @@ import {
NodeOrigin, NodeOrigin,
initialConnection, initialConnection,
CoordinateExtent, CoordinateExtent,
defaultLabelConfig, defaultAriaLabelConfig,
} from '@xyflow/system'; } from '@xyflow/system';
import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types'; import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
@@ -142,7 +142,7 @@ const getInitialState = ({
lib: 'react', lib: 'react',
debug: false, debug: false,
labelConfig: defaultLabelConfig, ariaLabelConfig: defaultAriaLabelConfig,
}; };
}; };
+2 -2
View File
@@ -21,7 +21,7 @@ import type {
ColorMode, ColorMode,
SnapGrid, SnapGrid,
OnReconnect, OnReconnect,
LabelConfig, AriaLabelConfig,
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -671,5 +671,5 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
* Configuration for customizable labels, descriptions, and UI text. Provided keys will override the corresponding defaults. * Configuration for customizable labels, descriptions, and UI text. Provided keys will override the corresponding defaults.
* Allows localization, customization of ARIA descriptions, control labels, minimap labels, and other UI strings. * Allows localization, customization of ARIA descriptions, control labels, minimap labels, and other UI strings.
*/ */
labelConfig?: Partial<LabelConfig>; ariaLabelConfig?: Partial<AriaLabelConfig>;
} }
+2 -2
View File
@@ -28,7 +28,7 @@ import {
type NodeChange, type NodeChange,
type EdgeChange, type EdgeChange,
type ParentLookup, type ParentLookup,
type LabelConfig, type AriaLabelConfig,
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -148,7 +148,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
lib: string; lib: string;
debug: boolean; debug: boolean;
labelConfig: LabelConfig; ariaLabelConfig: AriaLabelConfig;
}; };
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = { export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
@@ -8,11 +8,11 @@
<div id={`${ARIA_NODE_DESC_KEY}-${store.flowId}`} class="a11y-hidden"> <div id={`${ARIA_NODE_DESC_KEY}-${store.flowId}`} class="a11y-hidden">
{store.disableKeyboardA11y {store.disableKeyboardA11y
? store.labelConfig['a11yDescription.node.default'] ? store.ariaLabelConfig['node.a11yDescription.default']
: store.labelConfig['a11yDescription.node.keyboardDisabled']} : store.ariaLabelConfig['node.a11yDescription.keyboardDisabled']}
</div> </div>
<div id={`${ARIA_EDGE_DESC_KEY}-${store.flowId}`} class="a11y-hidden"> <div id={`${ARIA_EDGE_DESC_KEY}-${store.flowId}`} class="a11y-hidden">
{store.labelConfig['a11yDescription.edge.default']} {store.ariaLabelConfig['edge.a11yDescription.default']}
</div> </div>
{#if !store.disableKeyboardA11y} {#if !store.disableKeyboardA11y}
@@ -44,7 +44,7 @@
); );
let store = useStore(); let store = useStore();
let labelConfig = $derived(store.labelConfig); let ariaLabelConfig = $derived(store.ariaLabelConfig);
let prevConnections: Map<string, HandleConnection> | null = null; let prevConnections: Map<string, HandleConnection> | null = null;
@@ -229,7 +229,7 @@ The Handle component is the part of a node that can be used to connect nodes.
onkeypress={() => {}} onkeypress={() => {}}
{style} {style}
role="button" role="button"
aria-label={labelConfig[`handle.ariaLabel`]} aria-label={ariaLabelConfig[`handle.ariaLabel`]}
tabindex="-1" tabindex="-1"
{...rest} {...rest}
> >
@@ -85,7 +85,7 @@
let prevTargetPosition: Position | undefined = targetPosition; let prevTargetPosition: Position | undefined = targetPosition;
let NodeComponent = $derived(store.nodeTypes[type] ?? DefaultNode); let NodeComponent = $derived(store.nodeTypes[type] ?? DefaultNode);
let labelConfig = $derived(store.labelConfig); let ariaLabelConfig = $derived(store.ariaLabelConfig);
let connectableContext: ConnectableContext = { let connectableContext: ConnectableContext = {
get value() { get value() {
@@ -189,7 +189,7 @@
) { ) {
// 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();
store.ariaLiveMessage = labelConfig['a11yDescription.ariaLiveMessage']({ store.ariaLiveMessage = ariaLabelConfig['a11yDescription.ariaLiveMessage']({
direction: event.key.replace('Arrow', '').toLowerCase(), direction: event.key.replace('Arrow', '').toLowerCase(),
x: ~~node.internals.positionAbsolute.x, x: ~~node.internals.positionAbsolute.x,
y: ~~node.internals.positionAbsolute.y y: ~~node.internals.positionAbsolute.y
@@ -92,7 +92,7 @@
noDragClass, noDragClass,
noPanClass, noPanClass,
noWheelClass, noWheelClass,
labelConfig, ariaLabelConfig,
...divAttributes ...divAttributes
} = $derived(rest); } = $derived(rest);
/* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
@@ -21,7 +21,7 @@ import type {
OnReconnect, OnReconnect,
OnReconnectStart, OnReconnectStart,
OnReconnectEnd, OnReconnectEnd,
LabelConfig AriaLabelConfig
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -476,5 +476,5 @@ export type SvelteFlowProps<
* Configuration for customizable labels, descriptions, and UI text. Provided keys will override the corresponding defaults. * Configuration for customizable labels, descriptions, and UI text. Provided keys will override the corresponding defaults.
* Allows localization, customization of ARIA descriptions, control labels, minimap labels, and other UI strings. * Allows localization, customization of ARIA descriptions, control labels, minimap labels, and other UI strings.
*/ */
labelConfig?: Partial<LabelConfig>; ariaLabelConfig?: Partial<AriaLabelConfig>;
}; };
+1 -1
View File
@@ -113,7 +113,7 @@ export {
type ResizeDragEvent, type ResizeDragEvent,
type IsValidConnection, type IsValidConnection,
type NodeConnection, type NodeConnection,
type LabelConfig type AriaLabelConfig
} from '@xyflow/system'; } from '@xyflow/system';
// system utils // system utils
@@ -46,7 +46,7 @@
); );
let minZoomReached = $derived(store.viewport.zoom <= store.minZoom); let minZoomReached = $derived(store.viewport.zoom <= store.minZoom);
let maxZoomReached = $derived(store.viewport.zoom >= store.maxZoom); let maxZoomReached = $derived(store.viewport.zoom >= store.maxZoom);
let labelConfig = $derived(store.labelConfig); let ariaLabelConfig = $derived(store.ariaLabelConfig);
let orientationClass = $derived(orientation === 'horizontal' ? 'horizontal' : 'vertical'); let orientationClass = $derived(orientation === 'horizontal' ? 'horizontal' : 'vertical');
const onZoomInHandler = () => { const onZoomInHandler = () => {
@@ -73,7 +73,7 @@
class={['svelte-flow__controls', orientationClass, className]} class={['svelte-flow__controls', orientationClass, className]}
{position} {position}
data-testid="svelte-flow__controls" data-testid="svelte-flow__controls"
aria-label={labelConfig['controls.ariaLabel']} aria-label={ariaLabelConfig['controls.ariaLabel']}
{style} {style}
{...rest} {...rest}
> >
@@ -84,8 +84,8 @@
<ControlButton <ControlButton
onclick={onZoomInHandler} onclick={onZoomInHandler}
class="svelte-flow__controls-zoomin" class="svelte-flow__controls-zoomin"
title={labelConfig['controls.zoomin.title']} title={ariaLabelConfig['controls.zoomin.ariaLabel']}
aria-label={labelConfig['controls.zoomin.title']} aria-label={ariaLabelConfig['controls.zoomin.ariaLabel']}
disabled={maxZoomReached} disabled={maxZoomReached}
{...buttonProps} {...buttonProps}
> >
@@ -94,8 +94,8 @@
<ControlButton <ControlButton
onclick={onZoomOutHandler} onclick={onZoomOutHandler}
class="svelte-flow__controls-zoomout" class="svelte-flow__controls-zoomout"
title={labelConfig['controls.zoomout.title']} title={ariaLabelConfig['controls.zoomout.ariaLabel']}
aria-label={labelConfig['controls.zoomout.title']} aria-label={ariaLabelConfig['controls.zoomout.ariaLabel']}
disabled={minZoomReached} disabled={minZoomReached}
{...buttonProps} {...buttonProps}
> >
@@ -106,8 +106,8 @@
<ControlButton <ControlButton
class="svelte-flow__controls-fitview" class="svelte-flow__controls-fitview"
onclick={onFitViewHandler} onclick={onFitViewHandler}
title={labelConfig['controls.fitview.title']} title={ariaLabelConfig['controls.fitview.ariaLabel']}
aria-label={labelConfig['controls.fitview.title']} aria-label={ariaLabelConfig['controls.fitview.ariaLabel']}
{...buttonProps} {...buttonProps}
> >
<FitViewIcon /> <FitViewIcon />
@@ -117,8 +117,8 @@
<ControlButton <ControlButton
class="svelte-flow__controls-interactive" class="svelte-flow__controls-interactive"
onclick={onToggleInteractivity} onclick={onToggleInteractivity}
title={labelConfig['controls.interactive.title']} title={ariaLabelConfig['controls.interactive.ariaLabel']}
aria-label={labelConfig['controls.interactive.title']} aria-label={ariaLabelConfig['controls.interactive.ariaLabel']}
{...buttonProps} {...buttonProps}
> >
{#if isInteractive}<UnlockIcon />{:else}<LockIcon />{/if} {#if isInteractive}<UnlockIcon />{:else}<LockIcon />{/if}
@@ -42,7 +42,7 @@
}: MiniMapProps = $props(); }: MiniMapProps = $props();
let store = $derived(useStore()); let store = $derived(useStore());
let labelConfig = $derived(store.labelConfig); let ariaLabelConfig = $derived(store.ariaLabelConfig);
const nodeColorFunc = nodeColor === undefined ? undefined : getAttrFunction(nodeColor); const nodeColorFunc = nodeColor === undefined ? undefined : getAttrFunction(nodeColor);
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
@@ -114,8 +114,8 @@
zoomable zoomable
}} }}
> >
{#if ariaLabel ?? labelConfig['minimap.ariaLabel']} {#if ariaLabel ?? ariaLabelConfig['minimap.ariaLabel']}
<title id={labelledBy}>{ariaLabel ?? labelConfig['minimap.ariaLabel']}</title> <title id={labelledBy}>{ariaLabel ?? ariaLabelConfig['minimap.ariaLabel']}</title>
{/if} {/if}
{#each store.nodes as userNode (userNode.id)} {#each store.nodes as userNode (userNode.id)}
@@ -7,7 +7,7 @@ import {
getViewportForBounds, getViewportForBounds,
updateConnectionLookup, updateConnectionLookup,
initialConnection, initialConnection,
mergeLabelConfig, mergeAriaLabelConfig,
type SelectionRect, type SelectionRect,
type SnapGrid, type SnapGrid,
type MarkerProps, type MarkerProps,
@@ -34,7 +34,7 @@ import {
type OnReconnect, type OnReconnect,
type OnReconnectStart, type OnReconnectStart,
type OnReconnectEnd, type OnReconnectEnd,
type LabelConfig type AriaLabelConfig
} from '@xyflow/system'; } from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -291,7 +291,9 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
noPanClass: string = $derived(signals.props.noPanClass ?? 'nopan'); noPanClass: string = $derived(signals.props.noPanClass ?? 'nopan');
noDragClass: string = $derived(signals.props.noDragClass ?? 'nodrag'); noDragClass: string = $derived(signals.props.noDragClass ?? 'nodrag');
noWheelClass: string = $derived(signals.props.noWheelClass ?? 'nowheel'); noWheelClass: string = $derived(signals.props.noWheelClass ?? 'nowheel');
labelConfig: LabelConfig = $derived(mergeLabelConfig(signals.props.labelConfig)); ariaLabelConfig: AriaLabelConfig = $derived(
mergeAriaLabelConfig(signals.props.ariaLabelConfig)
);
// _viewport is the internal viewport. // _viewport is the internal viewport.
// when binding to viewport, we operate on signals.viewport instead // when binding to viewport, we operate on signals.viewport instead
+9 -9
View File
@@ -37,22 +37,22 @@ export const infiniteExtent: CoordinateExtent = [
export const elementSelectionKeys = ['Enter', ' ', 'Escape']; export const elementSelectionKeys = ['Enter', ' ', 'Escape'];
export const defaultLabelConfig = { export const defaultAriaLabelConfig = {
'a11yDescription.node.default': 'node.a11yDescription.default':
'Press enter or space to select a node. Press delete to remove it and escape to cancel.', 'Press enter or space to select a node. Press delete to remove it and escape to cancel.',
'a11yDescription.node.keyboardDisabled': 'node.a11yDescription.keyboardDisabled':
'Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.', 'Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.',
'a11yDescription.edge.default': 'edge.a11yDescription.default':
'Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.', 'Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.',
'a11yDescription.ariaLiveMessage': ({ direction, x, y }: { direction: string; x: number; y: number }) => 'a11yDescription.ariaLiveMessage': ({ direction, x, y }: { direction: string; x: number; y: number }) =>
`Moved selected node ${direction}. New position, x: ${x}, y: ${y}`, `Moved selected node ${direction}. New position, x: ${x}, y: ${y}`,
// Control elements // Control elements
'controls.ariaLabel': 'Control Panel', 'controls.ariaLabel': 'Control Panel',
'controls.zoomin.title': 'Zoom In', 'controls.zoomin.ariaLabel': 'Zoom In',
'controls.zoomout.title': 'Zoom Out', 'controls.zoomout.ariaLabel': 'Zoom Out',
'controls.fitview.title': 'Fit View', 'controls.fitview.ariaLabel': 'Fit View',
'controls.interactive.title': 'Toggle Interactivity', 'controls.interactive.ariaLabel': 'Toggle Interactivity',
// Mini map // Mini map
'minimap.ariaLabel': 'Mini Map', 'minimap.ariaLabel': 'Mini Map',
@@ -61,4 +61,4 @@ export const defaultLabelConfig = {
'handle.ariaLabel': 'Handle', 'handle.ariaLabel': 'Handle',
}; };
export type LabelConfig = typeof defaultLabelConfig; export type AriaLabelConfig = typeof defaultAriaLabelConfig;
+3 -3
View File
@@ -16,7 +16,7 @@ import type {
import { type Viewport } from '../types'; import { type Viewport } from '../types';
import { getNodePositionWithOrigin, isInternalNodeBase } from './graph'; import { getNodePositionWithOrigin, isInternalNodeBase } from './graph';
import { defaultLabelConfig, type LabelConfig } from '../constants'; import { defaultAriaLabelConfig, type AriaLabelConfig } from '../constants';
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max); export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max);
@@ -421,6 +421,6 @@ export function withResolvers<T>(): {
return { promise, resolve, reject }; return { promise, resolve, reject };
} }
export function mergeLabelConfig(partial?: Partial<LabelConfig>): LabelConfig { export function mergeAriaLabelConfig(partial?: Partial<AriaLabelConfig>): AriaLabelConfig {
return { ...defaultLabelConfig, ...(partial || {}) }; return { ...defaultAriaLabelConfig, ...(partial || {}) };
} }