Match-id-9a5a55ba654db4419702e54249dd665a02d65553

This commit is contained in:
* 2022-05-24 17:08:13 +08:00 committed by *
parent 0677dfcaca
commit 2feaad7690
12 changed files with 0 additions and 736 deletions

View File

@ -1,37 +0,0 @@
import {getVNodeProps} from '../dom/DOMInternalKeys';
import {resetValue} from '../dom/valueHandler';
let updateList: Array<any> | null = null;
// 受控组件值重新赋值
function updateValue(target: Element) {
const props = getVNodeProps(target);
if (props) {
resetValue(target, 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;
}

View File

@ -1,132 +0,0 @@
/**
*
*/
import {allDelegatedNativeEvents} from './EventCollection';
import {isDocument} from '../dom/utils/Common';
import {
getNearestVNode,
getNonDelegatedListenerMap,
} from '../dom/DOMInternalKeys';
import {runDiscreteUpdates} from '../renderer/TreeBuilder';
import {isMounted} from '../renderer/vnode/VNodeUtils';
import {SuspenseComponent} from '../renderer/vnode/VNodeTags';
import {handleEventMain} from './HorizonEventMain';
import {decorateNativeEvent} from './customEvents/EventFactory';
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
// 触发委托事件
function triggerDelegatedEvent(
nativeEvtName: string,
isCapture: boolean,
targetDom: EventTarget,
nativeEvent, // 事件对象event
) {
// 执行之前的调度事件
runDiscreteUpdates();
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
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);
}
// 监听委托事件
function listenToNativeEvent(
nativeEvtName: string,
delegatedElement: Element,
isCapture: boolean,
): void {
let dom: Element | Document = delegatedElement;
// document层次可能触发selectionchange事件为了捕获这类事件selectionchange事件绑定在document节点上
if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) {
dom = delegatedElement.ownerDocument;
}
const listener = triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, dom);
dom.addEventListener(nativeEvtName, listener, isCapture);
}
// 监听所有委托事件
export function listenDelegatedEvents(dom: Element) {
if (dom[listeningMarker]) {
// 不需要重复注册事件
return;
}
dom[listeningMarker] = true;
allDelegatedNativeEvents.forEach((nativeEvtName: string) => {
// 委托冒泡事件
listenToNativeEvent(nativeEvtName, dom, false);
// 委托捕获事件
listenToNativeEvent(nativeEvtName, 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 isCaptureEvent(horizonEventName) {
if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') {
return false;
}
return horizonEventName.slice(-7) === 'Capture';
}
// 封装监听函数
function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) {
return event => {
const customEvent = decorateNativeEvent(horizonEventName, nativeEvtName, event);
listener(customEvent);
};
}
// 非委托事件单独监听到各自dom节点
export function listenNonDelegatedEvent(
horizonEventName: string,
domElement: Element,
listener,
): void {
const isCapture = isCaptureEvent(horizonEventName);
const nativeEvtName = getNativeEvtName(horizonEventName, isCapture);
// 先判断是否存在老的监听事件,若存在则移除
const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement);
const currentListener = nonDelegatedListenerMap.get(horizonEventName);
if (currentListener) {
domElement.removeEventListener(nativeEvtName, currentListener);
nonDelegatedListenerMap.delete(horizonEventName);
}
if (typeof listener !== 'function') {
return;
}
// 为了和委托事件对外行为一致将事件对象封装成CustomBaseEvent
const wrapperListener = getWrapperListener(horizonEventName, nativeEvtName, domElement, listener);
// 添加新的监听
nonDelegatedListenerMap.set(horizonEventName, wrapperListener);
domElement.addEventListener(nativeEvtName, wrapperListener, isCapture);
}

View File

@ -1,15 +0,0 @@
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);
});
});

View File

@ -1,166 +0,0 @@
import type { AnyNativeEvent } from './Types';
import type { VNode } from '../renderer/Types';
import {
CommonEventToHorizonMap,
horizonEventToNativeMap,
EVENT_TYPE_BUBBLE,
EVENT_TYPE_CAPTURE,
} from './const';
import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler';
import { getListeners as getSelectionListeners } from './simulatedEvtHandler/SelectionEventHandler';
import {
setPropertyWritable,
} from './utils';
import { decorateNativeEvent } from './customEvents/EventFactory';
import { getListenersFromTree } from './ListenerGetter';
import { shouldUpdateValue, updateControlledValue } from './ControlledValueUpdater';
import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer';
import { getExactNode } from '../renderer/vnode/VNodeUtils';
import {ListenerUnitList} from './Types';
// 获取事件触发的普通事件监听方法队列
function getCommonListeners(
nativeEvtName: string,
vNode: null | VNode,
nativeEvent: AnyNativeEvent,
target: null | EventTarget,
isCapture: boolean,
): ListenerUnitList {
const name = CommonEventToHorizonMap[nativeEvtName];
const horizonEvtName = !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`; // 例dragEnd -> onDragEnd
if (!horizonEvtName) {
return [];
}
// 鼠标点击右键
if (nativeEvent instanceof MouseEvent && nativeEvtName === 'click' && nativeEvent.button === 2) {
return [];
}
if (nativeEvtName === 'focusin') {
nativeEvtName = 'focus';
}
if (nativeEvtName === 'focusout') {
nativeEvtName = 'blur';
}
const horizonEvent = decorateNativeEvent(horizonEvtName, nativeEvtName, nativeEvent);
return getListenersFromTree(
vNode,
horizonEvtName,
horizonEvent,
isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE,
);
}
// 按顺序执行事件队列
function processListeners(listenerList: ListenerUnitList): void {
listenerList.forEach(eventUnit => {
const { currentTarget, listener, event } = eventUnit;
if (event.isPropagationStopped()) {
return;
}
setPropertyWritable(event, 'currentTarget');
event.currentTarget = currentTarget;
listener(event);
event.currentTarget = null;
});
}
function getProcessListeners(
nativeEvtName: string,
vNode: VNode | null,
nativeEvent: AnyNativeEvent,
target,
isCapture: boolean
): ListenerUnitList {
// 触发普通委托事件
let listenerList: ListenerUnitList = getCommonListeners(
nativeEvtName,
vNode,
nativeEvent,
target,
isCapture,
);
// 触发特殊handler委托事件
if (!isCapture) {
if (horizonEventToNativeMap.get('onChange').includes(nativeEvtName)) {
listenerList = listenerList.concat(getChangeListeners(
nativeEvtName,
nativeEvent,
vNode,
target,
));
}
if (horizonEventToNativeMap.get('onSelect').includes(nativeEvtName)) {
listenerList = listenerList.concat(getSelectionListeners(
nativeEvtName,
nativeEvent,
vNode,
target,
));
}
}
return listenerList;
}
// 触发可以被执行的horizon事件监听
function triggerHorizonEvents(
nativeEvtName: string,
isCapture: boolean,
nativeEvent: AnyNativeEvent,
vNode: VNode | null,
): void {
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
// 获取委托事件队列
const listenerList = getProcessListeners(nativeEvtName, vNode, nativeEvent, nativeEventTarget, isCapture);
// 处理触发的事件队列
processListeners(listenerList);
}
// 其他事件正在执行中标记
let isInEventsExecution = false;
// 处理委托事件入口
export function handleEventMain(
nativeEvtName: string,
isCapture: boolean,
nativeEvent: AnyNativeEvent,
vNode: null | VNode,
targetContainer: EventTarget,
): void {
let startVNode = vNode;
if (startVNode !== null) {
startVNode = getExactNode(startVNode, targetContainer);
if (!startVNode) {
return;
}
}
// 有事件正在执行,同步执行事件
if (isInEventsExecution) {
triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode);
return;
}
// 没有事件在执行,经过调度再执行事件
isInEventsExecution = true;
try {
asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
} finally {
isInEventsExecution = false;
if (shouldUpdateValue()) {
runDiscreteUpdates();
updateControlledValue();
}
}
}

View File

@ -1,72 +0,0 @@
import { VNode } from '../renderer/Types';
import { DomComponent } from '../renderer/vnode/VNodeTags';
import { EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE } from './const';
import { AnyNativeEvent, ListenerUnitList } from './Types';
// 从vnode属性中获取事件listener
function getListenerFromVNode(vNode: VNode, eventName: string): Function | null {
const props = vNode.props;
const mouseEvents = ['onClick', 'onDoubleClick', 'onMouseDown', 'onMouseMove', 'onMouseUp', 'onMouseEnter'];
const formElements = ['button', 'input', 'select', 'textarea'];
// 是否应该阻止禁用的表单元素触发鼠标事件
const shouldPreventMouseEvent =
mouseEvents.includes(eventName) && props.disabled && formElements.includes(vNode.type);
const listener = props[eventName];
if (shouldPreventMouseEvent) {
return null;
} else {
return listener;
}
}
// 获取监听事件
export function getListenersFromTree(
targetVNode: VNode | null,
horizonEvtName: string | null,
nativeEvent: AnyNativeEvent,
eventType: string
): ListenerUnitList {
if (!horizonEvtName) {
return [];
}
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 captureName = horizonEvtName + EVENT_TYPE_CAPTURE;
const captureListener = getListenerFromVNode(vNode, captureName);
if (captureListener) {
listeners.unshift({
vNode,
listener: captureListener,
currentTarget: realNode,
event: nativeEvent,
});
}
}
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) {
const bubbleListener = getListenerFromVNode(vNode, horizonEvtName);
if (bubbleListener) {
listeners.push({
vNode,
listener: bubbleListener,
currentTarget: realNode,
event: nativeEvent,
});
}
}
}
vNode = vNode.parent;
}
return listeners;
}

View File

@ -1,13 +0,0 @@
import type {VNode} from '../renderer/Types';
export type AnyNativeEvent = KeyboardEvent | MouseEvent | TouchEvent | UIEvent | Event;
export type ListenerUnit = {
vNode: null | VNode;
listener: Function;
currentTarget: EventTarget;
event: AnyNativeEvent;
};
export type ListenerUnitList = Array<ListenerUnit>;

View File

@ -1,79 +0,0 @@
// 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']],
['onChange', ['change', 'click', 'focusout', 'input']],
['onSelect', ['focusout', 'contextmenu', 'dragend', 'focusin',
'keydown', 'keyup', 'mousedown', 'mouseup', 'selectionchange']],
['onAnimationEnd', ['animationend']],
['onAnimationIteration', ['animationiteration']],
['onAnimationStart', ['animationstart']],
['onTransitionEnd', ['transitionend']]
]);
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',
animationend: 'animationEnd',
animationiteration: 'animationIteration',
animationstart: 'animationStart',
transitionend: 'transitionEnd',
compositionstart: 'compositionStart',
compositionend: 'compositionEnd',
compositionupdate: 'compositionUpdate',
};
export const CHAR_CODE_SPACE = 32;
export const EVENT_TYPE_BUBBLE = 'Bubble';
export const EVENT_TYPE_CAPTURE = 'Capture';
export const EVENT_TYPE_ALL = 'All';

View File

@ -1,36 +0,0 @@
// 兼容IE的event key
const uniqueKeyMap = new Map([
['Esc', 'Escape'],
['Spacebar', ' '],
['Left', 'ArrowLeft'],
['Up', 'ArrowUp'],
['Right', 'ArrowRight'],
['Down', 'ArrowDown'],
['Del', 'Delete'],
]);
const noop = () => {};
// 创建普通自定义事件对象实例,和原生事件对应
export function decorateNativeEvent(customEventName, nativeEvtName, nativeEvent) {
nativeEvent.isDefaultPrevented = () => nativeEvent.defaultPrevented;
nativeEvent.isPropagationStopped = () => nativeEvent.cancelBubble;
// 适配老版本事件api
nativeEvent.persist = noop;
// custom事件自定义属性
nativeEvent.customEventName = customEventName;
nativeEvent.nativeEvent = nativeEvent;
// 保存原生的事件类型,因为下面会修改
nativeEvent.nativeEventType = nativeEvent.type;
Object.defineProperty(nativeEvent, 'type', { writable: true });
nativeEvent.type = nativeEvtName;
const orgKey = nativeEvent.key;
Object.defineProperty(nativeEvent, 'key', { writable: true });
nativeEvent.key = uniqueKeyMap.get(orgKey) || orgKey;
return nativeEvent;
}

View File

@ -1,60 +0,0 @@
import {decorateNativeEvent} from '../customEvents/EventFactory';
import {getDom} from '../../dom/DOMInternalKeys';
import {isInputValueChanged} from '../../dom/valueHandler/ValueChangeHandler';
import {addValueUpdateList} from '../ControlledValueUpdater';
import {isInputElement} from '../utils';
import {EVENT_TYPE_ALL} from '../const';
import {AnyNativeEvent, ListenerUnitList} 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 (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
if (evtName === 'click') {
return isInputValueChanged(targetDom);
}
} else if (isInputElement(targetDom)) {
if (evtName === 'input' || evtName === 'change') {
return isInputValueChanged(targetDom);
}
}
return false;
}
/**
*
* input/textarea/select的onChange事件
*/
export function getListeners(
nativeEvtName: string,
nativeEvt: AnyNativeEvent,
vNode: null | VNode,
target: null | EventTarget,
): ListenerUnitList {
if (!vNode) {
return [];
}
const targetDom = getDom(vNode);
// 判断是否需要触发change事件
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
addValueUpdateList(target);
const event = decorateNativeEvent(
'onChange',
'change',
nativeEvt,
);
return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL);
}
return [];
}

View File

@ -1,112 +0,0 @@
import {decorateNativeEvent} 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 {isInputElement, setPropertyWritable} from '../utils';
import type {AnyNativeEvent} from '../Types';
import {getListenersFromTree} from '../ListenerGetter';
import type {VNode} from '../../renderer/Types';
import {EVENT_TYPE_ALL} from '../const';
import {ListenerUnitList} from '../Types';
const horizonEventName = 'onSelect';
let currentElement = null;
let currentVNode = null;
let lastSelection: Selection | null = null;
function initTargetCache(dom, vNode) {
if (isInputElement(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 = decorateNativeEvent(
horizonEventName,
'select',
nativeEvent,
);
setPropertyWritable(nativeEvent, 'target');
event.target = currentElement;
return getListenersFromTree(
currentVNode,
horizonEventName,
event,
EVENT_TYPE_ALL
);
}
return [];
}
/**
* onSelect事件
* inputtextareacontentEditable元素
*
*/
export function getListeners(
nativeEvtName: string,
nativeEvt: AnyNativeEvent,
vNode: null | VNode,
target: null | EventTarget,
): ListenerUnitList {
const targetNode = vNode ? getDom(vNode) : window;
let eventUnitList: ListenerUnitList = [];
switch (nativeEvtName) {
case 'focusin':
initTargetCache(targetNode, vNode);
break;
case 'focusout':
clearTargetCache();
break;
case 'mousedown':
isInMouseEvent = true;
break;
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;
}

View File

@ -1,14 +0,0 @@
export function isInputElement(dom?: HTMLElement): boolean {
if (dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement) {
return true;
}
return false;
}
export function setPropertyWritable(obj, propName) {
const desc = Object.getOwnPropertyDescriptor(obj, propName);
if (!desc || !desc.writable) {
Object.defineProperty(obj, propName, { writable : true });
}
}