Match-id-b191193225d35dcd637d7ef1902db54e05f62f6f
This commit is contained in:
parent
c2b21d887f
commit
914a0ce6e5
|
@ -18,7 +18,8 @@ import { createPortal } from '../renderer/components/CreatePortal';
|
||||||
import type { Container } from './DOMOperator';
|
import type { Container } from './DOMOperator';
|
||||||
import { isElement } from './utils/Common';
|
import { isElement } from './utils/Common';
|
||||||
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
||||||
import { Callback } from '../renderer/UpdateHandler';
|
import {Callback} from '../renderer/UpdateHandler';
|
||||||
|
import {listenSimulatedDelegatedEvents} from '../event/EventBinding';
|
||||||
|
|
||||||
function createRoot(children: any, container: Container, callback?: Callback) {
|
function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
// 清空容器
|
// 清空容器
|
||||||
|
@ -31,6 +32,7 @@ function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
// 调度器创建根节点,并给容器dom赋vNode结构体
|
// 调度器创建根节点,并给容器dom赋vNode结构体
|
||||||
const treeRoot = createTreeRootVNode(container);
|
const treeRoot = createTreeRootVNode(container);
|
||||||
container._treeRoot = treeRoot;
|
container._treeRoot = treeRoot;
|
||||||
|
listenSimulatedDelegatedEvents(treeRoot);
|
||||||
|
|
||||||
// 执行回调
|
// 执行回调
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
|
|
|
@ -16,16 +16,14 @@
|
||||||
/**
|
/**
|
||||||
* 事件绑定实现,分为绑定委托事件和非委托事件
|
* 事件绑定实现,分为绑定委托事件和非委托事件
|
||||||
*/
|
*/
|
||||||
import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventHub';
|
import {allDelegatedHorizonEvents, simulatedDelegatedEvents} from './EventHub';
|
||||||
import { isDocument } from '../dom/utils/Common';
|
import {isDocument} from '../dom/utils/Common';
|
||||||
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
|
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
|
||||||
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
|
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
|
||||||
import { handleEventMain } from './HorizonEventMain';
|
import { handleEventMain } from './HorizonEventMain';
|
||||||
import { decorateNativeEvent } from './EventWrapper';
|
import { decorateNativeEvent } from './EventWrapper';
|
||||||
import { VNode } from '../renderer/vnode/VNode';
|
import { VNode } from '../renderer/vnode/VNode';
|
||||||
|
|
||||||
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
|
|
||||||
|
|
||||||
// 触发委托事件
|
// 触发委托事件
|
||||||
function triggerDelegatedEvent(
|
function triggerDelegatedEvent(
|
||||||
nativeEvtName: string,
|
nativeEvtName: string,
|
||||||
|
@ -64,6 +62,13 @@ function isCaptureEvent(horizonEventName) {
|
||||||
return horizonEventName.slice(-7) === 'Capture';
|
return horizonEventName.slice(-7) === 'Capture';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 利用冒泡事件模拟不冒泡事件,需要直接在根节点绑定
|
||||||
|
export function listenSimulatedDelegatedEvents(root: VNode) {
|
||||||
|
for (let i = 0; i < simulatedDelegatedEvents.length; i++) {
|
||||||
|
lazyDelegateOnRoot(root, simulatedDelegatedEvents[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
||||||
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
||||||
currentRoot.delegatedEvents.add(eventName);
|
currentRoot.delegatedEvents.add(eventName);
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
// 需要委托的horizon事件和原生事件对应关系
|
// 需要委托的horizon事件和原生事件对应关系
|
||||||
export const allDelegatedHorizonEvents = new Map();
|
export const allDelegatedHorizonEvents = new Map();
|
||||||
|
|
||||||
|
// 模拟委托事件,事件本事不冒泡,需要其他事件来触发冒泡过程
|
||||||
|
export const simulatedDelegatedEvents = ['onMouseEnter', 'onMouseLeave'];
|
||||||
// 所有委托的原生事件集合
|
// 所有委托的原生事件集合
|
||||||
export const allDelegatedNativeEvents = new Set();
|
export const allDelegatedNativeEvents = new Set();
|
||||||
|
|
||||||
|
@ -49,6 +52,8 @@ export const horizonEventToNativeMap = new Map([
|
||||||
['onCompositionUpdate', ['compositionupdate']],
|
['onCompositionUpdate', ['compositionupdate']],
|
||||||
['onChange', ['change', 'click', 'focusout', 'input']],
|
['onChange', ['change', 'click', 'focusout', 'input']],
|
||||||
['onSelect', ['select']],
|
['onSelect', ['select']],
|
||||||
|
['onMouseEnter', ['mouseout', 'mouseover']],
|
||||||
|
['onMouseLeave', ['mouseout', 'mouseover']],
|
||||||
|
|
||||||
['onAnimationEnd', ['animationend']],
|
['onAnimationEnd', ['animationend']],
|
||||||
['onAnimationIteration', ['animationiteration']],
|
['onAnimationIteration', ['animationiteration']],
|
||||||
|
|
|
@ -37,6 +37,9 @@ export class WrappedEvent {
|
||||||
key: string;
|
key: string;
|
||||||
currentTarget: EventTarget | null = null;
|
currentTarget: EventTarget | null = null;
|
||||||
|
|
||||||
|
target: HTMLElement;
|
||||||
|
relatedTarget: HTMLElement;
|
||||||
|
|
||||||
stopPropagation: () => void;
|
stopPropagation: () => void;
|
||||||
preventDefault: () => void;
|
preventDefault: () => void;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,8 @@ import {
|
||||||
import { getDomTag } from '../dom/utils/Common';
|
import { getDomTag } from '../dom/utils/Common';
|
||||||
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
||||||
import { getDom } from '../dom/DOMInternalKeys';
|
import { getDom } from '../dom/DOMInternalKeys';
|
||||||
import { recordChangeEventTargets, shouldControlValue, tryControlValue } from './FormValueController';
|
import {recordChangeEventTargets, shouldControlValue, tryControlValue} from './FormValueController';
|
||||||
|
import {getMouseEnterListeners} from './MouseEvent';
|
||||||
|
|
||||||
// web规范,鼠标右键key值
|
// web规范,鼠标右键key值
|
||||||
const RIGHT_MOUSE_BUTTON = 2;
|
const RIGHT_MOUSE_BUTTON = 2;
|
||||||
|
@ -141,18 +142,26 @@ function triggerHorizonEvents(
|
||||||
const target = nativeEvent.target || nativeEvent.srcElement!;
|
const target = nativeEvent.target || nativeEvent.srcElement!;
|
||||||
|
|
||||||
// 触发普通委托事件
|
// 触发普通委托事件
|
||||||
let listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture);
|
const listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture);
|
||||||
|
|
||||||
|
let mouseEnterListeners: ListenerUnitList = [];
|
||||||
|
if (horizonEventToNativeMap.get('onMouseEnter')!.includes(nativeEvtName)) {
|
||||||
|
mouseEnterListeners = getMouseEnterListeners(
|
||||||
|
nativeEvtName,
|
||||||
|
vNode,
|
||||||
|
nativeEvent,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let changeEvents: ListenerUnitList = [];
|
||||||
// 触发特殊handler委托事件
|
// 触发特殊handler委托事件
|
||||||
if (!isCapture && horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
if (!isCapture && horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
||||||
const changeListeners = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
|
changeEvents = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
|
||||||
if (changeListeners.length) {
|
|
||||||
listenerList = listenerList.concat(changeListeners);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理触发的事件队列
|
// 处理触发的事件队列
|
||||||
processListeners(listenerList);
|
processListeners([...listenerList, ...mouseEnterListeners, ...changeEvents]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他事件正在执行中标记
|
// 其他事件正在执行中标记
|
||||||
|
|
|
@ -86,3 +86,93 @@ export function getListenersFromTree(
|
||||||
|
|
||||||
return listeners;
|
return listeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取enter和leave事件队列
|
||||||
|
export function collectMouseListeners(
|
||||||
|
leaveEvent: null | WrappedEvent,
|
||||||
|
enterEvent: null | WrappedEvent,
|
||||||
|
from: VNode | null,
|
||||||
|
to: VNode | null,
|
||||||
|
): ListenerUnitList {
|
||||||
|
// 确定公共父节点,作为在树上遍历的终点
|
||||||
|
const commonParent = from && to ? getCommonAncestor(from, to) : null;
|
||||||
|
let leaveEventList: ListenerUnitList = [];
|
||||||
|
if (from && leaveEvent) {
|
||||||
|
// 遍历树,获取绑定的leave事件
|
||||||
|
leaveEventList = getMouseListenersFromTree(
|
||||||
|
leaveEvent,
|
||||||
|
from,
|
||||||
|
commonParent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let enterEventList: ListenerUnitList = [];
|
||||||
|
if (to && enterEvent) {
|
||||||
|
// 先触发父节点enter事件,所以需要逆序
|
||||||
|
enterEventList = getMouseListenersFromTree(
|
||||||
|
enterEvent,
|
||||||
|
to,
|
||||||
|
commonParent,
|
||||||
|
).reverse();
|
||||||
|
}
|
||||||
|
return [...leaveEventList, ...enterEventList];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMouseListenersFromTree(
|
||||||
|
event: WrappedEvent,
|
||||||
|
target: VNode,
|
||||||
|
commonParent: VNode | null,
|
||||||
|
): ListenerUnitList {
|
||||||
|
const registrationName = event.customEventName;
|
||||||
|
const listeners: ListenerUnitList = [];
|
||||||
|
|
||||||
|
let vNode = target;
|
||||||
|
while (vNode !== null) {
|
||||||
|
// commonParent作为终点
|
||||||
|
if (vNode === commonParent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const {realNode, tag} = vNode;
|
||||||
|
if (tag === DomComponent && realNode !== null) {
|
||||||
|
const currentTarget = realNode;
|
||||||
|
const listener = getListenerFromVNode(vNode, registrationName);
|
||||||
|
if (listener) {
|
||||||
|
listeners.push({
|
||||||
|
vNode,
|
||||||
|
listener,
|
||||||
|
currentTarget,
|
||||||
|
event,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vNode = vNode.parent;
|
||||||
|
}
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寻找两个节点的共同最近祖先,如果没有则返回null
|
||||||
|
function getCommonAncestor(instA: VNode, instB: VNode): VNode | null {
|
||||||
|
const parentsSet = new Set<VNode>();
|
||||||
|
for (let tempA: VNode | null = instA; tempA; tempA = getParent(tempA)) {
|
||||||
|
parentsSet.add(tempA);
|
||||||
|
}
|
||||||
|
for (let tempB: VNode | null = instB; tempB; tempB = getParent(tempB)) {
|
||||||
|
if (parentsSet.has(tempB)) {
|
||||||
|
return tempB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取父节点
|
||||||
|
function getParent(inst: VNode | null): VNode | null {
|
||||||
|
if (inst === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
inst = inst.parent;
|
||||||
|
} while (inst && inst.tag !== DomComponent);
|
||||||
|
if (inst) {
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openGauss is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getNearestVNode} from '../dom/DOMInternalKeys';
|
||||||
|
import {WrappedEvent} from './EventWrapper';
|
||||||
|
import {VNode} from '../renderer/vnode/VNode';
|
||||||
|
import {AnyNativeEvent, ListenerUnitList} from './Types';
|
||||||
|
import {DomComponent, DomText} from '../renderer/vnode/VNodeTags';
|
||||||
|
import {collectMouseListeners} from './ListenerGetter';
|
||||||
|
import {getNearestMountedVNode} from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景: mouseEnter和mouseLeave事件不冒泡,所以无法直接委托给根节点进行代理
|
||||||
|
* 实现方案:利用mouseout、mouseover事件的,找到事件触发的起点和终点,判断出鼠标移动轨迹,在轨迹中的节点触发mouseEnter和mouseLeave事件
|
||||||
|
* 步骤:
|
||||||
|
* 1. 根节点绑定mouseout和mouseover事件
|
||||||
|
* 2. 事件触发后找到事件的起点和终点
|
||||||
|
* 3. 封装装enter和leave事件
|
||||||
|
* 4. 根据起止点找到公共父节点,作为事件冒泡的终点
|
||||||
|
* 5. 遍历treeNode,找到每个节点绑定的mouseEnter和mouseLeave监听方法
|
||||||
|
* 例如: mouseOut事件由D->C, A节点作为公共父节点,将触发 D、B的mouseLeave事件和C节点的mouseEnter事件
|
||||||
|
* A
|
||||||
|
* / \
|
||||||
|
* B C
|
||||||
|
* / \
|
||||||
|
* D E
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getWrapperEvents(nativeEventTarget, fromInst, toInst, nativeEvent, targetInst): (WrappedEvent | null)[] {
|
||||||
|
const vWindow = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget : nativeEventTarget.ownerDocument.defaultView;
|
||||||
|
|
||||||
|
// 起点或者终点为空的话默认值为所在window
|
||||||
|
const fromNode = fromInst?.realNode || vWindow;
|
||||||
|
const toNode = toInst?.realNode || vWindow;
|
||||||
|
let leave: WrappedEvent | null = null;
|
||||||
|
let enter: WrappedEvent | null = null;
|
||||||
|
const nativeTargetInst = getNearestVNode(nativeEventTarget);
|
||||||
|
|
||||||
|
// 在Mounted的dom节点上render一个子组件,系统中存在两个根节点,子节点的mouseout事件触发两次,取离target近的根节点生效
|
||||||
|
if (nativeTargetInst === targetInst) {
|
||||||
|
leave = new WrappedEvent('onMouseLeave', 'mouseleave', nativeEvent);
|
||||||
|
leave.target = fromNode;
|
||||||
|
leave.relatedTarget = toNode;
|
||||||
|
|
||||||
|
enter = new WrappedEvent('onMouseEnter', 'mouseenter', nativeEvent);
|
||||||
|
enter.target = toNode;
|
||||||
|
enter.relatedTarget = fromNode;
|
||||||
|
}
|
||||||
|
return [leave, enter];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEndpointVNode(
|
||||||
|
domEventName: string,
|
||||||
|
targetInst: null | VNode,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
): (VNode | null)[] {
|
||||||
|
let fromVNode;
|
||||||
|
let toVNode;
|
||||||
|
if (domEventName === 'mouseover') {
|
||||||
|
fromVNode = null;
|
||||||
|
toVNode = targetInst;
|
||||||
|
} else {
|
||||||
|
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
|
||||||
|
fromVNode = targetInst;
|
||||||
|
toVNode = related ? getNearestVNode(related) : null;
|
||||||
|
if (toVNode !== null) {
|
||||||
|
const nearestMounted = getNearestMountedVNode(toVNode);
|
||||||
|
if (toVNode !== nearestMounted || (toVNode.tag !== DomComponent && toVNode.tag !== DomText)) {
|
||||||
|
toVNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [fromVNode, toVNode];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMouseEnterListeners(
|
||||||
|
domEventName: string,
|
||||||
|
targetInst: null | VNode,
|
||||||
|
nativeEvent: AnyNativeEvent,
|
||||||
|
nativeEventTarget: null | EventTarget,
|
||||||
|
): ListenerUnitList {
|
||||||
|
|
||||||
|
// 获取起点和终点的VNode
|
||||||
|
const [fromVNode, toVNode] = getEndpointVNode(domEventName, targetInst, nativeEvent);
|
||||||
|
if (fromVNode === toVNode) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取包装后的leave和enter事件
|
||||||
|
const [leave, enter] = getWrapperEvents(nativeEventTarget, fromVNode, toVNode, nativeEvent, targetInst);
|
||||||
|
|
||||||
|
// 收集事件的监听方法
|
||||||
|
return collectMouseListeners(leave, enter, fromVNode, toVNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {VNode} from '../renderer/vnode/VNode';
|
||||||
|
import {Addition, FlagUtils} from '../renderer/vnode/VNodeFlags';
|
||||||
|
import {TreeRoot} from '../renderer/vnode/VNodeTags';
|
||||||
|
|
||||||
export function isInputElement(dom?: HTMLElement): boolean {
|
export function isInputElement(dom?: HTMLElement): boolean {
|
||||||
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
|
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +24,26 @@ export function isInputElement(dom?: HTMLElement): boolean {
|
||||||
export function setPropertyWritable(obj, propName) {
|
export function setPropertyWritable(obj, propName) {
|
||||||
const desc = Object.getOwnPropertyDescriptor(obj, propName);
|
const desc = Object.getOwnPropertyDescriptor(obj, propName);
|
||||||
if (!desc || !desc.writable) {
|
if (!desc || !desc.writable) {
|
||||||
Object.defineProperty(obj, propName, { writable: true });
|
Object.defineProperty(obj, propName, {writable: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取离 vNode 最近的已挂载 vNode,包含它自己
|
||||||
|
export function getNearestMountedVNode(vNode: VNode): null | VNode {
|
||||||
|
let node = vNode;
|
||||||
|
let target = vNode;
|
||||||
|
// 如果没有alternate,说明是可能是未插入的新树,需要处理插入的副作用。
|
||||||
|
while (node.parent) {
|
||||||
|
// 存在更新,节点未挂载,查找父节点,但是父节点也可能未挂载,需要继续往上查找无更新节点
|
||||||
|
if (FlagUtils.hasFlag(node, Addition)) {
|
||||||
|
target = node.parent;
|
||||||
|
}
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
// 如果根节点是 Dom 类型节点,表示已经挂载
|
||||||
|
if (node.tag === TreeRoot) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
// 如果没有找到根节点,意味着Tree已经卸载或者未挂载
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
import type { VNode } from '../Types';
|
import type { VNode } from '../Types';
|
||||||
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
||||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||||
import { popCurrentRoot, pushCurrentRoot } from '../RootStack';
|
import {popCurrentRoot, pushCurrentRoot} from '../RootStack';
|
||||||
|
import {listenSimulatedDelegatedEvents} from '../../event/EventBinding';
|
||||||
|
|
||||||
export function bubbleRender(processing: VNode) {
|
export function bubbleRender(processing: VNode) {
|
||||||
resetNamespaceCtx(processing);
|
resetNamespaceCtx(processing);
|
||||||
|
listenSimulatedDelegatedEvents(processing);
|
||||||
popCurrentRoot();
|
popCurrentRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,20 @@ export class FlagUtils {
|
||||||
static removeFlag(node: VNode, flag: number) {
|
static removeFlag(node: VNode, flag: number) {
|
||||||
node.flags &= ~flag;
|
node.flags &= ~flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeLifecycleEffectFlags(node) {
|
static removeLifecycleEffectFlags(node) {
|
||||||
node.flags &= ~LifecycleEffectArr;
|
node.flags &= ~LifecycleEffectArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static hasAnyFlag(node: VNode) {
|
static hasAnyFlag(node: VNode) {
|
||||||
// 有标志位
|
// 有标志位
|
||||||
return node.flags !== InitFlag;
|
return node.flags !== InitFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static hasFlag(node: VNode, flag) {
|
||||||
|
return (node.flags & flag) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
static setNoFlags(node: VNode) {
|
static setNoFlags(node: VNode) {
|
||||||
node.flags = InitFlag;
|
node.flags = InitFlag;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +61,7 @@ export class FlagUtils {
|
||||||
static markAddition(node: VNode) {
|
static markAddition(node: VNode) {
|
||||||
node.flags |= Addition;
|
node.flags |= Addition;
|
||||||
}
|
}
|
||||||
|
|
||||||
static setAddition(node: VNode) {
|
static setAddition(node: VNode) {
|
||||||
node.flags = Addition;
|
node.flags = Addition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,301 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openGauss is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
|
import * as Renderer from '../../../libs/horizon/src/renderer/Renderer';
|
||||||
|
import { doc } from 'prettier';
|
||||||
|
|
||||||
|
describe('EnterLeaveEventPlugin', () => {
|
||||||
|
let container;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
// The container has to be attached for events to fire.
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.removeChild(container);
|
||||||
|
container = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set onMouseLeave relatedTarget properly in iframe', () => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
container.appendChild(iframe);
|
||||||
|
const iframeDocument = iframe.contentDocument;
|
||||||
|
iframeDocument.write(
|
||||||
|
'<!DOCTYPE html><html><head></head><body><div></div></body></html>',
|
||||||
|
);
|
||||||
|
iframeDocument.close();
|
||||||
|
|
||||||
|
const leaveEvents = [];
|
||||||
|
const node = Horizon.render(
|
||||||
|
<div
|
||||||
|
onMouseLeave={e => {
|
||||||
|
e.persist();
|
||||||
|
leaveEvents.push(e);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
iframeDocument.body.getElementsByTagName('div')[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
node.dispatchEvent(
|
||||||
|
new MouseEvent('mouseout', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: iframe.contentWindow,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(leaveEvents.length).toBe(1);
|
||||||
|
expect(leaveEvents[0].target).toBe(node);
|
||||||
|
expect(leaveEvents[0].relatedTarget).toBe(iframe.contentWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set onMouseEnter relatedTarget properly in iframe', () => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
container.appendChild(iframe);
|
||||||
|
const iframeDocument = iframe.contentDocument;
|
||||||
|
iframeDocument.write(
|
||||||
|
'<!DOCTYPE html><html><head></head><body><div></div></body></html>',
|
||||||
|
);
|
||||||
|
iframeDocument.close();
|
||||||
|
|
||||||
|
const enterEvents = [];
|
||||||
|
const node = Horizon.render(
|
||||||
|
<div
|
||||||
|
onMouseEnter={e => {
|
||||||
|
e.persist();
|
||||||
|
enterEvents.push(e);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
iframeDocument.body.getElementsByTagName('div')[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
node.dispatchEvent(
|
||||||
|
new MouseEvent('mouseover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(enterEvents.length).toBe(1);
|
||||||
|
expect(enterEvents[0].target).toBe(node);
|
||||||
|
expect(enterEvents[0].relatedTarget).toBe(iframe.contentWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regression test for https://github.com/facebook/Horizon/issues/10906.
|
||||||
|
it('should find the common parent after updates', () => {
|
||||||
|
let parentEnterCalls = 0;
|
||||||
|
let childEnterCalls = 0;
|
||||||
|
let parent = null;
|
||||||
|
|
||||||
|
class Parent extends Horizon.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => parentEnterCalls++}
|
||||||
|
ref={node => (parent = node)}>
|
||||||
|
{this.props.showChild && (
|
||||||
|
<div onMouseEnter={() => childEnterCalls++}/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<Parent/>, container);
|
||||||
|
// The issue only reproduced on insertion during the first update.
|
||||||
|
Horizon.render(<Parent showChild={true}/>, container);
|
||||||
|
|
||||||
|
// Enter from parent into the child.
|
||||||
|
parent.dispatchEvent(
|
||||||
|
new MouseEvent('mouseout', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: parent.firstChild,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Entering a child should fire on the child, not on the parent.
|
||||||
|
expect(childEnterCalls).toBe(1);
|
||||||
|
expect(parentEnterCalls).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call mouseEnter once from sibling rendered inside a rendered component', done => {
|
||||||
|
const mockFn1 = jest.fn();
|
||||||
|
const mockFn2 = jest.fn();
|
||||||
|
const mockFn3 = jest.fn();
|
||||||
|
|
||||||
|
class Parent extends Horizon.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.parentEl = Horizon.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
Horizon.render(<MouseEnterDetect/>, this.parentEl.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div ref={this.parentEl} id="parent" onMouseLeave={mockFn3}/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MouseEnterDetect extends Horizon.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.firstEl = Horizon.createRef();
|
||||||
|
this.siblingEl = Horizon.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.siblingEl.current.dispatchEvent(
|
||||||
|
new MouseEvent('mouseout', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: this.firstEl.current,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(mockFn1.mock.calls.length).toBe(1);
|
||||||
|
expect(mockFn2.mock.calls.length).toBe(1);
|
||||||
|
expect(mockFn3.mock.calls.length).toBe(0);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Horizon.Fragment>
|
||||||
|
<div ref={this.firstEl} id="first" onMouseEnter={mockFn1}/>
|
||||||
|
<div ref={this.siblingEl} id="sibling" onMouseLeave={mockFn2}/>
|
||||||
|
</Horizon.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<Parent/>, container);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call mouseEnter when pressing a non tracked Horizon node', done => {
|
||||||
|
const mockFn = jest.fn();
|
||||||
|
|
||||||
|
class Parent extends Horizon.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.parentEl = Horizon.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
Horizon.render(<MouseEnterDetect/>, this.parentEl.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div ref={this.parentEl}/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MouseEnterDetect extends Horizon.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.divRef = Horizon.createRef();
|
||||||
|
this.siblingEl = Horizon.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const attachedNode = document.createElement('div');
|
||||||
|
this.divRef.current.appendChild(attachedNode);
|
||||||
|
attachedNode.dispatchEvent(
|
||||||
|
new MouseEvent('mouseout', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: this.siblingEl.current,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(mockFn.mock.calls.length).toBe(1);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div ref={this.divRef}>
|
||||||
|
<div ref={this.siblingEl} onMouseEnter={mockFn}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<Parent/>, container);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with portals outside of the root that has onMouseLeave', () => {
|
||||||
|
const divRef = Horizon.createRef();
|
||||||
|
const onMouseLeave = jest.fn();
|
||||||
|
|
||||||
|
function Component() {
|
||||||
|
return (
|
||||||
|
<div onMouseLeave={onMouseLeave} id="parent">
|
||||||
|
{Horizon.createPortal(<div ref={divRef} id="sub"/>, document.body)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<Component/>, container);
|
||||||
|
|
||||||
|
// Leave from the portal div
|
||||||
|
divRef.current.dispatchEvent(
|
||||||
|
new MouseEvent('mouseout', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: document.body,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onMouseLeave).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with portals that have onMouseEnter outside of the root ', () => {
|
||||||
|
const divRef = Horizon.createRef();
|
||||||
|
const otherDivRef = Horizon.createRef();
|
||||||
|
const onMouseEnter = jest.fn();
|
||||||
|
|
||||||
|
function Component() {
|
||||||
|
return (
|
||||||
|
<div ref={divRef}>
|
||||||
|
{Horizon.createPortal(
|
||||||
|
<div ref={otherDivRef} onMouseEnter={onMouseEnter}/>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<Component/>, container);
|
||||||
|
|
||||||
|
// Leave from the portal div
|
||||||
|
divRef.current.dispatchEvent(
|
||||||
|
new MouseEvent('mouseout', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
relatedTarget: otherDivRef.current,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onMouseEnter).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue