Skip to content

Commit f5a196f

Browse files
committed
Input bugs (#2)
* Modified compositoin mode event handlers to provide better support for mobile browsers * Added test cases for DraftEditorCompositionHandler * Linter compliance * Added support for `compositionupdate` event. * Made the linter happy! * Normalize beforeInput and compositionUpdate data This continues from facebookarchive#1152 and facebookarchive#1285 making them work together for broad support for polatform inconsistencies. * Put new composition fixes behind a feature flag * lint * correct resets * Fix Synthetic Event flow types * Convert DraftEditorCompositionHandler tests to root tests + snapshots * Remove unnecessary DraftFeatureFlags
1 parent 93bc209 commit f5a196f

File tree

4 files changed

+371
-4
lines changed

4 files changed

+371
-4
lines changed

src/component/base/DraftEditor.react.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
9696
_onCharacterData: Function;
9797
_onCompositionEnd: Function;
9898
_onCompositionStart: Function;
99+
_onCompositionUpdate: Function;
99100
_onCopy: Function;
100101
_onCut: Function;
101102
_onDragEnd: Function;
@@ -143,6 +144,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
143144
this._onCharacterData = this._buildHandler('onCharacterData');
144145
this._onCompositionEnd = this._buildHandler('onCompositionEnd');
145146
this._onCompositionStart = this._buildHandler('onCompositionStart');
147+
this._onCompositionUpdate = this._buildHandler('onCompositionUpdate');
146148
this._onCopy = this._buildHandler('onCopy');
147149
this._onCut = this._buildHandler('onCut');
148150
this._onDragEnd = this._buildHandler('onDragEnd');
@@ -287,6 +289,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
287289
onBlur={this._onBlur}
288290
onCompositionEnd={this._onCompositionEnd}
289291
onCompositionStart={this._onCompositionStart}
292+
onCompositionUpdate={this._onCompositionUpdate}
290293
onCopy={this._onCopy}
291294
onCut={this._onCut}
292295
onDragEnd={this._onDragEnd}

src/component/handlers/composition/DraftEditorCompositionHandler.js

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,24 @@ const RESOLVE_DELAY = 20;
4545
let resolved = false;
4646
let stillComposing = false;
4747
let textInputData = '';
48+
let beforeInputData = null;
49+
let compositionUpdateData = null;
50+
let compositionEndData = null;
4851

4952
var DraftEditorCompositionHandler = {
53+
/**
54+
* Some IMEs (Firefox Mobile, notably) fire multiple `beforeinput` events
55+
* which include the text so far typed, in addition to (broken)
56+
* `compositionend` events. In these cases, we construct beforeInputData from
57+
* the `beforeinput` and cnsider that to be the definitive version of what
58+
* was actually typed.
59+
*
60+
* Proper, compliant browsers will not do this and will instead include the
61+
* entire resolved composition result in the `data` member of the
62+
* `compositionend` events that they fire.
63+
*/
5064
onBeforeInput: function(editor: DraftEditor, e: SyntheticInputEvent<>): void {
51-
textInputData = (textInputData || '') + e.data;
65+
beforeInputData = (beforeInputData || '') + e.data;
5266
},
5367

5468
/**
@@ -59,6 +73,17 @@ var DraftEditorCompositionHandler = {
5973
stillComposing = true;
6074
},
6175

76+
/**
77+
* A `compositionupdate` event has fired. Update the current composition
78+
* session.
79+
*/
80+
onCompositionUpdate: function(
81+
editor: DraftEditor,
82+
e: SyntheticInputEvent<>,
83+
): void {
84+
compositionUpdateData = e.data;
85+
},
86+
6287
/**
6388
* Attempt to end the current composition session.
6489
*
@@ -73,9 +98,14 @@ var DraftEditorCompositionHandler = {
7398
* twice could break the DOM, we only use the first event. Example: Arabic
7499
* Google Input Tools on Windows 8.1 fires `compositionend` three times.
75100
*/
76-
onCompositionEnd: function(editor: DraftEditor): void {
101+
onCompositionEnd: function(
102+
editor: DraftEditor,
103+
e: SyntheticCompositionEvent<>,
104+
): void {
77105
resolved = false;
78106
stillComposing = false;
107+
// Use e.data from the first compositionend event seen
108+
compositionEndData = compositionEndData || e.data;
79109
setTimeout(() => {
80110
if (!resolved) {
81111
DraftEditorCompositionHandler.resolveComposition(editor);
@@ -115,6 +145,34 @@ var DraftEditorCompositionHandler = {
115145
}
116146
},
117147

148+
/**
149+
* Normalizes platform inconsistencies with input event data.
150+
*
151+
* When beforeInputData is present, it is only preferred if its length
152+
* is greater than that of the last compositionUpdate event data. This is
153+
* meant to resolve IME incosistencies where compositionUpdate may contain
154+
* only the last character or the entire composition depending on language
155+
* (e.g. Korean vs. Japanese).
156+
*
157+
* When beforeInputData is not present, compositionUpdate data is preferred.
158+
* This resolves issues with some platforms where beforeInput is never fired
159+
* (e.g. Android with certain keyboard and browser combinations).
160+
*
161+
* Lastly, if neither beforeInput nor compositionUpdate events are fired, use
162+
* the data in the compositionEnd event
163+
*/
164+
normalizeCompositionInput: function(): ?string {
165+
const beforeInputDataLength = beforeInputData ? beforeInputData.length : 0;
166+
const compositionUpdateDataLength = compositionUpdateData
167+
? compositionUpdateData.length
168+
: 0;
169+
const updateData =
170+
beforeInputDataLength > compositionUpdateDataLength
171+
? beforeInputData
172+
: compositionUpdateData;
173+
return updateData || compositionEndData;
174+
},
175+
118176
/**
119177
* Attempt to insert composed characters into the document.
120178
*
@@ -136,8 +194,12 @@ var DraftEditorCompositionHandler = {
136194
}
137195

138196
resolved = true;
139-
const composedChars = textInputData;
140-
textInputData = '';
197+
198+
let composedChars;
199+
composedChars = this.normalizeCompositionInput();
200+
beforeInputData = null;
201+
compositionUpdateData = null;
202+
compositionEndData = null;
141203

142204
const editorState = EditorState.set(editor._latestEditorState, {
143205
inCompositionMode: false,

0 commit comments

Comments
 (0)