UNPKG

6.06 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, 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 */
10
11'use strict';
12
13var EventPropagators = require('./EventPropagators');
14var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
15var ReactDOMComponentTree = require('./ReactDOMComponentTree');
16var ReactInputSelection = require('./ReactInputSelection');
17var SyntheticEvent = require('./SyntheticEvent');
18
19var getActiveElement = require('fbjs/lib/getActiveElement');
20var isTextInputElement = require('./isTextInputElement');
21var shallowEqual = require('fbjs/lib/shallowEqual');
22
23var skipSelectionChangeEvent = ExecutionEnvironment.canUseDOM && 'documentMode' in document && document.documentMode <= 11;
24
25var eventTypes = {
26 select: {
27 phasedRegistrationNames: {
28 bubbled: 'onSelect',
29 captured: 'onSelectCapture'
30 },
31 dependencies: ['topBlur', 'topContextMenu', 'topFocus', 'topKeyDown', 'topKeyUp', 'topMouseDown', 'topMouseUp', 'topSelectionChange']
32 }
33};
34
35var activeElement = null;
36var activeElementInst = null;
37var lastSelection = null;
38var mouseDown = false;
39
40// Track whether a listener exists for this plugin. If none exist, we do
41// not extract events. See #3639.
42var hasListener = false;
43
44/**
45 * Get an object which is a unique representation of the current selection.
46 *
47 * The return value will not be consistent across nodes or browsers, but
48 * two identical selections on the same node will return identical objects.
49 *
50 * @param {DOMElement} node
51 * @return {object}
52 */
53function getSelection(node) {
54 if ('selectionStart' in node && ReactInputSelection.hasSelectionCapabilities(node)) {
55 return {
56 start: node.selectionStart,
57 end: node.selectionEnd
58 };
59 } else if (window.getSelection) {
60 var selection = window.getSelection();
61 return {
62 anchorNode: selection.anchorNode,
63 anchorOffset: selection.anchorOffset,
64 focusNode: selection.focusNode,
65 focusOffset: selection.focusOffset
66 };
67 } else if (document.selection) {
68 var range = document.selection.createRange();
69 return {
70 parentElement: range.parentElement(),
71 text: range.text,
72 top: range.boundingTop,
73 left: range.boundingLeft
74 };
75 }
76}
77
78/**
79 * Poll selection to see whether it's changed.
80 *
81 * @param {object} nativeEvent
82 * @return {?SyntheticEvent}
83 */
84function constructSelectEvent(nativeEvent, nativeEventTarget) {
85 // Ensure we have the right element, and that the user is not dragging a
86 // selection (this matches native `select` event behavior). In HTML5, select
87 // fires only on input and textarea thus if there's no focused element we
88 // won't dispatch.
89 if (mouseDown || activeElement == null || activeElement !== getActiveElement()) {
90 return null;
91 }
92
93 // Only fire when selection has actually changed.
94 var currentSelection = getSelection(activeElement);
95 if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
96 lastSelection = currentSelection;
97
98 var syntheticEvent = SyntheticEvent.getPooled(eventTypes.select, activeElementInst, nativeEvent, nativeEventTarget);
99
100 syntheticEvent.type = 'select';
101 syntheticEvent.target = activeElement;
102
103 EventPropagators.accumulateTwoPhaseDispatches(syntheticEvent);
104
105 return syntheticEvent;
106 }
107
108 return null;
109}
110
111/**
112 * This plugin creates an `onSelect` event that normalizes select events
113 * across form elements.
114 *
115 * Supported elements are:
116 * - input (see `isTextInputElement`)
117 * - textarea
118 * - contentEditable
119 *
120 * This differs from native browser implementations in the following ways:
121 * - Fires on contentEditable fields as well as inputs.
122 * - Fires for collapsed selection.
123 * - Fires after user input.
124 */
125var SelectEventPlugin = {
126 eventTypes: eventTypes,
127
128 extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
129 if (!hasListener) {
130 return null;
131 }
132
133 var targetNode = targetInst ? ReactDOMComponentTree.getNodeFromInstance(targetInst) : window;
134
135 switch (topLevelType) {
136 // Track the input node that has focus.
137 case 'topFocus':
138 if (isTextInputElement(targetNode) || targetNode.contentEditable === 'true') {
139 activeElement = targetNode;
140 activeElementInst = targetInst;
141 lastSelection = null;
142 }
143 break;
144 case 'topBlur':
145 activeElement = null;
146 activeElementInst = null;
147 lastSelection = null;
148 break;
149 // Don't fire the event while the user is dragging. This matches the
150 // semantics of the native select event.
151 case 'topMouseDown':
152 mouseDown = true;
153 break;
154 case 'topContextMenu':
155 case 'topMouseUp':
156 mouseDown = false;
157 return constructSelectEvent(nativeEvent, nativeEventTarget);
158 // Chrome and IE fire non-standard event when selection is changed (and
159 // sometimes when it hasn't). IE's event fires out of order with respect
160 // to key and input events on deletion, so we discard it.
161 //
162 // Firefox doesn't support selectionchange, so check selection status
163 // after each key entry. The selection changes after keydown and before
164 // keyup, but we check on keydown as well in the case of holding down a
165 // key, when multiple keydown events are fired but only one keyup is.
166 // This is also our approach for IE handling, for the reason above.
167 case 'topSelectionChange':
168 if (skipSelectionChangeEvent) {
169 break;
170 }
171 // falls through
172 case 'topKeyDown':
173 case 'topKeyUp':
174 return constructSelectEvent(nativeEvent, nativeEventTarget);
175 }
176
177 return null;
178 },
179
180 didPutListener: function (inst, registrationName, listener) {
181 if (registrationName === 'onSelect') {
182 hasListener = true;
183 }
184 }
185};
186
187module.exports = SelectEventPlugin;
\No newline at end of file