diff --git a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js index 4c8fee1e2d6a9..b0de1bc83d240 100644 --- a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js +++ b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js @@ -39,6 +39,9 @@ var eventTypes = { 'topKeyDown', 'topKeyUp', 'topSelectionChange', + 'topCompositionStart', + 'topCompositionUpdate', + 'topCompositionEnd', ], }, }; @@ -62,7 +65,10 @@ function createAndAccumulateChangeEvent(inst, nativeEvent, target) { var activeElement = null; var activeElementInst = null; - +/** + * For composition events + */ +var lastTopLevelType = null; /** * SECTION: handle `change` event @@ -281,7 +287,20 @@ function getTargetInstForInputOrChangeEvent( topLevelType, targetInst ) { - if ( + if (inComposition(topLevelType)) { + return; + } else if ( + topLevelType === 'topInput' && + lastTopLevelType === 'topCompositionEnd' + ) { + return getInstIfValueChanged(targetInst); + } else if ( + // Webkit fires compositionEnd event after input + topLevelType === 'topKeyUp' && + lastTopLevelType === 'topCompositionEnd' + ) { + return getInstIfValueChanged(targetInst); + } else if ( topLevelType === 'topInput' || topLevelType === 'topChange' ) { @@ -289,6 +308,16 @@ function getTargetInstForInputOrChangeEvent( } } +var isComposing = false; +function inComposition(topLevelType) { + if (topLevelType === 'topCompositionStart') { + isComposing = true; + } else if (topLevelType === 'topCompositionEnd') { + isComposing = false; + } + return isComposing; +} + /** * This plugin creates an `onChange` event that normalizes change events * across form elements. This event fires at a time when it's possible to @@ -334,6 +363,7 @@ var ChangeEventPlugin = { if (getTargetInstFunc) { var inst = getTargetInstFunc(topLevelType, targetInst); + lastTopLevelType = topLevelType; if (inst) { var event = createAndAccumulateChangeEvent( inst, diff --git a/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js b/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js index efe04b0874af4..e9a3484acc247 100644 --- a/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js +++ b/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js @@ -212,4 +212,156 @@ describe('ChangeEventPlugin', () => { ReactTestUtils.SimulateNative.change(input); expect(called).toBe(2); }); + + describe('composition events', () => { + function simulateEvent(inst, event) { + ReactTestUtils.SimulateNative[event](inst); + } + + function TestCompositionEvent(Scenario) { + var called = 0; + var value = null; + + function cb(e) { + called += 1; + value = e.target.value; + } + + var input = ReactTestUtils.renderIntoDocument( + + ); + + Scenario.forEach(el => { + el.run.apply(null, [input].concat(el.args)) + }); + + expect(called).toBe(1); + expect(value).toBe('你'); + } + + var Scenario = { + Webkit: [ + { run: setUntrackedValue, args: [ 'n' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionStart' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ 'ni' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ '你' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'textInput' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'compositionEnd' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + ], + Firefox: [ + { run: setUntrackedValue, args: [ 'n' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionStart' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: setUntrackedValue, args: [ 'ni' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: setUntrackedValue, args: [ '你' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'compositionEnd' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + ], + IE9: [ + { run: setUntrackedValue, args: [ 'n' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionStart' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ 'ni' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ '你' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'compositionEnd' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + ], + IE10: [ + { run: setUntrackedValue, args: [ 'n' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionStart' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ 'ni' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ '你' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionEnd' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + ], + IE11: [ + { run: setUntrackedValue, args: [ 'n' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionStart' ] }, + { run: simulateEvent, args: [ 'compositionUpdate' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ 'ni' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ '你' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionEnd' ] }, + { run: simulateEvent, args: [ 'input' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + ], + Edge: [ + { run: setUntrackedValue, args: [ 'n' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionStart' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ 'ni' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'keyUp' ] }, + { run: setUntrackedValue, args: [ '你' ] }, + { run: simulateEvent, args: [ 'keyDown' ] }, + { run: simulateEvent, args: [ 'compositionEnd' ] }, + { run: simulateEvent, args: [ 'input' ] }, + ], + }; + + it('should only fire change once on Webkit', () => { + TestCompositionEvent(Scenario.Webkit); + }); + + it('should only fire change once on Firefox', () => { + TestCompositionEvent(Scenario.Firefox); + }); + + it('should only fire change once on IE9', () => { + TestCompositionEvent(Scenario.IE9); + }); + + it('should only fire change once on IE10', () => { + TestCompositionEvent(Scenario.IE10); + }); + + it('should only fire change once on IE11', () => { + TestCompositionEvent(Scenario.IE11); + }); + + it('should only fire change once on Edge', () => { + TestCompositionEvent(Scenario.Edge); + }); + }); });