From bab66e536e094464773aa1908b697563a9a0789b Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Mon, 2 Dec 2024 15:26:48 +0100 Subject: [PATCH 01/12] Improve performance of `getSelectedBlocks` --- .../ckeditor5-engine/src/model/selection.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index 50c03db49e3..f166811fc17 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -17,7 +17,7 @@ import type DocumentSelection from './documentselection.js'; import type Element from './element.js'; import type Item from './item.js'; -import { CKEditorError, EmitterMixin, isIterable } from '@ckeditor/ckeditor5-utils'; +import { CKEditorError, EmitterMixin, first, isIterable } from '@ckeditor/ckeditor5-utils'; /** * Selection is a set of {@link module:engine/model/range~Range ranges}. It has a direction specified by its @@ -667,11 +667,21 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab yield startBlock as any; } - for ( const value of range.getWalker() ) { - const block = value.item; + const lastElement = first( range.getWalker( { direction: 'backward' } ) ); - if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block as any, visited, range ) ) { - yield block as Element; + if ( + lastElement && + lastElement.type == 'elementEnd' && + lastElement.item.root.document!.model.schema.isBlock( lastElement.item ) + ) { + yield lastElement.item as any; + } else { + for ( const value of range.getWalker() ) { + const block = value.item; + + if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block as any, visited, range ) ) { + yield block as Element; + } } } From 15ecaa4ea2adfaf7a0aaebb387433f065ceab942 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 3 Dec 2024 11:36:21 +0100 Subject: [PATCH 02/12] Fix for failing lists tests --- packages/ckeditor5-engine/src/model/selection.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index f166811fc17..d58aa60bc43 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -667,10 +667,13 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab yield startBlock as any; } + const firstElement = first( range.getWalker() ); const lastElement = first( range.getWalker( { direction: 'backward' } ) ); if ( + firstElement && lastElement && + firstElement.item === lastElement.item && lastElement.type == 'elementEnd' && lastElement.item.root.document!.model.schema.isBlock( lastElement.item ) ) { From cc29a9a4a372abcf071f7f2529456fa3a322a0e8 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 3 Dec 2024 13:29:45 +0100 Subject: [PATCH 03/12] Simplify change --- .../ckeditor5-engine/src/model/selection.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index d58aa60bc43..b8325900548 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -17,7 +17,7 @@ import type DocumentSelection from './documentselection.js'; import type Element from './element.js'; import type Item from './item.js'; -import { CKEditorError, EmitterMixin, first, isIterable } from '@ckeditor/ckeditor5-utils'; +import { CKEditorError, EmitterMixin, isIterable } from '@ckeditor/ckeditor5-utils'; /** * Selection is a set of {@link module:engine/model/range~Range ranges}. It has a direction specified by its @@ -667,17 +667,11 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab yield startBlock as any; } - const firstElement = first( range.getWalker() ); - const lastElement = first( range.getWalker( { direction: 'backward' } ) ); - - if ( - firstElement && - lastElement && - firstElement.item === lastElement.item && - lastElement.type == 'elementEnd' && - lastElement.item.root.document!.model.schema.isBlock( lastElement.item ) - ) { - yield lastElement.item as any; + const containedElement = range.getContainedElement(); + + if ( containedElement ) { + // Fast path for selection that contains only one element (e.g. big table) + yield containedElement; } else { for ( const value of range.getWalker() ) { const block = value.item; From b1e9b22384b0e57d22816226eb89d3f617f960c9 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 3 Dec 2024 13:30:32 +0100 Subject: [PATCH 04/12] Add missing dot --- packages/ckeditor5-engine/src/model/selection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index b8325900548..c4bb26f142d 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -670,7 +670,7 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab const containedElement = range.getContainedElement(); if ( containedElement ) { - // Fast path for selection that contains only one element (e.g. big table) + // Fast path for selection that contains only one element (e.g. big table). yield containedElement; } else { for ( const value of range.getWalker() ) { From c759e5fafcede15eceb45d6751634d0ace47bf06 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 3 Dec 2024 13:37:05 +0100 Subject: [PATCH 05/12] Ensure that fast path is only used for block elements --- packages/ckeditor5-engine/src/model/selection.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index c4bb26f142d..8ca42eebd8c 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -669,8 +669,11 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab const containedElement = range.getContainedElement(); - if ( containedElement ) { - // Fast path for selection that contains only one element (e.g. big table). + if ( + containedElement && + containedElement.root.document!.model.schema.isBlock( containedElement ) + ) { + // Fast path for selection that contains only one **block** element (e.g. big table). yield containedElement; } else { for ( const value of range.getWalker() ) { From 5ae6c1008177b842545ff0f13f2fd6efe0fdba9d Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Fri, 6 Dec 2024 15:50:07 +0100 Subject: [PATCH 06/12] Add `jumpTo` to `TreeWalker` --- .../ckeditor5-engine/src/model/selection.ts | 30 +++++++++++-------- .../ckeditor5-engine/src/model/treewalker.ts | 5 ++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index 8ca42eebd8c..61c8430a518 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -667,20 +667,24 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab yield startBlock as any; } - const containedElement = range.getContainedElement(); - - if ( - containedElement && - containedElement.root.document!.model.schema.isBlock( containedElement ) - ) { - // Fast path for selection that contains only one **block** element (e.g. big table). - yield containedElement; - } else { - for ( const value of range.getWalker() ) { - const block = value.item; + const treewalker = range.getWalker(); + + for ( const value of treewalker ) { + const block = value.item; + + if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block as any, visited, range ) ) { + yield block as Element; + } else if ( block.is( 'model:element' ) && block.root.document!.model.schema.isBlock( block ) ) { + if ( value.type == 'elementEnd' ) { + treewalker.jumpTo( value.nextPosition ); + } else { + const position = treewalker.position.clone(); + + position.offset = block.maxOffset; - if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block as any, visited, range ) ) { - yield block as Element; + if ( range.containsPosition( position ) ) { + treewalker.jumpTo( position ); + } } } } diff --git a/packages/ckeditor5-engine/src/model/treewalker.ts b/packages/ckeditor5-engine/src/model/treewalker.ts index 91b42ceb71c..dea573da004 100644 --- a/packages/ckeditor5-engine/src/model/treewalker.ts +++ b/packages/ckeditor5-engine/src/model/treewalker.ts @@ -183,6 +183,11 @@ export default class TreeWalker implements Iterable { } } + public jumpTo( position: Position ): void { + this._position = position; + this._visitedParent = position.parent; + } + /** * Gets the next tree walker's value. */ From 476e12d26a9880e9db956a492004971005e31b63 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Mon, 9 Dec 2024 11:04:29 +0100 Subject: [PATCH 07/12] Add tests and docs --- .../ckeditor5-engine/src/model/treewalker.ts | 5 +++++ .../ckeditor5-engine/tests/model/treewalker.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/ckeditor5-engine/src/model/treewalker.ts b/packages/ckeditor5-engine/src/model/treewalker.ts index dea573da004..f8503e7286f 100644 --- a/packages/ckeditor5-engine/src/model/treewalker.ts +++ b/packages/ckeditor5-engine/src/model/treewalker.ts @@ -183,6 +183,11 @@ export default class TreeWalker implements Iterable { } } + /** + * Moves {@link #position} to provided position. + * + * @param position Position to jump to + */ public jumpTo( position: Position ): void { this._position = position; this._visitedParent = position.parent; diff --git a/packages/ckeditor5-engine/tests/model/treewalker.js b/packages/ckeditor5-engine/tests/model/treewalker.js index ffe87a21111..1048d55a478 100644 --- a/packages/ckeditor5-engine/tests/model/treewalker.js +++ b/packages/ckeditor5-engine/tests/model/treewalker.js @@ -720,6 +720,24 @@ describe( 'TreeWalker', () => { } ); } ); } ); + + describe( 'jumpTo', () => { + it( 'should jump to the given position', () => { + const walker = new TreeWalker( { + startPosition: Position._createAt( paragraph, 0 ) + } ); + + walker.jumpTo( new Position( paragraph, [ 2 ] ) ); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + + walker.next(); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 3 ); + } ); + } ); } ); function expectValue( value, expected, options ) { From 97250c301ea049537615d9b43a7b4494a6b7b617 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 11 Dec 2024 15:37:49 +0100 Subject: [PATCH 08/12] Update `getSelectedBlocks` and `jumpTo` --- .../ckeditor5-engine/src/model/selection.ts | 20 +++--- .../ckeditor5-engine/src/model/treewalker.ts | 16 ++++- .../ckeditor5-engine/src/view/treewalker.ts | 21 ++++++ .../tests/model/treewalker.js | 46 +++++++++++++ .../ckeditor5-engine/tests/view/treewalker.js | 64 +++++++++++++++++++ 5 files changed, 153 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index 61c8430a518..c255edcfcd0 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -674,18 +674,14 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block as any, visited, range ) ) { yield block as Element; - } else if ( block.is( 'model:element' ) && block.root.document!.model.schema.isBlock( block ) ) { - if ( value.type == 'elementEnd' ) { - treewalker.jumpTo( value.nextPosition ); - } else { - const position = treewalker.position.clone(); - - position.offset = block.maxOffset; - - if ( range.containsPosition( position ) ) { - treewalker.jumpTo( position ); - } - } + } + // If element is block, we can skip its children and jump to the end of it. + else if ( + value.type == 'elementStart' && + block.is( 'model:element' ) && + block.root.document!.model.schema.isBlock( block ) + ) { + treewalker.jumpTo( Position._createAt( block, 'end' ) ); } } diff --git a/packages/ckeditor5-engine/src/model/treewalker.ts b/packages/ckeditor5-engine/src/model/treewalker.ts index f8503e7286f..082b5dc6523 100644 --- a/packages/ckeditor5-engine/src/model/treewalker.ts +++ b/packages/ckeditor5-engine/src/model/treewalker.ts @@ -184,11 +184,23 @@ export default class TreeWalker implements Iterable { } /** - * Moves {@link #position} to provided position. + * Moves treewalker {@link #position} to provided `position`. Treewalker will + * continue traversing from that position. * - * @param position Position to jump to + * If the provided position is before the start boundary, the position will be + * set to the start boundary. If the provided position is after the end boundary, + * the position will be set to the end boundary. + * This is done to prevent the treewalker from traversing outside the boundaries. + * + * @param position Position to jump to. */ public jumpTo( position: Position ): void { + if ( this._boundaryStartParent && position.isBefore( this.boundaries!.start ) ) { + position = this.boundaries!.start; + } else if ( this._boundaryEndParent && position.isAfter( this.boundaries!.end ) ) { + position = this.boundaries!.end; + } + this._position = position; this._visitedParent = position.parent; } diff --git a/packages/ckeditor5-engine/src/view/treewalker.ts b/packages/ckeditor5-engine/src/view/treewalker.ts index 719990bd7ca..08c00579002 100644 --- a/packages/ckeditor5-engine/src/view/treewalker.ts +++ b/packages/ckeditor5-engine/src/view/treewalker.ts @@ -159,6 +159,27 @@ export default class TreeWalker implements IterableIterator { } } + /** + * Moves treewalker {@link #position} to provided `position`. Treewalker will + * continue traversing from that position. + * + * If the provided position is before the start boundary, the position will be + * set to the start boundary. If the provided position is after the end boundary, + * the position will be set to the end boundary. + * This is done to prevent the treewalker from traversing outside the boundaries. + * + * @param position Position to jump to. + */ + public jumpTo( position: Position ): void { + if ( this._boundaryStartParent && position.isBefore( this.boundaries!.start ) ) { + position = this.boundaries!.start; + } else if ( this._boundaryEndParent && position.isAfter( this.boundaries!.end ) ) { + position = this.boundaries!.end; + } + + this._position = position; + } + /** * Gets the next tree walker's value. * diff --git a/packages/ckeditor5-engine/tests/model/treewalker.js b/packages/ckeditor5-engine/tests/model/treewalker.js index 1048d55a478..071b71c513b 100644 --- a/packages/ckeditor5-engine/tests/model/treewalker.js +++ b/packages/ckeditor5-engine/tests/model/treewalker.js @@ -737,6 +737,52 @@ describe( 'TreeWalker', () => { expect( walker.position.parent ).to.equal( paragraph ); expect( walker.position.offset ).to.equal( 3 ); } ); + + it( 'cannot move position before the #_boundaryStartParent', () => { + const range = new Range( + new Position( paragraph, [ 2 ] ), + new Position( paragraph, [ 4 ] ) + ); + const walker = new TreeWalker( { + boundaries: range + } ); + + const positionBeforeAllowedRange = new Position( paragraph, [ 0 ] ); + + walker.jumpTo( positionBeforeAllowedRange ); + + // `jumpTo()` autocorrected the position to the first allowed position. + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + + walker.next(); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 3 ); + } ); + + it( 'cannot move position after the #_boundaryStartParent', () => { + const range = new Range( + new Position( paragraph, [ 0 ] ), + new Position( paragraph, [ 2 ] ) + ); + const walker = new TreeWalker( { + boundaries: range + } ); + + const positionAfterAllowedRange = new Position( paragraph, [ 4 ] ); + + // `jumpTo()` autocorrected the position to the last allowed position. + walker.jumpTo( positionAfterAllowedRange ); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + + walker.next(); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + } ); } ); } ); diff --git a/packages/ckeditor5-engine/tests/view/treewalker.js b/packages/ckeditor5-engine/tests/view/treewalker.js index 1687ae9dc19..4a6ba3a8b27 100644 --- a/packages/ckeditor5-engine/tests/view/treewalker.js +++ b/packages/ckeditor5-engine/tests/view/treewalker.js @@ -1216,6 +1216,70 @@ describe( 'TreeWalker', () => { } ); } ); } ); + + describe( 'jumpTo', () => { + it( 'should jump to the given position', () => { + const walker = new TreeWalker( { + startPosition: Position._createAt( paragraph, 0 ) + } ); + + walker.jumpTo( new Position( paragraph, 2 ) ); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + + walker.next(); + + expect( walker.position.parent ).to.equal( img2 ); + expect( walker.position.offset ).to.equal( 0 ); + } ); + + it( 'cannot move position before the #_boundaryStartParent', () => { + const range = new Range( + new Position( paragraph, 2 ), + new Position( paragraph, 4 ) + ); + const walker = new TreeWalker( { + boundaries: range + } ); + + const positionBeforeAllowedRange = new Position( paragraph, 0 ); + + walker.jumpTo( positionBeforeAllowedRange ); + + // `jumpTo()` autocorrected the position to the first allowed position. + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + + walker.next(); + + expect( walker.position.parent ).to.equal( img2 ); + expect( walker.position.offset ).to.equal( 0 ); + } ); + + it( 'cannot move position after the #_boundaryStartParent', () => { + const range = new Range( + new Position( paragraph, 0 ), + new Position( paragraph, 2 ) + ); + const walker = new TreeWalker( { + boundaries: range + } ); + + const positionAfterAllowedRange = new Position( paragraph, 4 ); + + // `jumpTo()` autocorrected the position to the last allowed position. + walker.jumpTo( positionAfterAllowedRange ); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + + walker.next(); + + expect( walker.position.parent ).to.equal( paragraph ); + expect( walker.position.offset ).to.equal( 2 ); + } ); + } ); } ); function expectValue( value, expected, options = {} ) { From 4883755fc0e6408c3b39d2f733233fac43928831 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 11 Dec 2024 15:47:54 +0100 Subject: [PATCH 09/12] Fix typos --- packages/ckeditor5-engine/tests/model/treewalker.js | 2 +- packages/ckeditor5-engine/tests/view/treewalker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-engine/tests/model/treewalker.js b/packages/ckeditor5-engine/tests/model/treewalker.js index 071b71c513b..534e1935995 100644 --- a/packages/ckeditor5-engine/tests/model/treewalker.js +++ b/packages/ckeditor5-engine/tests/model/treewalker.js @@ -761,7 +761,7 @@ describe( 'TreeWalker', () => { expect( walker.position.offset ).to.equal( 3 ); } ); - it( 'cannot move position after the #_boundaryStartParent', () => { + it( 'cannot move position after the #_boundaryEndParent', () => { const range = new Range( new Position( paragraph, [ 0 ] ), new Position( paragraph, [ 2 ] ) diff --git a/packages/ckeditor5-engine/tests/view/treewalker.js b/packages/ckeditor5-engine/tests/view/treewalker.js index 4a6ba3a8b27..34ef0176910 100644 --- a/packages/ckeditor5-engine/tests/view/treewalker.js +++ b/packages/ckeditor5-engine/tests/view/treewalker.js @@ -1257,7 +1257,7 @@ describe( 'TreeWalker', () => { expect( walker.position.offset ).to.equal( 0 ); } ); - it( 'cannot move position after the #_boundaryStartParent', () => { + it( 'cannot move position after the #_boundaryEndParent', () => { const range = new Range( new Position( paragraph, 0 ), new Position( paragraph, 2 ) From dfff427e4814c41e245794fd49a7404773143f36 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 11 Dec 2024 17:06:35 +0100 Subject: [PATCH 10/12] Avoid cloning the `Position` object, since it's not necessary --- packages/ckeditor5-engine/src/model/selection.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index c255edcfcd0..ab449a936fe 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -681,7 +681,9 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab block.is( 'model:element' ) && block.root.document!.model.schema.isBlock( block ) ) { - treewalker.jumpTo( Position._createAt( block, 'end' ) ); + treewalker.position.offset = block.maxOffset; + + treewalker.jumpTo( treewalker.position ); } } From 05d54128c15f5f919a0fa8e726f39cd59b3b8410 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Mon, 16 Dec 2024 16:48:52 +0100 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Szymon Cofalik --- packages/ckeditor5-engine/src/model/treewalker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-engine/src/model/treewalker.ts b/packages/ckeditor5-engine/src/model/treewalker.ts index 082b5dc6523..b4eed459c07 100644 --- a/packages/ckeditor5-engine/src/model/treewalker.ts +++ b/packages/ckeditor5-engine/src/model/treewalker.ts @@ -184,9 +184,13 @@ export default class TreeWalker implements Iterable { } /** - * Moves treewalker {@link #position} to provided `position`. Treewalker will + * Moves tree walker {@link #position} to provided `position`. Tree walker will * continue traversing from that position. * + * Note: in contrary to {@link ~TreeWalker#skip}, this method does not iterate over the nodes along the way. + * It simply sets the current tree walker position to a new one. + * From the performance standpoint, it is better to use {@link ~TreeWalker#jumpTo} rather than {@link ~TreeWalker#skip}. + * * If the provided position is before the start boundary, the position will be * set to the start boundary. If the provided position is after the end boundary, * the position will be set to the end boundary. From 73f6109f714b2365c25b8bbd072944a22abbc1b4 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Mon, 16 Dec 2024 16:55:41 +0100 Subject: [PATCH 12/12] Apply suggestions --- packages/ckeditor5-engine/src/model/selection.ts | 4 +--- packages/ckeditor5-engine/src/model/treewalker.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-engine/src/model/selection.ts b/packages/ckeditor5-engine/src/model/selection.ts index ab449a936fe..c255edcfcd0 100644 --- a/packages/ckeditor5-engine/src/model/selection.ts +++ b/packages/ckeditor5-engine/src/model/selection.ts @@ -681,9 +681,7 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin( TypeCheckab block.is( 'model:element' ) && block.root.document!.model.schema.isBlock( block ) ) { - treewalker.position.offset = block.maxOffset; - - treewalker.jumpTo( treewalker.position ); + treewalker.jumpTo( Position._createAt( block, 'end' ) ); } } diff --git a/packages/ckeditor5-engine/src/model/treewalker.ts b/packages/ckeditor5-engine/src/model/treewalker.ts index b4eed459c07..10986666348 100644 --- a/packages/ckeditor5-engine/src/model/treewalker.ts +++ b/packages/ckeditor5-engine/src/model/treewalker.ts @@ -205,7 +205,7 @@ export default class TreeWalker implements Iterable { position = this.boundaries!.end; } - this._position = position; + this._position = position.clone(); this._visitedParent = position.parent; }