keyboard.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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. * Set of utils related to keyboard support.
  7. *
  8. * @module utils/keyboard
  9. */
  10. import CKEditorError from './ckeditorerror';
  11. import env from './env';
  12. const macGlyphsToModifiers = {
  13. '⌘': 'ctrl',
  14. '⇧': 'shift',
  15. '⌥': 'alt'
  16. };
  17. const modifiersToMacGlyphs = {
  18. 'ctrl': '⌘',
  19. 'shift': '⇧',
  20. 'alt': '⌥'
  21. };
  22. /**
  23. * Object with `keyName => keyCode` pairs for a set of known keys.
  24. *
  25. * Contains:
  26. *
  27. * * `a-z`,
  28. * * `0-9`,
  29. * * `f1-f12`,
  30. * * `arrow(left|up|right|bottom)`,
  31. * * `backspace`, `delete`, `enter`, `esc`, `tab`,
  32. * * `ctrl`, `cmd`, `shift`, `alt`.
  33. */
  34. export const keyCodes = generateKnownKeyCodes();
  35. /**
  36. * Converts a key name or a {@link module:utils/keyboard~KeystrokeInfo keystroke info} into a key code.
  37. *
  38. * Note: Key names are matched with {@link module:utils/keyboard~keyCodes} in a case-insensitive way.
  39. *
  40. * @param {String|module:utils/keyboard~KeystrokeInfo} Key name (see {@link module:utils/keyboard~keyCodes})
  41. * or a keystroke data object.
  42. * @returns {Number} Key or keystroke code.
  43. */
  44. export function getCode( key ) {
  45. let keyCode;
  46. if ( typeof key == 'string' ) {
  47. keyCode = keyCodes[ key.toLowerCase() ];
  48. if ( !keyCode ) {
  49. /**
  50. * Unknown key name. Only key names contained by the {@link module:utils/keyboard~keyCodes} can be used.
  51. *
  52. * @errror keyboard-unknown-key
  53. * @param {String} key
  54. */
  55. throw new CKEditorError(
  56. 'keyboard-unknown-key',
  57. null, { key }
  58. );
  59. }
  60. } else {
  61. keyCode = key.keyCode +
  62. ( key.altKey ? keyCodes.alt : 0 ) +
  63. ( key.ctrlKey ? keyCodes.ctrl : 0 ) +
  64. ( key.shiftKey ? keyCodes.shift : 0 );
  65. }
  66. return keyCode;
  67. }
  68. /**
  69. * Parses keystroke and returns a keystroke code that will match the code returned by
  70. * link {@link module:utils/keyboard~getCode} for a corresponding {@link module:utils/keyboard~KeystrokeInfo keystroke info}.
  71. *
  72. * The keystroke can be passed in two formats:
  73. *
  74. * * as a single string – e.g. `ctrl + A`,
  75. * * as an array of {@link module:utils/keyboard~keyCodes known key names} and key codes – e.g.:
  76. * * `[ 'ctrl', 32 ]` (ctrl + space),
  77. * * `[ 'ctrl', 'a' ]` (ctrl + A).
  78. *
  79. * Note: Key names are matched with {@link module:utils/keyboard~keyCodes} in a case-insensitive way.
  80. *
  81. * Note: Only keystrokes with a single non-modifier key are supported (e.g. `ctrl+A` is OK, but `ctrl+A+B` is not).
  82. *
  83. * @param {String|Array.<Number|String>} keystroke Keystroke definition.
  84. * @returns {Number} Keystroke code.
  85. */
  86. export function parseKeystroke( keystroke ) {
  87. if ( typeof keystroke == 'string' ) {
  88. keystroke = splitKeystrokeText( keystroke );
  89. }
  90. return keystroke
  91. .map( key => ( typeof key == 'string' ) ? getCode( key ) : key )
  92. .reduce( ( key, sum ) => sum + key, 0 );
  93. }
  94. /**
  95. * It translates any keystroke string text like `"CTRL+A"` to an
  96. * environment–specific keystroke, i.e. `"⌘A"` on Mac OSX.
  97. *
  98. * @param {String} keystroke Keystroke text.
  99. * @returns {String} Keystroke text specific for the environment.
  100. */
  101. export function getEnvKeystrokeText( keystroke ) {
  102. if ( !env.isMac ) {
  103. return keystroke;
  104. }
  105. return splitKeystrokeText( keystroke )
  106. // Replace modifiers (e.g. "ctrl") with Mac glyphs (e.g. "⌘") first.
  107. .map( key => modifiersToMacGlyphs[ key.toLowerCase() ] || key )
  108. // Decide whether to put "+" between keys in the keystroke or not.
  109. .reduce( ( value, key ) => {
  110. if ( value.slice( -1 ) in macGlyphsToModifiers ) {
  111. return value + key;
  112. } else {
  113. return value + '+' + key;
  114. }
  115. } );
  116. }
  117. /**
  118. * Returns `true` if the provided key code represents one of the arrow keys.
  119. *
  120. * @param {Number} keyCode A key code as in {@link module:utils/keyboard~KeystrokeInfo#keyCode}.
  121. * @returns {Boolean}
  122. */
  123. export function isArrowKeyCode( keyCode ) {
  124. return keyCode == keyCodes.arrowright ||
  125. keyCode == keyCodes.arrowleft ||
  126. keyCode == keyCodes.arrowup ||
  127. keyCode == keyCodes.arrowdown;
  128. }
  129. /**
  130. * Returns the direction in which the {@link module:engine/model/documentselection~DocumentSelection selection}
  131. * will move when a provided arrow key code is pressed considering the language direction of the editor content.
  132. *
  133. * For instance, in right–to–left (RTL) content languages, pressing the left arrow means moving selection right (forward)
  134. * in the model structure. Similarly, pressing the right arrow moves the selection left (backward).
  135. *
  136. * @param {Number} keyCode A key code as in {@link module:utils/keyboard~KeystrokeInfo#keyCode}.
  137. * @param {'ltr'|'rtl'} contentLanguageDirection The content language direction, corresponding to
  138. * {@link module:utils/locale~Locale#contentLanguageDirection}.
  139. * @returns {'left'|'up'|'right'|'down'} Localized arrow direction.
  140. */
  141. export function getLocalizedArrowKeyCodeDirection( keyCode, contentLanguageDirection ) {
  142. const isLtrContent = contentLanguageDirection === 'ltr';
  143. switch ( keyCode ) {
  144. case keyCodes.arrowleft:
  145. return isLtrContent ? 'left' : 'right';
  146. case keyCodes.arrowright:
  147. return isLtrContent ? 'right' : 'left';
  148. case keyCodes.arrowup:
  149. return 'up';
  150. case keyCodes.arrowdown:
  151. return 'down';
  152. }
  153. }
  154. /**
  155. * Determines if the provided key code moves the {@link module:engine/model/documentselection~DocumentSelection selection}
  156. * forward or backward considering the language direction of the editor content.
  157. *
  158. * For instance, in right–to–left (RTL) languages, pressing the left arrow means moving forward
  159. * in the model structure. Similarly, pressing the right arrow moves the selection backward.
  160. *
  161. * @param {Number} keyCode A key code as in {@link module:utils/keyboard~KeystrokeInfo#keyCode}.
  162. * @param {'ltr'|'rtl'} contentLanguageDirection The content language direction, corresponding to
  163. * {@link module:utils/locale~Locale#contentLanguageDirection}.
  164. * @returns {Boolean}
  165. */
  166. export function isForwardArrowKeyCode( keyCode, contentLanguageDirection ) {
  167. const localizedKeyCodeDirection = getLocalizedArrowKeyCodeDirection( keyCode, contentLanguageDirection );
  168. return localizedKeyCodeDirection === 'down' || localizedKeyCodeDirection === 'right';
  169. }
  170. function generateKnownKeyCodes() {
  171. const keyCodes = {
  172. arrowleft: 37,
  173. arrowup: 38,
  174. arrowright: 39,
  175. arrowdown: 40,
  176. backspace: 8,
  177. delete: 46,
  178. enter: 13,
  179. space: 32,
  180. esc: 27,
  181. tab: 9,
  182. // The idea about these numbers is that they do not collide with any real key codes, so we can use them
  183. // like bit masks.
  184. ctrl: 0x110000,
  185. // Has the same code as ctrl, because their behaviour should be unified across the editor.
  186. // See http://ckeditor.github.io/editor-recommendations/general-policies#ctrl-vs-cmd
  187. cmd: 0x110000,
  188. shift: 0x220000,
  189. alt: 0x440000
  190. };
  191. // a-z
  192. for ( let code = 65; code <= 90; code++ ) {
  193. const letter = String.fromCharCode( code );
  194. keyCodes[ letter.toLowerCase() ] = code;
  195. }
  196. // 0-9
  197. for ( let code = 48; code <= 57; code++ ) {
  198. keyCodes[ code - 48 ] = code;
  199. }
  200. // F1-F12
  201. for ( let code = 112; code <= 123; code++ ) {
  202. keyCodes[ 'f' + ( code - 111 ) ] = code;
  203. }
  204. return keyCodes;
  205. }
  206. function splitKeystrokeText( keystroke ) {
  207. return keystroke.split( /\s*\+\s*/ );
  208. }
  209. /**
  210. * Information about a keystroke.
  211. *
  212. * @interface module:utils/keyboard~KeystrokeInfo
  213. */
  214. /**
  215. * The [key code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode).
  216. *
  217. * @member {Number} module:utils/keyboard~KeystrokeInfo#keyCode
  218. */
  219. /**
  220. * Whether the <kbd>Alt</kbd> modifier was pressed.
  221. *
  222. * @member {Bolean} module:utils/keyboard~KeystrokeInfo#altKey
  223. */
  224. /**
  225. * Whether the <kbd>Ctrl</kbd> or <kbd>Cmd</kbd> modifier was pressed.
  226. *
  227. * @member {Bolean} module:utils/keyboard~KeystrokeInfo#ctrlKey
  228. */
  229. /**
  230. * Whether the <kbd>Shift</kbd> modifier was pressed.
  231. *
  232. * @member {Bolean} module:utils/keyboard~KeystrokeInfo#shiftKey
  233. */