Skip to content

Instantly share code, notes, and snippets.

@bowbowbow
Last active November 29, 2016 00:36
Show Gist options
  • Save bowbowbow/8ca91694cb03670492c60510e60c4aad to your computer and use it in GitHub Desktop.
Save bowbowbow/8ca91694cb03670492c60510e60c4aad to your computer and use it in GitHub Desktop.
/**
* 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