From 482b618f49f018c0bdc3a8626cf862afdb3a9dc3 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 9 Oct 2025 16:00:06 +0200 Subject: [PATCH 01/15] initial implementation of edge toolbar --- examples/react/src/App/routes.ts | 6 ++ .../src/examples/EdgeToolbar/CustomEdge.tsx | 17 ++++ .../EdgeToolbar/SelectedNodesToolbar.tsx | 15 ++++ .../react/src/examples/EdgeToolbar/index.tsx | 86 +++++++++++++++++++ .../EdgeToolbar/EdgeToolbar.tsx | 86 +++++++++++++++++++ .../EdgeToolbar/index.tsx | 2 + .../EdgeToolbar/types.ts | 41 +++++++++ .../react/src/additional-components/index.ts | 1 + packages/system/src/utils/edge-toolbar.ts | 6 ++ packages/system/src/utils/index.ts | 1 + 10 files changed, 261 insertions(+) create mode 100644 examples/react/src/examples/EdgeToolbar/CustomEdge.tsx create mode 100644 examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx create mode 100644 examples/react/src/examples/EdgeToolbar/index.tsx create mode 100644 packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx create mode 100644 packages/react/src/additional-components/EdgeToolbar/index.tsx create mode 100644 packages/react/src/additional-components/EdgeToolbar/types.ts create mode 100644 packages/system/src/utils/edge-toolbar.ts diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 52f80979..886eecbd 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -50,6 +50,7 @@ import CancelConnection from '../examples/CancelConnection'; import InteractiveMinimap from '../examples/InteractiveMinimap'; import UseOnSelectionChange from '../examples/UseOnSelectionChange'; import NodeToolbar from '../examples/NodeToolbar'; +import EdgeToolbar from '../examples/EdgeToolbar'; import UseConnection from '../examples/UseConnection'; import UseNodesInitialized from '../examples/UseNodesInit'; import UseNodesData from '../examples/UseNodesData'; @@ -192,6 +193,11 @@ const routes: IRoute[] = [ path: 'edge-routing', component: EdgeRouting, }, + { + name: 'Edge Toolbar', + path: 'edge-toolbar', + component: EdgeToolbar, + }, { name: 'Empty', path: 'empty', diff --git a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx new file mode 100644 index 00000000..c3e63e83 --- /dev/null +++ b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx @@ -0,0 +1,17 @@ +import { EdgeLabelRenderer, getBezierPath, BaseEdge, EdgeProps } from '@xyflow/react'; + +import { EdgeToolbar } from '@xyflow/react'; + +export function CustomEdge({ id, data, ...props }: EdgeProps) { + const [edgePath, labelX, labelY] = getBezierPath(props); + + return ( + <> + + + + + + + ); +} diff --git a/examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx b/examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx new file mode 100644 index 00000000..4996a45a --- /dev/null +++ b/examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx @@ -0,0 +1,15 @@ +import { NodeToolbar, ReactFlowState, useStore } from '@xyflow/react'; + +const selectedNodesSelector = (state: ReactFlowState) => + state.nodes.filter((node) => node.selected).map((node) => node.id); + +export default function SelectedNodesToolbar() { + const selectedNodeIds = useStore(selectedNodesSelector); + const isVisible = selectedNodeIds.length > 1; + + return ( + + + + ); +} diff --git a/examples/react/src/examples/EdgeToolbar/index.tsx b/examples/react/src/examples/EdgeToolbar/index.tsx new file mode 100644 index 00000000..57faef24 --- /dev/null +++ b/examples/react/src/examples/EdgeToolbar/index.tsx @@ -0,0 +1,86 @@ +import { + ReactFlow, + MiniMap, + Background, + BackgroundVariant, + Controls, + Node, + Edge, + NodeTypes, + Position, + NodeOrigin, + EdgeTypes, +} from '@xyflow/react'; + +import { CustomEdge } from './CustomEdge'; +import SelectedNodesToolbar from './SelectedNodesToolbar'; + +const edgeTypes: EdgeTypes = { + custom: CustomEdge, +}; + +const initialNodes: Node[] = [ + { + id: '1', + data: { label: 'Node 1', toolbarPosition: Position.Top }, + position: { x: 0, y: 0 }, + className: 'react-flow__node-default', + }, + { + id: '2', + data: { label: 'Node 2', toolbarPosition: Position.Top }, + position: { x: 200, y: 0 }, + className: 'react-flow__node-default', + }, + { + id: '3', + data: { label: 'Node 3', toolbarPosition: Position.Top }, + position: { x: 100, y: 150 }, + className: 'react-flow__node-default', + }, +]; + +const initialEdges: Edge[] = [ + { + id: 'e1-2', + source: '1', + target: '2', + type: 'custom', + }, + { + id: 'e2-3', + source: '2', + target: '3', + type: 'custom', + }, + { + id: 'e3-1', + source: '3', + target: '1', + type: 'custom', + }, +]; + +const defaultEdgeOptions = { zIndex: 0 }; +const nodeOrigin: NodeOrigin = [0.5, 0.5]; + +export default function EdgeToolbarExample() { + return ( + + + + + + + ); +} diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx new file mode 100644 index 00000000..6292eeea --- /dev/null +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -0,0 +1,86 @@ +import { getEdgeToolbarTransform, Position } from '@xyflow/system'; +import cc from 'classcat'; +import { CSSProperties, useCallback } from 'react'; +import { shallow } from 'zustand/shallow'; + +import { EdgeLabelRenderer } from '../../components/EdgeLabelRenderer'; +import { useStore } from '../../hooks/useStore'; +import { Edge, ReactFlowState } from '../../types'; +import type { EdgeToolbarProps } from './types'; + +const storeSelector = (state: ReactFlowState) => ({ + x: state.transform[0], + y: state.transform[1], + zoom: state.transform[2], +}); + +export function EdgeToolbar({ + edgeId, + labelX, + labelY, + children, + className, + style, + isVisible, + position = Position.Top, + offset = 10, + align = '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); + console.log('edge', edge, edgeId); + + const { x, y, zoom } = useStore(storeSelector, shallow); + + // if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected + const isActive = typeof isVisible === 'boolean' ? isVisible : edge?.selected; + + if (!isActive || !edge) { + return null; + } + + // TODO: how to get the bounds of an edge? + // const edgeRect = getInternalEdgesBounds(edge.source, edge.target); + // TODO: how to get the z-index of an edge? + const zIndex = edge.zIndex ?? 0 + 1; + + const transform = getEdgeToolbarTransform(labelX, labelY, { x, y, zoom }); + console.log('transform', transform); + const wrapperStyle: CSSProperties = { + position: 'absolute', + // TODO: offset + // transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, + transform, + zIndex, + ...style, + }; + + console.log('wrapperStyle', wrapperStyle); + + return ( + +
+ aaa + {children} +
+
+ ); +} diff --git a/packages/react/src/additional-components/EdgeToolbar/index.tsx b/packages/react/src/additional-components/EdgeToolbar/index.tsx new file mode 100644 index 00000000..5e1bc931 --- /dev/null +++ b/packages/react/src/additional-components/EdgeToolbar/index.tsx @@ -0,0 +1,2 @@ +export { EdgeToolbar } from './EdgeToolbar'; +export type { EdgeToolbarProps } from './types'; diff --git a/packages/react/src/additional-components/EdgeToolbar/types.ts b/packages/react/src/additional-components/EdgeToolbar/types.ts new file mode 100644 index 00000000..96261694 --- /dev/null +++ b/packages/react/src/additional-components/EdgeToolbar/types.ts @@ -0,0 +1,41 @@ +import type { HTMLAttributes } from 'react'; +import type { Position, Align } from '@xyflow/system'; + +/** + * @expand + */ +export type EdgeToolbarProps = HTMLAttributes & { + /** + * By passing in an array of edge id's you can render a single tooltip for a group or collection + * of edges. + */ + 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. + */ + labelX: number; + /** + * The `y` position of the edge label. + */ + labelY: number; +}; diff --git a/packages/react/src/additional-components/index.ts b/packages/react/src/additional-components/index.ts index 94a1595e..787303a5 100644 --- a/packages/react/src/additional-components/index.ts +++ b/packages/react/src/additional-components/index.ts @@ -3,3 +3,4 @@ export * from './Controls'; export * from './MiniMap'; export * from './NodeResizer'; export * from './NodeToolbar'; +export * from './EdgeToolbar'; diff --git a/packages/system/src/utils/edge-toolbar.ts b/packages/system/src/utils/edge-toolbar.ts new file mode 100644 index 00000000..3fe2c5c2 --- /dev/null +++ b/packages/system/src/utils/edge-toolbar.ts @@ -0,0 +1,6 @@ +import { Position, type Viewport, type Align } from '..'; + +export function getEdgeToolbarTransform(labelX: number, labelY: number, viewport: Viewport): string { + // Position the toolbar at the edge label center (scaling is handled by EdgeLabelRenderer) + return `translate(${labelX}px, ${labelY}px)`; +} diff --git a/packages/system/src/utils/index.ts b/packages/system/src/utils/index.ts index 2640195d..caf8b026 100644 --- a/packages/system/src/utils/index.ts +++ b/packages/system/src/utils/index.ts @@ -5,6 +5,7 @@ export * from './graph'; export * from './general'; export * from './marker'; export * from './node-toolbar'; +export * from './edge-toolbar'; export * from './store'; export * from './types'; export * from './shallow-node-data'; From bb9fe4f830ea89b7386017234745869390f7153a Mon Sep 17 00:00:00 2001 From: Alessandro Date: Mon, 13 Oct 2025 13:26:04 +0200 Subject: [PATCH 02/15] Correct positioning --- .../react/src/examples/EdgeToolbar/CustomEdge.tsx | 12 ++++++++---- .../examples/EdgeToolbar/SelectedNodesToolbar.tsx | 15 --------------- examples/react/src/examples/EdgeToolbar/index.tsx | 2 -- .../EdgeToolbar/EdgeToolbar.tsx | 13 ++++++------- .../additional-components/EdgeToolbar/types.ts | 12 ++++++++++-- packages/system/src/utils/edge-toolbar.ts | 12 +++++++++--- 6 files changed, 33 insertions(+), 33 deletions(-) delete mode 100644 examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx diff --git a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx index c3e63e83..c6429f17 100644 --- a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx +++ b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx @@ -1,16 +1,20 @@ -import { EdgeLabelRenderer, getBezierPath, BaseEdge, EdgeProps } from '@xyflow/react'; +import { getBezierPath, BaseEdge, EdgeProps, useReactFlow } from '@xyflow/react'; import { EdgeToolbar } from '@xyflow/react'; export function CustomEdge({ id, data, ...props }: EdgeProps) { const [edgePath, labelX, labelY] = getBezierPath(props); + const { setEdges } = useReactFlow(); + + const deleteEdge = () => { + setEdges((edges) => edges.filter((edge) => edge.id !== id)); + }; return ( <> - - - + + ); diff --git a/examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx b/examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx deleted file mode 100644 index 4996a45a..00000000 --- a/examples/react/src/examples/EdgeToolbar/SelectedNodesToolbar.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { NodeToolbar, ReactFlowState, useStore } from '@xyflow/react'; - -const selectedNodesSelector = (state: ReactFlowState) => - state.nodes.filter((node) => node.selected).map((node) => node.id); - -export default function SelectedNodesToolbar() { - const selectedNodeIds = useStore(selectedNodesSelector); - const isVisible = selectedNodeIds.length > 1; - - return ( - - - - ); -} diff --git a/examples/react/src/examples/EdgeToolbar/index.tsx b/examples/react/src/examples/EdgeToolbar/index.tsx index 57faef24..4470fe33 100644 --- a/examples/react/src/examples/EdgeToolbar/index.tsx +++ b/examples/react/src/examples/EdgeToolbar/index.tsx @@ -13,7 +13,6 @@ import { } from '@xyflow/react'; import { CustomEdge } from './CustomEdge'; -import SelectedNodesToolbar from './SelectedNodesToolbar'; const edgeTypes: EdgeTypes = { custom: CustomEdge, @@ -80,7 +79,6 @@ export default function EdgeToolbarExample() { - ); } diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index 6292eeea..9228fa3d 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -16,14 +16,15 @@ const storeSelector = (state: ReactFlowState) => ({ export function EdgeToolbar({ edgeId, - labelX, - labelY, + x, + y, children, className, style, isVisible, position = Position.Top, - offset = 10, + offsetX = 0, + offsetY = 0, align = 'center', ...rest }: EdgeToolbarProps) { @@ -56,12 +57,11 @@ export function EdgeToolbar({ // TODO: how to get the z-index of an edge? const zIndex = edge.zIndex ?? 0 + 1; - const transform = getEdgeToolbarTransform(labelX, labelY, { x, y, zoom }); + const transform = getEdgeToolbarTransform(x, y, { x, y, zoom }, 120, offsetY); console.log('transform', transform); const wrapperStyle: CSSProperties = { position: 'absolute', // TODO: offset - // transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, transform, zIndex, ...style, @@ -72,13 +72,12 @@ export function EdgeToolbar({ return (
- aaa {children}
diff --git a/packages/react/src/additional-components/EdgeToolbar/types.ts b/packages/react/src/additional-components/EdgeToolbar/types.ts index 96261694..651a27c8 100644 --- a/packages/react/src/additional-components/EdgeToolbar/types.ts +++ b/packages/react/src/additional-components/EdgeToolbar/types.ts @@ -33,9 +33,17 @@ export type EdgeToolbarProps = HTMLAttributes & { /** * The `x` position of the edge label. */ - labelX: number; + x: number; /** * The `y` position of the edge label. */ - labelY: number; + 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; }; diff --git a/packages/system/src/utils/edge-toolbar.ts b/packages/system/src/utils/edge-toolbar.ts index 3fe2c5c2..84ef5a5c 100644 --- a/packages/system/src/utils/edge-toolbar.ts +++ b/packages/system/src/utils/edge-toolbar.ts @@ -1,6 +1,12 @@ -import { Position, type Viewport, type Align } from '..'; +import { type Viewport } from '..'; -export function getEdgeToolbarTransform(labelX: number, labelY: number, viewport: Viewport): string { +export function getEdgeToolbarTransform( + labelX: number, + labelY: number, + viewport: Viewport, + offsetX: number = 0, + offsetY: number = 0 +): string { // Position the toolbar at the edge label center (scaling is handled by EdgeLabelRenderer) - return `translate(${labelX}px, ${labelY}px)`; + return `translate(${labelX + offsetX}px, ${labelY + offsetY}px) translate(-50%, -50%) scale(${1 / viewport.zoom})`; } From 3204173b2c64ae654fece7a51d80a8cb928de205 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Mon, 13 Oct 2025 16:10:47 +0200 Subject: [PATCH 03/15] add svelte version --- .../src/examples/EdgeToolbar/CustomEdge.tsx | 3 +- .../react/src/examples/EdgeToolbar/index.tsx | 31 +++++----- .../src/components/Header/Header.svelte | 1 + .../routes/examples/edge-toolbar/+page.svelte | 62 +++++++++++++++++++ .../examples/edge-toolbar/CustomEdge.svelte | 26 ++++++++ .../EdgeToolbar/EdgeToolbar.tsx | 35 ++++++++--- .../EdgeToolbar/types.ts | 9 ++- packages/svelte/src/lib/index.ts | 1 + .../plugins/EdgeToolbar/EdgeToolbar.svelte | 52 ++++++++++++++++ .../src/lib/plugins/EdgeToolbar/index.ts | 2 + .../src/lib/plugins/EdgeToolbar/types.ts | 42 +++++++++++++ packages/system/src/utils/edge-toolbar.ts | 10 ++- 12 files changed, 236 insertions(+), 38 deletions(-) create mode 100644 examples/svelte/src/routes/examples/edge-toolbar/+page.svelte create mode 100644 examples/svelte/src/routes/examples/edge-toolbar/CustomEdge.svelte create mode 100644 packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte create mode 100644 packages/svelte/src/lib/plugins/EdgeToolbar/index.ts create mode 100644 packages/svelte/src/lib/plugins/EdgeToolbar/types.ts diff --git a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx index c6429f17..2d6b621a 100644 --- a/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx +++ b/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx @@ -1,5 +1,4 @@ import { getBezierPath, BaseEdge, EdgeProps, useReactFlow } from '@xyflow/react'; - import { EdgeToolbar } from '@xyflow/react'; export function CustomEdge({ id, data, ...props }: EdgeProps) { @@ -13,7 +12,7 @@ export function CustomEdge({ id, data, ...props }: EdgeProps) { return ( <> - + diff --git a/examples/react/src/examples/EdgeToolbar/index.tsx b/examples/react/src/examples/EdgeToolbar/index.tsx index 4470fe33..e69ccadb 100644 --- a/examples/react/src/examples/EdgeToolbar/index.tsx +++ b/examples/react/src/examples/EdgeToolbar/index.tsx @@ -1,15 +1,14 @@ import { - ReactFlow, - MiniMap, Background, BackgroundVariant, Controls, - Node, Edge, - NodeTypes, - Position, - NodeOrigin, EdgeTypes, + MiniMap, + Node, + NodeOrigin, + Position, + ReactFlow, } from '@xyflow/react'; import { CustomEdge } from './CustomEdge'; @@ -28,13 +27,13 @@ const initialNodes: Node[] = [ { id: '2', data: { label: 'Node 2', toolbarPosition: Position.Top }, - position: { x: 200, y: 0 }, + position: { x: 100, y: 150 }, className: 'react-flow__node-default', }, { id: '3', data: { label: 'Node 3', toolbarPosition: Position.Top }, - position: { x: 100, y: 150 }, + position: { x: 200, y: 0 }, className: 'react-flow__node-default', }, ]; @@ -47,20 +46,20 @@ const initialEdges: Edge[] = [ type: 'custom', }, { - id: 'e2-3', - source: '2', - target: '3', + id: 'e3-2', + source: '3', + target: '2', type: 'custom', }, { - id: 'e3-1', - source: '3', - target: '1', + id: 'e1-3', + source: '1', + target: '3', type: 'custom', }, ]; -const defaultEdgeOptions = { zIndex: 0 }; +// const defaultEdgeOptions = { zIndex: 0 }; const nodeOrigin: NodeOrigin = [0.5, 0.5]; export default function EdgeToolbarExample() { @@ -72,7 +71,7 @@ export default function EdgeToolbarExample() { minZoom={0.2} maxZoom={4} fitView - defaultEdgeOptions={defaultEdgeOptions} + // defaultEdgeOptions={defaultEdgeOptions} edgeTypes={edgeTypes} nodeOrigin={nodeOrigin} > diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte index 0ebe84e9..46ea8114 100644 --- a/examples/svelte/src/components/Header/Header.svelte +++ b/examples/svelte/src/components/Header/Header.svelte @@ -13,6 +13,7 @@ 'detached-handle', 'drag-n-drop', 'edges', + 'edge-toolbar', 'figma', 'handle-connect', 'interaction', diff --git a/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte b/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte new file mode 100644 index 00000000..c0591ff8 --- /dev/null +++ b/examples/svelte/src/routes/examples/edge-toolbar/+page.svelte @@ -0,0 +1,62 @@ + + +
+ + + +
diff --git a/examples/svelte/src/routes/examples/edge-toolbar/CustomEdge.svelte b/examples/svelte/src/routes/examples/edge-toolbar/CustomEdge.svelte new file mode 100644 index 00000000..24876c64 --- /dev/null +++ b/examples/svelte/src/routes/examples/edge-toolbar/CustomEdge.svelte @@ -0,0 +1,26 @@ + + + + + + diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index 9228fa3d..b7745b67 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -9,11 +9,32 @@ import { Edge, ReactFlowState } from '../../types'; import type { EdgeToolbarProps } from './types'; const storeSelector = (state: ReactFlowState) => ({ - x: state.transform[0], - y: state.transform[1], zoom: 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. + * + * @public + * @example + * ```jsx + * import { EdgeToolbar } from "@xyflow/react"; + * + * export function CustomEdge({ id, data, ...props }: EdgeProps) { + * const [edgePath, labelX, labelY] = getBezierPath(props); + * + * return ( + * <> + * + * + * + * + * + * ); + * } + * ``` + */ export function EdgeToolbar({ edgeId, x, @@ -41,9 +62,6 @@ export function EdgeToolbar({ ); const edge = useStore(edgeSelector); - console.log('edge', edge, edgeId); - - const { x, y, zoom } = useStore(storeSelector, shallow); // if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected const isActive = typeof isVisible === 'boolean' ? isVisible : edge?.selected; @@ -52,12 +70,11 @@ export function EdgeToolbar({ return null; } - // TODO: how to get the bounds of an edge? - // const edgeRect = getInternalEdgesBounds(edge.source, edge.target); - // TODO: how to get the z-index of an edge? const zIndex = edge.zIndex ?? 0 + 1; - const transform = getEdgeToolbarTransform(x, y, { x, y, zoom }, 120, offsetY); + const { zoom } = useStore(storeSelector, shallow); + + const transform = getEdgeToolbarTransform(x, y, zoom, offsetX, offsetY); console.log('transform', transform); const wrapperStyle: CSSProperties = { position: 'absolute', diff --git a/packages/react/src/additional-components/EdgeToolbar/types.ts b/packages/react/src/additional-components/EdgeToolbar/types.ts index 651a27c8..b78adb2b 100644 --- a/packages/react/src/additional-components/EdgeToolbar/types.ts +++ b/packages/react/src/additional-components/EdgeToolbar/types.ts @@ -6,10 +6,9 @@ import type { Position, Align } from '@xyflow/system'; */ export type EdgeToolbarProps = HTMLAttributes & { /** - * By passing in an array of edge id's you can render a single tooltip for a group or collection - * of edges. + * An edge toolbar must be attached to an edge. */ - edgeId?: string; + edgeId: string; /** If `true`, edge toolbar is visible even if edge is not selected. */ isVisible?: boolean; /** @@ -41,9 +40,9 @@ export type EdgeToolbarProps = HTMLAttributes & { /** * The `offsetX` position of the edge label relative to the edge in pixels. */ - offsetX: number; + offsetX?: number; /** * The `offsetY` position of the edge label relative to the edge in pixels. */ - offsetY: number; + offsetY?: number; }; diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 981d1442..815803f7 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -22,6 +22,7 @@ export * from '$lib/plugins/Controls'; export * from '$lib/plugins/Background'; export * from '$lib/plugins/Minimap'; export * from '$lib/plugins/NodeToolbar'; +export * from '$lib/plugins/EdgeToolbar'; export * from '$lib/plugins/NodeResizer'; // store diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte new file mode 100644 index 00000000..23a82a27 --- /dev/null +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte @@ -0,0 +1,52 @@ + + +{#if store.domNode && isActive} + +
+ {@render children?.()} +
+
+{/if} diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/index.ts b/packages/svelte/src/lib/plugins/EdgeToolbar/index.ts new file mode 100644 index 00000000..602e9a61 --- /dev/null +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/index.ts @@ -0,0 +1,2 @@ +export { default as EdgeToolbar } from './EdgeToolbar.svelte'; +export * from './types'; diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts b/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts new file mode 100644 index 00000000..cfbcbff7 --- /dev/null +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/types.ts @@ -0,0 +1,42 @@ +import type { Position, Align } 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; + children?: Snippet; +} & HTMLAttributes; diff --git a/packages/system/src/utils/edge-toolbar.ts b/packages/system/src/utils/edge-toolbar.ts index 84ef5a5c..75c694aa 100644 --- a/packages/system/src/utils/edge-toolbar.ts +++ b/packages/system/src/utils/edge-toolbar.ts @@ -1,12 +1,10 @@ -import { type Viewport } from '..'; - export function getEdgeToolbarTransform( - labelX: number, - labelY: number, - viewport: Viewport, + x: number, + y: number, + zoom: number, offsetX: number = 0, offsetY: number = 0 ): string { // Position the toolbar at the edge label center (scaling is handled by EdgeLabelRenderer) - return `translate(${labelX + offsetX}px, ${labelY + offsetY}px) translate(-50%, -50%) scale(${1 / viewport.zoom})`; + return `translate(${x + offsetX}px, ${y + offsetY}px) translate(-50%, -50%) scale(${1 / zoom})`; } From a94bdc1cd0131a2c596da7d71ffb7ac2f5f167fe Mon Sep 17 00:00:00 2001 From: Alessandro Cheli <17289614+0x0f0f0f@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:16:40 +0200 Subject: [PATCH 04/15] Update packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index b7745b67..632f4b9f 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -93,7 +93,7 @@ export function EdgeToolbar({ className={cc(['react-flow__edge-toolbar', className])} {...rest} // @todo: check if we could only do this for non-prod envs - data-id={`${edge.id} `} + data-id={edge.id} > {children} From 869e5a32d53aeac48449928453b0d8c92220aed4 Mon Sep 17 00:00:00 2001 From: Alessandro Cheli <17289614+0x0f0f0f@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:16:51 +0200 Subject: [PATCH 05/15] Update packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index 632f4b9f..c8a757df 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -84,7 +84,6 @@ export function EdgeToolbar({ ...style, }; - console.log('wrapperStyle', wrapperStyle); return ( From 883667c1328faa45b88eea2726f82af7cd0c0a6b Mon Sep 17 00:00:00 2001 From: Alessandro Cheli <17289614+0x0f0f0f@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:17:03 +0200 Subject: [PATCH 06/15] Update packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index c8a757df..edc78bf3 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -70,7 +70,7 @@ export function EdgeToolbar({ return null; } - const zIndex = edge.zIndex ?? 0 + 1; + const zIndex = (edge.zIndex ?? 0) + 1; const { zoom } = useStore(storeSelector, shallow); From 10cbaac46d5b7126bed7999ed7e725dd2fabd0e1 Mon Sep 17 00:00:00 2001 From: Alessandro Cheli <17289614+0x0f0f0f@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:17:10 +0200 Subject: [PATCH 07/15] Update packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte index 23a82a27..39202b9f 100644 --- a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte @@ -1,5 +1,5 @@ -
- - - -
+ + + 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 + )}%)`; } From 391d6f04eaee32babfc60d618cb5e4cb7de7aa63 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 20 Oct 2025 12:50:41 +0200 Subject: [PATCH 10/15] chore(edge-toolbar): cleanup --- .../src/additional-components/EdgeToolbar/EdgeToolbar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx index e71594f5..9f097741 100644 --- a/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx +++ b/packages/react/src/additional-components/EdgeToolbar/EdgeToolbar.tsx @@ -50,11 +50,11 @@ export function EdgeToolbar({ const isActive = typeof isVisible === 'boolean' ? isVisible : edge?.selected; const zoom = useStore(zoomSelector); - if (!isActive || !edge) { + if (!isActive) { return null; } - const zIndex = (edge.zIndex ?? 0) + 1; + const zIndex = (edge?.zIndex ?? 0) + 1; const transform = getEdgeToolbarTransform(x, y, zoom, alignX, alignY); return ( @@ -69,7 +69,7 @@ export function EdgeToolbar({ ...style, }} className={cc(['react-flow__edge-toolbar', className])} - data-id={edge.id} + data-id={edge?.id ?? ''} {...rest} > {children} From c17b49f4c16167da3f791430163edd592159d27d Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 20 Oct 2025 12:51:08 +0200 Subject: [PATCH 11/15] chore(changeset): add --- .changeset/brave-plums-work.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/brave-plums-work.md diff --git a/.changeset/brave-plums-work.md b/.changeset/brave-plums-work.md new file mode 100644 index 00000000..99516140 --- /dev/null +++ b/.changeset/brave-plums-work.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Add EdgeToolbar component From 98ff26312b1e1d4db3a18cc70f2bcc449a86b630 Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Mon, 20 Oct 2025 12:51:29 +0200 Subject: [PATCH 12/15] Update brave-plums-work.md --- .changeset/brave-plums-work.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/brave-plums-work.md b/.changeset/brave-plums-work.md index 99516140..8d6cd559 100644 --- a/.changeset/brave-plums-work.md +++ b/.changeset/brave-plums-work.md @@ -1,6 +1,6 @@ --- -'@xyflow/react': patch -'@xyflow/svelte': patch +'@xyflow/react': minor +'@xyflow/svelte': minor '@xyflow/system': patch --- From c7e103d27f57b236af1aedcbb764942a71228fcf Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 20 Oct 2025 12:54:56 +0200 Subject: [PATCH 13/15] chore(edge-toolbar): cleanup --- .../svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte index fd3723de..290878a4 100644 --- a/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte +++ b/packages/svelte/src/lib/plugins/EdgeToolbar/EdgeToolbar.svelte @@ -17,10 +17,10 @@ }: EdgeToolbarProps = $props(); const store = useStore(); - const edge = store.edgeLookup.get(edgeId); + const edge = $derived(store.edgeLookup.get(edgeId)); const isActive = $derived(typeof isVisible === 'boolean' ? isVisible : edge?.selected); const transform = $derived(getEdgeToolbarTransform(x, y, store.viewport.zoom, alignX, alignY)); - const zIndex = (edge?.zIndex ?? 0) + 1; + const zIndex = $derived((edge?.zIndex ?? 0) + 1); {#if store.domNode && isActive} From 35a1864ed9122334219fe1339125ac48ea0d1ddf Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 20 Oct 2025 12:56:05 +0200 Subject: [PATCH 14/15] chore(edge-toolbar): cleanup --- packages/system/src/types/edges.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index cfd82c67..20e43eda 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -148,13 +148,13 @@ export type EdgeToolbarBaseProps = { */ isVisible?: boolean; /** - * Align the toolbar x relative to the edge. + * Align the vertical toolbar position relative to the passed x position. * @default "center" * @example 'left', 'center', 'right' */ alignX?: 'left' | 'center' | 'right'; /** - * Align the toolbar x relative to the edge. + * Align the horizontal toolbar position relative to the passed y position. * @default "center" * @example 'top', 'center', 'bottom' */ From 47f1e86cbe754d4e18c6c13e4c06532aa7ff3956 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 20 Oct 2025 12:57:16 +0200 Subject: [PATCH 15/15] chore(edge-toolbar): cleanup --- packages/system/src/utils/edge-toolbar.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/system/src/utils/edge-toolbar.ts b/packages/system/src/utils/edge-toolbar.ts index d1245663..512e318f 100644 --- a/packages/system/src/utils/edge-toolbar.ts +++ b/packages/system/src/utils/edge-toolbar.ts @@ -17,7 +17,6 @@ export function getEdgeToolbarTransform( 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}px, ${y}px) scale(${1 / zoom}) translate(${-(alignXToPercent[alignX] ?? 50)}%, ${-( alignYToPercent[alignY] ?? 50 )}%)`;