chore: update LabelConfig name, add to store, create a11y example
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import A11y from '../examples/A11y';
|
||||
import Basic from '../examples/Basic';
|
||||
import Backgrounds from '../examples/Backgrounds';
|
||||
import BrokenNodes from '../examples/BrokenNodes';
|
||||
@@ -68,6 +69,11 @@ const routes: IRoute[] = [
|
||||
path: 'add-node-edge-drop',
|
||||
component: AddNodeOnEdgeDrop,
|
||||
},
|
||||
{
|
||||
name: 'A11y',
|
||||
path: 'a11y',
|
||||
component: A11y,
|
||||
},
|
||||
{
|
||||
name: 'Basic',
|
||||
path: 'basic',
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { MouseEvent, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
MiniMap,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Controls,
|
||||
ReactFlowProvider,
|
||||
Node,
|
||||
Edge,
|
||||
OnNodeDrag,
|
||||
FitViewOptions,
|
||||
useStore,
|
||||
} from '@xyflow/react';
|
||||
|
||||
const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
|
||||
const onNodeDragStart = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'A11y Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const A11y = () => {
|
||||
const labelConfig = useStore((state) => state.labelConfig);
|
||||
const updateLabelConfig = useStore((state) => state.updateLabelConfig);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Label Config:', labelConfig);
|
||||
}, [labelConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Updating labelConfig...');
|
||||
updateLabelConfig({
|
||||
'a11yDescription.node.default': 'Updated Node Desc.',
|
||||
'a11yDescription.node.keyboardDisabled': 'Updated Keyboard Desc.',
|
||||
'a11yDescription.edge.default': 'Updated Edge Desc.',
|
||||
});
|
||||
}, [updateLabelConfig]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
defaultNodes={initialNodes}
|
||||
defaultEdges={initialEdges}
|
||||
onNodesChange={console.log}
|
||||
onNodeClick={onNodeClick}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
className="react-flow-basic-example"
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
selectNodesOnDrag={false}
|
||||
elevateEdgesOnSelect
|
||||
elevateNodesOnSelect={false}
|
||||
nodeDragThreshold={0}
|
||||
labelConfig={{
|
||||
'a11yDescription.node.default': 'Custom Node Desc.',
|
||||
'a11yDescription.node.keyboardDisabled': 'Custom Keyboard Desc.',
|
||||
'a11yDescription.edge.default': 'Custom Edge Desc.',
|
||||
}}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<A11y />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
@@ -235,11 +235,6 @@ const OverviewFlow = () => {
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
onDelete={onDelete}
|
||||
onPaneMouseMove={onPaneMouseMove}
|
||||
descriptions={{
|
||||
'a11yDescription.node.default': 'Custom Node Desc.',
|
||||
'a11yDescription.node.keyboardDisabled': 'Custom Keyboard Desc.',
|
||||
'a11yDescription.edge.default': 'Custom Edge Desc.',
|
||||
}}
|
||||
>
|
||||
<MiniMap nodeBorderRadius={2} />
|
||||
<Controls orientation="horizontal" />
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
console.log('on selection changed via prop', { nodes, edges });
|
||||
}}
|
||||
selectNodesOnDrag
|
||||
descriptions={{
|
||||
labelConfig={{
|
||||
'a11yDescription.node.default': 'Svelte Custom Node Description.',
|
||||
'a11yDescription.node.keyboardDisabled': 'Svelte Custom Keyboard Description',
|
||||
'a11yDescription.edge.default': 'Svelte Custom Edge Desc.',
|
||||
|
||||
@@ -2,7 +2,6 @@ import { CSSProperties } from 'react';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
import type { descriptions } from '@xyflow/system';
|
||||
|
||||
const style: CSSProperties = { display: 'none' };
|
||||
const ariaLiveStyle: CSSProperties = {
|
||||
@@ -21,19 +20,11 @@ export const ARIA_NODE_DESC_KEY = 'react-flow__node-desc';
|
||||
export const ARIA_EDGE_DESC_KEY = 'react-flow__edge-desc';
|
||||
export const ARIA_LIVE_MESSAGE = 'react-flow__aria-live';
|
||||
|
||||
const defaultDescriptions: Required<descriptions> = {
|
||||
'a11yDescription.node.default':
|
||||
'Press enter or space to select a node. Press delete to remove it and escape to cancel.',
|
||||
'a11yDescription.node.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.',
|
||||
'a11yDescription.edge.default':
|
||||
'Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.',
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => s.ariaLiveMessage;
|
||||
const ariaLiveSelector = (s: ReactFlowState) => s.ariaLiveMessage;
|
||||
const labelConfigSelector = (s: ReactFlowState) => s.labelConfig;
|
||||
|
||||
function AriaLiveMessage({ rfId }: { rfId: string }) {
|
||||
const ariaLiveMessage = useStore(selector);
|
||||
const ariaLiveMessage = useStore(ariaLiveSelector);
|
||||
|
||||
return (
|
||||
<div id={`${ARIA_LIVE_MESSAGE}-${rfId}`} aria-live="assertive" aria-atomic="true" style={ariaLiveStyle}>
|
||||
@@ -42,20 +33,13 @@ function AriaLiveMessage({ rfId }: { rfId: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function A11yDescriptions({
|
||||
rfId,
|
||||
disableKeyboardA11y,
|
||||
descriptions = {},
|
||||
}: {
|
||||
rfId: string;
|
||||
disableKeyboardA11y: boolean;
|
||||
descriptions?: descriptions;
|
||||
}) {
|
||||
export function A11yDescriptions({ rfId, disableKeyboardA11y }: { rfId: string; disableKeyboardA11y: boolean }) {
|
||||
const labelConfig = useStore(labelConfigSelector);
|
||||
|
||||
const nodeDesc = disableKeyboardA11y
|
||||
? descriptions['a11yDescription.node.default'] || defaultDescriptions['a11yDescription.node.default']
|
||||
: descriptions['a11yDescription.node.keyboardDisabled'] ||
|
||||
defaultDescriptions['a11yDescription.node.keyboardDisabled'];
|
||||
const edgeDesc = descriptions['a11yDescription.edge.default'] || defaultDescriptions['a11yDescription.edge.default'];
|
||||
? labelConfig['a11yDescription.node.default']
|
||||
: labelConfig['a11yDescription.node.keyboardDisabled'];
|
||||
const edgeDesc = labelConfig['a11yDescription.edge.default'];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -145,7 +145,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
colorMode = 'light',
|
||||
debug,
|
||||
onScroll,
|
||||
descriptions,
|
||||
labelConfig,
|
||||
...rest
|
||||
}: ReactFlowProps<NodeType, EdgeType>,
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
@@ -310,7 +310,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
<SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
<Attribution proOptions={proOptions} position={attributionPosition} />
|
||||
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} descriptions={descriptions} />
|
||||
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} />
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
NodeOrigin,
|
||||
CoordinateExtent,
|
||||
fitViewport,
|
||||
LabelConfig,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||
@@ -370,6 +371,11 @@ const createStore = ({
|
||||
},
|
||||
|
||||
reset: () => set({ ...getInitialState() }),
|
||||
updateLabelConfig: (newLabelConfig: Partial<LabelConfig>) => {
|
||||
set((state) => ({
|
||||
labelConfig: { ...state.labelConfig, ...newLabelConfig },
|
||||
}));
|
||||
},
|
||||
};
|
||||
}, Object.is);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
NodeOrigin,
|
||||
initialConnection,
|
||||
CoordinateExtent,
|
||||
defaultLabelConfig,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
|
||||
@@ -141,6 +142,7 @@ const getInitialState = ({
|
||||
|
||||
lib: 'react',
|
||||
debug: false,
|
||||
labelConfig: defaultLabelConfig,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
ColorMode,
|
||||
SnapGrid,
|
||||
OnReconnect,
|
||||
descriptions,
|
||||
LabelConfig,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -671,5 +671,5 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* Custom accessibility messages for screen readers and a11y features.
|
||||
* Allows localization and customization of ARIA descriptions.
|
||||
*/
|
||||
descriptions?: descriptions;
|
||||
labelConfig?: LabelConfig;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type NodeChange,
|
||||
type EdgeChange,
|
||||
type ParentLookup,
|
||||
type LabelConfig,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -67,7 +68,6 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
domNode: HTMLDivElement | null;
|
||||
paneDragging: boolean;
|
||||
noPanClassName: string;
|
||||
|
||||
panZoom: PanZoomInstance | null;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
@@ -148,6 +148,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
|
||||
lib: string;
|
||||
debug: boolean;
|
||||
labelConfig: Required<LabelConfig>;
|
||||
};
|
||||
|
||||
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||
@@ -171,6 +172,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
||||
panBy: PanBy;
|
||||
setPaneClickDistance: (distance: number) => void;
|
||||
updateLabelConfig: (newLabelConfig: Partial<LabelConfig>) => void;
|
||||
};
|
||||
|
||||
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
|
||||
|
||||
@@ -36,3 +36,12 @@ export const infiniteExtent: CoordinateExtent = [
|
||||
];
|
||||
|
||||
export const elementSelectionKeys = ['Enter', ' ', 'Escape'];
|
||||
|
||||
export const defaultLabelConfig = {
|
||||
'a11yDescription.node.default':
|
||||
'Press enter or space to select a node. Press delete to remove it and escape to cancel.',
|
||||
'a11yDescription.node.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.',
|
||||
'a11yDescription.edge.default':
|
||||
'Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.',
|
||||
};
|
||||
|
||||
@@ -314,7 +314,7 @@ export type OnBeforeDeleteBase<NodeType extends NodeBase = NodeBase, EdgeType ex
|
||||
edges: EdgeType[];
|
||||
}) => Promise<boolean | { nodes: NodeType[]; edges: EdgeType[] }>;
|
||||
|
||||
export type descriptions = {
|
||||
export type LabelConfig = {
|
||||
'a11yDescription.node.default'?: string;
|
||||
'a11yDescription.node.keyboardDisabled'?: string;
|
||||
'a11yDescription.edge.default'?: string;
|
||||
|
||||
Reference in New Issue
Block a user