chore(react/hooks): add docs

This commit is contained in:
moklick
2023-12-19 12:36:49 +01:00
parent 97ab615e6a
commit 2681ead50c
26 changed files with 175 additions and 65 deletions
@@ -9,6 +9,12 @@ function getMediaQuery() {
return window.matchMedia('(prefers-color-scheme: dark)'); return window.matchMedia('(prefers-color-scheme: dark)');
} }
/**
* Hook for receiving the current color mode class 'dark' or 'light'.
*
* @internal
* @param colorMode - The color mode to use ('dark', 'light' or 'system')
*/
export default function useColorModeClass(colorMode: ColorMode): ColorModeClass { export default function useColorModeClass(colorMode: ColorMode): ColorModeClass {
const [colorModeClass, setColorModeClass] = useState<ColorModeClass | null>( const [colorModeClass, setColorModeClass] = useState<ColorModeClass | null>(
colorMode === 'system' ? null : colorMode colorMode === 'system' ? null : colorMode
+5
View File
@@ -13,6 +13,11 @@ type UseDragParams = {
isSelectable?: boolean; isSelectable?: boolean;
}; };
/**
* Hook for calling XYDrag helper from @xyflow/system.
*
* @internal
*/
function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, nodeId, isSelectable }: UseDragParams) { function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, nodeId, isSelectable }: UseDragParams) {
const store = useStoreApi(); const store = useStoreApi();
const [dragging, setDragging] = useState<boolean>(false); const [dragging, setDragging] = useState<boolean>(false);
+6
View File
@@ -5,6 +5,12 @@ import type { Edge, ReactFlowState } from '../types';
const edgesSelector = (state: ReactFlowState) => state.edges; const edgesSelector = (state: ReactFlowState) => state.edges;
/**
* Hook for getting the current edges from the store.
*
* @public
* @returns An array of edges
*/
function useEdges<EdgeData>(): Edge<EdgeData>[] { function useEdges<EdgeData>(): Edge<EdgeData>[] {
const edges = useStore(edgesSelector, shallow); const edges = useStore(edgesSelector, shallow);
@@ -10,6 +10,11 @@ const selected = (item: Node | Edge) => item.selected;
const deleteKeyOptions: UseKeyPressOptions = { actInsideInputWithModifier: false }; const deleteKeyOptions: UseKeyPressOptions = { actInsideInputWithModifier: false };
/**
* Hook for handling global key events.
*
* @internal
*/
export default ({ export default ({
deleteKeyCode, deleteKeyCode,
multiSelectionKeyCode, multiSelectionKeyCode,
+12 -4
View File
@@ -12,11 +12,19 @@ export type UseKeyPressOptions = {
const defaultDoc = typeof document !== 'undefined' ? document : null; const defaultDoc = typeof document !== 'undefined' ? document : null;
// the keycode can be a string 'a' or an array of strings ['a', 'a+d'] /**
// a string means a single key 'a' or a combination when '+' is used 'a+d' * Hook for handling key events.
// an array means different possibilites. Explainer: ['a', 'd+s'] here the *
// user can use the single key 'a' or the combination 'd' + 's' * @public
* @param param.keyCode - The key code (string or array of strings) to use
* @param param.options - Options
* @returns boolean
*/
export default ( export default (
// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
// a string means a single key 'a' or a combination when '+' is used 'a+d'
// an array means different possibilites. Explainer: ['a', 'd+s'] here the
// user can use the single key 'a' or the combination 'd' + 's'
keyCode: KeyCode | null = null, keyCode: KeyCode | null = null,
options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true } options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true }
): boolean => { ): boolean => {
+6
View File
@@ -5,6 +5,12 @@ import type { Node, ReactFlowState } from '../types';
const nodesSelector = (state: ReactFlowState) => state.nodes; const nodesSelector = (state: ReactFlowState) => state.nodes;
/**
* Hook for getting the current nodes from the store.
*
* @public
* @returns An array of nodes
*/
function useNodes<NodeType extends Node = Node>(): NodeType[] { function useNodes<NodeType extends Node = Node>(): NodeType[] {
const nodes = useStore(nodesSelector, shallow) as NodeType[]; const nodes = useStore(nodesSelector, shallow) as NodeType[];
+8
View File
@@ -4,6 +4,14 @@ import { shallow } from 'zustand/shallow';
import { useStore } from '../hooks/useStore'; import { useStore } from '../hooks/useStore';
import type { Node } from '../types'; import type { Node } from '../types';
/**
* Hook for receiving data of one or multiple nodes
*
* @public
* @param nodeId - The id (or ids) of the node to get the data from
* @param guard - Optional guard function to narrow down the node type
* @returns An array data objects
*/
export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null; export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null;
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][]; export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][];
export function useNodesData<NodeType extends Node = Node>( export function useNodesData<NodeType extends Node = Node>(
+15 -2
View File
@@ -7,8 +7,6 @@ import type { Node, NodeChange, Edge, EdgeChange } from '../types';
type ApplyChanges<ItemType, ChangesType> = (changes: ChangesType[], items: ItemType[]) => ItemType[]; type ApplyChanges<ItemType, ChangesType> = (changes: ChangesType[], items: ItemType[]) => ItemType[];
type OnChange<ChangesType> = (changes: ChangesType[]) => void; type OnChange<ChangesType> = (changes: ChangesType[]) => void;
// returns a hook that can be used liked this:
// const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
function createUseItemsState( function createUseItemsState(
applyChanges: ApplyChanges<Node, NodeChange> applyChanges: ApplyChanges<Node, NodeChange>
): <NodeType extends Node = Node>( ): <NodeType extends Node = Node>(
@@ -31,5 +29,20 @@ function createUseItemsState(
}; };
} }
/**
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
*
* @public
* @param initialNodes
* @returns an array [nodes, setNodes, onNodesChange]
*/
export const useNodesState = createUseItemsState(applyNodeChanges); export const useNodesState = createUseItemsState(applyNodeChanges);
/**
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
*
* @public
* @param initialEdges
* @returns an array [edges, setEdges, onEdgesChange]
*/
export const useEdgesState = createUseItemsState(applyEdgeChanges); export const useEdgesState = createUseItemsState(applyEdgeChanges);
@@ -21,6 +21,13 @@ const defaultOptions = {
includeHiddenNodes: false, includeHiddenNodes: false,
}; };
/**
* Hook which returns true when all nodes are initialized.
*
* @public
* @param options.includeHiddenNodes - defaults to false
* @returns boolean indicating whether all nodes are initialized
*/
function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean { function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
const initialized = useStore(selector(options)); const initialized = useStore(selector(options));
@@ -3,6 +3,11 @@ import { useEffect, useRef } from 'react';
import useReactFlow from './useReactFlow'; import useReactFlow from './useReactFlow';
import type { OnInit } from '../types'; import type { OnInit } from '../types';
/**
* Hook for calling onInit handler.
*
* @internal
*/
function useOnInitHandler(onInit: OnInit | undefined) { function useOnInitHandler(onInit: OnInit | undefined) {
const rfInstance = useReactFlow(); const rfInstance = useReactFlow();
const isInitialized = useRef<boolean>(false); const isInitialized = useRef<boolean>(false);
@@ -7,6 +7,12 @@ export type UseOnSelectionChangeOptions = {
onChange: OnSelectionChangeFunc; onChange: OnSelectionChangeFunc;
}; };
/**
* Hook for registering an onSelectionChange handler.
*
* @public
* @params params.onChange - The handler to register
*/
function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
const store = useStoreApi(); const store = useStoreApi();
@@ -9,6 +9,14 @@ export type UseOnViewportChangeOptions = {
onEnd?: OnViewportChange; onEnd?: OnViewportChange;
}; };
/**
* Hook for registering an onViewportChange handler.
*
* @public
* @param params.onStart - gets called when the viewport starts changing
* @param params.onChange - gets called when the viewport changes
* @param params.onEnd - gets called when the viewport stops changing
*/
function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) { function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
const store = useStoreApi(); const store = useStoreApi();
+7 -54
View File
@@ -1,13 +1,5 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system';
getElementsToRemove,
getIncomersBase,
getOutgoersBase,
getOverlappingArea,
isRectObject,
nodeToRect,
type Rect,
} from '@xyflow/system';
import useViewportHelper from './useViewportHelper'; import useViewportHelper from './useViewportHelper';
import { useStoreApi } from './useStore'; import { useStoreApi } from './useStore';
@@ -238,48 +230,13 @@ export default function useReactFlow<NodeType extends Node = Node, EdgeType exte
[] []
); );
const getConnectedEdges = useCallback<Instance.getConnectedEdges>((node) => { const updateNode = useCallback<Instance.UpdateNode<NodeType>>(
const { edges } = store.getState();
const nodeIds = new Set();
if (typeof node === 'string') {
nodeIds.add(node);
} else if (node.length >= 1) {
node.forEach((n) => {
nodeIds.add(n.id);
});
}
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
}, []);
const getIncomers = useCallback<Instance.getIncomers>((node) => {
const { nodes, edges } = store.getState();
if (typeof node === 'string') {
return getIncomersBase({ id: node }, nodes, edges);
}
return getIncomersBase(node, nodes, edges);
}, []);
const getOutgoers = useCallback<Instance.getOutgoers>((node) => {
const { nodes, edges } = store.getState();
if (typeof node == 'string') {
return getOutgoersBase({ id: node }, nodes, edges);
}
return getOutgoersBase(node, nodes, edges);
}, []);
const updateNode = useCallback<Instance.UpdateNode>(
(id, nodeUpdate, options = { replace: true }) => { (id, nodeUpdate, options = { replace: true }) => {
setNodes((prevNodes) => setNodes((prevNodes) =>
prevNodes.map((node) => { prevNodes.map((node) => {
if (node.id === id) { if (node.id === id) {
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate; const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as NodeType) : nodeUpdate;
return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode }; return options.replace && isNode(nextNode) ? (nextNode as NodeType) : { ...node, ...nextNode };
} }
return node; return node;
@@ -289,7 +246,7 @@ export default function useReactFlow<NodeType extends Node = Node, EdgeType exte
[setNodes] [setNodes]
); );
const updateNodeData = useCallback<Instance.UpdateNodeData>( const updateNodeData = useCallback<Instance.UpdateNodeData<NodeType>>(
(id, dataUpdate, options = { replace: false }) => { (id, dataUpdate, options = { replace: false }) => {
updateNode( updateNode(
id, id,
@@ -318,9 +275,6 @@ export default function useReactFlow<NodeType extends Node = Node, EdgeType exte
deleteElements, deleteElements,
getIntersectingNodes, getIntersectingNodes,
isNodeIntersecting, isNodeIntersecting,
getConnectedEdges,
getIncomers,
getOutgoers,
updateNode, updateNode,
updateNodeData, updateNodeData,
}; };
@@ -338,8 +292,7 @@ export default function useReactFlow<NodeType extends Node = Node, EdgeType exte
deleteElements, deleteElements,
getIntersectingNodes, getIntersectingNodes,
isNodeIntersecting, isNodeIntersecting,
getConnectedEdges, updateNode,
getIncomers, updateNodeData,
getOutgoers,
]); ]);
} }
@@ -3,6 +3,11 @@ import { errorMessages, getDimensions } from '@xyflow/system';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
/**
* Hook for handling resize events.
*
* @internal
*/
function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null>): void { function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null>): void {
const store = useStoreApi(); const store = useStoreApi();
+8
View File
@@ -10,6 +10,14 @@ const zustandErrorMessage = errorMessages['error001']();
type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never; type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never;
/**
* Hook for accessing the internal store. Should only be used in rare cases.
*
* @public
* @param selector
* @param equalityFn
* @returns The selected state slice
*/
function useStore<StateSlice = ExtractState>( function useStore<StateSlice = ExtractState>(
selector: (state: ReactFlowState) => StateSlice, selector: (state: ReactFlowState) => StateSlice,
equalityFn?: (a: StateSlice, b: StateSlice) => boolean equalityFn?: (a: StateSlice, b: StateSlice) => boolean
@@ -3,6 +3,12 @@ import type { UpdateNodeInternals, NodeDimensionUpdate } from '@xyflow/system';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
/**
* Hook for updating node internals.
*
* @public
* @returns function for updating node internals
*/
function useUpdateNodeInternals(): UpdateNodeInternals { function useUpdateNodeInternals(): UpdateNodeInternals {
const store = useStoreApi(); const store = useStoreApi();
@@ -7,6 +7,12 @@ import { useStoreApi } from '../hooks/useStore';
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) => const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined')); n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'));
/**
* Hook for updating node positions.
*
* @internal
* @returns function for updating node positions
*/
function useUpdateNodePositions() { function useUpdateNodePositions() {
const store = useStoreApi(); const store = useStoreApi();
+6
View File
@@ -10,6 +10,12 @@ const viewportSelector = (state: ReactFlowState) => ({
zoom: state.transform[2], zoom: state.transform[2],
}); });
/**
* Hook for getting the current viewport from the store.
*
* @public
* @returns The current viewport
*/
function useViewport(): Viewport { function useViewport(): Viewport {
const viewport = useStore(viewportSelector, shallow); const viewport = useStore(viewportSelector, shallow);
@@ -12,6 +12,12 @@ import type { ViewportHelperFunctions, ReactFlowState } from '../types';
const selector = (s: ReactFlowState) => !!s.panZoom; const selector = (s: ReactFlowState) => !!s.panZoom;
/**
* Hook for getting viewport helper functions.
*
* @internal
* @returns viewport helper functions
*/
const useViewportHelper = (): ViewportHelperFunctions => { const useViewportHelper = (): ViewportHelperFunctions => {
const store = useStoreApi(); const store = useStoreApi();
const panZoomInitialized = useStore(selector); const panZoomInitialized = useStore(selector);
@@ -6,6 +6,12 @@ import type { ReactFlowState } from '../types';
const selector = (state: ReactFlowState) => state.panZoom?.syncViewport; const selector = (state: ReactFlowState) => state.panZoom?.syncViewport;
/**
* Hook for syncing the viewport with the panzoom instance.
*
* @internal
* @param viewport
*/
export default function useViewportSync(viewport?: Viewport) { export default function useViewportSync(viewport?: Viewport) {
const syncViewport = useStore(selector); const syncViewport = useStore(selector);
const store = useStoreApi(); const store = useStoreApi();
@@ -5,6 +5,13 @@ import { isEdgeVisible } from '@xyflow/system';
import { useStore } from './useStore'; import { useStore } from './useStore';
import { type ReactFlowState } from '../types'; import { type ReactFlowState } from '../types';
/**
* Hook for getting the visible edge ids from the store.
*
* @internal
* @param onlyRenderVisible
* @returns array with visible edge ids
*/
function useVisibleEdgeIds(onlyRenderVisible: boolean): string[] { function useVisibleEdgeIds(onlyRenderVisible: boolean): string[] {
const edgeIds = useStore( const edgeIds = useStore(
useCallback( useCallback(
@@ -13,6 +13,13 @@ const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
: Array.from(s.nodeLookup.keys()); : Array.from(s.nodeLookup.keys());
}; };
/**
* Hook for getting the visible node ids from the store.
*
* @internal
* @param onlyRenderVisible
* @returns array with visible node ids
*/
function useVisibleNodeIds(onlyRenderVisible: boolean) { function useVisibleNodeIds(onlyRenderVisible: boolean) {
const nodeIds = useStore(useCallback(selector(onlyRenderVisible), [onlyRenderVisible]), shallow); const nodeIds = useStore(useCallback(selector(onlyRenderVisible), [onlyRenderVisible]), shallow);
+4
View File
@@ -40,6 +40,10 @@ export type NodeResetChange<NodeType extends Node = Node> = {
type: 'reset'; type: 'reset';
}; };
/**
* Union type of all possible node changes.
* @public
*/
export type NodeChange = export type NodeChange =
| NodeDimensionChange | NodeDimensionChange
| NodePositionChange | NodePositionChange
@@ -45,6 +45,10 @@ import type {
EdgeMouseHandler, EdgeMouseHandler,
} from '.'; } from '.';
/**
* ReactFlow component props.
* @public
*/
export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & { export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
+13 -4
View File
@@ -54,7 +54,8 @@ type StepEdgeType<T> = DefaultEdge<T> & {
}; };
/** /**
* The Edge type is mainly used for the `edges` that get passed to the ReactFlow component. * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component
* @public
*/ */
export type Edge<T = any> = DefaultEdge<T> | SmoothStepEdgeType<T> | BezierEdgeType<T> | StepEdgeType<T>; export type Edge<T = any> = DefaultEdge<T> | SmoothStepEdgeType<T> | BezierEdgeType<T> | StepEdgeType<T>;
@@ -92,7 +93,8 @@ export type EdgeTextProps = HTMLAttributes<SVGElement> &
}; };
/** /**
* Custom edge component props. * Custom edge component props
* @public
*/ */
export type EdgeProps<T = any> = Pick< export type EdgeProps<T = any> = Pick<
Edge<T>, Edge<T>,
@@ -110,7 +112,8 @@ export type EdgeProps<T = any> = Pick<
}; };
/** /**
* BaseEdge component props. * BaseEdge component props
* @public
*/ */
export type BaseEdgeProps = EdgeLabelOptions & { export type BaseEdgeProps = EdgeLabelOptions & {
id?: string; id?: string;
@@ -124,7 +127,8 @@ export type BaseEdgeProps = EdgeLabelOptions & {
}; };
/** /**
* Helper type for edge components that get exported by the library. * Helper type for edge components that get exported by the library
* @public
*/ */
export type EdgeComponentProps = EdgePosition & export type EdgeComponentProps = EdgePosition &
EdgeLabelOptions & { EdgeLabelOptions & {
@@ -143,26 +147,31 @@ export type EdgeComponentWithPathOptions<PathOptions> = EdgeComponentProps & {
/** /**
* BezierEdge component props * BezierEdge component props
* @public
*/ */
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>; export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
/** /**
* SmoothStepEdge component props * SmoothStepEdge component props
* @public
*/ */
export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>; export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
/** /**
* StepEdge component props * StepEdge component props
* @public
*/ */
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>; export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
/** /**
* StraightEdge component props * StraightEdge component props
* @public
*/ */
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>; export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
/** /**
* SimpleBezier component props * SimpleBezier component props
* @public
*/ */
export type SimpleBezierEdgeProps = EdgeComponentProps; export type SimpleBezierEdgeProps = EdgeComponentProps;
+1 -1
View File
@@ -51,7 +51,7 @@ export namespace Instance {
export type UpdateNode<NodeType extends Node = Node> = ( export type UpdateNode<NodeType extends Node = Node> = (
id: string, id: string,
dataUpdate: Partial<NodeType> | ((node: NodeType) => Partial<NodeType>), nodeUpdate: Partial<NodeType> | ((node: NodeType) => Partial<NodeType>),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => void;
export type UpdateNodeData<NodeType extends Node = Node> = ( export type UpdateNodeData<NodeType extends Node = Node> = (