keystrokehandler.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module utils/keystrokehandler
  7. */
  8. import DomEmitterMixin from './dom/emittermixin';
  9. import { getCode, parseKeystroke } from './keyboard';
  10. /**
  11. * Keystroke handler allows registering callbacks for given keystrokes.
  12. *
  13. * The most frequent use of this class is through the {@link module:core/editor/editor~Editor#keystrokes `editor.keystrokes`}
  14. * property. It allows listening to keystrokes executed in the editing view:
  15. *
  16. * editor.keystrokes.set( 'Ctrl+A', ( keyEvtData, cancel ) => {
  17. * console.log( 'Ctrl+A has been pressed' );
  18. * cancel();
  19. * } );
  20. *
  21. * However, this utility class can be used in various part of the UI. For instance, a certain {@link module:ui/view~View}
  22. * can use it like this:
  23. *
  24. * class MyView extends View {
  25. * constructor() {
  26. * this.keystrokes = new KeystrokeHandler();
  27. *
  28. * this.keystrokes.set( 'tab', handleTabKey );
  29. * }
  30. *
  31. * render() {
  32. * super.render();
  33. *
  34. * this.keystrokes.listenTo( this.element );
  35. * }
  36. * }
  37. *
  38. * That keystroke handler will listen to `keydown` events fired in this view's main element.
  39. *
  40. */
  41. export default class KeystrokeHandler {
  42. /**
  43. * Creates an instance of the keystroke handler.
  44. */
  45. constructor() {
  46. /**
  47. * Listener used to listen to events for easier keystroke handler destruction.
  48. *
  49. * @protected
  50. * @member {module:utils/dom/emittermixin~Emitter}
  51. */
  52. this._listener = Object.create( DomEmitterMixin );
  53. }
  54. /**
  55. * Starts listening for `keydown` events from a given emitter.
  56. *
  57. * @param {module:utils/emittermixin~Emitter} emitter
  58. */
  59. listenTo( emitter ) {
  60. // The #_listener works here as a kind of dispatcher. It groups the events coming from the same
  61. // keystroke so the listeners can be attached to them with different priorities.
  62. //
  63. // E.g. all the keystrokes with the `keyCode` of 42 coming from the `emitter` are propagated
  64. // as a `_keydown:42` event by the `_listener`. If there's a callback created by the `set`
  65. // method for this 42 keystroke, it listens to the `_listener#_keydown:42` event only and interacts
  66. // only with other listeners of this particular event, thus making it possible to prioritize
  67. // the listeners and safely cancel execution, when needed. Instead of duplicating the Emitter logic,
  68. // the KeystrokeHandler re–uses it to do its job.
  69. this._listener.listenTo( emitter, 'keydown', ( evt, keyEvtData ) => {
  70. this._listener.fire( '_keydown:' + getCode( keyEvtData ), keyEvtData );
  71. } );
  72. }
  73. /**
  74. * Registers a handler for the specified keystroke.
  75. *
  76. * @param {String|Array.<String|Number>} keystroke Keystroke defined in a format accepted by
  77. * the {@link module:utils/keyboard~parseKeystroke} function.
  78. * @param {Function} callback A function called with the
  79. * {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and
  80. * a helper funcion to call both `preventDefault()` and `stopPropagation()` on the underlying event.
  81. * @param {Object} [options={}] Additional options.
  82. * @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of the keystroke
  83. * callback. The higher the priority value the sooner the callback will be executed. Keystrokes having the same priority
  84. * are called in the order they were added.
  85. */
  86. set( keystroke, callback, options = {} ) {
  87. const keyCode = parseKeystroke( keystroke );
  88. const priority = options.priority;
  89. // Execute the passed callback on KeystrokeHandler#_keydown.
  90. // TODO: https://github.com/ckeditor/ckeditor5-utils/issues/144
  91. this._listener.listenTo( this._listener, '_keydown:' + keyCode, ( evt, keyEvtData ) => {
  92. callback( keyEvtData, () => {
  93. // Stop the event in the DOM: no listener in the web page
  94. // will be triggered by this event.
  95. keyEvtData.preventDefault();
  96. keyEvtData.stopPropagation();
  97. // Stop the event in the KeystrokeHandler: no more callbacks
  98. // will be executed for this keystroke.
  99. evt.stop();
  100. } );
  101. // Mark this keystroke as handled by the callback. See: #press.
  102. evt.return = true;
  103. }, { priority } );
  104. }
  105. /**
  106. * Triggers a keystroke handler for a specified key combination, if such a keystroke was {@link #set defined}.
  107. *
  108. * @param {module:engine/view/observer/keyobserver~KeyEventData} keyEvtData Key event data.
  109. * @returns {Boolean} Whether the keystroke was handled.
  110. */
  111. press( keyEvtData ) {
  112. return !!this._listener.fire( '_keydown:' + getCode( keyEvtData ), keyEvtData );
  113. }
  114. /**
  115. * Destroys the keystroke handler.
  116. */
  117. destroy() {
  118. this._listener.stopListening();
  119. }
  120. }