feat(svelte): edge marker support
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { EdgeMarker } from '@reactflow/system';
|
||||
import { getMarkerId } from '@reactflow/utils';
|
||||
import type { MarkerProps } from '@reactflow/system';
|
||||
import { createMarkerIds } from '@reactflow/utils';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { useMarkerSymbol } from './MarkerSymbols';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
|
||||
type MarkerProps = EdgeMarker & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
type MarkerDefinitionsProps = {
|
||||
defaultColor: string;
|
||||
rfId?: string;
|
||||
@@ -51,22 +47,9 @@ const Marker = ({
|
||||
const markerSelector =
|
||||
({ defaultColor, rfId }: { defaultColor: string; rfId?: string }) =>
|
||||
(s: ReactFlowState) => {
|
||||
const ids: string[] = [];
|
||||
const markers = createMarkerIds(s.edges, { id: rfId, defaultColor });
|
||||
|
||||
return s.edges
|
||||
.reduce<MarkerProps[]>((markers, edge) => {
|
||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
||||
if (marker && typeof marker === 'object') {
|
||||
const markerId = getMarkerId(marker, rfId);
|
||||
if (!ids.includes(markerId)) {
|
||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||
ids.push(markerId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return markers;
|
||||
}, [])
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
return markers;
|
||||
};
|
||||
|
||||
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||
|
||||
@@ -4,14 +4,26 @@
|
||||
|
||||
type $$Props = BaseEdgeProps;
|
||||
|
||||
export let id: $$Props['id'] = undefined;
|
||||
export let path: $$Props['path'] = '';
|
||||
export let label: $$Props['label'] = undefined;
|
||||
export let labelX: $$Props['labelX'] = undefined;
|
||||
export let labelY: $$Props['labelY'] = undefined;
|
||||
export let markerStart: $$Props['markerStart'] = undefined;
|
||||
export let markerEnd: $$Props['markerEnd'] = undefined;
|
||||
export let style: $$Props['style'] = undefined;
|
||||
export let interactionWidth: $$Props['interactionWidth'] = 20;
|
||||
</script>
|
||||
|
||||
<path d={path} fill="none" class="svelte-flow__edge-path" />
|
||||
<path
|
||||
d={path}
|
||||
id={id}
|
||||
fill="none"
|
||||
class="svelte-flow__edge-path"
|
||||
marker-start={markerStart}
|
||||
marker-end={markerEnd}
|
||||
style={style}
|
||||
/>
|
||||
|
||||
{#if interactionWidth}
|
||||
<path
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { EdgeProps } from '$lib/types';
|
||||
|
||||
export type BaseEdgeProps = Pick<EdgeProps, 'interactionWidth' | 'label'> & {
|
||||
export type BaseEdgeProps = Pick<EdgeProps, 'interactionWidth' | 'label' | 'style'> & {
|
||||
id?: string;
|
||||
path: string;
|
||||
labelX?: number;
|
||||
labelY?: number;
|
||||
markerStart?: string;
|
||||
markerEnd?: string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, type SvelteComponentTyped } from 'svelte';
|
||||
import { Position } from '@reactflow/system';
|
||||
import { getMarkerId } from '@reactflow/utils';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
|
||||
@@ -16,20 +17,28 @@
|
||||
export let sourceY: $$Props['sourceY'] = 0;
|
||||
export let targetX: $$Props['targetX'] = 0;
|
||||
export let targetY: $$Props['targetY'] = 0;
|
||||
// @ todo: support edge updates
|
||||
// export let sourceHandleId: $$Props['sourceHandleId'] = undefined;
|
||||
// export let targetHandleId: $$Props['targetHandleId'] = undefined;
|
||||
export let data: $$Props['data'] = {};
|
||||
export let style: $$Props['style'] = undefined;
|
||||
export let sourcePosition: $$Props['sourcePosition'] = Position.Bottom;
|
||||
export let targetPosition: $$Props['targetPosition'] = Position.Top;
|
||||
export let animated: $$Props['animated'] = false;
|
||||
export let selected: $$Props['selected'] = false;
|
||||
export let label: $$Props['label'] = undefined;
|
||||
export let markerStart: $$Props['markerStart'] = undefined;
|
||||
export let markerEnd: $$Props['markerEnd'] = undefined;
|
||||
export let sourceHandleId: $$Props['sourceHandleId'] = undefined;
|
||||
export let targetHandleId: $$Props['targetHandleId'] = undefined;
|
||||
|
||||
const { edges, edgeTypes } = useStore();
|
||||
// @ todo: support edge updates
|
||||
|
||||
const { edges, edgeTypes, flowId } = useStore();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
|
||||
|
||||
$: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined;
|
||||
$: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined;
|
||||
|
||||
function onClick() {
|
||||
const edge = $edges.find(e => e.id === id);
|
||||
dispatch('edge:click', edge);
|
||||
@@ -57,6 +66,12 @@
|
||||
{animated}
|
||||
{selected}
|
||||
{label}
|
||||
{data}
|
||||
{style}
|
||||
{sourceHandleId}
|
||||
{targetHandleId}
|
||||
markerStart={markerStartUrl}
|
||||
markerEnd={markerEndUrl}
|
||||
/>
|
||||
</g>
|
||||
|
||||
|
||||
@@ -16,4 +16,15 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||
<BaseEdge
|
||||
{path}
|
||||
{labelX}
|
||||
{labelY}
|
||||
id={$$props.id}
|
||||
label={$$props.label}
|
||||
markerStart={$$props.markerStart}
|
||||
markerEnd={$$props.markerEnd}
|
||||
interactionWidth={$$props.interactionWidth}
|
||||
style={$$props.style}
|
||||
/>
|
||||
|
||||
|
||||
@@ -16,4 +16,14 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||
<BaseEdge
|
||||
{path}
|
||||
{labelX}
|
||||
{labelY}
|
||||
id={$$props.id}
|
||||
label={$$props.label}
|
||||
markerStart={$$props.markerStart}
|
||||
markerEnd={$$props.markerEnd}
|
||||
interactionWidth={$$props.interactionWidth}
|
||||
style={$$props.style}
|
||||
/>
|
||||
|
||||
@@ -17,4 +17,14 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||
<BaseEdge
|
||||
{path}
|
||||
{labelX}
|
||||
{labelY}
|
||||
id={$$props.id}
|
||||
label={$$props.label}
|
||||
markerStart={$$props.markerStart}
|
||||
markerEnd={$$props.markerEnd}
|
||||
interactionWidth={$$props.interactionWidth}
|
||||
style={$$props.style}
|
||||
/>
|
||||
|
||||
@@ -14,4 +14,15 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<BaseEdge {path} {labelX} {labelY} {...$$props} />
|
||||
<BaseEdge
|
||||
{path}
|
||||
{labelX}
|
||||
{labelY}
|
||||
id={$$props.id}
|
||||
label={$$props.label}
|
||||
markerStart={$$props.markerStart}
|
||||
markerEnd={$$props.markerEnd}
|
||||
interactionWidth={$$props.interactionWidth}
|
||||
style={$$props.style}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { EdgeWrapper } from '$lib/components/EdgeWrapper';
|
||||
import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
const { edgesLayouted, width, height } = useStore();
|
||||
@@ -9,8 +10,11 @@
|
||||
{#each $edgesLayouted as edge (edge.id)}
|
||||
<EdgeWrapper {...edge} on:edge:click />
|
||||
{/each}
|
||||
|
||||
<MarkerDefinition />
|
||||
</svg>
|
||||
|
||||
|
||||
<style>
|
||||
.svelte-flow__edges {
|
||||
width: 100%;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { type MarkerProps, MarkerType } from '@reactflow/system';
|
||||
|
||||
type $$Props = MarkerProps;
|
||||
|
||||
export let id: $$Props['id'];
|
||||
export let type: $$Props['type'];
|
||||
export let width: $$Props['width'] = 12.5;
|
||||
export let height: $$Props['height'] = 12.5;
|
||||
export let markerUnits: $$Props['markerUnits'] = 'strokeWidth';
|
||||
export let orient: $$Props['orient']= 'auto-start-reverse';
|
||||
export let color: $$Props['color'] = undefined;
|
||||
export let strokeWidth: $$Props['strokeWidth'] = undefined;
|
||||
</script>
|
||||
|
||||
<marker
|
||||
class="svelte-flow__arrowhead"
|
||||
id={id}
|
||||
markerWidth={`${width}`}
|
||||
markerHeight={`${height}`}
|
||||
viewBox="-10 -10 20 20"
|
||||
markerUnits={markerUnits}
|
||||
orient={orient}
|
||||
refX="0"
|
||||
refY="0"
|
||||
>
|
||||
{#if type === MarkerType.Arrow}
|
||||
<polyline
|
||||
stroke={color}
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={strokeWidth}
|
||||
fill="none"
|
||||
points="-5,-4 0,0 -5,4"
|
||||
/>
|
||||
{:else if type === MarkerType.ArrowClosed}
|
||||
<polyline
|
||||
stroke={color}
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={strokeWidth}
|
||||
fill={color}
|
||||
points="-5,-4 0,0 -5,4 -5,-4"
|
||||
/>
|
||||
{/if}
|
||||
</marker>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { useStore } from '$lib/store';
|
||||
import { Marker } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
||||
|
||||
const { markers } = useStore();
|
||||
</script>
|
||||
|
||||
<defs>
|
||||
{#each $markers as marker(marker.id)}
|
||||
<Marker
|
||||
id={marker.id}
|
||||
type={marker.type}
|
||||
color={marker.color}
|
||||
width={marker.width}
|
||||
height={marker.height}
|
||||
markerUnits={marker.markerUnits}
|
||||
strokeWidth={marker.strokeWidth}
|
||||
orient={marker.orient}
|
||||
/>
|
||||
{/each}
|
||||
</defs>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as MarkerDefinition } from './MarkerDefinition.svelte';
|
||||
export { default as Marker } from './Marker.svelte';
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
type $$Props = SvelteFlowProps;
|
||||
|
||||
export let id: $$Props['id'] = '1';
|
||||
export let id = '1';
|
||||
export let fitView: $$Props['fitView'] = undefined;
|
||||
export let minZoom: $$Props['minZoom'] = undefined;
|
||||
export let maxZoom: $$Props['maxZoom'] = undefined;
|
||||
@@ -31,13 +31,14 @@
|
||||
export let connectionRadius: $$Props['connectionRadius'] = undefined;
|
||||
export let connectionLineType: $$Props['connectionLineType'] = undefined;
|
||||
export let isValidConnection: $$Props['isValidConnection'] = undefined;
|
||||
export let defaultMarkerColor = '#b1b1b7';
|
||||
|
||||
export let style: $$Props['style'] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
|
||||
let domNode: HTMLDivElement;
|
||||
|
||||
|
||||
$: flowId = id;
|
||||
const store = useStore();
|
||||
|
||||
onMount(() => {
|
||||
@@ -53,12 +54,13 @@
|
||||
|
||||
$: {
|
||||
const updatableProps = {
|
||||
id,
|
||||
flowId,
|
||||
connectionLineType,
|
||||
connectionRadius,
|
||||
selectionMode,
|
||||
snapGrid,
|
||||
isValidConnection
|
||||
isValidConnection,
|
||||
defaultMarkerColor
|
||||
};
|
||||
|
||||
Object.keys(updatableProps).forEach(prop => {
|
||||
|
||||
@@ -32,6 +32,7 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
||||
connectionRadius?: number;
|
||||
selectionMode?: SelectionMode;
|
||||
snapGrid?: SnapGrid;
|
||||
defaultMarkerColor?: string;
|
||||
|
||||
class?: string;
|
||||
style?: string;
|
||||
|
||||
@@ -31,14 +31,14 @@
|
||||
let className: $$Props['class'] = '';
|
||||
export { className as class };
|
||||
|
||||
const { transform, id } = useStore();
|
||||
const { transform, flowId } = useStore();
|
||||
const patternColor = color || defaultColor[variant!];
|
||||
const patternSize = size || defaultSize[variant!];
|
||||
const isDots = variant === BackgroundVariant.Dots;
|
||||
const isCross = variant === BackgroundVariant.Cross;
|
||||
const gapXY: number[] = Array.isArray(gap!) ? gap! : [gap!, gap!];
|
||||
|
||||
$: patternId = `background-pattern-${$id}`;
|
||||
$: patternId = `background-pattern-${$flowId}`;
|
||||
$: scaledGap = [gapXY[0] * $transform[2] || 1, gapXY[1] * $transform[2] || 1];
|
||||
$: scaledSize = patternSize * $transform[2];
|
||||
$: patternDimensions = (isCross ? [scaledSize, scaledSize] : scaledGap) as [number, number];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getContext } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { derived, get } from 'svelte/store';
|
||||
import { zoomIdentity } from 'd3-zoom';
|
||||
import {
|
||||
type NodeDragItem,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type CoordinateExtent
|
||||
} from '@reactflow/system';
|
||||
import {
|
||||
createMarkerIds,
|
||||
fitView as fitViewUtil,
|
||||
getD3Transition,
|
||||
getDimensions,
|
||||
@@ -314,6 +315,12 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore {
|
||||
// derived state
|
||||
edgesLayouted: getEdgesLayouted(store),
|
||||
connectionPath: getConnectionPath(store),
|
||||
markers: derived(
|
||||
[store.edges, store.defaultMarkerColor, store.flowId],
|
||||
([edges, defaultColor, id]) => {
|
||||
return createMarkerIds(edges, { defaultColor, id });
|
||||
}
|
||||
),
|
||||
|
||||
// actions
|
||||
setNodeTypes,
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
ConnectionLineType,
|
||||
type SelectionRect,
|
||||
type Transform,
|
||||
type SnapGrid
|
||||
type SnapGrid,
|
||||
type MarkerProps
|
||||
} from '@reactflow/system';
|
||||
|
||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||
@@ -49,7 +50,7 @@ export const initialEdgeTypes = {
|
||||
};
|
||||
|
||||
export const initialStoreState = {
|
||||
id: writable<string | null>(null),
|
||||
flowId: writable<string | null>(null),
|
||||
nodes: writable<Node[]>([]),
|
||||
edges: writable<Edge[]>([]),
|
||||
edgesLayouted: readable<EdgeLayouted[]>([]),
|
||||
@@ -80,5 +81,7 @@ export const initialStoreState = {
|
||||
connection: writable<ConnectionData>(initConnectionData),
|
||||
connectionRadius: writable<number>(25),
|
||||
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
|
||||
isValidConnection: writable<IsValidConnection>(() => true)
|
||||
isValidConnection: writable<IsValidConnection>(() => true),
|
||||
markers: readable<MarkerProps[]>([]),
|
||||
defaultMarkerColor: writable<string>('#b1b1b7')
|
||||
};
|
||||
|
||||
@@ -47,11 +47,15 @@ export type EdgeLayouted = Omit<Edge, 'sourceHandle' | 'targetHandle'> & {
|
||||
targetPosition: Position;
|
||||
sourceHandleId?: string;
|
||||
targetHandleId?: string;
|
||||
markerStart?: string;
|
||||
markerEnd?: string;
|
||||
};
|
||||
|
||||
export type EdgeProps = Pick<
|
||||
EdgeLayouted,
|
||||
| 'id'
|
||||
| 'data'
|
||||
| 'style'
|
||||
| 'source'
|
||||
| 'target'
|
||||
| 'sourceX'
|
||||
@@ -64,6 +68,10 @@ export type EdgeProps = Pick<
|
||||
| 'selected'
|
||||
| 'label'
|
||||
| 'interactionWidth'
|
||||
| 'markerStart'
|
||||
| 'markerEnd'
|
||||
| 'sourceHandleId'
|
||||
| 'targetHandleId'
|
||||
>;
|
||||
|
||||
export type EdgeTypes = Record<string, typeof SvelteComponentTyped<EdgeProps>>;
|
||||
|
||||
@@ -57,3 +57,7 @@ export enum MarkerType {
|
||||
Arrow = 'arrow',
|
||||
ArrowClosed = 'arrowclosed',
|
||||
}
|
||||
|
||||
export type MarkerProps = EdgeMarker & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import { boxToRect, clamp, devWarn, getBoundsOfBoxes, getOverlappingArea, rectTo
|
||||
import {
|
||||
errorMessages,
|
||||
type Connection,
|
||||
type EdgeMarkerType,
|
||||
type Transform,
|
||||
type XYPosition,
|
||||
type Rect,
|
||||
@@ -54,23 +53,6 @@ export const getIncomersBase = <NodeType extends BaseNode = BaseNode, EdgeType e
|
||||
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection | BaseEdge): string =>
|
||||
`reactflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
|
||||
|
||||
export const getMarkerId = (marker: EdgeMarkerType | undefined, rfId?: string): string => {
|
||||
if (typeof marker === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof marker === 'string') {
|
||||
return marker;
|
||||
}
|
||||
|
||||
const idPrefix = rfId ? `${rfId}__` : '';
|
||||
|
||||
return `${idPrefix}${Object.keys(marker)
|
||||
.sort()
|
||||
.map((key: string) => `${key}=${(marker as any)[key]}`)
|
||||
.join('&')}`;
|
||||
};
|
||||
|
||||
const connectionExists = (edge: BaseEdge, edges: BaseEdge[]) => {
|
||||
return edges.some(
|
||||
(el) =>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './graph';
|
||||
export * from './utils';
|
||||
export * from './marker';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { BaseEdge, EdgeMarker, EdgeMarkerType, MarkerProps } from '@reactflow/system';
|
||||
|
||||
export function getMarkerId(marker: EdgeMarkerType | undefined, id?: string | null): string {
|
||||
if (!marker) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof marker === 'string') {
|
||||
return marker;
|
||||
}
|
||||
|
||||
const idPrefix = id ? `${id}__` : '';
|
||||
|
||||
return `${idPrefix}${Object.keys(marker)
|
||||
.sort()
|
||||
.map((key) => `${key}=${marker[key as keyof EdgeMarker]}`)
|
||||
.join('&')}`;
|
||||
}
|
||||
|
||||
export function createMarkerIds(
|
||||
edges: BaseEdge[],
|
||||
{ id, defaultColor }: { id?: string | null; defaultColor?: string }
|
||||
) {
|
||||
const ids: string[] = [];
|
||||
|
||||
return edges
|
||||
.reduce<MarkerProps[]>((markers, edge) => {
|
||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
||||
if (marker && typeof marker === 'object') {
|
||||
const markerId = getMarkerId(marker, id);
|
||||
if (!ids.includes(markerId)) {
|
||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||
ids.push(markerId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return markers;
|
||||
}, [])
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
Reference in New Issue
Block a user