Merge pull request #5419 from xyflow/arrow-heads-fallback-color
Style Arrow Heads Edge Markers with CSS Variables
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': patch
|
||||||
|
'@xyflow/svelte': patch
|
||||||
|
'@xyflow/system': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Make arrow heads markers fallback to --xy-edge-stroke CSS variable when passing null as marker color
|
||||||
@@ -70,6 +70,30 @@ const initialNodes: Node[] = [
|
|||||||
data: { label: 'Output 10' },
|
data: { label: 'Output 10' },
|
||||||
position: { x: 50, y: 400 },
|
position: { x: 50, y: 400 },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: '11',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 11' },
|
||||||
|
position: { x: 825, y: 400 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '12',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 12' },
|
||||||
|
position: { x: 825, y: 300 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '13',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 13' },
|
||||||
|
position: { x: 900, y: 200 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '14',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 14' },
|
||||||
|
position: { x: 825, y: 100 },
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialEdges: Edge[] = [
|
const initialEdges: Edge[] = [
|
||||||
@@ -182,6 +206,57 @@ const initialEdges: Edge[] = [
|
|||||||
height: 20,
|
height: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-11',
|
||||||
|
source: '4',
|
||||||
|
target: '11',
|
||||||
|
label: 'Explicit Blue Prop Color (should override CSS)',
|
||||||
|
className: 'css-variable-edge',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: '#0000ff',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-12',
|
||||||
|
source: '4',
|
||||||
|
target: '12',
|
||||||
|
label: 'Marker explicitly undefined Color (defaults to none)',
|
||||||
|
className: 'css-variable-edge',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: undefined,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-13',
|
||||||
|
source: '4',
|
||||||
|
target: '13',
|
||||||
|
label: 'Marker null Color (should use `--xy-edge-stroke` CSS variable)',
|
||||||
|
className: 'css-variable-edge',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: null,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-14',
|
||||||
|
source: '4',
|
||||||
|
target: '14',
|
||||||
|
label: 'Marker implicitly undefined Color (defaults to defaultMarkerColor)',
|
||||||
|
className: 'css-variable-edge',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const edgeTypes: EdgeTypes = {
|
const edgeTypes: EdgeTypes = {
|
||||||
@@ -205,28 +280,41 @@ const EdgesFlow = () => {
|
|||||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<>
|
||||||
nodes={nodes}
|
<style>
|
||||||
edges={edges}
|
{`
|
||||||
onNodesChange={onNodesChange}
|
/* Test CSS variables on specific edges */
|
||||||
onEdgesChange={onEdgesChange}
|
.react-flow {
|
||||||
onNodeClick={onNodeClick}
|
--xy-edge-stroke-width: 1;
|
||||||
onConnect={onConnect}
|
--xy-edge-stroke: #00ff00;
|
||||||
onNodeDragStop={onNodeDragStop}
|
}
|
||||||
snapToGrid={true}
|
|
||||||
edgeTypes={edgeTypes}
|
`}
|
||||||
onEdgeClick={onEdgeClick}
|
</style>
|
||||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
<ReactFlow
|
||||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
nodes={nodes}
|
||||||
onEdgeMouseMove={onEdgeMouseMove}
|
edges={edges}
|
||||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
onNodesChange={onNodesChange}
|
||||||
onDelete={console.log}
|
onEdgesChange={onEdgesChange}
|
||||||
defaultEdgeOptions={defaultEdgeOptions}
|
onNodeClick={onNodeClick}
|
||||||
>
|
onConnect={onConnect}
|
||||||
<MiniMap />
|
onNodeDragStop={onNodeDragStop}
|
||||||
<Controls />
|
snapToGrid={true}
|
||||||
<Background />
|
edgeTypes={edgeTypes}
|
||||||
</ReactFlow>
|
onEdgeClick={onEdgeClick}
|
||||||
|
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||||
|
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||||
|
onEdgeMouseMove={onEdgeMouseMove}
|
||||||
|
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||||
|
onDelete={console.log}
|
||||||
|
defaultEdgeOptions={defaultEdgeOptions}
|
||||||
|
defaultMarkerColor={'purple'}
|
||||||
|
>
|
||||||
|
<MiniMap />
|
||||||
|
<Controls />
|
||||||
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,30 @@
|
|||||||
type: 'output',
|
type: 'output',
|
||||||
data: { label: 'Output 9' },
|
data: { label: 'Output 9' },
|
||||||
position: { x: 675, y: 500 }
|
position: { x: 675, y: 500 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '10',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 10' },
|
||||||
|
position: { x: 825, y: 400 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '11',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 11' },
|
||||||
|
position: { x: 825, y: 300 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '12',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 12' },
|
||||||
|
position: { x: 900, y: 200 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '13',
|
||||||
|
type: 'output',
|
||||||
|
data: { label: 'Output 13' },
|
||||||
|
position: { x: 825, y: 100 }
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -129,6 +153,55 @@
|
|||||||
label: 'hi',
|
label: 'hi',
|
||||||
labelStyle: 'background: red; font-weight: 700; padding: 5px;',
|
labelStyle: 'background: red; font-weight: 700; padding: 5px;',
|
||||||
style: 'stroke: #ffcc0'
|
style: 'stroke: #ffcc0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-10',
|
||||||
|
source: '4',
|
||||||
|
target: '10',
|
||||||
|
label: 'Explicit Prop Blue Color (should override CSS)',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: '#0000ff',
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-11',
|
||||||
|
source: '4',
|
||||||
|
target: '11',
|
||||||
|
label: 'Marker explicitly undefined Color (defaults to none)',
|
||||||
|
className: 'css-variable-edge',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: undefined,
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-12',
|
||||||
|
source: '4',
|
||||||
|
target: '12',
|
||||||
|
label: 'Marker null Color (should use `--xy-edge-stroke` CSS variable)',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: null,
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e4-13',
|
||||||
|
source: '4',
|
||||||
|
target: '13',
|
||||||
|
label: 'Marker implicitly undefined Color (defaults to defaultMarkerColor)',
|
||||||
|
className: 'css-variable-edge',
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -141,6 +214,15 @@
|
|||||||
return `edge-${connection.source}-${connection.target}}`;
|
return `edge-${connection.source}-${connection.target}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultEdgeOptions = {
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: 'red',
|
||||||
|
width: 20,
|
||||||
|
height: 20
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$inspect(edges);
|
$inspect(edges);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -149,6 +231,7 @@
|
|||||||
bind:edges
|
bind:edges
|
||||||
{edgeTypes}
|
{edgeTypes}
|
||||||
fitView
|
fitView
|
||||||
|
{defaultEdgeOptions}
|
||||||
nodeDragThreshold={2}
|
nodeDragThreshold={2}
|
||||||
onbeforeconnect={(connection) => {
|
onbeforeconnect={(connection) => {
|
||||||
console.log('on edge create', connection);
|
console.log('on edge create', connection);
|
||||||
@@ -158,8 +241,17 @@
|
|||||||
id: getEdgeId(connection)
|
id: getEdgeId(connection)
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
defaultMarkerColor={'purple'}
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
<Background variant={BackgroundVariant.Dots} />
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
</SvelteFlow>
|
</SvelteFlow>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Test CSS variables on the marker SVG container */
|
||||||
|
:global(.svelte-flow) {
|
||||||
|
--xy-edge-stroke-width: 1;
|
||||||
|
--xy-edge-stroke: #00ff00;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useStore } from '../../hooks/useStore';
|
|||||||
import { useMarkerSymbol } from './MarkerSymbols';
|
import { useMarkerSymbol } from './MarkerSymbols';
|
||||||
|
|
||||||
type MarkerDefinitionsProps = {
|
type MarkerDefinitionsProps = {
|
||||||
defaultColor: string;
|
defaultColor: string | null;
|
||||||
rfId?: string;
|
rfId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,33 +6,21 @@ import { useStoreApi } from '../../hooks/useStore';
|
|||||||
type SymbolProps = Omit<EdgeMarker, 'type'>;
|
type SymbolProps = Omit<EdgeMarker, 'type'>;
|
||||||
|
|
||||||
const ArrowSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
|
const ArrowSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
|
||||||
return (
|
const style = {
|
||||||
<polyline
|
strokeWidth,
|
||||||
style={{
|
...(color && { stroke: color }),
|
||||||
stroke: color,
|
};
|
||||||
strokeWidth,
|
|
||||||
}}
|
return <polyline style={style} strokeLinecap="round" strokeLinejoin="round" fill="none" points="-5,-4 0,0 -5,4" />;
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
fill="none"
|
|
||||||
points="-5,-4 0,0 -5,4"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArrowClosedSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
|
const ArrowClosedSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
|
||||||
return (
|
const style = {
|
||||||
<polyline
|
strokeWidth,
|
||||||
style={{
|
...(color && { stroke: color, fill: color }),
|
||||||
stroke: color,
|
};
|
||||||
fill: color,
|
|
||||||
strokeWidth,
|
return <polyline style={style} strokeLinecap="round" strokeLinejoin="round" points="-5,-4 0,0 -5,4 -5,-4" />;
|
||||||
}}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
points="-5,-4 0,0 -5,4 -5,-4"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MarkerSymbols = {
|
export const MarkerSymbols = {
|
||||||
|
|||||||
@@ -139,7 +139,12 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* This event fires when the user releases the source or target of an editable edge. It is called
|
* This event fires when the user releases the source or target of an editable edge. It is called
|
||||||
* even if an edge update does not occur.
|
* even if an edge update does not occur.
|
||||||
*/
|
*/
|
||||||
onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType, connectionState: FinalConnectionState) => void;
|
onReconnectEnd?: (
|
||||||
|
event: MouseEvent | TouchEvent,
|
||||||
|
edge: EdgeType,
|
||||||
|
handleType: HandleType,
|
||||||
|
connectionState: FinalConnectionState
|
||||||
|
) => void;
|
||||||
/**
|
/**
|
||||||
* Use this event handler to add interactivity to a controlled flow.
|
* Use this event handler to add interactivity to a controlled flow.
|
||||||
* It is called on node drag, select, and move.
|
* It is called on node drag, select, and move.
|
||||||
@@ -494,9 +499,10 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
/**
|
/**
|
||||||
* Color of edge markers.
|
* Color of edge markers.
|
||||||
|
* You can pass `null` to use the CSS variable `--xy-edge-stroke` for the marker color.
|
||||||
* @default '#b1b1b7'
|
* @default '#b1b1b7'
|
||||||
*/
|
*/
|
||||||
defaultMarkerColor?: string;
|
defaultMarkerColor?: string | null;
|
||||||
/**
|
/**
|
||||||
* Controls if the viewport should zoom by scrolling inside the container.
|
* Controls if the viewport should zoom by scrolling inside the container.
|
||||||
* @default true
|
* @default true
|
||||||
|
|||||||
@@ -8,9 +8,12 @@
|
|||||||
height = 12.5,
|
height = 12.5,
|
||||||
markerUnits = 'strokeWidth',
|
markerUnits = 'strokeWidth',
|
||||||
orient = 'auto-start-reverse',
|
orient = 'auto-start-reverse',
|
||||||
color,
|
color = 'none',
|
||||||
strokeWidth
|
strokeWidth
|
||||||
}: MarkerProps = $props();
|
}: MarkerProps = $props();
|
||||||
|
|
||||||
|
// Create inline styles when explicit color is provided
|
||||||
|
const polylineStyle = color ? `stroke: ${color}; fill: ${color};` : '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<marker
|
<marker
|
||||||
@@ -26,7 +29,7 @@
|
|||||||
>
|
>
|
||||||
{#if type === MarkerType.Arrow}
|
{#if type === MarkerType.Arrow}
|
||||||
<polyline
|
<polyline
|
||||||
stroke={color}
|
style={polylineStyle}
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width={strokeWidth}
|
stroke-width={strokeWidth}
|
||||||
@@ -35,11 +38,10 @@
|
|||||||
/>
|
/>
|
||||||
{:else if type === MarkerType.ArrowClosed}
|
{:else if type === MarkerType.ArrowClosed}
|
||||||
<polyline
|
<polyline
|
||||||
stroke={color}
|
style={polylineStyle}
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width={strokeWidth}
|
stroke-width={strokeWidth}
|
||||||
fill={color}
|
|
||||||
points="-5,-4 0,0 -5,4 -5,-4"
|
points="-5,-4 0,0 -5,4 -5,-4"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -230,9 +230,10 @@ export type SvelteFlowProps<
|
|||||||
*/
|
*/
|
||||||
snapGrid?: SnapGrid;
|
snapGrid?: SnapGrid;
|
||||||
/** Color of edge markers
|
/** Color of edge markers
|
||||||
|
* You can pass `null` to use the CSS variable `--xy-edge-stroke` for the marker color.
|
||||||
* @example "#b1b1b7"
|
* @example "#b1b1b7"
|
||||||
*/
|
*/
|
||||||
defaultMarkerColor?: string;
|
defaultMarkerColor?: string | null;
|
||||||
/**
|
/**
|
||||||
* Controls if all nodes should be draggable
|
* Controls if all nodes should be draggable
|
||||||
* @default true
|
* @default true
|
||||||
|
|||||||
@@ -365,7 +365,9 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
|
|||||||
|
|
||||||
selectNodesOnDrag: boolean = $derived(signals.props.selectNodesOnDrag ?? true);
|
selectNodesOnDrag: boolean = $derived(signals.props.selectNodesOnDrag ?? true);
|
||||||
|
|
||||||
defaultMarkerColor: string = $derived(signals.props.defaultMarkerColor ?? '#b1b1b7');
|
defaultMarkerColor: string | null = $derived(
|
||||||
|
signals.props.defaultMarkerColor === undefined ? '#b1b1b7' : signals.props.defaultMarkerColor
|
||||||
|
);
|
||||||
markers: MarkerProps[] = $derived.by(() => {
|
markers: MarkerProps[] = $derived.by(() => {
|
||||||
return createMarkerIds(signals.edges, {
|
return createMarkerIds(signals.edges, {
|
||||||
defaultColor: this.defaultMarkerColor,
|
defaultColor: this.defaultMarkerColor,
|
||||||
|
|||||||
@@ -165,6 +165,13 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Arrowhead marker styles - use CSS custom properties as default */
|
||||||
|
.xy-flow__arrowhead polyline {
|
||||||
|
fill: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
|
||||||
|
stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
|
||||||
|
}
|
||||||
|
|
||||||
.xy-flow__connection {
|
.xy-flow__connection {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export enum ConnectionLineType {
|
|||||||
*/
|
*/
|
||||||
export type EdgeMarker = {
|
export type EdgeMarker = {
|
||||||
type: MarkerType | `${MarkerType}`;
|
type: MarkerType | `${MarkerType}`;
|
||||||
color?: string;
|
color?: string | null;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
markerUnits?: string;
|
markerUnits?: string;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function createMarkerIds(
|
|||||||
defaultMarkerEnd,
|
defaultMarkerEnd,
|
||||||
}: {
|
}: {
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
defaultColor?: string;
|
defaultColor?: string | null;
|
||||||
defaultMarkerStart?: EdgeMarkerType;
|
defaultMarkerStart?: EdgeMarkerType;
|
||||||
defaultMarkerEnd?: EdgeMarkerType;
|
defaultMarkerEnd?: EdgeMarkerType;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user