Skip to content

Commit ae54338

Browse files
committed
Handle composition events in ChangeEventPlugin
1 parent f589274 commit ae54338

File tree

3 files changed

+190
-2
lines changed

3 files changed

+190
-2
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,12 @@ src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js
835835
* should deduplicate input value change events
836836
* should listen for both change and input events when supported
837837
* should only fire events when the value changes for range inputs
838+
* should only fire change once on Webkit
839+
* should only fire change once on Firefox
840+
* should only fire change once on IE9
841+
* should only fire change once on IE10
842+
* should only fire change once on IE11
843+
* should only fire change once on Edge
838844

839845
src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
840846
* should set relatedTarget properly in iframe

src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ var eventTypes = {
3939
'topKeyDown',
4040
'topKeyUp',
4141
'topSelectionChange',
42+
'topCompositionStart',
43+
'topCompositionUpdate',
44+
'topCompositionEnd',
4245
],
4346
},
4447
};
@@ -62,7 +65,10 @@ function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
6265
var activeElement = null;
6366
var activeElementInst = null;
6467

65-
68+
/**
69+
* For composition events
70+
*/
71+
var lastTopLevelType = null;
6672

6773
/**
6874
* SECTION: handle `change` event
@@ -281,14 +287,37 @@ function getTargetInstForInputOrChangeEvent(
281287
topLevelType,
282288
targetInst
283289
) {
284-
if (
290+
if (inComposition(topLevelType)) {
291+
return;
292+
} else if (
293+
topLevelType === 'topInput' &&
294+
lastTopLevelType === 'topCompositionEnd'
295+
) {
296+
return getInstIfValueChanged(targetInst);
297+
} else if (
298+
// Webkit fires 'compositionEnd' event after 'input' event.
299+
topLevelType === 'topKeyUp' &&
300+
lastTopLevelType === 'topCompositionEnd'
301+
) {
302+
return getInstIfValueChanged(targetInst);
303+
} else if (
285304
topLevelType === 'topInput' ||
286305
topLevelType === 'topChange'
287306
) {
288307
return getInstIfValueChanged(targetInst);
289308
}
290309
}
291310

311+
var isComposing = false;
312+
function inComposition(topLevelType) {
313+
if (topLevelType === 'topCompositionStart') {
314+
isComposing = true;
315+
} else if (topLevelType === 'topCompositionEnd') {
316+
isComposing = false;
317+
}
318+
return isComposing;
319+
}
320+
292321
/**
293322
* This plugin creates an `onChange` event that normalizes change events
294323
* across form elements. This event fires at a time when it's possible to
@@ -334,6 +363,7 @@ var ChangeEventPlugin = {
334363

335364
if (getTargetInstFunc) {
336365
var inst = getTargetInstFunc(topLevelType, targetInst);
366+
lastTopLevelType = topLevelType;
337367
if (inst) {
338368
var event = createAndAccumulateChangeEvent(
339369
inst,

src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,156 @@ describe('ChangeEventPlugin', () => {
212212
ReactTestUtils.SimulateNative.change(input);
213213
expect(called).toBe(2);
214214
});
215+
216+
describe('composition events', () => {
217+
function simulateEvent(inst, event) {
218+
ReactTestUtils.SimulateNative[event](inst);
219+
}
220+
221+
function TestCompositionEvent(Scenario) {
222+
var called = 0;
223+
var value = null;
224+
225+
function cb(e) {
226+
called += 1;
227+
value = e.target.value;
228+
}
229+
230+
var input = ReactTestUtils.renderIntoDocument(
231+
<input type="text" onChange={cb} />
232+
);
233+
234+
Scenario.forEach(el => {
235+
el.run.apply(null, [input].concat(el.args))
236+
});
237+
238+
expect(called).toBe(1);
239+
expect(value).toBe('你');
240+
}
241+
242+
var Scenario = {
243+
Webkit: [
244+
{ run: setUntrackedValue, args: [ 'n' ] },
245+
{ run: simulateEvent, args: [ 'keyDown' ] },
246+
{ run: simulateEvent, args: [ 'compositionStart' ] },
247+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
248+
{ run: simulateEvent, args: [ 'input' ] },
249+
{ run: simulateEvent, args: [ 'keyUp' ] },
250+
{ run: setUntrackedValue, args: [ 'ni' ] },
251+
{ run: simulateEvent, args: [ 'keyDown' ] },
252+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
253+
{ run: simulateEvent, args: [ 'input' ] },
254+
{ run: simulateEvent, args: [ 'keyUp' ] },
255+
{ run: setUntrackedValue, args: [ '你' ] },
256+
{ run: simulateEvent, args: [ 'keyDown' ] },
257+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
258+
{ run: simulateEvent, args: [ 'textInput' ] },
259+
{ run: simulateEvent, args: [ 'input' ] },
260+
{ run: simulateEvent, args: [ 'compositionEnd' ] },
261+
{ run: simulateEvent, args: [ 'keyUp' ] },
262+
],
263+
Firefox: [
264+
{ run: setUntrackedValue, args: [ 'n' ] },
265+
{ run: simulateEvent, args: [ 'keyDown' ] },
266+
{ run: simulateEvent, args: [ 'compositionStart' ] },
267+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
268+
{ run: simulateEvent, args: [ 'input' ] },
269+
{ run: setUntrackedValue, args: [ 'ni' ] },
270+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
271+
{ run: simulateEvent, args: [ 'input' ] },
272+
{ run: setUntrackedValue, args: [ '你' ] },
273+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
274+
{ run: simulateEvent, args: [ 'compositionEnd' ] },
275+
{ run: simulateEvent, args: [ 'input' ] },
276+
{ run: simulateEvent, args: [ 'keyUp' ] },
277+
],
278+
IE9: [
279+
{ run: setUntrackedValue, args: [ 'n' ] },
280+
{ run: simulateEvent, args: [ 'keyDown' ] },
281+
{ run: simulateEvent, args: [ 'compositionStart' ] },
282+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
283+
{ run: simulateEvent, args: [ 'keyUp' ] },
284+
{ run: setUntrackedValue, args: [ 'ni' ] },
285+
{ run: simulateEvent, args: [ 'keyDown' ] },
286+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
287+
{ run: simulateEvent, args: [ 'keyUp' ] },
288+
{ run: setUntrackedValue, args: [ '你' ] },
289+
{ run: simulateEvent, args: [ 'keyDown' ] },
290+
{ run: simulateEvent, args: [ 'input' ] },
291+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
292+
{ run: simulateEvent, args: [ 'compositionEnd' ] },
293+
{ run: simulateEvent, args: [ 'keyUp' ] },
294+
],
295+
IE10: [
296+
{ run: setUntrackedValue, args: [ 'n' ] },
297+
{ run: simulateEvent, args: [ 'keyDown' ] },
298+
{ run: simulateEvent, args: [ 'compositionStart' ] },
299+
{ run: simulateEvent, args: [ 'keyUp' ] },
300+
{ run: setUntrackedValue, args: [ 'ni' ] },
301+
{ run: simulateEvent, args: [ 'keyDown' ] },
302+
{ run: simulateEvent, args: [ 'keyUp' ] },
303+
{ run: setUntrackedValue, args: [ '你' ] },
304+
{ run: simulateEvent, args: [ 'keyDown' ] },
305+
{ run: simulateEvent, args: [ 'keyUp' ] },
306+
{ run: simulateEvent, args: [ 'keyDown' ] },
307+
{ run: simulateEvent, args: [ 'compositionEnd' ] },
308+
{ run: simulateEvent, args: [ 'input' ] },
309+
{ run: simulateEvent, args: [ 'keyUp' ] },
310+
],
311+
IE11: [
312+
{ run: setUntrackedValue, args: [ 'n' ] },
313+
{ run: simulateEvent, args: [ 'keyDown' ] },
314+
{ run: simulateEvent, args: [ 'compositionStart' ] },
315+
{ run: simulateEvent, args: [ 'compositionUpdate' ] },
316+
{ run: simulateEvent, args: [ 'input' ] },
317+
{ run: simulateEvent, args: [ 'keyUp' ] },
318+
{ run: setUntrackedValue, args: [ 'ni' ] },
319+
{ run: simulateEvent, args: [ 'keyDown' ] },
320+
{ run: simulateEvent, args: [ 'input' ] },
321+
{ run: simulateEvent, args: [ 'keyUp' ] },
322+
{ run: setUntrackedValue, args: [ '你' ] },
323+
{ run: simulateEvent, args: [ 'keyDown' ] },
324+
{ run: simulateEvent, args: [ 'compositionEnd' ] },
325+
{ run: simulateEvent, args: [ 'input' ] },
326+
{ run: simulateEvent, args: [ 'keyUp' ] },
327+
],
328+
Edge: [
329+
{ run: setUntrackedValue, args: [ 'n' ] },
330+
{ run: simulateEvent, args: [ 'keyDown' ] },
331+
{ run: simulateEvent, args: [ 'compositionStart' ] },
332+
{ run: simulateEvent, args: [ 'keyUp' ] },
333+
{ run: setUntrackedValue, args: [ 'ni' ] },
334+
{ run: simulateEvent, args: [ 'keyDown' ] },
335+
{ run: simulateEvent, args: [ 'keyUp' ] },
336+
{ run: setUntrackedValue, args: [ '你' ] },
337+
{ run: simulateEvent, args: [ 'keyDown' ] },
338+
{ run: simulateEvent, args: [ 'compositionEnd' ] },
339+
{ run: simulateEvent, args: [ 'input' ] },
340+
],
341+
};
342+
343+
it('should only fire change once on Webkit', () => {
344+
TestCompositionEvent(Scenario.Webkit);
345+
});
346+
347+
it('should only fire change once on Firefox', () => {
348+
TestCompositionEvent(Scenario.Firefox);
349+
});
350+
351+
it('should only fire change once on IE9', () => {
352+
TestCompositionEvent(Scenario.IE9);
353+
});
354+
355+
it('should only fire change once on IE10', () => {
356+
TestCompositionEvent(Scenario.IE10);
357+
});
358+
359+
it('should only fire change once on IE11', () => {
360+
TestCompositionEvent(Scenario.IE11);
361+
});
362+
363+
it('should only fire change once on Edge', () => {
364+
TestCompositionEvent(Scenario.Edge);
365+
});
366+
});
215367
});

0 commit comments

Comments
 (0)