Match-id-5d3fe224de6eaeeb3682e388a086c506c36fdcb4
This commit is contained in:
commit
8d9b6310f1
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,3 +1,25 @@
|
||||||
|
## 0.0.20 (2022-09-14)
|
||||||
|
- **core**: #81 fix Memo场景路径错误
|
||||||
|
|
||||||
|
## 0.0.19 (2022-09-13)
|
||||||
|
- **core**: fix 弹窗的input可能无法触发onChange事件
|
||||||
|
|
||||||
|
## 0.0.18 (2022-09-08)
|
||||||
|
- **core**: fix 键盘事件使用历史记录填充时key为undefined
|
||||||
|
|
||||||
|
## 0.0.17 (2022-09-07)
|
||||||
|
- **core**: fix 不在树上的节点发起更新导致错误
|
||||||
|
|
||||||
|
## 0.0.16 (2022-09-07)
|
||||||
|
- **core**: #56,#65 diff null 不能正确卸载组件
|
||||||
|
|
||||||
|
## 0.0.15 (2022-09-06)
|
||||||
|
- **core**: #43 fix portal root 跟 app root重合时重复监听
|
||||||
|
- **core**: #38 修复合成事件受普通事件stopPropagation影响无法触发
|
||||||
|
|
||||||
|
## 0.0.14 (2022-09-04)
|
||||||
|
- **core**: #44 修复unmount根节点事件未清除
|
||||||
|
|
||||||
## 0.0.13 (2022-08-02)
|
## 0.0.13 (2022-08-02)
|
||||||
- **horizonX**: 修复redux兼容器bug
|
- **horizonX**: 修复redux兼容器bug
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
useReducer,
|
useReducer,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
useDebugValue
|
useDebugValue,
|
||||||
} from './src/renderer/hooks/HookExternal';
|
} from './src/renderer/hooks/HookExternal';
|
||||||
import { asyncUpdates } from './src/renderer/TreeBuilder';
|
import { asyncUpdates } from './src/renderer/TreeBuilder';
|
||||||
import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue';
|
import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue';
|
||||||
|
@ -86,7 +86,7 @@ const Horizon = {
|
||||||
useStore,
|
useStore,
|
||||||
clearStore,
|
clearStore,
|
||||||
reduxAdapter,
|
reduxAdapter,
|
||||||
watch
|
watch,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const version = __VERSION__;
|
export const version = __VERSION__;
|
||||||
|
@ -127,7 +127,7 @@ export {
|
||||||
useStore,
|
useStore,
|
||||||
clearStore,
|
clearStore,
|
||||||
reduxAdapter,
|
reduxAdapter,
|
||||||
watch
|
watch,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Horizon;
|
export default Horizon;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"horizon"
|
"horizon"
|
||||||
],
|
],
|
||||||
"version": "0.0.13",
|
"version": "0.0.20",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"bugs": "",
|
"bugs": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import {
|
import { asyncUpdates, getFirstCustomDom, syncUpdates, startUpdate, createTreeRootVNode } from '../renderer/Renderer';
|
||||||
asyncUpdates, getFirstCustomDom,
|
|
||||||
syncUpdates, startUpdate,
|
|
||||||
createTreeRootVNode,
|
|
||||||
} from '../renderer/Renderer';
|
|
||||||
import { createPortal } from '../renderer/components/CreatePortal';
|
import { createPortal } from '../renderer/components/CreatePortal';
|
||||||
import type { Container } from './DOMOperator';
|
import type { Container } from './DOMOperator';
|
||||||
import { isElement } from './utils/Common';
|
import { isElement } from './utils/Common';
|
||||||
import {listenDelegatedEvents} from '../event/EventBinding';
|
|
||||||
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
||||||
import { Callback } from '../renderer/UpdateHandler';
|
import { Callback } from '../renderer/UpdateHandler';
|
||||||
|
|
||||||
|
@ -39,16 +34,13 @@ function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
return treeRoot;
|
return treeRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeRender(
|
function executeRender(children: any, container: Container, callback?: Callback) {
|
||||||
children: any,
|
|
||||||
container: Container,
|
|
||||||
callback?: Callback,
|
|
||||||
) {
|
|
||||||
let treeRoot = container._treeRoot;
|
let treeRoot = container._treeRoot;
|
||||||
|
|
||||||
if (!treeRoot) {
|
if (!treeRoot) {
|
||||||
treeRoot = createRoot(children, container, callback);
|
treeRoot = createRoot(children, container, callback);
|
||||||
} else { // container被render过
|
} else {
|
||||||
|
// container被render过
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
const cb = callback;
|
const cb = callback;
|
||||||
callback = function () {
|
callback = function () {
|
||||||
|
@ -77,11 +69,27 @@ function findDOMNode(domOrEle?: Element): null | Element | Text {
|
||||||
return findDOMByClassInst(domOrEle);
|
return findDOMByClassInst(domOrEle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 情况根节点监听器
|
||||||
|
function removeRootEventLister(container: Container) {
|
||||||
|
const events = (container._treeRoot as any).$EV;
|
||||||
|
if (events) {
|
||||||
|
Object.keys(events).forEach(event => {
|
||||||
|
const listener = events[event];
|
||||||
|
|
||||||
|
if (listener) {
|
||||||
|
container.removeEventListener(event, listener);
|
||||||
|
events[event] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 卸载入口
|
// 卸载入口
|
||||||
function destroy(container: Container) {
|
function destroy(container: Container): boolean {
|
||||||
if (container._treeRoot) {
|
if (container && container._treeRoot) {
|
||||||
syncUpdates(() => {
|
syncUpdates(() => {
|
||||||
executeRender(null, container, () => {
|
executeRender(null, container, () => {
|
||||||
|
removeRootEventLister(container);
|
||||||
container._treeRoot = null;
|
container._treeRoot = null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,7 @@ export type Props = Record<string, any> & {
|
||||||
style?: { display?: string };
|
style?: { display?: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Container = (Element & { _treeRoot?: VNode }) | (Document & { _treeRoot?: VNode });
|
export type Container = (Element & { _treeRoot?: VNode | null }) | (Document & { _treeRoot?: VNode | null });
|
||||||
|
|
||||||
let selectionInfo: null | SelectionData = null;
|
let selectionInfo: null | SelectionData = null;
|
||||||
|
|
||||||
|
@ -225,7 +225,3 @@ export function unHideDom(tag: string, dom: Element | Text, props: Props) {
|
||||||
dom.textContent = props;
|
dom.textContent = props;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prePortal(portal: Element): void {
|
|
||||||
listenDelegatedEvents(portal);
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { updateCommonProp } from './UpdateCommonProp';
|
||||||
import { setStyles } from './StyleHandler';
|
import { setStyles } from './StyleHandler';
|
||||||
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
||||||
import { isEventProp } from '../validators/ValidateProps';
|
import { isEventProp } from '../validators/ValidateProps';
|
||||||
import { getCurrentRoot } from '../../renderer/TreeBuilder';
|
import { getCurrentRoot } from '../../renderer/RootStack';
|
||||||
|
|
||||||
// 初始化DOM属性和更新 DOM 属性
|
// 初始化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 {
|
||||||
|
|
|
@ -35,7 +35,7 @@ function triggerDelegatedEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听委托事件
|
// 监听委托事件
|
||||||
function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, isCapture: boolean): void {
|
function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, isCapture: boolean) {
|
||||||
let dom: Element | Document = delegatedElement;
|
let dom: Element | Document = delegatedElement;
|
||||||
// document层次可能触发selectionchange事件,为了捕获这类事件,selectionchange事件绑定在document节点上
|
// document层次可能触发selectionchange事件,为了捕获这类事件,selectionchange事件绑定在document节点上
|
||||||
if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) {
|
if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) {
|
||||||
|
@ -44,22 +44,8 @@ function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, i
|
||||||
|
|
||||||
const listener = triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, dom);
|
const listener = triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, dom);
|
||||||
dom.addEventListener(nativeEvtName, listener, isCapture);
|
dom.addEventListener(nativeEvtName, listener, isCapture);
|
||||||
}
|
|
||||||
|
|
||||||
// 监听所有委托事件
|
return listener;
|
||||||
export function listenDelegatedEvents(dom: Element) {
|
|
||||||
if (dom[listeningMarker]) {
|
|
||||||
// 不需要重复注册事件
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dom[listeningMarker] = true;
|
|
||||||
|
|
||||||
allDelegatedNativeEvents.forEach((nativeEvtName: string) => {
|
|
||||||
// 委托冒泡事件
|
|
||||||
listenToNativeEvent(nativeEvtName, dom, false);
|
|
||||||
// 委托捕获事件
|
|
||||||
listenToNativeEvent(nativeEvtName, dom, true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
||||||
|
@ -71,9 +57,17 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
||||||
|
|
||||||
nativeEvents.forEach(nativeEvent => {
|
nativeEvents.forEach(nativeEvent => {
|
||||||
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
|
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
|
||||||
if (!currentRoot.delegatedNativeEvents.has(nativeFullName)) {
|
|
||||||
listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
|
// 事件存储在DOM节点属性,避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听
|
||||||
currentRoot.delegatedNativeEvents.add(nativeFullName);
|
let events = currentRoot.realNode.$EV;
|
||||||
|
|
||||||
|
if (!events) {
|
||||||
|
events = (currentRoot.realNode as any).$EV = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!events[nativeFullName]) {
|
||||||
|
const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
|
||||||
|
events[nativeFullName] = listener;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ export class WrappedEvent {
|
||||||
stopPropagation: () => void;
|
stopPropagation: () => void;
|
||||||
preventDefault: () => void;
|
preventDefault: () => void;
|
||||||
|
|
||||||
|
propagationStopped = false
|
||||||
|
isPropagationStopped = (): boolean => this.propagationStopped;
|
||||||
|
|
||||||
// 适配Keyboard键盘事件该函数不能由合成事件调用
|
// 适配Keyboard键盘事件该函数不能由合成事件调用
|
||||||
getModifierState?: (keyArgs: string) => boolean;
|
getModifierState?: (keyArgs: string) => boolean;
|
||||||
// 适配老版本事件api
|
// 适配老版本事件api
|
||||||
|
@ -39,7 +42,11 @@ export class WrappedEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// stopPropagation和preventDefault 必须通过Event实例调用
|
// stopPropagation和preventDefault 必须通过Event实例调用
|
||||||
this.stopPropagation = () => nativeEvent.stopPropagation();
|
this.stopPropagation = () => {
|
||||||
|
nativeEvent.stopPropagation();
|
||||||
|
this.propagationStopped = true;
|
||||||
|
};
|
||||||
|
|
||||||
this.preventDefault = () => nativeEvent.preventDefault();
|
this.preventDefault = () => nativeEvent.preventDefault();
|
||||||
|
|
||||||
// custom事件自定义属性
|
// custom事件自定义属性
|
||||||
|
@ -51,17 +58,13 @@ export class WrappedEvent {
|
||||||
this.type = nativeEvtName;
|
this.type = nativeEvtName;
|
||||||
|
|
||||||
// 兼容IE的event key
|
// 兼容IE的event key
|
||||||
const orgKey = (nativeEvent as any).key;
|
const orgKey = (nativeEvent as any).key ?? '';
|
||||||
this.key = uniqueKeyMap.get(orgKey) || orgKey;
|
this.key = uniqueKeyMap.get(orgKey) || orgKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefaultPrevented(): boolean {
|
isDefaultPrevented(): boolean {
|
||||||
return this.nativeEvent.defaultPrevented;
|
return this.nativeEvent.defaultPrevented;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPropagationStopped(): boolean {
|
|
||||||
return this.nativeEvent.cancelBubble;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建普通自定义事件对象实例,和原生事件对应
|
// 创建普通自定义事件对象实例,和原生事件对应
|
||||||
|
|
|
@ -39,7 +39,9 @@ export const helper = {
|
||||||
return { name: HookName.RefHook, hIndex, value: (state as Ref<any>).current };
|
return { name: HookName.RefHook, hIndex, value: (state as Ref<any>).current };
|
||||||
} else if (isEffectHook(state)) {
|
} else if (isEffectHook(state)) {
|
||||||
const name =
|
const name =
|
||||||
state.effectConstant == EffectConstant.LayoutEffect ? HookName.LayoutEffectHook : HookName.EffectHook;
|
state.effectConstant == EffectConstant.LayoutEffect || (EffectConstant.LayoutEffect | EffectConstant.DepsChange)
|
||||||
|
? HookName.LayoutEffectHook
|
||||||
|
: HookName.EffectHook;
|
||||||
return { name, hIndex, value: (state as Effect).effect };
|
return { name, hIndex, value: (state as Effect).effect };
|
||||||
} else if (isCallbackHook(state)) {
|
} else if (isCallbackHook(state)) {
|
||||||
return { name: HookName.CallbackHook, hIndex, value: (state as CallBack<any>).func };
|
return { name: HookName.CallbackHook, hIndex, value: (state as CallBack<any>).func };
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { launchUpdateFromVNode } from '../../renderer/TreeBuilder';
|
||||||
import { getProcessingVNode } from '../../renderer/GlobalVar';
|
import { getProcessingVNode } from '../../renderer/GlobalVar';
|
||||||
import { VNode } from '../../renderer/vnode/VNode';
|
import { VNode } from '../../renderer/vnode/VNode';
|
||||||
export interface IObserver {
|
export interface IObserver {
|
||||||
|
|
||||||
useProp: (key: string) => void;
|
useProp: (key: string) => void;
|
||||||
|
|
||||||
addListener: (listener: () => void) => void;
|
addListener: (listener: () => void) => void;
|
||||||
|
@ -26,13 +25,14 @@ export interface IObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Observer implements IObserver {
|
export class Observer implements IObserver {
|
||||||
|
|
||||||
vNodeKeys = new WeakMap();
|
vNodeKeys = new WeakMap();
|
||||||
|
|
||||||
keyVNodes = new Map();
|
keyVNodes = new Map();
|
||||||
|
|
||||||
listeners: (() => void)[] = [];
|
listeners: (() => void)[] = [];
|
||||||
|
|
||||||
|
watchers = {} as { [key: string]: ((key: string, oldValue: any, newValue: any) => void)[] };
|
||||||
|
|
||||||
watchers={} as {[key:string]:((key:string, oldValue:any, newValue:any)=>void)[]}
|
watchers={} as {[key:string]:((key:string, oldValue:any, newValue:any)=>void)[]}
|
||||||
|
|
||||||
useProp(key: string | symbol): void {
|
useProp(key: string | symbol): void {
|
||||||
|
|
|
@ -22,8 +22,8 @@ function get(rawObj: any[], key: string, receiver: any) {
|
||||||
observer.watchers[prop].push(handler);
|
observer.watchers[prop].push(handler);
|
||||||
return () => {
|
return () => {
|
||||||
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
|
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValidIntegerKey(key) || key === 'length') {
|
if (isValidIntegerKey(key) || key === 'length') {
|
||||||
|
|
|
@ -44,8 +44,8 @@ function get(rawObj: { size: number }, key: any, receiver: any): any {
|
||||||
observer.watchers[prop].push(handler);
|
observer.watchers[prop].push(handler);
|
||||||
return () => {
|
return () => {
|
||||||
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
|
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return Reflect.get(rawObj, key, receiver);
|
return Reflect.get(rawObj, key, receiver);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export function watch(stateVariable:any,listener:(stateVariable:any)=>void){
|
export function watch(stateVariable: any, listener: (state: any) => void) {
|
||||||
listener = listener.bind(null, stateVariable);
|
listener = listener.bind(null, stateVariable);
|
||||||
stateVariable.addListener(listener);
|
stateVariable.addListener(listener);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stateVariable.removeListener(listener);
|
stateVariable.removeListener(listener);
|
||||||
}
|
};
|
||||||
}
|
}
|
|
@ -7,7 +7,6 @@ import type { VNode, ContextType } from './Types';
|
||||||
import type { Container } from '../dom/DOMOperator';
|
import type { Container } from '../dom/DOMOperator';
|
||||||
|
|
||||||
import { getNSCtx } from '../dom/DOMOperator';
|
import { getNSCtx } from '../dom/DOMOperator';
|
||||||
import { ContextProvider } from './vnode/VNodeTags';
|
|
||||||
|
|
||||||
// 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”,
|
// 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”,
|
||||||
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM
|
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM
|
||||||
|
@ -44,32 +43,3 @@ export function resetContext(providerVNode: VNode) {
|
||||||
|
|
||||||
context.value = providerVNode.context;
|
context.value = providerVNode.context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在局部更新时,从上到下恢复父节点的context
|
|
||||||
export function recoverParentContext(vNode: VNode) {
|
|
||||||
const contextProviders: VNode[] = [];
|
|
||||||
let parent = vNode.parent;
|
|
||||||
while (parent !== null) {
|
|
||||||
if (parent.tag === ContextProvider) {
|
|
||||||
contextProviders.unshift(parent);
|
|
||||||
}
|
|
||||||
parent = parent.parent;
|
|
||||||
}
|
|
||||||
contextProviders.forEach(node => {
|
|
||||||
setContext(node, node.props.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在局部更新时,从下到上重置父节点的context
|
|
||||||
export function resetParentContext(vNode: VNode) {
|
|
||||||
let parent = vNode.parent;
|
|
||||||
|
|
||||||
while (parent !== null) {
|
|
||||||
if (parent.tag === ContextProvider) {
|
|
||||||
resetContext(parent);
|
|
||||||
}
|
|
||||||
parent = parent.parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { VNode } from './vnode/VNode';
|
||||||
|
const currentRootStack: VNode[] = [];
|
||||||
|
export function getCurrentRoot() {
|
||||||
|
return currentRootStack[currentRootStack.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushCurrentRoot(root: VNode) {
|
||||||
|
return currentRootStack.push(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function popCurrentRoot() {
|
||||||
|
return currentRootStack.pop();
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import type { VNode } from './Types';
|
||||||
|
|
||||||
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
|
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
|
||||||
import { updateVNode } from './vnode/VNodeCreator';
|
import { updateVNode } from './vnode/VNodeCreator';
|
||||||
import { TreeRoot, DomComponent, DomPortal } from './vnode/VNodeTags';
|
import { ContextProvider, DomComponent, DomPortal, TreeRoot } from './vnode/VNodeTags';
|
||||||
import { FlagUtils, InitFlag, Interrupted } from './vnode/VNodeFlags';
|
import { FlagUtils, InitFlag, Interrupted } from './vnode/VNodeFlags';
|
||||||
import { captureVNode } from './render/BaseComponent';
|
import { captureVNode } from './render/BaseComponent';
|
||||||
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
|
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
|
||||||
|
@ -12,41 +12,44 @@ import componentRenders from './render';
|
||||||
import {
|
import {
|
||||||
BuildCompleted,
|
BuildCompleted,
|
||||||
BuildFatalErrored,
|
BuildFatalErrored,
|
||||||
BuildInComplete, getBuildResult,
|
BuildInComplete,
|
||||||
|
getBuildResult,
|
||||||
getStartVNode,
|
getStartVNode,
|
||||||
setBuildResult,
|
setBuildResult,
|
||||||
setProcessingClassVNode,
|
setProcessingClassVNode,
|
||||||
setStartVNode
|
setStartVNode,
|
||||||
} from './GlobalVar';
|
} from './GlobalVar';
|
||||||
import {
|
import {
|
||||||
ByAsync,
|
ByAsync,
|
||||||
BySync,
|
BySync,
|
||||||
InRender,
|
|
||||||
InEvent,
|
|
||||||
changeMode,
|
changeMode,
|
||||||
checkMode,
|
checkMode,
|
||||||
copyExecuteMode,
|
copyExecuteMode,
|
||||||
|
InEvent,
|
||||||
|
InRender,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
setExecuteMode
|
setExecuteMode,
|
||||||
} from './ExecuteMode';
|
} from './ExecuteMode';
|
||||||
import { recoverParentContext, resetParentContext, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver';
|
import {
|
||||||
|
resetContext,
|
||||||
|
resetNamespaceCtx,
|
||||||
|
setContext,
|
||||||
|
setNamespaceCtx,
|
||||||
|
} from './ContextSaver';
|
||||||
import {
|
import {
|
||||||
updateChildShouldUpdate,
|
updateChildShouldUpdate,
|
||||||
updateParentsChildShouldUpdate,
|
updateParentsChildShouldUpdate,
|
||||||
updateShouldUpdateOfTree
|
updateShouldUpdateOfTree,
|
||||||
} from './vnode/VNodeShouldUpdate';
|
} from './vnode/VNodeShouldUpdate';
|
||||||
import { getPathArr } from './utils/vNodePath';
|
import { getPathArr } from './utils/vNodePath';
|
||||||
import { injectUpdater } from '../external/devtools';
|
import { injectUpdater } from '../external/devtools';
|
||||||
|
import { popCurrentRoot, pushCurrentRoot } from './RootStack';
|
||||||
|
|
||||||
// 不可恢复错误
|
// 不可恢复错误
|
||||||
let unrecoverableErrorDuringBuild: any = null;
|
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;
|
||||||
|
@ -178,8 +181,13 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toUpdateNodes.length === 1) {
|
if (toUpdateNodes.length === 1) {
|
||||||
|
const toUpdateNode = toUpdateNodes[0];
|
||||||
|
if (toUpdateNode.isCleared) {
|
||||||
|
return treeRoot;
|
||||||
|
} else {
|
||||||
return toUpdateNodes[0];
|
return toUpdateNodes[0];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 要计算的节点过多,直接返回根节点
|
// 要计算的节点过多,直接返回根节点
|
||||||
if (toUpdateNodes.length > 100) {
|
if (toUpdateNodes.length > 100) {
|
||||||
|
@ -241,7 +249,7 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复父节点的context
|
// 恢复父节点的context
|
||||||
recoverParentContext(startVNode);
|
recoverTreeContext(startVNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置环境变量,为重新进行深度遍历做准备
|
// 重置环境变量,为重新进行深度遍历做准备
|
||||||
|
@ -269,7 +277,7 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
}
|
}
|
||||||
if (startVNode.tag !== TreeRoot) { // 不是根节点
|
if (startVNode.tag !== TreeRoot) { // 不是根节点
|
||||||
// 恢复父节点的context
|
// 恢复父节点的context
|
||||||
resetParentContext(startVNode);
|
resetTreeContext(startVNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setProcessingClassVNode(null);
|
setProcessingClassVNode(null);
|
||||||
|
@ -277,10 +285,43 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
setExecuteMode(preMode);
|
setExecuteMode(preMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在局部更新时,从上到下恢复父节点的context和PortalStack
|
||||||
|
function recoverTreeContext(vNode: VNode) {
|
||||||
|
const contextProviders: VNode[] = [];
|
||||||
|
let parent = vNode.parent;
|
||||||
|
while (parent !== null) {
|
||||||
|
if (parent.tag === ContextProvider) {
|
||||||
|
contextProviders.unshift(parent);
|
||||||
|
}
|
||||||
|
if(parent.tag === DomPortal){
|
||||||
|
pushCurrentRoot(parent);
|
||||||
|
}
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
contextProviders.forEach(node => {
|
||||||
|
setContext(node, node.props.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在局部更新时,从下到上重置父节点的context
|
||||||
|
function resetTreeContext(vNode: VNode) {
|
||||||
|
let parent = vNode.parent;
|
||||||
|
|
||||||
|
while (parent !== null) {
|
||||||
|
if (parent.tag === ContextProvider) {
|
||||||
|
resetContext(parent);
|
||||||
|
}
|
||||||
|
if(parent.tag === DomPortal){
|
||||||
|
popCurrentRoot();
|
||||||
|
}
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 总体任务入口
|
// 总体任务入口
|
||||||
function renderFromRoot(treeRoot) {
|
function renderFromRoot(treeRoot) {
|
||||||
runAsyncEffects();
|
runAsyncEffects();
|
||||||
currentRoot = treeRoot;
|
pushCurrentRoot(treeRoot);
|
||||||
// 1. 构建vNode树
|
// 1. 构建vNode树
|
||||||
buildVNodeTree(treeRoot);
|
buildVNodeTree(treeRoot);
|
||||||
|
|
||||||
|
@ -291,8 +332,7 @@ function renderFromRoot(treeRoot) {
|
||||||
|
|
||||||
// 2. 提交变更
|
// 2. 提交变更
|
||||||
submitToRender(treeRoot);
|
submitToRender(treeRoot);
|
||||||
currentRoot = null;
|
popCurrentRoot();
|
||||||
|
|
||||||
if (window.__HORIZON_DEV_HOOK__) {
|
if (window.__HORIZON_DEV_HOOK__) {
|
||||||
const hook = window.__HORIZON_DEV_HOOK__;
|
const hook = window.__HORIZON_DEV_HOOK__;
|
||||||
// injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量
|
// injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量
|
||||||
|
|
|
@ -55,7 +55,7 @@ function deleteVNodes(parentVNode: VNode, startDelVNode: VNode | null, endVNode?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCanReuseNode(oldNode: VNode | null, newChild: any): boolean {
|
function checkCanReuseNode(oldNode: VNode | null, newChild: any, newNodeIdx: number): boolean {
|
||||||
if (newChild === null) {
|
if (newChild === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,13 @@ function checkCanReuseNode(oldNode: VNode | null, newChild: any): boolean {
|
||||||
return oldKey === null;
|
return oldKey === null;
|
||||||
}
|
}
|
||||||
if (newChild.vtype === TYPE_COMMON_ELEMENT || newChild.vtype === TYPE_PORTAL) {
|
if (newChild.vtype === TYPE_COMMON_ELEMENT || newChild.vtype === TYPE_PORTAL) {
|
||||||
|
// key存在时用key判断复用
|
||||||
|
if (oldKey !== null || newChild.key !== null) {
|
||||||
return oldKey === newChild.key;
|
return oldKey === newChild.key;
|
||||||
|
} else {
|
||||||
|
// 新旧节点的index应该相同才能复用,null会影响位置
|
||||||
|
return oldNode?.eIndex === newNodeIdx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +260,7 @@ function diffArrayNodesHandler(
|
||||||
nextOldNode = oldNode.next;
|
nextOldNode = oldNode.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
canBeReuse = checkCanReuseNode(oldNode, newChildren[leftIdx]);
|
canBeReuse = checkCanReuseNode(oldNode, newChildren[leftIdx], leftIdx);
|
||||||
// 不能复用,break
|
// 不能复用,break
|
||||||
if (!canBeReuse) {
|
if (!canBeReuse) {
|
||||||
oldNode = oldNode ?? nextOldNode;
|
oldNode = oldNode ?? nextOldNode;
|
||||||
|
@ -295,7 +301,7 @@ function diffArrayNodesHandler(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
canBeReuse = checkCanReuseNode(rightOldNode, newChildren[rightIdx - 1]);
|
canBeReuse = checkCanReuseNode(rightOldNode, newChildren[rightIdx - 1], rightIdx - 1);
|
||||||
// 不能复用,break
|
// 不能复用,break
|
||||||
if (!canBeReuse) {
|
if (!canBeReuse) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
import type { VNode } from '../Types';
|
import type { VNode } from '../Types';
|
||||||
|
|
||||||
import {
|
import { ContextProvider, DomComponent, DomPortal, TreeRoot, SuspenseComponent } from '../vnode/VNodeTags';
|
||||||
ContextProvider,
|
|
||||||
DomComponent,
|
|
||||||
DomPortal,
|
|
||||||
TreeRoot,
|
|
||||||
SuspenseComponent,
|
|
||||||
} from '../vnode/VNodeTags';
|
|
||||||
import { setContext, setNamespaceCtx } from '../ContextSaver';
|
import { setContext, setNamespaceCtx } from '../ContextSaver';
|
||||||
import { FlagUtils } from '../vnode/VNodeFlags';
|
import { FlagUtils } from '../vnode/VNodeFlags';
|
||||||
import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
|
import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
|
||||||
import componentRenders from './index';
|
import componentRenders from './index';
|
||||||
import { setProcessingVNode } from '../GlobalVar';
|
import { setProcessingVNode } from '../GlobalVar';
|
||||||
import { clearVNodeObservers } from '../../horizonx/store/StoreHandler';
|
import { clearVNodeObservers } from '../../horizonx/store/StoreHandler';
|
||||||
|
import { pushCurrentRoot } from '../RootStack';
|
||||||
|
|
||||||
// 复用vNode时,也需对stack进行处理
|
// 复用vNode时,也需对树的上下文值处理,如context,portal, namespaceContext
|
||||||
function handlerContext(processing: VNode) {
|
function setTreeContextValue(processing: VNode) {
|
||||||
switch (processing.tag) {
|
switch (processing.tag) {
|
||||||
case TreeRoot:
|
case TreeRoot:
|
||||||
setNamespaceCtx(processing, processing.realNode);
|
setNamespaceCtx(processing, processing.realNode);
|
||||||
|
@ -25,6 +20,7 @@ function handlerContext(processing: VNode) {
|
||||||
break;
|
break;
|
||||||
case DomPortal:
|
case DomPortal:
|
||||||
setNamespaceCtx(processing, processing.realNode);
|
setNamespaceCtx(processing, processing.realNode);
|
||||||
|
pushCurrentRoot(processing);
|
||||||
break;
|
break;
|
||||||
case ContextProvider: {
|
case ContextProvider: {
|
||||||
const newValue = processing.props.value;
|
const newValue = processing.props.value;
|
||||||
|
@ -40,13 +36,9 @@ export function captureVNode(processing: VNode): VNode | null {
|
||||||
|
|
||||||
if (processing.tag !== SuspenseComponent) {
|
if (processing.tag !== SuspenseComponent) {
|
||||||
// 该vNode没有变化,不用进入capture,直接复用。
|
// 该vNode没有变化,不用进入capture,直接复用。
|
||||||
if (
|
if (!processing.isCreated && processing.oldProps === processing.props && !processing.shouldUpdate) {
|
||||||
!processing.isCreated &&
|
|
||||||
processing.oldProps === processing.props &&
|
|
||||||
!processing.shouldUpdate
|
|
||||||
) {
|
|
||||||
// 复用还需对stack进行处理
|
// 复用还需对stack进行处理
|
||||||
handlerContext(processing);
|
setTreeContextValue(processing);
|
||||||
|
|
||||||
return onlyUpdateChildVNodes(processing);
|
return onlyUpdateChildVNodes(processing);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import type { VNode } from '../Types';
|
import type { VNode } from '../Types';
|
||||||
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
||||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||||
import { prePortal } from '../../dom/DOMOperator';
|
import { popCurrentRoot, pushCurrentRoot } from '../RootStack';
|
||||||
|
|
||||||
export function bubbleRender(processing: VNode) {
|
export function bubbleRender(processing: VNode) {
|
||||||
resetNamespaceCtx(processing);
|
resetNamespaceCtx(processing);
|
||||||
|
popCurrentRoot();
|
||||||
if (processing.isCreated) {
|
|
||||||
prePortal(processing.realNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function capturePortalComponent(processing: VNode) {
|
function capturePortalComponent(processing: VNode) {
|
||||||
setNamespaceCtx(processing, processing.realNode);
|
setNamespaceCtx(processing, processing.realNode);
|
||||||
|
pushCurrentRoot(processing);
|
||||||
|
|
||||||
const newElements = processing.props;
|
const newElements = processing.props;
|
||||||
if (processing.isCreated) {
|
if (processing.isCreated) {
|
||||||
|
|
|
@ -39,6 +39,8 @@ export class VNode {
|
||||||
ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上
|
ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上
|
||||||
oldProps: any = null;
|
oldProps: any = null;
|
||||||
|
|
||||||
|
// 是否已经被从树上移除
|
||||||
|
isCleared = false;
|
||||||
changeList: any; // DOM的变更列表
|
changeList: any; // DOM的变更列表
|
||||||
effectList: any[] | null; // useEffect 的更新数组
|
effectList: any[] | null; // useEffect 的更新数组
|
||||||
updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组
|
updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组
|
||||||
|
@ -78,7 +80,6 @@ export class VNode {
|
||||||
// 根节点数据
|
// 根节点数据
|
||||||
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
|
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
|
||||||
delegatedEvents: Set<string>;
|
delegatedEvents: Set<string>;
|
||||||
delegatedNativeEvents: Set<string>;
|
|
||||||
|
|
||||||
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
|
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
|
||||||
|
|
||||||
|
@ -100,7 +101,6 @@ export class VNode {
|
||||||
this.task = null;
|
this.task = null;
|
||||||
this.toUpdateNodes = new Set<VNode>();
|
this.toUpdateNodes = new Set<VNode>();
|
||||||
this.delegatedEvents = new Set<string>();
|
this.delegatedEvents = new Set<string>();
|
||||||
this.delegatedNativeEvents = new Set<string>();
|
|
||||||
this.updates = null;
|
this.updates = null;
|
||||||
this.stateCallbacks = null;
|
this.stateCallbacks = null;
|
||||||
this.state = null;
|
this.state = null;
|
||||||
|
@ -137,6 +137,7 @@ export class VNode {
|
||||||
case DomPortal:
|
case DomPortal:
|
||||||
this.realNode = null;
|
this.realNode = null;
|
||||||
this.context = null;
|
this.context = null;
|
||||||
|
this.delegatedEvents = new Set<string>();
|
||||||
this.src = null;
|
this.src = null;
|
||||||
break;
|
break;
|
||||||
case DomComponent:
|
case DomComponent:
|
||||||
|
|
|
@ -230,7 +230,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
putChildrenIntoQueue(processing.child);
|
putChildrenIntoQueue(processing);
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const vNode = queue.shift()!;
|
const vNode = queue.shift()!;
|
||||||
|
|
|
@ -73,6 +73,7 @@ export function travelVNodeTree(
|
||||||
|
|
||||||
// 置空vNode
|
// 置空vNode
|
||||||
export function clearVNode(vNode: VNode) {
|
export function clearVNode(vNode: VNode) {
|
||||||
|
vNode.isCleared = true;
|
||||||
vNode.child = null;
|
vNode.child = null;
|
||||||
vNode.next = null;
|
vNode.next = null;
|
||||||
vNode.depContexts = null;
|
vNode.depContexts = null;
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
|
|
||||||
|
describe('Diff Algorithm', () => {
|
||||||
|
it('null should diff correctly', () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
|
||||||
|
class C extends Horizon.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let update;
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [current, setCurrent] = Horizon.useState(1);
|
||||||
|
update = setCurrent;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{current === 1 ? <C /> : null}
|
||||||
|
{current === 2 ? <C /> : null}
|
||||||
|
{current === 3 ? <C /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<App text="app" />, container);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
update(2);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
update(3);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
|
update(1);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(4);
|
||||||
|
});
|
||||||
|
});
|
|
@ -81,7 +81,7 @@ describe('useEffect Hook Test', () => {
|
||||||
expect(LogUtils.getAndClear()).toEqual([]);
|
expect(LogUtils.getAndClear()).toEqual([]);
|
||||||
// 在执行新的render前,会执行完上一次render的useEffect,所以LogUtils会加入'NewApp effect'。
|
// 在执行新的render前,会执行完上一次render的useEffect,所以LogUtils会加入'NewApp effect'。
|
||||||
Horizon.render([na], container);
|
Horizon.render([na], container);
|
||||||
expect(LogUtils.getAndClear()).toEqual(['NewApp effect']);
|
expect(LogUtils.getAndClear()).toEqual(['NewApp effect', 'NewApp']);
|
||||||
expect(container.textContent).toBe('NewApp');
|
expect(container.textContent).toBe('NewApp');
|
||||||
expect(LogUtils.getAndClear()).toEqual([]);
|
expect(LogUtils.getAndClear()).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
|
|
||||||
|
describe('Memo Test', () => {
|
||||||
|
it('Memo should not make the path wrong', function () {
|
||||||
|
let updateApp;
|
||||||
|
|
||||||
|
function Child() {
|
||||||
|
const [_, update] = Horizon.useState({});
|
||||||
|
updateApp = () => update({});
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
const MemoChild = Horizon.memo(Child);
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MemoChild />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const MemoApp = Horizon.memo(App);
|
||||||
|
Horizon.render(
|
||||||
|
<div>
|
||||||
|
<MemoApp key="1" />
|
||||||
|
</div>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
Horizon.render(
|
||||||
|
<div>
|
||||||
|
<span></span>
|
||||||
|
<MemoApp key="1" />
|
||||||
|
</div>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
expect(() => updateApp()).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,6 @@
|
||||||
import * as Horizon from '@cloudsop/horizon/index.ts';
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
import { getLogUtils } from '../jest/testUtils';
|
import { getLogUtils } from '../jest/testUtils';
|
||||||
|
import dispatchChangeEvent from '../utils/dispatchChangeEvent';
|
||||||
|
|
||||||
describe('PortalComponent Test', () => {
|
describe('PortalComponent Test', () => {
|
||||||
const LogUtils = getLogUtils();
|
const LogUtils = getLogUtils();
|
||||||
|
@ -14,12 +15,10 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, this.element);
|
||||||
this.props.child,
|
|
||||||
this.element,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
||||||
|
@ -43,17 +42,12 @@ describe('PortalComponent Test', () => {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
Horizon.createPortal(
|
Horizon.createPortal(this.props.child, this.element),
|
||||||
this.props.child,
|
Horizon.createPortal(this.props.child, this.newElement),
|
||||||
this.element,
|
|
||||||
),
|
|
||||||
Horizon.createPortal(
|
|
||||||
this.props.child,
|
|
||||||
this.newElement,
|
|
||||||
)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
||||||
|
@ -82,21 +76,16 @@ describe('PortalComponent Test', () => {
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
<div>PortalApp1st</div>,
|
<div>PortalApp1st</div>,
|
||||||
Horizon.createPortal([
|
|
||||||
<div>PortalApp4</div>,
|
|
||||||
Horizon.createPortal(
|
Horizon.createPortal(
|
||||||
this.props.child,
|
[<div>PortalApp4</div>, Horizon.createPortal(this.props.child, this.element3rd)],
|
||||||
this.element3rd,
|
this.element
|
||||||
),
|
),
|
||||||
], this.element),
|
|
||||||
<div>PortalApp2nd</div>,
|
<div>PortalApp2nd</div>,
|
||||||
Horizon.createPortal(
|
Horizon.createPortal(this.props.child, this.newElement),
|
||||||
this.props.child,
|
|
||||||
this.newElement,
|
|
||||||
)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('PortalApp1stPortalApp2nd');
|
expect(container.textContent).toBe('PortalApp1stPortalApp2nd');
|
||||||
// <div>PortalApp4</div>会挂载在this.element上
|
// <div>PortalApp4</div>会挂载在this.element上
|
||||||
|
@ -120,25 +109,23 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, this.element);
|
||||||
this.props.child,
|
|
||||||
this.element,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Horizon.render(<PortalApp key='portal' child={<div>PortalApp</div>} />, container);
|
|
||||||
|
Horizon.render(<PortalApp key="portal" child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('PortalApp');
|
expect(portalRoot.textContent).toBe('PortalApp');
|
||||||
|
|
||||||
Horizon.render(<PortalApp key='portal' child={<div>AppPortal</div>} />, container);
|
Horizon.render(<PortalApp key="portal" child={<div>AppPortal</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('AppPortal');
|
expect(portalRoot.textContent).toBe('AppPortal');
|
||||||
|
|
||||||
Horizon.render(<PortalApp key='portal' child={['por', 'tal']} />, container);
|
Horizon.render(<PortalApp key="portal" child={['por', 'tal']} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('portal');
|
expect(portalRoot.textContent).toBe('portal');
|
||||||
|
|
||||||
Horizon.render(<PortalApp key='portal' child={null} />, container);
|
Horizon.render(<PortalApp key="portal" child={null} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('');
|
expect(portalRoot.textContent).toBe('');
|
||||||
|
|
||||||
|
@ -158,10 +145,7 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, this.element);
|
||||||
this.props.child,
|
|
||||||
this.element,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +157,6 @@ describe('PortalComponent Test', () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
LogUtils.log('bubble click event');
|
LogUtils.log('bubble click event');
|
||||||
|
@ -185,9 +168,7 @@ describe('PortalComponent Test', () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClickCapture={handleCaptureClick()} onClick={handleClick()}>
|
<div onClickCapture={handleCaptureClick()} onClick={handleClick()}>
|
||||||
<PortalApp child={<Child />}>
|
<PortalApp child={<Child />}></PortalApp>
|
||||||
|
|
||||||
</PortalApp>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -199,7 +180,95 @@ describe('PortalComponent Test', () => {
|
||||||
expect(LogUtils.getAndClear()).toEqual([
|
expect(LogUtils.getAndClear()).toEqual([
|
||||||
// 从外到内先捕获再冒泡
|
// 从外到内先捕获再冒泡
|
||||||
'capture click event',
|
'capture click event',
|
||||||
'bubble click event'
|
'bubble click event',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Create portal at app root should not add event listener multiple times', () => {
|
||||||
|
const btnRef = Horizon.createRef();
|
||||||
|
|
||||||
|
class PortalApp extends Horizon.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return Horizon.createPortal(this.props.child, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick = jest.fn();
|
||||||
|
|
||||||
|
class App extends Horizon.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={onClick} ref={btnRef}></button>
|
||||||
|
<PortalApp />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<App />, container);
|
||||||
|
btnRef.current.click();
|
||||||
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#76 Portal onChange should activate', () => {
|
||||||
|
class Dialog extends Horizon.Component {
|
||||||
|
node;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.node = window.document.createElement('div');
|
||||||
|
window.document.body.appendChild(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return Horizon.createPortal(this.props.children, this.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let showPortalInput;
|
||||||
|
const fn = jest.fn();
|
||||||
|
const inputRef = Horizon.createRef();
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const Input = () => {
|
||||||
|
const [show, setShow] = Horizon.useState(false);
|
||||||
|
showPortalInput = setShow;
|
||||||
|
|
||||||
|
Horizon.useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setShow(true);
|
||||||
|
}, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <input onChange={fn} ref={inputRef} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Dialog>
|
||||||
|
<Input />
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<App />, container);
|
||||||
|
showPortalInput(true);
|
||||||
|
jest.advanceTimersToNextTimer();
|
||||||
|
dispatchChangeEvent(inputRef.current, 'test');
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -1,24 +1,24 @@
|
||||||
import { createStore } from "@cloudsop/horizon/src/horizonx/store/StoreHandler";
|
import { createStore } from '@cloudsop/horizon/src/horizonx/store/StoreHandler';
|
||||||
import { watch } from "@cloudsop/horizon/src/horizonx/proxy/watch";
|
import { watch } from '@cloudsop/horizon/src/horizonx/proxy/watch';
|
||||||
|
|
||||||
describe("watch",()=>{
|
describe('watch', () => {
|
||||||
it('shouhld watch promitive state variable', async () => {
|
it('shouhld watch promitive state variable', async () => {
|
||||||
const useStore = createStore({
|
const useStore = createStore({
|
||||||
state: {
|
state: {
|
||||||
variable:'x'
|
variable: 'x',
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
change:(state)=>state.variable = "a"
|
change: state => (state.variable = 'a'),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|
||||||
watch(store.$s,(state)=>{
|
watch(store.$s, state => {
|
||||||
counter++;
|
counter++;
|
||||||
expect(state.variable).toBe('a');
|
expect(state.variable).toBe('a');
|
||||||
})
|
});
|
||||||
|
|
||||||
store.change();
|
store.change();
|
||||||
|
|
||||||
|
@ -27,11 +27,11 @@ describe("watch",()=>{
|
||||||
it('shouhld watch object variable', async () => {
|
it('shouhld watch object variable', async () => {
|
||||||
const useStore = createStore({
|
const useStore = createStore({
|
||||||
state: {
|
state: {
|
||||||
variable:'x'
|
variable: 'x',
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
change:(state)=>state.variable = "a"
|
change: state => (state.variable = 'a'),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
@ -39,7 +39,7 @@ describe("watch",()=>{
|
||||||
|
|
||||||
store.$s.watch('variable', () => {
|
store.$s.watch('variable', () => {
|
||||||
counter++;
|
counter++;
|
||||||
})
|
});
|
||||||
|
|
||||||
store.change();
|
store.change();
|
||||||
|
|
||||||
|
@ -49,11 +49,11 @@ describe("watch",()=>{
|
||||||
it('shouhld watch array item', async () => {
|
it('shouhld watch array item', async () => {
|
||||||
const useStore = createStore({
|
const useStore = createStore({
|
||||||
state: {
|
state: {
|
||||||
arr:['x']
|
arr: ['x'],
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
change:(state)=>state.arr[0]='a'
|
change: state => (state.arr[0] = 'a'),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
@ -61,7 +61,7 @@ describe("watch",()=>{
|
||||||
|
|
||||||
store.arr.watch('0', () => {
|
store.arr.watch('0', () => {
|
||||||
counter++;
|
counter++;
|
||||||
})
|
});
|
||||||
|
|
||||||
store.change();
|
store.change();
|
||||||
|
|
||||||
|
@ -71,13 +71,11 @@ describe("watch",()=>{
|
||||||
it('shouhld watch collection item', async () => {
|
it('shouhld watch collection item', async () => {
|
||||||
const useStore = createStore({
|
const useStore = createStore({
|
||||||
state: {
|
state: {
|
||||||
collection:new Map([
|
collection: new Map([['a', 'a']]),
|
||||||
['a', 'a'],
|
|
||||||
])
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
change:(state)=>state.collection.set('a','x')
|
change: state => state.collection.set('a', 'x'),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
@ -85,7 +83,7 @@ describe("watch",()=>{
|
||||||
|
|
||||||
store.collection.watch('a', () => {
|
store.collection.watch('a', () => {
|
||||||
counter++;
|
counter++;
|
||||||
})
|
});
|
||||||
|
|
||||||
store.change();
|
store.change();
|
||||||
|
|
||||||
|
@ -96,12 +94,12 @@ describe("watch",()=>{
|
||||||
const useStore = createStore({
|
const useStore = createStore({
|
||||||
state: {
|
state: {
|
||||||
bool1: true,
|
bool1: true,
|
||||||
bool2:false
|
bool2: false,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggle1:state=>state.bool1=!state.bool1,
|
toggle1: state => (state.bool1 = !state.bool1),
|
||||||
toggle2:state=>state.bool2=!state.bool2
|
toggle2: state => (state.bool2 = !state.bool2),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let counter1 = 0;
|
let counter1 = 0;
|
||||||
|
@ -110,7 +108,7 @@ describe("watch",()=>{
|
||||||
|
|
||||||
watch(store.$s, () => {
|
watch(store.$s, () => {
|
||||||
counterAll++;
|
counterAll++;
|
||||||
})
|
});
|
||||||
|
|
||||||
store.$s.watch('bool1', () => {
|
store.$s.watch('bool1', () => {
|
||||||
counter1++;
|
counter1++;
|
||||||
|
@ -128,5 +126,5 @@ describe("watch",()=>{
|
||||||
|
|
||||||
expect(counter1).toBe(3);
|
expect(counter1).toBe(3);
|
||||||
expect(counterAll).toBe(6);
|
expect(counterAll).toBe(6);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
|
@ -8,18 +8,18 @@ const { unmountComponentAtNode } = Horizon;
|
||||||
const useStore1 = createStore({
|
const useStore1 = createStore({
|
||||||
state: { counter: 1 },
|
state: { counter: 1 },
|
||||||
actions: {
|
actions: {
|
||||||
add:(state)=>state.counter++,
|
add: state => state.counter++,
|
||||||
reset: (state)=>state.counter=1
|
reset: state => (state.counter = 1),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const useStore2 = createStore({
|
const useStore2 = createStore({
|
||||||
state: { counter2: 1 },
|
state: { counter2: 1 },
|
||||||
actions: {
|
actions: {
|
||||||
add2:(state)=>state.counter2++,
|
add2: state => state.counter2++,
|
||||||
reset: (state)=>state.counter2=1
|
reset: state => (state.counter2 = 1),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('Using multiple stores', () => {
|
describe('Using multiple stores', () => {
|
||||||
let container: HTMLElement | null = null;
|
let container: HTMLElement | null = null;
|
||||||
|
@ -65,9 +65,11 @@ describe('Using multiple stores', () => {
|
||||||
>
|
>
|
||||||
add
|
add
|
||||||
</button>
|
</button>
|
||||||
<p id={RESULT_ID}>{counter} {counter2}</p>
|
<p id={RESULT_ID}>
|
||||||
|
{counter} {counter2}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,28 +78,25 @@ describe('Using multiple stores', () => {
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
|
||||||
Horizon.act(() => {
|
Horizon.act(() => {
|
||||||
triggerClickEvent(container, BUTTON_ID);
|
triggerClickEvent(container, BUTTON_ID);
|
||||||
|
|
||||||
});
|
});
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
|
||||||
|
|
||||||
Horizon.act(() => {
|
Horizon.act(() => {
|
||||||
triggerClickEvent(container, BUTTON_ID2);
|
triggerClickEvent(container, BUTTON_ID2);
|
||||||
|
|
||||||
});
|
});
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should use use stores in cycles and multiple methods', () => {
|
it('Should use use stores in cycles and multiple methods', () => {
|
||||||
interface App {
|
interface App {
|
||||||
store:any,
|
store: any;
|
||||||
store2:any
|
store2: any;
|
||||||
}
|
}
|
||||||
class App extends Horizon.Component {
|
class App extends Horizon.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.store = useStore1();
|
this.store = useStore1();
|
||||||
this.store2 = useStore2()
|
this.store2 = useStore2();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -129,9 +128,11 @@ describe('Using multiple stores', () => {
|
||||||
>
|
>
|
||||||
add
|
add
|
||||||
</button>
|
</button>
|
||||||
<p id={RESULT_ID}>{counter} {counter2}</p>
|
<p id={RESULT_ID}>
|
||||||
|
{counter} {counter2}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,16 +141,13 @@ describe('Using multiple stores', () => {
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
|
||||||
Horizon.act(() => {
|
Horizon.act(() => {
|
||||||
triggerClickEvent(container, BUTTON_ID);
|
triggerClickEvent(container, BUTTON_ID);
|
||||||
|
|
||||||
});
|
});
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
|
||||||
|
|
||||||
Horizon.act(() => {
|
Horizon.act(() => {
|
||||||
triggerClickEvent(container, BUTTON_ID2);
|
triggerClickEvent(container, BUTTON_ID2);
|
||||||
|
|
||||||
});
|
});
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should use multiple stores in function component', () => {
|
it('Should use multiple stores in function component', () => {
|
||||||
|
@ -176,7 +174,9 @@ describe('Using multiple stores', () => {
|
||||||
>
|
>
|
||||||
add
|
add
|
||||||
</button>
|
</button>
|
||||||
<p id={RESULT_ID}>{counter} {counter2}</p>
|
<p id={RESULT_ID}>
|
||||||
|
{counter} {counter2}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -186,13 +186,11 @@ describe('Using multiple stores', () => {
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('1 1');
|
||||||
Horizon.act(() => {
|
Horizon.act(() => {
|
||||||
triggerClickEvent(container, BUTTON_ID);
|
triggerClickEvent(container, BUTTON_ID);
|
||||||
|
|
||||||
});
|
});
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 1');
|
||||||
|
|
||||||
Horizon.act(() => {
|
Horizon.act(() => {
|
||||||
triggerClickEvent(container, BUTTON_ID2);
|
triggerClickEvent(container, BUTTON_ID2);
|
||||||
|
|
||||||
});
|
});
|
||||||
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
|
expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('2 2');
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
|
||||||
|
*/
|
||||||
|
export default function dispatchChangeEvent(inputEle, value) {
|
||||||
|
const nativeInputSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||||
|
nativeInputSetter.call(inputEle, value);
|
||||||
|
|
||||||
|
inputEle.dispatchEvent(new Event('input', { bubbles: true}));
|
||||||
|
}
|
Loading…
Reference in New Issue