8
0

focustracker.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global setTimeout, clearTimeout */
  6. import DOMEmitterMixin from '../ui/domemittermixin.js';
  7. import ObservableMixin from './observablemixin.js';
  8. import CKEditorError from './ckeditorerror.js';
  9. import mix from './mix.js';
  10. /**
  11. * Allows observing a group of `HTMLElement`s whether at least one of them is focused.
  12. *
  13. * Used by the {@link core.Editor} in order to track whether the focus is still within the application,
  14. * or were used outside of its UI.
  15. *
  16. * **Note** `focus` and `blur` listeners use event capturing, so it is only needed to register wrapper `HTMLElement`
  17. * which contain other `focusable` elements. But note that this wrapper element has to be focusable too
  18. * (have e.g. `tabindex="-1"`).
  19. *
  20. * @memberOf utils
  21. * @mixes utils.DOMEmitterMixin
  22. * @mixes utils.ObservableMixin
  23. */
  24. export default class FocusTracker {
  25. constructor() {
  26. /**
  27. * True when one of the registered elements is focused.
  28. *
  29. * @readonly
  30. * @observable
  31. * @member {Boolean} utils.FocusTracker#isFocused
  32. */
  33. this.set( 'isFocused', false );
  34. /**
  35. * List of registered elements.
  36. *
  37. * @private
  38. * @member {Set<HTMLElement>} utils.FocusTracker#_elements
  39. */
  40. this._elements = new Set();
  41. /**
  42. * Event loop timeout.
  43. *
  44. * @private
  45. * @member {Number} utils.FocusTracker#_nextEventLoopTimeout
  46. */
  47. this._nextEventLoopTimeout = null;
  48. /**
  49. * Currently focused element.
  50. *
  51. * @private
  52. * @member {HTMLElement} utils.FocusTracker#_focusedElement
  53. */
  54. this._focusedElement = null;
  55. }
  56. /**
  57. * Starts tracking the specified element.
  58. *
  59. * @param {HTMLElement} element
  60. */
  61. add( element ) {
  62. if ( this._elements.has( element ) ) {
  63. throw new CKEditorError( 'focusTracker-add-element-already-exist' );
  64. }
  65. this.listenTo( element, 'focus', () => this._focus( element ), { useCapture: true } );
  66. this.listenTo( element, 'blur', () => this._blur(), { useCapture: true } );
  67. this._elements.add( element );
  68. }
  69. /**
  70. * Stops tracking the specified element and stops listening on this element.
  71. *
  72. * @param {HTMLElement} element
  73. */
  74. remove( element ) {
  75. if ( element === this._focusedElement ) {
  76. this._blur( element );
  77. }
  78. if ( this._elements.has( element ) ) {
  79. this.stopListening( element );
  80. this._elements.delete( element );
  81. }
  82. }
  83. /**
  84. * Stores currently focused element and set {utils.FocusTracker#isFocused} as `true`.
  85. *
  86. * @private
  87. * @param {HTMLElement} element Element which has been focused.
  88. */
  89. _focus( element ) {
  90. clearTimeout( this._nextEventLoopTimeout );
  91. this._focusedElement = element;
  92. this.isFocused = true;
  93. }
  94. /**
  95. * Clears currently focused element and set {utils.FocusTracker#isFocused} as `false`.
  96. * This method uses `setTimeout` to change order of fires `blur` and `focus` events.
  97. *
  98. * @private
  99. * @fires utils.FocusTracker#blur
  100. */
  101. _blur() {
  102. this._nextEventLoopTimeout = setTimeout( () => {
  103. this._focusedElement = null;
  104. this.isFocused = false;
  105. }, 0 );
  106. }
  107. }
  108. mix( FocusTracker, DOMEmitterMixin );
  109. mix( FocusTracker, ObservableMixin );