merg main

This commit is contained in:
peterkogo
2025-05-14 11:25:48 +02:00
31 changed files with 363 additions and 106 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@xyflow/system': patch
---
Added OnReconnect types
+6 -3
View File
@@ -11,6 +11,7 @@ import {
useReactFlow, useReactFlow,
Panel, Panel,
OnNodeDrag, OnNodeDrag,
FitViewOptions,
} from '@xyflow/react'; } from '@xyflow/react';
const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes); const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
@@ -54,6 +55,9 @@ const initialEdges: Edge[] = [
]; ];
const defaultEdgeOptions = {}; const defaultEdgeOptions = {};
const fitViewOptions: FitViewOptions = {
padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 },
};
const BasicFlow = () => { const BasicFlow = () => {
const { const {
@@ -135,6 +139,7 @@ const BasicFlow = () => {
<ReactFlow <ReactFlow
defaultNodes={initialNodes} defaultNodes={initialNodes}
defaultEdges={initialEdges} defaultEdges={initialEdges}
onNodesChange={console.log}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
@@ -146,9 +151,7 @@ const BasicFlow = () => {
minZoom={0.2} minZoom={0.2}
maxZoom={4} maxZoom={4}
fitView fitView
fitViewOptions={{ fitViewOptions={fitViewOptions}
padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 },
}}
defaultEdgeOptions={defaultEdgeOptions} defaultEdgeOptions={defaultEdgeOptions}
selectNodesOnDrag={false} selectNodesOnDrag={false}
elevateEdgesOnSelect elevateEdgesOnSelect
@@ -0,0 +1,21 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps, NodeResizeControl, ResizeControlVariant } from '@xyflow/react';
const CustomResizerNode: FC<NodeProps> = ({ data }) => {
return (
<>
<NodeResizeControl
variant={ResizeControlVariant.Handle}
position="bottom-right"
resizeDirection="horizontal"
minWidth={100}
maxWidth={500}
/>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomResizerNode);
@@ -15,12 +15,14 @@ import DefaultResizer from './DefaultResizer';
import CustomResizer from './CustomResizer'; import CustomResizer from './CustomResizer';
import VerticalResizer from './VerticalResizer'; import VerticalResizer from './VerticalResizer';
import HorizontalResizer from './HorizontalResizer'; import HorizontalResizer from './HorizontalResizer';
import BottomRightResizer from './BottomRightResizer';
const nodeTypes = { const nodeTypes = {
defaultResizer: DefaultResizer, defaultResizer: DefaultResizer,
customResizer: CustomResizer, customResizer: CustomResizer,
verticalResizer: VerticalResizer, verticalResizer: VerticalResizer,
horizontalResizer: HorizontalResizer, horizontalResizer: HorizontalResizer,
bottomRightResizer: BottomRightResizer,
}; };
const nodeStyle = { const nodeStyle = {
@@ -166,6 +168,13 @@ const initialNodes: Node[] = [
expandParent: true, expandParent: true,
style: { ...nodeStyle }, style: { ...nodeStyle },
}, },
{
id: '6',
type: 'bottomRightResizer',
data: { label: 'Bottom Right with horizontal direction' },
position: { x: 500, y: 500 },
style: { ...nodeStyle },
},
]; ];
const CustomNodeFlow = () => { const CustomNodeFlow = () => {
@@ -10,6 +10,7 @@ import {
useEdgesState, useEdgesState,
useOnSelectionChange, useOnSelectionChange,
OnSelectionChangeParams, OnSelectionChangeParams,
Panel,
} from '@xyflow/react'; } from '@xyflow/react';
const initialNodes: Node[] = [ const initialNodes: Node[] = [
@@ -51,6 +52,12 @@ const Flow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]);
const [elementsSelectable, setElementsSelectable] = useState<boolean>(true);
const [secondLoggerActive, setSecondLoggerActive] = useState<boolean>(true);
const toggleSecondLogger = () => {
setSecondLoggerActive(!secondLoggerActive);
};
return ( return (
<ReactFlow <ReactFlow
@@ -59,27 +66,17 @@ const Flow = () => {
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}
/> elementsSelectable={elementsSelectable}
); >
}; <Panel>
<button onClick={toggleSecondLogger}>{secondLoggerActive ? 'Disable' : 'Enable'} Logger 2</button>
<button onClick={() => setElementsSelectable((s) => !s)}>toggle selectable</button>
</Panel>
const WrappedFlow = () => {
const [secondLoggerActive, setSecondLoggerActive] = useState<boolean>(true);
const toggleSecondLogger = () => {
setSecondLoggerActive(!secondLoggerActive);
};
return (
<ReactFlowProvider>
<Flow />
<SelectionLogger id="Logger 1" /> <SelectionLogger id="Logger 1" />
{secondLoggerActive && <SelectionLogger id="Logger 2" />} {secondLoggerActive && <SelectionLogger id="Logger 2" />}
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}> </ReactFlow>
<button onClick={toggleSecondLogger}>{secondLoggerActive ? 'Disable' : 'Enable'} Logger 2</button>
</div>
</ReactFlowProvider>
); );
}; };
export default WrappedFlow; export default Flow;
+43
View File
@@ -1,5 +1,48 @@
# @xyflow/react # @xyflow/react
## 12.6.1
### Patch Changes
- [#5249](https://github.com/xyflow/xyflow/pull/5249) [`895b5d81`](https://github.com/xyflow/xyflow/commit/895b5d81c8ee5236009820ecd0ed6806c6e59e29) Thanks [@moklick](https://github.com/moklick)! - Call `onNodesChange` for uncontrolled flows that use `updateNode`
- [#5247](https://github.com/xyflow/xyflow/pull/5247) [`67e1cb68`](https://github.com/xyflow/xyflow/commit/67e1cb6891078dbcb9e1d06b9f9fdbfc79860ab6) Thanks [@moklick](https://github.com/moklick)! - Cleanup TSDoc annotations for ReactFlow
- Updated dependencies [[`2a03213b`](https://github.com/xyflow/xyflow/commit/2a03213b0695d504f831579ec9df3f9de2d3e0bd)]:
- @xyflow/system@0.0.58
## 12.6.0
### Minor Changes
- [#5219](https://github.com/xyflow/xyflow/pull/5219) [`4236adbc`](https://github.com/xyflow/xyflow/commit/4236adbc462b3f65ee41869ef426491ed3fa8ba0) Thanks [@moklick](https://github.com/moklick)! - Add initialMinZoom, initialMaxZoom and initialFitViewOptions to ReactFlowProvider
- [#5227](https://github.com/xyflow/xyflow/pull/5227) [`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35) Thanks [@moklick](https://github.com/moklick)! - Add `resizeDirection` prop for the `NodeResizeControl` component
### Patch Changes
- [#5217](https://github.com/xyflow/xyflow/pull/5217) [`bce74e88`](https://github.com/xyflow/xyflow/commit/bce74e8811a98c967b5bd06c3e5aecde24c8b679) Thanks [@moklick](https://github.com/moklick)! - Keep node seleciton on pane click if elementsSelectable=false
- Updated dependencies [[`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35), [`4e681f9c`](https://github.com/xyflow/xyflow/commit/4e681f9c529c3f4f8b2ac5d25b4db7878c197e14)]:
- @xyflow/system@0.0.57
## 12.5.6
### Patch Changes
- [#5212](https://github.com/xyflow/xyflow/pull/5212) [`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13) Thanks [@moklick](https://github.com/moklick)! - Add polyfill for `Promise.withResolvers`
- [#5192](https://github.com/xyflow/xyflow/pull/5192) [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7) Thanks [@peterkogo](https://github.com/peterkogo)! - Optimize performance of nodesInitialized
- [#5196](https://github.com/xyflow/xyflow/pull/5196) [`7f902db4`](https://github.com/xyflow/xyflow/commit/7f902db46b6f1ea4adc94390db8d5db47f8c5903) Thanks [@moklick](https://github.com/moklick)! - Hide edge marker and attribution for screenreaders
- [#5213](https://github.com/xyflow/xyflow/pull/5213) [`78782c16`](https://github.com/xyflow/xyflow/commit/78782c1696323ee9be0d38bbd807bd3fde5f549d) Thanks [@moklick](https://github.com/moklick)! - Do not trigger selection events if elementsSelectable=false
- [#5191](https://github.com/xyflow/xyflow/pull/5191) [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix legacy padding being slightly larger than before
- Updated dependencies [[`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13), [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7), [`98fe23c7`](https://github.com/xyflow/xyflow/commit/98fe23c7c2b12972f1b7def866215ce82a86e2c0), [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e)]:
- @xyflow/system@0.0.56
## 12.5.5 ## 12.5.5
### Patch Changes ### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xyflow/react", "name": "@xyflow/react",
"version": "12.5.5", "version": "12.6.1",
"description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
"keywords": [ "keywords": [
"react", "react",
@@ -32,6 +32,7 @@ function ResizeControl({
maxWidth = Number.MAX_VALUE, maxWidth = Number.MAX_VALUE,
maxHeight = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE,
keepAspectRatio = false, keepAspectRatio = false,
resizeDirection,
shouldResize, shouldResize,
onResizeStart, onResizeStart,
onResize, onResize,
@@ -117,11 +118,12 @@ function ResizeControl({
} }
if (change.width !== undefined && change.height !== undefined) { if (change.width !== undefined && change.height !== undefined) {
const setAttributes = !resizeDirection ? true : resizeDirection === 'horizontal' ? 'width' : 'height';
const dimensionChange: NodeDimensionChange = { const dimensionChange: NodeDimensionChange = {
id, id,
type: 'dimensions', type: 'dimensions',
resizing: true, resizing: true,
setAttributes: true, setAttributes,
dimensions: { dimensions: {
width: change.width, width: change.width,
height: change.height, height: change.height,
@@ -166,6 +168,7 @@ function ResizeControl({
maxHeight, maxHeight,
}, },
keepAspectRatio, keepAspectRatio,
resizeDirection,
onResizeStart, onResizeStart,
onResize, onResize,
onResizeEnd, onResizeEnd,
@@ -3,6 +3,7 @@ import type {
ControlPosition, ControlPosition,
ControlLinePosition, ControlLinePosition,
ResizeControlVariant, ResizeControlVariant,
ResizeControlDirection,
ShouldResize, ShouldResize,
OnResizeStart, OnResizeStart,
OnResize, OnResize,
@@ -97,6 +98,11 @@ export type ResizeControlProps = Pick<
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line * @example ResizeControlVariant.Handle, ResizeControlVariant.Line
*/ */
variant?: ResizeControlVariant; variant?: ResizeControlVariant;
/**
* The direction the user can resize the node.
* If not provided, the user can resize in any direction.
*/
resizeDirection?: ResizeControlDirection;
className?: string; className?: string;
style?: CSSProperties; style?: CSSProperties;
children?: ReactNode; children?: ReactNode;
@@ -105,6 +111,6 @@ export type ResizeControlProps = Pick<
/** /**
* @expand * @expand
*/ */
export type ResizeControlLineProps = ResizeControlProps & { export type ResizeControlLineProps = Omit<ResizeControlProps, 'resizeDirection'> & {
position?: ControlLinePosition; position?: ControlLinePosition;
}; };
@@ -40,28 +40,27 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
next = typeof payload === 'function' ? payload(next) : payload; next = typeof payload === 'function' ? payload(next) : payload;
} }
const changes = getElementsDiffChanges({
items: next,
lookup: nodeLookup,
}) as NodeChange<NodeType>[];
if (hasDefaultNodes) { if (hasDefaultNodes) {
setNodes(next); setNodes(next);
} else { }
// When a controlled flow is used we need to collect the changes
const changes = getElementsDiffChanges({
items: next,
lookup: nodeLookup,
}) as NodeChange<NodeType>[];
// We only want to fire onNodesChange if there are changes to the nodes // We only want to fire onNodesChange if there are changes to the nodes
if (changes.length > 0) { if (changes.length > 0) {
onNodesChange?.(changes); onNodesChange?.(changes);
} else if (fitViewQueued) { } else if (fitViewQueued) {
// If there are no changes to the nodes, we still need to call setNodes // If there are no changes to the nodes, we still need to call setNodes
// to trigger a re-render and fitView. // to trigger a re-render and fitView.
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
const { fitViewQueued, nodes, setNodes } = store.getState(); const { fitViewQueued, nodes, setNodes } = store.getState();
if (fitViewQueued) { if (fitViewQueued) {
setNodes(nodes); setNodes(nodes);
} }
}); });
}
} }
}, []); }, []);
@@ -3,18 +3,49 @@ import { useState, type ReactNode } from 'react';
import { Provider } from '../../contexts/StoreContext'; import { Provider } from '../../contexts/StoreContext';
import { createStore } from '../../store'; import { createStore } from '../../store';
import { BatchProvider } from '../BatchProvider'; import { BatchProvider } from '../BatchProvider';
import type { Node, Edge } from '../../types'; import type { Node, Edge, FitViewOptions } from '../../types';
import { CoordinateExtent, NodeOrigin } from '@xyflow/system'; import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
export type ReactFlowProviderProps = { export type ReactFlowProviderProps = {
/** These nodes are used to initialize the flow. They are not dynamic. */
initialNodes?: Node[]; initialNodes?: Node[];
/** These edges are used to initialize the flow. They are not dynamic. */
initialEdges?: Edge[]; initialEdges?: Edge[];
/** These nodes are used to initialize the flow. They are not dynamic. */
defaultNodes?: Node[]; defaultNodes?: Node[];
/** These edges are used to initialize the flow. They are not dynamic. */
defaultEdges?: Edge[]; defaultEdges?: Edge[];
/** The initial width is necessary to be able to use fitView on the server */
initialWidth?: number; initialWidth?: number;
/** The initial height is necessary to be able to use fitView on the server */
initialHeight?: number; initialHeight?: number;
/** When `true`, the flow will be zoomed and panned to fit all the nodes initially provided. */
fitView?: boolean; fitView?: boolean;
/**
* You can provide an object of options to customize the initial fitView behavior.
*/
initialFitViewOptions?: FitViewOptions;
/** Initial minimum zoom level */
initialMinZoom?: number;
/** Initial maximum zoom level */
initialMaxZoom?: number;
/**
* The origin of the node to use when placing it in the flow or looking up its `x` and `y`
* position. An origin of `[0, 0]` means that a node's top left corner will be placed at the `x`
* and `y` position.
* @default [0, 0]
* @example
* [0, 0] // default, top left
* [0.5, 0.5] // center
* [1, 1] // bottom right
*/
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
/**
* By default, nodes can be placed on an infinite flow. You can use this prop to set a boundary.
*
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
* @example [[-1000, -10000], [1000, 1000]]
*/
nodeExtent?: CoordinateExtent; nodeExtent?: CoordinateExtent;
children: ReactNode; children: ReactNode;
}; };
@@ -60,6 +91,9 @@ export function ReactFlowProvider({
defaultEdges, defaultEdges,
initialWidth: width, initialWidth: width,
initialHeight: height, initialHeight: height,
initialMinZoom: minZoom,
initialMaxZoom: maxZoom,
initialFitViewOptions: fitViewOptions,
fitView, fitView,
nodeOrigin, nodeOrigin,
nodeExtent, nodeExtent,
@@ -74,6 +108,9 @@ export function ReactFlowProvider({
width, width,
height, height,
fitView, fitView,
minZoom,
maxZoom,
fitViewOptions,
nodeOrigin, nodeOrigin,
nodeExtent, nodeExtent,
}) })
@@ -67,7 +67,7 @@ const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
} }
return ( return (
<svg className="react-flow__marker"> <svg className="react-flow__marker" aria-hidden="true">
<defs> <defs>
{markers.map((marker: MarkerProps) => ( {markers.map((marker: MarkerProps) => (
<Marker <Marker
@@ -2,7 +2,7 @@ import { useContext, type ReactNode } from 'react';
import StoreContext from '../../contexts/StoreContext'; import StoreContext from '../../contexts/StoreContext';
import { ReactFlowProvider } from '../../components/ReactFlowProvider'; import { ReactFlowProvider } from '../../components/ReactFlowProvider';
import type { Node, Edge } from '../../types'; import type { Node, Edge, FitViewOptions } from '../../types';
import { CoordinateExtent, NodeOrigin } from '@xyflow/system'; import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
export function Wrapper({ export function Wrapper({
@@ -14,6 +14,9 @@ export function Wrapper({
width, width,
height, height,
fitView, fitView,
fitViewOptions,
minZoom,
maxZoom,
nodeOrigin, nodeOrigin,
nodeExtent, nodeExtent,
}: { }: {
@@ -25,6 +28,9 @@ export function Wrapper({
width?: number; width?: number;
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
fitViewOptions?: FitViewOptions;
minZoom?: number;
maxZoom?: number;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent; nodeExtent?: CoordinateExtent;
}) { }) {
@@ -47,6 +53,9 @@ export function Wrapper({
initialWidth={width} initialWidth={width}
initialHeight={height} initialHeight={height}
fitView={fitView} fitView={fitView}
initialFitViewOptions={fitViewOptions}
initialMinZoom={minZoom}
initialMaxZoom={maxZoom}
nodeOrigin={nodeOrigin} nodeOrigin={nodeOrigin}
nodeExtent={nodeExtent} nodeExtent={nodeExtent}
> >
@@ -177,6 +177,9 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
width={width} width={width}
height={height} height={height}
fitView={fitView} fitView={fitView}
fitViewOptions={fitViewOptions}
minZoom={minZoom}
maxZoom={maxZoom}
nodeOrigin={nodeOrigin} nodeOrigin={nodeOrigin}
nodeExtent={nodeExtent} nodeExtent={nodeExtent}
> >
@@ -9,15 +9,17 @@ export type UseNodesInitializedOptions = {
}; };
const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => { const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => {
if (!options.includeHiddenNodes) {
return s.nodesInitialized;
}
if (s.nodeLookup.size === 0) { if (s.nodeLookup.size === 0) {
return false; return false;
} }
for (const [, { hidden, internals }] of s.nodeLookup) { for (const [, { internals }] of s.nodeLookup) {
if (options.includeHiddenNodes || !hidden) { if (internals.handleBounds === undefined || !nodeHasDimensions(internals.userNode)) {
if (internals.handleBounds === undefined || !nodeHasDimensions(internals.userNode)) { return false;
return false;
}
} }
} }
+2 -1
View File
@@ -8,6 +8,7 @@ import {
isRectObject, isRectObject,
NodeRemoveChange, NodeRemoveChange,
nodeToRect, nodeToRect,
withResolvers,
type Rect, type Rect,
} from '@xyflow/system'; } from '@xyflow/system';
@@ -280,7 +281,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
fitView: async (options: FitViewOptions<NodeType> | undefined) => { fitView: async (options: FitViewOptions<NodeType> | undefined) => {
// We either create a new Promise or reuse the existing one // We either create a new Promise or reuse the existing one
// Even if fitView is called multiple times in a row, we only end up with a single Promise // Even if fitView is called multiple times in a row, we only end up with a single Promise
const fitViewResolver = store.getState().fitViewResolver ?? Promise.withResolvers<boolean>(); const fitViewResolver = store.getState().fitViewResolver ?? withResolvers<boolean>();
// We schedule a fitView by setting fitViewQueued and triggering a setNodes // We schedule a fitView by setting fitViewQueued and triggering a setNodes
store.setState({ fitViewQueued: true, fitViewOptions: options, fitViewResolver }); store.setState({ fitViewQueued: true, fitViewOptions: options, fitViewResolver });
+28 -5
View File
@@ -18,7 +18,7 @@ import {
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
import getInitialState from './initialState'; import getInitialState from './initialState';
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams } from '../types'; import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions } from '../types';
const createStore = ({ const createStore = ({
nodes, nodes,
@@ -28,6 +28,9 @@ const createStore = ({
width, width,
height, height,
fitView, fitView,
fitViewOptions,
minZoom,
maxZoom,
nodeOrigin, nodeOrigin,
nodeExtent, nodeExtent,
}: { }: {
@@ -38,6 +41,9 @@ const createStore = ({
width?: number; width?: number;
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
fitViewOptions?: FitViewOptions;
minZoom?: number;
maxZoom?: number;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent; nodeExtent?: CoordinateExtent;
}) => }) =>
@@ -70,7 +76,20 @@ const createStore = ({
} }
return { return {
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }), ...getInitialState({
nodes,
edges,
width,
height,
fitView,
fitViewOptions,
minZoom,
maxZoom,
nodeOrigin,
nodeExtent,
defaultNodes,
defaultEdges,
}),
setNodes: (nodes: Node[]) => { setNodes: (nodes: Node[]) => {
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get(); const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get();
/* /*
@@ -91,9 +110,9 @@ const createStore = ({
if (fitViewQueued && nodesInitialized) { if (fitViewQueued && nodesInitialized) {
resolveFitView(); resolveFitView();
set({ nodes, fitViewQueued: false, fitViewOptions: undefined }); set({ nodes, nodesInitialized, fitViewQueued: false, fitViewOptions: undefined });
} else { } else {
set({ nodes }); set({ nodes, nodesInitialized });
} }
}, },
setEdges: (edges: Edge[]) => { setEdges: (edges: Edge[]) => {
@@ -297,7 +316,11 @@ const createStore = ({
get().panZoom?.setClickDistance(clickDistance); get().panZoom?.setClickDistance(clickDistance);
}, },
resetSelectedElements: () => { resetSelectedElements: () => {
const { edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get(); const { edges, nodes, triggerNodeChanges, triggerEdgeChanges, elementsSelectable } = get();
if (!elementsSelectable) {
return;
}
const nodeChanges = nodes.reduce<NodeSelectionChange[]>( const nodeChanges = nodes.reduce<NodeSelectionChange[]>(
(res, node) => (node.selected ? [...res, createSelectionChange(node.id, false)] : res), (res, node) => (node.selected ? [...res, createSelectionChange(node.id, false)] : res),
+20 -6
View File
@@ -12,7 +12,7 @@ import {
CoordinateExtent, CoordinateExtent,
} from '@xyflow/system'; } from '@xyflow/system';
import type { Edge, InternalNode, Node, ReactFlowStore } from '../types'; import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
const getInitialState = ({ const getInitialState = ({
nodes, nodes,
@@ -22,6 +22,9 @@ const getInitialState = ({
width, width,
height, height,
fitView, fitView,
fitViewOptions,
minZoom = 0.5,
maxZoom = 2,
nodeOrigin, nodeOrigin,
nodeExtent, nodeExtent,
}: { }: {
@@ -32,6 +35,9 @@ const getInitialState = ({
width?: number; width?: number;
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
fitViewOptions?: FitViewOptions;
minZoom?: number;
maxZoom?: number;
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent; nodeExtent?: CoordinateExtent;
} = {}): ReactFlowStore => { } = {}): ReactFlowStore => {
@@ -46,7 +52,7 @@ const getInitialState = ({
const storeNodeExtent = nodeExtent ?? infiniteExtent; const storeNodeExtent = nodeExtent ?? infiniteExtent;
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges); updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
adoptUserNodes(storeNodes, nodeLookup, parentLookup, { const nodesInitialized = adoptUserNodes(storeNodes, nodeLookup, parentLookup, {
nodeOrigin: storeNodeOrigin, nodeOrigin: storeNodeOrigin,
nodeExtent: storeNodeExtent, nodeExtent: storeNodeExtent,
elevateNodesOnSelect: false, elevateNodesOnSelect: false,
@@ -59,7 +65,14 @@ const getInitialState = ({
filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)), filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)),
}); });
const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); const { x, y, zoom } = getViewportForBounds(
bounds,
width,
height,
minZoom,
maxZoom,
fitViewOptions?.padding ?? 0.1
);
transform = [x, y, zoom]; transform = [x, y, zoom];
} }
@@ -69,6 +82,7 @@ const getInitialState = ({
height: 0, height: 0,
transform, transform,
nodes: storeNodes, nodes: storeNodes,
nodesInitialized,
nodeLookup, nodeLookup,
parentLookup, parentLookup,
edges: storeEdges, edges: storeEdges,
@@ -79,8 +93,8 @@ const getInitialState = ({
hasDefaultNodes: defaultNodes !== undefined, hasDefaultNodes: defaultNodes !== undefined,
hasDefaultEdges: defaultEdges !== undefined, hasDefaultEdges: defaultEdges !== undefined,
panZoom: null, panZoom: null,
minZoom: 0.5, minZoom,
maxZoom: 2, maxZoom,
translateExtent: infiniteExtent, translateExtent: infiniteExtent,
nodeExtent: storeNodeExtent, nodeExtent: storeNodeExtent,
nodesSelectionActive: false, nodesSelectionActive: false,
@@ -109,7 +123,7 @@ const getInitialState = ({
multiSelectionActive: false, multiSelectionActive: false,
fitViewQueued: fitView ?? false, fitViewQueued: fitView ?? false,
fitViewOptions: undefined, fitViewOptions,
fitViewResolver: null, fitViewResolver: null,
connection: { ...initialConnection }, connection: { ...initialConnection },
+3 -30
View File
@@ -85,22 +85,11 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
defaultEdges?: EdgeType[]; defaultEdges?: EdgeType[];
/** /**
* Defaults to be applied to all new edges that are added to the flow. * Defaults to be applied to all new edges that are added to the flow.
*
* Properties on a new edge will override these defaults if they exist. * Properties on a new edge will override these defaults if they exist.
* @example * @example
* const defaultEdgeOptions = { * const defaultEdgeOptions = {
* type: 'customEdgeType', * type: 'customEdgeType',
* animated: true, * animated: true
* interactionWidth: 10,
* data: { label: 'custom label' },
* hidden: false,
* deletable: true,
* selected: false,
* focusable: true,
* markerStart: EdgeMarker.ArrowClosed,
* markerEnd: EdgeMarker.ArrowClosed,
* zIndex: 12,
* ariaLabel: 'custom aria label'
* } * }
*/ */
defaultEdgeOptions?: DefaultEdgeOptions; defaultEdgeOptions?: DefaultEdgeOptions;
@@ -137,7 +126,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
/** /**
* This handler is called when the source or target of a reconnectable edge is dragged from the * This handler is called when the source or target of a reconnectable edge is dragged from the
* current node. It will fire even if the edge's source or target do not end up changing. * current node. It will fire even if the edge's source or target do not end up changing.
*
* You can use the `reconnectEdge` utility to convert the connection to a new edge. * You can use the `reconnectEdge` utility to convert the connection to a new edge.
*/ */
onReconnect?: OnReconnect<EdgeType>; onReconnect?: OnReconnect<EdgeType>;
@@ -148,7 +136,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
/** /**
* This event fires when the user releases the source or target of an editable edge. It is called * This event fires when the user releases the source or target of an editable edge. It is called
* even if an edge update does not occur. * even if an edge update does not occur.
*
*/ */
onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void; onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void;
/** /**
@@ -209,7 +196,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void; onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void;
/** /**
* When a connection line is completed and two nodes are connected by the user, this event fires with the new connection. * When a connection line is completed and two nodes are connected by the user, this event fires with the new connection.
*
* You can use the `addEdge` utility to convert the connection to a complete edge. * You can use the `addEdge` utility to convert the connection to a complete edge.
* @example // Use helper function to update edges onConnect * @example // Use helper function to update edges onConnect
* import ReactFlow, { addEdge } from '@xyflow/react'; * import ReactFlow, { addEdge } from '@xyflow/react';
@@ -277,9 +263,7 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>; onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>;
/** /**
* Custom node types to be available in a flow. * Custom node types to be available in a flow.
*
* React Flow matches a node's type to a component in the `nodeTypes` object. * React Flow matches a node's type to a component in the `nodeTypes` object.
* @TODO check if @default is correct
* @default { * @default {
* input: InputNode, * input: InputNode,
* default: DefaultNode, * default: DefaultNode,
@@ -294,9 +278,7 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
nodeTypes?: NodeTypes; nodeTypes?: NodeTypes;
/** /**
* Custom edge types to be available in a flow. * Custom edge types to be available in a flow.
*
* React Flow matches an edge's type to a component in the `edgeTypes` object. * React Flow matches an edge's type to a component in the `edgeTypes` object.
* @TODO check if @default is correct
* @default { * @default {
* default: BezierEdge, * default: BezierEdge,
* straight: StraightEdge, * straight: StraightEdge,
@@ -312,7 +294,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
edgeTypes?: EdgeTypes; edgeTypes?: EdgeTypes;
/** /**
* The type of edge path to use for connection lines. * The type of edge path to use for connection lines.
*
* Although created edges can be of any type, React Flow needs to know what type of path to render for the connection line before the edge is created! * Although created edges can be of any type, React Flow needs to know what type of path to render for the connection line before the edge is created!
* @default ConnectionLineType.Bezier * @default ConnectionLineType.Bezier
*/ */
@@ -333,7 +314,7 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
/** /**
* If set, pressing the key or chord will delete any selected nodes and edges. Passing an array * If set, pressing the key or chord will delete any selected nodes and edges. Passing an array
* represents multiple keys that can be pressed. * represents multiple keys that can be pressed.
*
* For example, `["Delete", "Backspace"]` will delete selected elements when either key is pressed. * For example, `["Delete", "Backspace"]` will delete selected elements when either key is pressed.
* @default 'Backspace' * @default 'Backspace'
*/ */
@@ -450,7 +431,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
selectNodesOnDrag?: boolean; selectNodesOnDrag?: boolean;
/** /**
* Enabling this prop allows users to pan the viewport by clicking and dragging. * Enabling this prop allows users to pan the viewport by clicking and dragging.
*
* You can also set this prop to an array of numbers to limit which mouse buttons can activate panning. * You can also set this prop to an array of numbers to limit which mouse buttons can activate panning.
* @default true * @default true
* @example [0, 2] // allows panning with the left and right mouse buttons * @example [0, 2] // allows panning with the left and right mouse buttons
@@ -489,7 +469,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
onViewportChange?: (viewport: Viewport) => void; onViewportChange?: (viewport: Viewport) => void;
/** /**
* By default, the viewport extends infinitely. You can use this prop to set a boundary. * By default, the viewport extends infinitely. You can use this prop to set a boundary.
*
* The first pair of coordinates is the top left boundary and the second pair is the bottom right. * The first pair of coordinates is the top left boundary and the second pair is the bottom right.
* @default [[-∞, -∞], [+∞, +∞]] * @default [[-∞, -∞], [+∞, +∞]]
* @example [[-1000, -10000], [1000, 1000]] * @example [[-1000, -10000], [1000, 1000]]
@@ -502,7 +481,6 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
preventScrolling?: boolean; preventScrolling?: boolean;
/** /**
* By default, nodes can be placed on an infinite flow. You can use this prop to set a boundary. * By default, nodes can be placed on an infinite flow. You can use this prop to set a boundary.
*
* The first pair of coordinates is the top left boundary and the second pair is the bottom right. * The first pair of coordinates is the top left boundary and the second pair is the bottom right.
* @example [[-1000, -10000], [1000, 1000]] * @example [[-1000, -10000], [1000, 1000]]
*/ */
@@ -524,21 +502,18 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
zoomOnPinch?: boolean; zoomOnPinch?: boolean;
/** /**
* Controls if the viewport should pan by scrolling inside the container. * Controls if the viewport should pan by scrolling inside the container.
*
* Can be limited to a specific direction with `panOnScrollMode`. * Can be limited to a specific direction with `panOnScrollMode`.
* @default false * @default false
*/ */
panOnScroll?: boolean; panOnScroll?: boolean;
/** /**
* Controls how fast viewport should be panned on scroll. * Controls how fast viewport should be panned on scroll.
*
* Use together with `panOnScroll` prop. * Use together with `panOnScroll` prop.
* @default 0.5 * @default 0.5
*/ */
panOnScrollSpeed?: number; panOnScrollSpeed?: number;
/** /**
* This prop is used to limit the direction of panning when `panOnScroll` is enabled. * This prop is used to limit the direction of panning when `panOnScroll` is enabled.
*
* The `"free"` option allows panning in any direction. * The `"free"` option allows panning in any direction.
* @default "free" * @default "free"
* @example "horizontal" | "vertical" * @example "horizontal" | "vertical"
@@ -671,10 +646,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
isValidConnection?: IsValidConnection<EdgeType>; isValidConnection?: IsValidConnection<EdgeType>;
/** /**
* With a threshold greater than zero you can delay node drag events. * With a threshold greater than zero you can delay node drag events.
*
* If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired. * If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired.
* * 1 is the default value, so that clicks don't trigger drag events.
* 1 is the default value, so clicks don't trigger drag events.
* @default 1 * @default 1
*/ */
nodeDragThreshold?: number; nodeDragThreshold?: number;
+3 -1
View File
@@ -1,5 +1,6 @@
import { import {
ConnectionMode, ConnectionMode,
withResolvers,
type ConnectionState, type ConnectionState,
type CoordinateExtent, type CoordinateExtent,
type InternalNodeUpdate, type InternalNodeUpdate,
@@ -53,6 +54,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
height: number; height: number;
transform: Transform; transform: Transform;
nodes: NodeType[]; nodes: NodeType[];
nodesInitialized: boolean;
nodeLookup: NodeLookup<InternalNode<NodeType>>; nodeLookup: NodeLookup<InternalNode<NodeType>>;
parentLookup: ParentLookup<InternalNode<NodeType>>; parentLookup: ParentLookup<InternalNode<NodeType>>;
edges: EdgeType[]; edges: EdgeType[];
@@ -121,7 +123,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
fitViewQueued: boolean; fitViewQueued: boolean;
fitViewOptions: FitViewOptions | undefined; fitViewOptions: FitViewOptions | undefined;
fitViewResolver: PromiseWithResolvers<boolean> | null; fitViewResolver: ReturnType<typeof withResolvers<boolean>> | null;
onNodesDelete?: OnNodesDelete<NodeType>; onNodesDelete?: OnNodesDelete<NodeType>;
onEdgesDelete?: OnEdgesDelete<EdgeType>; onEdgesDelete?: OnEdgesDelete<EdgeType>;
+6 -2
View File
@@ -130,8 +130,12 @@ function applyChange(change: any, element: any): any {
element.measured.height = change.dimensions.height; element.measured.height = change.dimensions.height;
if (change.setAttributes) { if (change.setAttributes) {
element.width = change.dimensions.width; if (change.setAttributes === true || change.setAttributes === 'width') {
element.height = change.dimensions.height; element.width = change.dimensions.width;
}
if (change.setAttributes === true || change.setAttributes === 'height') {
element.height = change.dimensions.height;
}
} }
} }
+25
View File
@@ -1,5 +1,30 @@
# @xyflow/svelte # @xyflow/svelte
## 0.1.38
### Patch Changes
- Updated dependencies [[`2a03213b`](https://github.com/xyflow/xyflow/commit/2a03213b0695d504f831579ec9df3f9de2d3e0bd)]:
- @xyflow/system@0.0.58
## 0.1.37
### Patch Changes
- Updated dependencies [[`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35), [`4e681f9c`](https://github.com/xyflow/xyflow/commit/4e681f9c529c3f4f8b2ac5d25b4db7878c197e14)]:
- @xyflow/system@0.0.57
## 0.1.36
### Patch Changes
- [#5212](https://github.com/xyflow/xyflow/pull/5212) [`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13) Thanks [@moklick](https://github.com/moklick)! - Add polyfill for `Promise.withResolvers`
- [#5191](https://github.com/xyflow/xyflow/pull/5191) [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix legacy padding being slightly larger than before
- Updated dependencies [[`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13), [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7), [`98fe23c7`](https://github.com/xyflow/xyflow/commit/98fe23c7c2b12972f1b7def866215ce82a86e2c0), [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e)]:
- @xyflow/system@0.0.56
## 0.1.35 ## 0.1.35
### Patch Changes ### Patch Changes
+26
View File
@@ -1,5 +1,31 @@
# @xyflow/system # @xyflow/system
## 0.0.58
### Patch Changes
- [#5252](https://github.com/xyflow/xyflow/pull/5252) [`2a03213b`](https://github.com/xyflow/xyflow/commit/2a03213b0695d504f831579ec9df3f9de2d3e0bd) Thanks [@moklick](https://github.com/moklick)! - Center panel correctly when bottom-center or top-center position is used
## 0.0.57
### Patch Changes
- [#5227](https://github.com/xyflow/xyflow/pull/5227) [`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35) Thanks [@moklick](https://github.com/moklick)! - add resizeDirection for XYResizer
- [#5221](https://github.com/xyflow/xyflow/pull/5221) [`4e681f9c`](https://github.com/xyflow/xyflow/commit/4e681f9c529c3f4f8b2ac5d25b4db7878c197e14) Thanks [@moklick](https://github.com/moklick)! - Allow zero as a valid node width/height value
## 0.0.56
### Patch Changes
- [#5212](https://github.com/xyflow/xyflow/pull/5212) [`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13) Thanks [@moklick](https://github.com/moklick)! - Add polyfill for `Promise.withResolvers`
- [#5192](https://github.com/xyflow/xyflow/pull/5192) [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7) Thanks [@peterkogo](https://github.com/peterkogo)! - Optimize performance of nodesInitialized
- [#5153](https://github.com/xyflow/xyflow/pull/5153) [`98fe23c7`](https://github.com/xyflow/xyflow/commit/98fe23c7c2b12972f1b7def866215ce82a86e2c0) Thanks [@nick-lawrence-ctm](https://github.com/nick-lawrence-ctm)! - Add separators to horizontal control buttons
- [#5191](https://github.com/xyflow/xyflow/pull/5191) [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix legacy padding being slightly larger than before
## 0.0.55 ## 0.0.55
### Patch Changes ### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xyflow/system", "name": "@xyflow/system",
"version": "0.0.55", "version": "0.0.58",
"description": "xyflow core system that powers React Flow and Svelte Flow.", "description": "xyflow core system that powers React Flow and Svelte Flow.",
"keywords": [ "keywords": [
"node-based UI", "node-based UI",
+2 -2
View File
@@ -282,7 +282,7 @@ svg.xy-flow__connectionline {
&.bottom { &.bottom {
&.center { &.center {
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-15px) translateX(-50%);
} }
} }
@@ -298,7 +298,7 @@ svg.xy-flow__connectionline {
&.right { &.right {
&.center { &.center {
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-15px) translateY(-50%);
} }
} }
} }
+13
View File
@@ -158,4 +158,17 @@
&-button:last-child { &-button:last-child {
border-bottom: none; border-bottom: none;
} }
&.horizontal &-button {
border-bottom: none;
border-right: 1px solid
var(
--xy-controls-button-border-color-props,
var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default))
);
}
&.horizontal &-button:last-child {
border-right: none;
}
} }
+1 -1
View File
@@ -7,7 +7,7 @@ export type NodeDimensionChange = {
/* if this is true, the node is currently being resized via the NodeResizer */ /* if this is true, the node is currently being resized via the NodeResizer */
resizing?: boolean; resizing?: boolean;
/* if this is true, we will set width and height of the node and not just the measured dimensions */ /* if this is true, we will set width and height of the node and not just the measured dimensions */
setAttributes?: boolean; setAttributes?: boolean | 'width' | 'height';
}; };
export type NodePositionChange = { export type NodePositionChange = {
+20 -1
View File
@@ -184,7 +184,7 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra
*/ */
function parsePadding(padding: PaddingWithUnit, viewport: number): number { function parsePadding(padding: PaddingWithUnit, viewport: number): number {
if (typeof padding === 'number') { if (typeof padding === 'number') {
return Math.floor(viewport - viewport / (1 + padding)); return Math.floor((viewport - viewport / (1 + padding)) * 0.5);
} }
if (typeof padding === 'string' && padding.endsWith('px')) { if (typeof padding === 'string' && padding.endsWith('px')) {
@@ -399,3 +399,22 @@ export function areSetsEqual(a: Set<string>, b: Set<string>) {
return true; return true;
} }
/**
* Polyfill for Promise.withResolvers until we can use it in all browsers
* @internal
*/
export function withResolvers<T>(): {
promise: Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
+4 -2
View File
@@ -88,7 +88,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
): boolean { ): boolean {
const _options = mergeObjects(adoptUserNodesDefaultOptions, options); const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
let nodesInitialized = true; let nodesInitialized = nodes.length > 0;
const tmpLookup = new Map(nodeLookup); const tmpLookup = new Map(nodeLookup);
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? 1000 : 0; const selectedNodeZ: number = _options?.elevateNodesOnSelect ? 1000 : 0;
@@ -125,7 +125,9 @@ export function adoptUserNodes<NodeType extends NodeBase>(
} }
if ( if (
(!internalNode.measured || !internalNode.measured.width || !internalNode.measured.height) && (internalNode.measured === undefined ||
internalNode.measured.width === undefined ||
internalNode.measured.height === undefined) &&
!internalNode.hidden !internalNode.hidden
) { ) {
nodesInitialized = false; nodesInitialized = false;
+15 -3
View File
@@ -12,7 +12,15 @@ import type {
Transform, Transform,
XYPosition, XYPosition,
} from '../types'; } from '../types';
import type { OnResize, OnResizeEnd, OnResizeStart, ResizeDragEvent, ShouldResize, ControlPosition } from './types'; import type {
OnResize,
OnResizeEnd,
OnResizeStart,
ResizeDragEvent,
ShouldResize,
ControlPosition,
ResizeControlDirection,
} from './types';
const initPrevValues = { width: 0, height: 0, x: 0, y: 0 }; const initPrevValues = { width: 0, height: 0, x: 0, y: 0 };
@@ -60,6 +68,7 @@ type XYResizerUpdateParams = {
maxHeight: number; maxHeight: number;
}; };
keepAspectRatio: boolean; keepAspectRatio: boolean;
resizeDirection?: ResizeControlDirection;
onResizeStart: OnResizeStart | undefined; onResizeStart: OnResizeStart | undefined;
onResize: OnResize | undefined; onResize: OnResize | undefined;
onResizeEnd: OnResizeEnd | undefined; onResizeEnd: OnResizeEnd | undefined;
@@ -99,6 +108,7 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
controlPosition, controlPosition,
boundaries, boundaries,
keepAspectRatio, keepAspectRatio,
resizeDirection,
onResizeStart, onResizeStart,
onResize, onResize,
onResizeEnd, onResizeEnd,
@@ -251,8 +261,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
} }
if (isWidthChange || isHeightChange) { if (isWidthChange || isHeightChange) {
change.width = isWidthChange ? width : prevValues.width; change.width =
change.height = isHeightChange ? height : prevValues.height; isWidthChange && (!resizeDirection || resizeDirection === 'horizontal') ? width : prevValues.width;
change.height =
isHeightChange && (!resizeDirection || resizeDirection === 'vertical') ? height : prevValues.height;
prevValues.width = change.width; prevValues.width = change.width;
prevValues.height = change.height; prevValues.height = change.height;
} }
+6
View File
@@ -35,6 +35,12 @@ export enum ResizeControlVariant {
Handle = 'handle', Handle = 'handle',
} }
/**
* The direction the user can resize the node.
* @public
*/
export type ResizeControlDirection = 'horizontal' | 'vertical';
export const XY_RESIZER_HANDLE_POSITIONS: ControlPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']; export const XY_RESIZER_HANDLE_POSITIONS: ControlPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
export const XY_RESIZER_LINE_POSITIONS: ControlLinePosition[] = ['top', 'right', 'bottom', 'left']; export const XY_RESIZER_LINE_POSITIONS: ControlLinePosition[] = ['top', 'right', 'bottom', 'left'];