diff --git a/packages/ckeditor5-link/src/linkui.js b/packages/ckeditor5-link/src/linkui.js
index fcfefc15f6d..843dd190216 100644
--- a/packages/ckeditor5-link/src/linkui.js
+++ b/packages/ckeditor5-link/src/linkui.js
@@ -662,16 +662,27 @@ export default class LinkUI extends Plugin {
const model = this.editor.model;
model.change( writer => {
+ const range = model.document.selection.getFirstRange();
+
if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
- writer.updateMarker( VISUAL_SELECTION_MARKER_NAME, {
- range: model.document.selection.getFirstRange()
- } );
+ writer.updateMarker( VISUAL_SELECTION_MARKER_NAME, { range } );
} else {
- writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
- usingOperation: false,
- affectsData: false,
- range: model.document.selection.getFirstRange()
- } );
+ if ( range.start.isAtEnd ) {
+ const focus = model.document.selection.focus;
+ const nextValidRange = getNextValidRange( range, focus, writer );
+
+ writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
+ usingOperation: false,
+ affectsData: false,
+ range: nextValidRange
+ } );
+ } else {
+ writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
+ usingOperation: false,
+ affectsData: false,
+ range
+ } );
+ }
}
} );
}
@@ -700,3 +711,27 @@ export default class LinkUI extends Plugin {
function findLinkElementAncestor( position ) {
return position.getAncestors().find( ancestor => isLinkElement( ancestor ) );
}
+
+// Returns next valid range for the fake visual selection marker.
+//
+// @private
+// @param {module:engine/model/range~Range} range Current range.
+// @param {module:engine/model/position~Position} focus Selection focus.
+// @param {module:engine/model/writer~Writer} writer Writer.
+// @returns {module:engine/model/range~Range} New valid range for the fake visual selection marker.
+function getNextValidRange( range, focus, writer ) {
+ const nextStartPath = [ range.start.path[ 0 ] + 1, 0 ];
+ const nextStartPosition = writer.createPositionFromPath( range.start.root, nextStartPath, 'toNext' );
+ const nextRange = writer.createRange( nextStartPosition, range.end );
+
+ // Block creating a potential next valid range over the current range end.
+ if ( nextRange.start.path[ 0 ] > range.end.path[ 0 ] ) {
+ return writer.createRange( focus );
+ }
+
+ if ( nextStartPosition.isAtStart && nextStartPosition.isAtEnd ) {
+ return getNextValidRange( nextRange, focus, writer );
+ }
+
+ return nextRange;
+}
diff --git a/packages/ckeditor5-link/tests/linkui.js b/packages/ckeditor5-link/tests/linkui.js
index 789ff91cd82..8caa29c18c7 100644
--- a/packages/ckeditor5-link/tests/linkui.js
+++ b/packages/ckeditor5-link/tests/linkui.js
@@ -477,46 +477,229 @@ describe( 'LinkUI', () => {
} );
} );
- it( 'should display a fake visual selection when a text fragment is selected', () => {
- setModelData( editor.model, '
f{o}o
' ); - expect( editor.getData() ).to.equal( 'foo
' ); - } ); + expect( getViewData( editor.editing.view ) ).to.equal( 'f{o}o
' ); + expect( editor.getData() ).to.equal( 'foo
' ); + } ); - it( 'should display a fake visual selection on a collapsed selection', () => { - setModelData( editor.model, '[
' + + 'foo]
' + ); + expect( editor.getData() ).to.equal( '
foo
' ); + } ); + + it( 'should display a fake visual selection on the next non-empty text node when selection starts at the end ' + + 'of the first block in the multiline selection', () => { + setModelData( editor.model, 'foo{
' + + 'bar]
' + ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + + it( 'should be displayed on first text node in non-empty element when selection contains few empty elements', () => { + setModelData( editor.model, 'foo{
' + + '' + + '' + + 'bar
' + + '' + + '' + + '}baz
'; + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( + 'foo
' + + '
' + + '
bar
' + + '
' + + '
baz
' + ); + } ); + } ); - expect( getViewData( editor.editing.view ) ).to.equal( - 'f{}o
' - ); - expect( editor.getData() ).to.equal( 'fo
' ); + describe( 'collapsed', () => { + it( 'should be displayed on a collapsed selection', () => { + setModelData( editor.model, 'f{}o
' + ); + expect( editor.getData() ).to.equal( 'fo
' ); + } ); + + it( 'should be displayed on selection focus when selection contains only one empty element ' + + '(selection focus is at the beginning of the first non-empty element)', () => { + setModelData( editor.model, 'foo{
' + + '' + + ']bar
'; + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + + it( 'should be displayed on selection focus when selection contains few empty elements ' + + '(selection focus is at the beginning of the first non-empty element)', () => { + setModelData( editor.model, 'foo{
' + + '' + + '' + + ']bar
'; + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + + it( 'should be displayed on selection focus when selection contains few empty elements ' + + '(selection focus is inside an empty element)', () => { + setModelData( editor.model, 'foo{
' + + '' + + ']
' + + 'bar
'; + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + } ); } ); function getMarkersRange( editor ) {