@samitouri / QOS-React-2 / commits / dbc613199b

Fix SelectEventPlugin

There were 2 issues: I was reusing event outside the original event handler (activeNativeEvent). This is a bad idea. I've changed deferred dispatch to have an empty object as the nativeEvent. I didn't handle inputs without .selectionStart (e.g. file inputs). I extracted a input type check from ChangeEventPlugin and reuse it here.

Josh Duck committed Oct 8, 2013 at 10:26 UTC dbc613199bd9f7da81692d5c0941339d3ffe0a8e
3 files changed +89 -61
src/eventPlugins/ChangeEventPlugin.js
+3 -31
@@ -25,6 +25,7 @@ var ExecutionEnvironment = require('ExecutionEnvironment');
25 var SyntheticEvent = require('SyntheticEvent');
26
27 var isEventSupported = require('isEventSupported');
28 +var isTextInputElement = require('isTextInputElement');
29 var keyOf = require('keyOf');
30
31 var topLevelTypes = EventConstants.topLevelTypes;
@@ -130,35 +131,6 @@ if (ExecutionEnvironment.canUseDOM) {
131 );
132 }
133
133 -
134 -/**
135 - * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
136 - */
137 -var supportedInputTypes = {
138 - 'color': true,
139 - 'date': true,
140 - 'datetime': true,
141 - 'datetime-local': true,
142 - 'email': true,
143 - 'month': true,
144 - 'number': true,
145 - 'password': true,
146 - 'range': true,
147 - 'search': true,
148 - 'tel': true,
149 - 'text': true,
150 - 'time': true,
151 - 'url': true,
152 - 'week': true
153 -};
154 -
155 -function shouldUseInputEvent(elem) {
156 - return (
157 - (elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
158 - elem.nodeName === 'TEXTAREA'
159 - );
160 -}
161 -
134 /**
135 * (For old IE.) Replacement getter/setter for the `value` property that gets
136 * set on the active element.
@@ -322,7 +294,7 @@ function getTargetIDForClickEvent(
294 * change the element's value without seeing a flicker.
295 *
296 * Supported elements are:
325 - * - input (see `supportedInputTypes`)
297 + * - input (see `isTextInputElement`)
298 * - textarea
299 * - select
300 */
@@ -351,7 +323,7 @@ var ChangeEventPlugin = {
323 } else {
324 handleEventFunc = handleEventsForChangeEventIE8;
325 }
354 - } else if (shouldUseInputEvent(topLevelTarget)) {
326 + } else if (isTextInputElement(topLevelTarget)) {
327 if (isInputEventSupported) {
328 getTargetIDFunc = getTargetIDForInputEvent;
329 } else {
src/eventPlugins/SelectEventPlugin.js
+37 -30
@@ -26,6 +26,7 @@ var SyntheticEvent = require('SyntheticEvent');
26
27 var getActiveElement = require('getActiveElement');
28 var isEventSupported = require('isEventSupported');
29 +var isTextInputElement = require('isTextInputElement');
30 var keyOf = require('keyOf');
31 var shallowEqual = require('shallowEqual');
32
@@ -106,30 +107,35 @@ function constructSelectEvent(nativeEvent) {
107 if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
108 lastSelection = currentSelection;
109
109 - return SyntheticEvent.getPooled(
110 + var syntheticEvent = SyntheticEvent.getPooled(
111 eventTypes.select,
112 activeElementID,
113 nativeEvent
114 );
115 +
116 + syntheticEvent.type = 'select';
117 + syntheticEvent.target = activeElement;
118 +
119 + EventPropagators.accumulateTwoPhaseDispatches(syntheticEvent);
120 +
121 + return syntheticEvent;
122 }
123 }
124
125 /**
126 * Handle deferred event. And manually dispatch synthetic events.
127 */
120 -function handleDeferredEvent() {
128 +function dispatchDeferredSelectEvent() {
129 if (!activeNativeEvent) {
130 return;
131 }
132
125 - var event = constructSelectEvent(activeNativeEvent);
133 + var syntheticEvent = constructSelectEvent(activeNativeEvent);
134 activeNativeEvent = null;
135
128 - if (event) {
129 - EventPropagators.accumulateTwoPhaseDispatches(event);
130 -
131 - // Enqueue and process the abstract event manually.
132 - EventPluginHub.enqueueEvents(event);
136 + // Enqueue and process the abstract event manually.
137 + if (syntheticEvent) {
138 + EventPluginHub.enqueueEvents(syntheticEvent);
139 EventPluginHub.processEventQueue();
140 }
141 }
@@ -139,8 +145,8 @@ function handleDeferredEvent() {
145 * across form elements.
146 *
147 * Supported elements are:
142 - * - input (see `supportedInputTypes`)
143 - * - TEXTAREA
148 + * - input (see `isTextInputElement`)
149 + * - textarea
150 * - contentEditable
151 *
152 * This differs from native browser implementations in the following ways:
@@ -167,12 +173,15 @@ var SelectEventPlugin = {
173 nativeEvent) {
174
175 switch (topLevelType) {
176 + // Track the input node that has focus.
177 case topLevelTypes.topFocus:
171 - // Track the input node that has focus.
172 - activeElement = topLevelTarget;
173 - activeElementID = topLevelTargetID;
174 - lastSelection = null;
175 - mouseDown = false;
178 + if (isTextInputElement(topLevelTarget) ||
179 + topLevelTarget.contentEditable === 'true') {
180 + activeElement = topLevelTarget;
181 + activeElementID = topLevelTargetID;
182 + lastSelection = null;
183 + mouseDown = false;
184 + }
185 break;
186 case topLevelTypes.topBlur:
187 activeElement = null;
@@ -180,29 +189,27 @@ var SelectEventPlugin = {
189 lastSelection = null;
190 mouseDown = false;
191 break;
183 - case topLevelTypes.topSelectionChange:
184 - // Chrome and IE fire non-standard event whenever selection is
185 - // changed (and sometimes when it hasn't).
186 - var event = constructSelectEvent(nativeEvent);
187 - if (event) {
188 - EventPropagators.accumulateTwoPhaseDispatches(event);
189 - return event;
190 - }
191 - break;
192 +
193 + // Don't fire the event while the user is dragging. This matches the
194 + // semantics of the native select event.
195 case topLevelTypes.topMouseDown:
193 - // Don't fire the event while the user is dragging.
196 mouseDown = true;
197 break;
198 case topLevelTypes.topMouseUp:
199 mouseDown = false;
198 - activeNativeEvent = nativeEvent;
199 - handleDeferredEvent.defer();
200 - break;
200 + return constructSelectEvent(nativeEvent);
201 +
202 + // Chrome and IE fire non-standard event when selection is changed (and
203 + // sometimes when it hasn't).
204 + case topLevelTypes.topSelectionChange:
205 + return constructSelectEvent(nativeEvent);
206 +
207 + // Firefox doesn't support selectionchange, so check selection status
208 + // after each key entry.
209 case topLevelTypes.topKeyDown:
202 - // For Firefox we check seleciton after each key.
210 if (!useSelectionChange) {
211 activeNativeEvent = nativeEvent;
205 - handleDeferredEvent.defer();
212 + setTimeout(dispatchDeferredSelectEvent, 0);
213 }
214 break;
215 }
src/utils/isTextInputElement.js new
+49
@@ -0,0 +1,49 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule isTextInputElement
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
23 + */
24 +var supportedInputTypes = {
25 + 'color': true,
26 + 'date': true,
27 + 'datetime': true,
28 + 'datetime-local': true,
29 + 'email': true,
30 + 'month': true,
31 + 'number': true,
32 + 'password': true,
33 + 'range': true,
34 + 'search': true,
35 + 'tel': true,
36 + 'text': true,
37 + 'time': true,
38 + 'url': true,
39 + 'week': true
40 +};
41 +
42 +function isTextInputElement(elem) {
43 + return elem && (
44 + (elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
45 + elem.nodeName === 'TEXTAREA'
46 + );
47 +}
48 +
49 +module.exports = isTextInputElement;