@@ -91,6 +91,9 @@ interface TypedMap<T> extends Map<keyof T, unknown> {
9191
9292const unreachable = ( n : never ) => n ;
9393
94+ // Annotation to mark programmatic changes (not user edits)
95+ const programmaticChangeAnnotation = Annotation . define < boolean > ( ) ;
96+
9497/**
9598 * A basic text editor with syntax highlighting for HTML, CSS, and JavaScript.
9699 */
@@ -339,7 +342,6 @@ export class PlaygroundCodeEditor extends LitElement {
339342 @queryAssignedElements ( { slot : 'extensions' , flatten : true } )
340343 private _extensionElements ! : HTMLElement [ ] ;
341344
342- private _valueChangingFromOutside = false ;
343345 private _diagnosticDecorations : DecorationSet = Decoration . none ;
344346 private _diagnosticsMouseoverListenerActive = false ;
345347 private _lastTransactions : Transaction [ ] = [ ] ;
@@ -424,6 +426,7 @@ export class PlaygroundCodeEditor extends LitElement {
424426 insert : this . value ?? '' ,
425427 } ,
426428 ] ,
429+ annotations : programmaticChangeAnnotation . of ( true ) ,
427430 } ) ;
428431 }
429432 }
@@ -447,14 +450,13 @@ export class PlaygroundCodeEditor extends LitElement {
447450 insert : this . value ?? '' ,
448451 } ,
449452 ] ,
453+ annotations : programmaticChangeAnnotation . of ( true ) ,
450454 } ) ;
451455 docState = tempView . state ;
452456 this . _docCache . set ( docKey , docState ) ;
453457 tempView . destroy ( ) ;
454458 }
455459
456- this . _valueChangingFromOutside = true ;
457-
458460 // Replace the entire view with the new editor state. Unlike CM5, CM6
459461 // is modular, and the history stays on the state object rather than
460462 // the editor / view.
@@ -465,8 +467,6 @@ export class PlaygroundCodeEditor extends LitElement {
465467 if ( needsHideAndFold ) {
466468 void this . _applyHideAndFoldRegions ( ) ;
467469 }
468-
469- this . _valueChangingFromOutside = false ;
470470 break ;
471471 }
472472 case 'value' :
@@ -476,8 +476,6 @@ export class PlaygroundCodeEditor extends LitElement {
476476 }
477477
478478 if ( this . value !== view ?. state . doc . toString ( ) ) {
479- this . _valueChangingFromOutside = true ;
480-
481479 // The 'value' property was changed externally and it differs from
482480 // the editor's current document content, so we need to update the
483481 // view model to match.
@@ -492,9 +490,8 @@ export class PlaygroundCodeEditor extends LitElement {
492490 insert : this . value ?? '' ,
493491 } ,
494492 ] ,
493+ annotations : programmaticChangeAnnotation . of ( true ) ,
495494 } ) ;
496-
497- this . _valueChangingFromOutside = false ;
498495 }
499496 break ;
500497 case 'lineNumbers' :
@@ -720,9 +717,16 @@ export class PlaygroundCodeEditor extends LitElement {
720717 tr . annotation ( Transaction . userEvent ) === 'redo'
721718 ) ;
722719
720+ // Check if ALL transactions are programmatic (not user edits).
721+ // We fire the change event if ANY transaction is a user edit, even if
722+ // there are also programmatic transactions in the same update.
723+ const allProgrammatic = update . transactions . every (
724+ ( tr ) => tr . annotation ( programmaticChangeAnnotation ) === true
725+ ) ;
726+
723727 // External changes are usually things like the editor switching which
724728 // file it is displaying.
725- if ( this . _valueChangingFromOutside ) {
729+ if ( allProgrammatic ) {
726730 // Apply hide/fold regions when value changes from outside
727731 void this . _applyHideAndFoldRegions ( ) ;
728732 this . _showDiagnostics ( ) ;
@@ -731,6 +735,7 @@ export class PlaygroundCodeEditor extends LitElement {
731735 // Always reapply hide/fold regions after undo/redo
732736 void this . _applyHideAndFoldRegions ( ) ;
733737 }
738+ // Only fire change event for user-initiated edits
734739 this . dispatchEvent ( new Event ( 'change' ) ) ;
735740 }
736741 }
0 commit comments