@@ -72,6 +72,10 @@ class FXGraphCompare {
7272 this . _followSelection = true ;
7373 this . _openPortalMenus = [ ] ;
7474 this . _currentTheme = this . viewers [ 0 ] ?. controller ?. state ?. themeName || 'light' ;
75+ this . _layoutRefreshQueued = false ;
76+ this . _layoutRefreshAgain = false ;
77+ this . _pendingRefreshAgainResetView = false ;
78+ this . _needsResetOnNextVisibleLayout = false ;
7579
7680 if ( this . container ) {
7781 this . _buildCompareDOM ( ) ;
@@ -166,11 +170,11 @@ class FXGraphCompare {
166170
167171 // ResizeObserver on canvas cell
168172 if ( typeof ResizeObserver !== 'undefined' ) {
169- const ro = new ResizeObserver ( ( ) => {
170- viewer . canvasRenderer . resize ( ) ;
171- viewer . renderAll ( ) ;
172- } ) ;
173+ const ro = new ResizeObserver ( ( ) => this . _scheduleLayoutRefresh ( ) ) ;
173174 ro . observe ( canvasCell ) ;
175+ if ( viewer . minimapRenderer ) {
176+ ro . observe ( minimapCell ) ;
177+ }
174178 this . _colResizeObservers . push ( ro ) ;
175179 }
176180 } ) ;
@@ -183,21 +187,67 @@ class FXGraphCompare {
183187
184188 this . _applyCompareTheme ( this . viewers [ 0 ] ?. controller ?. state ?. themeName || 'light' ) ;
185189
186- // Resize all viewers after DOM settles (double-rAF ensures grid layout is complete)
190+ this . _scheduleLayoutRefresh ( { resetView : true } ) ;
191+ }
192+
193+ _scheduleLayoutRefresh ( options = { } ) {
194+ if ( ! this . _root ) return ;
195+ if ( this . _layoutRefreshQueued ) {
196+ this . _layoutRefreshAgain = true ;
197+ this . _pendingRefreshAgainResetView = this . _pendingRefreshAgainResetView || ! ! options . resetView ;
198+ return ;
199+ }
200+ this . _pendingRefreshResetView = this . _pendingRefreshResetView || ! ! options . resetView ;
201+ this . _layoutRefreshQueued = true ;
187202 requestAnimationFrame ( ( ) => {
188203 requestAnimationFrame ( ( ) => {
189- this . viewers . forEach ( ( v ) => {
190- v . canvasRenderer . resize ( ) ;
191- if ( v . minimapRenderer ) {
192- v . minimapRenderer . resize ( ) ;
193- v . minimapRenderer . generateThumbnail ( ) ;
194- }
195- v . init ( ) ;
196- } ) ;
204+ this . _layoutRefreshQueued = false ;
205+ if ( ! this . _root ) return ;
206+ const resetView = ! ! this . _pendingRefreshResetView || this . _needsResetOnNextVisibleLayout ;
207+ this . _pendingRefreshResetView = false ;
208+ this . _refreshViewerLayout ( { resetView } ) ;
209+ if ( this . _layoutRefreshAgain ) {
210+ const resetAgain = this . _pendingRefreshAgainResetView ;
211+ this . _layoutRefreshAgain = false ;
212+ this . _pendingRefreshAgainResetView = false ;
213+ this . _scheduleLayoutRefresh ( { resetView : resetAgain } ) ;
214+ }
197215 } ) ;
198216 } ) ;
199217 }
200218
219+ _refreshViewerLayout ( options = { } ) {
220+ const resetView = ! ! options . resetView ;
221+ let sawInvalidLayout = false ;
222+ this . viewers . forEach ( ( viewer , i ) => {
223+ if ( ! this . _visibleViewers . has ( this . _viewerNames [ i ] ) ) return ;
224+ if ( viewer . canvasRenderer && typeof viewer . canvasRenderer . resetInteractionState === 'function' ) {
225+ viewer . canvasRenderer . resetInteractionState ( ) ;
226+ }
227+ if ( viewer . minimapRenderer && typeof viewer . minimapRenderer . resetInteractionState === 'function' ) {
228+ viewer . minimapRenderer . resetInteractionState ( ) ;
229+ }
230+
231+ const canvasRect = viewer . canvasContainer && viewer . canvasContainer . getBoundingClientRect ( ) ;
232+ const minimapRect = viewer . minimapRenderer && viewer . minimapRenderer . container . getBoundingClientRect ( ) ;
233+ const hasCanvasLayout = canvasRect && canvasRect . width > 0 && canvasRect . height > 0 ;
234+ const hasMinimapLayout = ! viewer . minimapRenderer || ( minimapRect && minimapRect . width > 0 && minimapRect . height > 0 ) ;
235+ if ( ! hasCanvasLayout || ! hasMinimapLayout ) {
236+ sawInvalidLayout = true ;
237+ return ;
238+ }
239+
240+ if ( viewer . canvasRenderer ) viewer . canvasRenderer . resize ( ) ;
241+ if ( viewer . minimapRenderer ) {
242+ viewer . minimapRenderer . resize ( ) ;
243+ viewer . minimapRenderer . generateThumbnail ( ) ;
244+ }
245+ if ( resetView ) viewer . init ( ) ;
246+ else viewer . renderAll ( ) ;
247+ } ) ;
248+ this . _needsResetOnNextVisibleLayout = sawInvalidLayout ;
249+ }
250+
201251 _buildSidebar ( sidebar ) {
202252 // Layers button + menu (portal pattern — menu appended to document.body when open)
203253 const layersWrap = document . createElement ( 'div' ) ;
@@ -489,15 +539,24 @@ class FXGraphCompare {
489539 }
490540 if ( isVis ) {
491541 colIdx ++ ;
492- v . canvasRenderer . resize ( ) ;
493- v . renderAll ( ) ;
494542 }
495543 } ) ;
496544
545+ this . _scheduleLayoutRefresh ( ) ;
546+
497547 if ( visible ) {
498548 const newViewer = this . viewers [ this . _viewerNames . indexOf ( name ) ] ;
499549 requestAnimationFrame ( ( ) => {
500- newViewer . canvasRenderer . resize ( ) ;
550+ const canvasRect = newViewer . canvasContainer && newViewer . canvasContainer . getBoundingClientRect ( ) ;
551+ if ( ! canvasRect || canvasRect . width <= 0 || canvasRect . height <= 0 ) {
552+ this . _needsResetOnNextVisibleLayout = true ;
553+ return ;
554+ }
555+ if ( newViewer . canvasRenderer ) newViewer . canvasRenderer . resize ( ) ;
556+ if ( newViewer . minimapRenderer ) {
557+ newViewer . minimapRenderer . resize ( ) ;
558+ newViewer . minimapRenderer . generateThumbnail ( ) ;
559+ }
501560 let srcViewer = null , srcNodeId = null ;
502561 this . viewers . forEach ( ( v , i ) => {
503562 if ( v === newViewer || ! this . _visibleViewers . has ( this . _viewerNames [ i ] ) ) return ;
@@ -994,6 +1053,10 @@ class FXGraphCompare {
9941053 this . _setViewerVisible ( name , visible ) ;
9951054 }
9961055
1056+ refreshLayout ( options = { } ) {
1057+ this . _scheduleLayoutRefresh ( options ) ;
1058+ }
1059+
9971060 destroy ( ) {
9981061 this . _teardownCompareDOM ( ) ;
9991062 this . _offs . forEach ( ( off ) => {
0 commit comments