diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 2665abfa..4bab00f5 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -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', diff --git a/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx b/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx index aebc8e0e..6857e1ac 100644 --- a/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx +++ b/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx @@ -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' }; diff --git a/examples/react/src/examples/UpdateNode/index.tsx b/examples/react/src/examples/UpdateNode/index.tsx index 7c320606..d6e8a67a 100644 --- a/examples/react/src/examples/UpdateNode/index.tsx +++ b/examples/react/src/examples/UpdateNode/index.tsx @@ -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('Node 1'); const [nodeBg, setNodeBg] = useState('#eee'); @@ -80,9 +81,19 @@ const UpdateNode = () => { setNodeHidden(evt.target.checked)} /> + + ); }; -export default UpdateNode; +export default () => ( + + + +); diff --git a/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx b/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx new file mode 100644 index 00000000..e04f3448 --- /dev/null +++ b/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx @@ -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 ; +} + +const CustomNode: FC = ({ id }) => { + return ( +
+ +
node {id}
+ + +
+ ); +}; + +export default memo(CustomNode); diff --git a/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx b/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx new file mode 100644 index 00000000..444499fb --- /dev/null +++ b/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx @@ -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 ; +} + +const CustomNode: FC = ({ id }) => { + return ( +
+ +
node {id}
+ +
+ ); +}; + +export default memo(CustomNode); diff --git a/examples/react/src/examples/UseHandleConnections/index.tsx b/examples/react/src/examples/UseHandleConnections/index.tsx new file mode 100644 index 00000000..c6aa4a68 --- /dev/null +++ b/examples/react/src/examples/UseHandleConnections/index.tsx @@ -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 ( + + + + + + ); +}; + +export default CustomNodeFlow; diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx new file mode 100644 index 00000000..ac9777dd --- /dev/null +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -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 ( +
+ +
+ incoming texts:{' '} + {nodesData?.filter((nodeData) => nodeData.text !== undefined).map(({ text }, i) =>
{text}
) || + 'none'} +
+
+ ); +} + +export default memo(ResultNode); diff --git a/examples/react/src/examples/UseNodesData/TextNode.tsx b/examples/react/src/examples/UseNodesData/TextNode.tsx new file mode 100644 index 00000000..6ca424e6 --- /dev/null +++ b/examples/react/src/examples/UseNodesData/TextNode.tsx @@ -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 = (evt) => updateNodeData(id, { text: evt.target.value }); + + return ( +
+
node {id}
+
+ +
+ +
+ ); +} + +export default memo(TextNode); diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx new file mode 100644 index 00000000..699dc37e --- /dev/null +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -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 ( +
+ +
uppercase transform
+ +
+ ); +} + +export default memo(UppercaseNode); diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx new file mode 100644 index 00000000..7bb1a94e --- /dev/null +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -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 ( + + + + + ); +}; + +export default CustomNodeFlow; diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte index ad34d289..9d67f049 100644 --- a/examples/svelte/src/components/Header/Header.svelte +++ b/examples/svelte/src/components/Header/Header.svelte @@ -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' diff --git a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte index 5556ca80..809ae82c 100644 --- a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte +++ b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte @@ -1,6 +1,12 @@ @@ -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} /> diff --git a/examples/svelte/src/routes/examples/customnode/+page.svelte b/examples/svelte/src/routes/examples/customnode/+page.svelte index 123932f9..525b0706 100644 --- a/examples/svelte/src/routes/examples/customnode/+page.svelte +++ b/examples/svelte/src/routes/examples/customnode/+page.svelte @@ -87,7 +87,7 @@ {nodeTypes} style="--xy-background-color: {$bgColor}" fitView - on:connect={onConnect} + onconnect={onConnect} > diff --git a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte index 51ba33ea..ca01be5e 100644 --- a/examples/svelte/src/routes/examples/customnode/CustomNode.svelte +++ b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte @@ -1,6 +1,6 @@
- +
Custom Color Picker Node: {$colorStore}
@@ -20,14 +20,8 @@ on:input={(evt) => colorStore.set(evt.currentTarget.value)} value={$colorStore} /> - - + +
diff --git a/examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte b/examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte new file mode 100644 index 00000000..7c1e026d --- /dev/null +++ b/examples/svelte/src/routes/examples/handle-connect/SingleHandleNode.svelte @@ -0,0 +1,77 @@ + + +
+ +
node {id}
+ +
+ + diff --git a/examples/svelte/src/routes/examples/overview/+page.svelte b/examples/svelte/src/routes/examples/overview/+page.svelte index 68fd4aaa..d8293666 100644 --- a/examples/svelte/src/routes/examples/overview/+page.svelte +++ b/examples/svelte/src/routes/examples/overview/+page.svelte @@ -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); diff --git a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte new file mode 100644 index 00000000..7b2d2204 --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte @@ -0,0 +1,80 @@ + + + + + + + diff --git a/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte new file mode 100644 index 00000000..9dd98173 --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte @@ -0,0 +1,37 @@ + + +
+ +
incoming texts:
+ + {#each $nodeData as data} +
{data.text}
+ {/each} +
+ + diff --git a/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte new file mode 100644 index 00000000..c1642da2 --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/TextNode.svelte @@ -0,0 +1,29 @@ + + +
+
node {id}
+
+ updateNodeData(id, { text: evt.currentTarget.value })} + /> +
+ +
+ + diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte new file mode 100644 index 00000000..2dcf3bde --- /dev/null +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -0,0 +1,40 @@ + + +
+ +
uppercase transform
+ +
+ + diff --git a/examples/svelte/src/routes/examples/useupdatenodeinternals/CustomNode.svelte b/examples/svelte/src/routes/examples/useupdatenodeinternals/CustomNode.svelte index f4d306ac..fbe9c8d7 100644 --- a/examples/svelte/src/routes/examples/useupdatenodeinternals/CustomNode.svelte +++ b/examples/svelte/src/routes/examples/useupdatenodeinternals/CustomNode.svelte @@ -15,15 +15,9 @@ }; - + {#each Array.from({ length: handleCount }) as handle, i} - + {/each} diff --git a/examples/svelte/src/routes/examples/useupdatenodeinternals/Flow.svelte b/examples/svelte/src/routes/examples/useupdatenodeinternals/Flow.svelte index 13b48cb3..42c76cd0 100644 --- a/examples/svelte/src/routes/examples/useupdatenodeinternals/Flow.svelte +++ b/examples/svelte/src/routes/examples/useupdatenodeinternals/Flow.svelte @@ -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 } })); + };
@@ -57,6 +65,8 @@ + +
diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 29231b71..a2c0101f 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -171,7 +171,7 @@ const Handle = forwardRef( lib, }); - if (isValid) { + if (isValid && connection) { onConnectExtended(connection); } diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts new file mode 100644 index 00000000..c9a026d9 --- /dev/null +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -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 is connected to another 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 | 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]); +} diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts new file mode 100644 index 00000000..4c242ad3 --- /dev/null +++ b/packages/react/src/hooks/useNodesData.ts @@ -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(nodeId: string): NodeType['data'] | null; +export function useNodesData(nodeIds: string[]): NodeType['data'][]; +export function useNodesData( + 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; +} diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 44e29c14..9bb17eb9 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -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(): ReactFlowInstance { @@ -271,6 +272,36 @@ export default function useReactFlow(): ReactFlo return getOutgoersBase(node, nodes, edges); }, []); + const updateNode = useCallback( + (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( + (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(): ReactFlo getConnectedEdges, getIncomers, getOutgoers, + updateNode, + updateNodeData, }; }, [ viewportHelper, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2a693638..ae82e78b 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -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'; diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 0f50f385..c5c21d7a 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -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 diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 48e5434f..8d07617a 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -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(); + 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, diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index f229ec87..a1c822bb 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -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) => Partial), + options?: { replace: boolean } + ) => void; + export type UpdateNodeData = ( + id: string, + dataUpdate: object | ((node: Node) => object), + options?: { replace: boolean } + ) => void; } export type ReactFlowInstance = { @@ -60,5 +71,7 @@ export type ReactFlowInstance = { deleteElements: Instance.DeleteElements; getIntersectingNodes: Instance.GetIntersectingNodes; isNodeIntersecting: Instance.IsNodeIntersecting; + updateNode: Instance.UpdateNode; + updateNodeData: Instance.UpdateNodeData; viewportInitialized: boolean; } & Omit; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index c473cbd5..5aebb99d 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -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; edges: Edge[]; + connectionLookup: Map>; + onNodesChange: OnNodesChange | null; onEdgesChange: OnEdgesChange | null; hasDefaultNodes: boolean; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index e13247c1..a106e182 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -10,8 +10,8 @@ import { import type { Edge, Node } from '../types'; -export const isNode = isNodeBase; -export const isEdge = isEdgeBase; +export const isNode = isNodeBase; +export const isEdge = isEdgeBase; export const getOutgoers = getOutgoersBase; export const getIncomers = getIncomersBase; export const addEdge = addEdgeBase; diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 5ed9f8e9..60f4b8ed 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -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" }, diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index e4b6a823..807594e0 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -1,17 +1,18 @@ diff --git a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte index f059ff8e..f6697cb2 100644 --- a/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte +++ b/packages/svelte/src/lib/components/KeyHandler/KeyHandler.svelte @@ -1,10 +1,10 @@ - + {data?.label} - + diff --git a/packages/svelte/src/lib/components/nodes/InputNode.svelte b/packages/svelte/src/lib/components/nodes/InputNode.svelte index aa911d1c..a8a25eb8 100644 --- a/packages/svelte/src/lib/components/nodes/InputNode.svelte +++ b/packages/svelte/src/lib/components/nodes/InputNode.svelte @@ -39,4 +39,4 @@ {data?.label} - + diff --git a/packages/svelte/src/lib/components/nodes/OutputNode.svelte b/packages/svelte/src/lib/components/nodes/OutputNode.svelte index b5ebc2b1..7fff504f 100644 --- a/packages/svelte/src/lib/components/nodes/OutputNode.svelte +++ b/packages/svelte/src/lib/components/nodes/OutputNode.svelte @@ -36,4 +36,4 @@ {data?.label} - + diff --git a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte index b4b05a1b..df399a7d 100644 --- a/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte +++ b/packages/svelte/src/lib/container/NodeRenderer/NodeRenderer.svelte @@ -83,9 +83,6 @@ on:nodemouseenter on:nodemousemove on:nodemouseleave - on:connectstart - on:connect - on:connectend on:nodedrag on:nodedragstart on:nodedragstop diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte index 21ceec49..81aa81bd 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte @@ -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 diff --git a/packages/svelte/src/lib/container/SvelteFlow/types.ts b/packages/svelte/src/lib/container/SvelteFlow/types.ts index 9116dc60..99361624 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/types.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/types.ts @@ -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 & { ondelete?: OnDelete; onedgecreate?: OnEdgeCreate; + + onconnect?: OnConnect; + onconnectstart?: OnConnectStart; + onconnectend?: OnConnectEnd; }; diff --git a/packages/svelte/src/lib/container/SvelteFlow/utils.ts b/packages/svelte/src/lib/container/SvelteFlow/utils.ts index 0281cd55..497ccd14 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/utils.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/utils.ts @@ -66,6 +66,9 @@ export type UpdatableStoreProps = { ondelete?: UnwrapWritable; onedgecreate?: UnwrapWritable; nodeDragThreshold?: UnwrapWritable; + onconnect?: UnwrapWritable; + onconnectstart?: UnwrapWritable; + onconnectend?: UnwrapWritable; }; export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) { diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts new file mode 100644 index 00000000..c8df0dae --- /dev/null +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -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 | 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 + ); +} diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts new file mode 100644 index 00000000..f50bc8b8 --- /dev/null +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -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( + nodeId: string +): Readable; +export function useNodesData( + nodeIds: string[] +): Readable; +export function useNodesData( + nodeIds: string[], + guard: (node: Node) => node is NodeType +): Readable; +// 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]); + } + }); +} diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.ts index 3c266f43..6e66a998 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.ts @@ -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; - 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) => Partial), + 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) => Partial), + 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 }; } diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index a9107415..895793d2 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -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 { diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 9f51abc3..0691c202 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -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(); + 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>(nodeLookup), visibleNodes: readable([]), - edges: createEdgesStore(edges), + edges: createEdgesStore(edges, connectionLookup), edgeTree: readable[]>([]), + connectionLookup: readable(connectionLookup), height: writable(500), width: writable(500), minZoom: writable(0.5), @@ -136,6 +143,9 @@ export const getInitialStore = ({ onlyRenderVisibleElements: writable(false), onerror: writable(devWarn), ondelete: writable(undefined), - onedgecreate: writable(undefined) + onedgecreate: writable(undefined), + onconnect: writable(undefined), + onconnectstart: writable(undefined), + onconnectend: writable(undefined) }; }; diff --git a/packages/svelte/src/lib/store/utils.ts b/packages/svelte/src/lib/store/utils.ts index a11bf1f6..b9292ca7 100644 --- a/packages/svelte/src/lib/store/utils.ts +++ b/packages/svelte/src/lib/store/utils.ts @@ -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 & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => { const { subscribe, set, update } = writable([]); @@ -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); }; diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index 62666705..5d33a97c 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -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; diff --git a/packages/svelte/src/lib/utils/index.ts b/packages/svelte/src/lib/utils/index.ts index 23b29448..09769c62 100644 --- a/packages/svelte/src/lib/utils/index.ts +++ b/packages/svelte/src/lib/utils/index.ts @@ -10,8 +10,8 @@ import { import type { Edge, Node } from '$lib/types'; -export const isNode = isNodeBase; -export const isEdge = isEdgeBase; +export const isNode = isNodeBase; +export const isEdge = isEdgeBase; export const getOutgoers = getOutgoersBase; export const getIncomers = getIncomersBase; export const addEdge = addEdgeBase; diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 22ad889e..1591cca4 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -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>; diff --git a/packages/system/src/utils/connections.ts b/packages/system/src/utils/connections.ts new file mode 100644 index 00000000..ded405c3 --- /dev/null +++ b/packages/system/src/utils/connections.ts @@ -0,0 +1,53 @@ +import { Connection } from '../types'; + +/** + * @internal + */ +export function areConnectionMapsEqual(a?: Map, b?: Map) { + 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, + b: Map, + 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); + } +} diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index 6a946a10..df815510 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -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 = ( - element: NodeType | Connection | EdgeType -): element is EdgeType => 'id' in element && 'source' in element && 'target' in element; +export const isEdgeBase = (element: any): element is EdgeType => + 'id' in element && 'source' in element && 'target' in element; -export const isNodeBase = ( - element: NodeType | Connection | EdgeType -): element is NodeType => 'id' in element && !('source' in element) && !('target' in element); +export const isNodeBase = (element: any): element is NodeType => + 'id' in element && !('source' in element) && !('target' in element); export const getOutgoersBase = ( node: NodeType | { id: string }, diff --git a/packages/system/src/utils/index.ts b/packages/system/src/utils/index.ts index 4937033c..cd66e951 100644 --- a/packages/system/src/utils/index.ts +++ b/packages/system/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './connections'; export * from './dom'; export * from './edges'; export * from './graph'; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index eca6b8de..86434a72 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -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( 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; +} diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 6ec86528..826a1c80 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -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, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05a5637d..0286010e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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