You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

225 lines
6.2 KiB

  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow strict-local
  9. * @emails oncall+draft_js
  10. */
  11. 'use strict';
  12. import type DraftEditor from "./DraftEditor.react";
  13. import type { DraftEditorCommand } from "./DraftEditorCommand";
  14. const DraftModifier = require("./DraftModifier");
  15. const EditorState = require("./EditorState");
  16. const KeyBindingUtil = require("./KeyBindingUtil");
  17. const Keys = require("fbjs/lib/Keys");
  18. const SecondaryClipboard = require("./SecondaryClipboard");
  19. const UserAgent = require("fbjs/lib/UserAgent");
  20. const isEventHandled = require("./isEventHandled");
  21. const keyCommandBackspaceToStartOfLine = require("./keyCommandBackspaceToStartOfLine");
  22. const keyCommandBackspaceWord = require("./keyCommandBackspaceWord");
  23. const keyCommandDeleteWord = require("./keyCommandDeleteWord");
  24. const keyCommandInsertNewline = require("./keyCommandInsertNewline");
  25. const keyCommandMoveSelectionToEndOfBlock = require("./keyCommandMoveSelectionToEndOfBlock");
  26. const keyCommandMoveSelectionToStartOfBlock = require("./keyCommandMoveSelectionToStartOfBlock");
  27. const keyCommandPlainBackspace = require("./keyCommandPlainBackspace");
  28. const keyCommandPlainDelete = require("./keyCommandPlainDelete");
  29. const keyCommandTransposeCharacters = require("./keyCommandTransposeCharacters");
  30. const keyCommandUndo = require("./keyCommandUndo");
  31. const {
  32. isOptionKeyCommand
  33. } = KeyBindingUtil;
  34. const isChrome = UserAgent.isBrowser('Chrome');
  35. /**
  36. * Map a `DraftEditorCommand` command value to a corresponding function.
  37. */
  38. function onKeyCommand(command: DraftEditorCommand | string, editorState: EditorState, e: SyntheticKeyboardEvent<HTMLElement>): EditorState {
  39. switch (command) {
  40. case 'redo':
  41. return EditorState.redo(editorState);
  42. case 'delete':
  43. return keyCommandPlainDelete(editorState);
  44. case 'delete-word':
  45. return keyCommandDeleteWord(editorState);
  46. case 'backspace':
  47. return keyCommandPlainBackspace(editorState);
  48. case 'backspace-word':
  49. return keyCommandBackspaceWord(editorState);
  50. case 'backspace-to-start-of-line':
  51. return keyCommandBackspaceToStartOfLine(editorState, e);
  52. case 'split-block':
  53. return keyCommandInsertNewline(editorState);
  54. case 'transpose-characters':
  55. return keyCommandTransposeCharacters(editorState);
  56. case 'move-selection-to-start-of-block':
  57. return keyCommandMoveSelectionToStartOfBlock(editorState);
  58. case 'move-selection-to-end-of-block':
  59. return keyCommandMoveSelectionToEndOfBlock(editorState);
  60. case 'secondary-cut':
  61. return SecondaryClipboard.cut(editorState);
  62. case 'secondary-paste':
  63. return SecondaryClipboard.paste(editorState);
  64. default:
  65. return editorState;
  66. }
  67. }
  68. /**
  69. * Intercept keydown behavior to handle keys and commands manually, if desired.
  70. *
  71. * Keydown combinations may be mapped to `DraftCommand` values, which may
  72. * correspond to command functions that modify the editor or its contents.
  73. *
  74. * See `getDefaultKeyBinding` for defaults. Alternatively, the top-level
  75. * component may provide a custom mapping via the `keyBindingFn` prop.
  76. */
  77. function editOnKeyDown(editor: DraftEditor, e: SyntheticKeyboardEvent<HTMLElement>): void {
  78. const keyCode = e.which;
  79. const editorState = editor._latestEditorState;
  80. function callDeprecatedHandler(handlerName: 'onDownArrow' | 'onEscape' | 'onLeftArrow' | 'onRightArrow' | 'onTab' | 'onUpArrow'): boolean {
  81. const deprecatedHandler = editor.props[handlerName];
  82. if (deprecatedHandler) {
  83. deprecatedHandler(e);
  84. return true;
  85. } else {
  86. return false;
  87. }
  88. }
  89. switch (keyCode) {
  90. case Keys.RETURN:
  91. e.preventDefault(); // The top-level component may manually handle newline insertion. If
  92. // no special handling is performed, fall through to command handling.
  93. if (editor.props.handleReturn && isEventHandled(editor.props.handleReturn(e, editorState))) {
  94. return;
  95. }
  96. break;
  97. case Keys.ESC:
  98. e.preventDefault();
  99. if (callDeprecatedHandler('onEscape')) {
  100. return;
  101. }
  102. break;
  103. case Keys.TAB:
  104. if (callDeprecatedHandler('onTab')) {
  105. return;
  106. }
  107. break;
  108. case Keys.UP:
  109. if (callDeprecatedHandler('onUpArrow')) {
  110. return;
  111. }
  112. break;
  113. case Keys.RIGHT:
  114. if (callDeprecatedHandler('onRightArrow')) {
  115. return;
  116. }
  117. break;
  118. case Keys.DOWN:
  119. if (callDeprecatedHandler('onDownArrow')) {
  120. return;
  121. }
  122. break;
  123. case Keys.LEFT:
  124. if (callDeprecatedHandler('onLeftArrow')) {
  125. return;
  126. }
  127. break;
  128. case Keys.SPACE:
  129. // Prevent Chrome on OSX behavior where option + space scrolls.
  130. if (isChrome && isOptionKeyCommand(e)) {
  131. e.preventDefault();
  132. }
  133. }
  134. const command = editor.props.keyBindingFn(e); // If no command is specified, allow keydown event to continue.
  135. if (command == null || command === '') {
  136. if (keyCode === Keys.SPACE && isChrome && isOptionKeyCommand(e)) {
  137. // The default keydown event has already been prevented in order to stop
  138. // Chrome from scrolling. Insert a nbsp into the editor as OSX would for
  139. // other browsers.
  140. const contentState = DraftModifier.replaceText(editorState.getCurrentContent(), editorState.getSelection(), '\u00a0');
  141. editor.update(EditorState.push(editorState, contentState, 'insert-characters'));
  142. }
  143. return;
  144. }
  145. if (command === 'undo') {
  146. // Since undo requires some special updating behavior to keep the editor
  147. // in sync, handle it separately.
  148. keyCommandUndo(e, editorState, editor.update);
  149. return;
  150. } // At this point, we know that we're handling a command of some kind, so
  151. // we don't want to insert a character following the keydown.
  152. e.preventDefault(); // Allow components higher up the tree to handle the command first.
  153. if (editor.props.handleKeyCommand && isEventHandled(editor.props.handleKeyCommand(command, editorState, e.timeStamp))) {
  154. return;
  155. }
  156. const newState = onKeyCommand(command, editorState, e);
  157. if (newState !== editorState) {
  158. editor.update(newState);
  159. }
  160. }
  161. module.exports = editOnKeyDown;