Merge branch 'next' into handle-connection-fixes

This commit is contained in:
peterkogo
2024-02-27 17:02:09 +01:00
28 changed files with 362 additions and 60 deletions
@@ -81,6 +81,8 @@ const initialNodes: Node[] = [
id: '4', id: '4',
data: { label: 'Node 4' }, data: { label: 'Node 4' },
position: { x: 400, y: 200 }, position: { x: 400, y: 200 },
width: 200,
height: 50,
type: 'custom', type: 'custom',
}, },
]; ];
@@ -0,0 +1,21 @@
import { memo, useState } from 'react';
import { Handle, Position } from '@xyflow/react';
function CustomNode() {
const [text, setText] = useState('this is a pretty long text');
return (
<>
<Handle type="target" position={Position.Top} />
<div style={{ background: '#eee', padding: 10 }}>
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<div>text: {text}</div>
</div>
</div>
<Handle type="source" position={Position.Bottom} />
</>
);
}
export default memo(CustomNode);
@@ -0,0 +1,69 @@
import { useCallback } from 'react';
import {
ReactFlow,
addEdge,
useEdgesState,
useNodesState,
Background,
Controls,
type Connection,
type Edge,
type Node,
} from '@xyflow/react';
import CustomNode from './CustomNode';
import '@xyflow/react/dist/style.css';
const initialNodes: Node[] = [
{
id: '1',
data: {},
position: { x: 0, y: 0 },
initialWidth: 200,
initialHeight: 50,
type: 'custom',
},
{
id: '2',
data: {},
position: { x: 0, y: 200 },
width: 200,
initialHeight: 50,
type: 'custom',
},
];
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
const nodeTypes = {
custom: CustomNode,
};
function Flow() {
const [nodes, , onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
return (
<div style={{ width: 700, height: 400 }}>
<ReactFlow
nodes={nodes}
onNodesChange={onNodesChange}
edges={edges}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
width={700}
height={400}
debug
>
<Background />
<Controls />
</ReactFlow>
</div>
);
}
export default Flow;
@@ -0,0 +1,25 @@
<script lang="ts">
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
let text = 'some default text';
</script>
<Handle type="target" position={Position.Left} />
<div class="custom">
<div>
text: {text}
</div>
<input type="text" bind:value={text} />
</div>
<Handle type="source" position={Position.Right} />
<style>
.custom {
background-color: white;
padding: 10px;
border: 1px solid #777;
border-radius: 20px;
}
</style>
@@ -0,0 +1,39 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Background, BackgroundVariant, type Node, type Edge } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
import CustomNode from './CustomNode.svelte';
const nodes = writable<Node[]>([
{
id: '1',
position: { x: 0, y: 0 },
data: {},
initialWidth: 100,
initialHeight: 40,
type: 'input-node',
},
{
id: '2',
position: { x: 0, y: 150 },
data: {},
initialWidth: 100,
initialHeight: 40,
type: 'input-node',
},
]);
const edges = writable<Edge[]>([{ id: '1-2', source: '1', target: '2' }]);
const nodeTypes = {
'input-node': CustomNode,
};
</script>
<div style="height: 400px; width: 700px;">
<SvelteFlow {nodes} {edges} {nodeTypes} fitView width={700} height={400}>
<Controls />
<Background variant={BackgroundVariant.Dots} />
</SvelteFlow>
</div>
+20 -4
View File
@@ -1,6 +1,8 @@
--- ---
import ReactFlowApp from '../components/ReactFlowExample' import ReactFlowApp from '../components/ReactFlowExample';
import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte' import ReactFlowInitialApp from '../components/ReactFlowInitialExample';
import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte';
import SvelteFlowInitialApp from '../components/SvelteFlowInitialExample/index.svelte';
--- ---
<html lang="en"> <html lang="en">
@@ -18,10 +20,24 @@ import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte'
</style> </style>
</head> </head>
<body> <body>
<h2>React Flow</h2>
<p>no client hydration</p>
<ReactFlowApp />
<p>client hydration on load (client:load)</p>
<ReactFlowApp client:load />
<p>client hydration on load (client:load) and initialWidth / initialHeight</p>
<ReactFlowInitialApp client:load />
<h2>Svelte Flow</h2> <h2>Svelte Flow</h2>
<SvelteFlowApp /> <SvelteFlowApp />
<h2>React Flow</h2> <p>client hydration on load (client:load)</p>
<ReactFlowApp /> <SvelteFlowApp client:load />
<p>client hydration on load (client:load) and initialWidth / initialHeight</p>
<SvelteFlowInitialApp client:load />
</body> </body>
</html> </html>
+2 -1
View File
@@ -4,13 +4,14 @@
## ⚠️ Breaking changes ## ⚠️ Breaking changes
- `useNodesData` not only returns data objects but also the type and the id of the node. - `useNodesData` not only returns data objects but also the type and the id of the node
## Patch changes ## Patch changes
- better cursor defaults for the pane, nodes and edges - better cursor defaults for the pane, nodes and edges
- `disableKeyboardA11y` now also disables Enter and Escape for selecting/deselecting nodes and edges - `disableKeyboardA11y` now also disables Enter and Escape for selecting/deselecting nodes and edges
- fix bug where users couldn't drag a node after toggle nodes `hidden` attribute - fix bug where users couldn't drag a node after toggle nodes `hidden` attribute
- add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr
## 12.0.0-next.9 ## 12.0.0-next.9
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { ComponentType, memo } from 'react'; import { ComponentType, memo } from 'react';
import { NodeOrigin, getNodePositionWithOrigin } from '@xyflow/system'; import { NodeOrigin, getNodeDimensions, getNodePositionWithOrigin, nodeHasDimensions } from '@xyflow/system';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
@@ -94,16 +94,19 @@ function NodeComponentWrapperInner<NodeType extends Node>({
y, y,
}; };
}, shallow); }, shallow);
if (!node || node.hidden || !(node.computed?.width || node.width) || !(node.computed?.height || node.height)) {
if (!node || node.hidden || !nodeHasDimensions(node)) {
return null; return null;
} }
const { width, height } = getNodeDimensions(node);
return ( return (
<NodeComponent <NodeComponent
x={x} x={x}
y={y} y={y}
width={node.computed?.width ?? node.width ?? 0} width={width}
height={node.computed?.height ?? node.height ?? 0} height={height}
style={node.style} style={node.style}
selected={!!node.selected} selected={!!node.selected}
className={nodeClassNameFunc(node)} className={nodeClassNameFunc(node)}
@@ -5,9 +5,11 @@ import {
clampPosition, clampPosition,
elementSelectionKeys, elementSelectionKeys,
errorMessages, errorMessages,
getNodeDimensions,
getPositionWithOrigin, getPositionWithOrigin,
internalsSymbol, internalsSymbol,
isInputDOMNode, isInputDOMNode,
nodeHasDimensions,
} from '@xyflow/system'; } from '@xyflow/system';
import { useStore, useStoreApi } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
@@ -16,7 +18,7 @@ import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
import { useDrag } from '../../hooks/useDrag'; import { useDrag } from '../../hooks/useDrag';
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes'; import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
import { handleNodeClick } from '../Nodes/utils'; import { handleNodeClick } from '../Nodes/utils';
import { arrowKeyDiffs, builtinNodeTypes } from './utils'; import { arrowKeyDiffs, builtinNodeTypes, getNodeInlineStyleDimensions } from './utils';
import type { Node, NodeWrapperProps } from '../../types'; import type { Node, NodeWrapperProps } from '../../types';
export function NodeWrapper<NodeType extends Node>({ export function NodeWrapper<NodeType extends Node>({
@@ -79,11 +81,9 @@ export function NodeWrapper<NodeType extends Node>({
const prevTargetPosition = useRef(node.targetPosition); const prevTargetPosition = useRef(node.targetPosition);
const prevType = useRef(nodeType); const prevType = useRef(nodeType);
const width = node.width ?? undefined; const nodeDimensions = getNodeDimensions(node);
const height = node.height ?? undefined; const inlineDimensions = getNodeInlineStyleDimensions(node);
const computedWidth = node.computed?.width; const initialized = nodeHasDimensions(node);
const computedHeight = node.computed?.height;
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
const hasHandleBounds = !!node[internalsSymbol]?.handleBounds; const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
const moveSelectedNodes = useMoveSelectedNodes(); const moveSelectedNodes = useMoveSelectedNodes();
@@ -143,8 +143,7 @@ export function NodeWrapper<NodeType extends Node>({
const positionAbsoluteOrigin = getPositionWithOrigin({ const positionAbsoluteOrigin = getPositionWithOrigin({
x: positionAbsoluteX, x: positionAbsoluteX,
y: positionAbsoluteY, y: positionAbsoluteY,
width: computedWidth ?? width ?? 0, ...nodeDimensions,
height: computedHeight ?? height ?? 0,
origin: node.origin || nodeOrigin, origin: node.origin || nodeOrigin,
}); });
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
@@ -226,8 +225,7 @@ export function NodeWrapper<NodeType extends Node>({
pointerEvents: hasPointerEvents ? 'all' : 'none', pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: initialized ? 'visible' : 'hidden', visibility: initialized ? 'visible' : 'hidden',
...node.style, ...node.style,
width: width ?? node.style?.width, ...inlineDimensions,
height: height ?? node.style?.height,
}} }}
data-id={id} data-id={id}
data-testid={`rf__node-${id}`} data-testid={`rf__node-${id}`}
@@ -248,8 +246,6 @@ export function NodeWrapper<NodeType extends Node>({
id={id} id={id}
data={node.data} data={node.data}
type={nodeType} type={nodeType}
width={computedWidth}
height={computedHeight}
positionAbsoluteX={positionAbsoluteX} positionAbsoluteX={positionAbsoluteX}
positionAbsoluteY={positionAbsoluteY} positionAbsoluteY={positionAbsoluteY}
selected={node.selected} selected={node.selected}
@@ -259,6 +255,7 @@ export function NodeWrapper<NodeType extends Node>({
dragging={dragging} dragging={dragging}
dragHandle={node.dragHandle} dragHandle={node.dragHandle}
zIndex={zIndex} zIndex={zIndex}
{...nodeDimensions}
/> />
</Provider> </Provider>
</div> </div>
@@ -5,7 +5,7 @@ import { InputNode } from '../Nodes/InputNode';
import { DefaultNode } from '../Nodes/DefaultNode'; import { DefaultNode } from '../Nodes/DefaultNode';
import { GroupNode } from '../Nodes/GroupNode'; import { GroupNode } from '../Nodes/GroupNode';
import { OutputNode } from '../Nodes/OutputNode'; import { OutputNode } from '../Nodes/OutputNode';
import type { NodeTypes } from '../../types'; import type { Node, NodeTypes } from '../../types';
export const arrowKeyDiffs: Record<string, XYPosition> = { export const arrowKeyDiffs: Record<string, XYPosition> = {
ArrowUp: { x: 0, y: -1 }, ArrowUp: { x: 0, y: -1 },
@@ -20,3 +20,22 @@ export const builtinNodeTypes: NodeTypes = {
output: OutputNode as ComponentType<NodeProps>, output: OutputNode as ComponentType<NodeProps>,
group: GroupNode as ComponentType<NodeProps>, group: GroupNode as ComponentType<NodeProps>,
}; };
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
node: NodeType
): {
width: number | string | undefined;
height: number | string | undefined;
} {
if (!node.computed) {
return {
width: node.width ?? node.initialWidth ?? node.style?.width,
height: node.height ?? node.initialHeight ?? node.style?.height,
};
}
return {
width: node.width ?? node.style?.width,
height: node.height ?? node.style?.height,
};
}
@@ -65,6 +65,7 @@ const reactFlowFieldsToTrack = [
'selectNodesOnDrag', 'selectNodesOnDrag',
'nodeDragThreshold', 'nodeDragThreshold',
'onBeforeDelete', 'onBeforeDelete',
'debug',
] as const; ] as const;
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
@@ -139,6 +139,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
width, width,
height, height,
colorMode = 'light', colorMode = 'light',
debug,
...rest ...rest
}: ReactFlowProps<NodeType, EdgeType>, }: ReactFlowProps<NodeType, EdgeType>,
ref: ForwardedRef<ReactFlowRefType> ref: ForwardedRef<ReactFlowRefType>
@@ -274,6 +275,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
selectNodesOnDrag={selectNodesOnDrag} selectNodesOnDrag={selectNodesOnDrag}
nodeDragThreshold={nodeDragThreshold} nodeDragThreshold={nodeDragThreshold}
onBeforeDelete={onBeforeDelete} onBeforeDelete={onBeforeDelete}
debug={debug}
/> />
<SelectionListener onSelectionChange={onSelectionChange} /> <SelectionListener onSelectionChange={onSelectionChange} />
{children} {children}
+14 -2
View File
@@ -89,6 +89,7 @@ const createRFStore = ({
fitViewOnInitOptions, fitViewOnInitOptions,
domNode, domNode,
nodeOrigin, nodeOrigin,
debug,
} = get(); } = get();
const changes: NodeDimensionChange[] = []; const changes: NodeDimensionChange[] = [];
@@ -130,6 +131,9 @@ const createRFStore = ({
set({ nodes: nextNodes, fitViewDone: nextFitViewDone }); set({ nodes: nextNodes, fitViewDone: nextFitViewDone });
if (changes?.length > 0) { if (changes?.length > 0) {
if (debug) {
console.log('React Flow: trigger node changes', changes);
}
onNodesChange?.(changes); onNodesChange?.(changes);
} }
}, },
@@ -149,7 +153,7 @@ const createRFStore = ({
get().triggerNodeChanges(changes); get().triggerNodeChanges(changes);
}, },
triggerNodeChanges: (changes) => { triggerNodeChanges: (changes) => {
const { onNodesChange, setNodes, nodes, hasDefaultNodes } = get(); const { onNodesChange, setNodes, nodes, hasDefaultNodes, debug } = get();
if (changes?.length) { if (changes?.length) {
if (hasDefaultNodes) { if (hasDefaultNodes) {
@@ -157,11 +161,15 @@ const createRFStore = ({
setNodes(updatedNodes); setNodes(updatedNodes);
} }
if (debug) {
console.log('React Flow: trigger node changes', changes);
}
onNodesChange?.(changes); onNodesChange?.(changes);
} }
}, },
triggerEdgeChanges: (changes) => { triggerEdgeChanges: (changes) => {
const { onEdgesChange, setEdges, edges, hasDefaultEdges } = get(); const { onEdgesChange, setEdges, edges, hasDefaultEdges, debug } = get();
if (changes?.length) { if (changes?.length) {
if (hasDefaultEdges) { if (hasDefaultEdges) {
@@ -169,6 +177,10 @@ const createRFStore = ({
setEdges(updatedEdges); setEdges(updatedEdges);
} }
if (debug) {
console.log('React Flow: trigger edge changes', changes);
}
onEdgesChange?.(changes); onEdgesChange?.(changes);
} }
}, },
+4 -1
View File
@@ -43,7 +43,9 @@ const getInitialState = ({
let transform: Transform = [0, 0, 1]; let transform: Transform = [0, 0, 1];
if (fitView && width && height) { if (fitView && width && height) {
const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height); const nodesWithDimensions = nextNodes.filter(
(node) => (node.width || node.initialWidth) && (node.height || node.initialHeight)
);
// @todo users nodeOrigin should be used here // @todo users nodeOrigin should be used here
const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] }); const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
@@ -113,6 +115,7 @@ const getInitialState = ({
onSelectionChangeHandlers: [], onSelectionChangeHandlers: [],
lib: 'react', lib: 'react',
debug: false,
}; };
}; };
@@ -503,6 +503,11 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
* @example 'system' | 'light' | 'dark' * @example 'system' | 'light' | 'dark'
*/ */
colorMode?: ColorMode; colorMode?: ColorMode;
/** If set true, some debug information will be logged to the console like which events are fired.
*
* @default undefined
*/
debug?: boolean;
} }
export type ReactFlowRefType = HTMLDivElement; export type ReactFlowRefType = HTMLDivElement;
+1
View File
@@ -146,6 +146,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
isValidConnection?: IsValidConnection<EdgeType>; isValidConnection?: IsValidConnection<EdgeType>;
lib: string; lib: string;
debug: boolean;
}; };
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = { export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
+3 -2
View File
@@ -4,11 +4,12 @@
## ⚠️ Breaking changes ## ⚠️ Breaking changes
- `useNodesData` not only returns data objects but also the type and the id of the node. - `useNodesData` not only returns data objects but also the type and the id of the node
## Patch changes ## Patch changes
- better cursor defaults for the pane, nodes and edges. - better cursor defaults for the pane, nodes and edges
- add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr
## 0.0.36 ## 0.0.36
@@ -11,6 +11,7 @@
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import type { NodeWrapperProps } from './types'; import type { NodeWrapperProps } from './types';
import type { Node } from '$lib/types'; import type { Node } from '$lib/types';
import { getNodeInlineStyleDimensions } from './utils';
interface $$Props extends NodeWrapperProps {} interface $$Props extends NodeWrapperProps {}
@@ -34,6 +35,10 @@
export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined; export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined;
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined; export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
export let zIndex: NodeWrapperProps['zIndex']; export let zIndex: NodeWrapperProps['zIndex'];
export let computedWidth: NodeWrapperProps['computedWidth'] = undefined;
export let computedHeight: NodeWrapperProps['computedHeight'] = undefined;
export let initialWidth: NodeWrapperProps['initialWidth'] = undefined;
export let initialHeight: NodeWrapperProps['initialHeight'] = undefined;
export let width: NodeWrapperProps['width'] = undefined; export let width: NodeWrapperProps['width'] = undefined;
export let height: NodeWrapperProps['height'] = undefined; export let height: NodeWrapperProps['height'] = undefined;
export let dragHandle: NodeWrapperProps['dragHandle'] = undefined; export let dragHandle: NodeWrapperProps['dragHandle'] = undefined;
@@ -77,6 +82,15 @@
let prevSourcePosition: Position | undefined = undefined; let prevSourcePosition: Position | undefined = undefined;
let prevTargetPosition: Position | undefined = undefined; let prevTargetPosition: Position | undefined = undefined;
$: inlineStyleDimensions = getNodeInlineStyleDimensions({
width,
height,
initialWidth,
initialHeight,
computedWidth,
computedHeight
});
$: { $: {
connectableStore.set(!!connectable); connectableStore.set(!!connectable);
} }
@@ -170,9 +184,7 @@
style:z-index={zIndex} style:z-index={zIndex}
style:transform="translate({positionOriginX}px, {positionOriginY}px)" style:transform="translate({positionOriginX}px, {positionOriginY}px)"
style:visibility={initialized ? 'visible' : 'hidden'} style:visibility={initialized ? 'visible' : 'hidden'}
style="{style ?? ''}; {!width ? '' : `width:${width}px;`} {!height style="{inlineStyleDimensions.width} {inlineStyleDimensions.height} {style ?? ''};"
? ''
: `height:${height}px;`}"
on:click={onSelectNodeHandler} on:click={onSelectNodeHandler}
on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })} on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })}
on:mouseleave={(event) => dispatch('nodemouseleave', { node, event })} on:mouseleave={(event) => dispatch('nodemouseleave', { node, event })}
@@ -16,9 +16,13 @@ export type NodeWrapperProps = Pick<
| 'targetPosition' | 'targetPosition'
| 'dragHandle' | 'dragHandle'
| 'hidden' | 'hidden'
| 'width'
| 'height'
| 'initialWidth'
| 'initialHeight'
> & { > & {
width?: number; computedWidth?: number;
height?: number; computedHeight?: number;
type: string; type: string;
positionX: number; positionX: number;
positionY: number; positionY: number;
@@ -0,0 +1,33 @@
export function getNodeInlineStyleDimensions({
width,
height,
initialWidth,
initialHeight,
computedWidth,
computedHeight
}: {
width?: number;
height?: number;
initialWidth?: number;
initialHeight?: number;
computedWidth?: number;
computedHeight?: number;
}): {
width: string | undefined;
height: string | undefined;
} {
if (computedWidth === undefined && computedHeight === undefined) {
const styleWidth = width ?? initialWidth;
const styleHeight = height ?? initialHeight;
return {
width: styleWidth ? `width:${styleWidth}px;` : '',
height: styleHeight ? `height:${styleHeight}px;` : ''
};
}
return {
width: width ? `width:${width}px;` : '',
height: height ? `height:${height}px;` : ''
};
}
@@ -1,6 +1,11 @@
<script lang="ts"> <script lang="ts">
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { internalsSymbol, getPositionWithOrigin } from '@xyflow/system'; import {
internalsSymbol,
getPositionWithOrigin,
getNodeDimensions,
nodeHasDimensions
} from '@xyflow/system';
import { NodeWrapper } from '$lib/components/NodeWrapper'; import { NodeWrapper } from '$lib/components/NodeWrapper';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
@@ -39,11 +44,11 @@
<div class="svelte-flow__nodes"> <div class="svelte-flow__nodes">
{#each $visibleNodes as node (node.id)} {#each $visibleNodes as node (node.id)}
{@const nodeDimesions = getNodeDimensions(node)}
{@const posOrigin = getPositionWithOrigin({ {@const posOrigin = getPositionWithOrigin({
x: node.computed?.positionAbsolute?.x ?? 0, x: node.computed?.positionAbsolute?.x ?? 0,
y: node.computed?.positionAbsolute?.y ?? 0, y: node.computed?.positionAbsolute?.y ?? 0,
width: node.computed?.width ?? node.width ?? 0, ...nodeDimesions,
height: node.computed?.height ?? node.height ?? 0,
origin: node.origin origin: node.origin
})} })}
<NodeWrapper <NodeWrapper
@@ -74,10 +79,13 @@
dragging={node.dragging} dragging={node.dragging}
zIndex={node[internalsSymbol]?.z ?? 0} zIndex={node[internalsSymbol]?.z ?? 0}
dragHandle={node.dragHandle} dragHandle={node.dragHandle}
width={node.width ?? undefined} initialized={nodeHasDimensions(node)}
height={node.height ?? undefined} width={node.width}
initialized={(!!node.computed?.width && !!node.computed?.height) || height={node.height}
(!!node.width && !!node.height)} initialWidth={node.initialWidth}
initialHeight={node.initialHeight}
computedWidth={node.computed?.width}
computedHeight={node.computed?.height}
{resizeObserver} {resizeObserver}
on:nodeclick on:nodeclick
on:nodemouseenter on:nodemouseenter
@@ -7,7 +7,13 @@
<script lang="ts"> <script lang="ts">
import cc from 'classcat'; import cc from 'classcat';
import { getBoundsOfRects, getNodePositionWithOrigin, getNodesBounds } from '@xyflow/system'; import {
getBoundsOfRects,
getNodeDimensions,
getNodePositionWithOrigin,
getNodesBounds,
nodeHasDimensions
} from '@xyflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
import { Panel } from '$lib/container/Panel'; import { Panel } from '$lib/container/Panel';
@@ -116,13 +122,13 @@
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if} {#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
{#each $nodes as node (node.id)} {#each $nodes as node (node.id)}
{#if (node.computed?.width || node?.width) && (node.computed?.height || node.height)} {#if nodeHasDimensions(node)}
{@const pos = getNodePositionWithOrigin(node).positionAbsolute} {@const pos = getNodePositionWithOrigin(node).positionAbsolute}
{@const nodeDimesions = getNodeDimensions(node)}
<MinimapNode <MinimapNode
x={pos.x} x={pos.x}
y={pos.y} y={pos.y}
width={node.computed?.width ?? node.width ?? 0} {...nodeDimesions}
height={node.computed?.height ?? node.height ?? 0}
selected={node.selected} selected={node.selected}
color={nodeColorFunc?.(node)} color={nodeColorFunc?.(node)}
borderRadius={nodeBorderRadius} borderRadius={nodeBorderRadius}
-2
View File
@@ -1,7 +1,6 @@
import { getContext, setContext } from 'svelte'; import { getContext, setContext } from 'svelte';
import { derived, get, writable } from 'svelte/store'; import { derived, get, writable } from 'svelte/store';
import { import {
internalsSymbol,
createMarkerIds, createMarkerIds,
fitView as fitViewUtil, fitView as fitViewUtil,
getElementsToRemove, getElementsToRemove,
@@ -15,7 +14,6 @@ import {
type XYPosition, type XYPosition,
type CoordinateExtent, type CoordinateExtent,
type UpdateConnection, type UpdateConnection,
type NodeDragItem,
errorMessages errorMessages
} from '@xyflow/system'; } from '@xyflow/system';
@@ -91,7 +91,9 @@ export const getInitialStore = ({
let viewport: Viewport = { x: 0, y: 0, zoom: 1 }; let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
if (fitView && width && height) { if (fitView && width && height) {
const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height); const nodesWithDimensions = nextNodes.filter(
(node) => (node.width && node.height) || (node.initialWidth && node.initialHeight)
);
// @todo users nodeOrigin should be used here // @todo users nodeOrigin should be used here
const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] }); const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
@@ -101,7 +103,7 @@ export const getInitialStore = ({
flowId: writable<string | null>(null), flowId: writable<string | null>(null),
nodes: createNodesStore(nextNodes, nodeLookup), nodes: createNodesStore(nextNodes, nodeLookup),
nodeLookup: readable<NodeLookup<Node>>(nodeLookup), nodeLookup: readable<NodeLookup<Node>>(nodeLookup),
edgeLookup: readable<EdgeLookup>(edgeLookup), edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
visibleNodes: readable<Node[]>([]), visibleNodes: readable<Node[]>([]),
edges: createEdgesStore(edges, connectionLookup, edgeLookup), edges: createEdgesStore(edges, connectionLookup, edgeLookup),
visibleEdges: readable<EdgeLayouted[]>([]), visibleEdges: readable<EdgeLayouted[]>([]),
+4 -2
View File
@@ -39,8 +39,10 @@ export type NodeBase<
connectable?: boolean; connectable?: boolean;
deletable?: boolean; deletable?: boolean;
dragHandle?: string; dragHandle?: string;
width?: number | null; width?: number;
height?: number | null; height?: number;
initialWidth?: number;
initialHeight?: number;
/** Parent node id, used for creating sub-flows */ /** Parent node id, used for creating sub-flows */
parentNode?: string; parentNode?: string;
zIndex?: number; zIndex?: number;
+8 -5
View File
@@ -4,6 +4,7 @@ import { NodeBase, NodeHandle } from '../../types/nodes';
import { Position } from '../../types/utils'; import { Position } from '../../types/utils';
import { errorMessages, internalsSymbol } from '../../constants'; import { errorMessages, internalsSymbol } from '../../constants';
import { HandleElement } from '../../types'; import { HandleElement } from '../../types';
import { getNodeDimensions } from '../general';
export type GetEdgePositionParams = { export type GetEdgePositionParams = {
id: string; id: string;
@@ -16,7 +17,10 @@ export type GetEdgePositionParams = {
}; };
function isNodeInitialized(node: NodeBase): boolean { function isNodeInitialized(node: NodeBase): boolean {
return !!(node?.[internalsSymbol]?.handleBounds || node?.handles?.length) && !!(node?.computed?.width || node?.width); return (
!!(node?.[internalsSymbol]?.handleBounds || node?.handles?.length) &&
!!(node?.computed?.width || node?.width || node?.initialWidth)
);
} }
export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null { export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null {
@@ -75,8 +79,8 @@ function toHandleBounds(handles?: NodeHandle[]) {
const target = []; const target = [];
for (const handle of handles) { for (const handle of handles) {
handle.width = handle.width || 1; handle.width = handle.width ?? 1;
handle.height = handle.height || 1; handle.height = handle.height ?? 1;
if (handle.type === 'source') { if (handle.type === 'source') {
source.push(handle as HandleElement); source.push(handle as HandleElement);
@@ -94,8 +98,7 @@ function toHandleBounds(handles?: NodeHandle[]) {
function getHandlePosition(position: Position, node: NodeBase, handle: HandleElement | null = null): number[] { function getHandlePosition(position: Position, node: NodeBase, handle: HandleElement | null = null): number[] {
const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0); const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0);
const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0); const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0);
const width = handle?.width || (node?.computed?.width ?? node?.width ?? 0); const { width, height } = handle ?? getNodeDimensions(node);
const height = handle?.height || (node?.computed?.height ?? node?.height ?? 0);
switch (position) { switch (position) {
case Position.Top: case Position.Top:
+16
View File
@@ -202,3 +202,19 @@ export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.user
export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent is CoordinateExtent { export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent is CoordinateExtent {
return extent !== undefined && extent !== 'parent'; return extent !== undefined && extent !== 'parent';
} }
export function getNodeDimensions<NodeType extends NodeBase = NodeBase>(
node: NodeType
): { width: number; height: number } {
return {
width: node.computed?.width ?? node.width ?? node.initialWidth ?? 0,
height: node.computed?.height ?? node.height ?? node.initialHeight ?? 0,
};
}
export function nodeHasDimensions<NodeType extends NodeBase = NodeBase>(node: NodeType): boolean {
return (
(node.computed?.width ?? node.width ?? node.initialWidth) !== undefined &&
(node.computed?.height ?? node.height ?? node.initialHeight) !== undefined
);
}
+7 -6
View File
@@ -9,6 +9,7 @@ import {
pointToRendererPoint, pointToRendererPoint,
getViewportForBounds, getViewportForBounds,
isCoordinateExtent, isCoordinateExtent,
getNodeDimensions,
} from './general'; } from './general';
import { import {
type Transform, type Transform,
@@ -116,8 +117,9 @@ export const getNodePositionWithOrigin = (
}; };
} }
const offsetX = (node.computed?.width ?? node.width ?? 0) * nodeOrigin[0]; const { width, height } = getNodeDimensions(node);
const offsetY = (node.computed?.height ?? node.height ?? 0) * nodeOrigin[1]; const offsetX = width * nodeOrigin[0];
const offsetY = height * nodeOrigin[1];
const position: XYPosition = { const position: XYPosition = {
x: node.position.x - offsetX, x: node.position.x - offsetX,
@@ -164,8 +166,7 @@ export const getNodesBounds = (
currBox, currBox,
rectToBox({ rectToBox({
...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'], ...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
width: node.computed?.width ?? node.width ?? 0, ...getNodeDimensions(node),
height: node.computed?.height ?? node.height ?? 0,
}) })
); );
}, },
@@ -192,8 +193,8 @@ export const getNodesInside = <NodeType extends NodeBase>(
const visibleNodes = nodes.reduce<NodeType[]>((res, node) => { const visibleNodes = nodes.reduce<NodeType[]>((res, node) => {
const { computed, selectable = true, hidden = false } = node; const { computed, selectable = true, hidden = false } = node;
const width = computed?.width ?? node.width ?? null; const width = computed?.width ?? node.width ?? node.initialWidth ?? null;
const height = computed?.height ?? node.height ?? null; const height = computed?.height ?? node.height ?? node.initialHeight ?? null;
if ((excludeNonSelectableNodes && !selectable) || hidden) { if ((excludeNonSelectableNodes && !selectable) || hidden) {
return res; return res;