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

View File

@@ -81,6 +81,8 @@ const initialNodes: Node[] = [
id: '4',
data: { label: 'Node 4' },
position: { x: 400, y: 200 },
width: 200,
height: 50,
type: 'custom',
},
];

View File

@@ -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);

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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)}

View File

@@ -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>

View File

@@ -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,
};
}

View File

@@ -65,6 +65,7 @@ const reactFlowFieldsToTrack = [
'selectNodesOnDrag',
'nodeDragThreshold',
'onBeforeDelete',
'debug',
] as const;
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];

View File

@@ -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}

View File

@@ -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);
}
},

View File

@@ -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,
};
};

View File

@@ -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;

View File

@@ -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> = {

View File

@@ -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

View File

@@ -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 })}

View File

@@ -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;

View 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;` : ''
};
}

View File

@@ -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

View File

@@ -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}

View File

@@ -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';

View File

@@ -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[]>([]),

View File

@@ -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;

View File

@@ -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:

View File

@@ -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
);
}

View File

@@ -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;