Merge pull request #4129 from xyflow/refactor/svelte-internal
Svelte: Refactor internals
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import '@xyflow/svelte/dist/style.css';
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
const nodes = writable([
|
const nodes = writable<Node[]>([
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
@@ -55,8 +55,6 @@
|
|||||||
const onDragOver = (event: DragEvent) => {
|
const onDragOver = (event: DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
console.log(event);
|
|
||||||
|
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
event.dataTransfer.dropEffect = 'move';
|
event.dataTransfer.dropEffect = 'move';
|
||||||
}
|
}
|
||||||
@@ -81,7 +79,8 @@
|
|||||||
data: { label: `${type} node` }
|
data: { label: `${type} node` }
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes.update((nds) => nds.concat(newNode));
|
$nodes.push(newNode);
|
||||||
|
$nodes = $nodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
const { getIntersectingNodes } = useSvelteFlow();
|
const { getIntersectingNodes } = useSvelteFlow();
|
||||||
|
|
||||||
function onNodeDrag({ detail: { node } }) {
|
function onNodeDrag({ detail: { targetNode } }) {
|
||||||
const intersections = getIntersectingNodes(node).map((n) => n.id);
|
const intersections = getIntersectingNodes(targetNode).map((n) => n.id);
|
||||||
|
|
||||||
$nodes.forEach((n) => {
|
$nodes.forEach((n) => {
|
||||||
n.class = intersections.includes(n.id) ? 'highlight' : '';
|
n.class = intersections.includes(n.id) ? 'highlight' : '';
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import type { Node, Edge } from '@xyflow/svelte';
|
import type { Node, Edge } from '@xyflow/svelte';
|
||||||
|
|
||||||
export const initialNodes: Node[] = [
|
export const initialNodes: Node[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
data: { label: 'Node 1' },
|
data: { label: 'Node 1' },
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
style: 'width: 200px; height: 100px;'
|
style: 'width: 200px; height: 100px;'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
data: { label: 'Node 2' },
|
data: { label: 'Node 2' },
|
||||||
position: { x: 0, y: 150 }
|
position: { x: 0, y: 150 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
data: { label: 'Node 3' },
|
data: { label: 'Node 3' },
|
||||||
position: { x: 250, y: 0 }
|
position: { x: 250, y: 0 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
data: { label: 'Node' },
|
data: { label: 'Node' },
|
||||||
position: { x: 350, y: 150 },
|
position: { x: 350, y: 150 },
|
||||||
style: 'width: 50px; height: 50px;'
|
style: 'width: 50px; height: 50px;'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const initialEdges: Edge[] = [];
|
export const initialEdges: Edge[] = [];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
type $$Props = NodeProps;
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
export let id: $$Props['id'];
|
export let id: $$Props['id'];
|
||||||
|
$$restProps;
|
||||||
|
|
||||||
const connections = useHandleConnections({
|
const connections = useHandleConnections({
|
||||||
nodeId: id,
|
nodeId: id,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
export let data: $$Props['data'];
|
export let data: $$Props['data'];
|
||||||
|
|
||||||
const { updateNodeData } = useSvelteFlow();
|
const { updateNodeData } = useSvelteFlow();
|
||||||
|
$$restProps;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="custom">
|
<div class="custom">
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
type $$Props = NodeProps;
|
type $$Props = NodeProps;
|
||||||
|
|
||||||
export let id: $$Props['id'];
|
export let id: $$Props['id'];
|
||||||
|
export let data: $$Props['data'];
|
||||||
|
$$restProps;
|
||||||
|
|
||||||
const { updateNodeData } = useSvelteFlow();
|
const { updateNodeData } = useSvelteFlow();
|
||||||
const connections = useHandleConnections({
|
const connections = useHandleConnections({
|
||||||
@@ -22,8 +24,12 @@
|
|||||||
$: nodeData = useNodesData<MyNode>($connections[0]?.source);
|
$: nodeData = useNodesData<MyNode>($connections[0]?.source);
|
||||||
$: textNode = isTextNode($nodeData) ? $nodeData : null;
|
$: textNode = isTextNode($nodeData) ? $nodeData : null;
|
||||||
|
|
||||||
|
$: console.log(textNode?.data, data);
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
updateNodeData(id, { text: textNode?.data.text.toUpperCase() || '' });
|
const input = textNode?.data.text.toUpperCase() ?? '';
|
||||||
|
updateNodeData(id, { text: input });
|
||||||
|
console.log('updatedNodeData with', input);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function NodeToolbar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin });
|
const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin });
|
||||||
const zIndex: number = Math.max(...nodes.map((node) => (node.internals?.z || 1) + 1));
|
const zIndex: number = Math.max(...nodes.map((node) => node.internals.z + 1));
|
||||||
|
|
||||||
const wrapperStyle: CSSProperties = {
|
const wrapperStyle: CSSProperties = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const ConnectionLine = ({
|
|||||||
),
|
),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
const fromHandleBounds = fromNode?.internals?.handleBounds;
|
const fromHandleBounds = fromNode?.internals.handleBounds;
|
||||||
let handleBounds = fromHandleBounds?.[handleType];
|
let handleBounds = fromHandleBounds?.[handleType];
|
||||||
|
|
||||||
if (connectionMode === ConnectionMode.Loose) {
|
if (connectionMode === ConnectionMode.Loose) {
|
||||||
@@ -66,8 +66,8 @@ const ConnectionLine = ({
|
|||||||
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
||||||
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.measured.width ?? 0) / 2;
|
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.measured.width ?? 0) / 2;
|
||||||
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0;
|
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0;
|
||||||
const fromX = (fromNode.internals.positionAbsolute.x ?? 0) + fromHandleX;
|
const fromX = fromNode.internals.positionAbsolute.x + fromHandleX;
|
||||||
const fromY = (fromNode.internals.positionAbsolute.y ?? 0) + fromHandleY;
|
const fromY = fromNode.internals.positionAbsolute.y + fromHandleY;
|
||||||
const fromPosition = fromHandle?.position;
|
const fromPosition = fromHandle?.position;
|
||||||
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
|
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ export type Node<
|
|||||||
focusable?: boolean;
|
focusable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node data structure that gets used for internal nodes.
|
||||||
|
* There are some data structures added under node.internal
|
||||||
|
* that are needed for tracking some properties
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
||||||
|
|
||||||
export type NodeMouseHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, node: NodeType) => void;
|
export type NodeMouseHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, node: NodeType) => void;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
/build
|
/build
|
||||||
|
/dist
|
||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
/package
|
/package
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
const {
|
const {
|
||||||
connectionMode,
|
connectionMode,
|
||||||
domNode,
|
domNode,
|
||||||
nodes,
|
nodeLookup,
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
viewport,
|
viewport,
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
isTarget,
|
isTarget,
|
||||||
connectionRadius: $connectionRadius,
|
connectionRadius: $connectionRadius,
|
||||||
domNode: $domNode,
|
domNode: $domNode,
|
||||||
nodes: $nodes,
|
nodeLookup: $nodeLookup,
|
||||||
connectionMode: $connectionMode,
|
connectionMode: $connectionMode,
|
||||||
lib: $lib,
|
lib: $lib,
|
||||||
autoPanOnConnect: $autoPanOnConnect,
|
autoPanOnConnect: $autoPanOnConnect,
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
export let sourcePosition: $$Props['sourcePosition'] = undefined;
|
export let sourcePosition: $$Props['sourcePosition'] = undefined;
|
||||||
export let targetPosition: $$Props['targetPosition'] = undefined;
|
export let targetPosition: $$Props['targetPosition'] = undefined;
|
||||||
export let zIndex: $$Props['zIndex'];
|
export let zIndex: $$Props['zIndex'];
|
||||||
export let computedWidth: $$Props['computedWidth'] = undefined;
|
export let measuredWidth: $$Props['measuredWidth'] = undefined;
|
||||||
export let computedHeight: $$Props['computedHeight'] = undefined;
|
export let measuredHeight: $$Props['measuredHeight'] = undefined;
|
||||||
export let initialWidth: $$Props['initialWidth'] = undefined;
|
export let initialWidth: $$Props['initialWidth'] = undefined;
|
||||||
export let initialHeight: $$Props['initialHeight'] = undefined;
|
export let initialHeight: $$Props['initialHeight'] = undefined;
|
||||||
export let width: $$Props['width'] = undefined;
|
export let width: $$Props['width'] = undefined;
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
height,
|
height,
|
||||||
initialWidth,
|
initialWidth,
|
||||||
initialHeight,
|
initialHeight,
|
||||||
computedWidth,
|
measuredWidth,
|
||||||
computedHeight
|
measuredHeight
|
||||||
});
|
});
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ export type NodeWrapperProps = Pick<
|
|||||||
| 'initialWidth'
|
| 'initialWidth'
|
||||||
| 'initialHeight'
|
| 'initialHeight'
|
||||||
> & {
|
> & {
|
||||||
computedWidth?: number;
|
measuredWidth?: number;
|
||||||
computedHeight?: number;
|
measuredHeight?: number;
|
||||||
type: string;
|
type: string;
|
||||||
positionX: number;
|
positionX: number;
|
||||||
positionY: number;
|
positionY: number;
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ export function getNodeInlineStyleDimensions({
|
|||||||
height,
|
height,
|
||||||
initialWidth,
|
initialWidth,
|
||||||
initialHeight,
|
initialHeight,
|
||||||
computedWidth,
|
measuredWidth,
|
||||||
computedHeight
|
measuredHeight
|
||||||
}: {
|
}: {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
initialWidth?: number;
|
initialWidth?: number;
|
||||||
initialHeight?: number;
|
initialHeight?: number;
|
||||||
computedWidth?: number;
|
measuredWidth?: number;
|
||||||
computedHeight?: number;
|
measuredHeight?: number;
|
||||||
}): {
|
}): {
|
||||||
width: string | undefined;
|
width: string | undefined;
|
||||||
height: string | undefined;
|
height: string | undefined;
|
||||||
} {
|
} {
|
||||||
if (computedWidth === undefined && computedHeight === undefined) {
|
if (measuredWidth === undefined && measuredHeight === undefined) {
|
||||||
const styleWidth = width ?? initialWidth;
|
const styleWidth = width ?? initialWidth;
|
||||||
const styleHeight = height ?? initialHeight;
|
const styleHeight = height ?? initialHeight;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import {
|
import { getPositionWithOrigin, getNodeDimensions, nodeHasDimensions } from '@xyflow/system';
|
||||||
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';
|
||||||
@@ -46,8 +41,8 @@
|
|||||||
{#each $visibleNodes as node (node.id)}
|
{#each $visibleNodes as node (node.id)}
|
||||||
{@const nodeDimesions = getNodeDimensions(node)}
|
{@const nodeDimesions = getNodeDimensions(node)}
|
||||||
{@const posOrigin = getPositionWithOrigin({
|
{@const posOrigin = getPositionWithOrigin({
|
||||||
x: node.computed?.positionAbsolute?.x ?? 0,
|
x: node.internals.positionAbsolute.x,
|
||||||
y: node.computed?.positionAbsolute?.y ?? 0,
|
y: node.internals.positionAbsolute.y,
|
||||||
...nodeDimesions,
|
...nodeDimesions,
|
||||||
origin: node.origin
|
origin: node.origin
|
||||||
})}
|
})}
|
||||||
@@ -66,26 +61,26 @@
|
|||||||
node.connectable ||
|
node.connectable ||
|
||||||
($nodesConnectable && typeof node.connectable === 'undefined')
|
($nodesConnectable && typeof node.connectable === 'undefined')
|
||||||
)}
|
)}
|
||||||
positionX={node.computed?.positionAbsolute?.x ?? 0}
|
positionX={node.internals.positionAbsolute.x}
|
||||||
positionY={node.computed?.positionAbsolute?.y ?? 0}
|
positionY={node.internals.positionAbsolute.y}
|
||||||
positionOriginX={posOrigin.x ?? 0}
|
positionOriginX={posOrigin.x ?? 0}
|
||||||
positionOriginY={posOrigin.y ?? 0}
|
positionOriginY={posOrigin.y ?? 0}
|
||||||
isParent={!!node[internalsSymbol]?.isParent}
|
isParent={!!node.internals.isParent}
|
||||||
style={node.style}
|
style={node.style}
|
||||||
class={node.class}
|
class={node.class}
|
||||||
type={node.type ?? 'default'}
|
type={node.type ?? 'default'}
|
||||||
sourcePosition={node.sourcePosition}
|
sourcePosition={node.sourcePosition}
|
||||||
targetPosition={node.targetPosition}
|
targetPosition={node.targetPosition}
|
||||||
dragging={node.dragging}
|
dragging={node.dragging}
|
||||||
zIndex={node[internalsSymbol]?.z ?? 0}
|
zIndex={node.internals.z ?? 0}
|
||||||
dragHandle={node.dragHandle}
|
dragHandle={node.dragHandle}
|
||||||
initialized={nodeHasDimensions(node)}
|
initialized={nodeHasDimensions(node)}
|
||||||
width={node.width}
|
width={node.width}
|
||||||
height={node.height}
|
height={node.height}
|
||||||
initialWidth={node.initialWidth}
|
initialWidth={node.initialWidth}
|
||||||
initialHeight={node.initialHeight}
|
initialHeight={node.initialHeight}
|
||||||
computedWidth={node.computed?.width}
|
measuredWidth={node.measured.width}
|
||||||
computedHeight={node.computed?.height}
|
measuredHeight={node.measured.height}
|
||||||
{resizeObserver}
|
{resizeObserver}
|
||||||
on:nodeclick
|
on:nodeclick
|
||||||
on:nodemouseenter
|
on:nodemouseenter
|
||||||
|
|||||||
@@ -17,10 +17,7 @@
|
|||||||
const isSelected = ids.includes(item.id);
|
const isSelected = ids.includes(item.id);
|
||||||
|
|
||||||
if (item.selected !== isSelected) {
|
if (item.selected !== isSelected) {
|
||||||
return {
|
item.selected = isSelected;
|
||||||
...item,
|
|
||||||
selected: isSelected
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@@ -56,6 +53,7 @@
|
|||||||
}>();
|
}>();
|
||||||
const {
|
const {
|
||||||
nodes,
|
nodes,
|
||||||
|
nodeLookup,
|
||||||
edges,
|
edges,
|
||||||
viewport,
|
viewport,
|
||||||
dragging,
|
dragging,
|
||||||
@@ -130,8 +128,8 @@
|
|||||||
const prevSelectedNodeIds = selectedNodes.map((n) => n.id);
|
const prevSelectedNodeIds = selectedNodes.map((n) => n.id);
|
||||||
const prevSelectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
const prevSelectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
|
||||||
|
|
||||||
selectedNodes = getNodesInside<Node>(
|
selectedNodes = getNodesInside(
|
||||||
$nodes,
|
$nodeLookup,
|
||||||
nextUserSelectRect,
|
nextUserSelectRect,
|
||||||
[$viewport.x, $viewport.y, $viewport.zoom],
|
[$viewport.x, $viewport.y, $viewport.zoom],
|
||||||
$selectionMode === SelectionMode.Partial,
|
$selectionMode === SelectionMode.Partial,
|
||||||
@@ -172,7 +170,7 @@
|
|||||||
selectionRect.set(null);
|
selectionRect.set(null);
|
||||||
|
|
||||||
if (selectedNodes.length > 0) {
|
if (selectedNodes.length > 0) {
|
||||||
selectionRectMode.set('nodes');
|
$selectionRectMode = 'nodes';
|
||||||
}
|
}
|
||||||
|
|
||||||
// onSelectionEnd?.(event);
|
// onSelectionEnd?.(event);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function useNodesData(nodeIds: any): any {
|
|||||||
const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds];
|
const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds];
|
||||||
|
|
||||||
for (const nodeId of _nodeIds) {
|
for (const nodeId of _nodeIds) {
|
||||||
const node = nodeLookup.get(nodeId);
|
const node = nodeLookup.get(nodeId)?.internals.userNode;
|
||||||
if (node) {
|
if (node) {
|
||||||
nextNodesData.push({
|
nextNodesData.push({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
|
|||||||
@@ -256,17 +256,28 @@ export function useSvelteFlow(): {
|
|||||||
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
|
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
|
||||||
options: { replace: boolean } = { replace: false }
|
options: { replace: boolean } = { replace: false }
|
||||||
) => {
|
) => {
|
||||||
nodes.update((nds) =>
|
const node = get(nodeLookup).get(id)?.internals.userNode;
|
||||||
nds.map((node) => {
|
|
||||||
if (node.id === id) {
|
|
||||||
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate;
|
|
||||||
|
|
||||||
return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode };
|
if (!node) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate;
|
||||||
})
|
|
||||||
);
|
if (options.replace) {
|
||||||
|
nodes.update((nds) =>
|
||||||
|
nds.map((node) => {
|
||||||
|
if (node.id === id) {
|
||||||
|
return isNode(nextNode) ? nextNode : { ...node, ...nextNode };
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Object.assign(node, nextNode);
|
||||||
|
nodes.update((nds) => nds);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -331,11 +342,12 @@ export function useSvelteFlow(): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (nodesToIntersect || get(nodes)).filter((n) => {
|
return (nodesToIntersect || get(nodes)).filter((n) => {
|
||||||
if (!isRect && (n.id === nodeOrRect.id || !n.computed?.positionAbsolute)) {
|
const internalNode = get(nodeLookup).get(n.id);
|
||||||
|
if (!internalNode || (!isRect && n.id === nodeOrRect.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currNodeRect = nodeToRect(n);
|
const currNodeRect = nodeToRect(internalNode);
|
||||||
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
||||||
const partiallyVisible = partially && overlappingArea > 0;
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
|
|
||||||
@@ -447,13 +459,17 @@ export function useSvelteFlow(): {
|
|||||||
},
|
},
|
||||||
updateNode,
|
updateNode,
|
||||||
updateNodeData: (id, dataUpdate, options) => {
|
updateNodeData: (id, dataUpdate, options) => {
|
||||||
updateNode(id, (node) => {
|
const node = get(nodeLookup).get(id)?.internals.userNode;
|
||||||
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
|
|
||||||
|
|
||||||
return options?.replace
|
if (!node) {
|
||||||
? { ...node, data: nextData }
|
return;
|
||||||
: { ...node, data: { ...node.data, ...nextData } };
|
}
|
||||||
});
|
|
||||||
|
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
|
||||||
|
|
||||||
|
node.data = options?.replace ? nextData : { ...node.data, ...nextData };
|
||||||
|
|
||||||
|
nodes.update((nds) => nds);
|
||||||
},
|
},
|
||||||
viewport
|
viewport
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -116,6 +116,5 @@ export {
|
|||||||
getOutgoers,
|
getOutgoers,
|
||||||
getConnectedEdges,
|
getConnectedEdges,
|
||||||
addEdge,
|
addEdge,
|
||||||
updateEdge,
|
updateEdge
|
||||||
internalsSymbol
|
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => {
|
onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => {
|
||||||
const node = $nodeLookup.get(id);
|
const node = $nodeLookup.get(id)?.internals.userNode;
|
||||||
if (node) {
|
if (node) {
|
||||||
node.height = change.isHeightChange ? change.height : node.height;
|
node.height = change.isHeightChange ? change.height : node.height;
|
||||||
node.width = change.isWidthChange ? change.width : node.width;
|
node.width = change.isWidthChange ? change.width : node.width;
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
: node.position;
|
: node.position;
|
||||||
|
|
||||||
for (const childChange of childChanges) {
|
for (const childChange of childChanges) {
|
||||||
const childNode = $nodeLookup.get(childChange.id);
|
const childNode = $nodeLookup.get(childChange.id)?.internals.userNode;
|
||||||
if (childNode) {
|
if (childNode) {
|
||||||
childNode.position = childChange.position;
|
childNode.position = childChange.position;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import {
|
import { getNodesBounds, Position, type Rect, getNodeToolbarTransform } from '@xyflow/system';
|
||||||
getNodesBounds,
|
|
||||||
Position,
|
|
||||||
type Rect,
|
|
||||||
internalsSymbol,
|
|
||||||
getNodeToolbarTransform
|
|
||||||
} from '@xyflow/system';
|
|
||||||
import portal from '$lib/actions/portal';
|
import portal from '$lib/actions/portal';
|
||||||
import type { Node } from '$lib/types';
|
import type { InternalNode } from '$lib/types';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
import type { NodeToolbarProps } from './types';
|
import type { NodeToolbarProps } from './types';
|
||||||
@@ -25,26 +19,26 @@
|
|||||||
const contextNodeId = getContext<string>('svelteflow__node_id');
|
const contextNodeId = getContext<string>('svelteflow__node_id');
|
||||||
|
|
||||||
let transform: string;
|
let transform: string;
|
||||||
let toolbarNodes: Node[] = [];
|
let toolbarNodes: InternalNode[] = [];
|
||||||
let _offset = offset !== undefined ? offset : 10;
|
let _offset = offset !== undefined ? offset : 10;
|
||||||
let _position = position !== undefined ? position : Position.Top;
|
let _position = position !== undefined ? position : Position.Top;
|
||||||
let _align = align !== undefined ? align : 'center';
|
let _align = align !== undefined ? align : 'center';
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// $nodes only needed to trigger updates, $nodeLookup is just a helper that does not trigger any updates
|
// nly needed to trigger updates, $nodeLookup is just a helper that does not trigger any updates
|
||||||
if ($nodes) {
|
$nodes;
|
||||||
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId];
|
|
||||||
|
|
||||||
toolbarNodes = nodeIds.reduce<Node[]>((res, nodeId) => {
|
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId];
|
||||||
const node = $nodeLookup.get(nodeId);
|
|
||||||
|
|
||||||
if (node) {
|
toolbarNodes = nodeIds.reduce<InternalNode[]>((res, nodeId) => {
|
||||||
res.push(node);
|
const node = $nodeLookup.get(nodeId);
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
if (node) {
|
||||||
}, []);
|
res.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
@@ -54,8 +48,8 @@
|
|||||||
const toolbarNode = toolbarNodes[0];
|
const toolbarNode = toolbarNodes[0];
|
||||||
nodeRect = {
|
nodeRect = {
|
||||||
...toolbarNode.position,
|
...toolbarNode.position,
|
||||||
width: toolbarNode.computed?.width ?? toolbarNode.width ?? 0,
|
width: toolbarNode.measured.width ?? toolbarNode.width ?? 0,
|
||||||
height: toolbarNode.computed?.height ?? toolbarNode.height ?? 0
|
height: toolbarNode.measured.height ?? toolbarNode.height ?? 0
|
||||||
};
|
};
|
||||||
} else if (toolbarNodes.length > 1) {
|
} else if (toolbarNodes.length > 1) {
|
||||||
nodeRect = getNodesBounds(toolbarNodes, { nodeOrigin: $nodeOrigin });
|
nodeRect = getNodesBounds(toolbarNodes, { nodeOrigin: $nodeOrigin });
|
||||||
@@ -69,7 +63,7 @@
|
|||||||
$: zIndex =
|
$: zIndex =
|
||||||
toolbarNodes.length === 0
|
toolbarNodes.length === 0
|
||||||
? 1
|
? 1
|
||||||
: Math.max(...toolbarNodes.map((node) => (node[internalsSymbol]?.z || 5) + 1));
|
: Math.max(...toolbarNodes.map((node) => (node.internals.z || 5) + 1));
|
||||||
|
|
||||||
//FIXME: Possible performance bottleneck
|
//FIXME: Possible performance bottleneck
|
||||||
$: selectedNodesCount = $nodes.filter((node) => node.selected).length;
|
$: selectedNodesCount = $nodes.filter((node) => node.selected).length;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
Position,
|
Position,
|
||||||
internalsSymbol,
|
|
||||||
type HandleElement
|
type HandleElement
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
@@ -65,8 +64,9 @@ export function getDerivedConnectionProps(
|
|||||||
return initConnectionProps;
|
return initConnectionProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: it should bail out if the node is not found
|
||||||
const fromNode = nodeLookup.get(connection.connectionStartHandle?.nodeId);
|
const fromNode = nodeLookup.get(connection.connectionStartHandle?.nodeId);
|
||||||
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
const fromHandleBounds = fromNode?.internals.handleBounds;
|
||||||
const handleBoundsStrict =
|
const handleBoundsStrict =
|
||||||
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
|
||||||
const handleBoundsLoose: HandleElement[] | undefined | null = handleBoundsStrict
|
const handleBoundsLoose: HandleElement[] | undefined | null = handleBoundsStrict
|
||||||
@@ -81,12 +81,12 @@ export function getDerivedConnectionProps(
|
|||||||
: handleBounds?.[0];
|
: handleBounds?.[0];
|
||||||
const fromHandleX = fromHandle
|
const fromHandleX = fromHandle
|
||||||
? fromHandle.x + fromHandle.width / 2
|
? fromHandle.x + fromHandle.width / 2
|
||||||
: (fromNode?.computed?.width ?? 0) / 2;
|
: (fromNode?.measured.width ?? 0) / 2;
|
||||||
const fromHandleY = fromHandle
|
const fromHandleY = fromHandle
|
||||||
? fromHandle.y + fromHandle.height / 2
|
? fromHandle.y + fromHandle.height / 2
|
||||||
: fromNode?.computed?.height ?? 0;
|
: fromNode?.measured.height ?? 0;
|
||||||
const fromX = (fromNode?.computed?.positionAbsolute?.x ?? 0) + fromHandleX;
|
const fromX = (fromNode?.internals.positionAbsolute.x ?? 0) + fromHandleX;
|
||||||
const fromY = (fromNode?.computed?.positionAbsolute?.y ?? 0) + fromHandleY;
|
const fromY = (fromNode?.internals.positionAbsolute.y ?? 0) + fromHandleY;
|
||||||
const fromPosition = fromHandle?.position;
|
const fromPosition = fromHandle?.position;
|
||||||
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
|
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -64,52 +64,71 @@ export function createStore({
|
|||||||
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
|
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
|
||||||
const nodeLookup = get(store.nodeLookup);
|
const nodeLookup = get(store.nodeLookup);
|
||||||
|
|
||||||
nodeDragItems.forEach((nodeDragItem) => {
|
for (const nodeDragItem of nodeDragItems) {
|
||||||
const node = nodeLookup.get(nodeDragItem.id);
|
const node = nodeLookup.get(nodeDragItem.id)?.internals.userNode;
|
||||||
|
|
||||||
if (node) {
|
if (!node) {
|
||||||
node.position = nodeDragItem.position;
|
continue;
|
||||||
node.dragging = dragging;
|
|
||||||
node.computed = {
|
|
||||||
...node.computed,
|
|
||||||
positionAbsolute: nodeDragItem.computed?.positionAbsolute
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
store.nodes.set(get(store.nodes));
|
node.position = nodeDragItem.position;
|
||||||
|
node.dragging = dragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.nodes.update((nds) => nds);
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateNodeDimensions(updates: Map<string, NodeDimensionUpdate>) {
|
function updateNodeDimensions(updates: Map<string, NodeDimensionUpdate>) {
|
||||||
const nextNodes = updateNodeDimensionsSystem(
|
const nodeLookup = get(store.nodeLookup);
|
||||||
|
const changes = updateNodeDimensionsSystem(
|
||||||
updates,
|
updates,
|
||||||
get(store.nodes),
|
nodeLookup,
|
||||||
get(store.nodeLookup),
|
|
||||||
get(store.domNode),
|
get(store.domNode),
|
||||||
get(store.nodeOrigin)
|
get(store.nodeOrigin)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!nextNodes) {
|
if (!changes) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
|
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
|
||||||
const fitViewOptions = get(store.fitViewOptions);
|
const fitViewOptions = get(store.fitViewOptions);
|
||||||
const fitViewOnInitDone = fitView(nextNodes, {
|
const fitViewOnInitDone = fitView({
|
||||||
...fitViewOptions,
|
...fitViewOptions,
|
||||||
nodes: fitViewOptions?.nodes || nextNodes
|
nodes: fitViewOptions?.nodes
|
||||||
});
|
});
|
||||||
store.fitViewOnInitDone.set(fitViewOnInitDone);
|
store.fitViewOnInitDone.set(fitViewOnInitDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
store.nodes.set(nextNodes);
|
for (const change of changes) {
|
||||||
|
const node = nodeLookup.get(change.id)?.internals.userNode;
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (change.type) {
|
||||||
|
case 'dimensions': {
|
||||||
|
const measured = { ...node.measured, ...change.dimensions };
|
||||||
|
node.width = change.dimensions?.width ?? node.width;
|
||||||
|
node.height = change.dimensions?.height ?? node.height;
|
||||||
|
node.measured = measured;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'position':
|
||||||
|
node.position = change.position ?? node.position;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.nodes.update((nds) => nds);
|
||||||
|
|
||||||
if (!get(store.nodesInitialized)) {
|
if (!get(store.nodesInitialized)) {
|
||||||
store.nodesInitialized.set(true);
|
store.nodesInitialized.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitView(nodes: Node[], options?: FitViewOptions) {
|
function fitView(options?: FitViewOptions) {
|
||||||
const panZoom = get(store.panZoom);
|
const panZoom = get(store.panZoom);
|
||||||
|
|
||||||
if (!panZoom) {
|
if (!panZoom) {
|
||||||
@@ -118,7 +137,7 @@ export function createStore({
|
|||||||
|
|
||||||
return fitViewUtil(
|
return fitViewUtil(
|
||||||
{
|
{
|
||||||
nodes,
|
nodeLookup: get(store.nodeLookup),
|
||||||
width: get(store.width),
|
width: get(store.width),
|
||||||
height: get(store.height),
|
height: get(store.height),
|
||||||
minZoom: get(store.minZoom),
|
minZoom: get(store.minZoom),
|
||||||
@@ -186,10 +205,10 @@ export function createStore({
|
|||||||
|
|
||||||
function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) {
|
function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) {
|
||||||
const resetNodes = resetSelectedElements(params?.nodes || get(store.nodes));
|
const resetNodes = resetSelectedElements(params?.nodes || get(store.nodes));
|
||||||
if (resetNodes) store.nodes.set(get(store.nodes));
|
if (resetNodes) store.nodes.update((nds) => nds);
|
||||||
|
|
||||||
const resetEdges = resetSelectedElements(params?.edges || get(store.edges));
|
const resetEdges = resetSelectedElements(params?.edges || get(store.edges));
|
||||||
if (resetEdges) store.edges.set(get(store.edges));
|
if (resetEdges) store.edges.update((nds) => nds);
|
||||||
}
|
}
|
||||||
|
|
||||||
store.deleteKeyPressed.subscribe(async (deleteKeyPressed) => {
|
store.deleteKeyPressed.subscribe(async (deleteKeyPressed) => {
|
||||||
@@ -384,7 +403,7 @@ export function createStore({
|
|||||||
updateNodeDimensions,
|
updateNodeDimensions,
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
fitView: (options?: FitViewOptions) => fitView(get(store.nodes), options),
|
fitView: (options?: FitViewOptions) => fitView(options),
|
||||||
setMinZoom,
|
setMinZoom,
|
||||||
setMaxZoom,
|
setMaxZoom,
|
||||||
setTranslateExtent,
|
setTranslateExtent,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
devWarn,
|
devWarn,
|
||||||
adoptUserProvidedNodes,
|
adoptUserNodes,
|
||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
updateConnectionLookup,
|
updateConnectionLookup,
|
||||||
@@ -47,7 +47,8 @@ import type {
|
|||||||
OnDelete,
|
OnDelete,
|
||||||
OnEdgeCreate,
|
OnEdgeCreate,
|
||||||
OnBeforeDelete,
|
OnBeforeDelete,
|
||||||
IsValidConnection
|
IsValidConnection,
|
||||||
|
InternalNode
|
||||||
} from '$lib/types';
|
} from '$lib/types';
|
||||||
import { createNodesStore, createEdgesStore } from './utils';
|
import { createNodesStore, createEdgesStore } from './utils';
|
||||||
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
|
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
|
||||||
@@ -80,9 +81,10 @@ export const getInitialStore = ({
|
|||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const nodeLookup: NodeLookup = new Map();
|
const nodeLookup: NodeLookup = new Map();
|
||||||
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
adoptUserNodes(nodes, nodeLookup, {
|
||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false
|
elevateNodesOnSelect: false,
|
||||||
|
checkEquality: false
|
||||||
});
|
});
|
||||||
const connectionLookup = new Map();
|
const connectionLookup = new Map();
|
||||||
const edgeLookup = new Map();
|
const edgeLookup = new Map();
|
||||||
@@ -91,9 +93,10 @@ 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(
|
const nodesWithDimensions = nodes.filter(
|
||||||
(node) => (node.width && node.height) || (node.initialWidth && node.initialHeight)
|
(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,10 +104,10 @@ export const getInitialStore = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
flowId: writable<string | null>(null),
|
flowId: writable<string | null>(null),
|
||||||
nodes: createNodesStore(nextNodes, nodeLookup),
|
nodes: createNodesStore(nodes, nodeLookup),
|
||||||
nodeLookup: readable<NodeLookup<Node>>(nodeLookup),
|
nodeLookup: readable<NodeLookup<InternalNode>>(nodeLookup),
|
||||||
edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
|
edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
|
||||||
visibleNodes: readable<Node[]>([]),
|
visibleNodes: readable<InternalNode[]>([]),
|
||||||
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
||||||
visibleEdges: readable<EdgeLayouted[]>([]),
|
visibleEdges: readable<EdgeLayouted[]>([]),
|
||||||
connectionLookup: readable<ConnectionLookup>(connectionLookup),
|
connectionLookup: readable<ConnectionLookup>(connectionLookup),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
type NodeLookup
|
type NodeLookup
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, InternalNode, Node } from '$lib/types';
|
||||||
|
|
||||||
// we need to sync the user nodes and the internal nodes so that the user can receive the updates
|
// we need to sync the user nodes and the internal nodes so that the user can receive the updates
|
||||||
// made by Svelte Flow (like dragging or selecting a node).
|
// made by Svelte Flow (like dragging or selecting a node).
|
||||||
@@ -127,7 +127,7 @@ export type NodeStoreOptions = {
|
|||||||
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
|
||||||
export const createNodesStore = (
|
export const createNodesStore = (
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
nodeLookup: NodeLookup<Node>
|
nodeLookup: NodeLookup<InternalNode>
|
||||||
): {
|
): {
|
||||||
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
|
||||||
update: (this: void, updater: Updater<Node[]>) => void;
|
update: (this: void, updater: Updater<Node[]>) => void;
|
||||||
@@ -141,12 +141,13 @@ export const createNodesStore = (
|
|||||||
let elevateNodesOnSelect = true;
|
let elevateNodesOnSelect = true;
|
||||||
|
|
||||||
const _set = (nds: Node[]): Node[] => {
|
const _set = (nds: Node[]): Node[] => {
|
||||||
const nextNodes = adoptUserNodes(nds, nodeLookup, {
|
adoptUserNodes(nds, nodeLookup, {
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
defaults
|
defaults,
|
||||||
|
checkEquality: false
|
||||||
});
|
});
|
||||||
|
|
||||||
value = nextNodes;
|
value = nds;
|
||||||
|
|
||||||
set(value);
|
set(value);
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
import { getNodesInside, type Transform } from '@xyflow/system';
|
import { getNodesInside, type Transform } from '@xyflow/system';
|
||||||
|
|
||||||
import type { Node } from '$lib/types';
|
|
||||||
import type { SvelteFlowStoreState } from './types';
|
import type { SvelteFlowStoreState } from './types';
|
||||||
|
|
||||||
export function getVisibleNodes(store: SvelteFlowStoreState) {
|
export function getVisibleNodes(store: SvelteFlowStoreState) {
|
||||||
return derived(
|
return derived(
|
||||||
[store.nodes, store.onlyRenderVisibleElements, store.width, store.height, store.viewport],
|
[
|
||||||
([nodes, onlyRenderVisibleElements, width, height, viewport]) => {
|
store.nodeLookup,
|
||||||
|
store.onlyRenderVisibleElements,
|
||||||
|
store.width,
|
||||||
|
store.height,
|
||||||
|
store.viewport,
|
||||||
|
store.nodes
|
||||||
|
],
|
||||||
|
([nodeLookup, onlyRenderVisibleElements, width, height, viewport]) => {
|
||||||
const transform: Transform = [viewport.x, viewport.y, viewport.zoom];
|
const transform: Transform = [viewport.x, viewport.y, viewport.zoom];
|
||||||
|
|
||||||
return onlyRenderVisibleElements
|
return onlyRenderVisibleElements
|
||||||
? getNodesInside<Node>(nodes, { x: 0, y: 0, width, height }, transform, true)
|
? getNodesInside(nodeLookup, { x: 0, y: 0, width, height }, transform, true)
|
||||||
: nodes;
|
: Array.from(nodeLookup.values());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import type { ComponentType, SvelteComponent } from 'svelte';
|
import type { ComponentType, SvelteComponent } from 'svelte';
|
||||||
import type { NodeBase, NodeProps as NodePropsBase } from '@xyflow/system';
|
import type { InternalNodeBase, NodeBase, NodeProps as NodePropsBase } from '@xyflow/system';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node data structure that gets used for internal nodes.
|
||||||
|
* There are some data structures added under node.internal
|
||||||
|
* that are needed for tracking some properties
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The node data structure that gets used for the nodes prop.
|
* The node data structure that gets used for the nodes prop.
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ function toHandleBounds(handles?: NodeHandle[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getHandlePosition(position: Position, node: InternalNodeBase, handle: HandleElement | null = null): number[] {
|
function getHandlePosition(position: Position, node: InternalNodeBase, handle: HandleElement | null = null): number[] {
|
||||||
const x = (handle?.x ?? 0) + (node.internals.positionAbsolute?.x ?? 0);
|
const x = (handle?.x ?? 0) + node.internals.positionAbsolute.x;
|
||||||
const y = (handle?.y ?? 0) + (node.internals.positionAbsolute?.y ?? 0);
|
const y = (handle?.y ?? 0) + node.internals.positionAbsolute.y;
|
||||||
const { width, height } = handle ?? getNodeDimensions(node);
|
const { width, height } = handle ?? getNodeDimensions(node);
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
|
|||||||
@@ -230,14 +230,14 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
|
|||||||
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
|
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
|
||||||
excludeNonSelectableNodes = false,
|
excludeNonSelectableNodes = false,
|
||||||
nodeOrigin: NodeOrigin = [0, 0]
|
nodeOrigin: NodeOrigin = [0, 0]
|
||||||
): NodeType[] => {
|
): InternalNodeBase<NodeType>[] => {
|
||||||
const paneRect = {
|
const paneRect = {
|
||||||
...pointToRendererPoint(rect, [tx, ty, tScale]),
|
...pointToRendererPoint(rect, [tx, ty, tScale]),
|
||||||
width: rect.width / tScale,
|
width: rect.width / tScale,
|
||||||
height: rect.height / tScale,
|
height: rect.height / tScale,
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibleNodes: NodeType[] = [];
|
const visibleNodes: InternalNodeBase<NodeType>[] = [];
|
||||||
|
|
||||||
for (const [, node] of nodeLookup) {
|
for (const [, node] of nodeLookup) {
|
||||||
const { measured, selectable = true, hidden = false } = node;
|
const { measured, selectable = true, hidden = false } = node;
|
||||||
@@ -290,6 +290,7 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
|
|||||||
nodeLookup.forEach((n) => {
|
nodeLookup.forEach((n) => {
|
||||||
const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
|
const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
|
||||||
|
|
||||||
|
// TODO: this remove options.nodes.some with a Set
|
||||||
if (
|
if (
|
||||||
isVisible &&
|
isVisible &&
|
||||||
(!options?.nodes || (options?.nodes.length && options?.nodes.some((optionNode) => optionNode.id === n.id)))
|
(!options?.nodes || (options?.nodes.length && options?.nodes.some((optionNode) => optionNode.id === n.id)))
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
NodeChange,
|
NodeChange,
|
||||||
NodeLookup,
|
NodeLookup,
|
||||||
Rect,
|
Rect,
|
||||||
|
NodeDimensionChange,
|
||||||
|
NodePositionChange,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getDimensions, getHandleBounds } from './dom';
|
import { getDimensions, getHandleBounds } from './dom';
|
||||||
import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general';
|
import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './general';
|
||||||
@@ -68,6 +70,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
|||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
elevateNodesOnSelect?: boolean;
|
elevateNodesOnSelect?: boolean;
|
||||||
defaults?: Partial<NodeType>;
|
defaults?: Partial<NodeType>;
|
||||||
|
checkEquality?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function adoptUserNodes<NodeType extends NodeBase>(
|
export function adoptUserNodes<NodeType extends NodeBase>(
|
||||||
@@ -77,6 +80,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
nodeOrigin: [0, 0] as NodeOrigin,
|
nodeOrigin: [0, 0] as NodeOrigin,
|
||||||
elevateNodesOnSelect: true,
|
elevateNodesOnSelect: true,
|
||||||
defaults: {},
|
defaults: {},
|
||||||
|
checkEquality: true,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const tmpLookup = new Map(nodeLookup);
|
const tmpLookup = new Map(nodeLookup);
|
||||||
@@ -91,7 +95,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
parentNodeIds.add(userNode.parentId);
|
parentNodeIds.add(userNode.parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userNode === currentStoreNode?.internals.userNode) {
|
if (options.checkEquality && userNode === currentStoreNode?.internals.userNode) {
|
||||||
nodeLookup.set(userNode.id, currentStoreNode);
|
nodeLookup.set(userNode.id, currentStoreNode);
|
||||||
} else {
|
} else {
|
||||||
nodeLookup.set(userNode.id, {
|
nodeLookup.set(userNode.id, {
|
||||||
@@ -103,7 +107,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
},
|
},
|
||||||
internals: {
|
internals: {
|
||||||
positionAbsolute: userNode.position,
|
positionAbsolute: userNode.position,
|
||||||
handleBounds: currentStoreNode?.internals?.handleBounds,
|
handleBounds: currentStoreNode?.internals.handleBounds,
|
||||||
z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
|
z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
|
||||||
userNode,
|
userNode,
|
||||||
isParent: false,
|
isParent: false,
|
||||||
@@ -142,8 +146,11 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleParentExpand(nodes: InternalNodeBase[], nodeLookup: NodeLookup): NodeChange[] {
|
export function handleParentExpand(
|
||||||
const changes: NodeChange[] = [];
|
nodes: InternalNodeBase[],
|
||||||
|
nodeLookup: NodeLookup
|
||||||
|
): (NodeDimensionChange | NodePositionChange)[] {
|
||||||
|
const changes: (NodeDimensionChange | NodePositionChange)[] = [];
|
||||||
const chilNodeRects = new Map<string, Rect>();
|
const chilNodeRects = new Map<string, Rect>();
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
@@ -211,14 +218,14 @@ export function updateNodeDimensions<NodeType extends InternalNodeBase>(
|
|||||||
nodeLookup: Map<string, NodeType>,
|
nodeLookup: Map<string, NodeType>,
|
||||||
domNode: HTMLElement | null,
|
domNode: HTMLElement | null,
|
||||||
nodeOrigin?: NodeOrigin
|
nodeOrigin?: NodeOrigin
|
||||||
): NodeChange[] {
|
): (NodeDimensionChange | NodePositionChange)[] {
|
||||||
const viewportNode = domNode?.querySelector('.xyflow__viewport');
|
const viewportNode = domNode?.querySelector('.xyflow__viewport');
|
||||||
|
|
||||||
if (!viewportNode) {
|
if (!viewportNode) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes: NodeChange[] = [];
|
const changes: (NodeDimensionChange | NodePositionChange)[] = [];
|
||||||
const style = window.getComputedStyle(viewportNode);
|
const style = window.getComputedStyle(viewportNode);
|
||||||
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
||||||
// in this array we collect nodes, that might trigger changes (like expanding parent)
|
// in this array we collect nodes, that might trigger changes (like expanding parent)
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export function getDragItems<NodeType extends NodeBase>(
|
|||||||
id: internalNode.id,
|
id: internalNode.id,
|
||||||
position: internalNode.position || { x: 0, y: 0 },
|
position: internalNode.position || { x: 0, y: 0 },
|
||||||
distance: {
|
distance: {
|
||||||
x: mousePos.x - (internalNode.internals.positionAbsolute?.x ?? 0),
|
x: mousePos.x - internalNode.internals.positionAbsolute.x,
|
||||||
y: mousePos.y - (internalNode.internals.positionAbsolute?.y ?? 0),
|
y: mousePos.y - internalNode.internals.positionAbsolute.y,
|
||||||
},
|
},
|
||||||
extent: internalNode.extent,
|
extent: internalNode.extent,
|
||||||
parentId: internalNode.parentId,
|
parentId: internalNode.parentId,
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export function getHandles(
|
|||||||
id: h.id || null,
|
id: h.id || null,
|
||||||
type,
|
type,
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
x: (node.internals.positionAbsolute.x ?? 0) + h.x + h.width / 2,
|
x: node.internals.positionAbsolute.x + h.x + h.width / 2,
|
||||||
y: (node.internals.positionAbsolute.y ?? 0) + h.y + h.height / 2,
|
y: node.internals.positionAbsolute.y + h.y + h.height / 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
Reference in New Issue
Block a user