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;