Match-id-8ce33abf265e5cd8c7284f1c95cee752495e9b55
This commit is contained in:
commit
d466afb2c6
|
@ -22,9 +22,6 @@ function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
const treeRoot = createTreeRootVNode(container);
|
const treeRoot = createTreeRootVNode(container);
|
||||||
container._treeRoot = treeRoot;
|
container._treeRoot = treeRoot;
|
||||||
|
|
||||||
// 根节点挂接全量事件
|
|
||||||
listenDelegatedEvents(container as Element);
|
|
||||||
|
|
||||||
// 执行回调
|
// 执行回调
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
const cb = callback;
|
const cb = callback;
|
||||||
|
|
|
@ -49,23 +49,17 @@ export function getVNode(dom: Node|Container): VNode | null {
|
||||||
|
|
||||||
// 用 DOM 对象,来寻找其对应或者说是最近父级的 vNode
|
// 用 DOM 对象,来寻找其对应或者说是最近父级的 vNode
|
||||||
export function getNearestVNode(dom: Node): null | VNode {
|
export function getNearestVNode(dom: Node): null | VNode {
|
||||||
let vNode = dom[INTERNAL_VNODE];
|
let domNode: Node | null = dom;
|
||||||
if (vNode) { // 如果是已经被框架标记过的 DOM 节点,那么直接返回其 VNode 实例
|
// 寻找当前节点及其所有祖先节点是否有标记VNODE
|
||||||
return vNode;
|
while (domNode) {
|
||||||
|
const vNode = domNode[INTERNAL_VNODE];
|
||||||
|
if (vNode) {
|
||||||
|
return vNode;
|
||||||
|
}
|
||||||
|
domNode = domNode.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下面处理的是为被框架标记过的 DOM 节点,向上找其父节点是否被框架标记过
|
return null;
|
||||||
let parentDom = dom.parentNode;
|
|
||||||
let nearVNode = null;
|
|
||||||
while (parentDom) {
|
|
||||||
vNode = parentDom[INTERNAL_VNODE];
|
|
||||||
if (vNode) {
|
|
||||||
nearVNode = vNode;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parentDom = parentDom.parentNode;
|
|
||||||
}
|
|
||||||
return nearVNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 vNode 上的属性相关信息
|
// 获取 vNode 上的属性相关信息
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { watchValueChange } from './valueHandler/ValueChangeHandler';
|
||||||
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
|
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
|
||||||
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
|
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = Record<string, any> & {
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
children?: any;
|
children?: any;
|
||||||
dangerouslySetInnerHTML?: any;
|
dangerouslySetInnerHTML?: any;
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
import {
|
import { allDelegatedHorizonEvents } from '../../event/EventCollection';
|
||||||
allDelegatedHorizonEvents,
|
|
||||||
} from '../../event/EventCollection';
|
|
||||||
import { updateCommonProp } from './UpdateCommonProp';
|
import { updateCommonProp } from './UpdateCommonProp';
|
||||||
import { setStyles } from './StyleHandler';
|
import { setStyles } from './StyleHandler';
|
||||||
import {
|
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
||||||
listenNonDelegatedEvent
|
|
||||||
} from '../../event/EventBinding';
|
|
||||||
import { isEventProp } from '../validators/ValidateProps';
|
import { isEventProp } from '../validators/ValidateProps';
|
||||||
|
import { getCurrentRoot } from '../../renderer/TreeBuilder';
|
||||||
|
|
||||||
// 初始化DOM属性和更新 DOM 属性
|
// 初始化DOM属性和更新 DOM 属性
|
||||||
export function setDomProps(
|
export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, isInit: boolean): void {
|
||||||
dom: Element,
|
|
||||||
props: Object,
|
|
||||||
isNativeTag: boolean,
|
|
||||||
isInit: boolean,
|
|
||||||
): void {
|
|
||||||
const keysOfProps = Object.keys(props);
|
const keysOfProps = Object.keys(props);
|
||||||
let propName;
|
let propName;
|
||||||
let propVal;
|
let propVal;
|
||||||
|
@ -27,10 +19,15 @@ export function setDomProps(
|
||||||
setStyles(dom, propVal);
|
setStyles(dom, propVal);
|
||||||
} else if (isEventProp(propName)) {
|
} else if (isEventProp(propName)) {
|
||||||
// 事件监听属性处理
|
// 事件监听属性处理
|
||||||
|
// TODO
|
||||||
|
const currentRoot = getCurrentRoot();
|
||||||
if (!allDelegatedHorizonEvents.has(propName)) {
|
if (!allDelegatedHorizonEvents.has(propName)) {
|
||||||
listenNonDelegatedEvent(propName, dom, propVal);
|
listenNonDelegatedEvent(propName, dom, propVal);
|
||||||
|
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
||||||
|
lazyDelegateOnRoot(currentRoot, propName);
|
||||||
}
|
}
|
||||||
} else if (propName === 'children') { // 只处理纯文本子节点,其他children在VNode树中处理
|
} else if (propName === 'children') {
|
||||||
|
// 只处理纯文本子节点,其他children在VNode树中处理
|
||||||
const type = typeof propVal;
|
const type = typeof propVal;
|
||||||
if (type === 'string' || type === 'number') {
|
if (type === 'string' || type === 'number') {
|
||||||
dom.textContent = propVal;
|
dom.textContent = propVal;
|
||||||
|
@ -44,10 +41,7 @@ export function setDomProps(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 找出两个 DOM 属性的差别,生成需要更新的属性集合
|
// 找出两个 DOM 属性的差别,生成需要更新的属性集合
|
||||||
export function compareProps(
|
export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||||
oldProps: Object,
|
|
||||||
newProps: Object,
|
|
||||||
): Object {
|
|
||||||
let updatesForStyle = {};
|
let updatesForStyle = {};
|
||||||
const toUpdateProps = {};
|
const toUpdateProps = {};
|
||||||
const keysOfOldProps = Object.keys(oldProps);
|
const keysOfOldProps = Object.keys(oldProps);
|
||||||
|
@ -107,7 +101,8 @@ export function compareProps(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propName === 'style') {
|
if (propName === 'style') {
|
||||||
if (oldPropValue) { // 之前 style 属性有设置非空值
|
if (oldPropValue) {
|
||||||
|
// 之前 style 属性有设置非空值
|
||||||
// 原来有这个 style,但现在没这个 style 了
|
// 原来有这个 style,但现在没这个 style 了
|
||||||
oldStyleProps = Object.keys(oldPropValue);
|
oldStyleProps = Object.keys(oldPropValue);
|
||||||
for (let j = 0; j < oldStyleProps.length; j++) {
|
for (let j = 0; j < oldStyleProps.length; j++) {
|
||||||
|
@ -125,7 +120,8 @@ export function compareProps(
|
||||||
updatesForStyle[styleProp] = newPropValue[styleProp];
|
updatesForStyle[styleProp] = newPropValue[styleProp];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // 之前未设置 style 属性或者设置了空值
|
} else {
|
||||||
|
// 之前未设置 style 属性或者设置了空值
|
||||||
if (Object.keys(updatesForStyle).length === 0) {
|
if (Object.keys(updatesForStyle).length === 0) {
|
||||||
toUpdateProps[propName] = null;
|
toUpdateProps[propName] = null;
|
||||||
}
|
}
|
||||||
|
@ -144,8 +140,11 @@ export function compareProps(
|
||||||
toUpdateProps[propName] = String(newPropValue);
|
toUpdateProps[propName] = String(newPropValue);
|
||||||
}
|
}
|
||||||
} else if (isEventProp(propName)) {
|
} else if (isEventProp(propName)) {
|
||||||
|
const currentRoot = getCurrentRoot();
|
||||||
if (!allDelegatedHorizonEvents.has(propName)) {
|
if (!allDelegatedHorizonEvents.has(propName)) {
|
||||||
toUpdateProps[propName] = newPropValue;
|
toUpdateProps[propName] = newPropValue;
|
||||||
|
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
||||||
|
lazyDelegateOnRoot(currentRoot, propName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toUpdateProps[propName] = newPropValue;
|
toUpdateProps[propName] = newPropValue;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
function isNeedUnitCSS(propName: string) {
|
function isNeedUnitCSS(styleName: string) {
|
||||||
return !(noUnitCSS.includes(propName)
|
return !(noUnitCSS.includes(styleName)
|
||||||
|| propName.startsWith('borderImage')
|
|| styleName.startsWith('borderImage')
|
||||||
|| propName.startsWith('flex')
|
|| styleName.startsWith('flex')
|
||||||
|| propName.startsWith('gridRow')
|
|| styleName.startsWith('gridRow')
|
||||||
|| propName.startsWith('gridColumn')
|
|| styleName.startsWith('gridColumn')
|
||||||
|| propName.startsWith('stroke')
|
|| styleName.startsWith('stroke')
|
||||||
|| propName.startsWith('box')
|
|| styleName.startsWith('box')
|
||||||
|| propName.endsWith('Opacity'));
|
|| styleName.endsWith('Opacity'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,9 +38,7 @@ export function setStyles(dom, styles) {
|
||||||
Object.keys(styles).forEach((name) => {
|
Object.keys(styles).forEach((name) => {
|
||||||
const styleVal = styles[name];
|
const styleVal = styles[name];
|
||||||
|
|
||||||
const validStyleValue = adjustStyleValue(name, styleVal);
|
style[name] = adjustStyleValue(name, styleVal);
|
||||||
|
|
||||||
style[name] = validStyleValue;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,10 @@ export function getDomTag(dom) {
|
||||||
return dom.nodeName.toLowerCase();
|
return dom.nodeName.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isInputElement(dom: Element): dom is HTMLInputElement {
|
||||||
|
return getDomTag(dom) === 'input';
|
||||||
|
}
|
||||||
|
|
||||||
const types = ['button', 'input', 'select', 'textarea'];
|
const types = ['button', 'input', 'select', 'textarea'];
|
||||||
|
|
||||||
// button、input、select、textarea、如果有 autoFocus 属性需要focus
|
// button、input、select、textarea、如果有 autoFocus 属性需要focus
|
||||||
|
|
|
@ -6,7 +6,7 @@ const INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/;
|
||||||
|
|
||||||
|
|
||||||
// 是内置元素
|
// 是内置元素
|
||||||
export function isNativeElement(tagName: string, props: Object) {
|
export function isNativeElement(tagName: string, props: Record<string, any>) {
|
||||||
return !tagName.includes('-') && props.is === undefined;
|
return !tagName.includes('-') && props.is === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import {updateCommonProp} from '../DOMPropertiesHandler/UpdateCommonProp';
|
import { updateCommonProp } from '../DOMPropertiesHandler/UpdateCommonProp';
|
||||||
import {getVNodeProps} from '../DOMInternalKeys';
|
import { IProperty } from '../utils/Interface';
|
||||||
import {IProperty} from '../utils/Interface';
|
import { isInputElement } from '../utils/Common';
|
||||||
import {isInputValueChanged} from './ValueChangeHandler';
|
import { getVNodeProps } from '../DOMInternalKeys';
|
||||||
|
import { updateInputValueIfChanged } from './ValueChangeHandler';
|
||||||
|
|
||||||
function getInitValue(dom: HTMLInputElement, properties: IProperty) {
|
function getInitValue(dom: HTMLInputElement, properties: IProperty) {
|
||||||
const {value, defaultValue, checked, defaultChecked} = properties;
|
const { value, defaultValue, checked, defaultChecked } = properties;
|
||||||
|
|
||||||
const defaultValueStr = defaultValue != null ? defaultValue : '';
|
const defaultValueStr = defaultValue != null ? defaultValue : '';
|
||||||
const initValue = value != null ? value : defaultValueStr;
|
const initValue = value != null ? value : defaultValueStr;
|
||||||
const initChecked = checked != null ? checked : defaultChecked;
|
const initChecked = checked != null ? checked : defaultChecked;
|
||||||
|
|
||||||
return {initValue, initChecked};
|
return { initValue, initChecked };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IProperty) {
|
export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IProperty) {
|
||||||
// checked属于必填属性,无法置空
|
// checked属于必填属性,无法置
|
||||||
let {checked} = properties;
|
let {checked} = properties;
|
||||||
if (checked == null) {
|
if (checked == null) {
|
||||||
checked = getInitValue(dom, properties).initChecked;
|
checked = getInitValue(dom, properties).initChecked;
|
||||||
|
@ -59,30 +60,26 @@ export function setInitInputValue(dom: HTMLInputElement, properties: IProperty)
|
||||||
dom.defaultChecked = Boolean(initChecked);
|
dom.defaultChecked = Boolean(initChecked);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetInputValue(dom: HTMLInputElement, properties: IProperty) {
|
// 找出同一form内,name相同的Radio,更新它们Handler的Value
|
||||||
const {name, type} = properties;
|
export function syncRadiosHandler(targetRadio: Element) {
|
||||||
// 如果是 radio,先更新相同 name 的 radio
|
if (isInputElement(targetRadio)) {
|
||||||
if (type === 'radio' && name != null) {
|
const props = getVNodeProps(targetRadio);
|
||||||
const radioList = document.querySelectorAll(`input[type="radio"][name="${name}"]`);
|
if (props) {
|
||||||
|
const { name, type } = props;
|
||||||
|
if (type === 'radio' && name != null) {
|
||||||
|
const radioList = document.querySelectorAll<HTMLInputElement>(`input[type="radio"][name="${name}"]`);
|
||||||
|
for (let i = 0; i < radioList.length; i++) {
|
||||||
|
const radio = radioList[i];
|
||||||
|
if (radio === targetRadio) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (radio.form != null && targetRadio.form != null && radio.form !== targetRadio.form) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < radioList.length; i++) {
|
updateInputValueIfChanged(radio);
|
||||||
const radio = radioList[i];
|
}
|
||||||
if (radio === dom) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
|
||||||
if (radio.form !== dom.form) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const nonHorizonRadioProps = getVNodeProps(radio);
|
|
||||||
|
|
||||||
isInputValueChanged(radio);
|
|
||||||
// @ts-ignore
|
|
||||||
updateInputValue(radio, nonHorizonRadioProps);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
updateInputValue(dom, properties);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function getSelectPropsWithoutValue(dom: HorizonSelect, properties: Objec
|
||||||
return {
|
return {
|
||||||
...properties,
|
...properties,
|
||||||
value: undefined,
|
value: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateSelectValue(dom: HorizonSelect, properties: IProperty, isInit: boolean = false) {
|
export function updateSelectValue(dom: HorizonSelect, properties: IProperty, isInit: boolean = false) {
|
||||||
|
|
|
@ -54,7 +54,7 @@ export function watchValueChange(dom) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInputValueChanged(dom) {
|
export function updateInputValueIfChanged(dom) {
|
||||||
const handler = dom[HANDLER_KEY];
|
const handler = dom[HANDLER_KEY];
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
getInputPropsWithoutValue,
|
getInputPropsWithoutValue,
|
||||||
setInitInputValue,
|
setInitInputValue,
|
||||||
updateInputValue,
|
updateInputValue,
|
||||||
resetInputValue,
|
|
||||||
} from './InputValueHandler';
|
} from './InputValueHandler';
|
||||||
import {
|
import {
|
||||||
getOptionPropsWithoutValue,
|
getOptionPropsWithoutValue,
|
||||||
|
@ -21,7 +20,6 @@ import {
|
||||||
getTextareaPropsWithoutValue,
|
getTextareaPropsWithoutValue,
|
||||||
updateTextareaValue,
|
updateTextareaValue,
|
||||||
} from './TextareaValueHandler';
|
} from './TextareaValueHandler';
|
||||||
import {getDomTag} from "../utils/Common";
|
|
||||||
|
|
||||||
// 获取元素除了被代理的值以外的属性
|
// 获取元素除了被代理的值以外的属性
|
||||||
function getPropsWithoutValue(type: string, dom: HorizonDom, properties: IProperty) {
|
function getPropsWithoutValue(type: string, dom: HorizonDom, properties: IProperty) {
|
||||||
|
@ -73,26 +71,8 @@ function updateValue(type: string, dom: HorizonDom, properties: IProperty) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetValue(dom: HorizonDom, properties: IProperty) {
|
|
||||||
const type = getDomTag(dom);
|
|
||||||
switch (type) {
|
|
||||||
case 'input':
|
|
||||||
resetInputValue(<HTMLInputElement>dom, properties);
|
|
||||||
break;
|
|
||||||
case 'select':
|
|
||||||
updateSelectValue(<HorizonSelect>dom, properties);
|
|
||||||
break;
|
|
||||||
case 'textarea':
|
|
||||||
updateTextareaValue(<HTMLTextAreaElement>dom, properties);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getPropsWithoutValue,
|
getPropsWithoutValue,
|
||||||
setInitValue,
|
setInitValue,
|
||||||
updateValue,
|
updateValue,
|
||||||
resetValue,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* 事件绑定实现,分为绑定委托事件和非委托事件
|
* 事件绑定实现,分为绑定委托事件和非委托事件
|
||||||
*/
|
*/
|
||||||
import {allDelegatedNativeEvents} from './EventCollection';
|
import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventCollection';
|
||||||
import {isDocument} from '../dom/utils/Common';
|
import {isDocument} from '../dom/utils/Common';
|
||||||
import {
|
import {
|
||||||
getNearestVNode,
|
getNearestVNode,
|
||||||
|
@ -12,6 +12,7 @@ import {isMounted} from '../renderer/vnode/VNodeUtils';
|
||||||
import {SuspenseComponent} from '../renderer/vnode/VNodeTags';
|
import {SuspenseComponent} from '../renderer/vnode/VNodeTags';
|
||||||
import {handleEventMain} from './HorizonEventMain';
|
import {handleEventMain} from './HorizonEventMain';
|
||||||
import {decorateNativeEvent} from './customEvents/EventFactory';
|
import {decorateNativeEvent} from './customEvents/EventFactory';
|
||||||
|
import { VNode } from '../renderer/vnode/VNode';
|
||||||
|
|
||||||
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
|
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
|
||||||
|
|
||||||
|
@ -26,18 +27,8 @@ function triggerDelegatedEvent(
|
||||||
runDiscreteUpdates();
|
runDiscreteUpdates();
|
||||||
|
|
||||||
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
|
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
|
||||||
let targetVNode = getNearestVNode(nativeEventTarget);
|
const 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);
|
handleEventMain(nativeEvtName, isCapture, nativeEvent, targetVNode, targetDom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +64,16 @@ export function listenDelegatedEvents(dom: Element) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
||||||
|
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
||||||
|
currentRoot.delegatedEvents.add(eventName);
|
||||||
|
|
||||||
|
const isCapture = isCaptureEvent(eventName);
|
||||||
|
const nativeEvents = allDelegatedHorizonEvents.get(eventName);
|
||||||
|
nativeEvents.forEach(nativeEvents => {
|
||||||
|
listenToNativeEvent(nativeEvents, currentRoot.realNode, isCapture);
|
||||||
|
});
|
||||||
|
}
|
||||||
// 通过horizon事件名获取到native事件名
|
// 通过horizon事件名获取到native事件名
|
||||||
function getNativeEvtName(horizonEventName, capture) {
|
function getNativeEvtName(horizonEventName, capture) {
|
||||||
let nativeName;
|
let nativeName;
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
import type { AnyNativeEvent } from './Types';
|
import type { AnyNativeEvent } from './Types';
|
||||||
|
import { ListenerUnitList } from './Types';
|
||||||
import type { VNode } from '../renderer/Types';
|
import type { VNode } from '../renderer/Types';
|
||||||
|
|
||||||
import {
|
import { CommonEventToHorizonMap, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE, horizonEventToNativeMap } from './const';
|
||||||
CommonEventToHorizonMap,
|
|
||||||
horizonEventToNativeMap,
|
|
||||||
EVENT_TYPE_BUBBLE,
|
|
||||||
EVENT_TYPE_CAPTURE,
|
|
||||||
} from './const';
|
|
||||||
import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler';
|
import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler';
|
||||||
import { getListeners as getSelectionListeners } from './simulatedEvtHandler/SelectionEventHandler';
|
import { setPropertyWritable } from './utils';
|
||||||
import {
|
|
||||||
setPropertyWritable,
|
|
||||||
} from './utils';
|
|
||||||
import { decorateNativeEvent } from './customEvents/EventFactory';
|
import { decorateNativeEvent } from './customEvents/EventFactory';
|
||||||
import { getListenersFromTree } from './ListenerGetter';
|
import { getListenersFromTree } from './ListenerGetter';
|
||||||
import { shouldUpdateValue, updateControlledValue } from './ControlledValueUpdater';
|
|
||||||
import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer';
|
import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer';
|
||||||
import { getExactNode } from '../renderer/vnode/VNodeUtils';
|
import { findRoot } from '../renderer/vnode/VNodeUtils';
|
||||||
import {ListenerUnitList} from './Types';
|
import { syncRadiosHandler } from '../dom/valueHandler/InputValueHandler';
|
||||||
|
|
||||||
|
// web规范,鼠标右键key值
|
||||||
|
const RIGHT_MOUSE_BUTTON = 2;
|
||||||
|
|
||||||
// 获取事件触发的普通事件监听方法队列
|
// 获取事件触发的普通事件监听方法队列
|
||||||
function getCommonListeners(
|
function getCommonListeners(
|
||||||
|
@ -35,7 +30,7 @@ function getCommonListeners(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标点击右键
|
// 鼠标点击右键
|
||||||
if (nativeEvent instanceof MouseEvent && nativeEvtName === 'click' && nativeEvent.button === 2) {
|
if (nativeEvent instanceof MouseEvent && nativeEvtName === 'click' && nativeEvent.button === RIGHT_MOUSE_BUTTON) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +71,7 @@ function getProcessListeners(
|
||||||
vNode: VNode | null,
|
vNode: VNode | null,
|
||||||
nativeEvent: AnyNativeEvent,
|
nativeEvent: AnyNativeEvent,
|
||||||
target,
|
target,
|
||||||
isCapture: boolean
|
isCapture: boolean,
|
||||||
): ListenerUnitList {
|
): ListenerUnitList {
|
||||||
// 触发普通委托事件
|
// 触发普通委托事件
|
||||||
let listenerList: ListenerUnitList = getCommonListeners(
|
let listenerList: ListenerUnitList = getCommonListeners(
|
||||||
|
@ -89,21 +84,11 @@ function getProcessListeners(
|
||||||
|
|
||||||
// 触发特殊handler委托事件
|
// 触发特殊handler委托事件
|
||||||
if (!isCapture) {
|
if (!isCapture) {
|
||||||
if (horizonEventToNativeMap.get('onChange').includes(nativeEvtName)) {
|
if (horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
||||||
listenerList = listenerList.concat(getChangeListeners(
|
listenerList = listenerList.concat(getChangeListeners(
|
||||||
nativeEvtName,
|
nativeEvtName,
|
||||||
nativeEvent,
|
nativeEvent,
|
||||||
vNode,
|
vNode,
|
||||||
target,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (horizonEventToNativeMap.get('onSelect').includes(nativeEvtName)) {
|
|
||||||
listenerList = listenerList.concat(getSelectionListeners(
|
|
||||||
nativeEvtName,
|
|
||||||
nativeEvent,
|
|
||||||
vNode,
|
|
||||||
target,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +101,7 @@ function triggerHorizonEvents(
|
||||||
isCapture: boolean,
|
isCapture: boolean,
|
||||||
nativeEvent: AnyNativeEvent,
|
nativeEvent: AnyNativeEvent,
|
||||||
vNode: VNode | null,
|
vNode: VNode | null,
|
||||||
): void {
|
) {
|
||||||
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
|
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
|
||||||
|
|
||||||
// 获取委托事件队列
|
// 获取委托事件队列
|
||||||
|
@ -124,6 +109,8 @@ function triggerHorizonEvents(
|
||||||
|
|
||||||
// 处理触发的事件队列
|
// 处理触发的事件队列
|
||||||
processListeners(listenerList);
|
processListeners(listenerList);
|
||||||
|
|
||||||
|
return listenerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,11 +123,11 @@ export function handleEventMain(
|
||||||
isCapture: boolean,
|
isCapture: boolean,
|
||||||
nativeEvent: AnyNativeEvent,
|
nativeEvent: AnyNativeEvent,
|
||||||
vNode: null | VNode,
|
vNode: null | VNode,
|
||||||
targetContainer: EventTarget,
|
targetDom: EventTarget,
|
||||||
): void {
|
): void {
|
||||||
let startVNode = vNode;
|
let startVNode = vNode;
|
||||||
if (startVNode !== null) {
|
if (startVNode !== null) {
|
||||||
startVNode = getExactNode(startVNode, targetContainer);
|
startVNode = findRoot(startVNode, targetDom);
|
||||||
if (!startVNode) {
|
if (!startVNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -154,13 +141,18 @@ export function handleEventMain(
|
||||||
|
|
||||||
// 没有事件在执行,经过调度再执行事件
|
// 没有事件在执行,经过调度再执行事件
|
||||||
isInEventsExecution = true;
|
isInEventsExecution = true;
|
||||||
|
let shouldDispatchUpdate = false;
|
||||||
try {
|
try {
|
||||||
asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
|
const listeners = asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
|
||||||
|
if (listeners.length) {
|
||||||
|
shouldDispatchUpdate = true;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isInEventsExecution = false;
|
isInEventsExecution = false;
|
||||||
if (shouldUpdateValue()) {
|
if (shouldDispatchUpdate) {
|
||||||
runDiscreteUpdates();
|
runDiscreteUpdates();
|
||||||
updateControlledValue();
|
// 若是Radio,同步同组其他Radio的Handler Value
|
||||||
|
syncRadiosHandler(nativeEvent.target as Element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ export const horizonEventToNativeMap = new Map([
|
||||||
['onCompositionStart', ['compositionstart']],
|
['onCompositionStart', ['compositionstart']],
|
||||||
['onCompositionUpdate', ['compositionupdate']],
|
['onCompositionUpdate', ['compositionupdate']],
|
||||||
['onChange', ['change', 'click', 'focusout', 'input']],
|
['onChange', ['change', 'click', 'focusout', 'input']],
|
||||||
['onSelect', ['focusout', 'contextmenu', 'dragend', 'focusin',
|
['onSelect', ['select']],
|
||||||
'keydown', 'keyup', 'mousedown', 'mouseup', 'selectionchange']],
|
|
||||||
|
|
||||||
['onAnimationEnd', ['animationend']],
|
['onAnimationEnd', ['animationend']],
|
||||||
['onAnimationIteration', ['animationiteration']],
|
['onAnimationIteration', ['animationiteration']],
|
||||||
|
@ -45,6 +44,7 @@ export const CommonEventToHorizonMap = {
|
||||||
focusin: 'focus',
|
focusin: 'focus',
|
||||||
focusout: 'blur',
|
focusout: 'blur',
|
||||||
input: 'input',
|
input: 'input',
|
||||||
|
select: 'select',
|
||||||
keydown: 'keyDown',
|
keydown: 'keyDown',
|
||||||
keypress: 'keyPress',
|
keypress: 'keyPress',
|
||||||
keyup: 'keyUp',
|
keyup: 'keyUp',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {decorateNativeEvent} from '../customEvents/EventFactory';
|
import {decorateNativeEvent} from '../customEvents/EventFactory';
|
||||||
import {getDom} from '../../dom/DOMInternalKeys';
|
import {getDom} from '../../dom/DOMInternalKeys';
|
||||||
import {isInputValueChanged} from '../../dom/valueHandler/ValueChangeHandler';
|
import {updateInputValueIfChanged} from '../../dom/valueHandler/ValueChangeHandler';
|
||||||
import {addValueUpdateList} from '../ControlledValueUpdater';
|
|
||||||
import {isInputElement} from '../utils';
|
import {isInputElement} from '../utils';
|
||||||
import {EVENT_TYPE_ALL} from '../const';
|
import {EVENT_TYPE_ALL} from '../const';
|
||||||
import {AnyNativeEvent, ListenerUnitList} from '../Types';
|
import {AnyNativeEvent, ListenerUnitList} from '../Types';
|
||||||
|
@ -12,6 +11,11 @@ import {VNode} from '../../renderer/Types';
|
||||||
import {getDomTag} from '../../dom/utils/Common';
|
import {getDomTag} from '../../dom/utils/Common';
|
||||||
|
|
||||||
// 返回是否需要触发change事件标记
|
// 返回是否需要触发change事件标记
|
||||||
|
// | 元素 | 事件 | 需要值变更 |
|
||||||
|
// | --- | --- | --------------- |
|
||||||
|
// | <select/> / <input type="file/> | change | NO |
|
||||||
|
// | <input type="checkbox" /> <input type="radio" /> | click | YES |
|
||||||
|
// | <input type="input /> / <input type="text" /> | input / change | YES |
|
||||||
function shouldTriggerChangeEvent(targetDom, evtName) {
|
function shouldTriggerChangeEvent(targetDom, evtName) {
|
||||||
const { type } = targetDom;
|
const { type } = targetDom;
|
||||||
const domTag = getDomTag(targetDom);
|
const domTag = getDomTag(targetDom);
|
||||||
|
@ -20,11 +24,11 @@ function shouldTriggerChangeEvent(targetDom, evtName) {
|
||||||
return evtName === 'change';
|
return evtName === 'change';
|
||||||
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
|
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
|
||||||
if (evtName === 'click') {
|
if (evtName === 'click') {
|
||||||
return isInputValueChanged(targetDom);
|
return updateInputValueIfChanged(targetDom);
|
||||||
}
|
}
|
||||||
} else if (isInputElement(targetDom)) {
|
} else if (isInputElement(targetDom)) {
|
||||||
if (evtName === 'input' || evtName === 'change') {
|
if (evtName === 'input' || evtName === 'change') {
|
||||||
return isInputValueChanged(targetDom);
|
return updateInputValueIfChanged(targetDom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -37,8 +41,7 @@ function shouldTriggerChangeEvent(targetDom, evtName) {
|
||||||
export function getListeners(
|
export function getListeners(
|
||||||
nativeEvtName: string,
|
nativeEvtName: string,
|
||||||
nativeEvt: AnyNativeEvent,
|
nativeEvt: AnyNativeEvent,
|
||||||
vNode: null | VNode,
|
vNode: null | VNode
|
||||||
target: null | EventTarget,
|
|
||||||
): ListenerUnitList {
|
): ListenerUnitList {
|
||||||
if (!vNode) {
|
if (!vNode) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -47,7 +50,6 @@ export function getListeners(
|
||||||
|
|
||||||
// 判断是否需要触发change事件
|
// 判断是否需要触发change事件
|
||||||
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
|
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
|
||||||
addValueUpdateList(target);
|
|
||||||
const event = decorateNativeEvent(
|
const event = decorateNativeEvent(
|
||||||
'onChange',
|
'onChange',
|
||||||
'change',
|
'change',
|
||||||
|
|
|
@ -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事件
|
|
||||||
* 支持元素: input、textarea、contentEditable元素
|
|
||||||
* 触发场景:用户输入、折叠选择、文本选择
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
|
|
||||||
export function isInputElement(dom?: HTMLElement): boolean {
|
export function isInputElement(dom?: HTMLElement): boolean {
|
||||||
if (dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement) {
|
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setPropertyWritable(obj, propName) {
|
export function setPropertyWritable(obj, propName) {
|
||||||
|
|
|
@ -43,6 +43,11 @@ let unrecoverableErrorDuringBuild: any = null;
|
||||||
|
|
||||||
// 当前运行的vNode节点
|
// 当前运行的vNode节点
|
||||||
let processing: VNode | null = null;
|
let processing: VNode | null = null;
|
||||||
|
let currentRoot: VNode | null = null;
|
||||||
|
export function getCurrentRoot() {
|
||||||
|
return currentRoot;
|
||||||
|
}
|
||||||
|
|
||||||
export function setProcessing(vNode: VNode | null) {
|
export function setProcessing(vNode: VNode | null) {
|
||||||
processing = vNode;
|
processing = vNode;
|
||||||
}
|
}
|
||||||
|
@ -267,7 +272,7 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
// 总体任务入口
|
// 总体任务入口
|
||||||
function renderFromRoot(treeRoot) {
|
function renderFromRoot(treeRoot) {
|
||||||
runAsyncEffects();
|
runAsyncEffects();
|
||||||
|
currentRoot = treeRoot;
|
||||||
// 1. 构建vNode树
|
// 1. 构建vNode树
|
||||||
buildVNodeTree(treeRoot);
|
buildVNodeTree(treeRoot);
|
||||||
|
|
||||||
|
@ -278,6 +283,7 @@ function renderFromRoot(treeRoot) {
|
||||||
|
|
||||||
// 2. 提交变更
|
// 2. 提交变更
|
||||||
submitToRender(treeRoot);
|
submitToRender(treeRoot);
|
||||||
|
currentRoot = null;
|
||||||
|
|
||||||
if (window.__HORIZON_DEV_HOOK__) {
|
if (window.__HORIZON_DEV_HOOK__) {
|
||||||
const hook = window.__HORIZON_DEV_HOOK__;
|
const hook = window.__HORIZON_DEV_HOOK__;
|
||||||
|
|
|
@ -74,7 +74,10 @@ export class VNode {
|
||||||
suspenseState: SuspenseState;
|
suspenseState: SuspenseState;
|
||||||
|
|
||||||
path = ''; // 保存从根到本节点的路径
|
path = ''; // 保存从根到本节点的路径
|
||||||
|
|
||||||
|
// 根节点数据
|
||||||
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
|
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
|
||||||
|
delegatedEvents: Set<string>
|
||||||
|
|
||||||
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
|
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
|
||||||
|
|
||||||
|
@ -94,6 +97,7 @@ export class VNode {
|
||||||
this.realNode = realNode;
|
this.realNode = realNode;
|
||||||
this.task = null;
|
this.task = null;
|
||||||
this.toUpdateNodes = new Set<VNode>();
|
this.toUpdateNodes = new Set<VNode>();
|
||||||
|
this.delegatedEvents = new Set<string>();
|
||||||
this.updates = null;
|
this.updates = null;
|
||||||
this.stateCallbacks = null;
|
this.stateCallbacks = null;
|
||||||
this.state = null;
|
this.state = null;
|
||||||
|
|
|
@ -31,7 +31,6 @@ export function travelVNodeTree(
|
||||||
finishVNode: VNode, // 结束遍历节点,有时候和beginVNode不相同
|
finishVNode: VNode, // 结束遍历节点,有时候和beginVNode不相同
|
||||||
handleWhenToParent: Function | null
|
handleWhenToParent: Function | null
|
||||||
): VNode | null {
|
): VNode | null {
|
||||||
const filter = childFilter === null;
|
|
||||||
let node = beginVNode;
|
let node = beginVNode;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -43,7 +42,7 @@ export function travelVNodeTree(
|
||||||
|
|
||||||
// 找子节点
|
// 找子节点
|
||||||
const childVNode = node.child;
|
const childVNode = node.child;
|
||||||
if (childVNode !== null && (filter || !childFilter(node))) {
|
if (childVNode !== null && (childFilter === null || !childFilter(node))) {
|
||||||
childVNode.parent = node;
|
childVNode.parent = node;
|
||||||
node = childVNode;
|
node = childVNode;
|
||||||
continue;
|
continue;
|
||||||
|
@ -194,20 +193,6 @@ export function getSiblingDom(vNode: VNode): Element | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSameContainer(
|
|
||||||
container: Element,
|
|
||||||
targetContainer: EventTarget,
|
|
||||||
): boolean {
|
|
||||||
if (container === targetContainer) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// 注释类型的节点
|
|
||||||
if (isComment(container) && container.parentNode === targetContainer) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPortalRoot(vNode, targetContainer) {
|
function isPortalRoot(vNode, targetContainer) {
|
||||||
if (vNode.tag === DomPortal) {
|
if (vNode.tag === DomPortal) {
|
||||||
let topVNode = vNode.parent;
|
let topVNode = vNode.parent;
|
||||||
|
@ -216,7 +201,7 @@ function isPortalRoot(vNode, targetContainer) {
|
||||||
if (grandTag === TreeRoot || grandTag === DomPortal) {
|
if (grandTag === TreeRoot || grandTag === DomPortal) {
|
||||||
const topContainer = topVNode.realNode;
|
const topContainer = topVNode.realNode;
|
||||||
// 如果topContainer是targetContainer,不需要在这里处理
|
// 如果topContainer是targetContainer,不需要在这里处理
|
||||||
if (isSameContainer(topContainer, targetContainer)) {
|
if (topContainer === targetContainer) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,28 +213,28 @@ function isPortalRoot(vNode, targetContainer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取根vNode节点
|
// 获取根vNode节点
|
||||||
export function getExactNode(targetVNode, targetContainer) {
|
export function findRoot(targetVNode, targetDom) {
|
||||||
// 确认vNode节点是否准确,portal场景下可能祖先节点不准确
|
// 确认vNode节点是否准确,portal场景下可能祖先节点不准确
|
||||||
let vNode = targetVNode;
|
let vNode = targetVNode;
|
||||||
while (vNode !== null) {
|
while (vNode !== null) {
|
||||||
if (vNode.tag === TreeRoot || vNode.tag === DomPortal) {
|
if (vNode.tag === TreeRoot || vNode.tag === DomPortal) {
|
||||||
let container = vNode.realNode;
|
let dom = vNode.realNode;
|
||||||
if (isSameContainer(container, targetContainer)) {
|
if (dom === targetDom) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (isPortalRoot(vNode, targetContainer)) {
|
if (isPortalRoot(vNode, targetDom)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (container !== null) {
|
while (dom !== null) {
|
||||||
const parentNode = getNearestVNode(container);
|
const parentNode = getNearestVNode(dom);
|
||||||
if (parentNode === null) {
|
if (parentNode === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (parentNode.tag === DomComponent || parentNode.tag === DomText) {
|
if (parentNode.tag === DomComponent || parentNode.tag === DomText) {
|
||||||
return getExactNode(parentNode, targetContainer);
|
return findRoot(parentNode, targetDom);
|
||||||
}
|
}
|
||||||
container = container.parentNode;
|
dom = dom.parentNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vNode = vNode.parent;
|
vNode = vNode.parent;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
"build": " rollup --config ./scripts/rollup/rollup.config.js",
|
"build": " rollup --config ./scripts/rollup/rollup.config.js",
|
||||||
|
"build:watch": " rollup --watch --config ./scripts/rollup/rollup.config.js",
|
||||||
"build-3rdLib": "node ./scripts/gen3rdLib.js",
|
"build-3rdLib": "node ./scripts/gen3rdLib.js",
|
||||||
"build-3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev",
|
"build-3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev",
|
||||||
"build-horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev --type horizon",
|
"build-horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev --type horizon",
|
||||||
|
|
|
@ -61,4 +61,10 @@ describe('Dom Attribute', () => {
|
||||||
container.querySelector('div').setAttribute('data-first-name', 'Tom');
|
container.querySelector('div').setAttribute('data-first-name', 'Tom');
|
||||||
expect(container.querySelector('div').dataset.firstName).toBe('Tom');
|
expect(container.querySelector('div').dataset.firstName).toBe('Tom');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('style 自动加px', () => {
|
||||||
|
const div = Horizon.render(<div style={{width: 10, height: 20}}/>, container);
|
||||||
|
expect(window.getComputedStyle(div).getPropertyValue('width')).toBe('10px');
|
||||||
|
expect(window.getComputedStyle(div).getPropertyValue('height')).toBe('20px');
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -172,6 +172,11 @@ describe('Dom Input', () => {
|
||||||
expect(realNode.getAttribute('value')).toBe('default');
|
expect(realNode.getAttribute('value')).toBe('default');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('value为0、defaultValue为1,input 的value应该为0', () => {
|
||||||
|
const input = Horizon.render(<input defaultValue={1} value={0} />, container);
|
||||||
|
expect(input.getAttribute('value')).toBe('0');
|
||||||
|
});
|
||||||
|
|
||||||
it('name属性', () => {
|
it('name属性', () => {
|
||||||
let realNode = Horizon.render(<input type='text' name={'name'} />, container);
|
let realNode = Horizon.render(<input type='text' name={'name'} />, container);
|
||||||
expect(realNode.name).toBe('name');
|
expect(realNode.name).toBe('name');
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe('事件', () => {
|
||||||
'btn capture',
|
'btn capture',
|
||||||
'btn bubble',
|
'btn bubble',
|
||||||
'p bubble',
|
'p bubble',
|
||||||
'div bubble'
|
'div bubble',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,14 +46,14 @@ describe('事件', () => {
|
||||||
keyCode = e.keyCode;
|
keyCode = e.keyCode;
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
container,
|
container
|
||||||
);
|
);
|
||||||
node.dispatchEvent(
|
node.dispatchEvent(
|
||||||
new KeyboardEvent('keypress', {
|
new KeyboardEvent('keypress', {
|
||||||
keyCode: 65,
|
keyCode: 65,
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
expect(keyCode).toBe(65);
|
expect(keyCode).toBe(65);
|
||||||
});
|
});
|
||||||
|
@ -64,7 +64,10 @@ describe('事件', () => {
|
||||||
<>
|
<>
|
||||||
<div onClickCapture={() => LogUtils.log('div capture')} onClick={() => LogUtils.log('div bubble')}>
|
<div onClickCapture={() => LogUtils.log('div capture')} onClick={() => LogUtils.log('div bubble')}>
|
||||||
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
|
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
|
||||||
<button onClickCapture={() => LogUtils.log('btn capture')} onClick={(e) => TestUtils.stopBubbleOrCapture(e, 'btn bubble')} />
|
<button
|
||||||
|
onClickCapture={() => LogUtils.log('btn capture')}
|
||||||
|
onClick={e => TestUtils.stopBubbleOrCapture(e, 'btn bubble')}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -78,7 +81,7 @@ describe('事件', () => {
|
||||||
'div capture',
|
'div capture',
|
||||||
'p capture',
|
'p capture',
|
||||||
'btn capture',
|
'btn capture',
|
||||||
'btn bubble'
|
'btn bubble',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -86,7 +89,10 @@ describe('事件', () => {
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div onClickCapture={(e) => TestUtils.stopBubbleOrCapture(e, 'div capture')} onClick={() => LogUtils.log('div bubble')}>
|
<div
|
||||||
|
onClickCapture={e => TestUtils.stopBubbleOrCapture(e, 'div capture')}
|
||||||
|
onClick={() => LogUtils.log('div bubble')}
|
||||||
|
>
|
||||||
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
|
<p onClickCapture={() => LogUtils.log('p capture')} onClick={() => LogUtils.log('p bubble')}>
|
||||||
<button onClickCapture={() => LogUtils.log('btn capture')} onClick={() => LogUtils.log('btn bubble')} />
|
<button onClickCapture={() => LogUtils.log('btn capture')} onClick={() => LogUtils.log('btn bubble')} />
|
||||||
</p>
|
</p>
|
||||||
|
@ -99,7 +105,7 @@ describe('事件', () => {
|
||||||
|
|
||||||
expect(LogUtils.getAndClear()).toEqual([
|
expect(LogUtils.getAndClear()).toEqual([
|
||||||
// 阻止捕获,不再继续向下执行
|
// 阻止捕获,不再继续向下执行
|
||||||
'div capture'
|
'div capture',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,19 +120,93 @@ describe('事件', () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Horizon.render(<App />, container);
|
Horizon.render(<App />, container);
|
||||||
container.querySelector('div').addEventListener('click', () => {
|
container.querySelector('div').addEventListener(
|
||||||
LogUtils.log('div bubble');
|
'click',
|
||||||
}, false);
|
() => {
|
||||||
container.querySelector('p').addEventListener('click', () => {
|
LogUtils.log('div bubble');
|
||||||
LogUtils.log('p bubble');
|
},
|
||||||
}, false);
|
false
|
||||||
container.querySelector('button').addEventListener('click', (e) => {
|
);
|
||||||
LogUtils.log('btn bubble');
|
container.querySelector('p').addEventListener(
|
||||||
e.stopPropagation();
|
'click',
|
||||||
}, false);
|
() => {
|
||||||
|
LogUtils.log('p bubble');
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
container.querySelector('button').addEventListener(
|
||||||
|
'click',
|
||||||
|
e => {
|
||||||
|
LogUtils.log('btn bubble');
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
container.querySelector('button').click();
|
container.querySelector('button').click();
|
||||||
expect(LogUtils.getAndClear()).toEqual([
|
expect(LogUtils.getAndClear()).toEqual(['btn bubble']);
|
||||||
'btn bubble'
|
});
|
||||||
]);
|
|
||||||
|
it('动态增加事件', () => {
|
||||||
|
let update;
|
||||||
|
let inputRef = Horizon.createRef();
|
||||||
|
|
||||||
|
function Test() {
|
||||||
|
const [inputProps, setProps] = Horizon.useState({});
|
||||||
|
update = setProps;
|
||||||
|
return <input ref={inputRef} {...inputProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<Test />, container);
|
||||||
|
update({
|
||||||
|
onChange: () => {
|
||||||
|
LogUtils.log('change');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||||
|
nativeInputValueSetter.call(inputRef.current, 'test');
|
||||||
|
|
||||||
|
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
|
||||||
|
expect(LogUtils.getAndClear()).toEqual(['change']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Radio change事件', () => {
|
||||||
|
let radio1Called = 0;
|
||||||
|
let radio2Called = 0;
|
||||||
|
|
||||||
|
function onChange1() {
|
||||||
|
radio1Called++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange2() {
|
||||||
|
radio2Called++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const radio1Ref = Horizon.createRef();
|
||||||
|
const radio2Ref = Horizon.createRef();
|
||||||
|
|
||||||
|
Horizon.render(
|
||||||
|
<>
|
||||||
|
<input type="radio" ref={radio1Ref} name="name" onChange={onChange1} />
|
||||||
|
<input type="radio" ref={radio2Ref} name="name" onChange={onChange2} />
|
||||||
|
</>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
|
||||||
|
function clickRadioAndExpect(radio, [expect1, expect2]) {
|
||||||
|
radio.click();
|
||||||
|
expect(radio1Called).toBe(expect1);
|
||||||
|
expect(radio2Called).toBe(expect2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先选择选项1
|
||||||
|
clickRadioAndExpect(radio1Ref.current, [1, 0]);
|
||||||
|
|
||||||
|
// 再选择选项1
|
||||||
|
clickRadioAndExpect(radio2Ref.current, [1, 1]);
|
||||||
|
|
||||||
|
// 先选择选项1,radio1应该重新触发onchange
|
||||||
|
clickRadioAndExpect(radio1Ref.current, [2, 1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue