select event plugin
一、作用
二、注册事件
备注
registerTwoPhaseEvent()由 EventRegistry#registerTwoPhaseEvent 实现
function registerEvents() {
registerTwoPhaseEvent('onSelect', [
'focusout',
'contextmenu',
'dragend',
'focusin',
'keydown',
'keyup',
'mousedown',
'mouseup',
'selectionchange',
]);
}
三、提取事件
备注
getNodeFromInstance()由 ReactDOMComponentTree#getNodeFromInstance 实现isTextInputElement()由 isTextInputElement 实现
/**
* This plugin creates an `onSelect` event that normalizes select events
* across form elements.
*
* 这个插件创建了一个 `onSelect` 事件,用于规范化表单元素中的选择事件。
*
* Supported elements are:
* - input (see `isTextInputElement`)
* - textarea
* - contentEditable
*
* 支持的元素有:
* - 输入框(参见 `isTextInputElement`)
* - 文本区域
* - 可编辑内容
*
* This differs from native browser implementations in the following ways:
* - Fires on contentEditable fields as well as inputs.
* - Fires for collapsed selection.
* - Fires after user input.
*
* 这与浏览器原生实现的不同之处如下:
* - 会在 contentEditable 字段以及输入框触发。
* - 会在选区折叠时触发。
* - 会在用户输入后触发。
*/
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
) {
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
switch (domEventName) {
// Track the input node that has focus.
// 跟踪获得焦点的输入节点。
case 'focusin':
if (
isTextInputElement(targetNode as any) ||
targetNode.contentEditable === 'true'
) {
activeElement = targetNode;
activeElementInst = targetInst;
lastSelection = null;
}
break;
case 'focusout':
activeElement = null;
activeElementInst = null;
lastSelection = null;
break;
// Don't fire the event while the user is dragging. This matches the
// semantics of the native select event.
// 用户拖动时不要触发事件。这与原生选择事件的语义一致。
case 'mousedown':
mouseDown = true;
break;
case 'contextmenu':
case 'mouseup':
case 'dragend':
mouseDown = false;
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
break;
// Chrome and IE fire non-standard event when selection is changed (and
// sometimes when it hasn't). IE's event fires out of order with respect
// to key and input events on deletion, so we discard it.
//
// 当选择更改时,Chrome 和 IE 会触发非标准事件(有时即使没有更改也会触发)。
// 在删除操作中,IE 的事件触发顺序与键盘和输入事件不一致,所以我们会忽略它。
//
// Firefox doesn't support selectionchange, so check selection status
// after each key entry. The selection changes after keydown and before
// keyup, but we check on keydown as well in the case of holding down a
// key, when multiple keydown events are fired but only one keyup is.
// This is also our approach for IE handling, for the reason above.
// Firefox 不支持 selectionchange,所以在每次按键输入后检查选择状态。
// 选择会在 keydown 之后、keyup 之前发生变化,但我们也在 keydown 时检查,
// 以防按住键时会触发多个 keydown 事件但只有一个 keyup。
// 出于上述原因,这也是我们处理 IE 的方法。
case 'selectionchange':
if (skipSelectionChangeEvent) {
break;
}
// falls through
// 贯穿
case 'keydown':
case 'keyup':
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
}
}
四、常量
1. 跳过选择更改事件
备注
源码中 29 - 30 行
canUseDOM()由 ExecutionEnvironment#canUseDOM 实现
const skipSelectionChangeEvent =
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
五、变量
1. 活动元素
备注
源码中 46 - 49 行
// 活动元素
let activeElement = null;
// 活动元素实例
let activeElementInst = null;
// 上次选择
let lastSelection = null;
// 按下鼠标
let mouseDown = false;
六、工具
1. 获取选区
备注
hasSelectionCapabilities()由 ReactInputSelection#hasSelectionCapabilities 实现
/**
* Get an object which is a unique representation of the current selection.
* 获取一个对象,该对象是当前选择的唯一表示。
*
* The return value will not be consistent across nodes or browsers, but
* two identical selections on the same node will return identical objects.
* 返回值在不同节点或浏览器中可能不一致,但在同一节点上两次相同的选择将返回相同的对象。
*/
function getSelection(node: any) {
if ('selectionStart' in node && hasSelectionCapabilities(node)) {
return {
start: node.selectionStart,
end: node.selectionEnd,
};
} else {
const win =
(node.ownerDocument && node.ownerDocument.defaultView) || window;
const selection = win.getSelection();
return {
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset,
};
}
}
2. 获取事件目标文档
/**
* Get document associated with the event target.
* 获取与事件目标关联的文档。
*/
function getEventTargetDocument(eventTarget: any) {
return eventTarget.window === eventTarget
? eventTarget.document
: eventTarget.nodeType === DOCUMENT_NODE
? eventTarget
: eventTarget.ownerDocument;
}
3. 构建选择事件
备注
getActiveElement()由 getActionElement 实现shallowEqual()由 shallowEqual 实现SyntheticEvent()由 SyntheticEvent#SyntheticEvent 实现accumulateTwoPhaseListeners()由 DOMPluginEventSystem#accumulateTwoPhaseListeners 提供
/**
* Poll selection to see whether it's changed.
* 投票选择以查看是否有变化。
*
* @param {object} nativeEvent
* @param {object} nativeEventTarget
* @return {?SyntheticEvent}
*/
function constructSelectEvent(
dispatchQueue: DispatchQueue,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
) {
// Ensure we have the right element, and that the user is not dragging a
// selection (this matches native `select` event behavior). In HTML5, select
// fires only on input and textarea thus if there's no focused element we
// won't dispatch.
// 确保我们有正确的元素,并且用户没有拖动选择范围(这与原生 `select` 事件的行为一致)。在
// HTML5 中,select 事件只会在 input 和 textarea 上触发,因此如果没有焦点元素,我们就不会
// 分发事件。
const doc = getEventTargetDocument(nativeEventTarget);
if (
mouseDown ||
activeElement == null ||
activeElement !== getActiveElement(doc)
) {
return;
}
// Only fire when selection has actually changed.
// 只有在选择实际改变时才触发。
const currentSelection = getSelection(activeElement);
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
lastSelection = currentSelection;
const listeners = accumulateTwoPhaseListeners(
activeElementInst,
'onSelect',
);
if (listeners.length > 0) {
const event: ReactSyntheticEvent = new SyntheticEvent(
'onSelect',
'select',
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({ event, listeners });
event.target = activeElement;
}
}
}