From 7394596736c1853bc20c6e6f8745b87fe3a4b1fb Mon Sep 17 00:00:00 2001 From: Szymon Cofalik Date: Wed, 25 Mar 2020 15:20:27 +0100 Subject: [PATCH] Feature: Introduced `View#hasDomSelection`. --- src/view/observer/selectionobserver.js | 4 ++ src/view/view.js | 8 ++++ tests/view/view/view.js | 60 +++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/view/observer/selectionobserver.js b/src/view/observer/selectionobserver.js index 87ac9a6de..4b3193c77 100644 --- a/src/view/observer/selectionobserver.js +++ b/src/view/observer/selectionobserver.js @@ -143,9 +143,13 @@ export default class SelectionObserver extends Observer { // It means that the DOM selection is in some way incorrect. Ranges that were in the DOM selection could not be // converted to the view. This happens when the DOM selection was moved outside of the editable element. if ( newViewSelection.rangeCount == 0 ) { + this.view.hasDomSelection = false; + return; } + this.view.hasDomSelection = true; + if ( this.selection.isEqual( newViewSelection ) && this.domConverter.isDomSelectionCorrect( domSelection ) ) { return; } diff --git a/src/view/view.js b/src/view/view.js index 379092a32..15fdcb496 100644 --- a/src/view/view.js +++ b/src/view/view.js @@ -100,6 +100,14 @@ export default class View { */ this.set( 'isRenderingInProgress', false ); + /** + * Informs whether the DOM selection is inside any of the DOM roots managed by the view. + * + * @readonly + * @member {Boolean} #hasDomSelection + */ + this.set( 'hasDomSelection', false ); + /** * Instance of the {@link module:engine/view/renderer~Renderer renderer}. * diff --git a/tests/view/view/view.js b/tests/view/view/view.js index c2a28d534..85ce1e2c9 100644 --- a/tests/view/view/view.js +++ b/tests/view/view/view.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -/* globals document, console */ +/* globals document, console, setTimeout */ import View from '../../../src/view/view'; import Observer from '../../../src/view/observer/observer'; @@ -514,6 +514,64 @@ describe( 'view', () => { } ); } ); + describe( 'hasDomSelection', () => { + let domElement, domP, domSelection; + + beforeEach( () => { + const viewRoot = createViewRoot( viewDocument, 'div', 'main' ); + + view.attachDomRoot( domRoot ); + + viewRoot._appendChild( new ViewElement( viewDocument, 'p' ) ); + view.forceRender(); + + domElement = createElement( document, 'div', { contenteditable: 'true' } ); + document.body.appendChild( domElement ); + + domSelection = document.getSelection(); + domP = domRoot.childNodes[ 0 ]; + } ); + + afterEach( () => { + domElement.remove(); + } ); + + it( 'should be true if selection is inside a DOM root element', done => { + domSelection.collapse( domP, 0 ); + + // Wait for async selectionchange event on DOM document. + setTimeout( () => { + expect( view.hasDomSelection ).to.be.true; + + done(); + }, 100 ); + } ); + + it( 'should be true if selection is inside a DOM root element - no focus', done => { + domSelection.collapse( domP, 0 ); + domRoot.blur(); + + // Wait for async selectionchange event on DOM document. + setTimeout( () => { + expect( view.hasDomSelection ).to.be.true; + expect( view.document.isFocused ).to.be.false; + + done(); + }, 100 ); + } ); + + it( 'should be false if selection is outside DOM root element', done => { + domSelection.collapse( domElement, 0 ); + + // Wait for async selectionchange event on DOM document. + setTimeout( () => { + expect( view.hasDomSelection ).to.be.false; + + done(); + }, 100 ); + } ); + } ); + describe( 'forceRender()', () => { it( 'disable observers, renders and enable observers', () => { const observerMock = view.addObserver( ObserverMock );