1616// ============================================================================
1717
1818const CONFIG = {
19- duration : 500 , // Animation duration (ms) - matches assembly animation
19+ duration : 300 , // Animation duration (ms)
2020 flyDistanceMargin : 100 , // Extra margin beyond viewport edge (px in flow coords)
2121 domReadyDelay : 50 // Wait for DOM to render after node creation (ms)
2222} ;
@@ -38,48 +38,85 @@ export interface ViewportInfo {
3838// ============================================================================
3939
4040/**
41- * Animate a single node flying in from the left edge of the viewport
41+ * Animate a single node flying in from the cursor position
4242 *
4343 * @param nodeId - The ID of the node to animate
4444 * @param targetPosition - The node's final position in flow coordinates
4545 * @param getViewport - Function to get current viewport info
46+ * @param cursorScreen - Cursor position in screen coordinates (optional)
47+ * @param screenToFlow - Function to convert screen to flow coordinates (optional)
4648 */
4749export function runFlyInAnimation (
4850 nodeId : string ,
4951 targetPosition : { x : number ; y : number } ,
50- getViewport : ( ) => ViewportInfo
52+ getViewport : ( ) => ViewportInfo ,
53+ cursorScreen ?: { x : number ; y : number } | null ,
54+ screenToFlow ?: ( pos : { x : number ; y : number } ) => { x : number ; y : number }
5155) : void {
52- // Wait for DOM element to exist
53- setTimeout ( ( ) => {
54- const nodeEl = document . querySelector ( `[data-id="${ nodeId } "]` ) as HTMLElement ;
55- if ( ! nodeEl ) return ;
56+ let flyFromX : number ;
57+ let flyFromY : number ;
5658
59+ if ( cursorScreen && screenToFlow ) {
60+ // Convert cursor screen position to flow coordinates
61+ const cursorFlow = screenToFlow ( cursorScreen ) ;
62+ // Calculate offset from target position (fly-from is relative to final position)
63+ flyFromX = cursorFlow . x - targetPosition . x ;
64+ flyFromY = cursorFlow . y - targetPosition . y ;
65+ } else {
66+ // Fallback: fly from left edge of viewport
5767 const viewport = getViewport ( ) ;
58-
59- // Calculate left edge of viewport in flow coordinates
6068 const leftEdgeX = - viewport . x / viewport . zoom ;
69+ flyFromX = leftEdgeX - CONFIG . flyDistanceMargin - targetPosition . x ;
70+ flyFromY = 0 ;
71+ }
72+
73+ // Poll for DOM element (typically appears within 1-2 frames)
74+ let attempts = 0 ;
75+ const maxAttempts = 20 ;
76+
77+ function tryAnimate ( ) {
78+ const nodeEl = document . querySelector ( `[data-id="${ nodeId } "]` ) as HTMLElement ;
6179
62- // Fly from left edge (with margin) to target position
63- // The fly-from value is relative to the node's final position
64- const flyFromX = leftEdgeX - CONFIG . flyDistanceMargin - targetPosition . x ;
65- const flyFromY = 0 ; // Keep vertical position (fly horizontally)
80+ if ( ! nodeEl ) {
81+ attempts ++ ;
82+ if ( attempts < maxAttempts ) {
83+ requestAnimationFrame ( tryAnimate ) ;
84+ }
85+ return ;
86+ }
87+
88+ // Immediately set initial position to prevent flicker
89+ // This positions the node at the start of the animation before CSS takes over
90+ nodeEl . style . translate = `${ flyFromX } px ${ flyFromY } px` ;
91+ nodeEl . style . scale = '0.8' ;
92+ nodeEl . style . opacity = '0' ;
6693
6794 // Set CSS variables for the animation
6895 nodeEl . style . setProperty ( '--fly-from-x' , `${ flyFromX } px` ) ;
6996 nodeEl . style . setProperty ( '--fly-from-y' , `${ flyFromY } px` ) ;
7097 nodeEl . style . setProperty ( '--assembly-duration' , `${ CONFIG . duration } ms` ) ;
7198 nodeEl . style . setProperty ( '--assembly-delay' , '0ms' ) ;
7299
73- // Add the assembling class to trigger the CSS animation
74- nodeEl . classList . add ( 'assembling' ) ;
75-
76- // Cleanup after animation completes
77- setTimeout ( ( ) => {
78- nodeEl . classList . remove ( 'assembling' ) ;
79- nodeEl . style . removeProperty ( '--fly-from-x' ) ;
80- nodeEl . style . removeProperty ( '--fly-from-y' ) ;
81- nodeEl . style . removeProperty ( '--assembly-duration' ) ;
82- nodeEl . style . removeProperty ( '--assembly-delay' ) ;
83- } , CONFIG . duration + 50 ) ;
84- } , CONFIG . domReadyDelay ) ;
100+ // Start animation in next frame (after initial styles are applied)
101+ requestAnimationFrame ( ( ) => {
102+ // Remove inline styles and let animation take over
103+ nodeEl . style . removeProperty ( 'translate' ) ;
104+ nodeEl . style . removeProperty ( 'scale' ) ;
105+ nodeEl . style . removeProperty ( 'opacity' ) ;
106+
107+ // Add the assembling class to trigger the CSS animation
108+ nodeEl . classList . add ( 'assembling' ) ;
109+
110+ // Cleanup after animation completes
111+ setTimeout ( ( ) => {
112+ nodeEl . classList . remove ( 'assembling' ) ;
113+ nodeEl . style . removeProperty ( '--fly-from-x' ) ;
114+ nodeEl . style . removeProperty ( '--fly-from-y' ) ;
115+ nodeEl . style . removeProperty ( '--assembly-duration' ) ;
116+ nodeEl . style . removeProperty ( '--assembly-delay' ) ;
117+ } , CONFIG . duration + 50 ) ;
118+ } ) ;
119+ }
120+
121+ requestAnimationFrame ( tryAnimate ) ;
85122}
0 commit comments