@@ -25,6 +25,14 @@ const CLOSED_PREVIEW_STATE: DesktopPreviewState = {
2525 error : null ,
2626} ;
2727
28+ const HIDDEN_PREVIEW_BOUNDS = {
29+ x : 0 ,
30+ y : 0 ,
31+ width : 0 ,
32+ height : 0 ,
33+ visible : false ,
34+ } as const ;
35+
2836export function resolvePreviewStatusCopy ( state : DesktopPreviewState ) : string {
2937 if ( state . error ) {
3038 return state . error . message ;
@@ -88,14 +96,21 @@ export function PreviewPanel({ threadId, projectId, projectName, onClose }: Prev
8896 }
8997
9098 if ( storedUrl . trim ( ) . length === 0 ) {
91- void previewBridge . close ( ) ;
99+ void previewBridge . setBounds ( HIDDEN_PREVIEW_BOUNDS ) . finally ( ( ) => {
100+ void previewBridge . close ( ) ;
101+ } ) ;
92102 setPreviewState ( CLOSED_PREVIEW_STATE ) ;
93103 return ;
94104 }
95105
96- void previewBridge . close ( ) . finally ( ( ) => {
97- void previewBridge . open ( { url : storedUrl , title : `${ projectName } preview` } ) ;
98- } ) ;
106+ void previewBridge
107+ . setBounds ( HIDDEN_PREVIEW_BOUNDS )
108+ . catch ( ( ) => undefined )
109+ . finally ( ( ) => {
110+ void previewBridge . close ( ) . finally ( ( ) => {
111+ void previewBridge . open ( { url : storedUrl , title : `${ projectName } preview` } ) ;
112+ } ) ;
113+ } ) ;
99114 } , [ previewBridge , projectName , storedUrl , threadId ] ) ;
100115
101116 useEffect ( ( ) => {
@@ -114,33 +129,43 @@ export function PreviewPanel({ threadId, projectId, projectName, onClose }: Prev
114129 let lastBoundsKey = "" ;
115130 let resizeObserver : ResizeObserver | null = null ;
116131
117- const syncBounds = ( ) => {
118- frameId = 0 ;
119- if ( destroyed ) {
120- return ;
121- }
122-
132+ const computeBounds = ( ) => {
123133 const element = surfaceRef . current ;
124134 if ( ! element ) {
125- return ;
135+ return HIDDEN_PREVIEW_BOUNDS ;
126136 }
127137
128138 const rect = element . getBoundingClientRect ( ) ;
129- const nextBounds = {
139+ const visible =
140+ storedUrl . trim ( ) . length > 0 &&
141+ document . visibilityState === "visible" &&
142+ rect . width > 0 &&
143+ rect . height > 0 ;
144+ return {
130145 x : rect . left ,
131146 y : rect . top ,
132147 width : rect . width ,
133148 height : rect . height ,
134- visible : rect . width > 0 && rect . height > 0 ,
149+ visible,
135150 } ;
151+ } ;
152+
153+ const syncBounds = ( ) => {
154+ if ( destroyed ) {
155+ return ;
156+ }
157+
158+ const nextBounds = computeBounds ( ) ;
136159 const nextKey = `${ Math . round ( nextBounds . x ) } :${ Math . round ( nextBounds . y ) } :${ Math . round ( nextBounds . width ) } :${ Math . round ( nextBounds . height ) } :${ nextBounds . visible ? 1 : 0 } ` ;
137160 if ( nextKey !== lastBoundsKey ) {
138161 lastBoundsKey = nextKey ;
139162 void previewBridge . setBounds ( nextBounds ) ;
140163 }
164+
165+ frameId = window . requestAnimationFrame ( syncBounds ) ;
141166 } ;
142167
143- const scheduleSync = ( ) => {
168+ const scheduleImmediateSync = ( ) => {
144169 if ( destroyed || frameId !== 0 ) {
145170 return ;
146171 }
@@ -150,38 +175,36 @@ export function PreviewPanel({ threadId, projectId, projectName, onClose }: Prev
150175 const element = surfaceRef . current ;
151176 if ( typeof ResizeObserver !== "undefined" && element ) {
152177 resizeObserver = new ResizeObserver ( ( ) => {
153- scheduleSync ( ) ;
178+ lastBoundsKey = "" ;
154179 } ) ;
155180 resizeObserver . observe ( element ) ;
156181 }
157182
158183 const visualViewport = window . visualViewport ;
159- window . addEventListener ( "resize" , scheduleSync ) ;
160- window . addEventListener ( "scroll" , scheduleSync , true ) ;
161- visualViewport ?. addEventListener ( "resize" , scheduleSync ) ;
162- visualViewport ?. addEventListener ( "scroll" , scheduleSync ) ;
184+ const invalidateBounds = ( ) => {
185+ lastBoundsKey = "" ;
186+ } ;
163187
164- scheduleSync ( ) ;
165- const settleTimeoutId = window . setTimeout ( scheduleSync , 50 ) ;
188+ window . addEventListener ( "resize" , invalidateBounds ) ;
189+ window . addEventListener ( "scroll" , invalidateBounds , true ) ;
190+ document . addEventListener ( "visibilitychange" , invalidateBounds ) ;
191+ visualViewport ?. addEventListener ( "resize" , invalidateBounds ) ;
192+ visualViewport ?. addEventListener ( "scroll" , invalidateBounds ) ;
193+
194+ scheduleImmediateSync ( ) ;
166195
167196 return ( ) => {
168197 destroyed = true ;
169- window . clearTimeout ( settleTimeoutId ) ;
170198 if ( frameId !== 0 ) {
171199 window . cancelAnimationFrame ( frameId ) ;
172200 }
173201 resizeObserver ?. disconnect ( ) ;
174- window . removeEventListener ( "resize" , scheduleSync ) ;
175- window . removeEventListener ( "scroll" , scheduleSync , true ) ;
176- visualViewport ?. removeEventListener ( "resize" , scheduleSync ) ;
177- visualViewport ?. removeEventListener ( "scroll" , scheduleSync ) ;
178- void previewBridge . setBounds ( {
179- x : 0 ,
180- y : 0 ,
181- width : 0 ,
182- height : 0 ,
183- visible : false ,
184- } ) ;
202+ window . removeEventListener ( "resize" , invalidateBounds ) ;
203+ window . removeEventListener ( "scroll" , invalidateBounds , true ) ;
204+ document . removeEventListener ( "visibilitychange" , invalidateBounds ) ;
205+ visualViewport ?. removeEventListener ( "resize" , invalidateBounds ) ;
206+ visualViewport ?. removeEventListener ( "scroll" , invalidateBounds ) ;
207+ void previewBridge . setBounds ( HIDDEN_PREVIEW_BOUNDS ) ;
185208 } ;
186209 } , [ previewBridge , storedUrl , threadId , projectId ] ) ;
187210
0 commit comments