diff --git a/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js b/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js index 143311b863f65..1009a12fa2809 100644 --- a/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js +++ b/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js @@ -177,6 +177,12 @@ var eventTypes = { captured: keyOf({onInputCapture: true}), }, }, + invalid: { + phasedRegistrationNames: { + bubbled: keyOf({onInvalid: true}), + captured: keyOf({onInvalidCapture: true}), + }, + }, keyDown: { phasedRegistrationNames: { bubbled: keyOf({onKeyDown: true}), @@ -404,6 +410,7 @@ var topLevelEventsToDispatchConfig = { topError: eventTypes.error, topFocus: eventTypes.focus, topInput: eventTypes.input, + topInvalid: eventTypes.invalid, topKeyDown: eventTypes.keyDown, topKeyPress: eventTypes.keyPress, topKeyUp: eventTypes.keyUp, @@ -480,6 +487,7 @@ var SimpleEventPlugin = { case topLevelTypes.topEnded: case topLevelTypes.topError: case topLevelTypes.topInput: + case topLevelTypes.topInvalid: case topLevelTypes.topLoad: case topLevelTypes.topLoadedData: case topLevelTypes.topLoadedMetadata: diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 319159cdc3e2a..c7a3ae1f194ee 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -414,6 +414,17 @@ function trapBubbledEventsLocal() { ), ]; break; + case 'input': + case 'select': + case 'textarea': + inst._wrapperState.listeners = [ + ReactBrowserEventEmitter.trapBubbledEvent( + EventConstants.topLevelTypes.topInvalid, + 'invalid', + node + ), + ]; + break; } } @@ -557,15 +568,13 @@ ReactDOMComponent.Mixin = { case 'form': case 'video': case 'audio': - this._wrapperState = { - listeners: null, - }; - transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this); + this._setupTrapBubbledEventsLocal(transaction); break; case 'button': props = ReactDOMButton.getNativeProps(this, props, nativeParent); break; case 'input': + this._setupTrapBubbledEventsLocal(transaction); ReactDOMInput.mountWrapper(this, props, nativeParent); props = ReactDOMInput.getNativeProps(this, props); break; @@ -574,10 +583,12 @@ ReactDOMComponent.Mixin = { props = ReactDOMOption.getNativeProps(this, props); break; case 'select': + this._setupTrapBubbledEventsLocal(transaction); ReactDOMSelect.mountWrapper(this, props, nativeParent); props = ReactDOMSelect.getNativeProps(this, props); break; case 'textarea': + this._setupTrapBubbledEventsLocal(transaction); ReactDOMTextarea.mountWrapper(this, props, nativeParent); props = ReactDOMTextarea.getNativeProps(this, props); break; @@ -686,6 +697,19 @@ ReactDOMComponent.Mixin = { return mountImage; }, + /** + * Setup this component to trap non-bubbling events locally + * + * @private + * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction + */ + _setupTrapBubbledEventsLocal: function(transaction) { + this._wrapperState = { + listeners: null, + }; + transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this); + }, + /** * Creates markup for the open tag and all attributes. * diff --git a/src/renderers/shared/event/EventConstants.js b/src/renderers/shared/event/EventConstants.js index 0bed5e39c1dc9..53b1578796dfc 100644 --- a/src/renderers/shared/event/EventConstants.js +++ b/src/renderers/shared/event/EventConstants.js @@ -47,6 +47,7 @@ var topLevelTypes = keyMirror({ topError: null, topFocus: null, topInput: null, + topInvalid: null, topKeyDown: null, topKeyPress: null, topKeyUp: null,