From c19bf9cffe163dce8bc687e6276c533ea5199808 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sun, 9 Jun 2013 03:52:01 -0700 Subject: [PATCH] Add new textChange event: input + IE shim IE8 doesn't support oninput and IE9 supports it badly but we can do almost a perfect shim by listening to a handful of different events (focus, blur, propertychange, selectionchange, keyup, keydown). This always triggers event handlers during the browser's event loop (not later in a setTimeout) and after the value property has been updated. The only case I know of where this doesn't fire the event immediately is if (in IE8) you modify the input value using JS and then the user does a key repeat, in which case we fire the event on the second keydown. Test Plan: Modify ballmer-peak example to add es5-shim and to use onTextChange instead of onInput. In IE8, IE9, and latest Chrome, make sure that the event is fired upon: * typing normally, * backspacing, * forward-deleting, * cutting, * pasting, * context-menu deleting, * dragging text to reorder characters. After modifying the example to change .value, make sure that the event is not fired as a result of the changes from JS (even when the input box is focused). --- src/core/ReactDefaultInjection.js | 4 +- src/core/ReactEventEmitter.js | 5 + src/event/EventConstants.js | 5 +- src/eventPlugins/DefaultEventPluginOrder.js | 1 + src/eventPlugins/TextChangeEventPlugin.js | 200 ++++++++++++++++++++ 5 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 src/eventPlugins/TextChangeEventPlugin.js diff --git a/src/core/ReactDefaultInjection.js b/src/core/ReactDefaultInjection.js index 75044d849f..27fb2afba0 100644 --- a/src/core/ReactDefaultInjection.js +++ b/src/core/ReactDefaultInjection.js @@ -23,6 +23,7 @@ var ReactDOMForm = require('ReactDOMForm'); var DefaultEventPluginOrder = require('DefaultEventPluginOrder'); var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin'); +var TextChangeEventPlugin = require('TextChangeEventPlugin'); var EventPluginHub = require('EventPluginHub'); var ReactInstanceHandles = require('ReactInstanceHandles'); var SimpleEventPlugin = require('SimpleEventPlugin'); @@ -40,7 +41,8 @@ function inject() { */ EventPluginHub.injection.injectEventPluginsByName({ 'SimpleEventPlugin': SimpleEventPlugin, - 'EnterLeaveEventPlugin': EnterLeaveEventPlugin + 'EnterLeaveEventPlugin': EnterLeaveEventPlugin, + 'TextChangeEventPlugin': TextChangeEventPlugin }); /* diff --git a/src/core/ReactEventEmitter.js b/src/core/ReactEventEmitter.js index ccd74c08a8..3fd59d3ad7 100644 --- a/src/core/ReactEventEmitter.js +++ b/src/core/ReactEventEmitter.js @@ -189,6 +189,11 @@ function listenAtTopLevel(touchNotMouse) { trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt); trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt); trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt); + trapBubbledEvent( + topLevelTypes.topSelectionChange, + 'selectionchange', + mountAt + ); trapBubbledEvent( topLevelTypes.topDOMCharacterDataModified, 'DOMCharacterDataModified', diff --git a/src/event/EventConstants.js b/src/event/EventConstants.js index b99696d95b..cd3e794f67 100644 --- a/src/event/EventConstants.js +++ b/src/event/EventConstants.js @@ -41,13 +41,14 @@ var topLevelTypes = keyMirror({ topMouseOut: null, topMouseOver: null, topMouseUp: null, - topWheel: null, topScroll: null, + topSelectionChange: null, topSubmit: null, topTouchCancel: null, topTouchEnd: null, topTouchMove: null, - topTouchStart: null + topTouchStart: null, + topWheel: null }); var EventConstants = { diff --git a/src/eventPlugins/DefaultEventPluginOrder.js b/src/eventPlugins/DefaultEventPluginOrder.js index 81eecc37c6..ab7ee4448d 100644 --- a/src/eventPlugins/DefaultEventPluginOrder.js +++ b/src/eventPlugins/DefaultEventPluginOrder.js @@ -34,6 +34,7 @@ var DefaultEventPluginOrder = [ keyOf({SimpleEventPlugin: null}), keyOf({TapEventPlugin: null}), keyOf({EnterLeaveEventPlugin: null}), + keyOf({TextChangeEventPlugin: null}), keyOf({AnalyticsEventPlugin: null}) ]; diff --git a/src/eventPlugins/TextChangeEventPlugin.js b/src/eventPlugins/TextChangeEventPlugin.js new file mode 100644 index 0000000000..0af5e409e9 --- /dev/null +++ b/src/eventPlugins/TextChangeEventPlugin.js @@ -0,0 +1,200 @@ +/** + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule TextChangeEventPlugin + */ + +"use strict"; + +var AbstractEvent = require('AbstractEvent'); +var EventConstants = require('EventConstants'); +var EventPluginHub = require('EventPluginHub'); +var EventPropagators = require('EventPropagators'); + +var isEventSupported = require('isEventSupported'); +var keyOf = require('keyOf'); + +var topLevelTypes = EventConstants.topLevelTypes; + +var abstractEventTypes = { + textChange: { + phasedRegistrationNames: { + bubbled: keyOf({onTextChange: null}), + captured: keyOf({onTextChangeCapture: null}) + } + } +}; + +// IE9 claims to support the input event but fails to trigger it when deleting +// text, so we ignore its input events +var isInputSupported = isEventSupported('input') && ( + !("documentMode" in document) || document.documentMode > 9 +); + +var hasInputCapabilities = function(elem) { + // The HTML5 spec lists many more types than `text` and `password` on which + // the input event is triggered but none of them exist in old IE, so we don't + // check them here. + // TODO: