Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81155 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule CompositionEventPlugin
10
* @typechecks static-only
11
*/
12
13
"use strict";
14
15
var EventConstants = require('EventConstants');
16
var EventPropagators = require('EventPropagators');
17
var ExecutionEnvironment = require('ExecutionEnvironment');
18
var ReactInputSelection = require('ReactInputSelection');
19
var SyntheticCompositionEvent = require('SyntheticCompositionEvent');
20
21
var getTextContentAccessor = require('getTextContentAccessor');
22
var keyOf = require('keyOf');
23
24
var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
25
var START_KEYCODE = 229;
26
27
var useCompositionEvent = (
28
ExecutionEnvironment.canUseDOM &&
29
'CompositionEvent' in window
30
);
31
32
// In IE9+, we have access to composition events, but the data supplied
33
// by the native compositionend event may be incorrect. In Korean, for example,
34
// the compositionend event contains only one character regardless of
35
// how many characters have been composed since compositionstart.
36
// We therefore use the fallback data while still using the native
37
// events as triggers.
38
var useFallbackData = (
39
!useCompositionEvent ||
40
(
41
'documentMode' in document &&
42
document.documentMode > 8 &&
43
document.documentMode <= 11
44
)
45
);
46
47
var topLevelTypes = EventConstants.topLevelTypes;
48
var currentComposition = null;
49
50
// Events and their corresponding property names.
51
var eventTypes = {
52
compositionEnd: {
53
phasedRegistrationNames: {
54
bubbled: keyOf({onCompositionEnd: null}),
55
captured: keyOf({onCompositionEndCapture: null})
56
},
57
dependencies: [
58
topLevelTypes.topBlur,
59
topLevelTypes.topCompositionEnd,
60
topLevelTypes.topKeyDown,
61
topLevelTypes.topKeyPress,
62
topLevelTypes.topKeyUp,
63
topLevelTypes.topMouseDown
64
]
65
},
66
compositionStart: {
67
phasedRegistrationNames: {
68
bubbled: keyOf({onCompositionStart: null}),
69
captured: keyOf({onCompositionStartCapture: null})
70
},
71
dependencies: [
72
topLevelTypes.topBlur,
73
topLevelTypes.topCompositionStart,
74
topLevelTypes.topKeyDown,
75
topLevelTypes.topKeyPress,
76
topLevelTypes.topKeyUp,
77
topLevelTypes.topMouseDown
78
]
79
},
80
compositionUpdate: {
81
phasedRegistrationNames: {
82
bubbled: keyOf({onCompositionUpdate: null}),
83
captured: keyOf({onCompositionUpdateCapture: null})
84
},
85
dependencies: [
86
topLevelTypes.topBlur,
87
topLevelTypes.topCompositionUpdate,
88
topLevelTypes.topKeyDown,
89
topLevelTypes.topKeyPress,
90
topLevelTypes.topKeyUp,
91
topLevelTypes.topMouseDown
92
]
93
}
94
};
95
96
/**
97
* Translate native top level events into event types.
98
*
99
* @param {string} topLevelType
100
* @return {object}
101
*/
102
function getCompositionEventType(topLevelType) {
103
switch (topLevelType) {
104
case topLevelTypes.topCompositionStart:
105
return eventTypes.compositionStart;
106
case topLevelTypes.topCompositionEnd:
107
return eventTypes.compositionEnd;
108
case topLevelTypes.topCompositionUpdate:
109
return eventTypes.compositionUpdate;
110
}
111
}
112
113
/**
114
* Does our fallback best-guess model think this event signifies that
115
* composition has begun?
116
*
117
* @param {string} topLevelType
118
* @param {object} nativeEvent
119
* @return {boolean}
120
*/
121
function isFallbackStart(topLevelType, nativeEvent) {
122
return (
123
topLevelType === topLevelTypes.topKeyDown &&
124
nativeEvent.keyCode === START_KEYCODE
125
);
126
}
127
128
/**
129
* Does our fallback mode think that this event is the end of composition?
130
*
131
* @param {string} topLevelType
132
* @param {object} nativeEvent
133
* @return {boolean}
134
*/
135
function isFallbackEnd(topLevelType, nativeEvent) {
136
switch (topLevelType) {
137
case topLevelTypes.topKeyUp:
138
// Command keys insert or clear IME input.
139
return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1);
140
case topLevelTypes.topKeyDown:
141
// Expect IME keyCode on each keydown. If we get any other
142
// code we must have exited earlier.
143
return (nativeEvent.keyCode !== START_KEYCODE);
144
case topLevelTypes.topKeyPress:
145
case topLevelTypes.topMouseDown:
146
case topLevelTypes.topBlur:
147
// Events are not possible without cancelling IME.
148
return true;
149
default:
150
return false;
151
}
152
}
153
154
/**
155
* Helper class stores information about selection and document state
156
* so we can figure out what changed at a later date.
157
*
158
* @param {DOMEventTarget} root
159
*/
160
function FallbackCompositionState(root) {
161
this.root = root;
162
this.startSelection = ReactInputSelection.getSelection(root);
163
this.startValue = this.getText();
164
}
165
166
/**
167
* Get current text of input.
168
*
169
* @return {string}
170
*/
171
FallbackCompositionState.prototype.getText = function() {
172
return this.root.value || this.root[getTextContentAccessor()];
173
};
174
175
/**
176
* Text that has changed since the start of composition.
177
*
178
* @return {string}
179
*/
180
FallbackCompositionState.prototype.getData = function() {
181
var endValue = this.getText();
182
var prefixLength = this.startSelection.start;
183
var suffixLength = this.startValue.length - this.startSelection.end;
184
185
return endValue.substr(
186
prefixLength,
187
endValue.length - suffixLength - prefixLength
188
);
189
};
190
191
/**
192
* This plugin creates `onCompositionStart`, `onCompositionUpdate` and
193
* `onCompositionEnd` events on inputs, textareas and contentEditable
194
* nodes.
195
*/
196
var CompositionEventPlugin = {
197
198
eventTypes: eventTypes,
199
200
/**
201
* @param {string} topLevelType Record from `EventConstants`.
202
* @param {DOMEventTarget} topLevelTarget The listening component root node.
203
* @param {string} topLevelTargetID ID of `topLevelTarget`.
204
* @param {object} nativeEvent Native browser event.
205
* @return {*} An accumulation of synthetic events.
206
* @see {EventPluginHub.extractEvents}
207
*/
208
extractEvents: function(
209
topLevelType,
210
topLevelTarget,
211
topLevelTargetID,
212
nativeEvent) {
213
214
var eventType;
215
var data;
216
217
if (useCompositionEvent) {
218
eventType = getCompositionEventType(topLevelType);
219
} else if (!currentComposition) {
220
if (isFallbackStart(topLevelType, nativeEvent)) {
221
eventType = eventTypes.compositionStart;
222
}
223
} else if (isFallbackEnd(topLevelType, nativeEvent)) {
224
eventType = eventTypes.compositionEnd;
225
}
226
227
if (useFallbackData) {
228
// The current composition is stored statically and must not be
229
// overwritten while composition continues.
230
if (!currentComposition && eventType === eventTypes.compositionStart) {
231
currentComposition = new FallbackCompositionState(topLevelTarget);
232
} else if (eventType === eventTypes.compositionEnd) {
233
if (currentComposition) {
234
data = currentComposition.getData();
235
currentComposition = null;
236
}
237
}
238
}
239
240
if (eventType) {
241
var event = SyntheticCompositionEvent.getPooled(
242
eventType,
243
topLevelTargetID,
244
nativeEvent
245
);
246
if (data) {
247
// Inject data generated from fallback path into the synthetic event.
248
// This matches the property of native CompositionEventInterface.
249
event.data = data;
250
}
251
EventPropagators.accumulateTwoPhaseDispatches(event);
252
return event;
253
}
254
}
255
};
256
257
module.exports = CompositionEventPlugin;
258
259