feat(svelte): add event handlers and props, rename class names

This commit is contained in:
moklick
2023-03-01 17:28:59 +01:00
parent 9391b36869
commit e2961bfa8c
38 changed files with 410 additions and 163 deletions
+19 -9
View File
@@ -2,7 +2,8 @@
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import { zoom as d3Zoom, zoomIdentity, type D3ZoomEvent } from 'd3-zoom'; import { zoom as d3Zoom, zoomIdentity, type D3ZoomEvent } from 'd3-zoom';
import type { D3SelectionInstance, D3ZoomInstance, Transform } from '@reactflow/system'; import type { D3SelectionInstance, D3ZoomInstance, Transform, Viewport } from '@reactflow/system';
import { clamp } from '@reactflow/utils';
const isWrappedWithClass = (event: any, className: string | undefined) => const isWrappedWithClass = (event: any, className: string | undefined) =>
event.target.closest(`.${className}`); event.target.closest(`.${className}`);
@@ -14,7 +15,8 @@ function filter(event: any, params: ZoomParams): boolean {
if ( if (
event.button === 1 && event.button === 1 &&
event.type === 'mousedown' && event.type === 'mousedown' &&
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge')) (isWrappedWithClass(event, 'svelte-flow__node') ||
isWrappedWithClass(event, 'svelte-flow__edge'))
) { ) {
return true; return true;
} }
@@ -80,15 +82,27 @@ type ZoomParams = {
transform: Writable<Transform>; transform: Writable<Transform>;
selecting: boolean; selecting: boolean;
d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>; d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
minZoom: number;
maxZoom: number;
initialViewport: Viewport;
}; };
export default function zoom(domNode: Element, params: ZoomParams) { export default function zoom(domNode: Element, params: ZoomParams) {
const { transform, d3 } = params; const { transform, d3, minZoom, maxZoom, initialViewport } = params;
const d3ZoomInstance = d3Zoom(); const d3ZoomInstance = d3Zoom().scaleExtent([minZoom, maxZoom]);
const selection = select(domNode).call(d3ZoomInstance); const selection = select(domNode).call(d3ZoomInstance);
const updatedTransform = zoomIdentity
.translate(initialViewport.x, initialViewport.y)
.scale(clamp(initialViewport.zoom, minZoom, maxZoom));
const d3ZoomHandler = selection.on('wheel.zoom'); const d3ZoomHandler = selection.on('wheel.zoom');
d3ZoomInstance.transform(selection, zoomIdentity); d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
transform.set([event.transform.x, event.transform.y, event.transform.k]);
});
d3ZoomInstance.transform(selection, updatedTransform);
selection.on('wheel.zoom', function (event: any, d: any) { selection.on('wheel.zoom', function (event: any, d: any) {
if (isWrappedWithClass(event, 'nowheel')) { if (isWrappedWithClass(event, 'nowheel')) {
@@ -104,10 +118,6 @@ export default function zoom(domNode: Element, params: ZoomParams) {
selection selection
}); });
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
transform.set([event.transform.x, event.transform.y, event.transform.k]);
});
d3ZoomInstance.filter((event: any) => filter(event, params)); d3ZoomInstance.filter((event: any) => filter(event, params));
return { return {
@@ -11,7 +11,7 @@
export let interactionWidth: $$Props['interactionWidth'] = 20; export let interactionWidth: $$Props['interactionWidth'] = 20;
</script> </script>
<path d={path} fill="none" class="react-flow__edge-path" /> <path d={path} fill="none" class="svelte-flow__edge-path" />
{#if interactionWidth} {#if interactionWidth}
<path <path
@@ -19,14 +19,14 @@
fill="none" fill="none"
stroke-opacity={0} stroke-opacity={0}
stroke-width={interactionWidth} stroke-width={interactionWidth}
class="react-flow__edge-interaction" class="svelte-flow__edge-interaction"
/> />
{/if} {/if}
{#if label} {#if label}
<EdgeLabelRenderer> <EdgeLabelRenderer>
<div <div
class="react-flow__edge-label" class="svelte-flow__edge-label"
style:transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`} style:transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
> >
{label} {label}
@@ -35,7 +35,7 @@
{/if} {/if}
<style> <style>
.react-flow__edge-label { .svelte-flow__edge-label {
position: absolute; position: absolute;
background: white; background: white;
} }
@@ -7,15 +7,15 @@
</script> </script>
{#if $connectionPath} {#if $connectionPath}
<svg width={$width} height={$height} class="react-flow__connectionline"> <svg width={$width} height={$height} class="svelte-flow__connectionline">
<g class={cc(['react-flow__connection', $connection.status])}> <g class={cc(['svelte-flow__connection', $connection.status])}>
<path d={$connectionPath} fill="none" class="react-flow__connection-path" /> <path d={$connectionPath} fill="none" class="svelte-flow__connection-path" />
</g> </g>
</svg> </svg>
{/if} {/if}
<style> <style>
.react-flow__connectionline { .svelte-flow__connectionline {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@@ -26,12 +26,12 @@
z-index: 1001; z-index: 1001;
} }
.react-flow__connection { .svelte-flow__connection {
pointer-events: none; pointer-events: none;
} }
.react-flow__edge-path, .svelte-flow__edge-path,
.react-flow__connection-path { .svelte-flow__connection-path {
stroke: #b1b1b7; stroke: #b1b1b7;
stroke-width: 1; stroke-width: 1;
fill: none; fill: none;
@@ -4,6 +4,6 @@
type $$Props = {}; type $$Props = {};
</script> </script>
<div use:portal={'.react-flow__edgelabel-renderer'}> <div use:portal={'.svelte-flow__edgelabel-renderer'}>
<slot /> <slot />
</div> </div>
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { SvelteComponentTyped } from 'svelte'; import { createEventDispatcher, type SvelteComponentTyped } from 'svelte';
import { Position } from '@reactflow/system'; import { Position } from '@reactflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
@@ -24,11 +24,23 @@
export let selected: $$Props['selected'] = false; export let selected: $$Props['selected'] = false;
export let label: $$Props['label'] = undefined; export let label: $$Props['label'] = undefined;
const { edgeTypes } = useStore(); const { edgeTypes, edges } = useStore();
const dispatch = createEventDispatcher();
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge; const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
function onClick() {
const edge = $edges.find(e => e.id === id);
dispatch('edge:click', edge);
}
</script> </script>
<g class="react-flow__edge" class:animated data-id={id}> <g
class="svelte-flow__edge"
class:animated
data-id={id}
on:click={onClick}
>
<svelte:component <svelte:component
this={edgeComponent} this={edgeComponent}
{id} {id}
@@ -47,12 +59,12 @@
</g> </g>
<style> <style>
.react-flow__edge { .svelte-flow__edge {
pointer-events: visibleStroke; pointer-events: visibleStroke;
cursor: pointer; cursor: pointer;
} }
.react-flow__edge :global(path) { .svelte-flow__edge :global(path) {
stroke: #ccc; stroke: #ccc;
stroke-width: 1; stroke-width: 1;
fill: none; fill: none;
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { createEventDispatcher } from 'svelte';
import cc from 'classcat'; import cc from 'classcat';
import { Position, type Connection, type HandleProps } from '@reactflow/system'; import { Position, type Connection, type HandleProps } from '@reactflow/system';
import { isMouseEvent } from '@reactflow/utils'; import { isMouseEvent } from '@reactflow/utils';
@@ -18,6 +19,8 @@
const isTarget = type === 'target'; const isTarget = type === 'target';
const nodeId = getContext<string>('rf_nodeid'); const nodeId = getContext<string>('rf_nodeid');
const handleId = id || null; const handleId = id || null;
const dispatch = createEventDispatcher();
const { const {
connectionMode, connectionMode,
@@ -31,11 +34,16 @@
updateConnection updateConnection
} = useStore(); } = useStore();
function dispatchEvent(eventName: string) {
dispatch(eventName, { nodeId, handleId, type });
}
function onConnectExtended(params: Connection) { function onConnectExtended(params: Connection) {
addEdge(params); addEdge(params);
// @todo add props // @todo add props
// onConnectAction?.(edgeParams); // onConnectAction?.(edgeParams);
// onConnect?.(edgeParams); // onConnect?.(edgeParams);
dispatchEvent('connect')
} }
function onPointerDown(event: MouseEvent | TouchEvent) { function onPointerDown(event: MouseEvent | TouchEvent) {
@@ -56,7 +64,9 @@
onConnect: onConnectExtended, onConnect: onConnectExtended,
updateConnection, updateConnection,
cancelConnection, cancelConnection,
panBy panBy,
onConnectStart: () => dispatchEvent('connect:start'),
onConnectEnd: () => dispatchEvent('connect:end')
}); });
} }
@@ -74,8 +84,8 @@
data-handlepos={position} data-handlepos={position}
data-id={`${nodeId}-${id}-${type}`} data-id={`${nodeId}-${id}-${type}`}
class={cc([ class={cc([
'react-flow__handle', 'svelte-flow__handle',
`react-flow__handle-${position}`, `svelte-flow__handle-${position}`,
'nodrag', 'nodrag',
'nopan', 'nopan',
position position
@@ -90,7 +100,7 @@
</div> </div>
<style> <style>
.react-flow__handle { .svelte-flow__handle {
position: absolute; position: absolute;
pointer-events: none; pointer-events: none;
min-width: 5px; min-width: 5px;
@@ -43,7 +43,9 @@ export function handlePointerDown({
cancelConnection, cancelConnection,
isValidConnection, isValidConnection,
edgeUpdaterType, edgeUpdaterType,
onEdgeUpdateEnd onEdgeUpdateEnd,
onConnectStart,
onConnectEnd
}: { }: {
event: MouseEvent | TouchEvent; event: MouseEvent | TouchEvent;
handleId: string | null; handleId: string | null;
@@ -61,8 +63,10 @@ export function handlePointerDown({
panBy: (delta: XYPosition) => void; panBy: (delta: XYPosition) => void;
edgeUpdaterType?: HandleType; edgeUpdaterType?: HandleType;
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void; onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
onConnectStart: () => void;
onConnectEnd: () => void;
}): void { }): void {
// when react-flow is used inside a shadow root we can't use document // when svelte-flow is used inside a shadow root we can't use document
const doc = getHostForElement(event.target as HTMLElement); const doc = getHostForElement(event.target as HTMLElement);
let autoPanId = 0; let autoPanId = 0;
let prevClosestHandle: ConnectionHandle | null; let prevClosestHandle: ConnectionHandle | null;
@@ -113,7 +117,9 @@ export function handlePointerDown({
}); });
// @todo add prop // @todo add prop
// onConnectStart?.(event, { nodeId, handleId, handleType }); // onConnectStart?.(event, { nodeId, handleId, handleType });
onConnectStart();
function onPointerMove(event: MouseEvent | TouchEvent) { function onPointerMove(event: MouseEvent | TouchEvent) {
const transform = get(transformStore); const transform = get(transformStore);
@@ -166,7 +172,7 @@ export function handlePointerDown({
if (connection.source !== connection.target && handleDomNode) { if (connection.source !== connection.target && handleDomNode) {
resetRecentHandle(prevActiveHandle); resetRecentHandle(prevActiveHandle);
prevActiveHandle = handleDomNode; prevActiveHandle = handleDomNode;
// @todo: remove the old class names "react-flow__handle-" in the next major version // @todo: remove the old class names "svelte-flow__handle-" in the next major version
handleDomNode.classList.add('connecting'); handleDomNode.classList.add('connecting');
handleDomNode.classList.toggle('valid', isValid); handleDomNode.classList.toggle('valid', isValid);
} }
@@ -179,8 +185,8 @@ export function handlePointerDown({
// it's important to get a fresh reference from the store here // it's important to get a fresh reference from the store here
// in order to get the latest state of onConnectEnd // in order to get the latest state of onConnectEnd
// @todo add onConnectEnd prop
// getState().onConnectEnd?.(event); onConnectEnd();
if (edgeUpdaterType) { if (edgeUpdaterType) {
onEdgeUpdateEnd?.(event); onEdgeUpdateEnd?.(event);
@@ -81,11 +81,11 @@ export function isValidHandle(
) { ) {
const isTarget = fromType === 'target'; const isTarget = fromType === 'target';
const handleDomNode = doc.querySelector( const handleDomNode = doc.querySelector(
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]` `.svelte-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
); );
const { x, y } = getEventPosition(event); const { x, y } = getEventPosition(event);
const handleBelow = doc.elementFromPoint(x, y); const handleBelow = doc.elementFromPoint(x, y);
const handleToCheck = handleBelow?.classList.contains('react-flow__handle') const handleToCheck = handleBelow?.classList.contains('svelte-flow__handle')
? handleBelow ? handleBelow
: handleDomNode; : handleDomNode;
@@ -11,7 +11,7 @@
$: rect = getRectOfNodes(selectedNodes, $nodeOrigin); $: rect = getRectOfNodes(selectedNodes, $nodeOrigin);
</script> </script>
{#if selectedNodes} {#if selectedNodes && $selectionRectMode === 'nodes'}
<div <div
class="selection-wrapper nopan" class="selection-wrapper nopan"
style={`width: ${rect.width}px; height: ${rect.height}px; transform: translate(${rect.x}px, ${rect.y}px)`} style={`width: ${rect.width}px; height: ${rect.height}px; transform: translate(${rect.x}px, ${rect.y}px)`}
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onMount, setContext, SvelteComponentTyped } from 'svelte'; import { createEventDispatcher, onMount, setContext, SvelteComponentTyped } from 'svelte';
import cc from 'classcat'; import cc from 'classcat';
import { type XYPosition, Position, errorMessages } from '@reactflow/system'; import { type XYPosition, Position, errorMessages } from '@reactflow/system';
@@ -28,10 +28,9 @@
let className: string = ''; let className: string = '';
export { className as class }; export { className as class };
let nodeRef: HTMLDivElement;
const { nodes, transform, nodeTypes, updateNodePositions, addSelectedNodes } = useStore(); const { nodes, transform, nodeTypes, updateNodePositions, addSelectedNodes } = useStore();
let nodeRef: HTMLDivElement;
const nodeTypeValid = !!$nodeTypes[type!]; const nodeTypeValid = !!$nodeTypes[type!];
if (!nodeTypeValid) { if (!nodeTypeValid) {
@@ -44,6 +43,7 @@
const isSelectable = true; const isSelectable = true;
const selectNodesOnDrag = false; const selectNodesOnDrag = false;
const isDraggable = true; const isDraggable = true;
const dispatch = createEventDispatcher();
setContext('rf_nodeid', id); setContext('rf_nodeid', id);
@@ -55,30 +55,35 @@
}; };
}); });
function dispatchEvent(eventName: string) {
const node = $nodes.find(n => n.id === id);
dispatch(eventName, node);
}
function onSelectNodeHandler(event: MouseEvent) { function onSelectNodeHandler(event: MouseEvent) {
if (isSelectable && (!selectNodesOnDrag || !isDraggable)) { if (isSelectable && (!selectNodesOnDrag || !isDraggable)) {
// this handler gets called within the drag start event when selectNodesOnDrag=true // this handler gets called within the drag start event when selectNodesOnDrag=true
addSelectedNodes([id]); addSelectedNodes([id]);
} }
// if (onClick) { dispatchEvent('node:click')
// const node = store.getState().nodeInternals.get(id)!;
// onClick(event, { ...node });
// }
} }
</script> </script>
<div <div
use:drag={{ nodeId: id, nodes, transform, updateNodePositions }} use:drag={{ nodeId: id, nodes, transform, updateNodePositions }}
class={cc(['react-flow__node', `react-flow__node-${type}`, className])} data-id={id}
class={cc(['svelte-flow__node', `svelte-flow__node-${type}`, className])}
class:initializing={!width && !height} class:initializing={!width && !height}
class:dragging class:dragging
class:selected class:selected
bind:this={nodeRef} bind:this={nodeRef}
on:click={onSelectNodeHandler}
style:transform={`translate(${positionAbsolute.x}px, ${positionAbsolute.y}px)`} style:transform={`translate(${positionAbsolute.x}px, ${positionAbsolute.y}px)`}
{style} {style}
data-id={id} on:click={onSelectNodeHandler}
on:mouseenter={() => dispatchEvent('node:mouseenter')}
on:mouseleave={() => dispatchEvent('node:mouseleave')}
on:mousemove={() => dispatchEvent('node:mousemove')}
> >
<svelte:component <svelte:component
this={nodeComponent} this={nodeComponent}
@@ -90,11 +95,14 @@
isConnectable={true} isConnectable={true}
xPos={positionAbsolute.x} xPos={positionAbsolute.x}
yPos={positionAbsolute.y} yPos={positionAbsolute.y}
on:connect:start
on:connect
on:connect:end
/> />
</div> </div>
<style> <style>
.react-flow__node { .svelte-flow__node {
border-radius: 3px; border-radius: 3px;
color: #222; color: #222;
text-align: center; text-align: center;
@@ -1,5 +1,6 @@
import type { Node } from '$lib/types'; import type { Node } from '$lib/types';
export type NodeWrapperProps = Node & { export type NodeWrapperProps = Node & {
'on:nodeclick'?: (event: MouseEvent) => void;
resizeObserver?: ResizeObserver | null; resizeObserver?: ResizeObserver | null;
}; };
@@ -8,13 +8,13 @@
{#if isVisible} {#if isVisible}
<div <div
class="react-flow__selection" class="svelte-flow__selection"
style={`width: ${width}px; height: ${height}px; transform: translate(${x}px, ${y}px)`} style={`width: ${width}px; height: ${height}px; transform: translate(${x}px, ${y}px)`}
/> />
{/if} {/if}
<style> <style>
.react-flow__selection { .svelte-flow__selection {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@@ -23,8 +23,8 @@
z-index: 6; z-index: 6;
} }
.react-flow__selection:focus, .svelte-flow__selection:focus,
.react-flow__selection:focus-visible { .svelte-flow__selection:focus-visible {
outline: none; outline: none;
} }
</style> </style>
@@ -16,6 +16,7 @@
export let selected: $$Props['selected'] = false; export let selected: $$Props['selected'] = false;
</script> </script>
<Handle type="target" position={targetPosition} /> <Handle type="target" position={targetPosition} on:connect:start on:connect on:connect:end />
{data?.label} {data?.label}
<Handle type="source" position={sourcePosition} /> <Handle type="source" position={sourcePosition} on:connect:start on:connect on:connect:end />
@@ -1,12 +1,20 @@
<script lang="ts"> <script lang="ts">
import { Position } from '@reactflow/system'; import { Position } from '@reactflow/system';
import type { NodeProps } from '$lib/types';
import { Handle } from '$lib/components/Handle'; import { Handle } from '$lib/components/Handle';
export let data: { label: string } = { label: 'Node' }; interface $$Props extends NodeProps<{ label: string }> {}
export let isConnectable: boolean = true;
export let sourcePosition: Position = Position.Bottom; export let id: $$Props['id'];
export let data: $$Props['data'] = { label: 'Node' };
export let isConnectable: $$Props['isConnectable'] = true;
export let targetPosition: $$Props['targetPosition'] = Position.Top;
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
export let xPos: $$Props['xPos'];
export let yPos: $$Props['yPos'];
export let selected: $$Props['selected'] = false;
</script> </script>
{data?.label} {data?.label}
<Handle type="source" position={sourcePosition} /> <Handle type="source" position={sourcePosition} on:connect:start on:connect on:connect:end />
@@ -1,12 +1,20 @@
<script lang="ts"> <script lang="ts">
import { Position } from '@reactflow/system'; import { Position } from '@reactflow/system';
import type { NodeProps } from '$lib/types';
import { Handle } from '$lib/components/Handle'; import { Handle } from '$lib/components/Handle';
export let data: { label: string } = { label: 'Node' }; interface $$Props extends NodeProps<{ label: string }> {}
export let isConnectable: boolean = true;
export let targetPosition: Position = Position.Top; export let id: $$Props['id'];
export let data: $$Props['data'] = { label: 'Node' };
export let isConnectable: $$Props['isConnectable'] = true;
export let targetPosition: $$Props['targetPosition'] = Position.Top;
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
export let xPos: $$Props['xPos'];
export let yPos: $$Props['yPos'];
export let selected: $$Props['selected'] = false;
</script> </script>
{data?.label} {data?.label}
<Handle type="target" position={targetPosition} /> <Handle type="target" position={targetPosition} on:connect:start on:connect on:connect:end />
@@ -5,14 +5,14 @@
const { edgesLayouted, width, height } = useStore(); const { edgesLayouted, width, height } = useStore();
</script> </script>
<svg width={$width} height={$height} class="react-flow__edges"> <svg width={$width} height={$height} class="svelte-flow__edges">
{#each $edgesLayouted as edge (edge.id)} {#each $edgesLayouted as edge (edge.id)}
<EdgeWrapper {...edge} /> <EdgeWrapper {...edge} on:edge:click />
{/each} {/each}
</svg> </svg>
<style> <style>
.react-flow__edges { .svelte-flow__edges {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
@@ -21,16 +21,27 @@
onDestroy(() => { onDestroy(() => {
resizeObserver?.disconnect(); resizeObserver?.disconnect();
}); });
</script> </script>
<div class="react-flow__nodes"> <div class="svelte-flow__nodes">
{#each $nodes as node (node.id)} {#each $nodes as node (node.id)}
<NodeWrapper {...node} {resizeObserver} /> <NodeWrapper
{...node}
{resizeObserver}
on:node:click
on:node:mouseenter
on:node:mousemove
on:node:mouseleave
on:connect:start
on:connect
on:connect:end
/>
{/each} {/each}
</div> </div>
<style> <style>
.react-flow__nodes { .svelte-flow__nodes {
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
@@ -29,13 +29,15 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { useStore } from '$lib/store'; import { createEventDispatcher } from 'svelte';
import { SelectionMode } from '@reactflow/system'; import { SelectionMode } from '@reactflow/system';
import { getEventPosition, getNodesInside } from '@reactflow/utils'; import { getEventPosition, getNodesInside } from '@reactflow/utils';
import { useStore } from '$lib/store';
import { getConnectedEdges } from '$lib/utils'; import { getConnectedEdges } from '$lib/utils';
import type { Node, Edge } from '$lib/types'; import type { Node, Edge } from '$lib/types';
const dispatch = createEventDispatcher();
const { const {
nodes, nodes,
edges, edges,
@@ -60,7 +62,7 @@
$: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectMode === 'user'); $: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectMode === 'user');
function onClick(event: MouseEvent) { function onClick(event: MouseEvent) {
// onPaneClick?.(event); dispatch('pane:click');
resetSelectedElements(); resetSelectedElements();
selectionRectMode.set(null); selectionRectMode.set(null);
@@ -155,11 +157,21 @@
selectionRect.set(null); selectionRect.set(null);
}; };
const onContextMenu = (event: MouseEvent) => {
// if (Array.isArray(panOnDrag) && panOnDrag?.includes(2)) {
// event.preventDefault();
// return;
// }
dispatch('pane:contextmenu');
};
</script> </script>
<div <div
bind:this={container} bind:this={container}
class="react-flow__pane" class="svelte-flow__pane"
class:dragging={$dragging} class:dragging={$dragging}
class:selection={isSelecting} class:selection={isSelecting}
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)} on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
@@ -167,12 +179,13 @@
on:mousemove={hasActiveSelection ? onMouseMove : undefined} on:mousemove={hasActiveSelection ? onMouseMove : undefined}
on:mouseup={hasActiveSelection ? onMouseUp : undefined} on:mouseup={hasActiveSelection ? onMouseUp : undefined}
on:mouseleave={hasActiveSelection ? onMouseLeave : undefined} on:mouseleave={hasActiveSelection ? onMouseLeave : undefined}
on:contextmenu={wrapHandler(onContextMenu, container)}
> >
<slot /> <slot />
</div> </div>
<style> <style>
.react-flow__pane { .svelte-flow__pane {
cursor: grab; cursor: grab;
position: absolute; position: absolute;
top: 0; top: 0;
@@ -12,34 +12,34 @@
$: positionClasses = `${position}`.split('-'); $: positionClasses = `${position}`.split('-');
</script> </script>
<div class={cc(['react-flow__panel', className, ...positionClasses])} {style}> <div class={cc(['svelte-flow__panel', className, ...positionClasses])} {style}>
<slot /> <slot />
</div> </div>
<style> <style>
.react-flow__panel { .svelte-flow__panel {
position: absolute; position: absolute;
z-index: 5; z-index: 5;
margin: 15px; margin: 15px;
} }
.react-flow__panel.top { .svelte-flow__panel.top {
top: 0; top: 0;
} }
.react-flow__panel.bottom { .svelte-flow__panel.bottom {
bottom: 0; bottom: 0;
} }
.react-flow__panel.left { .svelte-flow__panel.left {
left: 0; left: 0;
} }
.react-flow__panel.right { .svelte-flow__panel.right {
right: 0; right: 0;
} }
.react-flow__panel.center { .svelte-flow__panel.center {
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { setContext, onMount } from 'svelte'; import { setContext, onMount, createEventDispatcher } from 'svelte';
import { ConnectionLineType } from '@reactflow/system';
import cc from 'classcat'; import cc from 'classcat';
import { key, createStore } from '$lib/store'; import { key, createStore } from '$lib/store';
@@ -13,21 +12,27 @@
import { NodeSelection } from '$lib/components/NodeSelection'; import { NodeSelection } from '$lib/components/NodeSelection';
import { KeyHandler } from '$lib/components/KeyHandler'; import { KeyHandler } from '$lib/components/KeyHandler';
import { ConnectionLine } from '$lib/components/ConnectionLine'; import { ConnectionLine } from '$lib/components/ConnectionLine';
import type { SvelteFlowProps } from './types'; import type { SvelteFlowProps, SvelteFlowEvents } from './types';
type $$Props = SvelteFlowProps; type $$Props = SvelteFlowProps;
type $$Events = SvelteFlowEvents;
export let id: $$Props['id'] = '1'; export let id: $$Props['id'] = '1';
export let nodes: $$Props['nodes'] = []; export let nodes: $$Props['nodes'] = [];
export let edges: $$Props['edges'] = []; export let edges: $$Props['edges'] = [];
export let fitView: $$Props['fitView'] = undefined; export let fitView: $$Props['fitView'] = undefined;
export let minZoom: $$Props['minZoom'] = undefined;
export let maxZoom: $$Props['maxZoom'] = undefined;
export let initialViewport: $$Props['initialViewport'] = undefined;
export let nodeTypes: $$Props['nodeTypes'] = undefined; export let nodeTypes: $$Props['nodeTypes'] = undefined;
export let edgeTypes: $$Props['edgeTypes'] = undefined;
export let selectionKey: $$Props['selectionKey'] = undefined; export let selectionKey: $$Props['selectionKey'] = undefined;
export let deleteKey: $$Props['deleteKey'] = undefined; export let deleteKey: $$Props['deleteKey'] = undefined;
export let connectionLineType: $$Props['connectionLineType'] = ConnectionLineType.Bezier; export let connectionLineType: $$Props['connectionLineType'] = undefined
let className: $$Props['class'] = undefined; let className: $$Props['class'] = undefined;
export { className as class }; export { className as class };
const dispatch = createEventDispatcher<{ 'on:nodeclick': number }>();
let domNode: HTMLDivElement; let domNode: HTMLDivElement;
@@ -51,8 +56,33 @@
store.nodes.subscribe((ns) => { store.nodes.subscribe((ns) => {
nodes = ns; nodes = ns;
}); });
store.edges.subscribe((es) => {
edges = es;
});
}); });
$: {
const updatableProps = {
id,
connectionLineType,
};
Object.keys(updatableProps).forEach(prop => {
// @ts-ignore
if (updatableProps[prop] !== undefined) {
// @ts-ignore
if (!store[prop]) {
// @ts-ignore
console.warn(store[prop], prop, 'ups')
} else {
// @ts-ignore
store[prop].set(updatableProps[prop]);
}
}
})
}
$: { $: {
store.setNodes(nodes); store.setNodes(nodes);
} }
@@ -60,23 +90,47 @@
$: { $: {
store.setEdges(edges); store.setEdges(edges);
} }
$: {
if (nodeTypes !== undefined) {
store.setNodeTypes(nodeTypes);
}
if (edgeTypes !== undefined) {
store.setEdgeTypes(edgeTypes);
}
if (minZoom !== undefined) {
store.setMinZoom(minZoom);
}
if (maxZoom !== undefined) {
store.setMaxZoom(maxZoom);
}
}
</script> </script>
<div <div
{...$$restProps} class={cc(['svelte-flow', className])}
class={cc(['react-flow', className])}
data-testid="rf__wrapper" data-testid="rf__wrapper"
{id}
bind:this={domNode} bind:this={domNode}
> >
<KeyHandler {selectionKey} {deleteKey} /> <KeyHandler {selectionKey} {deleteKey} />
<Zoom> <Zoom {initialViewport}>
<Pane> <Pane on:pane:click>
<Viewport> <Viewport>
<EdgeRenderer /> <EdgeRenderer on:edge:click />
<ConnectionLine /> <ConnectionLine />
<div class="react-flow__edgelabel-renderer" /> <div class="svelte-flow__edgelabel-renderer" />
<NodeRenderer /> <NodeRenderer
on:node:click
on:node:mouseenter
on:node:mousemove
on:node:mouseleave
on:connect:start
on:connect
on:connect:end
/>
<NodeSelection /> <NodeSelection />
</Viewport> </Viewport>
<UserSelection /> <UserSelection />
@@ -86,7 +140,7 @@
</div> </div>
<style> <style>
.react-flow { .svelte-flow {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
@@ -94,13 +148,13 @@
z-index: 0; z-index: 0;
} }
.react-flow :global(.react-flow__node-default), .svelte-flow :global(.svelte-flow__node-default),
.react-flow :global(.react-flow__node-input), .svelte-flow :global(.svelte-flow__node-input),
.react-flow :global(.react-flow__node-output) { .svelte-flow :global(.svelte-flow__node-output) {
padding: 10px; padding: 10px;
} }
.react-flow__edgelabel-renderer { .svelte-flow__edgelabel-renderer {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -1,2 +1,2 @@
export { default as SvelteFlow } from './SvelteFlow.svelte'; export { default as SvelteFlow } from './SvelteFlow.svelte';
export type { SvelteFlowProps } from './types'; export * from './types';
@@ -1,17 +1,49 @@
import type { ConnectionLineType } from '@reactflow/system'; import type { ConnectionLineType, NodeOrigin, Viewport } from '@reactflow/system';
import type { Edge, Node, NodeTypes, KeyDefinition } from '$lib/types'; import type {
Edge,
Node,
NodeTypes,
KeyDefinition,
EdgeTypes,
DefaultEdgeOptions
} from '$lib/types';
export type SvelteFlowProps = { export type SvelteFlowProps = {
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
connectionLineType?: ConnectionLineType; id?: string;
nodeTypes?: NodeTypes;
edgeTypes?: EdgeTypes;
selectionKey?: KeyDefinition; selectionKey?: KeyDefinition;
deleteKey?: KeyDefinition; deleteKey?: KeyDefinition;
nodeTypes?: NodeTypes;
fitView?: boolean; fitView?: boolean;
nodeOrigin?: NodeOrigin;
minZoom?: number;
maxZoom?: number;
initialViewport?: Viewport;
defaultEdgeOptions?: DefaultEdgeOptions;
class?: string; class?: string;
style?: string; style?: string;
id?: string;
connectionLineType?: ConnectionLineType;
};
export type SvelteFlowEvents = {
'node:click': CustomEvent<Node>;
'node:mouseenter': CustomEvent<Node>;
'node:mousemove': CustomEvent<Node>;
'node:mouseleave': CustomEvent<Node>;
'edge:click': CustomEvent<Edge>;
'connect:start': CustomEvent<number>;
connect: CustomEvent<number>;
'connect:end': CustomEvent<number>;
'pane:click': CustomEvent;
'pane:contextmenu': CustomEvent;
};
export type SvelteFlowSlots = {
default: { slotValue: string };
}; };
@@ -5,14 +5,14 @@
</script> </script>
<div <div
class="react-flow__viewport" class="svelte-flow__viewport"
style="transform: translate({$transform[0]}px, {$transform[1]}px) scale({$transform[2]})" style="transform: translate({$transform[0]}px, {$transform[1]}px) scale({$transform[2]})"
> >
<slot /> <slot />
</div> </div>
<style> <style>
.react-flow__viewport { .svelte-flow__viewport {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
@@ -1,18 +1,23 @@
<script lang="ts"> <script lang="ts">
import { get } from 'svelte/store';
import type { Viewport } from '@reactflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
import zoom from '$lib/actions/zoom'; import zoom from '$lib/actions/zoom';
const { transform, d3, selectionKeyPressed, selectionRectMode } = useStore(); export let initialViewport: Viewport = { x: 0, y: 0, zoom: 1 };
const { transform, d3, selectionKeyPressed, selectionRectMode, minZoom, maxZoom } = useStore();
$: selecting = $selectionKeyPressed || $selectionRectMode === 'user'; $: selecting = $selectionKeyPressed || $selectionRectMode === 'user';
</script> </script>
<div class="react-flow__zoom" use:zoom={{ transform, d3, selecting }}> <div class="svelte-flow__zoom" use:zoom={{ transform, d3, selecting, minZoom: get(minZoom), maxZoom: get(maxZoom), initialViewport }}>
<slot /> <slot />
</div> </div>
<style> <style>
.react-flow__zoom { .svelte-flow__zoom {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
+8 -6
View File
@@ -1,11 +1,13 @@
import { SvelteFlow, type SvelteFlowProps } from '$lib/container/SvelteFlow'; import { SvelteFlow } from '$lib/container/SvelteFlow';
export { Controls, ControlButton } from '$lib/plugins/Controls';
export { Background, BackgroundVariant } from '$lib/plugins/Background'; export * from '$lib/container/SvelteFlow';
export { Minimap } from '$lib/plugins/Minimap'; export * from '$lib/container/Panel';
export { Panel, type PanelProps } from '$lib/container/Panel';
export * from '$lib/plugins/Controls';
export * from '$lib/plugins/Background';
export * from '$lib/plugins/Minimap';
export * from '$lib/types'; export * from '$lib/types';
export * from '$lib/utils'; export * from '$lib/utils';
export type { SvelteFlowProps };
export default SvelteFlow; export default SvelteFlow;
@@ -47,7 +47,7 @@
: [patternDimensions[0] / 2, patternDimensions[1] / 2]; : [patternDimensions[0] / 2, patternDimensions[1] / 2];
</script> </script>
<svg class={cc(['react-flow__background', className])} style={style}> <svg class={cc(['svelte-flow__background', className])} style={style}>
<pattern <pattern
id={patternId} id={patternId}
x={$transform[0] % scaledGap[0]} x={$transform[0] % scaledGap[0]}
@@ -67,7 +67,7 @@
</svg> </svg>
<style> <style>
.react-flow__background { .svelte-flow__background {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -9,14 +9,14 @@
<button <button
type="button" type="button"
on:click on:click
class={cc(['react-flow__controls-button', className])} class={cc(['svelte-flow__controls-button', className])}
{...$$restProps} {...$$restProps}
> >
<slot class="button-svg" /> <slot class="button-svg" />
</button> </button>
<style> <style>
.react-flow__controls-button { .svelte-flow__controls-button {
border: none; border: none;
background: #fefefe; background: #fefefe;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
@@ -31,7 +31,7 @@
padding: 5px; padding: 5px;
} }
.react-flow__controls-button :global(svg) { .svelte-flow__controls-button :global(svg) {
width: 100%; width: 100%;
max-width: 12px; max-width: 12px;
max-height: 12px; max-height: 12px;
@@ -43,11 +43,11 @@
}; };
</script> </script>
<Panel class="react-flow__controls" {position}> <Panel class="svelte-flow__controls" {position}>
{#if showZoom} {#if showZoom}
<ControlButton <ControlButton
on:click={onZoomInHandler} on:click={onZoomInHandler}
class="react-flow__controls-zoomin" class="svelte-flow__controls-zoomin"
title="zoom in" title="zoom in"
aria-label="zoom in" aria-label="zoom in"
> >
@@ -56,7 +56,7 @@
<svelte:component <svelte:component
this={ControlButton} this={ControlButton}
on:click={onZoomOutHandler} on:click={onZoomOutHandler}
class="react-flow__controls-zoomout" class="svelte-flow__controls-zoomout"
title="zoom out" title="zoom out"
aria-label="zoom out" aria-label="zoom out"
> >
@@ -66,7 +66,7 @@
{#if showFitView} {#if showFitView}
<svelte:component <svelte:component
this={ControlButton} this={ControlButton}
class="react-flow__controls-fitview" class="svelte-flow__controls-fitview"
on:click={onFitViewHandler} on:click={onFitViewHandler}
title="fit view" title="fit view"
aria-label="fit view" aria-label="fit view"
@@ -77,7 +77,7 @@
{#if showInteractive} {#if showInteractive}
<svelte:component <svelte:component
this={ControlButton} this={ControlButton}
class="react-flow__controls-interactive" class="svelte-flow__controls-interactive"
on:click={onToggleInteractivity} on:click={onToggleInteractivity}
title="toggle interactivity" title="toggle interactivity"
aria-label="toggle interactivity" aria-label="toggle interactivity"
@@ -1,3 +1,3 @@
export { default as Controls } from './Controls.svelte'; export { default as Controls } from './Controls.svelte';
export { default as ControlButton } from './ControlButton.svelte'; export { default as ControlButton } from './ControlButton.svelte';
export { type ControlsProps } from './types'; export * from './types';
@@ -1,8 +1,8 @@
import type { PanelPosition } from '@reactflow/system'; import type { PanelPosition } from '@reactflow/system';
export type ControlsProps = { export type ControlsProps = {
position: PanelPosition; position?: PanelPosition;
showZoom: boolean; showZoom?: boolean;
showFitView: boolean; showFitView?: boolean;
showInteractive: boolean; showInteractive?: boolean;
}; };
@@ -43,7 +43,7 @@
const nodeClassNameFunc = getAttrFunction(nodeClassName); const nodeClassNameFunc = getAttrFunction(nodeClassName);
const shapeRendering = const shapeRendering =
typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
const labelledBy = `react-flow__minimap-desc-${$id}`; const labelledBy = `svelte-flow__minimap-desc-${$id}`;
$: viewBB = { $: viewBB = {
x: -$transform[0] / $transform[2], x: -$transform[0] / $transform[2],
@@ -69,7 +69,7 @@
<Panel <Panel
{position} {position}
class={cc(['react-flow__minimap', className])} class={cc(['svelte-flow__minimap', className])}
style={`background-color: ${bgColor}; ${style}`} style={`background-color: ${bgColor}; ${style}`}
> >
<svg <svg
@@ -100,7 +100,7 @@
{/if} {/if}
{/each} {/each}
<path <path
class="react-flow__minimap-mask" class="svelte-flow__minimap-mask"
d={`M${x - offset},${y - offset}h${viewboxWidth + offset * 2}v${viewboxHeight + offset * 2}h${ d={`M${x - offset},${y - offset}h${viewboxWidth + offset * 2}v${viewboxHeight + offset * 2}h${
-viewboxWidth - offset * 2 -viewboxWidth - offset * 2
}z }z
@@ -19,7 +19,7 @@
</script> </script>
<rect <rect
class={cc(['react-flow__minimap-node', className])} class={cc(['svelte-flow__minimap-node', className])}
{x} {x}
{y} {y}
rx={borderRadius} rx={borderRadius}
+45 -10
View File
@@ -18,7 +18,12 @@ import { getHandleBounds, getConnectedEdges, addEdge as addEdgeUtil } from '$lib
import type { EdgeTypes, NodeTypes, Node, Edge, ConnectionData } from '$lib/types'; import type { EdgeTypes, NodeTypes, Node, Edge, ConnectionData } from '$lib/types';
import { getEdgesLayouted } from './edges-layouted'; import { getEdgesLayouted } from './edges-layouted';
import { getConnectionPath } from './connection-path'; import { getConnectionPath } from './connection-path';
import { initConnectionData, initialStoreState } from './initial-store'; import {
initConnectionData,
initialEdgeTypes,
initialNodeTypes,
initialStoreState
} from './initial-store';
import type { SvelteFlowStore } from './types'; import type { SvelteFlowStore } from './types';
export const key = Symbol(); export const key = Symbol();
@@ -32,20 +37,27 @@ type CreateStoreProps = {
id?: string; id?: string;
}; };
export function createStore({ export function createStore({ fitView: fitViewOnInit = false }: CreateStoreProps): SvelteFlowStore {
transform = [0, 0, 1],
nodeOrigin = [0, 0],
fitView: fitViewOnInit = false,
nodeTypes = {},
edgeTypes = {},
id = '1'
}: CreateStoreProps): SvelteFlowStore {
const store = { const store = {
...initialStoreState ...initialStoreState
}; };
let fitViewOnInitDone = false; let fitViewOnInitDone = false;
function setNodeTypes(nodeTypes: NodeTypes) {
store.nodeTypes.set({
...initialNodeTypes,
...nodeTypes
});
}
function setEdgeTypes(edgeTypes: EdgeTypes) {
store.edgeTypes.set({
...initialEdgeTypes,
...edgeTypes
});
}
function setEdges(edges: Edge[]) { function setEdges(edges: Edge[]) {
store.edges.set(edges); store.edges.set(edges);
} }
@@ -91,7 +103,7 @@ export function createStore({
} }
function updateNodeDimensions(updates: NodeDimensionUpdate[]) { function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
const viewportNode = document?.querySelector('.react-flow__viewport'); const viewportNode = document?.querySelector('.svelte-flow__viewport');
if (!viewportNode) { if (!viewportNode) {
return; return;
@@ -153,6 +165,25 @@ export function createStore({
} }
} }
function setMinZoom(minZoom: number) {
const d3Zoom = get(store.d3).zoom;
if (d3Zoom) {
d3Zoom?.scaleExtent([minZoom, get(store.maxZoom)]);
store.minZoom.set(minZoom);
}
}
function setMaxZoom(maxZoom: number) {
const d3Zoom = get(store.d3).zoom;
if (d3Zoom) {
d3Zoom?.scaleExtent([get(store.minZoom), maxZoom]);
store.maxZoom.set(maxZoom);
}
}
function fitView() { function fitView() {
const { zoom: d3Zoom, selection: d3Selection } = get(store.d3); const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
@@ -309,12 +340,16 @@ export function createStore({
// actions // actions
setNodes, setNodes,
setEdges, setEdges,
setNodeTypes,
setEdgeTypes,
addEdge, addEdge,
updateNodePositions, updateNodePositions,
updateNodeDimensions, updateNodeDimensions,
zoomIn, zoomIn,
zoomOut, zoomOut,
fitView, fitView,
setMinZoom,
setMaxZoom,
resetSelectedElements, resetSelectedElements,
addSelectedNodes, addSelectedNodes,
panBy, panBy,
+19 -13
View File
@@ -13,10 +13,10 @@ import {
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import InputNode from '$lib/components/nodes/InputNode.svelte'; import InputNode from '$lib/components/nodes/InputNode.svelte';
import OutputNode from '$lib/components/nodes/OutputNode.svelte'; import OutputNode from '$lib/components/nodes/OutputNode.svelte';
import type { Node, Edge, ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted } from '$lib/types';
import BezierEdge from '$lib/components/edges/BezierEdge.svelte'; import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
import StraightEdge from '$lib/components/edges/StraightEdge.svelte'; import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte'; import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
import type { Node, Edge, ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted } from '$lib/types';
export const initConnectionData = { export const initConnectionData = {
nodeId: null, nodeId: null,
@@ -26,18 +26,32 @@ export const initConnectionData = {
status: null status: null
}; };
export const initialNodeTypes = {
input: InputNode,
output: OutputNode,
default: DefaultNode
};
export const initialEdgeTypes = {
straight: StraightEdge,
smoothstep: SmoothStepEdge,
default: BezierEdge
};
export const initialStoreState = { export const initialStoreState = {
id: writable<string | null>(null),
nodes: writable<Node[]>([]), nodes: writable<Node[]>([]),
edges: writable<Edge[]>([]), edges: writable<Edge[]>([]),
edgesLayouted: readable<EdgeLayouted[]>([]), edgesLayouted: readable<EdgeLayouted[]>([]),
height: writable<number>(500), height: writable<number>(500),
width: writable<number>(500), width: writable<number>(500),
nodeOrigin: writable<NodeOrigin>([0.5, 0.5]), minZoom: writable<number>(0.5),
maxZoom: writable<number>(2),
nodeOrigin: writable<NodeOrigin>([0, 0]),
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({ d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
zoom: null, zoom: null,
selection: null selection: null
}), }),
id: writable<string | null>(null),
dragging: writable<boolean>(false), dragging: writable<boolean>(false),
selectionRect: writable<SelectionRect | null>(null), selectionRect: writable<SelectionRect | null>(null),
selectionKeyPressed: writable<boolean>(false), selectionKeyPressed: writable<boolean>(false),
@@ -45,16 +59,8 @@ export const initialStoreState = {
deleteKeyPressed: writable<boolean>(false), deleteKeyPressed: writable<boolean>(false),
selectionRectMode: writable<string | null>(null), selectionRectMode: writable<string | null>(null),
selectionMode: writable<SelectionMode>(SelectionMode.Partial), selectionMode: writable<SelectionMode>(SelectionMode.Partial),
nodeTypes: writable<NodeTypes>({ nodeTypes: writable<NodeTypes>(initialNodeTypes),
input: InputNode, edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
output: OutputNode,
default: DefaultNode
}),
edgeTypes: writable<EdgeTypes>({
straight: StraightEdge,
smoothstep: SmoothStepEdge,
default: BezierEdge
}),
transform: writable<Transform>([0, 0, 1]), transform: writable<Transform>([0, 0, 1]),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict), connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null), domNode: writable<HTMLDivElement | null>(null),
+5 -1
View File
@@ -7,14 +7,18 @@ import type {
} from '@reactflow/system'; } from '@reactflow/system';
import type { initialStoreState } from './initial-store'; import type { initialStoreState } from './initial-store';
import type { Node, Edge, ConnectionData } from '$lib/types'; import type { Node, Edge, ConnectionData, NodeTypes, EdgeTypes } from '$lib/types';
export type SvelteFlowStoreActions = { export type SvelteFlowStoreActions = {
setNodes: (nodes: Node[]) => void; setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void; setEdges: (edges: Edge[]) => void;
setNodeTypes: (nodeTypes: NodeTypes) => void;
setEdgeTypes: (edgeTypes: EdgeTypes) => void;
addEdge: (edge: Edge | Connection) => void; addEdge: (edge: Edge | Connection) => void;
zoomIn: (options?: ViewportHelperFunctionOptions) => void; zoomIn: (options?: ViewportHelperFunctionOptions) => void;
zoomOut: (options?: ViewportHelperFunctionOptions) => void; zoomOut: (options?: ViewportHelperFunctionOptions) => void;
setMinZoom: (minZoom: number) => void;
setMaxZoom: (maxZoom: number) => void;
fitView: (options?: ViewportHelperFunctionOptions) => boolean; fitView: (options?: ViewportHelperFunctionOptions) => boolean;
updateNodePositions: ( updateNodePositions: (
nodeDragItems: NodeDragItem[], nodeDragItems: NodeDragItem[],
+3
View File
@@ -3,6 +3,7 @@ import type { SvelteComponentTyped } from 'svelte';
import type { import type {
BaseEdge, BaseEdge,
BezierPathOptions, BezierPathOptions,
DefaultEdgeOptionsBase,
Position, Position,
SmoothStepPathOptions SmoothStepPathOptions
} from '@reactflow/system'; } from '@reactflow/system';
@@ -58,3 +59,5 @@ export type EdgeProps = Pick<
>; >;
export type EdgeTypes = Record<string, typeof SvelteComponentTyped<EdgeProps>>; export type EdgeTypes = Record<string, typeof SvelteComponentTyped<EdgeProps>>;
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
+21 -4
View File
@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { Node, Edge, NodeTypes } from '../lib/types';
import SvelteFlow, { import SvelteFlow, {
Controls, Controls,
Background, Background,
BackgroundVariant, BackgroundVariant,
Minimap, Minimap,
Panel Panel,
type Node, type Edge, type NodeTypes
} from '../lib/index'; } from '../lib/index';
import CustomNode from '../customnodes/Custom.svelte'; import CustomNode from '../customnodes/Custom.svelte';
@@ -120,7 +120,24 @@
// } // }
</script> </script>
<SvelteFlow bind:nodes bind:edges {nodeTypes} fitView> <SvelteFlow
bind:nodes
bind:edges
{nodeTypes}
fitView
minZoom={0.1}
maxZoom={2.5}
initialViewport={{ x: 100, y: 100, zoom: 2 }}
on:node:click={(event) => console.log('on node click', event)}
on:node:mouseenter={(event) => console.log('on node enter', event)}
on:node:mouseleave={(event) => console.log('on node leave', event)}
on:edge:click={(event) => console.log('edge click', event)}
on:connect:start={(event) => console.log('on connect start', event)}
on:connect={(event) => console.log('on connect', event)}
on:connect:end={(event) => console.log('on connect end', event)}
on:pane:click={(event) => console.log('on pane click', event)}
on:pane:contextmenu={(event) => { console.log('on pane contextmenu', event); }}
>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<Minimap /> <Minimap />
@@ -134,7 +151,7 @@
--node-width: 50; --node-width: 50;
} }
:global(.react-flow .custom-style) { :global(.svelte-flow .custom-style) {
background: #ff5050; background: #ff5050;
color: white; color: white;
} }
+1
View File
@@ -10,6 +10,7 @@
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// //
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes