refactor(useSetNodeData): put function in useReactFlow/useSvelteFlow

This commit is contained in:
moklick
2023-12-12 18:52:45 +01:00
parent 75979922ea
commit 8ceb1addc1
18 changed files with 254 additions and 247 deletions
+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,
@@ -1,30 +0,0 @@
import { useCallback } from 'react';
import useReactFlow from './useReactFlow';
import { Node } from '../types';
export function useSetNodeData<NodeType extends Node = Node>() {
const { setNodes } = useReactFlow();
const setNodeData = useCallback(
function setNodesData(
id: string,
dataUpdate: object | ((node: NodeType) => object),
options: { replace: boolean } = { replace: true }
) {
setNodes((prevNodes) =>
prevNodes.map((node) => {
if (node.id === id) {
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node as NodeType) : dataUpdate;
return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } };
}
return node;
})
);
},
[setNodes]
);
return setNodeData;
}
-1
View File
@@ -23,7 +23,6 @@ export { default as useOnViewportChange, type UseOnViewportChangeOptions } from
export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange';
export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized';
export { useHandleConnections } from './hooks/useHandleConnections';
export { useSetNodeData } from './hooks/useSetNodeData';
export { useNodesData } from './hooks/useNodesData';
export { useNodeId } from './contexts/NodeIdContext';
+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'>;
+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,89 +0,0 @@
// Shortcut action is copied and slightly adjusted from https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/shortcut
// If this discussion https://github.com/vnphanquang/svelte-put/discussions/256 gets resolved, we will use the original action again.
export interface ShortcutEventDetail {
event: KeyboardEvent;
trigger: ShortcutTrigger;
}
export type ShortcutModifier = 'alt' | 'ctrl' | 'meta' | 'shift';
export type ShortcutModifierDefinition =
| ShortcutModifier
| ShortcutModifier[]
| ShortcutModifier[][];
export type ShortcutTrigger = {
enabled?: boolean;
modifier?: ShortcutModifierDefinition;
id?: string;
key: string;
callback?: (detail: ShortcutEventDetail) => void;
preventDefault?: boolean;
};
export type ShortcutParameter = {
enabled?: boolean;
trigger: Array<ShortcutTrigger> | ShortcutTrigger;
type?: 'keydown' | 'keyup';
};
export function shortcut(node: Window, param: ShortcutParameter) {
let { enabled = true, trigger, type = 'keydown' } = param;
function handler(event: KeyboardEvent) {
const normalizedTriggers = Array.isArray(trigger) ? trigger : [trigger];
const modifiedMap = {
alt: event.altKey,
ctrl: event.ctrlKey,
shift: event.shiftKey,
meta: event.metaKey
};
for (const trigger of normalizedTriggers) {
const mergedTrigger = {
modifier: [],
preventDefault: false,
enabled: true,
...trigger
};
const { modifier, key, callback, preventDefault, enabled: triggerEnabled } = mergedTrigger;
if (triggerEnabled) {
if (modifier.length) {
const modifierDefs = (Array.isArray(modifier) ? modifier : [modifier]).map((def) =>
typeof def === 'string' ? [def] : def
);
const modified = modifierDefs.some((def) =>
def.every((modifier) => modifiedMap[modifier])
);
if (!modified) continue;
}
if (event.key === key) {
if (preventDefault) event.preventDefault();
const detail = { event, trigger: mergedTrigger };
callback?.(detail);
}
}
}
}
if (enabled) node.addEventListener(type, handler);
return {
update: (update: ShortcutParameter) => {
const { enabled: newEnabled = true, type: newType = 'keydown' } = update;
if (enabled && (!newEnabled || type !== newType)) {
node.removeEventListener(type, handler);
} else if (!enabled && newEnabled) {
node.addEventListener(newType, handler);
}
enabled = newEnabled;
type = newType;
trigger = update.trigger;
},
destroy: () => {
node.removeEventListener(type, handler);
}
};
}
@@ -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 { shortcut, type ShortcutModifierDefinition } from '../../actions/shortcut';
type $$Props = KeyHandlerProps;
@@ -113,7 +113,7 @@
{
...deleteKeyDefinition,
enabled: deleteKeyDefinition.key !== null,
callback: (detail) => !isInputDOMNode(detail.event) && deleteKeyPressed.set(true)
callback: (detail) => !isInputDOMNode(detail.originalEvent) && deleteKeyPressed.set(true)
}
],
type: 'keydown'
@@ -1,28 +0,0 @@
import { useStore } from '$lib/store';
import type { Node } from '$lib/types';
export function useSetNodeData<NodeType extends Node = Node>() {
const { nodes } = useStore();
const setNodeData = (
id: string,
dataUpdate: object | ((node: NodeType) => object),
options: { replace: boolean } = { replace: true }
) => {
nodes.update((nds) =>
nds.map((node) => {
if (node.id === id) {
const nextData =
typeof dataUpdate === 'function' ? dataUpdate(node as NodeType) : dataUpdate;
return options.replace
? { ...node, data: nextData }
: { ...node, data: { ...node.data, ...nextData } };
}
return node;
})
);
};
return setNodeData;
}
+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
};
}
-1
View File
@@ -29,7 +29,6 @@ export * from '$lib/hooks/useConnection';
export * from '$lib/hooks/useNodesEdges';
export * from '$lib/hooks/useHandleConnections';
export * from '$lib/hooks/useNodesData';
export * from '$lib/hooks/useSetNodeData';
// types
export type {
+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 -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 },