diff --git a/packages/ckeditor5-widget/src/widgettypearound/widgettypearound.js b/packages/ckeditor5-widget/src/widgettypearound/widgettypearound.js
index 5664651733e..98e15a99915 100644
--- a/packages/ckeditor5-widget/src/widgettypearound/widgettypearound.js
+++ b/packages/ckeditor5-widget/src/widgettypearound/widgettypearound.js
@@ -121,6 +121,7 @@ export default class WidgetTypeAround extends Plugin {
this._enableTypeAroundFakeCaretActivationUsingKeyboardArrows();
this._enableDeleteIntegration();
this._enableInsertContentIntegration();
+ this._enableDeleteContentIntegration();
}
/**
@@ -737,6 +738,35 @@ export default class WidgetTypeAround extends Plugin {
} );
}, { priority: 'high' } );
}
+
+ /**
+ * Attaches the {@link module:engine/model/model~Model#event:deleteContent} event listener to block the event when the fake
+ * caret is active.
+ *
+ * This is required for cases that trigger {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
+ * before calling {@link module:engine/model/model~Model#insertContent `model.insertContent()`} like, for instance,
+ * plain text pasting.
+ *
+ * @private
+ */
+ _enableDeleteContentIntegration() {
+ const editor = this.editor;
+ const model = this.editor.model;
+ const documentSelection = model.document.selection;
+
+ this._listenToIfEnabled( editor.model, 'deleteContent', ( evt, [ selection ] ) => {
+ if ( selection && !selection.is( 'documentSelection' ) ) {
+ return;
+ }
+
+ const typeAroundFakeCaretPosition = getTypeAroundFakeCaretPosition( documentSelection );
+
+ // Disable removing the selection content while pasting plain text.
+ if ( typeAroundFakeCaretPosition ) {
+ evt.stop();
+ }
+ }, { priority: 'high' } );
+ }
}
// Injects the type around UI into a view widget instance.
diff --git a/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js b/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js
index 0eecb10d8dc..c441932c9fa 100644
--- a/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js
+++ b/packages/ckeditor5-widget/tests/widgettypearound/widgettypearound.js
@@ -1567,6 +1567,26 @@ describe( 'WidgetTypeAround', () => {
expect( getModelData( model ) ).to.equal( 'bar[]' );
} );
+ it( 'should handle pasted content (with formatting)', () => {
+ setModelData( editor.model, '[]' );
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'before' );
+ } );
+
+ viewDocument.fire( 'clipboardInput', {
+ dataTransfer: {
+ getData() {
+ return 'foobar';
+ }
+ }
+ } );
+
+ expect( getModelData( model ) ).to.equal(
+ 'foo<$text bold="true">bar[]$text>'
+ );
+ } );
+
function createParagraph( text ) {
return model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
@@ -1590,6 +1610,152 @@ describe( 'WidgetTypeAround', () => {
}
} );
+ describe( 'Model#deleteContent() integration', () => {
+ let model, modelSelection;
+
+ beforeEach( () => {
+ model = editor.model;
+ modelSelection = model.document.selection;
+ } );
+
+ it( 'should not alter deleteContent for the selection other than the document selection', () => {
+ setModelData( editor.model, 'foo[]baz' );
+
+ const batchSet = setupBatchWatch();
+ const selection = model.createSelection( modelSelection );
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'before' );
+ model.deleteContent( selection );
+ } );
+
+ expect( getModelData( model ) ).to.equal( 'foo[]baz' );
+ expect( batchSet.size ).to.be.equal( 1 );
+ } );
+
+ it( 'should not alter deleteContent when the "fake caret" is not active', () => {
+ setModelData( editor.model, 'foo[]baz' );
+
+ const batchSet = setupBatchWatch();
+
+ expect( modelSelection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE ) ).to.be.undefined;
+
+ model.deleteContent( modelSelection );
+
+ expect( getModelData( model ) ).to.equal( 'foo[]baz' );
+ expect( modelSelection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE ) ).to.be.undefined;
+ expect( batchSet.size ).to.be.equal( 1 );
+ } );
+
+ it( 'should disable deleteContent before a widget when it\'s the first element of the root', () => {
+ setModelData( editor.model, '[]' );
+
+ const batchSet = setupBatchWatch();
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'before' );
+ } );
+
+ model.deleteContent( modelSelection );
+
+ expect( getModelData( model ) ).to.equal( '[]' );
+ expect( modelSelection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE ) ).to.equal( 'before' );
+ expect( batchSet.size ).to.be.equal( 0 );
+ } );
+
+ it( 'should disable insertContent after a widget when it\'s the last element of the root', () => {
+ setModelData( editor.model, '[]' );
+
+ const batchSet = setupBatchWatch();
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'after' );
+ } );
+
+ model.deleteContent( modelSelection );
+
+ expect( getModelData( model ) ).to.equal( '[]' );
+ expect( modelSelection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE ) ).to.equal( 'after' );
+ expect( batchSet.size ).to.be.equal( 0 );
+ } );
+
+ it( 'should disable insertContent before a widget when it\'s not the first element of the root', () => {
+ setModelData( editor.model, 'foo[]' );
+
+ const batchSet = setupBatchWatch();
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'before' );
+ } );
+
+ model.deleteContent( modelSelection );
+
+ expect( getModelData( model ) ).to.equal( 'foo[]' );
+ expect( modelSelection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE ) ).to.equal( 'before' );
+ expect( batchSet.size ).to.be.equal( 0 );
+ } );
+
+ it( 'should disable insertContent after a widget when it\'s not the last element of the root', () => {
+ setModelData( editor.model, '[]foo' );
+
+ const batchSet = setupBatchWatch();
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'after' );
+ } );
+
+ model.deleteContent( modelSelection );
+
+ expect( getModelData( model ) ).to.equal( '[]foo' );
+ expect( modelSelection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE ) ).to.equal( 'after' );
+ expect( batchSet.size ).to.be.equal( 0 );
+ } );
+
+ it( 'should not block when the plugin is disabled', () => {
+ setModelData( editor.model, '[]' );
+
+ editor.plugins.get( WidgetTypeAround ).isEnabled = false;
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'before' );
+ } );
+
+ model.deleteContent( modelSelection );
+
+ expect( getModelData( model ) ).to.equal( '[]' );
+ } );
+
+ it( 'should not remove widget while pasting a plain text', () => {
+ setModelData( editor.model, '[]' );
+
+ model.change( writer => {
+ writer.setSelectionAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE, 'before' );
+ } );
+
+ viewDocument.fire( 'clipboardInput', {
+ dataTransfer: {
+ getData() {
+ return 'bar';
+ }
+ }
+ } );
+
+ expect( getModelData( model ) ).to.equal( 'bar[]' );
+ } );
+
+ function setupBatchWatch() {
+ const createdBatches = new Set();
+
+ model.on( 'applyOperation', ( evt, [ operation ] ) => {
+ if ( operation.isDocumentOperation ) {
+ createdBatches.add( operation.batch );
+ }
+ } );
+
+ return createdBatches;
+ }
+ } );
+
function blockWidgetPlugin( editor ) {
editor.model.schema.register( 'blockWidget', {
inheritAllFrom: '$block',