Match-id-a3b2eeb82bb9e33e7bdf2d85c9fb79f7ed120f69
This commit is contained in:
commit
dbb26947d9
|
@ -0,0 +1,24 @@
|
||||||
|
// 处理事件的错误
|
||||||
|
let hasError = false;
|
||||||
|
let caughtError = null;
|
||||||
|
|
||||||
|
// 执行事件监听器,并且捕捉第一个错误,事件执行完成后抛出第一个错误
|
||||||
|
export function runListenerAndCatchFirstError(listener, event) {
|
||||||
|
try {
|
||||||
|
listener(event);
|
||||||
|
} catch (error) {
|
||||||
|
if (!hasError) {
|
||||||
|
hasError = true;
|
||||||
|
caughtError = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function throwCaughtEventError() {
|
||||||
|
if (hasError) {
|
||||||
|
const err = caughtError;
|
||||||
|
caughtError = null;
|
||||||
|
hasError = false;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
import type {AnyNativeEvent, ProcessingListenerList} from './types';
|
||||||
|
import type {VNode} from '../renderer/Types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommonEventToHorizonMap,
|
||||||
|
horizonEventToNativeMap,
|
||||||
|
EVENT_TYPE_BUBBLE,
|
||||||
|
EVENT_TYPE_CAPTURE,
|
||||||
|
} from './const';
|
||||||
|
import {
|
||||||
|
throwCaughtEventError,
|
||||||
|
runListenerAndCatchFirstError,
|
||||||
|
} from './EventError';
|
||||||
|
import {getListeners as getBeforeInputListeners} from './simulatedEvtHandler/BeforeInputEventHandler';
|
||||||
|
import {getListeners as getCompositionListeners} from './simulatedEvtHandler/CompositionEventHandler';
|
||||||
|
import {getListeners as getChangeListeners} from './simulatedEvtHandler/ChangeEventHandler';
|
||||||
|
import {getListeners as getSelectionListeners} from './simulatedEvtHandler/SelectionEventHandler';
|
||||||
|
import {
|
||||||
|
getCustomEventNameWithOn,
|
||||||
|
uniqueCharCode,
|
||||||
|
getEventTarget
|
||||||
|
} from './utils';
|
||||||
|
import {createCommonCustomEvent} from './customEvents/EventFactory';
|
||||||
|
import {getListenersFromTree} from './ListenerGetter';
|
||||||
|
import {shouldUpdateValue, updateControlledValue} from './ControlledValueUpdater';
|
||||||
|
import {asyncUpdates, runDiscreteUpdates} from '../renderer/Renderer';
|
||||||
|
import {getExactNode} from '../renderer/vnode/VNodeUtils';
|
||||||
|
|
||||||
|
// 获取事件触发的普通事件监听方法队列
|
||||||
|
function getCommonListeners(
|
||||||
|
nativeEvtName: string,
|
||||||
|
vNode: null | VNode,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
target: null | EventTarget,
|
||||||
|
isCapture: boolean,
|
||||||
|
): ProcessingListenerList {
|
||||||
|
const customEventName = getCustomEventNameWithOn(CommonEventToHorizonMap[nativeEvtName]);
|
||||||
|
if (!customEventName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 火狐浏览器兼容。火狐浏览器下功能键将触发keypress事件 火狐下keypress的charcode有值,keycode为0
|
||||||
|
if (nativeEvtName === 'keypress' && uniqueCharCode(nativeEvent) === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标点击右键
|
||||||
|
if (nativeEvent instanceof MouseEvent && nativeEvtName === 'click' && nativeEvent.button === 2) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nativeEvtName === 'focusin') {
|
||||||
|
nativeEvtName = 'focus';
|
||||||
|
}
|
||||||
|
if (nativeEvtName === 'focusout') {
|
||||||
|
nativeEvtName = 'blur';
|
||||||
|
}
|
||||||
|
|
||||||
|
const customEvent = createCommonCustomEvent(customEventName, nativeEvtName, nativeEvent, null, target);
|
||||||
|
return getListenersFromTree(
|
||||||
|
vNode,
|
||||||
|
customEventName,
|
||||||
|
customEvent,
|
||||||
|
isCapture ? EVENT_TYPE_CAPTURE: EVENT_TYPE_BUBBLE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按顺序执行事件队列
|
||||||
|
export function processListeners(
|
||||||
|
processingEventsList: ProcessingListenerList
|
||||||
|
): void {
|
||||||
|
processingEventsList.forEach(eventUnitList => {
|
||||||
|
let lastVNode;
|
||||||
|
eventUnitList.forEach(eventUnit => {
|
||||||
|
const {vNode, currentTarget, listener, event} = eventUnit;
|
||||||
|
if (vNode !== lastVNode && event.isPropagationStopped()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.currentTarget = currentTarget;
|
||||||
|
runListenerAndCatchFirstError(listener, event);
|
||||||
|
event.currentTarget = null;
|
||||||
|
lastVNode = vNode;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 执行所有事件后,重新throw遇到的第一个错误
|
||||||
|
throwCaughtEventError();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProcessListenersFacade(
|
||||||
|
nativeEvtName: string,
|
||||||
|
vNode: VNode,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
target,
|
||||||
|
isCapture: boolean
|
||||||
|
): ProcessingListenerList {
|
||||||
|
// 触发普通委托事件
|
||||||
|
let processingListenerList: ProcessingListenerList = getCommonListeners(
|
||||||
|
nativeEvtName,
|
||||||
|
vNode,
|
||||||
|
nativeEvent,
|
||||||
|
target,
|
||||||
|
isCapture,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 触发特殊handler委托事件
|
||||||
|
if (!isCapture) {
|
||||||
|
if (horizonEventToNativeMap.get('onChange').includes(nativeEvtName)) {
|
||||||
|
processingListenerList = processingListenerList.concat(getChangeListeners(
|
||||||
|
nativeEvtName,
|
||||||
|
nativeEvent,
|
||||||
|
vNode,
|
||||||
|
target,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizonEventToNativeMap.get('onSelect').includes(nativeEvtName)) {
|
||||||
|
processingListenerList = processingListenerList.concat(getSelectionListeners(
|
||||||
|
nativeEvtName,
|
||||||
|
nativeEvent,
|
||||||
|
vNode,
|
||||||
|
target,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nativeEvtName === 'compositionend' || nativeEvtName === 'compositionstart' || nativeEvtName === 'compositionupdate') {
|
||||||
|
processingListenerList = processingListenerList.concat(getCompositionListeners(
|
||||||
|
nativeEvtName,
|
||||||
|
nativeEvent,
|
||||||
|
vNode,
|
||||||
|
target,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizonEventToNativeMap.get('onBeforeInput').includes(nativeEvtName)) {
|
||||||
|
processingListenerList = processingListenerList.concat(getBeforeInputListeners(
|
||||||
|
nativeEvtName,
|
||||||
|
nativeEvent,
|
||||||
|
vNode,
|
||||||
|
target,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return processingListenerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发可以被执行的horizon事件监听
|
||||||
|
function triggerHorizonEvents(
|
||||||
|
nativeEvtName: string,
|
||||||
|
isCapture: boolean,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
vNode: null | VNode,
|
||||||
|
): void {
|
||||||
|
const nativeEventTarget = getEventTarget(nativeEvent);
|
||||||
|
const processingListenerList = getProcessListenersFacade(nativeEvtName, vNode, nativeEvent, nativeEventTarget, isCapture);
|
||||||
|
|
||||||
|
// 处理触发的事件队列
|
||||||
|
processListeners(processingListenerList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 其他事件正在执行中标记
|
||||||
|
let isInEventsExecution = false;
|
||||||
|
|
||||||
|
export function handleEventMain(
|
||||||
|
nativeEvtName: string,
|
||||||
|
isCapture: boolean,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
vNode: null | VNode,
|
||||||
|
target: EventTarget,
|
||||||
|
): void {
|
||||||
|
let rootVNode = vNode;
|
||||||
|
if (vNode !== null) {
|
||||||
|
rootVNode = getExactNode(vNode, target);
|
||||||
|
if (!rootVNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有事件正在执行,同步执行事件
|
||||||
|
if (isInEventsExecution) {
|
||||||
|
return triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, rootVNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有事件在执行,经过调度再执行事件
|
||||||
|
isInEventsExecution = true;
|
||||||
|
try {
|
||||||
|
return asyncUpdates(() =>
|
||||||
|
triggerHorizonEvents(
|
||||||
|
nativeEvtName,
|
||||||
|
isCapture,
|
||||||
|
nativeEvent,
|
||||||
|
rootVNode,
|
||||||
|
));
|
||||||
|
} finally {
|
||||||
|
isInEventsExecution = false;
|
||||||
|
if (shouldUpdateValue()) {
|
||||||
|
runDiscreteUpdates();
|
||||||
|
updateControlledValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
import {VNode} from '../renderer/Types';
|
||||||
|
import {DomComponent} from '../renderer/vnode/VNodeTags';
|
||||||
|
import {throwIfTrue} from '../renderer/utils/throwIfTrue';
|
||||||
|
import type {Props} from '../dom/DOMOperator';
|
||||||
|
import {EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE} from './const';
|
||||||
|
import {ProcessingListenerList, ListenerUnitList} from './Types';
|
||||||
|
import {CustomBaseEvent} from './customEvents/CustomBaseEvent';
|
||||||
|
|
||||||
|
// 返回是否应该阻止事件响应标记,disabled组件不响应鼠标事件
|
||||||
|
function shouldPrevent(
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
props: Props,
|
||||||
|
): boolean {
|
||||||
|
const canPreventMouseEvents = [
|
||||||
|
'onClick',
|
||||||
|
'onClickCapture',
|
||||||
|
'onDoubleClick',
|
||||||
|
'onDoubleClickCapture',
|
||||||
|
'onMouseDown',
|
||||||
|
'onMouseDownCapture',
|
||||||
|
'onMouseMove',
|
||||||
|
'onMouseMoveCapture',
|
||||||
|
'onMouseUp',
|
||||||
|
'onMouseUpCapture',
|
||||||
|
'onMouseEnter',
|
||||||
|
];
|
||||||
|
const interActiveElements = ['button', 'input', 'select', 'textarea'];
|
||||||
|
if (canPreventMouseEvents.includes(name)) {
|
||||||
|
return !!(props.disabled && interActiveElements.includes(type));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从vnode属性中获取事件listener
|
||||||
|
function getListener(
|
||||||
|
vNode: VNode,
|
||||||
|
eventName: string,
|
||||||
|
): Function | null {
|
||||||
|
const realNode = vNode.realNode;
|
||||||
|
if (realNode === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const props = vNode.props;
|
||||||
|
if (props === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const listener = props[eventName];
|
||||||
|
if (shouldPrevent(eventName, vNode.type, props)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throwIfTrue(
|
||||||
|
listener && typeof listener !== 'function',
|
||||||
|
'`%s` listener should be a function.',
|
||||||
|
eventName
|
||||||
|
);
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取监听事件
|
||||||
|
export function getListenersFromTree(
|
||||||
|
targetVNode: VNode | null,
|
||||||
|
name: string | null,
|
||||||
|
horizonEvent: CustomBaseEvent,
|
||||||
|
eventType: string,
|
||||||
|
): ProcessingListenerList {
|
||||||
|
if (!name) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const captureName = name + EVENT_TYPE_CAPTURE;
|
||||||
|
const listeners: ListenerUnitList = [];
|
||||||
|
|
||||||
|
let vNode = targetVNode;
|
||||||
|
|
||||||
|
// 从目标节点到根节点遍历获取listener
|
||||||
|
while (vNode !== null) {
|
||||||
|
const {realNode, tag} = vNode;
|
||||||
|
if (tag === DomComponent && realNode !== null) {
|
||||||
|
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) {
|
||||||
|
const captureListener = getListener(vNode, captureName);
|
||||||
|
if (captureListener) {
|
||||||
|
listeners.unshift({
|
||||||
|
vNode,
|
||||||
|
listener: captureListener,
|
||||||
|
currentTarget: realNode,
|
||||||
|
event: horizonEvent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) {
|
||||||
|
const bubbleListener = getListener(vNode, name);
|
||||||
|
if (bubbleListener) {
|
||||||
|
listeners.push({
|
||||||
|
vNode,
|
||||||
|
listener: bubbleListener,
|
||||||
|
currentTarget: realNode,
|
||||||
|
event: horizonEvent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vNode = vNode.parent;
|
||||||
|
}
|
||||||
|
return listeners.length > 0 ? [listeners]: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* style中的动画事件
|
||||||
|
*/
|
||||||
|
|
||||||
|
// style事件浏览器兼容前缀
|
||||||
|
const vendorPrefixes = {
|
||||||
|
animationend: {
|
||||||
|
MozAnimation: 'mozAnimationEnd',
|
||||||
|
WebkitAnimation: 'webkitAnimationEnd',
|
||||||
|
animation: 'animationend',
|
||||||
|
},
|
||||||
|
animationiteration: {
|
||||||
|
MozAnimation: 'mozAnimationIteration',
|
||||||
|
WebkitAnimation: 'webkitAnimationIteration',
|
||||||
|
animation: 'animationiteration',
|
||||||
|
},
|
||||||
|
animationstart: {
|
||||||
|
MozAnimation: 'mozAnimationStart',
|
||||||
|
WebkitAnimation: 'webkitAnimationStart',
|
||||||
|
animation: 'animationstart',
|
||||||
|
},
|
||||||
|
transitionend: {
|
||||||
|
MozTransition: 'mozTransitionEnd',
|
||||||
|
WebkitTransition: 'webkitTransitionEnd',
|
||||||
|
transition: 'transitionend',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取属性中对应事件名
|
||||||
|
function getEventNameByStyle(eventName) {
|
||||||
|
const prefixMap = vendorPrefixes[eventName];
|
||||||
|
if (!prefixMap) {
|
||||||
|
return eventName;
|
||||||
|
}
|
||||||
|
const style = document.createElement('div').style
|
||||||
|
for (const styleProp in prefixMap) {
|
||||||
|
if (styleProp in style) {
|
||||||
|
return prefixMap[styleProp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STYLE_AMT_END: string = getEventNameByStyle(
|
||||||
|
'animationend',
|
||||||
|
);
|
||||||
|
export const STYLE_AMT_ITERATION: string = getEventNameByStyle(
|
||||||
|
'animationiteration',
|
||||||
|
);
|
||||||
|
export const STYLE_AMT_START: string = getEventNameByStyle(
|
||||||
|
'animationstart',
|
||||||
|
);
|
||||||
|
export const STYLE_TRANS_END: string = getEventNameByStyle(
|
||||||
|
'transitionend',
|
||||||
|
);
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
import type {VNode} from '../renderer/Types';
|
||||||
|
import {CustomBaseEvent} from './customEvents/CustomBaseEvent';
|
||||||
|
|
||||||
|
export type AnyNativeEvent = KeyboardEvent | MouseEvent | TouchEvent | UIEvent | Event;
|
||||||
|
|
||||||
|
export type ListenerUnit = {
|
||||||
|
vNode: null | VNode,
|
||||||
|
listener: Function,
|
||||||
|
currentTarget: EventTarget,
|
||||||
|
event: CustomBaseEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListenerUnitList = Array<ListenerUnit>;
|
||||||
|
|
||||||
|
export type ProcessingListenerList = Array<ListenerUnitList>;
|
|
@ -0,0 +1,41 @@
|
||||||
|
import {isMounted} from '../renderer/vnode/VNodeUtils';
|
||||||
|
import {SuspenseComponent} from '../renderer/vnode/VNodeTags';
|
||||||
|
import {getNearestVNode} from '../dom/DOMInternalKeys';
|
||||||
|
import {handleEventMain} from './HorizonEventMain';
|
||||||
|
import {runDiscreteUpdates} from '../renderer/Renderer';
|
||||||
|
import {getEventTarget} from './utils';
|
||||||
|
|
||||||
|
// 生成委托事件的监听方法
|
||||||
|
export function createCustomEventListener(
|
||||||
|
target: EventTarget,
|
||||||
|
nativeEvtName: string,
|
||||||
|
isCapture: boolean,
|
||||||
|
): EventListener {
|
||||||
|
return triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发委托事件
|
||||||
|
function triggerDelegatedEvent(
|
||||||
|
nativeEvtName: string,
|
||||||
|
isCapture: boolean,
|
||||||
|
targetDom: EventTarget,
|
||||||
|
nativeEvent,
|
||||||
|
) {
|
||||||
|
// 执行之前的调度事件
|
||||||
|
runDiscreteUpdates();
|
||||||
|
|
||||||
|
const nativeEventTarget = getEventTarget(nativeEvent);
|
||||||
|
let targetVNode = getNearestVNode(nativeEventTarget);
|
||||||
|
|
||||||
|
if (targetVNode !== null) {
|
||||||
|
if (isMounted(targetVNode)) {
|
||||||
|
if (targetVNode.tag === SuspenseComponent) {
|
||||||
|
targetVNode = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// vnode已销毁
|
||||||
|
targetVNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleEventMain(nativeEvtName, isCapture, nativeEvent, targetVNode, targetDom);
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {isText} from '../dom/utils/Common';
|
||||||
|
import { CHAR_CODE_ENTER, CHAR_CODE_SPACE } from './const';
|
||||||
|
|
||||||
|
export function uniqueCharCode(nativeEvent): number {
|
||||||
|
let charCode = nativeEvent.charCode;
|
||||||
|
|
||||||
|
// 火狐浏览器没有设置enter键的charCode,用keyCode
|
||||||
|
if (charCode === 0 && nativeEvent.keyCode === CHAR_CODE_ENTER) {
|
||||||
|
charCode = CHAR_CODE_ENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当ctrl按下时10表示enter键按下
|
||||||
|
if (charCode === 10) {
|
||||||
|
charCode = CHAR_CODE_ENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略非打印的Enter键
|
||||||
|
if (charCode >= CHAR_CODE_SPACE || charCode === CHAR_CODE_ENTER) {
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取事件的target对象
|
||||||
|
export function getEventTarget(nativeEvent) {
|
||||||
|
const target = nativeEvent.target || nativeEvent.srcElement || window;
|
||||||
|
if (isText(target)) {
|
||||||
|
return target.parentNode;
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持的输入框类型
|
||||||
|
const supportedInputTypes = ['color', 'date', 'datetime', 'datetime-local', 'email', 'month',
|
||||||
|
'number', 'password', 'range', 'search', 'tel', 'text', 'time', 'url', 'week'];
|
||||||
|
|
||||||
|
export function isTextInputElement(dom?: HTMLElement): boolean {
|
||||||
|
if (dom instanceof HTMLInputElement) {
|
||||||
|
return supportedInputTypes.includes(dom.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeName = dom && dom.nodeName && dom.nodeName.toLowerCase();
|
||||||
|
return nodeName === 'textarea';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 例:dragEnd -> onDragEnd
|
||||||
|
export function getCustomEventNameWithOn(name) {
|
||||||
|
if (!name) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const capitalizedEvent = name[0].toUpperCase() + name.slice(1);
|
||||||
|
const horizonEventName = 'on' + capitalizedEvent;
|
||||||
|
return horizonEventName;
|
||||||
|
}
|
Loading…
Reference in New Issue