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 Basic from '../examples/Basic';
|
||||||
import Backgrounds from '../examples/Backgrounds';
|
import Backgrounds from '../examples/Backgrounds';
|
||||||
import BrokenNodes from '../examples/BrokenNodes';
|
import BrokenNodes from '../examples/BrokenNodes';
|
||||||
@@ -68,6 +69,11 @@ const routes: IRoute[] = [
|
|||||||
path: 'add-node-edge-drop',
|
path: 'add-node-edge-drop',
|
||||||
component: AddNodeOnEdgeDrop,
|
component: AddNodeOnEdgeDrop,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'A11y',
|
||||||
|
path: 'a11y',
|
||||||
|
component: A11y,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Basic',
|
name: 'Basic',
|
||||||
path: '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}
|
onBeforeDelete={onBeforeDelete}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onPaneMouseMove={onPaneMouseMove}
|
onPaneMouseMove={onPaneMouseMove}
|
||||||
descriptions={{
|
|
||||||
'a11yDescription.node.default': 'Custom Node Desc.',
|
|
||||||
'a11yDescription.node.keyboardDisabled': 'Custom Keyboard Desc.',
|
|
||||||
'a11yDescription.edge.default': 'Custom Edge Desc.',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<MiniMap nodeBorderRadius={2} />
|
<MiniMap nodeBorderRadius={2} />
|
||||||
<Controls orientation="horizontal" />
|
<Controls orientation="horizontal" />
|
||||||
|
|||||||
@@ -220,7 +220,7 @@
|
|||||||
console.log('on selection changed via prop', { nodes, edges });
|
console.log('on selection changed via prop', { nodes, edges });
|
||||||
}}
|
}}
|
||||||
selectNodesOnDrag
|
selectNodesOnDrag
|
||||||
descriptions={{
|
labelConfig={{
|
||||||
'a11yDescription.node.default': 'Svelte Custom Node Description.',
|
'a11yDescription.node.default': 'Svelte Custom Node Description.',
|
||||||
'a11yDescription.node.keyboardDisabled': 'Svelte Custom Keyboard Description',
|
'a11yDescription.node.keyboardDisabled': 'Svelte Custom Keyboard Description',
|
||||||
'a11yDescription.edge.default': 'Svelte Custom Edge Desc.',
|
'a11yDescription.edge.default': 'Svelte Custom Edge Desc.',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { CSSProperties } from 'react';
|
|||||||
|
|
||||||
import { useStore } from '../../hooks/useStore';
|
import { useStore } from '../../hooks/useStore';
|
||||||
import type { ReactFlowState } from '../../types';
|
import type { ReactFlowState } from '../../types';
|
||||||
import type { descriptions } from '@xyflow/system';
|
|
||||||
|
|
||||||
const style: CSSProperties = { display: 'none' };
|
const style: CSSProperties = { display: 'none' };
|
||||||
const ariaLiveStyle: CSSProperties = {
|
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_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 defaultDescriptions: Required<descriptions> = {
|
const ariaLiveSelector = (s: ReactFlowState) => s.ariaLiveMessage;
|
||||||
'a11yDescription.node.default':
|
const labelConfigSelector = (s: ReactFlowState) => s.labelConfig;
|
||||||
'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;
|
|
||||||
|
|
||||||
function AriaLiveMessage({ rfId }: { rfId: string }) {
|
function AriaLiveMessage({ rfId }: { rfId: string }) {
|
||||||
const ariaLiveMessage = useStore(selector);
|
const ariaLiveMessage = useStore(ariaLiveSelector);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={`${ARIA_LIVE_MESSAGE}-${rfId}`} aria-live="assertive" aria-atomic="true" style={ariaLiveStyle}>
|
<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({
|
export function A11yDescriptions({ rfId, disableKeyboardA11y }: { rfId: string; disableKeyboardA11y: boolean }) {
|
||||||
rfId,
|
const labelConfig = useStore(labelConfigSelector);
|
||||||
disableKeyboardA11y,
|
|
||||||
descriptions = {},
|
|
||||||
}: {
|
|
||||||
rfId: string;
|
|
||||||
disableKeyboardA11y: boolean;
|
|
||||||
descriptions?: descriptions;
|
|
||||||
}) {
|
|
||||||
const nodeDesc = disableKeyboardA11y
|
const nodeDesc = disableKeyboardA11y
|
||||||
? descriptions['a11yDescription.node.default'] || defaultDescriptions['a11yDescription.node.default']
|
? labelConfig['a11yDescription.node.default']
|
||||||
: descriptions['a11yDescription.node.keyboardDisabled'] ||
|
: labelConfig['a11yDescription.node.keyboardDisabled'];
|
||||||
defaultDescriptions['a11yDescription.node.keyboardDisabled'];
|
const edgeDesc = labelConfig['a11yDescription.edge.default'];
|
||||||
const edgeDesc = descriptions['a11yDescription.edge.default'] || defaultDescriptions['a11yDescription.edge.default'];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
|||||||
colorMode = 'light',
|
colorMode = 'light',
|
||||||
debug,
|
debug,
|
||||||
onScroll,
|
onScroll,
|
||||||
descriptions,
|
labelConfig,
|
||||||
...rest
|
...rest
|
||||||
}: ReactFlowProps<NodeType, EdgeType>,
|
}: ReactFlowProps<NodeType, EdgeType>,
|
||||||
ref: ForwardedRef<HTMLDivElement>
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
@@ -310,7 +310,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
|||||||
<SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
|
<SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
|
||||||
{children}
|
{children}
|
||||||
<Attribution proOptions={proOptions} position={attributionPosition} />
|
<Attribution proOptions={proOptions} position={attributionPosition} />
|
||||||
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} descriptions={descriptions} />
|
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
NodeOrigin,
|
NodeOrigin,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
fitViewport,
|
fitViewport,
|
||||||
|
LabelConfig,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
@@ -370,6 +371,11 @@ const createStore = ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
reset: () => set({ ...getInitialState() }),
|
reset: () => set({ ...getInitialState() }),
|
||||||
|
updateLabelConfig: (newLabelConfig: Partial<LabelConfig>) => {
|
||||||
|
set((state) => ({
|
||||||
|
labelConfig: { ...state.labelConfig, ...newLabelConfig },
|
||||||
|
}));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, Object.is);
|
}, Object.is);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
NodeOrigin,
|
NodeOrigin,
|
||||||
initialConnection,
|
initialConnection,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
|
defaultLabelConfig,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
|
import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
|
||||||
@@ -141,6 +142,7 @@ const getInitialState = ({
|
|||||||
|
|
||||||
lib: 'react',
|
lib: 'react',
|
||||||
debug: false,
|
debug: false,
|
||||||
|
labelConfig: defaultLabelConfig,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import type {
|
|||||||
ColorMode,
|
ColorMode,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
OnReconnect,
|
OnReconnect,
|
||||||
descriptions,
|
LabelConfig,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
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.
|
* Custom accessibility messages for screen readers and a11y features.
|
||||||
* Allows localization and customization of ARIA descriptions.
|
* Allows localization and customization of ARIA descriptions.
|
||||||
*/
|
*/
|
||||||
descriptions?: descriptions;
|
labelConfig?: LabelConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
type NodeChange,
|
type NodeChange,
|
||||||
type EdgeChange,
|
type EdgeChange,
|
||||||
type ParentLookup,
|
type ParentLookup,
|
||||||
|
type LabelConfig,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -67,7 +68,6 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
domNode: HTMLDivElement | null;
|
domNode: HTMLDivElement | null;
|
||||||
paneDragging: boolean;
|
paneDragging: boolean;
|
||||||
noPanClassName: string;
|
noPanClassName: string;
|
||||||
|
|
||||||
panZoom: PanZoomInstance | null;
|
panZoom: PanZoomInstance | null;
|
||||||
minZoom: number;
|
minZoom: number;
|
||||||
maxZoom: number;
|
maxZoom: number;
|
||||||
@@ -148,6 +148,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
|
|
||||||
lib: string;
|
lib: string;
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
|
labelConfig: Required<LabelConfig>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
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;
|
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
||||||
panBy: PanBy;
|
panBy: PanBy;
|
||||||
setPaneClickDistance: (distance: number) => void;
|
setPaneClickDistance: (distance: number) => void;
|
||||||
|
updateLabelConfig: (newLabelConfig: Partial<LabelConfig>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
|
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 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[];
|
edges: EdgeType[];
|
||||||
}) => Promise<boolean | { nodes: NodeType[]; edges: EdgeType[] }>;
|
}) => Promise<boolean | { nodes: NodeType[]; edges: EdgeType[] }>;
|
||||||
|
|
||||||
export type descriptions = {
|
export type LabelConfig = {
|
||||||
'a11yDescription.node.default'?: string;
|
'a11yDescription.node.default'?: string;
|
||||||
'a11yDescription.node.keyboardDisabled'?: string;
|
'a11yDescription.node.keyboardDisabled'?: string;
|
||||||
'a11yDescription.edge.default'?: string;
|
'a11yDescription.edge.default'?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user