Merge branch 'next' into handle-connection-fixes
This commit is contained in:
@@ -81,6 +81,8 @@ const initialNodes: Node[] = [
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
width: 200,
|
||||
height: 50,
|
||||
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>
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import ReactFlowApp from '../components/ReactFlowExample'
|
||||
import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte'
|
||||
import ReactFlowApp from '../components/ReactFlowExample';
|
||||
import ReactFlowInitialApp from '../components/ReactFlowInitialExample';
|
||||
import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte';
|
||||
import SvelteFlowInitialApp from '../components/SvelteFlowInitialExample/index.svelte';
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
@@ -18,10 +20,24 @@ import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte'
|
||||
</style>
|
||||
</head>
|
||||
<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>
|
||||
<SvelteFlowApp />
|
||||
|
||||
<h2>React Flow</h2>
|
||||
<ReactFlowApp />
|
||||
<p>client hydration on load (client:load)</p>
|
||||
<SvelteFlowApp client:load />
|
||||
|
||||
<p>client hydration on load (client:load) and initialWidth / initialHeight</p>
|
||||
<SvelteFlowInitialApp client:load />
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
|
||||
## ⚠️ 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
|
||||
|
||||
- better cursor defaults for the pane, 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
|
||||
- add `initialWidth` and `initialHeight` node attributes for specifying initial dimensions for ssr
|
||||
|
||||
## 12.0.0-next.9
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
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 { useStore } from '../../hooks/useStore';
|
||||
@@ -94,16 +94,19 @@ function NodeComponentWrapperInner<NodeType extends Node>({
|
||||
y,
|
||||
};
|
||||
}, shallow);
|
||||
if (!node || node.hidden || !(node.computed?.width || node.width) || !(node.computed?.height || node.height)) {
|
||||
|
||||
if (!node || node.hidden || !nodeHasDimensions(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { width, height } = getNodeDimensions(node);
|
||||
|
||||
return (
|
||||
<NodeComponent
|
||||
x={x}
|
||||
y={y}
|
||||
width={node.computed?.width ?? node.width ?? 0}
|
||||
height={node.computed?.height ?? node.height ?? 0}
|
||||
width={width}
|
||||
height={height}
|
||||
style={node.style}
|
||||
selected={!!node.selected}
|
||||
className={nodeClassNameFunc(node)}
|
||||
|
||||
@@ -5,9 +5,11 @@ import {
|
||||
clampPosition,
|
||||
elementSelectionKeys,
|
||||
errorMessages,
|
||||
getNodeDimensions,
|
||||
getPositionWithOrigin,
|
||||
internalsSymbol,
|
||||
isInputDOMNode,
|
||||
nodeHasDimensions,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
@@ -16,7 +18,7 @@ import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
|
||||
import { useDrag } from '../../hooks/useDrag';
|
||||
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
|
||||
import { handleNodeClick } from '../Nodes/utils';
|
||||
import { arrowKeyDiffs, builtinNodeTypes } from './utils';
|
||||
import { arrowKeyDiffs, builtinNodeTypes, getNodeInlineStyleDimensions } from './utils';
|
||||
import type { Node, NodeWrapperProps } from '../../types';
|
||||
|
||||
export function NodeWrapper<NodeType extends Node>({
|
||||
@@ -79,11 +81,9 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
const prevTargetPosition = useRef(node.targetPosition);
|
||||
const prevType = useRef(nodeType);
|
||||
|
||||
const width = node.width ?? undefined;
|
||||
const height = node.height ?? undefined;
|
||||
const computedWidth = node.computed?.width;
|
||||
const computedHeight = node.computed?.height;
|
||||
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
|
||||
const nodeDimensions = getNodeDimensions(node);
|
||||
const inlineDimensions = getNodeInlineStyleDimensions(node);
|
||||
const initialized = nodeHasDimensions(node);
|
||||
const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
|
||||
|
||||
const moveSelectedNodes = useMoveSelectedNodes();
|
||||
@@ -143,8 +143,7 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
const positionAbsoluteOrigin = getPositionWithOrigin({
|
||||
x: positionAbsoluteX,
|
||||
y: positionAbsoluteY,
|
||||
width: computedWidth ?? width ?? 0,
|
||||
height: computedHeight ?? height ?? 0,
|
||||
...nodeDimensions,
|
||||
origin: node.origin || nodeOrigin,
|
||||
});
|
||||
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||
@@ -226,8 +225,7 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
||||
visibility: initialized ? 'visible' : 'hidden',
|
||||
...node.style,
|
||||
width: width ?? node.style?.width,
|
||||
height: height ?? node.style?.height,
|
||||
...inlineDimensions,
|
||||
}}
|
||||
data-id={id}
|
||||
data-testid={`rf__node-${id}`}
|
||||
@@ -248,8 +246,6 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
id={id}
|
||||
data={node.data}
|
||||
type={nodeType}
|
||||
width={computedWidth}
|
||||
height={computedHeight}
|
||||
positionAbsoluteX={positionAbsoluteX}
|
||||
positionAbsoluteY={positionAbsoluteY}
|
||||
selected={node.selected}
|
||||
@@ -259,6 +255,7 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
dragging={dragging}
|
||||
dragHandle={node.dragHandle}
|
||||
zIndex={zIndex}
|
||||
{...nodeDimensions}
|
||||
/>
|
||||
</Provider>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { InputNode } from '../Nodes/InputNode';
|
||||
import { DefaultNode } from '../Nodes/DefaultNode';
|
||||
import { GroupNode } from '../Nodes/GroupNode';
|
||||
import { OutputNode } from '../Nodes/OutputNode';
|
||||
import type { NodeTypes } from '../../types';
|
||||
import type { Node, NodeTypes } from '../../types';
|
||||
|
||||
export const arrowKeyDiffs: Record<string, XYPosition> = {
|
||||
ArrowUp: { x: 0, y: -1 },
|
||||
@@ -20,3 +20,22 @@ export const builtinNodeTypes: NodeTypes = {
|
||||
output: OutputNode 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',
|
||||
'nodeDragThreshold',
|
||||
'onBeforeDelete',
|
||||
'debug',
|
||||
] as const;
|
||||
|
||||
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
|
||||
|
||||
@@ -139,6 +139,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
width,
|
||||
height,
|
||||
colorMode = 'light',
|
||||
debug,
|
||||
...rest
|
||||
}: ReactFlowProps<NodeType, EdgeType>,
|
||||
ref: ForwardedRef<ReactFlowRefType>
|
||||
@@ -274,6 +275,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
selectNodesOnDrag={selectNodesOnDrag}
|
||||
nodeDragThreshold={nodeDragThreshold}
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
debug={debug}
|
||||
/>
|
||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
|
||||
@@ -89,6 +89,7 @@ const createRFStore = ({
|
||||
fitViewOnInitOptions,
|
||||
domNode,
|
||||
nodeOrigin,
|
||||
debug,
|
||||
} = get();
|
||||
const changes: NodeDimensionChange[] = [];
|
||||
|
||||
@@ -130,6 +131,9 @@ const createRFStore = ({
|
||||
set({ nodes: nextNodes, fitViewDone: nextFitViewDone });
|
||||
|
||||
if (changes?.length > 0) {
|
||||
if (debug) {
|
||||
console.log('React Flow: trigger node changes', changes);
|
||||
}
|
||||
onNodesChange?.(changes);
|
||||
}
|
||||
},
|
||||
@@ -149,7 +153,7 @@ const createRFStore = ({
|
||||
get().triggerNodeChanges(changes);
|
||||
},
|
||||
triggerNodeChanges: (changes) => {
|
||||
const { onNodesChange, setNodes, nodes, hasDefaultNodes } = get();
|
||||
const { onNodesChange, setNodes, nodes, hasDefaultNodes, debug } = get();
|
||||
|
||||
if (changes?.length) {
|
||||
if (hasDefaultNodes) {
|
||||
@@ -157,11 +161,15 @@ const createRFStore = ({
|
||||
setNodes(updatedNodes);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
console.log('React Flow: trigger node changes', changes);
|
||||
}
|
||||
|
||||
onNodesChange?.(changes);
|
||||
}
|
||||
},
|
||||
triggerEdgeChanges: (changes) => {
|
||||
const { onEdgesChange, setEdges, edges, hasDefaultEdges } = get();
|
||||
const { onEdgesChange, setEdges, edges, hasDefaultEdges, debug } = get();
|
||||
|
||||
if (changes?.length) {
|
||||
if (hasDefaultEdges) {
|
||||
@@ -169,6 +177,10 @@ const createRFStore = ({
|
||||
setEdges(updatedEdges);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
console.log('React Flow: trigger edge changes', changes);
|
||||
}
|
||||
|
||||
onEdgesChange?.(changes);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,7 +43,9 @@ const getInitialState = ({
|
||||
let transform: Transform = [0, 0, 1];
|
||||
|
||||
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
|
||||
const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
|
||||
const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||
@@ -113,6 +115,7 @@ const getInitialState = ({
|
||||
onSelectionChangeHandlers: [],
|
||||
|
||||
lib: 'react',
|
||||
debug: false,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -503,6 +503,11 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* @example 'system' | 'light' | 'dark'
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -146,6 +146,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
isValidConnection?: IsValidConnection<EdgeType>;
|
||||
|
||||
lib: string;
|
||||
debug: boolean;
|
||||
};
|
||||
|
||||
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
## ⚠️ 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
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||
import type { NodeWrapperProps } from './types';
|
||||
import type { Node } from '$lib/types';
|
||||
import { getNodeInlineStyleDimensions } from './utils';
|
||||
|
||||
interface $$Props extends NodeWrapperProps {}
|
||||
|
||||
@@ -34,6 +35,10 @@
|
||||
export let sourcePosition: NodeWrapperProps['sourcePosition'] = undefined;
|
||||
export let targetPosition: NodeWrapperProps['targetPosition'] = undefined;
|
||||
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 height: NodeWrapperProps['height'] = undefined;
|
||||
export let dragHandle: NodeWrapperProps['dragHandle'] = undefined;
|
||||
@@ -77,6 +82,15 @@
|
||||
let prevSourcePosition: Position | undefined = undefined;
|
||||
let prevTargetPosition: Position | undefined = undefined;
|
||||
|
||||
$: inlineStyleDimensions = getNodeInlineStyleDimensions({
|
||||
width,
|
||||
height,
|
||||
initialWidth,
|
||||
initialHeight,
|
||||
computedWidth,
|
||||
computedHeight
|
||||
});
|
||||
|
||||
$: {
|
||||
connectableStore.set(!!connectable);
|
||||
}
|
||||
@@ -170,9 +184,7 @@
|
||||
style:z-index={zIndex}
|
||||
style:transform="translate({positionOriginX}px, {positionOriginY}px)"
|
||||
style:visibility={initialized ? 'visible' : 'hidden'}
|
||||
style="{style ?? ''}; {!width ? '' : `width:${width}px;`} {!height
|
||||
? ''
|
||||
: `height:${height}px;`}"
|
||||
style="{inlineStyleDimensions.width} {inlineStyleDimensions.height} {style ?? ''};"
|
||||
on:click={onSelectNodeHandler}
|
||||
on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })}
|
||||
on:mouseleave={(event) => dispatch('nodemouseleave', { node, event })}
|
||||
|
||||
@@ -16,9 +16,13 @@ export type NodeWrapperProps = Pick<
|
||||
| 'targetPosition'
|
||||
| 'dragHandle'
|
||||
| 'hidden'
|
||||
| 'width'
|
||||
| 'height'
|
||||
| 'initialWidth'
|
||||
| 'initialHeight'
|
||||
> & {
|
||||
width?: number;
|
||||
height?: number;
|
||||
computedWidth?: number;
|
||||
computedHeight?: number;
|
||||
type: string;
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
|
||||
33
packages/svelte/src/lib/components/NodeWrapper/utils.ts
Normal file
33
packages/svelte/src/lib/components/NodeWrapper/utils.ts
Normal file
@@ -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">
|
||||
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 { useStore } from '$lib/store';
|
||||
@@ -39,11 +44,11 @@
|
||||
|
||||
<div class="svelte-flow__nodes">
|
||||
{#each $visibleNodes as node (node.id)}
|
||||
{@const nodeDimesions = getNodeDimensions(node)}
|
||||
{@const posOrigin = getPositionWithOrigin({
|
||||
x: node.computed?.positionAbsolute?.x ?? 0,
|
||||
y: node.computed?.positionAbsolute?.y ?? 0,
|
||||
width: node.computed?.width ?? node.width ?? 0,
|
||||
height: node.computed?.height ?? node.height ?? 0,
|
||||
...nodeDimesions,
|
||||
origin: node.origin
|
||||
})}
|
||||
<NodeWrapper
|
||||
@@ -74,10 +79,13 @@
|
||||
dragging={node.dragging}
|
||||
zIndex={node[internalsSymbol]?.z ?? 0}
|
||||
dragHandle={node.dragHandle}
|
||||
width={node.width ?? undefined}
|
||||
height={node.height ?? undefined}
|
||||
initialized={(!!node.computed?.width && !!node.computed?.height) ||
|
||||
(!!node.width && !!node.height)}
|
||||
initialized={nodeHasDimensions(node)}
|
||||
width={node.width}
|
||||
height={node.height}
|
||||
initialWidth={node.initialWidth}
|
||||
initialHeight={node.initialHeight}
|
||||
computedWidth={node.computed?.width}
|
||||
computedHeight={node.computed?.height}
|
||||
{resizeObserver}
|
||||
on:nodeclick
|
||||
on:nodemouseenter
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
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 { Panel } from '$lib/container/Panel';
|
||||
@@ -116,13 +122,13 @@
|
||||
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
|
||||
|
||||
{#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 nodeDimesions = getNodeDimensions(node)}
|
||||
<MinimapNode
|
||||
x={pos.x}
|
||||
y={pos.y}
|
||||
width={node.computed?.width ?? node.width ?? 0}
|
||||
height={node.computed?.height ?? node.height ?? 0}
|
||||
{...nodeDimesions}
|
||||
selected={node.selected}
|
||||
color={nodeColorFunc?.(node)}
|
||||
borderRadius={nodeBorderRadius}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import {
|
||||
internalsSymbol,
|
||||
createMarkerIds,
|
||||
fitView as fitViewUtil,
|
||||
getElementsToRemove,
|
||||
@@ -15,7 +14,6 @@ import {
|
||||
type XYPosition,
|
||||
type CoordinateExtent,
|
||||
type UpdateConnection,
|
||||
type NodeDragItem,
|
||||
errorMessages
|
||||
} from '@xyflow/system';
|
||||
|
||||
|
||||
@@ -91,7 +91,9 @@ export const getInitialStore = ({
|
||||
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||
|
||||
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
|
||||
const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
|
||||
viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||
@@ -101,7 +103,7 @@ export const getInitialStore = ({
|
||||
flowId: writable<string | null>(null),
|
||||
nodes: createNodesStore(nextNodes, nodeLookup),
|
||||
nodeLookup: readable<NodeLookup<Node>>(nodeLookup),
|
||||
edgeLookup: readable<EdgeLookup>(edgeLookup),
|
||||
edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
|
||||
visibleNodes: readable<Node[]>([]),
|
||||
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
||||
visibleEdges: readable<EdgeLayouted[]>([]),
|
||||
|
||||
@@ -39,8 +39,10 @@ export type NodeBase<
|
||||
connectable?: boolean;
|
||||
deletable?: boolean;
|
||||
dragHandle?: string;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
width?: number;
|
||||
height?: number;
|
||||
initialWidth?: number;
|
||||
initialHeight?: number;
|
||||
/** Parent node id, used for creating sub-flows */
|
||||
parentNode?: string;
|
||||
zIndex?: number;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { NodeBase, NodeHandle } from '../../types/nodes';
|
||||
import { Position } from '../../types/utils';
|
||||
import { errorMessages, internalsSymbol } from '../../constants';
|
||||
import { HandleElement } from '../../types';
|
||||
import { getNodeDimensions } from '../general';
|
||||
|
||||
export type GetEdgePositionParams = {
|
||||
id: string;
|
||||
@@ -16,7 +17,10 @@ export type GetEdgePositionParams = {
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -75,8 +79,8 @@ function toHandleBounds(handles?: NodeHandle[]) {
|
||||
const target = [];
|
||||
|
||||
for (const handle of handles) {
|
||||
handle.width = handle.width || 1;
|
||||
handle.height = handle.height || 1;
|
||||
handle.width = handle.width ?? 1;
|
||||
handle.height = handle.height ?? 1;
|
||||
|
||||
if (handle.type === 'source') {
|
||||
source.push(handle as HandleElement);
|
||||
@@ -94,8 +98,7 @@ function toHandleBounds(handles?: NodeHandle[]) {
|
||||
function getHandlePosition(position: Position, node: NodeBase, handle: HandleElement | null = null): number[] {
|
||||
const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0);
|
||||
const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0);
|
||||
const width = handle?.width || (node?.computed?.width ?? node?.width ?? 0);
|
||||
const height = handle?.height || (node?.computed?.height ?? node?.height ?? 0);
|
||||
const { width, height } = handle ?? getNodeDimensions(node);
|
||||
|
||||
switch (position) {
|
||||
case Position.Top:
|
||||
|
||||
@@ -202,3 +202,19 @@ export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.user
|
||||
export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent is CoordinateExtent {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
pointToRendererPoint,
|
||||
getViewportForBounds,
|
||||
isCoordinateExtent,
|
||||
getNodeDimensions,
|
||||
} from './general';
|
||||
import {
|
||||
type Transform,
|
||||
@@ -116,8 +117,9 @@ export const getNodePositionWithOrigin = (
|
||||
};
|
||||
}
|
||||
|
||||
const offsetX = (node.computed?.width ?? node.width ?? 0) * nodeOrigin[0];
|
||||
const offsetY = (node.computed?.height ?? node.height ?? 0) * nodeOrigin[1];
|
||||
const { width, height } = getNodeDimensions(node);
|
||||
const offsetX = width * nodeOrigin[0];
|
||||
const offsetY = height * nodeOrigin[1];
|
||||
|
||||
const position: XYPosition = {
|
||||
x: node.position.x - offsetX,
|
||||
@@ -164,8 +166,7 @@ export const getNodesBounds = (
|
||||
currBox,
|
||||
rectToBox({
|
||||
...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
|
||||
width: node.computed?.width ?? node.width ?? 0,
|
||||
height: node.computed?.height ?? node.height ?? 0,
|
||||
...getNodeDimensions(node),
|
||||
})
|
||||
);
|
||||
},
|
||||
@@ -192,8 +193,8 @@ export const getNodesInside = <NodeType extends NodeBase>(
|
||||
|
||||
const visibleNodes = nodes.reduce<NodeType[]>((res, node) => {
|
||||
const { computed, selectable = true, hidden = false } = node;
|
||||
const width = computed?.width ?? node.width ?? null;
|
||||
const height = computed?.height ?? node.height ?? null;
|
||||
const width = computed?.width ?? node.width ?? node.initialWidth ?? null;
|
||||
const height = computed?.height ?? node.height ?? node.initialHeight ?? null;
|
||||
|
||||
if ((excludeNonSelectableNodes && !selectable) || hidden) {
|
||||
return res;
|
||||
|
||||
Reference in New Issue
Block a user