implemented new fitView logic
This commit is contained in:
@@ -90,4 +90,57 @@ function BackgroundComponent({
|
||||
|
||||
BackgroundComponent.displayName = 'Background';
|
||||
|
||||
/**
|
||||
* The `<Background />` component makes it convenient to render different types of backgrounds common in node-based UIs. It comes with three variants: lines, dots and cross.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* A simple example of how to use the Background component.
|
||||
*
|
||||
* ```tsx
|
||||
* import { useState } from 'react';
|
||||
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
||||
*
|
||||
* export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
||||
* <Background color="#ccc" variant={BackgroundVariant.Dots} />
|
||||
* </ReactFlow>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* In this example you can see how to combine multiple backgrounds
|
||||
*
|
||||
* ```tsx
|
||||
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
||||
* import '@xyflow/react/dist/style.css';
|
||||
*
|
||||
* export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
||||
* <Background
|
||||
* id="1"
|
||||
* gap={10}
|
||||
* color="#f1f1f1"
|
||||
* variant={BackgroundVariant.Lines}
|
||||
* />
|
||||
* <Background
|
||||
* id="2"
|
||||
* gap={100}
|
||||
* color="#ccc"
|
||||
* variant={BackgroundVariant.Lines}
|
||||
* />
|
||||
* </ReactFlow>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* When combining multiple <Background /> components it’s important to give each of them a unique id prop!
|
||||
*
|
||||
*/
|
||||
export const Background = memo(BackgroundComponent);
|
||||
|
||||
@@ -1,34 +1,60 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
/**
|
||||
* The three variants are exported as an enum for convenience. You can either import
|
||||
* the enum and use it like `BackgroundVariant.Lines` or you can use the raw string
|
||||
* value directly.
|
||||
* @public
|
||||
*/
|
||||
export enum BackgroundVariant {
|
||||
Lines = 'lines',
|
||||
Dots = 'dots',
|
||||
Cross = 'cross',
|
||||
}
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type BackgroundProps = {
|
||||
/** When multiple backgrounds are present on the page, each one should have a unique id. */
|
||||
id?: string;
|
||||
/** Color of the pattern */
|
||||
/** Color of the pattern. */
|
||||
color?: string;
|
||||
/** Color of the background */
|
||||
/** Color of the background. */
|
||||
bgColor?: string;
|
||||
/** Class applied to the container */
|
||||
/** Class applied to the container. */
|
||||
className?: string;
|
||||
/** Class applied to the pattern */
|
||||
/** Class applied to the pattern. */
|
||||
patternClassName?: string;
|
||||
/** Gap between repetitions of the pattern */
|
||||
/**
|
||||
* The gap between patterns. Passing in a tuple allows you to control the x and y gap
|
||||
* independently.
|
||||
* @default 20
|
||||
*/
|
||||
gap?: number | [number, number];
|
||||
/** Size of a single pattern element */
|
||||
/**
|
||||
* The radius of each dot or the size of each rectangle if `BackgroundVariant.Dots` or
|
||||
* `BackgroundVariant.Cross` is used. This defaults to 1 or 6 respectively, or ignored if
|
||||
* `BackgroundVariant.Lines` is used.
|
||||
*/
|
||||
size?: number;
|
||||
/** Offset of the pattern */
|
||||
/**
|
||||
* Offset of the pattern.
|
||||
* @default 0
|
||||
*/
|
||||
offset?: number | [number, number];
|
||||
/** Line width of the Line pattern */
|
||||
/**
|
||||
* The stroke thickness used when drawing the pattern.
|
||||
* @default 1
|
||||
*/
|
||||
lineWidth?: number;
|
||||
/** Variant of the pattern
|
||||
/**
|
||||
* Variant of the pattern.
|
||||
* @default BackgroundVariant.Dots
|
||||
* @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
|
||||
* 'lines', 'dots', 'cross'
|
||||
*/
|
||||
variant?: BackgroundVariant;
|
||||
/** Style applied to the container */
|
||||
/** Style applied to the container. */
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,29 @@ import cc from 'classcat';
|
||||
|
||||
import type { ControlButtonProps } from './types';
|
||||
|
||||
/**
|
||||
* You can add buttons to the control panel by using the `<ControlButton />` component
|
||||
* and pass it as a child to the [`<Controls />`](/api-reference/components/controls) component.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*```jsx
|
||||
*import { MagicWand } from '@radix-ui/react-icons'
|
||||
*import { ReactFlow, Controls, ControlButton } from '@xyflow/react'
|
||||
*
|
||||
*export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlow nodes={[...]} edges={[...]}>
|
||||
* <Controls>
|
||||
* <ControlButton onClick={() => alert('Something magical just happened. ✨')}>
|
||||
* <MagicWand />
|
||||
* </ControlButton>
|
||||
* </Controls>
|
||||
* </ReactFlow>
|
||||
* )
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function ControlButton({ children, className, ...rest }: ControlButtonProps) {
|
||||
return (
|
||||
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
|
||||
|
||||
@@ -125,4 +125,25 @@ function ControlsComponent({
|
||||
|
||||
ControlsComponent.displayName = 'Controls';
|
||||
|
||||
/**
|
||||
* The `<Controls />` component renders a small panel that contains convenient
|
||||
* buttons to zoom in, zoom out, fit the view, and lock the viewport.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*```tsx
|
||||
*import { ReactFlow, Controls } from '@xyflow/react'
|
||||
*
|
||||
*export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlow nodes={[...]} edges={[...]}>
|
||||
* <Controls />
|
||||
* </ReactFlow>
|
||||
* )
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks To extend or customise the controls, you can use the [`<ControlButton />`](/api-reference/components/control-button) component
|
||||
*
|
||||
*/
|
||||
export const Controls = memo(ControlsComponent);
|
||||
|
||||
@@ -3,24 +3,46 @@ import type { PanelPosition } from '@xyflow/system';
|
||||
|
||||
import type { FitViewOptions } from '../../types';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ControlProps = {
|
||||
/** Show button for zoom in/out */
|
||||
/**
|
||||
* Whether or not to show the zoom in and zoom out buttons. These buttons will adjust the viewport
|
||||
* zoom by a fixed amount each press.
|
||||
* @default true
|
||||
*/
|
||||
showZoom?: boolean;
|
||||
/** Show button for fit view */
|
||||
/**
|
||||
* Whether or not to show the fit view button. By default, this button will adjust the viewport so
|
||||
* that all nodes are visible at once.
|
||||
* @default true
|
||||
*/
|
||||
showFitView?: boolean;
|
||||
/** Show button for toggling interactivity */
|
||||
/**
|
||||
* Show button for toggling interactivity
|
||||
* @default true
|
||||
*/
|
||||
showInteractive?: boolean;
|
||||
/** Options being used when fit view button is clicked */
|
||||
/**
|
||||
* Customise the options for the fit view button. These are the same options you would pass to the
|
||||
* fitView function.
|
||||
*/
|
||||
fitViewOptions?: FitViewOptions;
|
||||
/** Callback when zoom in button is clicked */
|
||||
/** Called in addition the default zoom behavior when the zoom in button is clicked. */
|
||||
onZoomIn?: () => void;
|
||||
/** Callback when zoom out button is clicked */
|
||||
/** Called in addition the default zoom behavior when the zoom out button is clicked. */
|
||||
onZoomOut?: () => void;
|
||||
/** Callback when fit view button is clicked */
|
||||
/**
|
||||
* Called when the fit view button is clicked. When this is not provided, the viewport will be
|
||||
* adjusted so that all nodes are visible.
|
||||
*/
|
||||
onFitView?: () => void;
|
||||
/** Callback when interactivity is toggled */
|
||||
/** Called when the interactive (lock) button is clicked. */
|
||||
onInteractiveChange?: (interactiveStatus: boolean) => void;
|
||||
/** Position of the controls on the pane
|
||||
/**
|
||||
* Position of the controls on the pane
|
||||
* @default PanelPosition.BottomLeft
|
||||
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
||||
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
||||
*/
|
||||
@@ -28,10 +50,19 @@ export type ControlProps = {
|
||||
children?: ReactNode;
|
||||
/** Style applied to container */
|
||||
style?: React.CSSProperties;
|
||||
/** ClassName applied to container */
|
||||
/** Class name applied to container */
|
||||
className?: string;
|
||||
/**
|
||||
* @default 'React Flow controls'
|
||||
*/
|
||||
'aria-label'?: string;
|
||||
/**
|
||||
* @default 'vertical'
|
||||
*/
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
@@ -15,6 +15,8 @@ import type { MiniMapProps } from './types';
|
||||
const defaultWidth = 200;
|
||||
const defaultHeight = 150;
|
||||
|
||||
const filterHidden = (node: Node) => !node.hidden;
|
||||
|
||||
const selector = (s: ReactFlowState) => {
|
||||
const viewBB: Rect = {
|
||||
x: -s.transform[0] / s.transform[2],
|
||||
@@ -25,7 +27,10 @@ const selector = (s: ReactFlowState) => {
|
||||
|
||||
return {
|
||||
viewBB,
|
||||
boundingRect: s.nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup), viewBB) : viewBB,
|
||||
boundingRect:
|
||||
s.nodeLookup.size > 0
|
||||
? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, { filter: filterHidden }), viewBB)
|
||||
: viewBB,
|
||||
rfId: s.rfId,
|
||||
panZoom: s.panZoom,
|
||||
translateExtent: s.translateExtent,
|
||||
@@ -44,8 +49,10 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
nodeClassName = '',
|
||||
nodeBorderRadius = 5,
|
||||
nodeStrokeWidth,
|
||||
// We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
||||
// a component properly.
|
||||
/*
|
||||
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
||||
* a component properly.
|
||||
*/
|
||||
nodeComponent,
|
||||
bgColor,
|
||||
maskColor,
|
||||
@@ -118,7 +125,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
|
||||
const onSvgNodeClick = onNodeClick
|
||||
? useCallback((event: MouseEvent, nodeId: string) => {
|
||||
const node = store.getState().nodeLookup.get(nodeId)!;
|
||||
const node: NodeType = store.getState().nodeLookup.get(nodeId)!.internals.userNode;
|
||||
onNodeClick(event, node);
|
||||
}, [])
|
||||
: undefined;
|
||||
@@ -136,7 +143,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
typeof maskStrokeWidth === 'number' ? maskStrokeWidth * viewScale : undefined,
|
||||
'--xy-minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined,
|
||||
'--xy-minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined,
|
||||
'--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined,
|
||||
'--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'number' ? nodeStrokeWidth : undefined,
|
||||
} as CSSProperties
|
||||
}
|
||||
className={cc(['react-flow__minimap', className])}
|
||||
@@ -176,4 +183,24 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
|
||||
MiniMapComponent.displayName = 'MiniMap';
|
||||
|
||||
/**
|
||||
* The `<MiniMap />` component can be used to render an overview of your flow. It
|
||||
* renders each node as an SVG element and visualizes where the current viewport is
|
||||
* in relation to the rest of the flow.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```jsx
|
||||
*import { ReactFlow, MiniMap } from '@xyflow/react';
|
||||
*
|
||||
*export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlow nodes={[...]]} edges={[...]]}>
|
||||
* <MiniMap nodeStrokeWidth={3} />
|
||||
* </ReactFlow>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const MiniMap = memo(MiniMapComponent) as typeof MiniMapComponent;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { MiniMapNode } from './MiniMapNode';
|
||||
import type { ReactFlowState, Node, InternalNode } from '../../types';
|
||||
import type { ReactFlowState, Node } from '../../types';
|
||||
import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types';
|
||||
|
||||
declare const window: any;
|
||||
@@ -21,8 +21,10 @@ function MiniMapNodes<NodeType extends Node>({
|
||||
nodeClassName = '',
|
||||
nodeBorderRadius = 5,
|
||||
nodeStrokeWidth,
|
||||
// We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
||||
// a component properly.
|
||||
/*
|
||||
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
||||
* a component properly.
|
||||
*/
|
||||
nodeComponent: NodeComponent = MiniMapNode,
|
||||
onClick,
|
||||
}: MiniMapNodesProps<NodeType>) {
|
||||
@@ -36,11 +38,13 @@ function MiniMapNodes<NodeType extends Node>({
|
||||
return (
|
||||
<>
|
||||
{nodeIds.map((nodeId) => (
|
||||
// The split of responsibilities between MiniMapNodes and
|
||||
// NodeComponentWrapper may appear weird. However, it’s designed to
|
||||
// minimize the cost of updates when individual nodes change.
|
||||
//
|
||||
// For more details, see a similar commit in `NodeRenderer/index.tsx`.
|
||||
/*
|
||||
* The split of responsibilities between MiniMapNodes and
|
||||
* NodeComponentWrapper may appear weird. However, it’s designed to
|
||||
* minimize the cost of updates when individual nodes change.
|
||||
*
|
||||
* For more details, see a similar commit in `NodeRenderer/index.tsx`.
|
||||
*/
|
||||
<NodeComponentWrapper<NodeType>
|
||||
key={nodeId}
|
||||
id={nodeId}
|
||||
@@ -80,8 +84,9 @@ function NodeComponentWrapperInner<NodeType extends Node>({
|
||||
shapeRendering: string;
|
||||
}) {
|
||||
const { node, x, y, width, height } = useStore((s) => {
|
||||
const node = s.nodeLookup.get(id) as InternalNode<NodeType>;
|
||||
const { x, y } = node.internals.positionAbsolute;
|
||||
const { internals } = s.nodeLookup.get(id)!;
|
||||
const node = internals.userNode as NodeType;
|
||||
const { x, y } = internals.positionAbsolute;
|
||||
const { width, height } = getNodeDimensions(node);
|
||||
|
||||
return {
|
||||
|
||||
@@ -6,47 +6,97 @@ import type { Node } from '../../types';
|
||||
|
||||
export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & {
|
||||
/** Color of nodes on minimap */
|
||||
/**
|
||||
* Color of nodes on minimap.
|
||||
* @default "#e2e2e2"
|
||||
*/
|
||||
nodeColor?: string | GetMiniMapNodeAttribute<NodeType>;
|
||||
/** Stroke color of nodes on minimap */
|
||||
/**
|
||||
* Stroke color of nodes on minimap.
|
||||
* @default "transparent"
|
||||
*/
|
||||
nodeStrokeColor?: string | GetMiniMapNodeAttribute<NodeType>;
|
||||
/** ClassName applied to nodes on minimap */
|
||||
/**
|
||||
* Class name applied to nodes on minimap.
|
||||
* @default ""
|
||||
*/
|
||||
nodeClassName?: string | GetMiniMapNodeAttribute<NodeType>;
|
||||
/** Border radius of nodes on minimap */
|
||||
/**
|
||||
* Border radius of nodes on minimap.
|
||||
* @default 5
|
||||
*/
|
||||
nodeBorderRadius?: number;
|
||||
/** Stroke width of nodes on minimap */
|
||||
/**
|
||||
* Stroke width of nodes on minimap.
|
||||
* @default 2
|
||||
*/
|
||||
nodeStrokeWidth?: number;
|
||||
/** Component used to render nodes on minimap */
|
||||
/**
|
||||
* A custom component to render the nodes in the minimap. This component must render an SVG
|
||||
* element!
|
||||
*/
|
||||
nodeComponent?: ComponentType<MiniMapNodeProps>;
|
||||
/** Background color of minimap */
|
||||
/** Background color of minimap. */
|
||||
bgColor?: string;
|
||||
/** Color of mask representing viewport */
|
||||
/**
|
||||
* The color of the mask that covers the portion of the minimap not currently visible in the
|
||||
* viewport.
|
||||
* @default "rgba(240, 240, 240, 0.6)"
|
||||
*/
|
||||
maskColor?: string;
|
||||
/** Stroke color of mask representing viewport */
|
||||
/**
|
||||
* Stroke color of mask representing viewport.
|
||||
* @default transparent
|
||||
*/
|
||||
maskStrokeColor?: string;
|
||||
/** Stroke width of mask representing viewport */
|
||||
/**
|
||||
* Stroke width of mask representing viewport.
|
||||
* @default 1
|
||||
*/
|
||||
maskStrokeWidth?: number;
|
||||
/** Position of minimap on pane
|
||||
/**
|
||||
* Position of minimap on pane.
|
||||
* @default PanelPosition.BottomRight
|
||||
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
||||
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
||||
*/
|
||||
position?: PanelPosition;
|
||||
/** Callback caled when minimap is clicked*/
|
||||
/** Callback called when minimap is clicked. */
|
||||
onClick?: (event: MouseEvent, position: XYPosition) => void;
|
||||
/** Callback called when node on minimap is clicked */
|
||||
/** Callback called when node on minimap is clicked. */
|
||||
onNodeClick?: (event: MouseEvent, node: NodeType) => void;
|
||||
/** If true, viewport is pannable via mini map component */
|
||||
/**
|
||||
* Determines whether you can pan the viewport by dragging inside the minimap.
|
||||
* @default false
|
||||
*/
|
||||
pannable?: boolean;
|
||||
/** If true, viewport is zoomable via mini map component */
|
||||
/**
|
||||
* Determines whether you can zoom the viewport by scrolling inside the minimap.
|
||||
* @default false
|
||||
*/
|
||||
zoomable?: boolean;
|
||||
/** The aria-label attribute */
|
||||
/**
|
||||
* There is no text inside the minimap for a screen reader to use as an accessible name, so it's
|
||||
* important we provide one to make the minimap accessible. The default is sufficient, but you may
|
||||
* want to replace it with something more relevant to your app or product.
|
||||
* @default "React Flow mini map"
|
||||
*/
|
||||
ariaLabel?: string | null;
|
||||
/** Invert direction when panning the minimap viewport */
|
||||
/** Invert direction when panning the minimap viewport. */
|
||||
inversePan?: boolean;
|
||||
/** Step size for zooming in/out on minimap */
|
||||
/**
|
||||
* Step size for zooming in/out on minimap.
|
||||
* @default 10
|
||||
*/
|
||||
zoomStep?: number;
|
||||
/** Offset the viewport on the minmap, acts like a padding */
|
||||
/**
|
||||
* Offset the viewport on the minimap, acts like a padding.
|
||||
* @default 5
|
||||
*/
|
||||
offsetScale?: number;
|
||||
};
|
||||
|
||||
@@ -57,6 +107,12 @@ export type MiniMapNodes<NodeType extends Node = Node> = Pick<
|
||||
onClick?: (event: MouseEvent, nodeId: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The props that are passed to the MiniMapNode component
|
||||
*
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type MiniMapNodeProps = {
|
||||
id: string;
|
||||
x: number;
|
||||
|
||||
@@ -74,8 +74,8 @@ function ResizeControl({
|
||||
|
||||
if (node && node.expandParent && node.parentId) {
|
||||
const origin = node.origin ?? nodeOrigin;
|
||||
const width = change.width ?? node.measured.width!;
|
||||
const height = change.height ?? node.measured.height!;
|
||||
const width = change.width ?? node.measured.width ?? 0;
|
||||
const height = change.height ?? node.measured.height ?? 0;
|
||||
|
||||
const child: ParentExpandChild = {
|
||||
id: node.id,
|
||||
@@ -99,8 +99,10 @@ function ResizeControl({
|
||||
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
|
||||
changes.push(...parentExpandChanges);
|
||||
|
||||
// when the parent was expanded by the child node, its position will be clamped at
|
||||
// 0,0 when node origin is 0,0 and to width, height if it's 1,1
|
||||
/*
|
||||
* when the parent was expanded by the child node, its position will be clamped at
|
||||
* 0,0 when node origin is 0,0 and to width, height if it's 1,1
|
||||
*/
|
||||
nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
|
||||
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
|
||||
}
|
||||
@@ -140,11 +142,15 @@ function ResizeControl({
|
||||
|
||||
triggerNodeChanges(changes);
|
||||
},
|
||||
onEnd: () => {
|
||||
onEnd: ({ width, height }) => {
|
||||
const dimensionChange: NodeDimensionChange = {
|
||||
id: id,
|
||||
type: 'dimensions',
|
||||
resizing: false,
|
||||
dimensions: {
|
||||
width,
|
||||
height,
|
||||
},
|
||||
};
|
||||
store.getState().triggerNodeChanges([dimensionChange]);
|
||||
},
|
||||
@@ -201,4 +207,9 @@ export function ResizeControlLine(props: ResizeControlLineProps) {
|
||||
return <ResizeControl {...props} variant={ResizeControlVariant.Line} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* To create your own resizing UI, you can use the `NodeResizeControl` component where you can pass children (such as icons).
|
||||
* @public
|
||||
*
|
||||
*/
|
||||
export const NodeResizeControl = memo(ResizeControl);
|
||||
|
||||
@@ -3,6 +3,30 @@ import { ResizeControlVariant, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSI
|
||||
import { NodeResizeControl } from './NodeResizeControl';
|
||||
import type { NodeResizerProps } from './types';
|
||||
|
||||
/**
|
||||
* The `<NodeResizer />` component can be used to add a resize functionality to your
|
||||
* nodes. It renders draggable controls around the node to resize in all directions.
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
*```jsx
|
||||
*import { memo } from 'react';
|
||||
*import { Handle, Position, NodeResizer } from '@xyflow/react';
|
||||
*
|
||||
*function ResizableNode({ data }) {
|
||||
* return (
|
||||
* <>
|
||||
* <NodeResizer minWidth={100} minHeight={30} />
|
||||
* <Handle type="target" position={Position.Left} />
|
||||
* <div style={{ padding: 10 }}>{data.label}</div>
|
||||
* <Handle type="source" position={Position.Right} />
|
||||
* </>
|
||||
* );
|
||||
*};
|
||||
*
|
||||
*export default memo(ResizableNode);
|
||||
*```
|
||||
*/
|
||||
export function NodeResizer({
|
||||
nodeId,
|
||||
isVisible = true,
|
||||
|
||||
@@ -9,43 +9,68 @@ import type {
|
||||
OnResizeEnd,
|
||||
} from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type NodeResizerProps = {
|
||||
/** Id of the node it is resizing
|
||||
/**
|
||||
* Id of the node it is resizing.
|
||||
* @remarks optional if used inside custom node
|
||||
*/
|
||||
nodeId?: string;
|
||||
/** Color of the resize handle */
|
||||
/** Color of the resize handle. */
|
||||
color?: string;
|
||||
/** ClassName applied to handle */
|
||||
/** Class name applied to handle. */
|
||||
handleClassName?: string;
|
||||
/** Style applied to handle */
|
||||
/** Style applied to handle. */
|
||||
handleStyle?: CSSProperties;
|
||||
/** ClassName applied to line */
|
||||
/** Class name applied to line. */
|
||||
lineClassName?: string;
|
||||
/** Style applied to line */
|
||||
/** Style applied to line. */
|
||||
lineStyle?: CSSProperties;
|
||||
/** Are the controls visible */
|
||||
/**
|
||||
* Are the controls visible.
|
||||
* @default true
|
||||
*/
|
||||
isVisible?: boolean;
|
||||
/** Minimum width of node */
|
||||
/**
|
||||
* Minimum width of node.
|
||||
* @default 10
|
||||
*/
|
||||
minWidth?: number;
|
||||
/** Minimum height of node */
|
||||
/**
|
||||
* Minimum height of node.
|
||||
* @default 10
|
||||
*/
|
||||
minHeight?: number;
|
||||
/** Maximum width of node */
|
||||
/**
|
||||
* Maximum width of node.
|
||||
* @default Number.MAX_VALUE
|
||||
*/
|
||||
maxWidth?: number;
|
||||
/** Maximum height of node */
|
||||
/**
|
||||
* Maximum height of node.
|
||||
* @default Number.MAX_VALUE
|
||||
*/
|
||||
maxHeight?: number;
|
||||
/** Keep aspect ratio when resizing */
|
||||
/**
|
||||
* Keep aspect ratio when resizing.
|
||||
* @default false
|
||||
*/
|
||||
keepAspectRatio?: boolean;
|
||||
/** Callback to determine if node should resize */
|
||||
/** Callback to determine if node should resize. */
|
||||
shouldResize?: ShouldResize;
|
||||
/** Callback called when resizing starts */
|
||||
/** Callback called when resizing starts. */
|
||||
onResizeStart?: OnResizeStart;
|
||||
/** Callback called when resizing */
|
||||
/** Callback called when resizing. */
|
||||
onResize?: OnResize;
|
||||
/** Callback called when resizing ends */
|
||||
/** Callback called when resizing ends. */
|
||||
onResizeEnd?: OnResizeEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ResizeControlProps = Pick<
|
||||
NodeResizerProps,
|
||||
| 'nodeId'
|
||||
@@ -60,12 +85,15 @@ export type ResizeControlProps = Pick<
|
||||
| 'onResize'
|
||||
| 'onResizeEnd'
|
||||
> & {
|
||||
/** Position of the control
|
||||
/**
|
||||
* Position of the control.
|
||||
* @example ControlPosition.TopLeft, ControlPosition.TopRight,
|
||||
* ControlPosition.BottomLeft, ControlPosition.BottomRight
|
||||
*/
|
||||
position?: ControlPosition;
|
||||
/** Variant of the control
|
||||
/**
|
||||
* Variant of the control.
|
||||
* @default "handle"
|
||||
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line
|
||||
*/
|
||||
variant?: ResizeControlVariant;
|
||||
@@ -74,6 +102,9 @@ export type ResizeControlProps = Pick<
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ResizeControlLineProps = ResizeControlProps & {
|
||||
position?: ControlLinePosition;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,41 @@ const storeSelector = (state: ReactFlowState) => ({
|
||||
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
|
||||
});
|
||||
|
||||
/**
|
||||
* This component can render a toolbar or tooltip to one side of a custom node. This
|
||||
* toolbar doesn't scale with the viewport so that the content is always visible.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { memo } from 'react';
|
||||
*import { Handle, Position, NodeToolbar } from '@xyflow/react';
|
||||
*
|
||||
*function CustomNode({ data }) {
|
||||
* return (
|
||||
* <>
|
||||
* <NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
|
||||
* <button>delete</button>
|
||||
* <button>copy</button>
|
||||
* <button>expand</button>
|
||||
* </NodeToolbar>
|
||||
*
|
||||
* <div style={{ padding: '10px 20px' }}>
|
||||
* {data.label}
|
||||
* </div>
|
||||
*
|
||||
* <Handle type="target" position={Position.Left} />
|
||||
* <Handle type="source" position={Position.Right} />
|
||||
* </>
|
||||
* );
|
||||
*};
|
||||
*
|
||||
*export default memo(CustomNode);
|
||||
*```
|
||||
* @remarks By default, the toolbar is only visible when a node is selected. If multiple
|
||||
* nodes are selected it will not be visible to prevent overlapping toolbars or
|
||||
* clutter. You can override this behavior by setting the `isVisible` prop to `true`.
|
||||
*/
|
||||
export function NodeToolbar({
|
||||
nodeId,
|
||||
children,
|
||||
@@ -74,7 +109,7 @@ export function NodeToolbar({
|
||||
const isActive =
|
||||
typeof isVisible === 'boolean'
|
||||
? isVisible
|
||||
: nodes.size === 1 && nodes.values().next().value.selected && selectedNodesCount === 1;
|
||||
: nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1;
|
||||
|
||||
if (!isActive || !nodes.size) {
|
||||
return null;
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import type { Position, Align } from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
||||
/** Id of the node, or array of ids the toolbar should be displayed at */
|
||||
/**
|
||||
* By passing in an array of node id's you can render a single tooltip for a group or collection
|
||||
* of nodes.
|
||||
*/
|
||||
nodeId?: string | string[];
|
||||
/** If true, node toolbar is visible even if node is not selected */
|
||||
/** If `true`, node toolbar is visible even if node is not selected. */
|
||||
isVisible?: boolean;
|
||||
/** Position of the toolbar relative to the node
|
||||
* @example Position.TopLeft, Position.TopRight,
|
||||
* Position.BottomLeft, Position.BottomRight
|
||||
/**
|
||||
* Position of the toolbar relative to the node.
|
||||
* @default Position.Top
|
||||
* @example Position.TopLeft, Position.TopRight, Position.BottomLeft, Position.BottomRight
|
||||
*/
|
||||
position?: Position;
|
||||
/** Offset the toolbar from the node */
|
||||
/**
|
||||
* The space between the node and the toolbar, measured in pixels.
|
||||
* @default 10
|
||||
*/
|
||||
offset?: number;
|
||||
/** Align the toolbar relative to the node
|
||||
/**
|
||||
* Align the toolbar relative to the node.
|
||||
* @default "center"
|
||||
* @example Align.Start, Align.Center, Align.End
|
||||
*/
|
||||
align?: Align;
|
||||
|
||||
Reference in New Issue
Block a user