Match-id-c129f1002ec86566362106c475c16f5a771e5763

This commit is contained in:
* 2022-09-22 16:38:39 +08:00 committed by *
parent 17a8e1b13e
commit 2cddb7a6e5
11 changed files with 125 additions and 75 deletions

View File

@ -27,6 +27,7 @@ module.exports = {
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'off',
semi: ['warn', 'always'],

View File

@ -9,8 +9,6 @@ import { getSelectionInfo, resetSelectionRange, SelectionData } from './Selectio
import { shouldAutoFocus } from './utils/Common';
import { NSS } from './utils/DomCreator';
import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler';
import { listenDelegatedEvents } from '../event/EventBinding';
import type { VNode } from '../renderer/Types';
import {
setInitValue,

View File

@ -1,4 +1,4 @@
export interface IProperty {
export interface Props {
[propName: string]: any;
}

View File

@ -1,11 +1,8 @@
import { updateCommonProp } from '../DOMPropertiesHandler/UpdateCommonProp';
import { IProperty } from '../utils/Interface';
import { isInputElement } from '../utils/Common';
import { getVNodeProps } from '../DOMInternalKeys';
import { updateInputValueIfChanged } from './ValueChangeHandler';
import { Props } from '../utils/Interface';
function getInitValue(dom: HTMLInputElement, properties: IProperty) {
const { value, defaultValue, checked, defaultChecked } = properties;
function getInitValue(dom: HTMLInputElement, props: Props) {
const { value, defaultValue, checked, defaultChecked } = props;
const defaultValueStr = defaultValue != null ? defaultValue : '';
const initValue = value != null ? value : defaultValueStr;
@ -14,15 +11,15 @@ function getInitValue(dom: HTMLInputElement, properties: IProperty) {
return { initValue, initChecked };
}
export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IProperty) {
export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) {
// checked属于必填属性无法置
let {checked} = properties;
let {checked} = props;
if (checked == null) {
checked = getInitValue(dom, properties).initChecked;
checked = getInitValue(dom, props).initChecked;
}
return {
...properties,
...props,
value: undefined,
defaultValue: undefined,
defaultChecked: undefined,
@ -30,8 +27,8 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IPr
};
}
export function updateInputValue(dom: HTMLInputElement, properties: IProperty) {
const {value, checked} = properties;
export function updateInputValue(dom: HTMLInputElement, props: Props) {
const {value, checked} = props;
if (value != null) { // 处理 dom.value 逻辑
if (dom.value !== String(value)) {
@ -43,9 +40,9 @@ export function updateInputValue(dom: HTMLInputElement, properties: IProperty) {
}
// 设置input的初始值
export function setInitInputValue(dom: HTMLInputElement, properties: IProperty) {
const {value, defaultValue} = properties;
const {initValue, initChecked} = getInitValue(dom, properties);
export function setInitInputValue(dom: HTMLInputElement, props: Props) {
const {value, defaultValue} = props;
const {initValue, initChecked} = getInitValue(dom, props);
if (value != null || defaultValue != null) {
// value 的使用优先级 value 属性 > defaultValue 属性 > 空字符串
@ -59,27 +56,3 @@ export function setInitInputValue(dom: HTMLInputElement, properties: IProperty)
// checked 的使用优先级 checked 属性 > defaultChecked 属性 > false
dom.defaultChecked = Boolean(initChecked);
}
// 找出同一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;
}
updateInputValueIfChanged(radio);
}
}
}
}
}

View File

@ -1,5 +1,5 @@
import { Children } from '../../external/ChildrenUtil';
import { IProperty } from '../utils/Interface';
import { Props } from '../utils/Interface';
// 把 const a = 'a'; <option>gir{a}ffe</option> 转成 giraffe
function concatChildren(children) {
@ -11,11 +11,11 @@ function concatChildren(children) {
return content;
}
export function getOptionPropsWithoutValue(dom: Element, properties: IProperty) {
const content = concatChildren(properties.children);
export function getOptionPropsWithoutValue(dom: Element, props: Props) {
const content = concatChildren(props.children);
return {
...properties,
...props,
children: content || undefined, // 覆盖children
};
}

View File

@ -1,4 +1,4 @@
import {HorizonSelect, IProperty} from '../utils/Interface';
import {HorizonSelect, Props} from '../utils/Interface';
function updateMultipleValue(options, newValues) {
const newValueSet = new Set();
@ -46,8 +46,8 @@ export function getSelectPropsWithoutValue(dom: HorizonSelect, properties: Objec
};
}
export function updateSelectValue(dom: HorizonSelect, properties: IProperty, isInit: boolean = false) {
const {value, defaultValue, multiple} = properties;
export function updateSelectValue(dom: HorizonSelect, props: Props, isInit: boolean = false) {
const {value, defaultValue, multiple} = props;
const oldMultiple = dom._multiple !== undefined ? dom._multiple : dom.multiple;
const newMultiple = Boolean(multiple);

View File

@ -1,12 +1,12 @@
import {IProperty} from '../utils/Interface';
import {Props} from '../utils/Interface';
// 值的优先级 value > children > defaultValue
function getInitValue(properties: IProperty) {
const {value} = properties;
function getInitValue(props: Props) {
const {value} = props;
if (value == null) {
const {defaultValue, children} = properties;
const {defaultValue, children} = props;
let initValue = defaultValue;
// children content存在时会覆盖defaultValue
@ -30,15 +30,15 @@ export function getTextareaPropsWithoutValue(dom: HTMLTextAreaElement, propertie
};
}
export function updateTextareaValue(dom: HTMLTextAreaElement, properties: IProperty, isInit: boolean = false) {
export function updateTextareaValue(dom: HTMLTextAreaElement, props: Props, isInit: boolean = false) {
if (isInit) {
const initValue = getInitValue(properties);
const initValue = getInitValue(props);
if (initValue !== '') {
dom.value = initValue;
}
} else {
// 获取当前节点的 value 值
let value = properties.value;
let value = props.value;
if (value != null) {
value = String(value);
// 当且仅当值实际发生变化时才去设置节点的value值

View File

@ -54,7 +54,8 @@ export function watchValueChange(dom) {
}
}
export function updateInputValueIfChanged(dom) {
// 更新input dom的handler 状态,返回是否更新
export function updateInputHandlerIfChanged(dom) {
const handler = dom[HANDLER_KEY];
if (!handler) {
return true;

View File

@ -3,7 +3,7 @@
*
*/
import {HorizonDom, HorizonSelect, IProperty} from '../utils/Interface';
import {HorizonDom, HorizonSelect, Props} from '../utils/Interface';
import {
getInputPropsWithoutValue,
setInitInputValue,
@ -22,7 +22,7 @@ import {
} from './TextareaValueHandler';
// 获取元素除了被代理的值以外的属性
function getPropsWithoutValue(type: string, dom: HorizonDom, properties: IProperty) {
function getPropsWithoutValue(type: string, dom: HorizonDom, properties: Props) {
switch (type) {
case 'input':
return getInputPropsWithoutValue(<HTMLInputElement>dom, properties);
@ -38,7 +38,7 @@ function getPropsWithoutValue(type: string, dom: HorizonDom, properties: IProper
}
// 其它属性挂载完成后处理被代理值相关的属性
function setInitValue(type: string, dom: HorizonDom, properties: IProperty) {
function setInitValue(type: string, dom: HorizonDom, properties: Props) {
switch (type) {
case 'input':
setInitInputValue(<HTMLInputElement>dom, properties);
@ -55,7 +55,7 @@ function setInitValue(type: string, dom: HorizonDom, properties: IProperty) {
}
// 更新需要适配的属性
function updateValue(type: string, dom: HorizonDom, properties: IProperty) {
function updateValue(type: string, dom: HorizonDom, properties: Props) {
switch (type) {
case 'input':
updateInputValue(<HTMLInputElement>dom, properties);

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
*/
import { getVNodeProps } from '../dom/DOMInternalKeys';
import { getDomTag } from '../dom/utils/Common';
import { Props } from '../dom/utils/Interface';
import { updateTextareaValue } from '../dom/valueHandler/TextareaValueHandler';
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
import { updateInputValue } from '../dom/valueHandler/InputValueHandler';
// 记录表单控件 input/textarea/select的onChange事件的targets
let changeEventTargets: Array<any> | null = null;
// 存储队列中缓存组件
export function recordChangeEventTargets(target: EventTarget): void {
if (changeEventTargets) {
changeEventTargets.push(target);
} else {
changeEventTargets = [target];
}
}
// 判断是否需要控制value与props保持一致
export function shouldControlValue(): boolean {
return changeEventTargets !== null && changeEventTargets.length > 0;
}
// 从缓存队列中对受控组件进行赋值
export function tryControlValue() {
if (!changeEventTargets) {
return;
}
changeEventTargets.forEach(target => {
controlValue(target);
});
changeEventTargets = null;
}
// 受控组件值重新赋值
function controlValue(target: Element) {
const props = getVNodeProps(target);
if (props) {
const type = getDomTag(target);
switch (type) {
case 'input':
controlInputValue(<HTMLInputElement>target, props);
break;
case 'textarea':
updateTextareaValue(<HTMLTextAreaElement>target, props);
break;
default:
break;
}
}
}
function controlInputValue(inputDom: HTMLInputElement, props: Props) {
const { name, type } = props;
// 如果是 radio先更新相同 name 的 radio
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 === inputDom) {
continue;
}
if (radio.form != null && inputDom.form != null && radio.form !== inputDom.form) {
continue;
}
updateInputHandlerIfChanged(radio);
}
} else {
updateInputValue(inputDom, props);
}
}

View File

@ -5,7 +5,6 @@ import { decorateNativeEvent } from './EventWrapper';
import { getListenersFromTree } from './ListenerGetter';
import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer';
import { findRoot } from '../renderer/vnode/VNodeUtils';
import { syncRadiosHandler } from '../dom/valueHandler/InputValueHandler';
import {
EVENT_TYPE_ALL,
EVENT_TYPE_BUBBLE,
@ -14,8 +13,9 @@ import {
transformToHorizonEvent,
} from './EventHub';
import { getDomTag } from '../dom/utils/Common';
import { updateInputValueIfChanged } from '../dom/valueHandler/ValueChangeHandler';
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
import { getDom } from '../dom/DOMInternalKeys';
import { recordChangeEventTargets, shouldControlValue, tryControlValue } from './FormValueController';
// web规范鼠标右键key值
const RIGHT_MOUSE_BUTTON = 2;
@ -34,11 +34,11 @@ function shouldTriggerChangeEvent(targetDom, evtName) {
return evtName === 'change';
} else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
if (evtName === 'click') {
return updateInputValueIfChanged(targetDom);
return updateInputHandlerIfChanged(targetDom);
}
} else if (isInputElement(targetDom)) {
if (evtName === 'input' || evtName === 'change') {
return updateInputValueIfChanged(targetDom);
return updateInputHandlerIfChanged(targetDom);
}
}
return false;
@ -52,6 +52,7 @@ function getChangeListeners(
nativeEvtName: string,
nativeEvt: AnyNativeEvent,
vNode: null | VNode,
target: EventTarget
): ListenerUnitList {
if (!vNode) {
return [];
@ -60,6 +61,8 @@ function getChangeListeners(
// 判断是否需要触发change事件
if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
recordChangeEventTargets(target);
const event = decorateNativeEvent(
'onChange',
'change',
@ -129,8 +132,7 @@ function triggerHorizonEvents(
nativeEvent: AnyNativeEvent,
vNode: VNode | null,
) {
const target = nativeEvent.target || nativeEvent.srcElement;
let hasTriggeredChangeEvent = false;
const target = nativeEvent.target || nativeEvent.srcElement!;
// 触发普通委托事件
let listenerList: ListenerUnitList = getCommonListeners(
@ -147,17 +149,15 @@ function triggerHorizonEvents(
nativeEvtName,
nativeEvent,
vNode,
target
);
if (changeListeners.length) {
hasTriggeredChangeEvent = true;
listenerList = listenerList.concat(changeListeners);
}
}
// 处理触发的事件队列
processListeners(listenerList);
return hasTriggeredChangeEvent;
}
@ -188,15 +188,13 @@ export function handleEventMain(
// 没有事件在执行,经过调度再执行事件
isInEventsExecution = true;
let hasTriggeredChangeEvent = false;
try {
hasTriggeredChangeEvent = asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
} finally {
isInEventsExecution = false;
if (hasTriggeredChangeEvent) {
if (shouldControlValue()) {
runDiscreteUpdates();
// 若是Radio同步同组其他Radio的Handler Value
syncRadiosHandler(nativeEvent.target as Element);
tryControlValue();
}
}
}