浏览代码

Merge pull request #250 from ckeditor/t/ckeditor5-ui/431

Other: Made FocusTracker asynchronous again. Closes ckeditor/ckeditor5-ui#431.
Piotr Jasiun 7 年之前
父节点
当前提交
5adc615734
共有 2 个文件被更改,包括 49 次插入41 次删除
  1. 29 24
      packages/ckeditor5-utils/src/focustracker.js
  2. 20 17
      packages/ckeditor5-utils/tests/focustracker.js

+ 29 - 24
packages/ckeditor5-utils/src/focustracker.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* global setTimeout, clearTimeout */
+
 /**
  * @module utils/focustracker
  */
@@ -51,6 +53,14 @@ export default class FocusTracker {
 		 * @member {Set.<HTMLElement>}
 		 */
 		this._elements = new Set();
+
+		/**
+		 * Event loop timeout.
+		 *
+		 * @private
+		 * @member {Number}
+		 */
+		this._nextEventLoopTimeout = null;
 	}
 
 	/**
@@ -64,7 +74,7 @@ export default class FocusTracker {
 		}
 
 		this.listenTo( element, 'focus', () => this._focus( element ), { useCapture: true } );
-		this.listenTo( element, 'blur', ( evt, domEvt ) => this._blur( domEvt ), { useCapture: true } );
+		this.listenTo( element, 'blur', () => this._blur(), { useCapture: true } );
 		this._elements.add( element );
 	}
 
@@ -91,40 +101,35 @@ export default class FocusTracker {
 	 * @param {HTMLElement} element Element which has been focused.
 	 */
 	_focus( element ) {
+		clearTimeout( this._nextEventLoopTimeout );
+
 		this.focusedElement = element;
 		this.isFocused = true;
 	}
 
 	/**
 	 * Clears currently focused element and set {@link #isFocused} as `false`.
+	 * This method uses `setTimeout` to change order of fires `blur` and `focus` events.
 	 *
 	 * @private
-	 * @param {FocusEvent} domEvt The native DOM FocusEvent instance.
+	 * @fires blur
 	 */
-	_blur( domEvt ) {
-		const relatedTarget = domEvt.relatedTarget;
-		const isInnerBlur = Array.from( this._elements ).some( element => {
-			// element.contains( element ) -> true
-			return element.contains( relatedTarget );
-		} );
-
-		// If the blur was caused by focusing an element which is either:
-		//
-		// * registered in this focus tracker,
-		// * a child of an element registered in this focus tracker
-		//
-		// then don't fire the event to prevent rapid #isFocused changes. The focus remains within
-		// the registered elements; announcing this change is pointless.
-		//
-		// Note: In DOM, the native blur always precedes the following focus.
-		// https://github.com/ckeditor/ckeditor5-utils/issues/245
-		if ( isInnerBlur ) {
-			return;
-		}
+	_blur() {
+		clearTimeout( this._nextEventLoopTimeout );
 
-		this.focusedElement = null;
-		this.isFocused = false;
+		this._nextEventLoopTimeout = setTimeout( () => {
+			this.focusedElement = null;
+			this.isFocused = false;
+		}, 0 );
 	}
+
+	/**
+	 * @event focus
+	 */
+
+	/**
+	 * @event blur
+	 */
 }
 
 mix( FocusTracker, DomEmitterMixin );

+ 20 - 17
packages/ckeditor5-utils/tests/focustracker.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md.
  */
 
-/* global document, FocusEvent */
+/* global document, Event */
 
 import FocusTracker from '../src/focustracker';
 import CKEditorError from '../src/ckeditorerror';
@@ -60,7 +60,7 @@ describe( 'FocusTracker', () => {
 
 				expect( focusTracker.isFocused ).to.false;
 
-				containerFirstInput.dispatchEvent( new FocusEvent( 'focus' ) );
+				containerFirstInput.dispatchEvent( new Event( 'focus' ) );
 
 				expect( focusTracker.isFocused ).to.true;
 			} );
@@ -69,7 +69,8 @@ describe( 'FocusTracker', () => {
 				focusTracker.add( containerFirstInput );
 				focusTracker.isFocused = true;
 
-				containerFirstInput.dispatchEvent( new FocusEvent( 'blur' ) );
+				containerFirstInput.dispatchEvent( new Event( 'blur' ) );
+				testUtils.sinon.clock.tick( 0 );
 
 				expect( focusTracker.isFocused ).to.false;
 			} );
@@ -81,7 +82,7 @@ describe( 'FocusTracker', () => {
 
 				expect( focusTracker.isFocused ).to.false;
 
-				containerFirstInput.dispatchEvent( new FocusEvent( 'focus' ) );
+				containerFirstInput.dispatchEvent( new Event( 'focus' ) );
 
 				expect( focusTracker.isFocused ).to.true;
 			} );
@@ -90,7 +91,8 @@ describe( 'FocusTracker', () => {
 				focusTracker.add( container );
 				focusTracker.isFocused = true;
 
-				containerFirstInput.dispatchEvent( new FocusEvent( 'blur' ) );
+				containerFirstInput.dispatchEvent( new Event( 'blur' ) );
+				testUtils.sinon.clock.tick( 0 );
 
 				expect( focusTracker.isFocused ).to.false;
 			} );
@@ -100,17 +102,15 @@ describe( 'FocusTracker', () => {
 
 				focusTracker.add( container );
 
-				containerFirstInput.dispatchEvent( new FocusEvent( 'focus' ) );
+				containerFirstInput.dispatchEvent( new Event( 'focus' ) );
 
 				focusTracker.listenTo( focusTracker, 'change:isFocused', changeSpy );
 
 				expect( focusTracker.isFocused ).to.true;
 
-				containerFirstInput.dispatchEvent( new FocusEvent( 'blur', {
-					relatedTarget: containerSecondInput
-				} ) );
-
-				containerSecondInput.dispatchEvent( new FocusEvent( 'focus' ) );
+				containerFirstInput.dispatchEvent( new Event( 'blur' ) );
+				containerSecondInput.dispatchEvent( new Event( 'focus' ) );
+				testUtils.sinon.clock.tick( 0 );
 
 				expect( focusTracker.isFocused ).to.true;
 				expect( changeSpy.notCalled ).to.true;
@@ -121,9 +121,10 @@ describe( 'FocusTracker', () => {
 				focusTracker.add( container );
 				focusTracker.isFocused = true;
 
-				container.dispatchEvent( new FocusEvent( 'blur' ) );
-				containerFirstInput.dispatchEvent( new FocusEvent( 'blur' ) );
-				containerSecondInput.dispatchEvent( new FocusEvent( 'focus' ) );
+				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;
 			} );
@@ -141,7 +142,7 @@ describe( 'FocusTracker', () => {
 			focusTracker.add( containerFirstInput );
 			focusTracker.remove( containerFirstInput );
 
-			containerFirstInput.dispatchEvent( new FocusEvent( 'focus' ) );
+			containerFirstInput.dispatchEvent( new Event( 'focus' ) );
 
 			expect( focusTracker.isFocused ).to.false;
 		} );
@@ -151,18 +152,20 @@ describe( 'FocusTracker', () => {
 			focusTracker.remove( containerFirstInput );
 			focusTracker.isFocused = true;
 
-			containerFirstInput.dispatchEvent( new FocusEvent( 'blur' ) );
+			containerFirstInput.dispatchEvent( new Event( 'blur' ) );
+			testUtils.sinon.clock.tick( 0 );
 
 			expect( focusTracker.isFocused ).to.true;
 		} );
 
 		it( 'should blur element before removing when is focused', () => {
 			focusTracker.add( containerFirstInput );
-			containerFirstInput.dispatchEvent( new FocusEvent( 'focus' ) );
+			containerFirstInput.dispatchEvent( new Event( 'focus' ) );
 
 			expect( focusTracker.isFocused ).to.true;
 
 			focusTracker.remove( containerFirstInput );
+			testUtils.sinon.clock.tick( 0 );
 
 			expect( focusTracker.isFocused ).to.false;
 		} );