Last active
November 29, 2016 00:36
-
-
Save bowbowbow/8ca91694cb03670492c60510e60c4aad to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (c) 2013-present, Facebook, Inc. | |
* All rights reserved. | |
* | |
* This source code is licensed under the BSD-style license found in the | |
* LICENSE file in the root directory of this source tree. An additional grant | |
* of patent rights can be found in the PATENTS file in the same directory. | |
* | |
* @providesModule DraftEditorCompositionHandler | |
* @flow | |
*/ | |
'use strict'; | |
const DraftModifier = require('DraftModifier'); | |
const EditorState = require('EditorState'); | |
const Keys = require('Keys'); | |
const getEntityKeyForSelection = require('getEntityKeyForSelection'); | |
const isSelectionAtLeafStart = require('isSelectionAtLeafStart'); | |
/** | |
* Millisecond delay to allow `compositionstart` to fire again upon | |
* `compositionend`. | |
* | |
* This is used for Korean input to ensure that typing can continue without | |
* the editor trying to render too quickly. More specifically, Safari 7.1+ | |
* triggers `compositionstart` a little slower than Chrome/FF, which | |
* leads to composed characters being resolved and re-render occurring | |
* sooner than we want. | |
*/ | |
const RESOLVE_DELAY = 20; | |
/** | |
* A handful of variables used to track the current composition and its | |
* resolution status. These exist at the module level because it is not | |
* possible to have compositions occurring in multiple editors simultaneously, | |
* and it simplifies state management with respect to the DraftEditor component. | |
*/ | |
let resolved = false; | |
let stillComposing = false; | |
let textInputData = ''; | |
let beforeDownKey = ''; | |
var DraftEditorCompositionHandler = { | |
onBeforeInput: function(e: SyntheticInputEvent): void { | |
textInputData = (textInputData || '') + e.data; | |
}, | |
/** | |
* A `compositionstart` event has fired while we're still in composition | |
* mode. Continue the current composition session to prevent a re-render. | |
*/ | |
onCompositionStart: function(): void { | |
stillComposing = true; | |
}, | |
/** | |
* Attempt to end the current composition session. | |
* | |
* Defer handling because browser will still insert the chars into active | |
* element after `compositionend`. If a `compositionstart` event fires | |
* before `resolveComposition` executes, our composition session will | |
* continue. | |
* | |
* The `resolved` flag is useful because certain IME interfaces fire the | |
* `compositionend` event multiple times, thus queueing up multiple attempts | |
* at handling the composition. Since handling the same composition event | |
* twice could break the DOM, we only use the first event. Example: Arabic | |
* Google Input Tools on Windows 8.1 fires `compositionend` three times. | |
*/ | |
onCompositionEnd: function(): void { | |
resolved = false; | |
stillComposing = false; | |
setTimeout(() => { | |
if (!resolved) { | |
DraftEditorCompositionHandler.resolveComposition.call(this); | |
} | |
}, RESOLVE_DELAY); | |
}, | |
/** | |
* In Safari, keydown events may fire when committing compositions. If | |
* the arrow keys are used to commit, prevent default so that the cursor | |
* doesn't move, otherwise it will jump back noticeably on re-render. | |
*/ | |
onKeyDown: function(e: SyntheticKeyboardEvent): void { | |
if (e.key === 'Backspace') { | |
/** | |
* composition 모드에 있는 글자의 오른쪽에 있는 커서에서 backspace가 입력됐을 때 | |
* 전에 resolveComposition 함수가 호출되어 textInputData가 ''인 상태라면 | |
* resolveComposition 함수가 호출되어 composing 중인 문자를 | |
* 지우는 것만으로 한 글자가 지워지기 때문에 backspace가 composing 중이 아닌 음절을 지우면 유저는 | |
* 실제로 두 글자가 지워지는 것 처럼 느끼기 때문에 이 경우에는 backspace 키 이벤트의 수행을 막는다. | |
*/ | |
if (textInputData === ''){ | |
e.preventDefault(); | |
} | |
stillComposing = false; | |
DraftEditorCompositionHandler.resolveComposition.call(this); | |
} else if (e.key === 'Enter') { | |
/** | |
* 이 if 문을 넣은 이유는 backspace가 입력되고 resolveComposition 함수가 호출되어 | |
* textInputData가 ''인 상황에서 resolveComposition과 enter key event가 호출되면 | |
* 한 줄이 전부 지워지는 문제가 발생했기 때문이다. 따라서 이 때는 enter key event의 수행을 막는다. | |
* | |
* 한 줄이 전부 지워지는 문제는 resolveComposition 함수에 있지 않고 enter key event의 수행 자체에 | |
* 있는 것으로 추정된다. | |
*/ | |
if (textInputData === ''){ | |
e.preventDefault(); | |
} | |
stillComposing = false; | |
DraftEditorCompositionHandler.resolveComposition.call(this); | |
} | |
if (e.which === Keys.RIGHT || e.which === Keys.LEFT) { | |
e.preventDefault(); | |
} | |
beforeDownKey = e.key; | |
}, | |
/** | |
* Keypress events may fire when committing compositions. In Firefox, | |
* pressing RETURN commits the composition and inserts extra newline | |
* characters that we do not want. `preventDefault` allows the composition | |
* to be committed while preventing the extra characters. | |
*/ | |
onKeyPress: function(e: SyntheticKeyboardEvent): void { | |
if (e.which === Keys.RETURN) { | |
e.preventDefault(); | |
} | |
}, | |
/** | |
* Attempt to insert composed characters into the document. | |
* | |
* If we are still in a composition session, do nothing. Otherwise, insert | |
* the characters into the document and terminate the composition session. | |
* | |
* If no characters were composed -- for instance, the user | |
* deleted all composed characters and committed nothing new -- | |
* force a re-render. We also re-render when the composition occurs | |
* at the beginning of a leaf, to ensure that if the browser has | |
* created a new text node for the composition, we will discard it. | |
* | |
* Resetting innerHTML will move focus to the beginning of the editor, | |
* so we update to force it back to the correct place. | |
*/ | |
resolveComposition: function(): void { | |
if (stillComposing) { | |
return; | |
} | |
resolved = true; | |
const composedChars = textInputData; | |
textInputData = ''; | |
const editorState = EditorState.set(this.props.editorState, { | |
inCompositionMode: false, | |
}); | |
const currentStyle = editorState.getCurrentInlineStyle(); | |
const entityKey = getEntityKeyForSelection( | |
editorState.getCurrentContent(), | |
editorState.getSelection() | |
); | |
const mustReset = ( | |
!composedChars || | |
isSelectionAtLeafStart(editorState) || | |
currentStyle.size > 0 || | |
entityKey !== null | |
); | |
if (mustReset) { | |
this.restoreEditorDOM(); | |
} | |
this.exitCurrentMode(); | |
this.removeRenderGuard(); | |
if (composedChars) { | |
// If characters have been composed, re-rendering with the update | |
// is sufficient to reset the editor. | |
const contentState = DraftModifier.replaceText( | |
editorState.getCurrentContent(), | |
editorState.getSelection(), | |
composedChars, | |
currentStyle, | |
entityKey | |
); | |
this.update( | |
EditorState.push( | |
editorState, | |
contentState, | |
'insert-characters' | |
) | |
); | |
return; | |
} | |
if (mustReset) { | |
this.update( | |
EditorState.set(editorState, { | |
nativelyRenderedContent: null, | |
forceSelection: true, | |
}) | |
); | |
} | |
}, | |
}; | |
module.exports = DraftEditorCompositionHandler; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment