Match-id-ff887de05a36a9d53176b27c20249d9d7588d755
This commit is contained in:
parent
c34df0d5f4
commit
9802399b1f
|
@ -26,7 +26,7 @@ import { watchValueChange } from './valueHandler/ValueChangeHandler';
|
|||
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
|
||||
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
|
||||
|
||||
export type Props = {
|
||||
export type Props = Record<string, any> & {
|
||||
autoFocus?: boolean;
|
||||
children?: any;
|
||||
dangerouslySetInnerHTML?: any;
|
||||
|
|
|
@ -1,22 +1,12 @@
|
|||
import {
|
||||
allDelegatedHorizonEvents,
|
||||
} from '../../event/EventCollection';
|
||||
import { allDelegatedHorizonEvents } from '../../event/EventCollection';
|
||||
import { updateCommonProp } from './UpdateCommonProp';
|
||||
import { setStyles } from './StyleHandler';
|
||||
import {
|
||||
lazyDelegateOnRoot,
|
||||
listenNonDelegatedEvent,
|
||||
} from '../../event/EventBinding';
|
||||
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
||||
import { isEventProp } from '../validators/ValidateProps';
|
||||
import { getCurrentRoot } from '../../renderer/TreeBuilder';
|
||||
|
||||
// 初始化DOM属性和更新 DOM 属性
|
||||
export function setDomProps(
|
||||
dom: Element,
|
||||
props: Object,
|
||||
isNativeTag: boolean,
|
||||
isInit: boolean,
|
||||
): void {
|
||||
export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, isInit: boolean): void {
|
||||
const keysOfProps = Object.keys(props);
|
||||
let propName;
|
||||
let propVal;
|
||||
|
@ -36,7 +26,8 @@ export function setDomProps(
|
|||
} 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;
|
||||
if (type === 'string' || type === 'number') {
|
||||
dom.textContent = propVal;
|
||||
|
@ -50,10 +41,7 @@ export function setDomProps(
|
|||
}
|
||||
|
||||
// 找出两个 DOM 属性的差别,生成需要更新的属性集合
|
||||
export function compareProps(
|
||||
oldProps: Object,
|
||||
newProps: Object,
|
||||
): Object {
|
||||
export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||
let updatesForStyle = {};
|
||||
const toUpdateProps = {};
|
||||
const keysOfOldProps = Object.keys(oldProps);
|
||||
|
@ -113,7 +101,8 @@ export function compareProps(
|
|||
}
|
||||
|
||||
if (propName === 'style') {
|
||||
if (oldPropValue) { // 之前 style 属性有设置非空值
|
||||
if (oldPropValue) {
|
||||
// 之前 style 属性有设置非空值
|
||||
// 原来有这个 style,但现在没这个 style 了
|
||||
oldStyleProps = Object.keys(oldPropValue);
|
||||
for (let j = 0; j < oldStyleProps.length; j++) {
|
||||
|
@ -131,7 +120,8 @@ export function compareProps(
|
|||
updatesForStyle[styleProp] = newPropValue[styleProp];
|
||||
}
|
||||
}
|
||||
} else { // 之前未设置 style 属性或者设置了空值
|
||||
} else {
|
||||
// 之前未设置 style 属性或者设置了空值
|
||||
if (Object.keys(updatesForStyle).length === 0) {
|
||||
toUpdateProps[propName] = null;
|
||||
}
|
||||
|
@ -150,10 +140,12 @@ export function compareProps(
|
|||
toUpdateProps[propName] = String(newPropValue);
|
||||
}
|
||||
} else if (isEventProp(propName)) {
|
||||
const currentRoot = getCurrentRoot();
|
||||
if (!allDelegatedHorizonEvents.has(propName)) {
|
||||
toUpdateProps[propName] = newPropValue;
|
||||
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
||||
lazyDelegateOnRoot(currentRoot, propName);
|
||||
}
|
||||
// TODO
|
||||
} else {
|
||||
toUpdateProps[propName] = newPropValue;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,10 @@ export function getDomTag(dom) {
|
|||
return dom.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
export function isInputElement(dom: Element): dom is HTMLInputElement {
|
||||
return getDomTag(dom) === 'input';
|
||||
}
|
||||
|
||||
const types = ['button', 'input', 'select', 'textarea'];
|
||||
|
||||
// button、input、select、textarea、如果有 autoFocus 属性需要focus
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import {updateCommonProp} from '../DOMPropertiesHandler/UpdateCommonProp';
|
||||
import {getVNodeProps} from '../DOMInternalKeys';
|
||||
import {IProperty} from '../utils/Interface';
|
||||
import {isInputValueChanged} from './ValueChangeHandler';
|
||||
import { updateCommonProp } from '../DOMPropertiesHandler/UpdateCommonProp';
|
||||
import { IProperty } from '../utils/Interface';
|
||||
import { isInputElement } from '../utils/Common';
|
||||
import { getVNodeProps } from '../DOMInternalKeys';
|
||||
import { updateInputValueIfChanged } from './ValueChangeHandler';
|
||||
|
||||
function getInitValue(dom: HTMLInputElement, properties: IProperty) {
|
||||
const {value, defaultValue, checked, defaultChecked} = properties;
|
||||
const { value, defaultValue, checked, defaultChecked } = properties;
|
||||
|
||||
const defaultValueStr = defaultValue != null ? defaultValue : '';
|
||||
const initValue = value != null ? value : defaultValueStr;
|
||||
const initChecked = checked != null ? checked : defaultChecked;
|
||||
|
||||
return {initValue, initChecked};
|
||||
return { initValue, initChecked };
|
||||
}
|
||||
|
||||
export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IProperty) {
|
||||
// checked属于必填属性,无法置空
|
||||
// checked属于必填属性,无法置
|
||||
let {checked} = properties;
|
||||
if (checked == null) {
|
||||
checked = getInitValue(dom, properties).initChecked;
|
||||
|
@ -59,30 +60,26 @@ export function setInitInputValue(dom: HTMLInputElement, properties: IProperty)
|
|||
dom.defaultChecked = Boolean(initChecked);
|
||||
}
|
||||
|
||||
export function resetInputValue(dom: HTMLInputElement, properties: IProperty) {
|
||||
const {name, type} = properties;
|
||||
// 如果是 radio,先更新相同 name 的 radio
|
||||
if (type === 'radio' && name != null) {
|
||||
const radioList = document.querySelectorAll(`input[type="radio"][name="${name}"]`);
|
||||
// 找出同一form内,name相同的Radio,更新它们Handler的Value
|
||||
export function syncRadiosHandler(targetRadio: Element) {
|
||||
if (isInputElement(targetRadio)) {
|
||||
const props = getVNodeProps(targetRadio);
|
||||
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++) {
|
||||
const radio = radioList[i];
|
||||
if (radio === dom) {
|
||||
continue;
|
||||
updateInputValueIfChanged(radio);
|
||||
}
|
||||
}
|
||||
// @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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export function watchValueChange(dom) {
|
|||
}
|
||||
}
|
||||
|
||||
export function isInputValueChanged(dom) {
|
||||
export function updateInputValueIfChanged(dom) {
|
||||
const handler = dom[HANDLER_KEY];
|
||||
if (!handler) {
|
||||
return true;
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
getInputPropsWithoutValue,
|
||||
setInitInputValue,
|
||||
updateInputValue,
|
||||
resetInputValue,
|
||||
} from './InputValueHandler';
|
||||
import {
|
||||
getOptionPropsWithoutValue,
|
||||
|
@ -21,7 +20,6 @@ import {
|
|||
getTextareaPropsWithoutValue,
|
||||
updateTextareaValue,
|
||||
} from './TextareaValueHandler';
|
||||
import {getDomTag} from '../utils/Common';
|
||||
|
||||
// 获取元素除了被代理的值以外的属性
|
||||
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 {
|
||||
getPropsWithoutValue,
|
||||
setInitValue,
|
||||
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,22 +1,15 @@
|
|||
import type { AnyNativeEvent } from './Types';
|
||||
import { ListenerUnitList } from './Types';
|
||||
import type { VNode } from '../renderer/Types';
|
||||
|
||||
import {
|
||||
CommonEventToHorizonMap,
|
||||
horizonEventToNativeMap,
|
||||
EVENT_TYPE_BUBBLE,
|
||||
EVENT_TYPE_CAPTURE,
|
||||
} from './const';
|
||||
import { CommonEventToHorizonMap, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE, horizonEventToNativeMap } from './const';
|
||||
import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler';
|
||||
import {
|
||||
setPropertyWritable,
|
||||
} from './utils';
|
||||
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 { findRoot } from '../renderer/vnode/VNodeUtils';
|
||||
import { ListenerUnitList } from './Types';
|
||||
import { syncRadiosHandler } from '../dom/valueHandler/InputValueHandler';
|
||||
|
||||
// web规范,鼠标右键key值
|
||||
const RIGHT_MOUSE_BUTTON = 2;
|
||||
|
@ -80,7 +73,6 @@ function getProcessListeners(
|
|||
target,
|
||||
isCapture: boolean,
|
||||
): ListenerUnitList {
|
||||
// TODO 重复从树中获取监听器
|
||||
// 触发普通委托事件
|
||||
let listenerList: ListenerUnitList = getCommonListeners(
|
||||
nativeEvtName,
|
||||
|
@ -92,12 +84,11 @@ function getProcessListeners(
|
|||
|
||||
// 触发特殊handler委托事件
|
||||
if (!isCapture) {
|
||||
if (horizonEventToNativeMap.get('onChange').includes(nativeEvtName)) {
|
||||
if (horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
||||
listenerList = listenerList.concat(getChangeListeners(
|
||||
nativeEvtName,
|
||||
nativeEvent,
|
||||
vNode,
|
||||
target,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +101,7 @@ function triggerHorizonEvents(
|
|||
isCapture: boolean,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
vNode: VNode | null,
|
||||
): void {
|
||||
) {
|
||||
const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement;
|
||||
|
||||
// 获取委托事件队列
|
||||
|
@ -118,6 +109,8 @@ function triggerHorizonEvents(
|
|||
|
||||
// 处理触发的事件队列
|
||||
processListeners(listenerList);
|
||||
|
||||
return listenerList;
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,13 +141,18 @@ export function handleEventMain(
|
|||
|
||||
// 没有事件在执行,经过调度再执行事件
|
||||
isInEventsExecution = true;
|
||||
let shouldDispatchUpdate = false;
|
||||
try {
|
||||
asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
|
||||
const listeners = asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
|
||||
if (listeners.length) {
|
||||
shouldDispatchUpdate = true;
|
||||
}
|
||||
} finally {
|
||||
isInEventsExecution = false;
|
||||
if (shouldUpdateValue()) {
|
||||
if (shouldDispatchUpdate) {
|
||||
runDiscreteUpdates();
|
||||
updateControlledValue();
|
||||
// 若是Radio,同步同组其他Radio的Handler Value
|
||||
syncRadiosHandler(nativeEvent.target as Element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ export const CommonEventToHorizonMap = {
|
|||
focusin: 'focus',
|
||||
focusout: 'blur',
|
||||
input: 'input',
|
||||
select: 'select',
|
||||
keydown: 'keyDown',
|
||||
keypress: 'keyPress',
|
||||
keyup: 'keyUp',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {decorateNativeEvent} from '../customEvents/EventFactory';
|
||||
import {getDom} from '../../dom/DOMInternalKeys';
|
||||
import {isInputValueChanged} from '../../dom/valueHandler/ValueChangeHandler';
|
||||
import {addValueUpdateList} from '../ControlledValueUpdater';
|
||||
import {updateInputValueIfChanged} from '../../dom/valueHandler/ValueChangeHandler';
|
||||
import {isInputElement} from '../utils';
|
||||
import {EVENT_TYPE_ALL} from '../const';
|
||||
import {AnyNativeEvent, ListenerUnitList} from '../Types';
|
||||
|
@ -25,11 +24,11 @@ function shouldTriggerChangeEvent(targetDom, evtName) {
|
|||
return evtName === 'change';
|
||||
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
|
||||
if (evtName === 'click') {
|
||||
return isInputValueChanged(targetDom);
|
||||
return updateInputValueIfChanged(targetDom);
|
||||
}
|
||||
} else if (isInputElement(targetDom)) {
|
||||
if (evtName === 'input' || evtName === 'change') {
|
||||
return isInputValueChanged(targetDom);
|
||||
return updateInputValueIfChanged(targetDom);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -42,8 +41,7 @@ function shouldTriggerChangeEvent(targetDom, evtName) {
|
|||
export function getListeners(
|
||||
nativeEvtName: string,
|
||||
nativeEvt: AnyNativeEvent,
|
||||
vNode: null | VNode,
|
||||
target: null | EventTarget,
|
||||
vNode: null | VNode
|
||||
): ListenerUnitList {
|
||||
if (!vNode) {
|
||||
return [];
|
||||
|
@ -52,7 +50,6 @@ export function getListeners(
|
|||
|
||||
// 判断是否需要触发change事件
|
||||
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
|
||||
addValueUpdateList(target);
|
||||
const event = decorateNativeEvent(
|
||||
'onChange',
|
||||
'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;
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
"scripts": {
|
||||
"lint": "eslint . --ext .ts",
|
||||
"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-dev": "npm run build & node ./scripts/gen3rdLib.js --dev",
|
||||
"build-horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev --type horizon",
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('事件', () => {
|
|||
'btn capture',
|
||||
'btn bubble',
|
||||
'p bubble',
|
||||
'div bubble'
|
||||
'div bubble',
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -46,14 +46,14 @@ describe('事件', () => {
|
|||
keyCode = e.keyCode;
|
||||
}}
|
||||
/>,
|
||||
container,
|
||||
container
|
||||
);
|
||||
node.dispatchEvent(
|
||||
new KeyboardEvent('keypress', {
|
||||
keyCode: 65,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
expect(keyCode).toBe(65);
|
||||
});
|
||||
|
@ -64,7 +64,10 @@ describe('事件', () => {
|
|||
<>
|
||||
<div onClickCapture={() => LogUtils.log('div capture')} onClick={() => LogUtils.log('div 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>
|
||||
</div>
|
||||
</>
|
||||
|
@ -78,7 +81,7 @@ describe('事件', () => {
|
|||
'div capture',
|
||||
'p capture',
|
||||
'btn capture',
|
||||
'btn bubble'
|
||||
'btn bubble',
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -86,7 +89,10 @@ describe('事件', () => {
|
|||
const App = () => {
|
||||
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')}>
|
||||
<button onClickCapture={() => LogUtils.log('btn capture')} onClick={() => LogUtils.log('btn bubble')} />
|
||||
</p>
|
||||
|
@ -99,7 +105,7 @@ describe('事件', () => {
|
|||
|
||||
expect(LogUtils.getAndClear()).toEqual([
|
||||
// 阻止捕获,不再继续向下执行
|
||||
'div capture'
|
||||
'div capture',
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -114,19 +120,93 @@ describe('事件', () => {
|
|||
);
|
||||
};
|
||||
Horizon.render(<App />, container);
|
||||
container.querySelector('div').addEventListener('click', () => {
|
||||
LogUtils.log('div bubble');
|
||||
}, false);
|
||||
container.querySelector('p').addEventListener('click', () => {
|
||||
LogUtils.log('p bubble');
|
||||
}, false);
|
||||
container.querySelector('button').addEventListener('click', (e) => {
|
||||
LogUtils.log('btn bubble');
|
||||
e.stopPropagation();
|
||||
}, false);
|
||||
container.querySelector('div').addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
LogUtils.log('div bubble');
|
||||
},
|
||||
false
|
||||
);
|
||||
container.querySelector('p').addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
LogUtils.log('p bubble');
|
||||
},
|
||||
false
|
||||
);
|
||||
container.querySelector('button').addEventListener(
|
||||
'click',
|
||||
e => {
|
||||
LogUtils.log('btn bubble');
|
||||
e.stopPropagation();
|
||||
},
|
||||
false
|
||||
);
|
||||
container.querySelector('button').click();
|
||||
expect(LogUtils.getAndClear()).toEqual([
|
||||
'btn bubble'
|
||||
]);
|
||||
expect(LogUtils.getAndClear()).toEqual(['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