Feat: add hooks and functions for computed flows (#3688)

Feat: add hooks for computed flows
This commit is contained in:
Moritz Klack
2023-12-13 11:55:29 +01:00
committed by GitHub
60 changed files with 1547 additions and 187 deletions
+14 -2
View File
@@ -44,7 +44,9 @@ import CancelConnection from '../examples/CancelConnection';
import InteractiveMinimap from '../examples/InteractiveMinimap';
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
import NodeToolbar from '../examples/NodeToolbar';
import useNodesInitialized from '../examples/UseNodesInit';
import UseNodesInitialized from '../examples/UseNodesInit';
import UseNodesData from '../examples/UseNodesData';
import UseHandleConnections from '../examples/UseHandleConnections';
export interface IRoute {
name: string;
@@ -261,7 +263,7 @@ const routes: IRoute[] = [
{
name: 'useNodesInitialized',
path: 'use-nodes-initialized',
component: useNodesInitialized,
component: UseNodesInitialized,
},
{
name: 'useOnSelectionChange',
@@ -273,6 +275,16 @@ const routes: IRoute[] = [
path: 'usereactflow',
component: UseReactFlow,
},
{
name: 'useHandleConnections',
path: 'usehandleconnections',
component: UseHandleConnections,
},
{
name: 'useNodesData',
path: 'usenodesdata',
component: UseNodesData,
},
{
name: 'useUpdateNodeInternals',
path: 'useupdatenodeinternals',
@@ -1,4 +1,4 @@
import React, { memo, FC, CSSProperties, useCallback } from 'react';
import React, { memo, FC, CSSProperties, useCallback, useEffect } from 'react';
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from '@xyflow/react';
const targetHandleStyle: CSSProperties = { background: '#555' };
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { ReactFlow, Node, Edge, useNodesState, useEdgesState } from '@xyflow/react';
import { ReactFlow, Node, Edge, useNodesState, useEdgesState, ReactFlowProvider, useReactFlow } from '@xyflow/react';
import styles from './updatenode.module.css';
@@ -13,6 +13,7 @@ const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
const UpdateNode = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const { updateNode } = useReactFlow();
const [nodeName, setNodeName] = useState<string>('Node 1');
const [nodeBg, setNodeBg] = useState<string>('#eee');
@@ -80,9 +81,19 @@ const UpdateNode = () => {
<label>hidden:</label>
<input type="checkbox" checked={nodeHidden} onChange={(evt) => setNodeHidden(evt.target.checked)} />
</div>
<button
onClick={() => updateNode('1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } }))}
>
update position
</button>
</div>
</ReactFlow>
);
};
export default UpdateNode;
export default () => (
<ReactFlowProvider>
<UpdateNode />
</ReactFlowProvider>
);
@@ -0,0 +1,39 @@
import { memo, FC, useEffect, useCallback } from 'react';
import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleComponentProps } from '@xyflow/react';
function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) {
const onConnect = useCallback(
(connections: Connection[]) => console.log('onConnect handler, node id:', nodeId, connections),
[nodeId]
);
const onDisconnect = useCallback(
(connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections),
[nodeId]
);
const connections = useHandleConnections({
handleType: handleProps.type,
handleId: handleProps.id,
onConnect,
onDisconnect,
});
useEffect(() => {
console.log('useEffect, node id:', nodeId, handleProps.type, connections);
}, [connections]);
return <Handle {...handleProps} />;
}
const CustomNode: FC<NodeProps> = ({ id }) => {
return (
<div style={{ background: '#333', color: '#fff', padding: 10, fontSize: 12, borderRadius: 10 }}>
<CustomHandle nodeId={id} type="target" position={Position.Left} />
<div>node {id}</div>
<CustomHandle nodeId={id} type="source" position={Position.Right} id="a" style={{ top: 10 }} />
<CustomHandle nodeId={id} type="source" position={Position.Right} id="b" style={{ top: 20 }} />
</div>
);
};
export default memo(CustomNode);
@@ -0,0 +1,41 @@
import { memo, FC, useEffect, useCallback } from 'react';
import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleComponentProps } from '@xyflow/react';
function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeId: string }) {
const onConnect = useCallback(
(connections: Connection[]) => {
console.log('onConnect handler, node id:', nodeId, connections);
},
[nodeId]
);
const onDisconnect = useCallback(
(connections: Connection[]) => {
console.log('onDisconnect handler, node id:', nodeId, connections);
},
[nodeId]
);
const connections = useHandleConnections({
handleType: handleProps.type,
handleId: handleProps.id,
onConnect,
onDisconnect,
});
useEffect(() => {
console.log('useEffect, node id:', nodeId, handleProps.type, connections);
}, [connections]);
return <Handle {...handleProps} />;
}
const CustomNode: FC<NodeProps> = ({ id }) => {
return (
<div style={{ background: '#333', color: '#fff', padding: 10, fontSize: 12, borderRadius: 10 }}>
<CustomHandle nodeId={id} type="target" position={Position.Left} />
<div>node {id}</div>
<CustomHandle nodeId={id} type="source" position={Position.Right} />
</div>
);
};
export default memo(CustomNode);
@@ -0,0 +1,118 @@
import { useCallback } from 'react';
import {
ReactFlow,
MiniMap,
Controls,
addEdge,
Connection,
useNodesState,
useEdgesState,
Background,
} from '@xyflow/react';
import MultiHandleNode from './MultiHandleNode';
import SingleHandleNode from './SingleHandleNode';
const nodeTypes = {
multi: MultiHandleNode,
single: SingleHandleNode,
};
const initNodes = [
{
id: '1',
type: 'single',
data: {},
position: { x: 0, y: 0 },
},
{
id: '2',
type: 'single',
data: {},
position: { x: 200, y: -100 },
},
{
id: '3',
type: 'single',
data: {},
position: { x: 200, y: 100 },
},
{
id: '4',
type: 'multi',
data: {},
position: { x: 400, y: 0 },
},
{
id: '5',
type: 'multi',
data: {},
position: { x: 600, y: -100 },
},
{
id: '6',
type: 'multi',
data: {},
position: { x: 600, y: 100 },
},
];
const initEdges = [
{
id: 'e1-2',
source: '1',
target: '2',
},
{
id: 'e1-3',
source: '1',
target: '3',
},
{
id: 'e4a-5',
source: '4',
sourceHandle: 'a',
target: '5',
},
{
id: 'e4b-5',
source: '4',
sourceHandle: 'b',
target: '6',
},
];
const defaultEdgeOptions = {
animated: true,
};
const CustomNodeFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
minZoom={0.3}
maxZoom={2}
colorMode="dark"
defaultEdgeOptions={defaultEdgeOptions}
>
<MiniMap />
<Controls />
<Background />
</ReactFlow>
);
};
export default CustomNodeFlow;
@@ -0,0 +1,22 @@
import { memo } from 'react';
import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react';
function ResultNode() {
const connections = useHandleConnections({
type: 'target',
});
const nodesData = useNodesData(connections.map((connection) => connection.source));
return (
<div style={{ background: '#eee', color: '#222', padding: 10, fontSize: 12, borderRadius: 10 }}>
<Handle type="target" position={Position.Left} />
<div>
incoming texts:{' '}
{nodesData?.filter((nodeData) => nodeData.text !== undefined).map(({ text }, i) => <div key={i}>{text}</div>) ||
'none'}
</div>
</div>
);
}
export default memo(ResultNode);
@@ -0,0 +1,20 @@
import { memo, ChangeEventHandler } from 'react';
import { Position, NodeProps, Handle, useReactFlow } from '@xyflow/react';
function TextNode({ id, data }: NodeProps) {
const { updateNodeData } = useReactFlow();
const onChange: ChangeEventHandler<HTMLInputElement> = (evt) => updateNodeData(id, { text: evt.target.value });
return (
<div style={{ background: '#eee', color: '#222', padding: 10, fontSize: 12, borderRadius: 10 }}>
<div>node {id}</div>
<div>
<input onChange={onChange} value={data.text} />
</div>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default memo(TextNode);
@@ -0,0 +1,24 @@
import { memo, useEffect } from 'react';
import { Position, NodeProps, useReactFlow, Handle, useHandleConnections, useNodesData } from '@xyflow/react';
function UppercaseNode({ id }: NodeProps) {
const { updateNodeData } = useReactFlow();
const connections = useHandleConnections({
type: 'target',
});
const nodeData = useNodesData(connections[0]?.source);
useEffect(() => {
updateNodeData(id, { text: nodeData?.text.toUpperCase() });
}, [nodeData]);
return (
<div style={{ background: '#eee', color: '#222', padding: 10, fontSize: 12, borderRadius: 10 }}>
<Handle type="target" position={Position.Left} isConnectable={connections.length === 0} />
<div>uppercase transform</div>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default memo(UppercaseNode);
@@ -0,0 +1,102 @@
import { useCallback } from 'react';
import {
ReactFlow,
Controls,
addEdge,
Connection,
useNodesState,
useEdgesState,
Background,
Node,
Edge,
} from '@xyflow/react';
import TextNode from './TextNode';
import ResultNode from './ResultNode';
import UppercaseNode from './UppercaseNode';
export type TextNode = Node<{ text: string }, 'text'>;
export type ResultNode = Node<{}, 'result'>;
export type UppercaseNode = Node<{}, 'uppercase'>;
export type MyNode = Node<{ text: string }, 'text'> | Node<{}, 'result'> | Node<{}, 'uppercase'>;
const nodeTypes = {
text: TextNode,
result: ResultNode,
uppercase: UppercaseNode,
};
const initNodes: MyNode[] = [
{
id: '1',
type: 'text',
data: {
text: 'hello',
},
position: { x: -100, y: -50 },
},
{
id: '1a',
type: 'uppercase',
data: {},
position: { x: 100, y: 0 },
},
{
id: '2',
type: 'text',
data: {
text: 'world',
},
position: { x: 0, y: 100 },
},
{
id: '3',
type: 'result',
data: {},
position: { x: 300, y: 50 },
},
];
const initEdges: Edge[] = [
{
id: 'e1-1a',
source: '1',
target: '1a',
},
{
id: 'e1a-3',
source: '1a',
target: '3',
},
{
id: 'e2-3',
source: '2',
target: '3',
},
];
const CustomNodeFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
>
<Controls />
<Background />
</ReactFlow>
);
};
export default CustomNodeFlow;
@@ -11,6 +11,7 @@
'drag-n-drop',
'edges',
'figma',
'handle-connect',
'interaction',
'intersections',
'node-toolbar',
@@ -18,6 +19,7 @@
'stress',
'subflows',
'two-way-viewport',
'usenodesdata',
'usesvelteflow',
'useupdatenodeinternals',
'validation'
@@ -1,6 +1,12 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, useSvelteFlow, type Edge, type Node } from '@xyflow/svelte';
import {
SvelteFlow,
useSvelteFlow,
type Edge,
type Node,
type OnConnectEnd
} from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
@@ -16,16 +22,18 @@
const nodes = writable<Node[]>(initialNodes);
const edges = writable<Edge[]>([]);
let connectingNodeId: string = '0';
let connectingNodeId: string | null = '0';
let rect: DOMRectReadOnly;
let id = 1;
const getId = () => `${id++}`;
const { screenToFlowPosition, flowToScreenPosition } = useSvelteFlow();
function handleConnectEnd({ detail: { event } }: { detail: { event: MouseEvent | TouchEvent } }) {
const handleConnectEnd: OnConnectEnd = (event) => {
if (!connectingNodeId) return;
// See of connection landed inside the flow pane
const targetIsPane = event.target?.classList.contains('svelte-flow__pane');
const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('svelte-flow__pane');
if (targetIsPane) {
const id = getId();
const position = {
@@ -58,7 +66,7 @@
$nodes = $nodes;
$edges = $edges;
}
}
};
</script>
<svelte:window />
@@ -69,11 +77,11 @@
{edges}
fitView
fitViewOptions={{ padding: 2 }}
on:connectstart={({ detail: { nodeId } }) => {
onconnectstart={(_, { nodeId }) => {
// Memorize the nodeId you start draggin a connection line from a node
connectingNodeId = nodeId;
}}
on:connectend={handleConnectEnd}
onconnectend={handleConnectEnd}
/>
</div>
@@ -87,7 +87,7 @@
{nodeTypes}
style="--xy-background-color: {$bgColor}"
fitView
on:connect={onConnect}
onconnect={onConnect}
>
<Controls />
<Background variant={BackgroundVariant.Dots} />
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Writable } from 'svelte/store';
import { Handle, Position, type NodeProps, BezierEdge, SmoothStepEdge } from '@xyflow/svelte';
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
@@ -10,7 +10,7 @@
</script>
<div class="custom">
<Handle type="target" position={Position.Left} on:connect />
<Handle type="target" position={Position.Left} />
<div>
Custom Color Picker Node: <strong>{$colorStore}</strong>
</div>
@@ -20,14 +20,8 @@
on:input={(evt) => colorStore.set(evt.currentTarget.value)}
value={$colorStore}
/>
<Handle type="source" position={Position.Right} id="a" style="top: 20px;" on:connect />
<Handle
type="source"
position={Position.Right}
id="b"
style="top: auto; bottom: 10px;"
on:connect
/>
<Handle type="source" position={Position.Right} id="a" style="top: 20px;" />
<Handle type="source" position={Position.Right} id="b" style="top: auto; bottom: 10px;" />
</div>
<style>
@@ -0,0 +1,94 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
Background,
BackgroundVariant,
MiniMap,
type Node,
type NodeTypes,
type Edge
} from '@xyflow/svelte';
import SingleHandleNode from './SingleHandleNode.svelte';
import MultiHandleNode from './MultiHandleNode.svelte';
import '@xyflow/svelte/dist/style.css';
const nodeTypes: NodeTypes = {
single: SingleHandleNode,
multi: MultiHandleNode
};
const nodes = writable<Node[]>([
{
id: '1',
type: 'single',
data: {},
position: { x: 0, y: 0 }
},
{
id: '2',
type: 'single',
data: {},
position: { x: 200, y: -100 }
},
{
id: '3',
type: 'single',
data: {},
position: { x: 200, y: 100 }
},
{
id: '4',
type: 'multi',
data: {},
position: { x: 400, y: 0 }
},
{
id: '5',
type: 'multi',
data: {},
position: { x: 600, y: -100 }
},
{
id: '6',
type: 'multi',
data: {},
position: { x: 600, y: 100 }
}
]);
const edges = writable<Edge[]>([
{
id: 'e1-2',
source: '1',
target: '2'
},
{
id: 'e1-3',
source: '1',
target: '3'
},
{
id: 'e4a-5',
source: '4',
sourceHandle: 'a',
target: '5'
},
{
id: 'e4b-5',
source: '4',
sourceHandle: 'b',
target: '6'
}
]);
</script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView colorMode="dark">
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
</SvelteFlow>
@@ -0,0 +1,110 @@
<script lang="ts">
import {
Handle,
Position,
type NodeProps,
type Connection,
useHandleConnections
} from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
function onConnectTarget(connection: Connection[]) {
console.log('connect target', connection);
}
function onConnectSource(handleId: string, connection: Connection[]) {
console.log('connect source', handleId, connection);
}
function onDisconnectTarget(connection: Connection[]) {
console.log('disconnect target', connection);
}
function onDisconnectSource(handleId: string, connection: Connection[]) {
console.log('disconnect source', handleId, connection);
}
const connections = useHandleConnections({ nodeId: id, type: 'target' });
$: {
console.log('connections', id, $connections);
}
export let data: $$Props['data'];
export let targetPosition: $$Props['targetPosition'] = Position.Top;
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
export let width: $$Props['width'] = undefined;
export let height: $$Props['height'] = undefined;
export let selected: $$Props['selected'] = undefined;
export let type: $$Props['type'] = undefined;
export let zIndex: $$Props['zIndex'] = undefined;
export let dragging: $$Props['dragging'] = false;
export let dragHandle: $$Props['dragHandle'] = undefined;
export let positionAbsolute: $$Props['positionAbsolute'] = {
x: 0,
y: 0
};
export let isConnectable: $$Props['isConnectable'] = undefined;
data;
targetPosition;
sourcePosition;
width;
height;
selected;
type;
zIndex;
dragging;
dragHandle;
positionAbsolute;
isConnectable;
</script>
<div class="custom">
<Handle
type="target"
position={Position.Left}
onconnect={onConnectTarget}
ondisconnect={onDisconnectTarget}
/>
<div>node {id}</div>
<Handle
id="a"
type="source"
position={Position.Right}
onconnect={(connections) => onConnectSource('a', connections)}
ondisconnect={(connections) => onDisconnectSource('a', connections)}
class="source-a"
/>
<Handle
id="b"
type="source"
position={Position.Right}
onconnect={(connections) => onConnectSource('b', connections)}
ondisconnect={(connections) => onDisconnectSource('b', connections)}
class="source-b"
/>
</div>
<style>
.custom {
background-color: #333;
padding: 10px;
border-radius: 10px;
color: #fff;
}
.custom :global(.source-a) {
top: 5px;
transform: translate(50%, 0);
}
.custom :global(.source-b) {
bottom: 5px;
top: auto;
transform: translate(50%, 0);
}
</style>
@@ -0,0 +1,77 @@
<script lang="ts">
import { Handle, Position, type NodeProps, type Connection } from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
function onConnectTarget(connection: Connection[]) {
console.log('connect target', connection);
}
function onConnectSource(connection: Connection[]) {
console.log('connect source', connection);
}
function onDisconnectTarget(connection: Connection[]) {
console.log('disconnect target', connection);
}
function onDisconnectSource(connection: Connection[]) {
console.log('disconnect source', connection);
}
export let data: $$Props['data'];
export let targetPosition: $$Props['targetPosition'] = Position.Top;
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
export let width: $$Props['width'] = undefined;
export let height: $$Props['height'] = undefined;
export let selected: $$Props['selected'] = undefined;
export let type: $$Props['type'] = undefined;
export let zIndex: $$Props['zIndex'] = undefined;
export let dragging: $$Props['dragging'] = false;
export let dragHandle: $$Props['dragHandle'] = undefined;
export let positionAbsolute: $$Props['positionAbsolute'] = {
x: 0,
y: 0
};
export let isConnectable: $$Props['isConnectable'] = undefined;
data;
targetPosition;
sourcePosition;
width;
height;
selected;
type;
zIndex;
dragging;
dragHandle;
positionAbsolute;
isConnectable;
</script>
<div class="custom">
<Handle
type="target"
position={Position.Left}
onconnect={onConnectTarget}
ondisconnect={onDisconnectTarget}
/>
<div>node {id}</div>
<Handle
type="source"
position={Position.Right}
onconnect={onConnectSource}
ondisconnect={onDisconnectSource}
/>
</div>
<style>
.custom {
background-color: #333;
padding: 10px;
border-radius: 10px;
color: #fff;
}
</style>
@@ -12,7 +12,8 @@
type EdgeTypes,
type Node,
type Edge,
ConnectionMode
ConnectionMode,
useSvelteFlow
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
@@ -150,9 +151,9 @@
on:nodemouseenter={(event) => console.log('on node enter', event)}
on:nodemouseleave={(event) => console.log('on node leave', event)}
on:edgeclick={(event) => console.log('edge click', event)}
on:connectstart={(event) => console.log('on connect start', event)}
on:connect={(event) => console.log('on connect', event)}
on:connectend={(event) => console.log('on connect end', event)}
onconnectstart={(event) => console.log('on connect start', event)}
onconnect={(event) => console.log('on connect', event)}
onconnectend={(event) => console.log('on connect end', event)}
on:paneclick={(event) => console.log('on pane click', event)}
on:panecontextmenu={(event) => {
console.log('on pane contextmenu', event);
@@ -0,0 +1,80 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
Background,
BackgroundVariant,
MiniMap,
type Node,
type NodeTypes,
type Edge
} from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
import TextNode from './TextNode.svelte';
import UppercaseNode from './UppercaseNode.svelte';
import ResultNode from './ResultNode.svelte';
const nodeTypes: NodeTypes = {
text: TextNode,
uppercase: UppercaseNode,
result: ResultNode
};
const nodes = writable<Node[]>([
{
id: '1',
type: 'text',
data: {
text: 'hello'
},
position: { x: -100, y: -50 }
},
{
id: '1a',
type: 'uppercase',
data: {},
position: { x: 100, y: 0 }
},
{
id: '2',
type: 'text',
data: {
text: 'world'
},
position: { x: 0, y: 100 }
},
{
id: '3',
type: 'result',
data: {},
position: { x: 300, y: 50 }
}
]);
const edges = writable<Edge[]>([
{
id: 'e1-1a',
source: '1',
target: '1a'
},
{
id: 'e1a-3',
source: '1a',
target: '3'
},
{
id: 'e2-3',
source: '2',
target: '3'
}
]);
</script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
</SvelteFlow>
@@ -0,0 +1,37 @@
<script lang="ts">
import {
Handle,
Position,
useHandleConnections,
useNodesData,
type NodeProps
} from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
const connections = useHandleConnections({
nodeId: id,
type: 'target'
});
$: nodeData = useNodesData($connections.map((connection) => connection.source));
</script>
<div class="custom">
<Handle type="target" position={Position.Left} />
<div>incoming texts:</div>
{#each $nodeData as data}
<div>{data.text}</div>
{/each}
</div>
<style>
.custom {
background-color: #eee;
padding: 10px;
border-radius: 10px;
}
</style>
@@ -0,0 +1,29 @@
<script lang="ts">
import { Handle, Position, type NodeProps, useSvelteFlow } from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
export let data: $$Props['data'];
const { updateNodeData } = useSvelteFlow();
</script>
<div class="custom">
<div>node {id}</div>
<div>
<input
value={data.text}
on:input={(evt) => updateNodeData(id, { text: evt.currentTarget.value })}
/>
</div>
<Handle type="source" position={Position.Right} />
</div>
<style>
.custom {
background-color: #eee;
padding: 10px;
border-radius: 10px;
}
</style>
@@ -0,0 +1,40 @@
<script lang="ts">
import {
Handle,
Position,
useHandleConnections,
useNodesData,
useSvelteFlow,
type NodeProps
} from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
const { updateNodeData } = useSvelteFlow();
const connections = useHandleConnections({
nodeId: id,
type: 'target'
});
$: nodeData = useNodesData($connections[0]?.source);
$: {
updateNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' });
}
</script>
<div class="custom">
<Handle type="target" position={Position.Left} isConnectable={$connections.length === 0} />
<div>uppercase transform</div>
<Handle type="source" position={Position.Right} />
</div>
<style>
.custom {
background-color: #eee;
padding: 10px;
border-radius: 10px;
}
</style>
@@ -15,15 +15,9 @@
};
</script>
<Handle type="target" position={Position.Top} on:connect />
<Handle type="target" position={Position.Top} />
<button on:click={onClick}>add handle</button>
{#each Array.from({ length: handleCount }) as handle, i}
<Handle
type="source"
position={Position.Bottom}
id={`${i}`}
style={`left: ${i * 10}px;`}
on:connect
/>
<Handle type="source" position={Position.Bottom} id={`${i}`} style={`left: ${i * 10}px;`} />
{/each}
@@ -6,7 +6,9 @@
Background,
BackgroundVariant,
MiniMap,
type NodeTypes
type NodeTypes,
useSvelteFlow,
Panel
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
@@ -50,6 +52,12 @@
target: '3'
}
]);
const { updateNode } = useSvelteFlow();
const updateNodePosition = () => {
updateNode('1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } }));
};
</script>
<main>
@@ -57,6 +65,8 @@
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Panel><button on:click={updateNodePosition}>update node</button></Panel>
</SvelteFlow>
</main>
@@ -171,7 +171,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
lib,
});
if (isValid) {
if (isValid && connection) {
onConnectExtended(connection);
}
@@ -0,0 +1,54 @@
import { useEffect, useMemo, useRef } from 'react';
import { Connection, HandleType, areConnectionMapsEqual, handleConnectionChange } from '@xyflow/system';
import { useStore } from './useStore';
import { useNodeId } from '../contexts/NodeIdContext';
type useHandleConnectionsParams = {
type: HandleType;
id?: string | null;
nodeId?: string;
onConnect?: (connections: Connection[]) => void;
onDisconnect?: (connections: Connection[]) => void;
};
/**
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
*
* @public
* @param param.type - handle type 'source' or 'target'
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
* @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used
* @param param.onConnect - gets called when a connection is established
* @param param.onDisconnect - gets called when a connection is removed
* @returns an array with connections
*/
export function useHandleConnections({
type,
id = null,
nodeId,
onConnect,
onDisconnect,
}: useHandleConnectionsParams): Connection[] {
const _nodeId = useNodeId();
const prevConnections = useRef<Map<string, Connection> | null>(null);
const currentNodeId = nodeId || _nodeId;
const connections = useStore(
(state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`),
areConnectionMapsEqual
);
useEffect(() => {
// @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
if (prevConnections.current && prevConnections.current !== connections) {
const _connections = connections ?? new Map();
handleConnectionChange(prevConnections.current, _connections, onDisconnect);
handleConnectionChange(_connections, prevConnections.current, onConnect);
}
prevConnections.current = connections ?? new Map();
}, [connections, onConnect, onDisconnect]);
return useMemo(() => Array.from(connections?.values() ?? []), [connections]);
}
+40
View File
@@ -0,0 +1,40 @@
import { useCallback } from 'react';
import { shallow } from 'zustand/shallow';
import { useStore } from '../hooks/useStore';
import type { Node } from '../types';
export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null;
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][];
export function useNodesData<NodeType extends Node = Node>(
nodeIds: string[],
guard: (node: Node) => node is NodeType
): NodeType['data'][];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useNodesData(nodeIds: any): any {
const nodesData = useStore(
useCallback(
(s) => {
if (!Array.isArray(nodeIds)) {
return s.nodeLookup.get(nodeIds)?.data || null;
}
const data = [];
for (const nodeId of nodeIds) {
const nodeData = s.nodeLookup.get(nodeId)?.data;
if (nodeData) {
data.push(nodeData);
}
}
return data;
},
[nodeIds]
),
shallow
);
return nodesData;
}
+34 -1
View File
@@ -10,7 +10,7 @@ import {
} from '@xyflow/system';
import useViewportHelper from './useViewportHelper';
import { useStoreApi } from '../hooks/useStore';
import { useStoreApi } from './useStore';
import type {
ReactFlowInstance,
Instance,
@@ -24,6 +24,7 @@ import type {
Node,
Edge,
} from '../types';
import { isNode } from '../utils';
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> {
@@ -271,6 +272,36 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
return getOutgoersBase(node, nodes, edges);
}, []);
const updateNode = useCallback<Instance.UpdateNode>(
(id, nodeUpdate, options = { replace: true }) => {
setNodes((prevNodes) =>
prevNodes.map((node) => {
if (node.id === id) {
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate;
return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode };
}
return node;
})
);
},
[setNodes]
);
const updateNodeData = useCallback<Instance.UpdateNodeData>(
(id, dataUpdate, options = { replace: false }) => {
updateNode(
id,
(node) => {
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } };
},
options
);
},
[updateNode]
);
return useMemo(() => {
return {
...viewportHelper,
@@ -289,6 +320,8 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
getConnectedEdges,
getIncomers,
getOutgoers,
updateNode,
updateNodeData,
};
}, [
viewportHelper,
+3 -1
View File
@@ -1,5 +1,5 @@
export { default as ReactFlow } from './container/ReactFlow';
export { default as Handle } from './components/Handle';
export { default as Handle, type HandleComponentProps } from './components/Handle';
export { default as EdgeText } from './components/Edges/EdgeText';
export { StraightEdge } from './components/Edges/StraightEdge';
export { StepEdge } from './components/Edges/StepEdge';
@@ -22,6 +22,8 @@ export { useStore, useStoreApi } from './hooks/useStore';
export { default as useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/useOnViewportChange';
export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange';
export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized';
export { useHandleConnections } from './hooks/useHandleConnections';
export { useNodesData } from './hooks/useNodesData';
export { useNodeId } from './contexts/NodeIdContext';
export { applyNodeChanges, applyEdgeChanges, handleParentExpand } from './utils/changes';
+8 -2
View File
@@ -7,6 +7,7 @@ import {
panBy as panBySystem,
Dimensions,
updateNodeDimensions as updateNodeDimensionsSystem,
updateConnectionLookup,
} from '@xyflow/system';
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
@@ -49,8 +50,12 @@ const createRFStore = ({
set({ nodes: nextNodes });
},
setEdges: (edges: Edge[]) => {
const { defaultEdgeOptions = {} } = get();
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
const { defaultEdgeOptions = {}, connectionLookup } = get();
const nextEdges = edges.map((e) => ({ ...defaultEdgeOptions, ...e }));
updateConnectionLookup(connectionLookup, nextEdges);
set({ edges: nextEdges });
},
// when the user works with an uncontrolled flow,
// we set a flag `hasDefaultNodes` / `hasDefaultEdges`
@@ -326,6 +331,7 @@ const createRFStore = ({
set(currentConnection);
},
reset: () => {
// @todo: what should we do about this? Do we still need it?
// if you are on a SPA with multiple flows, we want to make sure that the store gets resetted
+4 -1
View File
@@ -5,6 +5,7 @@ import {
getNodesBounds,
getViewportForBounds,
Transform,
updateConnectionLookup,
} from '@xyflow/system';
import type { Edge, Node, ReactFlowStore } from '../types';
@@ -22,7 +23,8 @@ const getInitialState = ({
height?: number;
fitView?: boolean;
} = {}): ReactFlowStore => {
const nodeLookup = new Map<string, Node>();
const nodeLookup = new Map();
const connectionLookup = updateConnectionLookup(new Map(), edges);
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
let transform: Transform = [0, 0, 1];
@@ -42,6 +44,7 @@ const getInitialState = ({
nodes: nextNodes,
nodeLookup,
edges: edges,
connectionLookup,
onNodesChange: null,
onEdgesChange: null,
hasDefaultNodes: false,
+13
View File
@@ -45,6 +45,17 @@ export namespace Instance {
export type getConnectedEdges = (id: string | (Node | { id: Node['id'] })[]) => Edge[];
export type getIncomers = (node: string | Node | { id: Node['id'] }) => Node[];
export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[];
export type UpdateNode = (
id: string,
dataUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
options?: { replace: boolean }
) => void;
export type UpdateNodeData = (
id: string,
dataUpdate: object | ((node: Node) => object),
options?: { replace: boolean }
) => void;
}
export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
@@ -60,5 +71,7 @@ export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
deleteElements: Instance.DeleteElements;
getIntersectingNodes: Instance.GetIntersectingNodes<NodeData>;
isNodeIntersecting: Instance.IsNodeIntersecting<NodeData>;
updateNode: Instance.UpdateNode;
updateNodeData: Instance.UpdateNodeData;
viewportInitialized: boolean;
} & Omit<ViewportHelperFunctions, 'initialized'>;
+3
View File
@@ -24,6 +24,7 @@ import {
type OnMoveEnd,
type IsValidConnection,
type UpdateConnection,
Connection,
} from '@xyflow/system';
import type {
@@ -49,6 +50,8 @@ export type ReactFlowStore = {
nodes: Node[];
nodeLookup: Map<string, Node>;
edges: Edge[];
connectionLookup: Map<string, Map<string, Connection>>;
onNodesChange: OnNodesChange | null;
onEdgesChange: OnEdgesChange | null;
hasDefaultNodes: boolean;
+2 -2
View File
@@ -10,8 +10,8 @@ import {
import type { Edge, Node } from '../types';
export const isNode = isNodeBase<Node, Edge>;
export const isEdge = isEdgeBase<Node, Edge>;
export const isNode = isNodeBase<Node>;
export const isEdge = isEdgeBase<Edge>;
export const getOutgoers = getOutgoersBase<Node, Edge>;
export const getIncomers = getIncomersBase<Node, Edge>;
export const addEdge = addEdgeBase<Edge>;
+1 -1
View File
@@ -41,7 +41,7 @@
"access": "public"
},
"dependencies": {
"@svelte-put/shortcut": "^3.0.0",
"@svelte-put/shortcut": "^3.1.0",
"@xyflow/system": "workspace:*",
"classcat": "^5.0.4"
},
@@ -1,17 +1,18 @@
<script lang="ts">
import { getContext, createEventDispatcher } from 'svelte';
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import cc from 'classcat';
import {
Position,
XYHandle,
isMouseEvent,
type Connection,
type HandleType
areConnectionMapsEqual,
handleConnectionChange
} from '@xyflow/system';
import { useStore } from '$lib/store';
import type { HandleComponentProps } from '$lib/types';
import type { Writable } from 'svelte/store';
type $$Props = HandleComponentProps;
@@ -20,6 +21,8 @@
export let position: $$Props['position'] = Position.Top;
export let style: $$Props['style'] = undefined;
export let isConnectable: $$Props['isConnectable'] = undefined;
export let onconnect: $$Props['onconnect'] = undefined;
export let ondisconnect: $$Props['ondisconnect'] = undefined;
// export let isConnectableStart: $$Props['isConnectableStart'] = undefined;
// export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined;
@@ -32,18 +35,6 @@
$: handleConnectable = isConnectable !== undefined ? isConnectable : $connectable;
const handleId = id || null;
const dispatch = createEventDispatcher<{
connect: { connection: Connection };
connectstart: {
event: MouseEvent | TouchEvent;
nodeId: string | null;
handleId: string | null;
handleType: HandleType | null;
};
connectend: {
event: MouseEvent | TouchEvent;
};
}>();
const store = useStore();
const {
@@ -59,7 +50,12 @@
panBy,
cancelConnection,
updateConnection,
autoPanOnConnect
autoPanOnConnect,
edges,
connectionLookup,
onconnect: onConnectAction,
onconnectstart: onConnectStartAction,
onconnectend: onConnectEndAction
} = store;
function onPointerDown(event: MouseEvent | TouchEvent) {
@@ -88,26 +84,43 @@
}
addEdge(edge);
// @todo: should we change/ improve the stuff we are passing here?
// instead of source/target we could pass fromNodeId, fromHandleId, etc
dispatch('connect', { connection });
$onConnectAction?.(connection);
},
onConnectStart: (event, startParams) => {
dispatch('connectstart', {
event,
$onConnectStartAction?.(event, {
nodeId: startParams.nodeId,
handleId: startParams.handleId,
handleType: startParams.handleType
});
},
onConnectEnd: (event) => {
dispatch('connectend', { event });
$onConnectEndAction?.(event);
},
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom]
});
}
}
let prevConnections: Map<string, Connection> | null = null;
let connections: Map<string, Connection> | undefined;
$: if (onconnect || ondisconnect) {
// connectionLookup is not reactive, so we use edges to get notified about updates
$edges;
connections = $connectionLookup.get(`${nodeId}-${type}-${id || null}`);
}
$: {
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
const _connections = connections ?? new Map();
handleConnectionChange(prevConnections, _connections, ondisconnect);
handleConnectionChange(_connections, prevConnections, onconnect);
}
prevConnections = connections ?? new Map();
}
// @todo implement connectablestart, connectableend
</script>
@@ -1,10 +1,10 @@
<script lang="ts">
import { shortcut, type ShortcutModifierDefinition } from '@svelte-put/shortcut';
import { isInputDOMNode, isMacOs } from '@xyflow/system';
import { useStore } from '$lib/store';
import type { KeyHandlerProps } from './types';
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
import { isMacOs } from '@xyflow/system';
type $$Props = KeyHandlerProps;
@@ -23,7 +23,7 @@
} = useStore();
function isKeyObject(key?: KeyDefinition | null): key is KeyDefinitionObject {
return typeof key === 'object';
return key !== null && typeof key === 'object';
}
function getModifier(key?: KeyDefinition | null): ShortcutModifierDefinition {
@@ -71,16 +71,19 @@
trigger: [
{
...selectionKeyDefinition,
callback: () => selectionKeyDefinition.key && selectionKeyPressed.set(true)
enabled: selectionKeyDefinition.key !== null,
callback: () => selectionKeyPressed.set(true)
}
],
type: 'keydown'
}}
use:shortcut={{
trigger: [
{
...selectionKeyDefinition,
callback: () => selectionKeyDefinition.key && selectionKeyPressed.set(false)
enabled: selectionKeyDefinition.key !== null,
callback: () => selectionKeyPressed.set(false)
}
],
type: 'keyup'
@@ -89,7 +92,8 @@
trigger: [
{
...multiSelectionKeyDefinition,
callback: () => multiSelectionKeyDefinition.key && multiselectionKeyPressed.set(true)
enabled: multiSelectionKeyDefinition.key !== null,
callback: () => multiselectionKeyPressed.set(true)
}
],
type: 'keydown'
@@ -98,7 +102,8 @@
trigger: [
{
...multiSelectionKeyDefinition,
callback: () => multiSelectionKeyDefinition.key && multiselectionKeyPressed.set(false)
enabled: multiSelectionKeyDefinition.key !== null,
callback: () => multiselectionKeyPressed.set(false)
}
],
type: 'keyup'
@@ -107,7 +112,8 @@
trigger: [
{
...deleteKeyDefinition,
callback: () => deleteKeyDefinition.key && deleteKeyPressed.set(true)
enabled: deleteKeyDefinition.key !== null,
callback: (detail) => !isInputDOMNode(detail.originalEvent) && deleteKeyPressed.set(true)
}
],
type: 'keydown'
@@ -116,7 +122,8 @@
trigger: [
{
...deleteKeyDefinition,
callback: () => deleteKeyDefinition.key && deleteKeyPressed.set(false)
enabled: deleteKeyDefinition.key !== null,
callback: () => deleteKeyPressed.set(false)
}
],
type: 'keyup'
@@ -125,7 +132,8 @@
trigger: [
{
...panActivationKeyDefinition,
callback: () => panActivationKeyDefinition.key && panActivationKeyPressed.set(true)
enabled: panActivationKeyDefinition.key !== null,
callback: () => panActivationKeyPressed.set(true)
}
],
type: 'keydown'
@@ -134,7 +142,8 @@
trigger: [
{
...panActivationKeyDefinition,
callback: () => panActivationKeyDefinition.key && panActivationKeyPressed.set(false)
enabled: panActivationKeyDefinition.key !== null,
callback: () => panActivationKeyPressed.set(false)
}
],
type: 'keyup'
@@ -143,7 +152,8 @@
trigger: [
{
...zoomActivationKeyDefinition,
callback: () => zoomActivationKeyDefinition.key && zoomActivationKeyPressed.set(true)
enabled: zoomActivationKeyDefinition.key !== null,
callback: () => zoomActivationKeyPressed.set(true)
}
],
type: 'keydown'
@@ -152,7 +162,8 @@
trigger: [
{
...zoomActivationKeyDefinition,
callback: () => zoomActivationKeyDefinition.key && zoomActivationKeyPressed.set(false)
enabled: zoomActivationKeyDefinition.key !== null,
callback: () => zoomActivationKeyPressed.set(false)
}
],
type: 'keyup'
@@ -194,9 +194,6 @@
positionAbsolute={{ x: positionX, y: positionY }}
{width}
{height}
on:connectstart
on:connect
on:connectend
/>
</div>
{/if}
@@ -37,6 +37,6 @@
isConnectable;
</script>
<Handle type="target" position={targetPosition} on:connectstart on:connect on:connectend />
<Handle type="target" position={targetPosition} />
{data?.label}
<Handle type="source" position={sourcePosition} on:connectstart on:connect on:connectend />
<Handle type="source" position={sourcePosition} />
@@ -39,4 +39,4 @@
</script>
{data?.label}
<Handle type="source" position={sourcePosition} on:connectstart on:connect on:connectend />
<Handle type="source" position={sourcePosition} />
@@ -36,4 +36,4 @@
</script>
{data?.label}
<Handle type="target" position={targetPosition} on:connectstart on:connect on:connectend />
<Handle type="target" position={targetPosition} />
@@ -83,9 +83,6 @@
on:nodemouseenter
on:nodemousemove
on:nodemouseleave
on:connectstart
on:connect
on:connectend
on:nodedrag
on:nodedragstart
on:nodedragstop
@@ -78,6 +78,9 @@
export let width: $$Props['width'] = undefined;
export let height: $$Props['height'] = undefined;
export let colorMode: $$Props['colorMode'] = 'light';
export let onconnect: $$Props['onconnect'] = undefined;
export let onconnectstart: $$Props['onconnectstart'] = undefined;
export let onconnectend: $$Props['onconnectend'] = undefined;
export let defaultMarkerColor = '#b1b1b7';
@@ -152,7 +155,10 @@
ondelete,
onedgecreate,
connectionMode,
nodeDragThreshold
nodeDragThreshold,
onconnect,
onconnectstart,
onconnectend
};
updateStoreByKeys(store, updatableProps);
@@ -217,9 +223,6 @@
on:nodemouseenter
on:nodemousemove
on:nodemouseleave
on:connectstart
on:connect
on:connectend
on:nodedragstart
on:nodedrag
on:nodedragstop
@@ -15,7 +15,10 @@ import type {
ConnectionMode,
PanelPosition,
ProOptions,
ColorMode
ColorMode,
OnConnect,
OnConnectStart,
OnConnectEnd
} from '@xyflow/system';
import type {
@@ -92,4 +95,8 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
ondelete?: OnDelete;
onedgecreate?: OnEdgeCreate;
onconnect?: OnConnect;
onconnectstart?: OnConnectStart;
onconnectend?: OnConnectEnd;
};
@@ -66,6 +66,9 @@ export type UpdatableStoreProps = {
ondelete?: UnwrapWritable<SvelteFlowStore['ondelete']>;
onedgecreate?: UnwrapWritable<SvelteFlowStore['onedgecreate']>;
nodeDragThreshold?: UnwrapWritable<SvelteFlowStore['nodeDragThreshold']>;
onconnect?: UnwrapWritable<SvelteFlowStore['onconnect']>;
onconnectstart?: UnwrapWritable<SvelteFlowStore['onconnectstart']>;
onconnectend?: UnwrapWritable<SvelteFlowStore['onconnectend']>;
};
export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) {
@@ -0,0 +1,30 @@
import { derived } from 'svelte/store';
import { areConnectionMapsEqual, type Connection, type HandleType } from '@xyflow/system';
import { useStore } from '$lib/store';
export type useHandleConnectionsParams = {
nodeId: string;
type: HandleType;
id?: string | null;
};
const initialConnections: Connection[] = [];
export function useHandleConnections({ nodeId, type, id = null }: useHandleConnectionsParams) {
const { edges, connectionLookup } = useStore();
let prevConnections: Map<string, Connection> | undefined = undefined;
return derived(
[edges, connectionLookup],
([, connectionLookup], set) => {
const nextConnections = connectionLookup.get(`${nodeId}-${type}-${id || null}`);
if (!areConnectionMapsEqual(nextConnections, prevConnections)) {
prevConnections = nextConnections;
set(Array.from(prevConnections?.values() || []));
}
},
initialConnections
);
}
@@ -0,0 +1,64 @@
import { derived, type Readable } from 'svelte/store';
import type { Node } from '$lib/types';
import { useStore } from '$lib/store';
function areNodesDataEqual(a: Node['data'][] | null, b: Node['data'][] | null) {
if ((!a && !b) || (!a?.length && !b?.length)) {
true;
}
if (!a || !b || a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
export function useNodesData<NodeType extends Node = Node>(
nodeId: string
): Readable<NodeType['data'] | null>;
export function useNodesData<NodeType extends Node = Node>(
nodeIds: string[]
): Readable<NodeType['data'][]>;
export function useNodesData<NodeType extends Node = Node>(
nodeIds: string[],
guard: (node: Node) => node is NodeType
): Readable<NodeType['data'][]>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useNodesData(nodeIds: any): any {
const { nodes, nodeLookup } = useStore();
let prevNodesData: (Node['data'] | null)[] | null = null;
return derived([nodes, nodeLookup], ([, nodeLookup], set) => {
let nextNodesData: (Node['data'] | null)[] | null = null;
const nodeIdArray = Array.isArray(nodeIds);
if (!nodeIdArray) {
nextNodesData = [nodeLookup.get(nodeIds)?.data || null];
} else {
const data = [];
for (const nodeId of nodeIds) {
const nodeData = nodeLookup.get(nodeId)?.data;
if (nodeData) {
data.push(nodeData);
}
}
nextNodesData = data;
}
if (!areNodesDataEqual(nextNodesData, prevNodesData)) {
prevNodesData = nextNodesData;
set(nodeIdArray ? nextNodesData : nextNodesData[0]);
}
});
}
+39 -28
View File
@@ -1,7 +1,5 @@
import { get, type Writable } from 'svelte/store';
import {
getIncomersBase,
getOutgoersBase,
getOverlappingArea,
isRectObject,
nodeToRect,
@@ -20,6 +18,7 @@ import {
import { useStore } from '$lib/store';
import type { Edge, FitViewOptions, Node } from '$lib/types';
import { isNode } from '$lib/utils';
export function useSvelteFlow(): {
zoomIn: ZoomInOut;
@@ -48,9 +47,16 @@ export function useSvelteFlow(): {
screenToFlowPosition: (position: XYPosition) => XYPosition;
flowToScreenPosition: (position: XYPosition) => XYPosition;
viewport: Writable<Viewport>;
getConnectedEdges: (id: string | (Node | { id: Node['id'] })[]) => Edge[];
getIncomers: (node: string | Node | { id: Node['id'] }) => Node[];
getOutgoers: (node: string | Node | { id: Node['id'] }) => Node[];
updateNode: (
id: string,
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
options?: { replace: boolean }
) => void;
updateNodeData: (
id: string,
dataUpdate: object | ((node: Node) => object),
options?: { replace: boolean }
) => void;
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
} {
const {
@@ -84,6 +90,24 @@ export function useSvelteFlow(): {
return [nodeRect, node, isRect];
};
const updateNode = (
id: string,
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
options: { replace: boolean } = { replace: false }
) => {
nodes.update((nds) =>
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 };
}
return node;
})
);
};
return {
zoomIn,
zoomOut,
@@ -232,29 +256,6 @@ export function useSvelteFlow(): {
y: rendererPosition.y + domY
};
},
getConnectedEdges: (node) => {
const nodeIds = new Set();
if (typeof node === 'string') {
nodeIds.add(node);
} else if (node.length >= 1) {
node.forEach((n) => {
nodeIds.add(n.id);
});
}
return get(edges).filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
},
getIncomers: (node) => {
const _node = typeof node === 'string' ? { id: node } : node;
return getIncomersBase(_node, get(nodes), get(edges));
},
getOutgoers: (node) => {
const _node = typeof node === 'string' ? { id: node } : node;
return getOutgoersBase(_node, get(nodes), get(edges));
},
toObject: () => {
return {
nodes: get(nodes).map((node) => ({
@@ -268,6 +269,16 @@ export function useSvelteFlow(): {
viewport: { ...get(viewport) }
};
},
updateNode,
updateNodeData: (id, dataUpdate, options) => {
updateNode(id, (node) => {
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
return options?.replace
? { ...node, data: nextData }
: { ...node, data: { ...node.data, ...nextData } };
});
},
viewport
};
}
+2
View File
@@ -27,6 +27,8 @@ export * from '$lib/hooks/useSvelteFlow';
export * from '$lib/hooks/useUpdateNodeInternals';
export * from '$lib/hooks/useConnection';
export * from '$lib/hooks/useNodesEdges';
export * from '$lib/hooks/useHandleConnections';
export * from '$lib/hooks/useNodesData';
// types
export type {
+14 -4
View File
@@ -17,7 +17,12 @@ import {
type Viewport,
updateNodes,
getNodesBounds,
getViewportForBounds
getViewportForBounds,
updateConnectionLookup,
type ConnectionLookup,
type OnConnect,
type OnConnectStart,
type OnConnectEnd
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -72,11 +77,12 @@ export const getInitialStore = ({
height?: number;
fitView?: boolean;
}) => {
const nodeLookup = new Map<string, Node>();
const nodeLookup = new Map();
const nextNodes = updateNodes(nodes, nodeLookup, {
nodeOrigin: [0, 0],
elevateNodesOnSelect: false
});
const connectionLookup = updateConnectionLookup(new Map(), edges);
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
@@ -91,8 +97,9 @@ export const getInitialStore = ({
nodes: createNodesStore(nextNodes, nodeLookup),
nodeLookup: readable<Map<string, Node>>(nodeLookup),
visibleNodes: readable<Node[]>([]),
edges: createEdgesStore(edges),
edges: createEdgesStore(edges, connectionLookup),
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
connectionLookup: readable<ConnectionLookup>(connectionLookup),
height: writable<number>(500),
width: writable<number>(500),
minZoom: writable<number>(0.5),
@@ -136,6 +143,9 @@ export const getInitialStore = ({
onlyRenderVisibleElements: writable<boolean>(false),
onerror: writable<OnError>(devWarn),
ondelete: writable<OnDelete>(undefined),
onedgecreate: writable<OnEdgeCreate>(undefined)
onedgecreate: writable<OnEdgeCreate>(undefined),
onconnect: writable<OnConnect>(undefined),
onconnectstart: writable<OnConnectStart>(undefined),
onconnectend: writable<OnConnectEnd>(undefined)
};
};
+11 -1
View File
@@ -6,7 +6,13 @@ import {
type Writable,
get
} from 'svelte/store';
import { updateNodes, type Viewport, type PanZoomInstance } from '@xyflow/system';
import {
updateNodes,
type Viewport,
type PanZoomInstance,
type ConnectionLookup,
updateConnectionLookup
} from '@xyflow/system';
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
@@ -168,6 +174,7 @@ export const createNodesStore = (
export const createEdgesStore = (
edges: Edge[],
connectionLookup: ConnectionLookup,
defaultOptions?: DefaultEdgeOptions
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
const { subscribe, set, update } = writable<Edge[]>([]);
@@ -176,6 +183,9 @@ export const createEdgesStore = (
const _set: typeof set = (eds: Edge[]) => {
const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds;
updateConnectionLookup(connectionLookup, nextEdges);
value = nextEdges;
set(value);
};
+2
View File
@@ -31,6 +31,8 @@ export type HandleComponentProps = {
isConnectable?: boolean;
isConnectableStart?: boolean;
isConnectableEnd?: boolean;
onconnect?: (connections: Connection[]) => void;
ondisconnect?: (connections: Connection[]) => void;
};
export type FitViewOptions = FitViewOptionsBase<Node>;
+2 -2
View File
@@ -10,8 +10,8 @@ import {
import type { Edge, Node } from '$lib/types';
export const isNode = isNodeBase<Node, Edge>;
export const isEdge = isEdgeBase<Node, Edge>;
export const isNode = isNodeBase<Node>;
export const isEdge = isEdgeBase<Edge>;
export const getOutgoers = getOutgoersBase<Node, Edge>;
export const getIncomers = getIncomersBase<Node, Edge>;
export const addEdge = addEdgeBase<Edge>;
+4 -2
View File
@@ -22,8 +22,8 @@ export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => vo
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void;
export type Connection = {
source: string | null;
target: string | null;
source: string;
target: string;
sourceHandle: string | null;
targetHandle: string | null;
};
@@ -139,3 +139,5 @@ export type UpdateConnection = (params: {
export type ColorModeClass = 'light' | 'dark';
export type ColorMode = ColorModeClass | 'system';
export type ConnectionLookup = Map<string, Map<string, Connection>>;
+53
View File
@@ -0,0 +1,53 @@
import { Connection } from '../types';
/**
* @internal
*/
export function areConnectionMapsEqual(a?: Map<string, Connection>, b?: Map<string, Connection>) {
if (!a && !b) {
return true;
}
if (!a || !b || a.size !== b.size) {
return false;
}
if (!a.size && !b.size) {
return true;
}
for (const key of a.keys()) {
if (!b.has(key)) {
return false;
}
}
return true;
}
/**
* We call the callback for all connections in a that are not in b
*
* @internal
*/
export function handleConnectionChange(
a: Map<string, Connection>,
b: Map<string, Connection>,
cb?: (diff: Connection[]) => void
) {
if (!cb) {
return;
}
const diff: Connection[] = [];
a.forEach((connection, key) => {
if (!b?.has(key)) {
diff.push(connection);
}
});
if (diff.length) {
cb(diff);
}
}
+4 -7
View File
@@ -11,7 +11,6 @@ import {
getViewportForBounds,
} from './general';
import {
type Connection,
type Transform,
type XYPosition,
type Rect,
@@ -26,13 +25,11 @@ import {
} from '../types';
import { errorMessages } from '../constants';
export const isEdgeBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
element: NodeType | Connection | EdgeType
): element is EdgeType => 'id' in element && 'source' in element && 'target' in element;
export const isEdgeBase = <EdgeType extends EdgeBase = EdgeBase>(element: any): element is EdgeType =>
'id' in element && 'source' in element && 'target' in element;
export const isNodeBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
element: NodeType | Connection | EdgeType
): element is NodeType => 'id' in element && !('source' in element) && !('target' in element);
export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType =>
'id' in element && !('source' in element) && !('target' in element);
export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
node: NodeType | { id: string },
+1
View File
@@ -1,3 +1,4 @@
export * from './connections';
export * from './dom';
export * from './edges';
export * from './graph';
+25 -1
View File
@@ -9,6 +9,8 @@ import {
Transform,
XYPosition,
XYZPosition,
ConnectionLookup,
EdgeBase,
} from '../types';
import { getDimensions, getHandleBounds } from './dom';
import { isNumeric } from './general';
@@ -71,11 +73,13 @@ export function updateNodes<NodeType extends NodeBase>(
defaults: {},
}
): NodeType[] {
const tmpLookup = new Map(nodeLookup);
nodeLookup.clear();
const parentNodes: ParentNodes = {};
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
const nextNodes = nodes.map((n) => {
const currentStoreNode = nodeLookup.get(n.id);
const currentStoreNode = tmpLookup.get(n.id);
const node: NodeType = {
...options.defaults,
...n,
@@ -233,3 +237,23 @@ export function panBy({
return transformChanged;
}
export function updateConnectionLookup(lookup: ConnectionLookup, edges: EdgeBase[]) {
lookup.clear();
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
if (source && target) {
const sourceKey = `${source}-source-${sourceHandle}`;
const targetKey = `${target}-target-${targetHandle}`;
const prevSource = lookup.get(sourceKey) || new Map();
const prevTarget = lookup.get(targetKey) || new Map();
const connection = { source, target, sourceHandle, targetHandle };
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
}
});
return lookup;
}
+7 -5
View File
@@ -58,12 +58,10 @@ export type XYHandleInstance = {
type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection;
connection: Connection | null;
endHandle: ConnectingHandle | null;
};
const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null };
const alwaysValid = () => true;
let connectionStartHandle: ConnectingHandle | null = null;
@@ -197,7 +195,7 @@ function onPointerDown(
return resetRecentHandle(prevActiveHandle, lib);
}
if (connection.source !== connection.target && handleDomNode) {
if (connection?.source !== connection?.target && handleDomNode) {
resetRecentHandle(prevActiveHandle, lib);
prevActiveHandle = handleDomNode;
handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`);
@@ -269,7 +267,7 @@ function isValidHandle(
const result: Result = {
handleDomNode: handleToCheck,
isValid: false,
connection: nullConnection,
connection: null,
endHandle: null,
};
@@ -280,6 +278,10 @@ function isValidHandle(
const connectable = handleToCheck.classList.contains('connectable');
const connectableEnd = handleToCheck.classList.contains('connectableend');
if (!handleNodeId) {
return result;
}
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
sourceHandle: isTarget ? handleId : fromHandleId,
+145 -43
View File
@@ -61,10 +61,10 @@ importers:
dependencies:
'@astrojs/react':
specifier: ^3.0.2
version: registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0)
version: registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.1)
'@astrojs/svelte':
specifier: ^4.0.2
version: registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.2.2)(vite@4.5.0)
version: registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.3.3)(vite@4.5.1)
'@types/react':
specifier: ^18.2.24
version: registry.npmjs.org/@types/react@18.2.24
@@ -291,8 +291,8 @@ importers:
packages/svelte:
dependencies:
'@svelte-put/shortcut':
specifier: ^3.0.0
version: registry.npmjs.org/@svelte-put/shortcut@3.0.0
specifier: ^3.1.0
version: registry.npmjs.org/@svelte-put/shortcut@3.1.0
'@xyflow/system':
specifier: workspace:*
version: link:../system
@@ -305,7 +305,7 @@ importers:
version: registry.npmjs.org/@sveltejs/adapter-auto@2.1.0(@sveltejs/kit@1.22.6)
'@sveltejs/kit':
specifier: ^1.22.6
version: registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.0)
version: registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.1)
'@sveltejs/package':
specifier: ^2.2.1
version: registry.npmjs.org/@sveltejs/package@2.2.1(svelte@4.2.1)(typescript@5.1.3)
@@ -417,7 +417,7 @@ importers:
dependencies:
'@playwright/experimental-ct-react':
specifier: ^1.39.0
version: registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.0)
version: registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.1)
'@types/react':
specifier: ^18.2.31
version: registry.npmjs.org/@types/react@18.2.31
@@ -551,7 +551,7 @@ packages:
prismjs: registry.npmjs.org/prismjs@1.29.0
dev: false
registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0):
registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.1):
resolution: {integrity: sha512-aooNIuQxTg+IWGMZPuIEwBeBi4/TCPCMsr3714zuLjAjukVd5ZrX/bCNxJqDWU4HNwUm4XFU1OhcEvYOHa5uMQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@astrojs/react/-/react-3.0.2.tgz}
id: registry.npmjs.org/@astrojs/react/3.0.2
name: '@astrojs/react'
@@ -565,7 +565,7 @@ packages:
dependencies:
'@types/react': registry.npmjs.org/@types/react@18.2.24
'@types/react-dom': registry.npmjs.org/@types/react-dom@18.2.8
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.0)
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.1)
react: registry.npmjs.org/react@18.2.0
react-dom: registry.npmjs.org/react-dom@18.2.0(react@18.2.0)
ultrahtml: registry.npmjs.org/ultrahtml@1.5.2
@@ -574,7 +574,7 @@ packages:
- vite
dev: false
registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.2.2)(vite@4.5.0):
registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.3.3)(vite@4.5.1):
resolution: {integrity: sha512-XB9Sexq+iW5aUpctDk6zuKHbWIAuPlAnZKbfgaS9VOaOUtx1t12crP9YoKEVcTifXYgoRuWWfeEAm2I8pYlLIQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@astrojs/svelte/-/svelte-4.0.2.tgz}
id: registry.npmjs.org/@astrojs/svelte/4.0.2
name: '@astrojs/svelte'
@@ -584,10 +584,10 @@ packages:
astro: ^3.0.11
svelte: ^3.55.0 || ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0)
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1)
astro: registry.npmjs.org/astro@3.2.2
svelte: registry.npmjs.org/svelte@4.2.1
svelte2tsx: registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.2.2)
svelte2tsx: registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.3.3)
transitivePeerDependencies:
- supports-color
- typescript
@@ -1938,7 +1938,7 @@ packages:
- terser
dev: false
registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.0):
registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.1):
resolution: {integrity: sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz}
id: registry.npmjs.org/@playwright/experimental-ct-react/1.39.0
name: '@playwright/experimental-ct-react'
@@ -1947,7 +1947,7 @@ packages:
hasBin: true
dependencies:
'@playwright/experimental-ct-core': registry.npmjs.org/@playwright/experimental-ct-core@1.39.0(@types/node@18.7.16)
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.0)
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.1)
transitivePeerDependencies:
- '@types/node'
- less
@@ -2120,10 +2120,10 @@ packages:
version: 2.0.0
dev: true
registry.npmjs.org/@svelte-put/shortcut@3.0.0:
resolution: {integrity: sha512-nZg3pwpTi9wUsvQPlqOzEsxZcF2jmY5j+VBq/20IUjjd2OpM92XqZAga0PCCjE6OuEobOt58UMnC2QZgOvk0tQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-3.0.0.tgz}
registry.npmjs.org/@svelte-put/shortcut@3.1.0:
resolution: {integrity: sha512-EWMEDkZ0+O3yMhb9yrqe5UYisV9CNRKX6Pl/JW3x62t74CiN+3COu1L9NzZUG0omagc2Z3J14PZNYxs77IC9NA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-3.1.0.tgz}
name: '@svelte-put/shortcut'
version: 3.0.0
version: 3.1.0
dev: false
registry.npmjs.org/@sveltejs/adapter-auto@2.1.0(@sveltejs/kit@1.22.6):
@@ -2134,7 +2134,7 @@ packages:
peerDependencies:
'@sveltejs/kit': ^1.0.0
dependencies:
'@sveltejs/kit': registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.0)
'@sveltejs/kit': registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.1)
import-meta-resolve: registry.npmjs.org/import-meta-resolve@3.0.0
dev: true
@@ -2150,7 +2150,7 @@ packages:
import-meta-resolve: registry.npmjs.org/import-meta-resolve@4.0.0
dev: true
registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.0):
registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.1):
resolution: {integrity: sha512-SDKxI/QpsReCwIn5czjT53fKlPBybbmMk67d317gUqfeORroBAFN1Z6s/x0E1JYi+04i7kKllS+Sz9wVfmUkAQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/kit/-/kit-1.22.6.tgz}
id: registry.npmjs.org/@sveltejs/kit/1.22.6
name: '@sveltejs/kit'
@@ -2162,7 +2162,7 @@ packages:
svelte: ^3.54.0 || ^4.0.0-next.0
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0)
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1)
'@types/cookie': registry.npmjs.org/@types/cookie@0.5.3
cookie: registry.npmjs.org/cookie@0.5.0
devalue: registry.npmjs.org/devalue@4.3.2
@@ -2175,7 +2175,7 @@ packages:
sirv: registry.npmjs.org/sirv@2.0.3
svelte: registry.npmjs.org/svelte@4.2.1
undici: registry.npmjs.org/undici@5.23.0
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
transitivePeerDependencies:
- supports-color
dev: true
@@ -2231,7 +2231,7 @@ packages:
- typescript
dev: true
registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.0):
registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.1):
resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz}
id: registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/1.0.4
name: '@sveltejs/vite-plugin-svelte-inspector'
@@ -2242,10 +2242,10 @@ packages:
svelte: ^3.54.0 || ^4.0.0
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0)
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1)
debug: registry.npmjs.org/debug@4.3.4(supports-color@8.1.1)
svelte: registry.npmjs.org/svelte@4.2.1
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
transitivePeerDependencies:
- supports-color
@@ -2268,7 +2268,7 @@ packages:
- supports-color
dev: true
registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0):
registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1):
resolution: {integrity: sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz}
id: registry.npmjs.org/@sveltejs/vite-plugin-svelte/2.4.6
name: '@sveltejs/vite-plugin-svelte'
@@ -2278,15 +2278,15 @@ packages:
svelte: ^3.54.0 || ^4.0.0
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.0)
'@sveltejs/vite-plugin-svelte-inspector': registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.1)
debug: registry.npmjs.org/debug@4.3.4(supports-color@8.1.1)
deepmerge: registry.npmjs.org/deepmerge@4.3.1
kleur: registry.npmjs.org/kleur@4.1.5
magic-string: registry.npmjs.org/magic-string@0.30.5
svelte: registry.npmjs.org/svelte@4.2.1
svelte-hmr: registry.npmjs.org/svelte-hmr@0.15.3(svelte@4.2.1)
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
vitefu: registry.npmjs.org/vitefu@0.2.5(vite@4.5.0)
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
vitefu: registry.npmjs.org/vitefu@0.2.5(vite@4.5.1)
transitivePeerDependencies:
- supports-color
@@ -2972,6 +2972,12 @@ packages:
version: 7.5.4
dev: true
registry.npmjs.org/@types/semver@7.5.6:
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz}
name: '@types/semver'
version: 7.5.6
dev: true
registry.npmjs.org/@types/sinonjs__fake-timers@8.1.1:
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz}
name: '@types/sinonjs__fake-timers'
@@ -2996,10 +3002,10 @@ packages:
version: 3.0.1
dev: false
registry.npmjs.org/@types/yauzl@2.10.2:
resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.2.tgz}
registry.npmjs.org/@types/yauzl@2.10.3:
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz}
name: '@types/yauzl'
version: 2.10.2
version: 2.10.3
requiresBuild: true
dependencies:
'@types/node': registry.npmjs.org/@types/node@18.7.16
@@ -3092,7 +3098,7 @@ packages:
debug: registry.npmjs.org/debug@4.3.4(supports-color@8.1.1)
eslint: registry.npmjs.org/eslint@8.43.0
graphemer: registry.npmjs.org/graphemer@1.4.0
ignore: registry.npmjs.org/ignore@5.2.4
ignore: registry.npmjs.org/ignore@5.3.0
natural-compare: registry.npmjs.org/natural-compare@1.4.0
semver: registry.npmjs.org/semver@7.5.4
ts-api-utils: registry.npmjs.org/ts-api-utils@1.0.3(typescript@5.1.3)
@@ -3398,7 +3404,7 @@ packages:
dependencies:
'@eslint-community/eslint-utils': registry.npmjs.org/@eslint-community/eslint-utils@4.4.0(eslint@8.53.0)
'@types/json-schema': registry.npmjs.org/@types/json-schema@7.0.15
'@types/semver': registry.npmjs.org/@types/semver@7.5.4
'@types/semver': registry.npmjs.org/@types/semver@7.5.6
'@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager@6.10.0
'@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types@6.10.0
'@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2)
@@ -3420,7 +3426,7 @@ packages:
dependencies:
'@eslint-community/eslint-utils': registry.npmjs.org/@eslint-community/eslint-utils@4.4.0(eslint@8.43.0)
'@types/json-schema': registry.npmjs.org/@types/json-schema@7.0.15
'@types/semver': registry.npmjs.org/@types/semver@7.5.4
'@types/semver': registry.npmjs.org/@types/semver@7.5.6
'@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager@6.8.0
'@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types@6.8.0
'@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree@6.8.0(typescript@5.1.3)
@@ -3498,6 +3504,26 @@ packages:
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
transitivePeerDependencies:
- supports-color
dev: true
registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.1):
resolution: {integrity: sha512-Jie2HERK+uh27e+ORXXwEP5h0Y2lS9T2PRGbfebiHGlwzDO0dEnd2aNtOR/qjBlPb1YgxwAONeblL1xqLikLag==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.1.tgz}
id: registry.npmjs.org/@vitejs/plugin-react/4.1.1
name: '@vitejs/plugin-react'
version: 4.1.1
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: ^4.2.0
dependencies:
'@babel/core': registry.npmjs.org/@babel/core@7.23.2
'@babel/plugin-transform-react-jsx-self': registry.npmjs.org/@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.2)
'@babel/plugin-transform-react-jsx-source': registry.npmjs.org/@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.23.2)
'@types/babel__core': registry.npmjs.org/@types/babel__core@7.20.4
react-refresh: registry.npmjs.org/react-refresh@0.14.0
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
transitivePeerDependencies:
- supports-color
dev: false
registry.npmjs.org/acorn-jsx@5.3.2(acorn@8.10.0):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz}
@@ -5313,7 +5339,7 @@ packages:
is-weakref: registry.npmjs.org/is-weakref@1.0.2
object-inspect: registry.npmjs.org/object-inspect@1.13.1
object-keys: registry.npmjs.org/object-keys@1.1.1
object.assign: registry.npmjs.org/object.assign@4.1.4
object.assign: registry.npmjs.org/object.assign@4.1.5
regexp.prototype.flags: registry.npmjs.org/regexp.prototype.flags@1.5.1
safe-array-concat: registry.npmjs.org/safe-array-concat@1.0.1
safe-regex-test: registry.npmjs.org/safe-regex-test@1.0.0
@@ -5985,7 +6011,7 @@ packages:
get-stream: registry.npmjs.org/get-stream@5.2.0
yauzl: registry.npmjs.org/yauzl@2.10.0
optionalDependencies:
'@types/yauzl': registry.npmjs.org/@types/yauzl@2.10.2
'@types/yauzl': registry.npmjs.org/@types/yauzl@2.10.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -6487,7 +6513,7 @@ packages:
array-union: registry.npmjs.org/array-union@2.1.0
dir-glob: registry.npmjs.org/dir-glob@3.0.1
fast-glob: registry.npmjs.org/fast-glob@3.3.2
ignore: registry.npmjs.org/ignore@5.2.4
ignore: registry.npmjs.org/ignore@5.3.0
merge2: registry.npmjs.org/merge2@1.4.1
slash: registry.npmjs.org/slash@3.0.0
dev: true
@@ -6500,7 +6526,7 @@ packages:
dependencies:
dir-glob: registry.npmjs.org/dir-glob@3.0.1
fast-glob: registry.npmjs.org/fast-glob@3.3.2
ignore: registry.npmjs.org/ignore@5.2.4
ignore: registry.npmjs.org/ignore@5.3.0
merge2: registry.npmjs.org/merge2@1.4.1
slash: registry.npmjs.org/slash@4.0.0
dev: true
@@ -6791,6 +6817,13 @@ packages:
engines: {node: '>= 4'}
dev: true
registry.npmjs.org/ignore@5.3.0:
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz}
name: ignore
version: 5.3.0
engines: {node: '>= 4'}
dev: true
registry.npmjs.org/immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz}
name: immediate
@@ -7431,7 +7464,7 @@ packages:
dependencies:
array-includes: registry.npmjs.org/array-includes@3.1.7
array.prototype.flat: registry.npmjs.org/array.prototype.flat@1.3.2
object.assign: registry.npmjs.org/object.assign@4.1.4
object.assign: registry.npmjs.org/object.assign@4.1.5
object.values: registry.npmjs.org/object.values@1.1.7
dev: true
@@ -8560,10 +8593,10 @@ packages:
engines: {node: '>= 0.4'}
dev: true
registry.npmjs.org/object.assign@4.1.4:
resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz}
registry.npmjs.org/object.assign@4.1.5:
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz}
name: object.assign
version: 4.1.4
version: 4.1.5
engines: {node: '>= 0.4'}
dependencies:
call-bind: registry.npmjs.org/call-bind@1.0.5
@@ -9487,6 +9520,16 @@ packages:
picocolors: registry.npmjs.org/picocolors@1.0.0
source-map-js: registry.npmjs.org/source-map-js@1.0.2
registry.npmjs.org/postcss@8.4.32:
resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz}
name: postcss
version: 8.4.32
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: registry.npmjs.org/nanoid@3.3.7
picocolors: registry.npmjs.org/picocolors@1.0.0
source-map-js: registry.npmjs.org/source-map-js@1.0.2
registry.npmjs.org/prebuild-install@7.1.1:
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz}
name: prebuild-install
@@ -11206,7 +11249,7 @@ packages:
typescript: registry.npmjs.org/typescript@5.1.3
dev: true
registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.2.2):
registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.3.3):
resolution: {integrity: sha512-3bwd1PuWUA3oEXy8+85zrLDnmJOsVpShpKVAehGWeYsz/66zMihTpRpUN97VVAKTZbO5tP4wnchHUXYs0zOwdw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.23.tgz}
id: registry.npmjs.org/svelte2tsx/0.6.23
name: svelte2tsx
@@ -11218,7 +11261,7 @@ packages:
dedent-js: registry.npmjs.org/dedent-js@1.0.1
pascal-case: registry.npmjs.org/pascal-case@3.1.2
svelte: registry.npmjs.org/svelte@4.2.1
typescript: registry.npmjs.org/typescript@5.2.2
typescript: registry.npmjs.org/typescript@5.3.3
dev: false
registry.npmjs.org/svelte@4.2.1:
@@ -11755,6 +11798,14 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
registry.npmjs.org/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz}
name: typescript
version: 5.3.3
engines: {node: '>=14.17'}
hasBin: true
dev: false
registry.npmjs.org/ultrahtml@1.5.2:
resolution: {integrity: sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.2.tgz}
name: ultrahtml
@@ -12090,6 +12141,44 @@ packages:
optionalDependencies:
fsevents: registry.npmjs.org/fsevents@2.3.3
registry.npmjs.org/vite@4.5.1(@types/node@18.7.16):
resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vite/-/vite-4.5.1.tgz}
id: registry.npmjs.org/vite/4.5.1
name: vite
version: 4.5.1
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
'@types/node': registry.npmjs.org/@types/node@18.7.16
esbuild: registry.npmjs.org/esbuild@0.18.20
postcss: registry.npmjs.org/postcss@8.4.32
rollup: registry.npmjs.org/rollup@3.29.4
optionalDependencies:
fsevents: registry.npmjs.org/fsevents@2.3.3
registry.npmjs.org/vitefu@0.2.5(vite@4.5.0):
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz}
id: registry.npmjs.org/vitefu/0.2.5
@@ -12103,6 +12192,19 @@ packages:
dependencies:
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
registry.npmjs.org/vitefu@0.2.5(vite@4.5.1):
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz}
id: registry.npmjs.org/vitefu/0.2.5
name: vitefu
version: 0.2.5
peerDependencies:
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
peerDependenciesMeta:
vite:
optional: true
dependencies:
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
registry.npmjs.org/vscode-oniguruma@1.7.0:
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz}
name: vscode-oniguruma