diff --git a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx index 2d6b621a..daac31f3 100644 --- a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx +++ b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx @@ -1,19 +1,39 @@ -import { getBezierPath, BaseEdge, EdgeProps, useReactFlow } from '@xyflow/react'; +import { getBezierPath, BaseEdge, EdgeProps, useReactFlow, getStraightPath, getSmoothStepPath } from '@xyflow/react'; import { EdgeToolbar } from '@xyflow/react'; -export function CustomEdge({ id, data, ...props }: EdgeProps) { - const [edgePath, labelX, labelY] = getBezierPath(props); +const getPath = (props: EdgeProps) => { + switch (props.data!.type) { + case 'smoothstep': + return getSmoothStepPath(props); + case 'straight': + return getStraightPath(props); + default: + return getBezierPath(props); + } +}; + +export function CustomEdge(props: EdgeProps) { + const [edgePath, centerX, centerY] = getPath(props); const { setEdges } = useReactFlow(); const deleteEdge = () => { - setEdges((edges) => edges.filter((edge) => edge.id !== id)); + setEdges((edges) => edges.filter((edge) => edge.id !== props.id)); }; return ( <> - - - + + + ); diff --git a/examples/react/src/examples/EdgeToolbar/index.tsx b/examples/react/src/examples/EdgeToolbar/index.tsx index e69ccadb..31d37ffa 100644 --- a/examples/react/src/examples/EdgeToolbar/index.tsx +++ b/examples/react/src/examples/EdgeToolbar/index.tsx @@ -6,7 +6,6 @@ import { EdgeTypes, MiniMap, Node, - NodeOrigin, Position, ReactFlow, } from '@xyflow/react'; @@ -44,24 +43,24 @@ const initialEdges: Edge[] = [ source: '1', target: '2', type: 'custom', + data: { type: 'smoothstep', align: ['left', 'bottom'] }, }, { id: 'e3-2', source: '3', target: '2', type: 'custom', + data: { type: 'bezier', align: ['right', 'bottom'] }, }, { id: 'e1-3', source: '1', target: '3', type: 'custom', + data: { type: 'straight', align: ['center', 'center'] }, }, ]; -// const defaultEdgeOptions = { zIndex: 0 }; -const nodeOrigin: NodeOrigin = [0.5, 0.5]; - export default function EdgeToolbarExample() { return ( diff --git a/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte b/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte index c0591ff8..40240013 100644 --- a/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte +++ b/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte @@ -55,8 +55,6 @@ let edges = $state.raw(initialEdges); -
- - - -
+ + + diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index edc78bf3..e71594f5 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -1,34 +1,32 @@ -import { getEdgeToolbarTransform, Position } from '@xyflow/system'; +import { useCallback } from 'react'; import cc from 'classcat'; -import { CSSProperties, useCallback } from 'react'; import { shallow } from 'zustand/shallow'; +import { getEdgeToolbarTransform } from '@xyflow/system'; import { EdgeLabelRenderer } from '../../components/EdgeLabelRenderer'; import { useStore } from '../../hooks/useStore'; import { Edge, ReactFlowState } from '../../types'; import type { EdgeToolbarProps } from './types'; -const storeSelector = (state: ReactFlowState) => ({ - zoom: state.transform[2], -}); +const zoomSelector = (state: ReactFlowState) => state.transform[2]; /** * This component can render a toolbar or tooltip to one side of a custom edge. This - * toolbar doesn't scale with the viewport so that the content is always visible. + * toolbar doesn't scale with the viewport so that the content stays the same size. * * @public * @example * ```jsx - * import { EdgeToolbar } from "@xyflow/react"; + * import { EdgeToolbar, BaseEdge, getBezierPath, type EdgeProps } from "@xyflow/react"; * * export function CustomEdge({ id, data, ...props }: EdgeProps) { - * const [edgePath, labelX, labelY] = getBezierPath(props); + * const [edgePath, centerX, centerY] = getBezierPath(props); * * return ( * <> * - * - * + * + * * * * ); @@ -43,56 +41,36 @@ export function EdgeToolbar({ className, style, isVisible, - position = Position.Top, - offsetX = 0, - offsetY = 0, - align = 'center', + alignX = 'center', + alignY = 'center', ...rest }: EdgeToolbarProps) { - const edgeSelector = useCallback( - (state: ReactFlowState): Edge | undefined => { - const edge = state.edgeLookup.get(edgeId || ''); - if (edge) { - return edge; - } - - return undefined; - }, - [edgeId] - ); - - const edge = useStore(edgeSelector); - - // if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected + const edgeSelector = useCallback((state: ReactFlowState): Edge | undefined => state.edgeLookup.get(edgeId), [edgeId]); + const edge = useStore(edgeSelector, shallow); const isActive = typeof isVisible === 'boolean' ? isVisible : edge?.selected; + const zoom = useStore(zoomSelector); if (!isActive || !edge) { return null; } const zIndex = (edge.zIndex ?? 0) + 1; - - const { zoom } = useStore(storeSelector, shallow); - - const transform = getEdgeToolbarTransform(x, y, zoom, offsetX, offsetY); - console.log('transform', transform); - const wrapperStyle: CSSProperties = { - position: 'absolute', - // TODO: offset - transform, - zIndex, - ...style, - }; - + const transform = getEdgeToolbarTransform(x, y, zoom, alignX, alignY); return (
{children}
diff --git a/packages/react/src/additional-components/EdgeToolbar/types.ts b/packages/react/src/additional-components/EdgeToolbar/types.ts index b78adb2b..fb7bdfae 100644 --- a/packages/react/src/additional-components/EdgeToolbar/types.ts +++ b/packages/react/src/additional-components/EdgeToolbar/types.ts @@ -1,48 +1,10 @@ -import type { HTMLAttributes } from 'react'; -import type { Position, Align } from '@xyflow/system'; +import type { HTMLAttributes, ReactNode } from 'react'; +import type { EdgeToolbarBaseProps } from '@xyflow/system'; /** - * @expand + * @inline */ -export type EdgeToolbarProps = HTMLAttributes & { - /** - * An edge toolbar must be attached to an edge. - */ - edgeId: string; - /** If `true`, edge toolbar is visible even if edge is not selected. */ - isVisible?: boolean; - /** - * TODO: What and how?? Needed? - * Position of the toolbar relative to the edge. - * @default Position.Top - * @example Position.TopLeft, Position.TopRight, Position.BottomLeft, Position.BottomRight - */ - position?: Position; - /** - * The space between the edge and the toolbar, measured in pixels. - * @default 10 - */ - offset?: number; - /** - * Align the toolbar relative to the edge. - * @default "center" - * @example Align.Start, Align.Center, Align.End - */ - align?: Align; - /** - * The `x` position of the edge label. - */ - x: number; - /** - * The `y` position of the edge label. - */ - y: number; - /** - * The `offsetX` position of the edge label relative to the edge in pixels. - */ - offsetX?: number; - /** - * The `offsetY` position of the edge label relative to the edge in pixels. - */ - offsetY?: number; -}; +export type EdgeToolbarProps = EdgeToolbarBaseProps & + HTMLAttributes & { + children?: ReactNode; + }; diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte index 6579d146..fd3723de 100644 --- a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte @@ -1,38 +1,26 @@ {#if store.domNode && isActive} @@ -41,6 +29,7 @@ style:position="absolute" style:transform style:z-index={zIndex} + style:transform-origin="0 0" {...rest} class="svelte-flow__edge-toolbar" data-id={edgeId ?? ''} diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts b/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts index cfbcbff7..b1a63938 100644 --- a/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts @@ -1,42 +1,7 @@ -import type { Position, Align } from '@xyflow/system'; +import type { EdgeToolbarBaseProps } from '@xyflow/system'; import type { Snippet } from 'svelte'; import type { HTMLAttributes } from 'svelte/elements'; -export type EdgeToolbarProps = { - /** - * An edge toolbar must be attached to an edge. - */ - edgeId: string; - /** If `true`, edge toolbar is visible even if edge is not selected. */ - isVisible?: boolean; - /** - * TODO: What and how?? Needed? - * Position of the toolbar relative to the edge. - * @default Position.Top - * @example Position.TopLeft, Position.TopRight, Position.BottomLeft, Position.BottomRight - */ - position?: Position; - /** - * Align the toolbar relative to the edge. - * @default "center" - * @example Align.Start, Align.Center, Align.End - */ - align?: Align; - /** - * The `x` position of the edge label. - */ - x: number; - /** - * The `y` position of the edge label. - */ - y: number; - /** - * The `offsetX` position of the edge label relative to the edge in pixels. - */ - offsetX?: number; - /** - * The `offsetY` position of the edge label relative to the edge in pixels. - */ - offsetY?: number; +export type EdgeToolbarProps = EdgeToolbarBaseProps & { children?: Snippet; } & HTMLAttributes; diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 42678f8c..cfd82c67 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -129,3 +129,34 @@ export type EdgePosition = { }; export type EdgeLookup = Map; + +export type EdgeToolbarBaseProps = { + /** + * An edge toolbar must be attached to an edge. + */ + edgeId: string; + /** + * The `x` position of the edge label. + */ + x: number; + /** + * The `y` position of the edge label. + */ + y: number; + /** If `true`, edge toolbar is visible even if edge is not selected. + * @default false + */ + isVisible?: boolean; + /** + * Align the toolbar x relative to the edge. + * @default "center" + * @example 'left', 'center', 'right' + */ + alignX?: 'left' | 'center' | 'right'; + /** + * Align the toolbar x relative to the edge. + * @default "center" + * @example 'top', 'center', 'bottom' + */ + alignY?: 'top' | 'center' | 'bottom'; +}; diff --git a/packages/system/src/utils/edge-toolbar.ts b/packages/system/src/utils/edge-toolbar.ts index 75c694aa..d1245663 100644 --- a/packages/system/src/utils/edge-toolbar.ts +++ b/packages/system/src/utils/edge-toolbar.ts @@ -1,10 +1,24 @@ +const alignXToPercent: Record<'left' | 'center' | 'right', number> = { + left: 0, + center: 50, + right: 100, +}; + +const alignYToPercent: Record<'top' | 'center' | 'bottom', number> = { + top: 0, + center: 50, + bottom: 100, +}; + export function getEdgeToolbarTransform( x: number, y: number, zoom: number, - offsetX: number = 0, - offsetY: number = 0 + alignX: 'left' | 'center' | 'right' = 'center', + alignY: 'top' | 'center' | 'bottom' = 'center' ): string { // Position the toolbar at the edge label center (scaling is handled by EdgeLabelRenderer) - return `translate(${x + offsetX}px, ${y + offsetY}px) translate(-50%, -50%) scale(${1 / zoom})`; + return `translate(${x}px, ${y}px) scale(${1 / zoom}) translate(${-(alignXToPercent[alignX] ?? 50)}%, ${-( + alignYToPercent[alignY] ?? 50 + )}%)`; }