Use KeyboardEvent.char in the fallback logic if available (#9792)
When a user types an emoji via Touch keyboard in IE, React's fallback logic creates the `BeforeInput` event based on the `keypress`. However, the length of an emoji character (e.g. `\uD83D\uDE0A`) is two, so the `which` property does not represent an emoji correctly. Because IE's KeyboardEvent has the `char` property holding an entire emoji, we can use it directly instead of converting from the `which`.
tokikuch committed
Jun 16, 2017 at 15:14 UTC
00ba97a354e841701b4b83983c3a3904895e7b87
2 files changed
+36
-13
src/renderers/dom/shared/eventPlugins/BeforeInputEventPlugin.js
+12
-2
@@ -374,8 +374,18 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
374
* being used. Ex: `Cmd+C`. No character is inserted, and no
375
* `input` event will occur.
376
*/
377
- if (nativeEvent.which && !isKeypressCommand(nativeEvent)) {
378
- return String.fromCharCode(nativeEvent.which);
377
+ if (!isKeypressCommand(nativeEvent)) {
378
+ // IE fires the `keypress` event when a user types an emoji via
379
+ // Touch keyboard of Windows. In such a case, the `char` property
380
+ // holds an emoji character like `\uD83D\uDE0A`. Because its length
381
+ // is 2, the property `which` does not represent an emoji correctly.
382
+ // In such a case, we directly return the `char` property instead of
383
+ // using `which`.
384
+ if (nativeEvent.char && nativeEvent.char.length > 1) {
385
+ return nativeEvent.char;
386
+ } else if (nativeEvent.which) {
387
+ return String.fromCharCode(nativeEvent.which);
388
+ }
389
}
390
return null;
391
case 'topCompositionEnd':
src/renderers/dom/shared/eventPlugins/__tests__/BeforeInputEventPlugin-test.js
+24
-11
@@ -19,6 +19,7 @@ var EventMapping = {
19
compositionend: 'topCompositionEnd',
20
keyup: 'topKeyUp',
21
keydown: 'topKeyDown',
22
+ keypress: 'topKeyPress',
23
textInput: 'topTextInput',
24
textinput: null, // Not defined now
25
};
@@ -122,7 +123,8 @@ describe('BeforeInputEventPlugin', function() {
123
// instead of a standard name `textInput`. As of now, React does not have
124
// a corresponding topEvent to IE's textinput, but both events are added to
125
// this scenario data for future use.
125
- var Scenario_Composition = [
126
+ var Test_Scenario = [
127
+ // Composition test
128
{run: accumulateEvents, arg: ['compositionstart', {data: ''}]},
129
{run: accumulateEvents, arg: ['textInput', {data: 'A'}]},
130
{run: accumulateEvents, arg: ['textinput', {data: 'A'}]},
@@ -136,6 +138,13 @@ describe('BeforeInputEventPlugin', function() {
138
{run: accumulateEvents, arg: ['textinput', {data: 'xyz'}]},
139
{run: accumulateEvents, arg: ['keyup', {keyCode: 32}]},
140
{run: accumulateEvents, arg: ['compositionend', {data: 'Hello'}]},
141
+
142
+ // Emoji test
143
+ {
144
+ run: accumulateEvents,
145
+ arg: ['keypress', {char: '\uD83D\uDE0A', which: 65}],
146
+ },
147
+ {run: accumulateEvents, arg: ['textInput', {data: '\uD83D\uDE0A'}]},
148
];
149
150
/* Defined expected results as a factory of result data because we need
@@ -170,6 +179,12 @@ describe('BeforeInputEventPlugin', function() {
179
{type: null}, // keyUp of 32
180
{type: ModuleCache.SyntheticCompositionEvent, data: {data: 'Hello'}},
181
{type: null},
182
+
183
+ // Emoji test
184
+ {type: null},
185
+ {type: null},
186
+ {type: null},
187
+ {type: ModuleCache.SyntheticInputEvent, data: {data: '\uD83D\uDE0A'}},
188
];
189
190
// For IE11, we use fallback data instead of IE's textinput events.
@@ -205,6 +220,12 @@ describe('BeforeInputEventPlugin', function() {
220
// at a time of compositionend
221
{type: ModuleCache.SyntheticCompositionEvent, data: {}},
222
{type: ModuleCache.SyntheticInputEvent, data: {data: 'XYZ'}},
223
+
224
+ // Emoji test
225
+ {type: null},
226
+ {type: ModuleCache.SyntheticInputEvent, data: {data: '\uD83D\uDE0A'}},
227
+ {type: null},
228
+ {type: null},
229
];
230
231
function TestEditableReactComponent(Emulator, Scenario, ExpectedResult) {
@@ -225,18 +246,10 @@ describe('BeforeInputEventPlugin', function() {
246
}
247
248
it('extract onBeforeInput from native textinput events', function() {
228
- TestEditableReactComponent(
229
- simulateWebkit,
230
- Scenario_Composition,
231
- Expected_Webkit,
232
- );
249
+ TestEditableReactComponent(simulateWebkit, Test_Scenario, Expected_Webkit);
250
});
251
252
it('extract onBeforeInput from fallback objects', function() {
236
- TestEditableReactComponent(
237
- simulateIE11,
238
- Scenario_Composition,
239
- Expected_IE11,
240
- );
253
+ TestEditableReactComponent(simulateIE11, Test_Scenario, Expected_IE11);
254
});
255
});