diff --git a/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx b/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx index e1204169..cdd55a4b 100644 --- a/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx +++ b/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx @@ -12,7 +12,6 @@ import { Node, Edge, } from '@xyflow/react'; -import '@xyflow/react/dist/style.css'; const initialNodes: Node[] = [ { diff --git a/examples/react/src/examples/Redux/index.tsx b/examples/react/src/examples/Redux/index.tsx index 19bbd90c..ba706cd5 100644 --- a/examples/react/src/examples/Redux/index.tsx +++ b/examples/react/src/examples/Redux/index.tsx @@ -1,5 +1,4 @@ import { ReactFlow } from '@xyflow/react'; -import '@xyflow/react/dist/style.css'; import { useDispatch, useSelector, Provider } from 'react-redux'; diff --git a/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx b/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx index 4301e647..4b8fd2c1 100644 --- a/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx +++ b/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx @@ -1,7 +1,7 @@ import { memo, FC, useEffect, useCallback } from 'react'; -import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleComponentProps } from '@xyflow/react'; +import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleProps } from '@xyflow/react'; -function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) { +function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) { const onConnect = useCallback( (connections: Connection[]) => console.log('onConnect handler, node id:', nodeId, connections), [nodeId] diff --git a/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx b/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx index 6163f108..1c13dcb0 100644 --- a/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx +++ b/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx @@ -1,7 +1,7 @@ import { memo, FC, useEffect, useCallback } from 'react'; -import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleComponentProps } from '@xyflow/react'; +import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleProps } from '@xyflow/react'; -function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) { +function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) { const onConnect = useCallback( (connections: Connection[]) => { console.log('onConnect handler, node id:', nodeId, connections); diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 5bbe2a56..fbfcb489 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @xyflow/react +## 12.0.0-next.18 + +- don't show nodeTypes warning if not necessary you've created a new nodeTypes or edgeTypes +- add node resizer styles to base.css +- remove `HandleComponentProps` type, only export `HandleProps` type +- add warning when styles not loaded + ## 12.0.0-next.17 - fix broken `defaultNodes` @@ -9,6 +16,7 @@ - return user node in node event handlers - cleanup `useReactFlow` - export `KeyCode` and `Align` type +- remove `Instance` in favour of `ReactFlowInstance` type ## 12.0.0-next.16 diff --git a/packages/react/package.json b/packages/react/package.json index 1cfc0aec..c32b3309 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.0.0-next.17", + "version": "12.0.0-next.18", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 977a5c5b..760733a0 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -14,10 +14,11 @@ import { getHostForElement, isMouseEvent, addEdge, - type HandleProps, + type HandleProps as HandlePropsSystem, type Connection, type HandleType, ConnectionMode, + OnConnect, } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; @@ -25,7 +26,10 @@ import { useNodeId } from '../../contexts/NodeIdContext'; import { type ReactFlowState } from '../../types'; import { fixedForwardRef } from '../../utils'; -export interface HandleComponentProps extends HandleProps, Omit, 'id'> {} +export interface HandleProps extends HandlePropsSystem, Omit, 'id'> { + /** Callback called when connection is made */ + onConnect?: OnConnect; +} const selector = (s: ReactFlowState) => ({ connectOnClick: s.connectOnClick, @@ -75,7 +79,7 @@ function HandleComponent( onMouseDown, onTouchStart, ...rest - }: HandleComponentProps, + }: HandleProps, ref: ForwardedRef ) { const handleId = id || null; diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index 3edc380a..a2663851 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -9,6 +9,7 @@ import { useViewportSync } from '../../hooks/useViewportSync'; import { ConnectionLineWrapper } from '../../components/ConnectionLine'; import { useNodeOrEdgeTypesWarning } from './useNodeOrEdgeTypesWarning'; import type { Edge, Node, ReactFlowProps } from '../../types'; +import { useStylesLoadedWarning } from './useStylesLoadedWarning'; export type GraphViewProps = Omit< ReactFlowProps, @@ -104,6 +105,7 @@ function GraphViewComponent) { useNodeOrEdgeTypesWarning(nodeTypes); useNodeOrEdgeTypesWarning(edgeTypes); + useStylesLoadedWarning(); useOnInitHandler(onInit); useViewportSync(viewport); diff --git a/packages/react/src/container/GraphView/useNodeOrEdgeTypesWarning.ts b/packages/react/src/container/GraphView/useNodeOrEdgeTypesWarning.ts index 1c2ef365..828b37ac 100644 --- a/packages/react/src/container/GraphView/useNodeOrEdgeTypesWarning.ts +++ b/packages/react/src/container/GraphView/useNodeOrEdgeTypesWarning.ts @@ -1,27 +1,36 @@ import { useEffect, useRef } from 'react'; import { errorMessages } from '@xyflow/system'; -import type { EdgeTypes, NodeTypes } from '../../types'; import { useStoreApi } from '../../hooks/useStore'; +import type { EdgeTypes, NodeTypes } from '../../types'; const emptyTypes = {}; -/* - * This hook warns the user if node or edgeTypes change. +/** + * This hook warns the user if nodeTypes or edgeTypes changed. + * It is only used in development mode. + * + * @internal */ export function useNodeOrEdgeTypesWarning(nodeOrEdgeTypes?: NodeTypes): void; export function useNodeOrEdgeTypesWarning(nodeOrEdgeTypes?: EdgeTypes): void; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodeOrEdgeTypesWarning(nodeOrEdgeTypes: any = emptyTypes): any { - const updateCount = useRef(0); + const typesRef = useRef(nodeOrEdgeTypes); const store = useStoreApi(); useEffect(() => { if (process.env.NODE_ENV === 'development') { - if (updateCount.current > 1) { - store.getState().onError?.('002', errorMessages['error002']()); + const usedKeys = new Set([...Object.keys(typesRef.current), ...Object.keys(nodeOrEdgeTypes)]); + + for (const key of usedKeys) { + if (typesRef.current[key] !== nodeOrEdgeTypes[key]) { + store.getState().onError?.('002', errorMessages['error002']()); + break; + } } - updateCount.current += 1; + + typesRef.current = nodeOrEdgeTypes; } }, [nodeOrEdgeTypes]); } diff --git a/packages/react/src/container/GraphView/useStylesLoadedWarning.ts b/packages/react/src/container/GraphView/useStylesLoadedWarning.ts new file mode 100644 index 00000000..26841570 --- /dev/null +++ b/packages/react/src/container/GraphView/useStylesLoadedWarning.ts @@ -0,0 +1,23 @@ +import { useEffect, useRef } from 'react'; +import { errorMessages } from '@xyflow/system'; + +import { useStoreApi } from '../../hooks/useStore'; + +export function useStylesLoadedWarning() { + const store = useStoreApi(); + const checked = useRef(false); + + useEffect(() => { + if (process.env.NODE_ENV === 'development') { + if (!checked.current) { + const pane = document.querySelector('.react-flow__pane'); + + if (pane && !(window.getComputedStyle(pane).zIndex === '1')) { + store.getState().onError?.('013', errorMessages['error013']('react')); + } + + checked.current = true; + } + } + }, []); +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index f8ba9ea6..85b582d6 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,5 +1,5 @@ export { default as ReactFlow } from './container/ReactFlow'; -export { Handle, type HandleComponentProps } from './components/Handle'; +export { Handle, type HandleProps } from './components/Handle'; export { EdgeText } from './components/Edges/EdgeText'; export { StraightEdge } from './components/Edges/StraightEdge'; export { StepEdge } from './components/Edges/StepEdge'; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index e497dd99..04608f74 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -1,4 +1,4 @@ -import { ReactNode, Ref, RefAttributes, forwardRef } from 'react'; +import { type Ref, type RefAttributes, forwardRef } from 'react'; import { isNodeBase, isEdgeBase } from '@xyflow/system'; import type { Edge, Node } from '../types'; @@ -25,8 +25,8 @@ export const isEdge = (element: unknown): element // eslint-disable-next-line @typescript-eslint/ban-types export function fixedForwardRef( - render: (props: P, ref: Ref) => ReactNode -): (props: P & RefAttributes) => ReactNode { + render: (props: P, ref: Ref) => JSX.Element +): (props: P & RefAttributes) => JSX.Element { // eslint-disable-next-line @typescript-eslint/no-explicit-any return forwardRef(render) as any; } diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 430f2d82..5078722c 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # @xyflow/svelte +## 0.1.3 + +- fix `NodeToolbar` for subflows + +## 0.1.2 + +- export `InternalNode` type + +## 0.1.1 + +- export `useInternalNode` hook + ## 0.1.0 This is a bigger update for Svelte Flow to keep up with the latest changes we made for React Flow and the Svelte5 rewrite. The biggest change is the separation of user nodes (type `Node`) and internal nodes (type `InternalNode`), which includes a renaming of the `node.computed` attribute to `node.measured`. In the previous versions, we stored internals in `node[internalsSymbol]`. This doesn't exist anymore, but we only add it to our internal nodes in `node.internals.`. diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 4a8e1bb5..0b85252f 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "0.1.0", + "version": "0.1.3", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index a7b5493c..472ca2c2 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -13,9 +13,9 @@ } from '@xyflow/system'; import { useStore } from '$lib/store'; - import type { HandleComponentProps } from '$lib/types'; + import type { HandleProps } from '$lib/types'; - type $$Props = HandleComponentProps; + type $$Props = HandleProps; export let id: $$Props['id'] = undefined; export let type: $$Props['type'] = 'source'; diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 6a646f80..5b62f371 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -32,6 +32,7 @@ export * from '$lib/hooks/useConnection'; export * from '$lib/hooks/useNodesEdges'; export * from '$lib/hooks/useHandleConnections'; export * from '$lib/hooks/useNodesData'; +export * from '$lib/hooks/useInternalNode'; export { useInitialized, useNodesInitialized } from '$lib/hooks/useInitialized'; // types @@ -45,8 +46,15 @@ export type { EdgeTypes, DefaultEdgeOptions } from '$lib/types/edges'; -export type { HandleComponentProps, FitViewOptions } from '$lib/types/general'; -export type { Node, NodeTypes, DefaultNodeOptions, BuiltInNode, NodeProps } from '$lib/types/nodes'; +export type { HandleProps, FitViewOptions } from '$lib/types/general'; +export type { + Node, + NodeTypes, + DefaultNodeOptions, + BuiltInNode, + NodeProps, + InternalNode +} from '$lib/types/nodes'; export type { SvelteFlowStore } from '$lib/store/types'; // system types diff --git a/packages/svelte/src/lib/plugins/NodeToolbar/NodeToolbar.svelte b/packages/svelte/src/lib/plugins/NodeToolbar/NodeToolbar.svelte index 82250117..b635578a 100644 --- a/packages/svelte/src/lib/plugins/NodeToolbar/NodeToolbar.svelte +++ b/packages/svelte/src/lib/plugins/NodeToolbar/NodeToolbar.svelte @@ -42,18 +42,7 @@ } $: { - let nodeRect: Rect | undefined = undefined; - - if (toolbarNodes.length === 1) { - const toolbarNode = toolbarNodes[0]; - nodeRect = { - ...toolbarNode.position, - width: toolbarNode.measured.width ?? toolbarNode.width ?? 0, - height: toolbarNode.measured.height ?? toolbarNode.height ?? 0 - }; - } else if (toolbarNodes.length > 1) { - nodeRect = getNodesBounds(toolbarNodes, { nodeOrigin: $nodeOrigin }); - } + const nodeRect = getNodesBounds(toolbarNodes, { nodeOrigin: $nodeOrigin }); if (nodeRect) { transform = getNodeToolbarTransform(nodeRect, $viewport, _position, _offset, _align); diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index 8aaa9a0a..e69c83db 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -1,12 +1,11 @@ import type { ShortcutModifierDefinition } from '@svelte-put/shortcut'; import type { FitViewOptionsBase, - HandleType, - Position, XYPosition, ConnectingHandle, Connection, - OnBeforeDeleteBase + OnBeforeDeleteBase, + HandleProps as HandlePropsSystem } from '@xyflow/system'; import type { Node } from './nodes'; @@ -23,32 +22,9 @@ export type ConnectionData = { connectionStatus: string | null; }; -export type HandleComponentProps = { - /** Type of the handle - * @example HandleType.Source, HandleType.Target - */ - type: HandleType; - /** Position of the handle - * @example Position.TopLeft, Position.TopRight, - * Position.BottomLeft, Position.BottomRight - */ - position?: Position; - /** Id of the handle - * @remarks optional if there is only one handle of this type - */ - id?: string; +export type HandleProps = HandlePropsSystem & { class?: string; style?: string; - /** Should you be able to connect from/to this handle */ - isConnectable?: boolean; - /** Shoould you be able to connect from this handle */ - isConnectableStart?: boolean; - /** Should you be able to connect to this handle */ - isConnectableEnd?: boolean; - /** Function that is called when checking if connection is valid. - * Overrides the isValidConnection on the Flow component. - */ - isValidConnection?: IsValidConnection; onconnect?: (connections: Connection[]) => void; ondisconnect?: (connections: Connection[]) => void; }; diff --git a/packages/system/package.json b/packages/system/package.json index 468ee9b7..5a2eda70 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/system", - "version": "0.0.25", + "version": "0.0.26", "description": "xyflow core system that powers React Flow and Svelte Flow.", "keywords": [ "node-based UI", diff --git a/packages/system/src/constants.ts b/packages/system/src/constants.ts index 67b913a0..9f5c63a5 100644 --- a/packages/system/src/constants.ts +++ b/packages/system/src/constants.ts @@ -1,6 +1,5 @@ import { CoordinateExtent, HandleType } from './types'; -// @todo: update URLs to xyflow export const errorMessages = { error001: () => '[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001', @@ -23,6 +22,8 @@ export const errorMessages = { error011: (edgeType: string) => `Edge type "${edgeType}" not found. Using fallback type "default".`, error012: (id: string) => `Node with id "${id}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`, + error013: (lib: string = 'react') => + `It seems that you haven't loaded the styles. Please import '@xyflow/${lib}/dist/style.css' or base.css to make sure everything is working properly.`, }; export const infiniteExtent: CoordinateExtent = [ diff --git a/packages/system/src/types/handles.ts b/packages/system/src/types/handles.ts index 32732935..b75de4c5 100644 --- a/packages/system/src/types/handles.ts +++ b/packages/system/src/types/handles.ts @@ -1,4 +1,4 @@ -import type { Position, OnConnect, IsValidConnection } from '.'; +import type { Position, IsValidConnection } from '.'; export type HandleType = 'source' | 'target'; @@ -42,8 +42,6 @@ export type HandleProps = { isConnectableStart?: boolean; /** Should you be able to connect to this handle */ isConnectableEnd?: boolean; - /** Callback called when connection is made */ - onConnect?: OnConnect; /** Callback if connection is valid * @remarks connection becomes an edge if isValidConnection returns true */ diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index e3e378eb..f6e617d0 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -254,13 +254,10 @@ export function updateNodeInternals( const node = nodeLookup.get(update.id); if (node?.hidden) { - nodeLookup.set(node.id, { - ...node, - internals: { - ...node.internals, - handleBounds: undefined, - }, - }); + node.internals = { + ...node.internals, + handleBounds: undefined, + }; updatedInternals = true; } else if (node) { const dimensions = getDimensions(update.nodeElement); @@ -272,33 +269,29 @@ export function updateNodeInternals( ); if (doUpdate) { - const newNode = { - ...node, - measured: dimensions, - internals: { - ...node.internals, - handleBounds: { - source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin), - target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin), - }, + node.measured = dimensions; + node.internals = { + ...node.internals, + handleBounds: { + source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin), + target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin), }, }; - nodeLookup.set(node.id, newNode); updatedInternals = true; if (dimensionChanged) { changes.push({ - id: newNode.id, + id: node.id, type: 'dimensions', dimensions, }); - if (newNode.expandParent && newNode.parentId) { + if (node.expandParent && node.parentId) { parentExpandChildren.push({ - id: newNode.id, - parentId: newNode.parentId, - rect: nodeToRect(newNode, newNode.origin || nodeOrigin), + id: node.id, + parentId: node.parentId, + rect: nodeToRect(node, nodeOrigin), }); } }