@@ -6,7 +6,16 @@ import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle
66import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon' ;
77import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon' ;
88import styles from '../../css/topology-components' ;
9- import { BadgeLocation , GraphElement , isNode , LabelPosition , Node , NodeStatus , TopologyQuadrant } from '../../types' ;
9+ import {
10+ BadgeLocation ,
11+ GraphElement ,
12+ isNode ,
13+ LabelPosition ,
14+ Node ,
15+ NodeStatus ,
16+ ScaleDetailsLevel ,
17+ TopologyQuadrant
18+ } from '../../types' ;
1019import { ConnectDragSource , ConnectDropTarget , OnSelect , WithDndDragProps } from '../../behavior' ;
1120import Decorator from '../decorators/Decorator' ;
1221import { Layer } from '../layers' ;
@@ -122,10 +131,19 @@ interface DefaultNodeProps {
122131 raiseLabelOnHover ?: boolean ; // TODO: Update default to be false, assume demo code will be followed
123132 /** Hide context menu kebab for the node */
124133 hideContextMenuKebab ?: boolean ;
134+ /**
135+ * When true, a non-interactive copy of the node is drawn at the pre-drag position while dragging.
136+ * When false or omitted, only the live node is shown (previous behavior). Can also be set via
137+ * node model data: `data: { showDragGhost: true }`.
138+ */
139+ showDragGhost ?: boolean ;
125140}
126141
127142const SCALE_UP_TIME = 200 ;
128143
144+ /** Scale factor for the drag ghost when the graph is not at low details level (visual “preview” size). */
145+ const DRAG_GHOST_SCALE = 0.7 ;
146+
129147type DefaultNodeInnerProps = Omit < DefaultNodeProps , 'element' > & { element : Node } ;
130148
131149const DefaultNodeInner : React . FunctionComponent < DefaultNodeInnerProps > = observer (
@@ -172,8 +190,11 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
172190 onContextMenu,
173191 contextMenuOpen,
174192 raiseLabelOnHover = true ,
175- hideContextMenuKebab
193+ hideContextMenuKebab,
194+ showDragGhost
176195 } ) => {
196+ const showDragGhostResolved =
197+ showDragGhost ?? ( element . getData ( ) as { showDragGhost ?: boolean } | undefined ) ?. showDragGhost ?? false ;
177198 const [ nodeHovered , hoverRef ] = useHover ( ) ;
178199 const [ labelHovered , labelRef ] = useHover ( ) ;
179200 const hovered = nodeHovered || labelHovered ;
@@ -183,6 +204,8 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
183204 const isHover = hover !== undefined ? hover : hovered ;
184205 const [ nodeScale , setNodeScale ] = useState < number > ( 1 ) ;
185206 const decoratorRef = useRef ( null ) ;
207+ const boxXRef = useRef < number | null > ( null ) ;
208+ const boxYRef = useRef < number | null > ( null ) ;
186209
187210 const statusDecorator = useMemo ( ( ) => {
188211 if ( ! status || ! showStatusDecorator ) {
@@ -258,6 +281,8 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
258281
259282 const nodeLabelPosition = labelPosition || element . getLabelPosition ( ) ;
260283 const scale = element . getGraph ( ) . getScale ( ) ;
284+ const detailsLevel = element . getGraph ( ) . getDetailsLevel ( ) ;
285+ const isLowDetailsLevel = detailsLevel === ScaleDetailsLevel . low ;
261286
262287 const animationRef = useRef < number > ( null ) ;
263288 const scaleGoal = useRef < number > ( 1 ) ;
@@ -325,6 +350,12 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
325350 return { translateX, translateY } ;
326351 } , [ element , nodeScale , scaleNode ] ) ;
327352
353+ const box = element . getBounds ( ) ;
354+ if ( ! showDragGhostResolved || ! dragging || ! boxXRef . current || ! boxYRef . current ) {
355+ boxXRef . current = box . x ;
356+ boxYRef . current = box . y ;
357+ }
358+
328359 const renderLabel = ( ) => {
329360 if ( ! showLabel || ! ( label || element . getLabel ( ) ) ) {
330361 return null ;
@@ -397,7 +428,7 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
397428 return nodeLabel ;
398429 } ;
399430
400- return (
431+ const mainNode = (
401432 < g
402433 className = { groupClassName }
403434 transform = { `${ scaleNode ? `translate(${ translateX } , ${ translateY } )` : '' } scale(${ nodeScale } )` }
@@ -421,6 +452,41 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
421452 { attachments }
422453 </ g >
423454 ) ;
455+
456+ if ( ! showDragGhostResolved ) {
457+ return mainNode ;
458+ }
459+
460+ return (
461+ < >
462+ { dragging && (
463+ < g
464+ className = { groupClassName }
465+ transform = { `translate(${ boxXRef . current - box . x } , ${ boxYRef . current - box . y } ) ${
466+ isLowDetailsLevel ? '' : `scale(${ DRAG_GHOST_SCALE } )`
467+ } `}
468+ >
469+ < NodeShadows />
470+ < g >
471+ { ShapeComponent && (
472+ < ShapeComponent
473+ className = { backgroundClassName }
474+ element = { element }
475+ width = { width }
476+ height = { height }
477+ filter = { filter }
478+ />
479+ ) }
480+ { ! isLowDetailsLevel && renderLabel ( ) }
481+ { ! isLowDetailsLevel && children }
482+ </ g >
483+ { statusDecorator }
484+ { attachments }
485+ </ g >
486+ ) }
487+ { mainNode }
488+ </ >
489+ ) ;
424490 }
425491) ;
426492
0 commit comments