@@ -4,6 +4,7 @@ import { CellSelection } from 'prosemirror-tables';
44import { DecorationBridge } from './dom/DecorationBridge.js' ;
55import { SdtSelectionStyleManager } from './selection/SdtSelectionStyleManager.js' ;
66import { SemanticFlowController } from './layout/SemanticFlowController.js' ;
7+ import { LayoutErrorBanner } from './ui/LayoutErrorBanner.js' ;
78import type { EditorState , Transaction } from 'prosemirror-state' ;
89import type { Node as ProseMirrorNode , Mark } from 'prosemirror-model' ;
910import type { Mapping } from 'prosemirror-transform' ;
@@ -287,8 +288,7 @@ export class PresentationEditor extends EventEmitter {
287288 #dragDropManager: DragDropManager | null = null ;
288289 #layoutError: LayoutError | null = null ;
289290 #layoutErrorState: 'healthy' | 'degraded' | 'failed' = 'healthy' ;
290- #errorBanner: HTMLElement | null = null ;
291- #errorBannerMessage: HTMLElement | null = null ;
291+ #errorBanner! : LayoutErrorBanner ;
292292 #renderScheduled = false ;
293293 #pendingDocChange = false ;
294294 #pendingMapping: Mapping | null = null ;
@@ -431,6 +431,16 @@ export class PresentationEditor extends EventEmitter {
431431 this . #painterHost. addEventListener ( 'mouseover' , this . #sdtStyles. handleMouseEnter ) ;
432432 this . #painterHost. addEventListener ( 'mouseout' , this . #sdtStyles. handleMouseLeave ) ;
433433
434+ this . #errorBanner = new LayoutErrorBanner ( {
435+ host : this . #visibleHost,
436+ getDebugLabel : ( ) => this . #layoutOptions. debugLabel ,
437+ onRetry : ( ) => {
438+ this . #layoutError = null ;
439+ this . #pendingDocChange = true ;
440+ this . #scheduleRerender( ) ;
441+ } ,
442+ } ) ;
443+
434444 const win = this . #visibleHost?. ownerDocument ?. defaultView ?? window ;
435445 this . #domIndexObserverManager = new DomPositionIndexObserverManager ( {
436446 windowRoot : win ,
@@ -2461,7 +2471,7 @@ export class PresentationEditor extends EventEmitter {
24612471 this . #modeBanner = null ;
24622472 this . #ariaLiveRegion?. remove ( ) ;
24632473 this . #ariaLiveRegion = null ;
2464- this . #errorBanner?. remove ( ) ;
2474+ this . #errorBanner. destroy ( ) ;
24652475 if ( this . #editor) {
24662476 ( this . #editor as Editor & { presentationEditor ?: PresentationEditor | null } ) . presentationEditor = null ;
24672477 this . #editor. destroy ( ) ;
@@ -3588,7 +3598,7 @@ export class PresentationEditor extends EventEmitter {
35883598 // Reset error state on successful layout
35893599 this . #layoutError = null ;
35903600 this . #layoutErrorState = 'healthy' ;
3591- this . #dismissErrorBanner ( ) ;
3601+ this . #errorBanner . dismiss ( ) ;
35923602
35933603 // Update viewport dimensions after layout (page count may have changed)
35943604 this . #applyZoom( ) ;
@@ -5239,7 +5249,7 @@ export class PresentationEditor extends EventEmitter {
52395249 }
52405250
52415251 this . emit ( 'layoutError' , this . #layoutError) ;
5242- this . #showLayoutErrorBanner ( error ) ;
5252+ this . #errorBanner . show ( error ) ;
52435253 }
52445254
52455255 #decorateError( error : unknown , stage : string ) : Error {
@@ -5250,62 +5260,6 @@ export class PresentationEditor extends EventEmitter {
52505260 return new Error ( `[${ stage } ] ${ String ( error ) } ` ) ;
52515261 }
52525262
5253- #showLayoutErrorBanner( error : Error ) {
5254- const doc = this . #visibleHost. ownerDocument ?? document ;
5255- if ( ! this . #errorBanner) {
5256- const banner = doc . createElement ( 'div' ) ;
5257- banner . className = 'presentation-editor__layout-error' ;
5258- banner . style . display = 'flex' ;
5259- banner . style . alignItems = 'center' ;
5260- banner . style . justifyContent = 'space-between' ;
5261- banner . style . gap = '8px' ;
5262- banner . style . padding = '8px 12px' ;
5263- banner . style . background = '#FFF6E5' ;
5264- banner . style . border = '1px solid #F5B971' ;
5265- banner . style . borderRadius = '6px' ;
5266- banner . style . marginBottom = '8px' ;
5267-
5268- const message = doc . createElement ( 'span' ) ;
5269- banner . appendChild ( message ) ;
5270-
5271- const retry = doc . createElement ( 'button' ) ;
5272- retry . type = 'button' ;
5273- retry . textContent = 'Reload layout' ;
5274- retry . style . border = 'none' ;
5275- retry . style . borderRadius = '4px' ;
5276- retry . style . background = '#F5B971' ;
5277- retry . style . color = '#3F2D00' ;
5278- retry . style . padding = '6px 10px' ;
5279- retry . style . cursor = 'pointer' ;
5280- retry . addEventListener ( 'click' , ( ) => {
5281- this . #layoutError = null ;
5282- this . #dismissErrorBanner( ) ;
5283- this . #pendingDocChange = true ;
5284- this . #scheduleRerender( ) ;
5285- } ) ;
5286-
5287- banner . appendChild ( retry ) ;
5288- this . #visibleHost. prepend ( banner ) ;
5289-
5290- this . #errorBanner = banner ;
5291- this . #errorBannerMessage = message ;
5292- }
5293-
5294- if ( this . #errorBannerMessage) {
5295- this . #errorBannerMessage. textContent =
5296- 'Layout engine hit an error. Your document is safe — try reloading layout.' ;
5297- if ( this . #layoutOptions. debugLabel ) {
5298- this . #errorBannerMessage. textContent += ` (${ this . #layoutOptions. debugLabel } : ${ error . message } )` ;
5299- }
5300- }
5301- }
5302-
5303- #dismissErrorBanner( ) {
5304- this . #errorBanner?. remove ( ) ;
5305- this . #errorBanner = null ;
5306- this . #errorBannerMessage = null ;
5307- }
5308-
53095263 /**
53105264 * Determines whether the current viewing mode should block edits.
53115265 * When documentMode is viewing but the active editor has been toggled
0 commit comments