Match-id-83e1b0255618a15d7c8d75ae1129659faae1c249

This commit is contained in:
* 2022-10-03 17:53:40 +08:00
commit f6c6e0db43
17 changed files with 167 additions and 127 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

@ -1,3 +1,9 @@
## 0.0.22 (2022-09-22)
- **core**: #83 #75 #72 input支持受控
## 0.0.21 (2022-09-20)
- **core**: #85 所有事件中发生的多次更新都合并执行
## 0.0.20 (2022-09-14)
- **core**: #81 fix Memo场景路径错误

View File

@ -4,7 +4,7 @@
"keywords": [
"horizon"
],
"version": "0.0.20",
"version": "0.0.22",
"homepage": "",
"bugs": "",
"main": "index.js",

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,32 +22,32 @@ import {
} from './TextareaValueHandler';
// 获取元素除了被代理的值以外的属性
function getPropsWithoutValue(type: string, dom: HorizonDom, properties: IProperty) {
function getPropsWithoutValue(type: string, dom: HorizonDom, props: Props) {
switch (type) {
case 'input':
return getInputPropsWithoutValue(<HTMLInputElement>dom, properties);
return getInputPropsWithoutValue(<HTMLInputElement>dom, props);
case 'option':
return getOptionPropsWithoutValue(dom, properties);
return getOptionPropsWithoutValue(dom, props);
case 'select':
return getSelectPropsWithoutValue(<HorizonSelect>dom, properties);
return getSelectPropsWithoutValue(<HorizonSelect>dom, props);
case 'textarea':
return getTextareaPropsWithoutValue(<HTMLTextAreaElement>dom, properties);
return getTextareaPropsWithoutValue(<HTMLTextAreaElement>dom, props);
default:
return properties;
return props;
}
}
// 其它属性挂载完成后处理被代理值相关的属性
function setInitValue(type: string, dom: HorizonDom, properties: IProperty) {
function setInitValue(type: string, dom: HorizonDom, props: Props) {
switch (type) {
case 'input':
setInitInputValue(<HTMLInputElement>dom, properties);
setInitInputValue(<HTMLInputElement>dom, props);
break;
case 'select':
updateSelectValue(<HorizonSelect>dom, properties, true);
updateSelectValue(<HorizonSelect>dom, props, true);
break;
case 'textarea':
updateTextareaValue(<HTMLTextAreaElement>dom, properties, true);
updateTextareaValue(<HTMLTextAreaElement>dom, props, true);
break;
default:
break;
@ -55,16 +55,16 @@ function setInitValue(type: string, dom: HorizonDom, properties: IProperty) {
}
// 更新需要适配的属性
function updateValue(type: string, dom: HorizonDom, properties: IProperty) {
function updateValue(type: string, dom: HorizonDom, props: Props) {
switch (type) {
case 'input':
updateInputValue(<HTMLInputElement>dom, properties);
updateInputValue(<HTMLInputElement>dom, props);
break;
case 'select':
updateSelectValue(<HorizonSelect>dom, properties);
updateSelectValue(<HorizonSelect>dom, props);
break;
case 'textarea':
updateTextareaValue(<HTMLTextAreaElement>dom, properties);
updateTextareaValue(<HTMLTextAreaElement>dom, props);
break;
default:
break;

View File

@ -7,7 +7,7 @@ import {
} from './EventHub';
import { isDocument } from '../dom/utils/Common';
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
import { runDiscreteUpdates } from '../renderer/TreeBuilder';
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
import { handleEventMain } from './HorizonEventMain';
import { decorateNativeEvent } from './EventWrapper';
import { VNode } from '../renderer/vnode/VNode';
@ -98,7 +98,9 @@ function isCaptureEvent(horizonEventName) {
function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) {
return event => {
const customEvent = decorateNativeEvent(horizonEventName, nativeEvtName, event);
listener(customEvent);
asyncUpdates(() => {
listener(customEvent);
});
};
}

View File

@ -12,6 +12,7 @@ export const horizonEventToNativeMap = new Map([
['onFocus', ['focusin']],
['onBlur', ['focusout']],
['onInput', ['input']],
['onWheel', ['wheel']],
['onMouseOut', ['mouseout']],
['onMouseOver', ['mouseover']],
['onPointerOut', ['pointerout']],
@ -41,6 +42,7 @@ export const horizonEventToNativeMap = new Map([
]);
export const NativeEventToHorizonMap = {
click: 'click',
wheel: 'wheel',
dblclick: 'doubleClick',
contextmenu: 'contextMenu',
dragend: 'dragEnd',

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找出同一form内name相同的Radio更新它们Handler的Value
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();
}
}
}

View File

@ -7,9 +7,9 @@
"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",
"build:3rdLib": "node ./scripts/gen3rdLib.js build:3rdLib",
"build:3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js build:3rdLib-dev",
"build:horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js build:horizon3rdLib-dev",
"build-types": "tsc -p ./libs/horizon/index.ts --emitDeclarationOnly --declaration --declarationDir ./build/horizon/@types --skipLibCheck",
"debug-test": "yarn test --debug",
"test": "jest --config=jest.config.js",

View File

@ -1,44 +1,24 @@
'use strict';
const ejs = require('ejs');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const console = require('console');
const rimRaf = require('rimRaf');
const argv = require('minimist')(process.argv.slice(2));
const fs = require('fs');
const childProcess = require('child_process');
const libPathPrefix = '../build';
const suffix = argv.dev ? 'development.js' : 'production.js';
const template = argv.type === 'horizon' ? 'horizon3rdTemplate.ejs' : 'template.ejs';
const templatePath = path.resolve(__dirname, `./${template}`);
if (!fs.existsSync(templatePath)) {
console.log(chalk.yellow('Failed: Template file not exist'));
return;
const horizonEcoPath = path.resolve(__dirname, '../../horizon-ecosystem');
if (!fs.existsSync(horizonEcoPath)) {
throw Error('horizon-ecosystem not found, put horizon-core and horizon-ecosystem in same folder plz!');
}
const readLib = lib => {
const libName = lib.split('.')[0];
const libPath = path.resolve(__dirname, `${libPathPrefix}/${libName}/umd/${lib}`);
if (fs.existsSync(libPath)) {
return fs.readFileSync(libPath, 'utf-8');
} else {
console.log(chalk.red(`Error: "${libPath}" 文件不存在\n先运行 npm run build`));
}
};
ejs.renderFile(
path.resolve(__dirname, `./${template}`),
const cmd = process.argv[2];
childProcess.exec(
`npm run ${cmd}`,
{
Horizon: readLib(`horizon.${suffix}`),
cwd: horizonEcoPath,
},
null,
function(err, result) {
const common3rdLibPath = path.resolve(__dirname, `${libPathPrefix}/horizonCommon3rdlib.min.js`);
rimRaf(common3rdLibPath, e => {
if (e) {
console.log(e);
}
fs.writeFileSync(common3rdLibPath, result);
console.log(chalk.green(`成功生成: ${common3rdLibPath}`));
});
function (error, stdout) {
if (error) {
console.log(`Error: ${error}`);
} else {
console.log(`STDOUT: ${stdout}`);
}
}
);