Match-id-7ac125241fa134ee7baaca41cfcb10bee8c65f65
This commit is contained in:
parent
19a39513e6
commit
d76fcbb8ff
|
@ -0,0 +1,39 @@
|
||||||
|
import {getVNodeProps} from '../dom/DOMInternalKeys';
|
||||||
|
import {resetValue} from '../dom/valueHandler';
|
||||||
|
import {getDomTag} from '../dom/utils/Common';
|
||||||
|
|
||||||
|
let updateList = null;
|
||||||
|
|
||||||
|
// 受控组件值重新赋值
|
||||||
|
function updateValue(target: Element) {
|
||||||
|
const props = getVNodeProps(target);
|
||||||
|
if (props) {
|
||||||
|
const type = getDomTag(target);
|
||||||
|
resetValue(target, type, props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储队列中缓存组件
|
||||||
|
export function addValueUpdateList(target: EventTarget): void {
|
||||||
|
if (updateList) {
|
||||||
|
updateList.push(target);
|
||||||
|
} else {
|
||||||
|
updateList = [target];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否需要重新赋值
|
||||||
|
export function shouldUpdateValue(): boolean {
|
||||||
|
return updateList !== null && updateList.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从缓存队列中对受控组件进行赋值
|
||||||
|
export function updateControlledValue() {
|
||||||
|
if (!updateList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateList.forEach(item => {
|
||||||
|
updateValue(item);
|
||||||
|
});
|
||||||
|
updateList = null;
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* 事件绑定实现
|
||||||
|
*/
|
||||||
|
import {allDelegatedNativeEvents} from './EventCollection';
|
||||||
|
import {isDocument} from '../dom/utils/Common';
|
||||||
|
import {
|
||||||
|
getEventListeners,
|
||||||
|
getEventToListenerMap,
|
||||||
|
} from '../dom/DOMInternalKeys';
|
||||||
|
import {createCustomEventListener} from './WrapperListener';
|
||||||
|
import {CustomBaseEvent} from './customEvents/CustomBaseEvent';
|
||||||
|
|
||||||
|
const listeningMarker =
|
||||||
|
'_horizonListening' +
|
||||||
|
Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.slice(4);
|
||||||
|
|
||||||
|
// 获取节点上已经委托事件名称
|
||||||
|
function getListenerSetKey(nativeEvtName: string, isCapture: boolean): string {
|
||||||
|
const sufix = isCapture ? 'capture' : 'bubble';
|
||||||
|
return `${nativeEvtName}__${sufix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listenToNativeEvent(
|
||||||
|
nativeEvtName: string,
|
||||||
|
delegatedElement: Element,
|
||||||
|
isCapture: boolean,
|
||||||
|
): void {
|
||||||
|
let target: Element | Document = delegatedElement;
|
||||||
|
// document层次可能触发selectionchange事件,为了捕获这类事件,selectionchange事件绑定在document节点上
|
||||||
|
if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) {
|
||||||
|
target = delegatedElement.ownerDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listenerSet = getEventListeners(target);
|
||||||
|
const listenerSetKey = getListenerSetKey(nativeEvtName, isCapture);
|
||||||
|
|
||||||
|
if (!listenerSet.has(listenerSetKey)) {
|
||||||
|
const listener = createCustomEventListener(
|
||||||
|
target,
|
||||||
|
nativeEvtName,
|
||||||
|
isCapture,
|
||||||
|
);
|
||||||
|
target.addEventListener(nativeEvtName, listener, !!isCapture);
|
||||||
|
listenerSet.add(listenerSetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听所有委托事件
|
||||||
|
export function listenDelegatedEvents(dom: Element) {
|
||||||
|
if (dom[listeningMarker]) {
|
||||||
|
// 不需要重复注册事件
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dom[listeningMarker] = true;
|
||||||
|
allDelegatedNativeEvents.forEach((eventName: string) => {
|
||||||
|
// 委托冒泡事件
|
||||||
|
listenToNativeEvent(eventName, dom, false);
|
||||||
|
// 委托捕获事件
|
||||||
|
listenToNativeEvent(eventName, dom, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过horizon事件名获取到native事件名
|
||||||
|
function getNativeEvtName(horizonEventName, capture) {
|
||||||
|
let nativeName;
|
||||||
|
if (capture) {
|
||||||
|
nativeName = horizonEventName.slice(2, -7);
|
||||||
|
} else {
|
||||||
|
nativeName = horizonEventName.slice(2);
|
||||||
|
}
|
||||||
|
if (!nativeName) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return nativeName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否捕获事件
|
||||||
|
function getIsCapture(horizonEventName) {
|
||||||
|
if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return horizonEventName.slice(-7) === 'Capture';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 封装监听函数
|
||||||
|
function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) {
|
||||||
|
return (event) => {
|
||||||
|
const customEvent = new CustomBaseEvent(horizonEventName, nativeEvtName, event, null, targetElement);
|
||||||
|
listener(customEvent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非委托事件单独监听到各自dom节点
|
||||||
|
export function listenNonDelegatedEvent(
|
||||||
|
horizonEventName: string,
|
||||||
|
domElement: Element,
|
||||||
|
listener,
|
||||||
|
): void {
|
||||||
|
const isCapture = getIsCapture(horizonEventName);
|
||||||
|
const nativeEvtName = getNativeEvtName(horizonEventName, isCapture);
|
||||||
|
|
||||||
|
// 先判断是否存在老的监听事件,若存在则移除
|
||||||
|
const eventToListenerMap = getEventToListenerMap(domElement);
|
||||||
|
if (eventToListenerMap.get(horizonEventName)) {
|
||||||
|
domElement.removeEventListener(nativeEvtName, eventToListenerMap.get(horizonEventName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof listener !== 'function') {
|
||||||
|
eventToListenerMap.delete(nativeEvtName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了和委托事件对外行为一致,将事件对象封装成CustomBaseEvent
|
||||||
|
const wrapperListener = getWrapperListener(horizonEventName, nativeEvtName, domElement, listener);
|
||||||
|
// 添加新的监听
|
||||||
|
eventToListenerMap.set(horizonEventName, wrapperListener);
|
||||||
|
domElement.addEventListener(nativeEvtName, wrapperListener, isCapture);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {horizonEventToNativeMap} from './const';
|
||||||
|
|
||||||
|
// 需要委托的horizon事件和原生事件对应关系
|
||||||
|
export const allDelegatedHorizonEvents = new Map();
|
||||||
|
// 所有委托的原生事件集合
|
||||||
|
export const allDelegatedNativeEvents = new Set();
|
||||||
|
|
||||||
|
horizonEventToNativeMap.forEach((dependencies, horizonEvent) => {
|
||||||
|
allDelegatedHorizonEvents.set(horizonEvent, dependencies);
|
||||||
|
allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies);
|
||||||
|
dependencies.forEach(d => {
|
||||||
|
allDelegatedNativeEvents.add(d);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,83 @@
|
||||||
|
import {
|
||||||
|
STYLE_AMT_END,
|
||||||
|
STYLE_AMT_ITERATION,
|
||||||
|
STYLE_AMT_START,
|
||||||
|
STYLE_TRANS_END
|
||||||
|
} from './StyleEventNames';
|
||||||
|
|
||||||
|
// Horizon事件和原生事件对应关系
|
||||||
|
export const horizonEventToNativeMap = new Map([
|
||||||
|
['onKeyPress', ['keypress']],
|
||||||
|
['onTextInput', ['textInput']],
|
||||||
|
['onClick', ['click']],
|
||||||
|
['onDoubleClick', ['dblclick']],
|
||||||
|
['onFocus', ['focusin']],
|
||||||
|
['onBlur', ['focusout']],
|
||||||
|
['onInput', ['input']],
|
||||||
|
['onMouseOut', ['mouseout']],
|
||||||
|
['onMouseOver', ['mouseover']],
|
||||||
|
['onPointerOut', ['pointerout']],
|
||||||
|
['onPointerOver', ['pointerover']],
|
||||||
|
['onContextMenu', ['contextmenu']],
|
||||||
|
['onDragEnd', ['dragend']],
|
||||||
|
['onKeyDown', ['keydown']],
|
||||||
|
['onKeyUp', ['keyup']],
|
||||||
|
['onMouseDown', ['mousedown']],
|
||||||
|
['onMouseMove', ['mousemove']],
|
||||||
|
['onMouseUp', ['mouseup']],
|
||||||
|
['onSelectChange', ['selectionchange']],
|
||||||
|
['onTouchEnd', ['touchend']],
|
||||||
|
['onTouchMove', ['touchmove']],
|
||||||
|
['onTouchStart', ['touchstart']],
|
||||||
|
|
||||||
|
['onCompositionEnd', ['compositionend']],
|
||||||
|
['onCompositionStart', ['compositionstart']],
|
||||||
|
['onCompositionUpdate', ['compositionupdate']],
|
||||||
|
['onBeforeInput', ['compositionend', 'keypress', 'textInput']],
|
||||||
|
['onChange', ['change', 'click', 'focusout', 'input',]],
|
||||||
|
['onSelect', ['focusout', 'contextmenu', 'dragend', 'focusin', 'keydown', 'keyup', 'mousedown', 'mouseup', 'selectionchange']],
|
||||||
|
|
||||||
|
['onAnimationEnd', [STYLE_AMT_END]],
|
||||||
|
['onAnimationIteration', [STYLE_AMT_ITERATION]],
|
||||||
|
['onAnimationStart', [STYLE_AMT_START]],
|
||||||
|
['onTransitionEnd', [STYLE_TRANS_END]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CommonEventToHorizonMap = {
|
||||||
|
click: 'click',
|
||||||
|
dblclick: 'doubleClick',
|
||||||
|
contextmenu: 'contextMenu',
|
||||||
|
dragend: 'dragEnd',
|
||||||
|
focusin: 'focus',
|
||||||
|
focusout: 'blur',
|
||||||
|
input: 'input',
|
||||||
|
keydown: 'keyDown',
|
||||||
|
keypress: 'keyPress',
|
||||||
|
keyup: 'keyUp',
|
||||||
|
mousedown: 'mouseDown',
|
||||||
|
mouseup: 'mouseUp',
|
||||||
|
touchend: 'touchEnd',
|
||||||
|
touchstart: 'touchStart',
|
||||||
|
mousemove: 'mouseMove',
|
||||||
|
mouseout: 'mouseOut',
|
||||||
|
mouseover: 'mouseOver',
|
||||||
|
pointermove: 'pointerMove',
|
||||||
|
pointerout: 'pointerOut',
|
||||||
|
pointerover: 'pointerOver',
|
||||||
|
selectionchange: 'selectChange',
|
||||||
|
textInput: 'textInput',
|
||||||
|
touchmove: 'touchMove',
|
||||||
|
[STYLE_AMT_END]: 'animationEnd',
|
||||||
|
[STYLE_AMT_ITERATION]: 'animationIteration',
|
||||||
|
[STYLE_AMT_START]: 'animationStart',
|
||||||
|
[STYLE_TRANS_END]: 'transitionEnd',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CHAR_CODE_ENTER = 13;
|
||||||
|
export const CHAR_CODE_SPACE = 32;
|
||||||
|
|
||||||
|
|
||||||
|
export const EVENT_TYPE_BUBBLE = 'Bubble';
|
||||||
|
export const EVENT_TYPE_CAPTURE = 'Capture';
|
||||||
|
export const EVENT_TYPE_ALL = 'All';
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* 自定义的基本事件类型
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {VNode} from '../../renderer/Types';
|
||||||
|
|
||||||
|
export class CustomBaseEvent {
|
||||||
|
|
||||||
|
data: string;
|
||||||
|
defaultPrevented: boolean;
|
||||||
|
customEventName: string;
|
||||||
|
targetVNode: VNode;
|
||||||
|
type: string;
|
||||||
|
nativeEvent: any;
|
||||||
|
target: EventTarget;
|
||||||
|
timeStamp: number;
|
||||||
|
isDefaultPrevented: () => boolean;
|
||||||
|
isPropagationStopped: () => boolean;
|
||||||
|
currentTarget: EventTarget;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
customEvtName: string | null,
|
||||||
|
nativeEvtName: string,
|
||||||
|
nativeEvt: { [propName: string]: any },
|
||||||
|
vNode: VNode,
|
||||||
|
target: null | EventTarget
|
||||||
|
) {
|
||||||
|
// 复制原生属性到自定义事件
|
||||||
|
extendAttribute(this, nativeEvt);
|
||||||
|
|
||||||
|
// custom事件自定义属性
|
||||||
|
this.customEventName = customEvtName;
|
||||||
|
this.targetVNode = vNode;
|
||||||
|
this.type = nativeEvtName;
|
||||||
|
this.nativeEvent = nativeEvt;
|
||||||
|
this.target = target;
|
||||||
|
this.timeStamp = nativeEvt.timeStamp || Date.now();
|
||||||
|
|
||||||
|
const defaultPrevented = nativeEvt.defaultPrevented != null ? nativeEvt.defaultPrevented : nativeEvt.returnValue === false;
|
||||||
|
this.defaultPrevented = defaultPrevented;
|
||||||
|
|
||||||
|
this.preventDefault = this.preventDefault.bind(this);
|
||||||
|
this.stopPropagation = this.stopPropagation.bind(this);
|
||||||
|
this.isDefaultPrevented = () => defaultPrevented;
|
||||||
|
this.isPropagationStopped = () => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容性方法
|
||||||
|
persist() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止默认行为
|
||||||
|
preventDefault() {
|
||||||
|
this.defaultPrevented = true;
|
||||||
|
if (!this.nativeEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.nativeEvent.preventDefault === 'function') {
|
||||||
|
this.nativeEvent.preventDefault();
|
||||||
|
}
|
||||||
|
this.nativeEvent.returnValue = false;
|
||||||
|
this.isDefaultPrevented = () => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止冒泡
|
||||||
|
stopPropagation() {
|
||||||
|
if (!this.nativeEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.nativeEvent.stopPropagation === 'function') {
|
||||||
|
this.nativeEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
this.nativeEvent.cancelBubble = true;
|
||||||
|
this.isPropagationStopped = () => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从原生事件中复制属性到自定义事件中
|
||||||
|
function extendAttribute(target, source) {
|
||||||
|
const attributes = [
|
||||||
|
// AnimationEvent
|
||||||
|
'animationName', 'elapsedTime', 'pseudoElement',
|
||||||
|
// CompositionEvent、InputEvent
|
||||||
|
'data',
|
||||||
|
// DragEvent
|
||||||
|
'dataTransfer',
|
||||||
|
// FocusEvent
|
||||||
|
'relatedTarget',
|
||||||
|
// KeyboardEvent
|
||||||
|
'key', 'keyCode', 'charCode', 'code', 'location', 'ctrlKey', 'shiftKey', 'altKey', 'metaKey', 'repeat', 'locale', 'getModifierState', 'clipboardData',
|
||||||
|
// MouseEvent
|
||||||
|
'button', 'buttons', 'clientX', 'clientY', 'movementX', 'movementY', 'pageX', 'pageY', 'screenX', 'screenY', 'currentTarget',
|
||||||
|
// PointerEvent
|
||||||
|
'pointerId', 'width', 'height', 'pressure', 'tangentialPressure', 'tiltX', 'tiltY', 'twist', 'pointerType', 'isPrimary',
|
||||||
|
// TouchEvent
|
||||||
|
'touches', 'targetTouches', 'changedTouches',
|
||||||
|
// TransitionEvent
|
||||||
|
'propertyName',
|
||||||
|
// UIEvent
|
||||||
|
'view', 'detail',
|
||||||
|
// WheelEvent
|
||||||
|
'deltaX', 'deltaY', 'deltaZ', 'deltaMode',
|
||||||
|
];
|
||||||
|
|
||||||
|
attributes.forEach(attr => {
|
||||||
|
if (typeof source[attr] !== 'undefined') {
|
||||||
|
if (typeof source[attr] === 'function') {
|
||||||
|
target[attr] = function() {
|
||||||
|
return source[attr].apply(source, arguments);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
target[attr] = source[attr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/**
|
||||||
|
* 自定义键盘事件
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {VNode} from '../../renderer/Types';
|
||||||
|
import {uniqueCharCode} from '../utils';
|
||||||
|
import {CustomBaseEvent} from './CustomBaseEvent';
|
||||||
|
import {CHAR_CODE_ENTER} from '../const';
|
||||||
|
|
||||||
|
const uniqueKeyMap = new Map([
|
||||||
|
['Esc', 'Escape'],
|
||||||
|
['Spacebar', ' '],
|
||||||
|
['Left', 'ArrowLeft'],
|
||||||
|
['Up', 'ArrowUp'],
|
||||||
|
['Right', 'ArrowRight'],
|
||||||
|
['Down', 'ArrowDown'],
|
||||||
|
['Del', 'Delete'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const charCodeToKeyMap = new Map([
|
||||||
|
[8, 'Backspace'],
|
||||||
|
[9, 'Tab'],
|
||||||
|
[13, 'Enter'],
|
||||||
|
[16, 'Shift'],
|
||||||
|
[17, 'Control'],
|
||||||
|
[18, 'Alt'],
|
||||||
|
[19, 'Pause'],
|
||||||
|
[27, 'Escape'],
|
||||||
|
[32, ' '],
|
||||||
|
[33, 'PageUp'],
|
||||||
|
[34, 'PageDown'],
|
||||||
|
[35, 'End'],
|
||||||
|
[36, 'Home'],
|
||||||
|
[37, 'ArrowLeft'],
|
||||||
|
[38, 'ArrowUp'],
|
||||||
|
[39, 'ArrowRight'],
|
||||||
|
[40, 'ArrowDown'],
|
||||||
|
[46, 'Delete']
|
||||||
|
]);
|
||||||
|
|
||||||
|
function getKey(event) {
|
||||||
|
if (event.key) {
|
||||||
|
return uniqueKeyMap.get(event.key) || event.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'keypress') {
|
||||||
|
const charCode = uniqueCharCode(event);
|
||||||
|
return charCode === CHAR_CODE_ENTER ? 'Enter' : String.fromCharCode(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'keydown' || event.type === 'keyup') {
|
||||||
|
return charCodeToKeyMap.get(event.keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomKeyboardEvent extends CustomBaseEvent {
|
||||||
|
|
||||||
|
key: string;
|
||||||
|
charCode: number;
|
||||||
|
keyCode: number;
|
||||||
|
which: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
customEvtName: string | null,
|
||||||
|
nativeEvtName: string,
|
||||||
|
nativeEvt: { [propName: string]: any },
|
||||||
|
vNode: VNode,
|
||||||
|
target: null | EventTarget
|
||||||
|
) {
|
||||||
|
super(customEvtName, nativeEvtName, nativeEvt, vNode, target);
|
||||||
|
this.key = getKey(nativeEvt);
|
||||||
|
this.charCode = nativeEvtName === 'keypress' ? uniqueCharCode(nativeEvt) : 0;
|
||||||
|
this.keyCode = (nativeEvtName === 'keydown' || nativeEvtName === 'keyup') ? nativeEvt.keyCode : 0;
|
||||||
|
this.which = this.charCode || this.keyCode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import type {VNode} from '../../renderer/Types';
|
||||||
|
import {CustomBaseEvent} from './CustomBaseEvent';
|
||||||
|
|
||||||
|
export class CustomMouseEvent extends CustomBaseEvent {
|
||||||
|
relatedTarget: EventTarget;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
customEvtName: string | null,
|
||||||
|
nativeEvtName: string,
|
||||||
|
nativeEvt: { [propName: string]: any },
|
||||||
|
vNode: VNode,
|
||||||
|
target: null | EventTarget
|
||||||
|
) {
|
||||||
|
super(customEvtName, nativeEvtName, nativeEvt, vNode, target);
|
||||||
|
|
||||||
|
let relatedTarget = nativeEvt.relatedTarget;
|
||||||
|
if (relatedTarget === undefined) {
|
||||||
|
if (nativeEvt.fromElement === nativeEvt.srcElement) {
|
||||||
|
relatedTarget = nativeEvt.toElement;
|
||||||
|
} else {
|
||||||
|
relatedTarget = nativeEvt.fromElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.relatedTarget = relatedTarget;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {CustomKeyboardEvent} from './CustomKeyboardEvent';
|
||||||
|
import {CustomMouseEvent} from './CustomMouseEvent';
|
||||||
|
import {CustomBaseEvent} from './CustomBaseEvent';
|
||||||
|
|
||||||
|
const CommonEventToCustom = {
|
||||||
|
keypress: CustomKeyboardEvent,
|
||||||
|
keydown: CustomKeyboardEvent,
|
||||||
|
keyup: CustomKeyboardEvent,
|
||||||
|
click: CustomMouseEvent,
|
||||||
|
dblclick: CustomMouseEvent,
|
||||||
|
mousedown: CustomMouseEvent,
|
||||||
|
mousemove: CustomMouseEvent,
|
||||||
|
mouseup: CustomMouseEvent,
|
||||||
|
mouseout: CustomMouseEvent,
|
||||||
|
mouseover: CustomMouseEvent,
|
||||||
|
contextmenu: CustomMouseEvent,
|
||||||
|
pointercancel: CustomMouseEvent,
|
||||||
|
pointerdown: CustomMouseEvent,
|
||||||
|
pointermove: CustomMouseEvent,
|
||||||
|
pointerout: CustomMouseEvent,
|
||||||
|
pointerover: CustomMouseEvent,
|
||||||
|
pointerup: CustomMouseEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建普通自定义事件对象实例,和原生事件对应
|
||||||
|
export function createCommonCustomEvent(customEventName, nativeEvtName, nativeEvent, vNode, currentTarget) {
|
||||||
|
const EventConstructor = CommonEventToCustom[nativeEvtName] || CustomBaseEvent;
|
||||||
|
return new EventConstructor(
|
||||||
|
customEventName,
|
||||||
|
nativeEvtName,
|
||||||
|
nativeEvent,
|
||||||
|
vNode,
|
||||||
|
currentTarget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建模拟事件实例对象,需要handler特殊处理
|
||||||
|
export function createHandlerCustomEvent(customEventName, nativeEvtName, nativeEvent, vNode, currentTarget) {
|
||||||
|
return new CustomMouseEvent(
|
||||||
|
customEventName,
|
||||||
|
nativeEvtName,
|
||||||
|
nativeEvent,
|
||||||
|
vNode,
|
||||||
|
currentTarget,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import type {VNode} from '../../renderer/Types';
|
||||||
|
import type {AnyNativeEvent, ProcessingListenerList} from '../Types';
|
||||||
|
import {getListenersFromTree} from '../ListenerGetter';
|
||||||
|
import {createHandlerCustomEvent} from '../customEvents/EventFactory';
|
||||||
|
import {CHAR_CODE_SPACE, EVENT_TYPE_ALL} from '../const';
|
||||||
|
import {CustomBaseEvent} from '../customEvents/CustomBaseEvent';
|
||||||
|
const SPACE_CHAR = String.fromCharCode(CHAR_CODE_SPACE);
|
||||||
|
|
||||||
|
function getInputCharsByNative(
|
||||||
|
eventName: string,
|
||||||
|
nativeEvent: any,
|
||||||
|
): string | void {
|
||||||
|
if (eventName === 'compositionend') {
|
||||||
|
return (nativeEvent.detail && nativeEvent.detail.data) || null;
|
||||||
|
}
|
||||||
|
if (eventName === 'keypress') {
|
||||||
|
return nativeEvent.which === CHAR_CODE_SPACE ? SPACE_CHAR : null;
|
||||||
|
}
|
||||||
|
if (eventName === 'textInput') {
|
||||||
|
return nativeEvent.data === SPACE_CHAR ? null : nativeEvent.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义beforeInput的hook事件处理
|
||||||
|
export function getListeners(
|
||||||
|
nativeEvtName: string,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
vNode: null | VNode,
|
||||||
|
target: null | EventTarget,
|
||||||
|
): ProcessingListenerList {
|
||||||
|
const chars = getInputCharsByNative(nativeEvtName, nativeEvent);
|
||||||
|
// 无字符将要输入,无需处理
|
||||||
|
if (!chars) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: CustomBaseEvent = createHandlerCustomEvent(
|
||||||
|
'onBeforeInput',
|
||||||
|
'beforeinput',
|
||||||
|
nativeEvent,
|
||||||
|
null,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
event.data = chars;
|
||||||
|
|
||||||
|
return getListenersFromTree(vNode, 'onBeforeInput', event, EVENT_TYPE_ALL);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {createHandlerCustomEvent} from '../customEvents/EventFactory';
|
||||||
|
import {getDom} from '../../dom/DOMInternalKeys';
|
||||||
|
import {isInputValueChanged} from '../../dom/valueHandler/ValueChangeHandler';
|
||||||
|
import {addValueUpdateList} from '../ControlledValueUpdater';
|
||||||
|
import {isTextInputElement} from '../utils';
|
||||||
|
import {EVENT_TYPE_ALL} from '../const';
|
||||||
|
import {AnyNativeEvent, ProcessingListenerList} from '../Types';
|
||||||
|
import {
|
||||||
|
getListenersFromTree,
|
||||||
|
} from '../ListenerGetter';
|
||||||
|
import {VNode} from '../../renderer/Types';
|
||||||
|
import {getDomTag} from '../../dom/utils/Common';
|
||||||
|
|
||||||
|
// 返回是否需要触发change事件标记
|
||||||
|
function shouldTriggerChangeEvent(targetDom, evtName) {
|
||||||
|
const { type } = targetDom;
|
||||||
|
const domTag = getDomTag(targetDom);
|
||||||
|
|
||||||
|
if (domTag === 'select' || (domTag === 'input' && type === 'file')) {
|
||||||
|
return evtName === 'change';
|
||||||
|
} else if (isTextInputElement(targetDom)) {
|
||||||
|
if (evtName === 'input' || evtName === 'change') {
|
||||||
|
return isInputValueChanged(targetDom);
|
||||||
|
}
|
||||||
|
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
|
||||||
|
if (evtName === 'click') {
|
||||||
|
return isInputValueChanged(targetDom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 支持input/textarea/select的onChange事件
|
||||||
|
*/
|
||||||
|
export function getListeners(
|
||||||
|
nativeEvtName: string,
|
||||||
|
nativeEvt: AnyNativeEvent,
|
||||||
|
vNode: null | VNode,
|
||||||
|
target: null | EventTarget,
|
||||||
|
): ProcessingListenerList {
|
||||||
|
if (!vNode) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const targetDom = getDom(vNode);
|
||||||
|
|
||||||
|
// 判断是否需要触发change事件
|
||||||
|
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
|
||||||
|
addValueUpdateList(target);
|
||||||
|
const event = createHandlerCustomEvent(
|
||||||
|
'onChange',
|
||||||
|
'change',
|
||||||
|
nativeEvt,
|
||||||
|
null,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type {VNode} from '../../renderer/Types';
|
||||||
|
import type {AnyNativeEvent, ProcessingListenerList} from '../Types';
|
||||||
|
import {getListenersFromTree} from '../ListenerGetter';
|
||||||
|
import {createHandlerCustomEvent} from '../customEvents/EventFactory';
|
||||||
|
import {EVENT_TYPE_ALL} from '../const';
|
||||||
|
|
||||||
|
const compositionEventObj = {
|
||||||
|
compositionstart: 'onCompositionStart',
|
||||||
|
compositionend: 'onCompositionEnd',
|
||||||
|
compositionupdate: 'onCompositionUpdate',
|
||||||
|
};
|
||||||
|
|
||||||
|
// compoisition事件主要处理中文输入法输入时的触发事件
|
||||||
|
export function getListeners(
|
||||||
|
evtName: string,
|
||||||
|
nativeEvt: AnyNativeEvent,
|
||||||
|
vNode: null | VNode,
|
||||||
|
target: null | EventTarget,
|
||||||
|
): ProcessingListenerList {
|
||||||
|
const evtType = compositionEventObj[evtName];
|
||||||
|
|
||||||
|
const event = createHandlerCustomEvent(
|
||||||
|
evtType,
|
||||||
|
evtName,
|
||||||
|
nativeEvt,
|
||||||
|
null,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
return getListenersFromTree(vNode, evtType, event, EVENT_TYPE_ALL);
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import {createHandlerCustomEvent} from '../customEvents/EventFactory';
|
||||||
|
import {shallowCompare} from '../../renderer/utils/compare';
|
||||||
|
import {getFocusedDom} from '../../dom/utils/Common';
|
||||||
|
import {getDom} from '../../dom/DOMInternalKeys';
|
||||||
|
import {isDocument} from '../../dom/utils/Common';
|
||||||
|
import {isTextInputElement} from '../utils';
|
||||||
|
import type {AnyNativeEvent, ProcessingListenerList} from '../Types';
|
||||||
|
import {getListenersFromTree} from '../ListenerGetter';
|
||||||
|
import type {VNode} from '../../renderer/Types';
|
||||||
|
import {EVENT_TYPE_ALL} from '../const';
|
||||||
|
|
||||||
|
const horizonEventName = 'onSelect'
|
||||||
|
|
||||||
|
let currentElement = null;
|
||||||
|
let currentVNode = null;
|
||||||
|
let lastSelection = null;
|
||||||
|
|
||||||
|
function initTargetCache(dom, vNode) {
|
||||||
|
if (isTextInputElement(dom) || dom.contentEditable === 'true') {
|
||||||
|
currentElement = dom;
|
||||||
|
currentVNode = vNode;
|
||||||
|
lastSelection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTargetCache() {
|
||||||
|
currentElement = null;
|
||||||
|
currentVNode = null;
|
||||||
|
lastSelection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记是否在鼠标事件过程中
|
||||||
|
let isInMouseEvent = false;
|
||||||
|
|
||||||
|
// 获取节点所在的document对象
|
||||||
|
function getDocument(eventTarget) {
|
||||||
|
if (eventTarget.window === eventTarget) {
|
||||||
|
return eventTarget.document;
|
||||||
|
}
|
||||||
|
if (isDocument(eventTarget)) {
|
||||||
|
return eventTarget;
|
||||||
|
}
|
||||||
|
return eventTarget.ownerDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectEvent(nativeEvent, target) {
|
||||||
|
const doc = getDocument(target);
|
||||||
|
if (isInMouseEvent || currentElement == null || currentElement !== getFocusedDom(doc)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSelection = window.getSelection();
|
||||||
|
if (!shallowCompare(lastSelection, currentSelection)) {
|
||||||
|
lastSelection = currentSelection;
|
||||||
|
|
||||||
|
const event = createHandlerCustomEvent(
|
||||||
|
horizonEventName,
|
||||||
|
'select',
|
||||||
|
nativeEvent,
|
||||||
|
null,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
event.target = currentElement;
|
||||||
|
|
||||||
|
return getListenersFromTree(
|
||||||
|
currentVNode,
|
||||||
|
horizonEventName,
|
||||||
|
event,
|
||||||
|
EVENT_TYPE_ALL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该插件创建一个onSelect事件
|
||||||
|
* 支持元素: input、textarea、contentEditable元素
|
||||||
|
* 触发场景:用户输入、折叠选择、文本选择
|
||||||
|
*/
|
||||||
|
export function getListeners(
|
||||||
|
name: string,
|
||||||
|
nativeEvt: AnyNativeEvent,
|
||||||
|
vNode: null | VNode,
|
||||||
|
target: null | EventTarget,
|
||||||
|
): ProcessingListenerList {
|
||||||
|
const targetNode = vNode ? getDom(vNode) : window;
|
||||||
|
let eventUnitList = [];
|
||||||
|
switch (name) {
|
||||||
|
case 'focusin':
|
||||||
|
initTargetCache(targetNode, vNode);
|
||||||
|
return eventUnitList;
|
||||||
|
case 'focusout':
|
||||||
|
clearTargetCache();
|
||||||
|
return eventUnitList;
|
||||||
|
case 'mousedown':
|
||||||
|
isInMouseEvent = true;
|
||||||
|
return eventUnitList;
|
||||||
|
case 'contextmenu':
|
||||||
|
case 'mouseup':
|
||||||
|
case 'dragend':
|
||||||
|
isInMouseEvent = false;
|
||||||
|
eventUnitList = getSelectEvent(nativeEvt, target);
|
||||||
|
break;
|
||||||
|
case 'selectionchange':
|
||||||
|
case 'keydown':
|
||||||
|
case 'keyup':
|
||||||
|
eventUnitList = getSelectEvent(nativeEvt, target);
|
||||||
|
}
|
||||||
|
return eventUnitList;
|
||||||
|
}
|
|
@ -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