Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #276 from ckeditor/t/ckeditor5/1599
Browse files Browse the repository at this point in the history
Feature: Made `FocusTracker#focusedElement` observable to bring support for multi-root editors (see ckeditor/ckeditor5#1599).
  • Loading branch information
jodator authored Mar 26, 2019
2 parents 78107a4 + dbce45e commit 952d440
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 8 deletions.
11 changes: 8 additions & 3 deletions src/focustracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ export default class FocusTracker {
this.set( 'isFocused', false );

/**
* Currently focused element.
* The currently focused element.
*
* While {@link #isFocused `isFocused`} remains `true`, the focus can
* move between different UI elements. This property tracks those
* elements and tells which one is currently focused.
*
* @readonly
* @member {HTMLElement}
* @observable
* @member {HTMLElement|null}
*/
this.focusedElement = null;
this.set( 'focusedElement', null );

/**
* List of registered elements.
Expand Down
42 changes: 37 additions & 5 deletions tests/focustracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import FocusTracker from '../src/focustracker';
import CKEditorError from '../src/ckeditorerror';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';

describe( 'FocusTracker', () => {
let focusTracker, container, containerFirstInput, containerSecondInput;
Expand Down Expand Up @@ -43,6 +44,22 @@ describe( 'FocusTracker', () => {
expect( observableSpy.calledOnce ).to.true;
} );
} );

describe( 'focusedElement', () => {
it( 'should be null at default', () => {
expect( focusTracker.focusedElement ).to.be.null;
} );

it( 'should be observable', () => {
const observableSpy = testUtils.sinon.spy();

focusTracker.listenTo( focusTracker, 'change:focusedElement', observableSpy );

focusTracker.focusedElement = global.document.body;

expect( observableSpy.calledOnce ).to.true;
} );
} );
} );

describe( 'add', () => {
Expand All @@ -63,16 +80,20 @@ describe( 'FocusTracker', () => {
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.isFocused ).to.true;
expect( focusTracker.focusedElement ).to.equal( containerFirstInput );
} );

it( 'should start listening on element blur and update `isFocused` property', () => {
focusTracker.add( containerFirstInput );
focusTracker.isFocused = true;
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.focusedElement ).to.equal( containerFirstInput );

containerFirstInput.dispatchEvent( new Event( 'blur' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );
} );

Expand All @@ -85,16 +106,20 @@ describe( 'FocusTracker', () => {
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.isFocused ).to.true;
expect( focusTracker.focusedElement ).to.equal( container );
} );

it( 'should start listening on element blur using event capturing and update `isFocused` property', () => {
focusTracker.add( container );
focusTracker.isFocused = true;
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.focusedElement ).to.equal( container );

containerFirstInput.dispatchEvent( new Event( 'blur' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );

it( 'should not change `isFocused` property when focus is going between child elements', () => {
Expand All @@ -103,30 +128,34 @@ describe( 'FocusTracker', () => {
focusTracker.add( container );

containerFirstInput.dispatchEvent( new Event( 'focus' ) );
expect( focusTracker.focusedElement ).to.equal( container );
expect( focusTracker.isFocused ).to.true;

focusTracker.listenTo( focusTracker, 'change:isFocused', changeSpy );

expect( focusTracker.isFocused ).to.true;

containerFirstInput.dispatchEvent( new Event( 'blur' ) );
containerSecondInput.dispatchEvent( new Event( 'focus' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.focusedElement ).to.equal( container );
expect( focusTracker.isFocused ).to.true;
expect( changeSpy.notCalled ).to.true;
} );

// https://github.com/ckeditor/ckeditor5-utils/issues/159
it( 'should keep `isFocused` synced when multiple blur events are followed by the focus', () => {
focusTracker.add( container );
focusTracker.isFocused = true;
container.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.focusedElement ).to.equal( container );

container.dispatchEvent( new Event( 'blur' ) );
containerFirstInput.dispatchEvent( new Event( 'blur' ) );
containerSecondInput.dispatchEvent( new Event( 'focus' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.be.true;
expect( focusTracker.focusedElement ).to.equal( container );
} );
} );
} );
Expand All @@ -145,6 +174,7 @@ describe( 'FocusTracker', () => {
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );

it( 'should stop listening on element blur', () => {
Expand All @@ -161,13 +191,15 @@ describe( 'FocusTracker', () => {
it( 'should blur element before removing when is focused', () => {
focusTracker.add( containerFirstInput );
containerFirstInput.dispatchEvent( new Event( 'focus' ) );
expect( focusTracker.focusedElement ).to.equal( containerFirstInput );

expect( focusTracker.isFocused ).to.true;

focusTracker.remove( containerFirstInput );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );
} );

Expand Down

0 comments on commit 952d440

Please sign in to comment.