Match-id-34cc8d2e71fcba103088bb87b33f8df28ae8c31d
This commit is contained in:
parent
34abe28056
commit
19a39513e6
|
@ -3,19 +3,17 @@ import {
|
||||||
syncUpdates, startUpdate,
|
syncUpdates, startUpdate,
|
||||||
} from '../renderer/Renderer';
|
} from '../renderer/Renderer';
|
||||||
import {createPortal} from '../renderer/components/CreatePortal';
|
import {createPortal} from '../renderer/components/CreatePortal';
|
||||||
import {
|
|
||||||
clearContainer, saveContainer,
|
|
||||||
} from './DOMInternalKeys';
|
|
||||||
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 {listenDelegatedEvents} from '../event/EventBinding';
|
||||||
import {findDOMByClassInst} from '../renderer/vnode/VNodeUtils';
|
import {findDOMByClassInst} from '../renderer/vnode/VNodeUtils';
|
||||||
import {TreeRoot} from '../renderer/vnode/VNodeTags';
|
import {TreeRoot} from '../renderer/vnode/VNodeTags';
|
||||||
|
import {Callback} from '../renderer/UpdateHandler';
|
||||||
|
|
||||||
function executeRender(
|
function executeRender(
|
||||||
children: any,
|
children: any,
|
||||||
container: Container,
|
container: Container,
|
||||||
callback?: Function,
|
callback?: Callback,
|
||||||
) {
|
) {
|
||||||
let treeRoot = container._treeRoot;
|
let treeRoot = container._treeRoot;
|
||||||
|
|
||||||
|
@ -36,7 +34,7 @@ function executeRender(
|
||||||
return getFirstCustomDom(treeRoot);
|
return getFirstCustomDom(treeRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRoot(children: any, container: Container, callback?: Function) {
|
function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
// 清空容器
|
// 清空容器
|
||||||
let child = container.lastChild;
|
let child = container.lastChild;
|
||||||
while (child) {
|
while (child) {
|
||||||
|
@ -46,11 +44,10 @@ function createRoot(children: any, container: Container, callback?: Function) {
|
||||||
|
|
||||||
// 调度器创建根节点,并给容器dom赋vNode结构体
|
// 调度器创建根节点,并给容器dom赋vNode结构体
|
||||||
const treeRoot = createVNode(TreeRoot, container);
|
const treeRoot = createVNode(TreeRoot, container);
|
||||||
saveContainer(container, treeRoot._domRoot);
|
|
||||||
container._treeRoot = treeRoot;
|
container._treeRoot = treeRoot;
|
||||||
|
|
||||||
// 根节点挂接全量事件
|
// 根节点挂接全量事件
|
||||||
listenDelegatedEvents(container);
|
listenDelegatedEvents(container as Element);
|
||||||
|
|
||||||
// 执行回调
|
// 执行回调
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
|
@ -89,7 +86,6 @@ function destroy(container: Container) {
|
||||||
syncUpdates(() => {
|
syncUpdates(() => {
|
||||||
executeRender(null, container, () => {
|
executeRender(null, container, () => {
|
||||||
container._treeRoot = null;
|
container._treeRoot = null;
|
||||||
clearContainer(container);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
||||||
import {
|
import {
|
||||||
DomComponent,
|
DomComponent,
|
||||||
DomText,
|
DomText,
|
||||||
DomRoot,
|
TreeRoot,
|
||||||
} from '../renderer/vnode/VNodeTags';
|
} from '../renderer/vnode/VNodeTags';
|
||||||
|
|
||||||
const suffixKey = new Date().getTime().toString();
|
const suffixKey = new Date().getTime().toString();
|
||||||
|
@ -20,7 +20,6 @@ const prefix = '_horizon';
|
||||||
const internalKeys = {
|
const internalKeys = {
|
||||||
VNode: `${prefix}VNode@${suffixKey}`,
|
VNode: `${prefix}VNode@${suffixKey}`,
|
||||||
props: `${prefix}Props@${suffixKey}`,
|
props: `${prefix}Props@${suffixKey}`,
|
||||||
container: `${prefix}Container@${suffixKey}`,
|
|
||||||
events: `${prefix}Events@${suffixKey}`,
|
events: `${prefix}Events@${suffixKey}`,
|
||||||
nonDelegatedEvents: `${prefix}NonDelegatedEvents@${suffixKey}`,
|
nonDelegatedEvents: `${prefix}NonDelegatedEvents@${suffixKey}`,
|
||||||
};
|
};
|
||||||
|
@ -42,33 +41,33 @@ export function saveVNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用 DOM 节点,来找其对应的 VNode 实例
|
// 用 DOM 节点,来找其对应的 VNode 实例
|
||||||
export function getVNode(dom: Node): VNode | null {
|
export function getVNode(dom: Node|Container): VNode | null {
|
||||||
const vNode = dom[internalKeys.VNode] || dom[internalKeys.container];
|
const vNode = dom[internalKeys.VNode] || (dom as Container)._treeRoot;
|
||||||
if (vNode) {
|
if (vNode) {
|
||||||
const {tag} = vNode;
|
const {tag} = vNode;
|
||||||
if (tag === DomComponent || tag === DomText || tag === DomRoot) {
|
if (tag === DomComponent || tag === DomText || tag === TreeRoot) {
|
||||||
return vNode;
|
return vNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用 DOM 对象,来寻找其对应或者说是最近的 VNode 实例
|
// 用 DOM 对象,来寻找其对应或者说是最近父级的 vNode
|
||||||
export function getNearestVNode(dom: Node): null | VNode {
|
export function getNearestVNode(dom: Node): null | VNode {
|
||||||
let vNode = dom[internalKeys.VNode];
|
let vNode = dom[internalKeys.VNode];
|
||||||
if (vNode) { // 如果是已经被框架标记过的 DOM 节点,那么直接返回其 VNode 实例
|
if (vNode) { // 如果是已经被框架标记过的 DOM 节点,那么直接返回其 VNode 实例
|
||||||
return vNode;
|
return vNode;
|
||||||
}
|
}
|
||||||
// 下面处理的是为被框架标记过的 DOM 节点,向上找其父节点是否被框架标记过
|
// 下面处理的是为被框架标记过的 DOM 节点,向上找其父节点是否被框架标记过
|
||||||
let parent = dom.parentNode;
|
let parentDom = dom.parentNode;
|
||||||
let nearVNode = null;
|
let nearVNode = null;
|
||||||
while (parent) {
|
while (parentDom) {
|
||||||
vNode = parent[internalKeys.VNode];
|
vNode = parentDom[internalKeys.VNode];
|
||||||
if (vNode) {
|
if (vNode) {
|
||||||
nearVNode = vNode;
|
nearVNode = vNode;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
parent = parent.parentNode;
|
parentDom = parentDom.parentNode;
|
||||||
}
|
}
|
||||||
return nearVNode;
|
return nearVNode;
|
||||||
}
|
}
|
||||||
|
@ -99,11 +98,3 @@ export function getEventToListenerMap(target: EventTarget): Map<string, EventLis
|
||||||
}
|
}
|
||||||
return eventsMap;
|
return eventsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveContainer(dom: Container, domRoot: VNode): void {
|
|
||||||
dom[internalKeys.container] = domRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearContainer(dom: Container): void {
|
|
||||||
dom[internalKeys.container] = null;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
saveVNode,
|
saveVNode,
|
||||||
updateVNodeProps,
|
updateVNodeProps,
|
||||||
|
@ -10,7 +6,7 @@ import {
|
||||||
createDom,
|
createDom,
|
||||||
} from './utils/DomCreator';
|
} from './utils/DomCreator';
|
||||||
import {getSelectionInfo, resetSelectionRange, selectionData} from './SelectionRangeHandler';
|
import {getSelectionInfo, resetSelectionRange, selectionData} from './SelectionRangeHandler';
|
||||||
import {isElement, isComment, isDocument, isDocumentFragment} from './utils/Common';
|
import {isElement, isComment, isDocument, isDocumentFragment, getDomTag, shouldAutoFocus} from './utils/Common';
|
||||||
import {NSS} from './utils/DomCreator';
|
import {NSS} from './utils/DomCreator';
|
||||||
import {adjustStyleValue} from './DOMPropertiesHandler/StyleHandler';
|
import {adjustStyleValue} from './DOMPropertiesHandler/StyleHandler';
|
||||||
|
|
||||||
|
@ -20,7 +16,7 @@ import {
|
||||||
setInitValue,
|
setInitValue,
|
||||||
getPropsWithoutValue,
|
getPropsWithoutValue,
|
||||||
updateValue,
|
updateValue,
|
||||||
} from './valueHandler/ValueHandler';
|
} from './valueHandler';
|
||||||
import {
|
import {
|
||||||
compareProps,
|
compareProps,
|
||||||
setDomProps, updateDomProps
|
setDomProps, updateDomProps
|
||||||
|
@ -31,76 +27,39 @@ import {DomComponent, DomText} from '../renderer/vnode/VNodeTags';
|
||||||
import {updateCommonProp} from './DOMPropertiesHandler/UpdateCommonProp';
|
import {updateCommonProp} from './DOMPropertiesHandler/UpdateCommonProp';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
autoFocus?: boolean,
|
autoFocus?: boolean;
|
||||||
children?: any,
|
children?: any;
|
||||||
dangerouslySetInnerHTML?: any,
|
dangerouslySetInnerHTML?: any;
|
||||||
disabled?: boolean,
|
disabled?: boolean;
|
||||||
hidden?: boolean,
|
hidden?: boolean;
|
||||||
style?: { display?: string },
|
style?: { display?: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Container = (Element & { _treeRoot?: VNode }) | (Document & { _treeRoot?: VNode });
|
export type Container = (Element & { _treeRoot?: VNode }) | (Document & { _treeRoot?: VNode });
|
||||||
|
|
||||||
let selectionInfo: null | selectionData = null;
|
let selectionInfo: null | selectionData = null;
|
||||||
|
|
||||||
const types = ['button', 'input', 'select', 'textarea'];
|
function getChildNS(parentNS: string | null, tagName: string): string {
|
||||||
|
if (parentNS === NSS.svg && tagName === 'foreignObject') {
|
||||||
// button、input、select、textarea、如果有 autoFocus 属性需要focus
|
|
||||||
function shouldAutoFocus(type: string, props: Props): boolean {
|
|
||||||
return types.includes(type) ? Boolean(props.autoFocus) : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChildNS(parent: string | null, type: string,): string {
|
|
||||||
if (parent === NSS.svg && type === 'foreignObject') {
|
|
||||||
return NSS.html;
|
return NSS.html;
|
||||||
}
|
}
|
||||||
if (parent == null || parent === NSS.html) {
|
|
||||||
// 没有父命名空间
|
if (parentNS == null || parentNS === NSS.html) {
|
||||||
return Object.keys(NSS).includes(type) ? NSS[type] : NSS.html;
|
// 没有父命名空间,或父命名空间为xhtml
|
||||||
|
return NSS[tagName] ?? NSS.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认返回parentNamespace.
|
// 默认返回parentNamespace.
|
||||||
return parent;
|
return parentNS;
|
||||||
}
|
|
||||||
|
|
||||||
function getRootNS(dom, root, nextRoot) {
|
|
||||||
let namespace;
|
|
||||||
let tag;
|
|
||||||
let container, ownNamespace;
|
|
||||||
|
|
||||||
if (isDocument(dom)) {
|
|
||||||
tag = '#document';
|
|
||||||
namespace = root ? root.namespaceURI : getChildNS(null, '');
|
|
||||||
} else if (isDocumentFragment(dom)) {
|
|
||||||
tag = '#fragment';
|
|
||||||
namespace = root ? root.namespaceURI : getChildNS(null, '');
|
|
||||||
} else if (isComment(dom)) {
|
|
||||||
container = nextRoot.parentNode;
|
|
||||||
ownNamespace = container.namespaceURI || null;
|
|
||||||
tag = container.tagName;
|
|
||||||
namespace = getChildNS(ownNamespace, tag);
|
|
||||||
} else {
|
|
||||||
container = nextRoot;
|
|
||||||
ownNamespace = container.namespaceURI || null;
|
|
||||||
tag = container.tagName;
|
|
||||||
namespace = getChildNS(ownNamespace, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
return namespace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取容器
|
// 获取容器
|
||||||
export function getNSCtx(
|
export function getNSCtx(dom: Container, parentNS: string, type: string): string {
|
||||||
nextRoot: Container,
|
|
||||||
ctxNamespace: string,
|
|
||||||
type: string): string {
|
|
||||||
let namespace;
|
let namespace;
|
||||||
if (nextRoot) {
|
if (dom) {
|
||||||
// 获取并解析根节点容器
|
namespace = getChildNS(dom.namespaceURI ?? null, dom.nodeName);
|
||||||
const root = nextRoot.documentElement;
|
|
||||||
namespace = getRootNS(nextRoot, root, nextRoot);
|
|
||||||
} else {
|
} else {
|
||||||
// 获取子节点容器
|
namespace = getChildNS(parentNS, type);
|
||||||
namespace = getChildNS(ctxNamespace, type);
|
|
||||||
}
|
}
|
||||||
return namespace;
|
return namespace;
|
||||||
}
|
}
|
||||||
|
@ -114,21 +73,14 @@ export function resetAfterSubmit(): void {
|
||||||
selectionInfo = null;
|
selectionInfo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 创建 DOM 对象
|
||||||
* 在内存中创建 DOM 对象
|
|
||||||
* @param tagName 元素的类型
|
|
||||||
* @param props 属性
|
|
||||||
* @param parentNamespace 当前上下文
|
|
||||||
* @param vNode 当前元素对应的 VNode
|
|
||||||
* @returns DOM 对象
|
|
||||||
*/
|
|
||||||
export function newDom(
|
export function newDom(
|
||||||
tagName: string,
|
tagName: string,
|
||||||
props: Props,
|
props: Props,
|
||||||
parentNamespace: string,
|
parentNamespace: string,
|
||||||
vNode: VNode,
|
vNode: VNode,
|
||||||
): Element {
|
): Element {
|
||||||
const dom: Element = createDom(tagName, props, parentNamespace);
|
const dom: Element = createDom(tagName, parentNamespace);
|
||||||
// 将 vNode 节点挂到 DOM 对象上
|
// 将 vNode 节点挂到 DOM 对象上
|
||||||
saveVNode(vNode, dom);
|
saveVNode(vNode, dom);
|
||||||
// 将属性挂到 DOM 对象上
|
// 将属性挂到 DOM 对象上
|
||||||
|
@ -164,7 +116,7 @@ export function getPropChangeList(
|
||||||
type: string,
|
type: string,
|
||||||
lastRawProps: Props,
|
lastRawProps: Props,
|
||||||
nextRawProps: Props,
|
nextRawProps: Props,
|
||||||
): null | Array<any> {
|
): Array<any> {
|
||||||
// 校验两个对象的不同
|
// 校验两个对象的不同
|
||||||
validateProps(type, nextRawProps);
|
validateProps(type, nextRawProps);
|
||||||
|
|
||||||
|
@ -177,15 +129,11 @@ export function getPropChangeList(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTextChild(type: string, props: Props): boolean {
|
export function isTextChild(type: string, props: Props): boolean {
|
||||||
if (type === 'textarea') {
|
const typeArray = ['textarea', 'option', 'noscript'];
|
||||||
|
const typeOfPropsChild = ['string', 'number'];
|
||||||
|
if (typeArray.indexOf(type) >= 0) {
|
||||||
return true;
|
return true;
|
||||||
} else if (type === 'option') {
|
} else if (typeOfPropsChild.indexOf(typeof props.children) >= 0) {
|
||||||
return true;
|
|
||||||
} else if (type === 'noscript') {
|
|
||||||
return true;
|
|
||||||
} else if (typeof props.children === 'string') {
|
|
||||||
return true;
|
|
||||||
} else if (typeof props.children === 'number') {
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
@ -205,19 +153,8 @@ export function newTextDom(
|
||||||
return textNode;
|
return textNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitMount(
|
|
||||||
dom: Element,
|
|
||||||
type: string,
|
|
||||||
newProps: Props,
|
|
||||||
): void {
|
|
||||||
if (shouldAutoFocus(type, newProps)) {
|
|
||||||
// button、input、select、textarea、如果有 autoFocus 属性需要focus
|
|
||||||
dom.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交vNode的类型为Component或者Text的更新
|
// 提交vNode的类型为Component或者Text的更新
|
||||||
export function submitDomUpdate(tag: number, vNode: VNode) {
|
export function submitDomUpdate(tag: string, vNode: VNode) {
|
||||||
const newProps = vNode.props;
|
const newProps = vNode.props;
|
||||||
const element: Element = vNode.realNode;
|
const element: Element = vNode.realNode;
|
||||||
|
|
||||||
|
@ -251,71 +188,44 @@ export function clearText(dom: Element): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加child元素
|
// 添加child元素
|
||||||
export function appendChildElement(isContainer: boolean,
|
export function appendChildElement(
|
||||||
parent: Element | Container,
|
parent: Element | Container,
|
||||||
child: Element | Text): void {
|
child: Element | Text
|
||||||
if (isContainer && isComment(parent)) {
|
): void {
|
||||||
parent.parentNode.insertBefore(child, parent);
|
parent.appendChild(child);
|
||||||
} else {
|
|
||||||
parent.appendChild(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入dom元素
|
// 插入dom元素
|
||||||
export function insertDomBefore(
|
export function insertDomBefore(
|
||||||
isContainer: boolean,
|
|
||||||
parent: Element | Container,
|
parent: Element | Container,
|
||||||
child: Element | Text,
|
child: Element | Text,
|
||||||
beforeChild: Element | Text,
|
beforeChild: Element | Text,
|
||||||
) {
|
) {
|
||||||
if (isContainer && isComment(parent)) {
|
parent.insertBefore(child, beforeChild);
|
||||||
parent.parentNode.insertBefore(child, beforeChild);
|
|
||||||
} else {
|
|
||||||
parent.insertBefore(child, beforeChild);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeChildDom(
|
export function removeChildDom(
|
||||||
isContainer: boolean,
|
|
||||||
parent: Element | Container,
|
parent: Element | Container,
|
||||||
child: Element | Text
|
child: Element | Text
|
||||||
) {
|
) {
|
||||||
if (isContainer && isComment(parent)) {
|
parent.removeChild(child);
|
||||||
parent.parentNode.removeChild(child);
|
|
||||||
} else {
|
|
||||||
parent.removeChild(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏元素
|
// 隐藏元素
|
||||||
export function hideDom(tag: number, element: Element | Text) {
|
export function hideDom(tag: string, dom: Element | Text) {
|
||||||
if (tag === DomComponent) {
|
if (tag === DomComponent) {
|
||||||
// DomComponent类型
|
dom.style.display = 'none';
|
||||||
const {style} = element;
|
|
||||||
if (style.setProperty && typeof style.setProperty === 'function') {
|
|
||||||
style.setProperty('display', 'none', 'important');
|
|
||||||
} else {
|
|
||||||
style.display = 'none';
|
|
||||||
}
|
|
||||||
} else if (tag === DomText) {
|
} else if (tag === DomText) {
|
||||||
// text类型
|
dom.textContent = '';
|
||||||
element.textContent = '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不隐藏元素
|
// 不隐藏元素
|
||||||
export function unHideDom(tag: number, element: Element | Text, props: Props) {
|
export function unHideDom(tag: string, dom: Element | Text, props: Props) {
|
||||||
if (tag === DomComponent) {
|
if (tag === DomComponent) {
|
||||||
// DomComponent类型
|
dom.style.display = adjustStyleValue('display', props?.style?.display ?? '');
|
||||||
const style = props.style;
|
|
||||||
let display = null;
|
|
||||||
if (style !== undefined && style !== null && style.hasOwnProperty('display')) {
|
|
||||||
display = style.display;
|
|
||||||
}
|
|
||||||
element.style.display = adjustStyleValue('display', display);
|
|
||||||
} else if (tag === DomText) {
|
} else if (tag === DomText) {
|
||||||
// text类型
|
dom.textContent = props;
|
||||||
element.textContent = props;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ function updateOneProp(dom, propName, propVal, isNativeTag, isInit = false) {
|
||||||
export function compareProps(
|
export function compareProps(
|
||||||
oldProps: Object,
|
oldProps: Object,
|
||||||
newProps: Object,
|
newProps: Object,
|
||||||
): null | Array<any> {
|
): Array<any> {
|
||||||
let updatesForStyle = {};
|
let updatesForStyle = {};
|
||||||
const toBeDeletedProps = [];
|
const toBeDeletedProps = [];
|
||||||
const toBeUpdatedProps = [];
|
const toBeUpdatedProps = [];
|
||||||
|
|
|
@ -7,29 +7,44 @@ export function setStyles(dom, styles) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = dom.style;
|
const style = dom.style;
|
||||||
const styleKeys = Object.keys(styles);
|
Object.keys(styles).forEach((name) => {
|
||||||
|
const styleVal = styles[name];
|
||||||
|
|
||||||
for (let i = 0; i < styleKeys.length; i++) {
|
const validStyleValue = adjustStyleValue(name, styleVal);
|
||||||
const styleKey = styleKeys[i];
|
|
||||||
const styleVal = styles[styleKey];
|
|
||||||
|
|
||||||
const validStyleValue = adjustStyleValue(styleKey, styleVal);
|
style[name] = validStyleValue;
|
||||||
|
});
|
||||||
style[styleKey] = validStyleValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 对空值或布尔值进行适配,转为空字符串
|
* 不需要加长度单位的 css 属性
|
||||||
* 2. 去掉多余空字符
|
*/
|
||||||
|
const noUnitCSS = ['animationIterationCount', 'columnCount', 'columns', 'gridArea', 'fontWeight', 'lineClamp',
|
||||||
|
'lineHeight', 'opacity', 'order', 'orphans', 'tabSize', 'widows', 'zIndex', 'zoom'];
|
||||||
|
|
||||||
|
function isNeedUnitCSS(propName: string) {
|
||||||
|
return !(noUnitCSS.includes(propName)
|
||||||
|
|| propName.startsWith('borderImage')
|
||||||
|
|| propName.startsWith('flex')
|
||||||
|
|| propName.startsWith('gridRow')
|
||||||
|
|| propName.startsWith('gridColumn')
|
||||||
|
|| propName.startsWith('stroke')
|
||||||
|
|| propName.startsWith('box')
|
||||||
|
|| propName.endsWith('Opacity'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对一些没有写单位的样式进行适配,例如:width: 10 => width: 10px
|
||||||
|
* 对空值或布尔值进行适配,转为空字符串
|
||||||
|
* 去掉多余空字符
|
||||||
*/
|
*/
|
||||||
export function adjustStyleValue(name, value) {
|
export function adjustStyleValue(name, value) {
|
||||||
let validValue;
|
let validValue = value;
|
||||||
|
|
||||||
if (value === '' || value == null || typeof value === 'boolean') {
|
if (typeof value === 'number' && value !== 0 && isNeedUnitCSS(name)) {
|
||||||
|
validValue = `${value}px`;
|
||||||
|
} else if (value === '' || value == null || typeof value === 'boolean') {
|
||||||
validValue = '';
|
validValue = '';
|
||||||
} else {
|
|
||||||
validValue = String(value).trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return validValue;
|
return validValue;
|
||||||
|
|
|
@ -5,6 +5,18 @@ import {
|
||||||
import {isInvalidValue} from '../validators/ValidateProps';
|
import {isInvalidValue} from '../validators/ValidateProps';
|
||||||
import {getNamespaceCtx} from '../../renderer/ContextSaver';
|
import {getNamespaceCtx} from '../../renderer/ContextSaver';
|
||||||
import {NSS} from '../utils/DomCreator';
|
import {NSS} from '../utils/DomCreator';
|
||||||
|
import {getDomTag} from '../utils/Common';
|
||||||
|
|
||||||
|
const svgHumpAttr = new Set(['allowReorder', 'autoReverse', 'baseFrequency', 'baseProfile', 'calcMode', 'clipPathUnits',
|
||||||
|
'contentScriptType', 'contentStyleType', 'diffuseConstant', 'edgeMode', 'externalResourcesRequired', 'filterRes',
|
||||||
|
'filterUnits', 'glyphRef', 'gradientTransform', 'gradientUnits', 'kernelMatrix', 'kernelUnitLength', 'keyPoints',
|
||||||
|
'keySplines', 'keyTimes', 'lengthAdjust', 'limitingConeAngle', 'markerHeight', 'markerUnits', 'markerWidth',
|
||||||
|
'maskContentUnits', 'maskUnits', 'numOctaves', 'pathLength', 'patternContentUnits', 'patternTransform,',
|
||||||
|
'patternUnits', 'pointsAtX', 'pointsAtY', 'pointsAtZ', 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits',
|
||||||
|
'referrerPolicy', 'refX', 'refY', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures',
|
||||||
|
'specularConstant', 'specularExponent', 'spreadMethod', 'startOffset', 'stdDeviation', 'stitchTiles', 'surfaceScale',
|
||||||
|
'systemLanguage', 'tableValues', 'targetX', 'targetY', 'textLength', 'viewBox', 'viewTarget', 'xChannelSelector',
|
||||||
|
'yChannelSelector', 'zoomAndPan']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 给 dom 设置属性
|
* 给 dom 设置属性
|
||||||
|
@ -20,8 +32,10 @@ export function updateCommonProp(dom: Element, attrName: string, value: any, isN
|
||||||
|
|
||||||
if (!isNativeTag || propDetails === null) {
|
if (!isNativeTag || propDetails === null) {
|
||||||
// 特殊处理svg的属性,把驼峰式的属性名称转成'-'
|
// 特殊处理svg的属性,把驼峰式的属性名称转成'-'
|
||||||
if (dom.tagName.toLowerCase() === 'svg' || getNamespaceCtx() === NSS.svg) {
|
if (getDomTag(dom) === 'svg' || getNamespaceCtx() === NSS.svg) {
|
||||||
attrName = convertToLowerCase(attrName);
|
if (!svgHumpAttr.has(attrName)) {
|
||||||
|
attrName = convertToLowerCase(attrName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import {HorizonDom} from './Interface';
|
import {HorizonDom} from './Interface';
|
||||||
|
import {Props} from '../DOMOperator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前聚焦的 input 或者 textarea 元素
|
* 获取当前聚焦的 input 或者 textarea 元素
|
||||||
* @param currentDoc 指定 document
|
* @param doc 指定 document
|
||||||
*/
|
*/
|
||||||
export function getFocusedDom(currentDoc?: Document): HorizonDom | void {
|
export function getFocusedDom(doc?: Document): HorizonDom | void {
|
||||||
let currentDocument;
|
let currentDocument;
|
||||||
if (currentDoc) {
|
if (doc) {
|
||||||
currentDocument = currentDoc;
|
currentDocument = doc;
|
||||||
} else {
|
} else {
|
||||||
if (document) {
|
if (document) {
|
||||||
currentDocument = document;
|
currentDocument = document;
|
||||||
|
@ -63,13 +64,13 @@ export function isDocumentFragment(dom) {
|
||||||
return dom.nodeType === 11;
|
return dom.nodeType === 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRootElement(dom: HorizonDom): HorizonDom {
|
export function getDomTag(dom) {
|
||||||
let rootElement = dom;
|
return dom.nodeName.toLowerCase();
|
||||||
|
}
|
||||||
while (rootElement.parentNode) {
|
|
||||||
// @ts-ignore
|
const types = ['button', 'input', 'select', 'textarea'];
|
||||||
rootElement = rootElement.parentNode;
|
|
||||||
}
|
// button、input、select、textarea、如果有 autoFocus 属性需要focus
|
||||||
|
export function shouldAutoFocus(tagName: string, props: Props): boolean {
|
||||||
return rootElement;
|
return types.includes(tagName) ? Boolean(props.autoFocus) : false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ export const NSS = {
|
||||||
// 创建DOM元素
|
// 创建DOM元素
|
||||||
export function createDom(
|
export function createDom(
|
||||||
tagName: string,
|
tagName: string,
|
||||||
props: Object,
|
|
||||||
parentNamespace: string,
|
parentNamespace: string,
|
||||||
): Element {
|
): Element {
|
||||||
let dom: Element;
|
let dom: Element;
|
||||||
|
@ -20,6 +19,5 @@ export function createDom(
|
||||||
} else {
|
} else {
|
||||||
dom = document.createElement(tagName);
|
dom = document.createElement(tagName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ export function validateProps(type, props) {
|
||||||
throw new Error('style should be a object.');
|
throw new Error('style should be a object.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (isDev) {
|
||||||
// 校验属性
|
// 校验属性
|
||||||
const invalidProps = Object.keys(props).filter(key => !isValidProp(type, key, props[key]));
|
const invalidProps = Object.keys(props).filter(key => !isValidProp(type, key, props[key]));
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {updateCommonProp} from '../DOMPropertiesHandler/UpdateCommonProp';
|
import {updateCommonProp} from '../DOMPropertiesHandler/UpdateCommonProp';
|
||||||
import {getVNodeProps} from '../DOMInternalKeys';
|
import {getVNodeProps} from '../DOMInternalKeys';
|
||||||
import {IProperty} from '../utils/Interface';
|
import {IProperty} from '../utils/Interface';
|
||||||
import {getRootElement} from '../utils/Common';
|
|
||||||
import {isInputValueChanged} from './ValueChangeHandler';
|
import {isInputValueChanged} from './ValueChangeHandler';
|
||||||
|
|
||||||
function getInitValue(dom: HTMLInputElement, properties: IProperty) {
|
function getInitValue(dom: HTMLInputElement, properties: IProperty) {
|
||||||
|
@ -33,12 +32,12 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IPr
|
||||||
export function updateInputValue(dom: HTMLInputElement, properties: IProperty) {
|
export function updateInputValue(dom: HTMLInputElement, properties: IProperty) {
|
||||||
const {value, checked} = properties;
|
const {value, checked} = properties;
|
||||||
|
|
||||||
if (checked != null) {
|
if (value != null) { // 处理 dom.value 逻辑
|
||||||
updateCommonProp(dom, 'checked', checked);
|
|
||||||
} else if (value != null) { // 处理 dom.value 逻辑
|
|
||||||
if (dom.value !== String(value)) {
|
if (dom.value !== String(value)) {
|
||||||
dom.value = String(value);
|
dom.value = String(value);
|
||||||
}
|
}
|
||||||
|
} else if (checked != null) {
|
||||||
|
updateCommonProp(dom, 'checked', checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,17 +63,10 @@ export function resetInputValue(dom: HTMLInputElement, properties: IProperty) {
|
||||||
const {name, type} = properties;
|
const {name, type} = properties;
|
||||||
// 如果是 radio,先更新相同 name 的 radio
|
// 如果是 radio,先更新相同 name 的 radio
|
||||||
if (type === 'radio' && name != null) {
|
if (type === 'radio' && name != null) {
|
||||||
// radio 的根节点
|
const radioList = document.querySelectorAll(`input[type="radio"][name="${name}"]`);
|
||||||
const radioRoot = getRootElement(dom);
|
|
||||||
|
|
||||||
const radioList = radioRoot.querySelectorAll(`input[type="radio"]`);
|
|
||||||
|
|
||||||
for (let i = 0; i < radioList.length; i++) {
|
for (let i = 0; i < radioList.length; i++) {
|
||||||
const radio = radioList[i];
|
const radio = radioList[i];
|
||||||
// @ts-ignore
|
|
||||||
if (radio.name !== name) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (radio === dom) {
|
if (radio === dom) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,7 @@ function getInitValue(properties: IProperty) {
|
||||||
// children content存在时,会覆盖defaultValue
|
// children content存在时,会覆盖defaultValue
|
||||||
if (children != null) {
|
if (children != null) {
|
||||||
// 子节点不是纯文本,则取第一个子节点
|
// 子节点不是纯文本,则取第一个子节点
|
||||||
if (children instanceof Array) {
|
initValue = children instanceof Array ? children[0] : children;
|
||||||
initValue = children[0];
|
|
||||||
} else {
|
|
||||||
initValue = children;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultValue 属性未配置,置为空字符串
|
// defaultValue 属性未配置,置为空字符串
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
import {throwIfTrue} from '../renderer/utils/throwIfTrue';
|
||||||
|
import {TYPE_ELEMENT, TYPE_PORTAL} from '../renderer/utils/elementType';
|
||||||
|
|
||||||
|
import {isValidElement, HorizonElement} from './HorizonElement';
|
||||||
|
|
||||||
|
// 生成key
|
||||||
|
function getItemKey(item: any, index: number): string {
|
||||||
|
if (typeof item === 'object' && item !== null && item.key != null) {
|
||||||
|
return '.$' + item.key;
|
||||||
|
}
|
||||||
|
// 使用36进制减少生成字符串的长度以节省空间
|
||||||
|
return '.' + index.toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapChildrenToArray(
|
||||||
|
children: any,
|
||||||
|
arr: Array<any>,
|
||||||
|
prefix: string,
|
||||||
|
callback?: Function,
|
||||||
|
): number | void {
|
||||||
|
const type = typeof children;
|
||||||
|
switch (type) {
|
||||||
|
// 继承原有规格,undefined和boolean类型按照null处理
|
||||||
|
case 'undefined':
|
||||||
|
case 'boolean':
|
||||||
|
callMapFun(null, arr, prefix, callback);
|
||||||
|
return;
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
callMapFun(children, arr, prefix, callback);
|
||||||
|
return;
|
||||||
|
case 'object':
|
||||||
|
if (children === null) {
|
||||||
|
callMapFun(null, arr, prefix, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const vtype = children.vtype;
|
||||||
|
if (vtype === TYPE_ELEMENT || vtype === TYPE_PORTAL) {
|
||||||
|
callMapFun(children, arr, prefix, callback) ;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Array.isArray(children)) {
|
||||||
|
processArrayChildren(children, arr, prefix, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
'Object is invalid as a Horizon child. '
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processArrayChildren(
|
||||||
|
children: any,
|
||||||
|
arr: Array<any>,
|
||||||
|
prefix: string,
|
||||||
|
callback: Function,
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const childItem = children[i];
|
||||||
|
const nextPrefix = prefix + getItemKey(childItem, i);
|
||||||
|
mapChildrenToArray(
|
||||||
|
childItem,
|
||||||
|
arr,
|
||||||
|
nextPrefix,
|
||||||
|
callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function callMapFun(
|
||||||
|
children: any,
|
||||||
|
arr: Array<any>,
|
||||||
|
prefix: string,
|
||||||
|
callback: Function,
|
||||||
|
) {
|
||||||
|
let mappedChild = callback(children);
|
||||||
|
if (Array.isArray(mappedChild)) {
|
||||||
|
// 维持原有规格,如果callback返回结果是数组,处理函数修改为返回数组item
|
||||||
|
processArrayChildren(mappedChild, arr, prefix + '/', subChild => subChild);
|
||||||
|
} else if (mappedChild !== null && mappedChild !== undefined) {
|
||||||
|
// 给一个key值,确保返回的对象一定带有key
|
||||||
|
if (isValidElement(mappedChild)) {
|
||||||
|
const childKey = prefix === '' ? getItemKey(children, 0) : '';
|
||||||
|
const mappedKey = getItemKey(mappedChild, 0);
|
||||||
|
const newKey = prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0)
|
||||||
|
? '.$' + mappedChild.key
|
||||||
|
: '');
|
||||||
|
// 返回一个修改key的children
|
||||||
|
mappedChild = HorizonElement(
|
||||||
|
mappedChild.type,
|
||||||
|
newKey,
|
||||||
|
mappedChild.ref,
|
||||||
|
mappedChild._vNode,
|
||||||
|
mappedChild.props,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
arr.push(mappedChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg
|
||||||
|
function mapChildren(
|
||||||
|
children: any,
|
||||||
|
func: Function,
|
||||||
|
context?: any,
|
||||||
|
): Array<any> {
|
||||||
|
if (children === null || children === undefined) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
let count = 0;
|
||||||
|
const result = [];
|
||||||
|
mapChildrenToArray(children, result, '', (child) => {
|
||||||
|
return func.call(context, child, count++);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Children = {
|
||||||
|
forEach: (children, func, context?: any) => {
|
||||||
|
// 不返回数组即可
|
||||||
|
mapChildren(children, func, context);
|
||||||
|
},
|
||||||
|
map: mapChildren,
|
||||||
|
// 并非所有元素都会计数,只计数调用callMapFun函数次数
|
||||||
|
count: (children) => {
|
||||||
|
let n = 0;
|
||||||
|
mapChildren(children, () => {
|
||||||
|
n++;
|
||||||
|
});
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
only: (children) => {
|
||||||
|
throwIfTrue(
|
||||||
|
!isValidElement(children),
|
||||||
|
'Horizon.Children.only function received invalid element.'
|
||||||
|
);
|
||||||
|
return children;
|
||||||
|
},
|
||||||
|
toArray: (children) => {
|
||||||
|
const result = [];
|
||||||
|
mapChildrenToArray(children, result, '', child => child);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Children
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {
|
||||||
|
TYPE_FRAGMENT,
|
||||||
|
TYPE_PROFILER,
|
||||||
|
TYPE_STRICT_MODE,
|
||||||
|
TYPE_SUSPENSE,
|
||||||
|
} from '../renderer/utils/elementType';
|
||||||
|
|
||||||
|
import {Component, PureComponent} from '../renderer/components/BaseClassComponent';
|
||||||
|
import {createRef} from '../renderer/components/CreateRef';
|
||||||
|
import {Children} from './ChildrenUtil';
|
||||||
|
import {
|
||||||
|
createElement,
|
||||||
|
cloneElement,
|
||||||
|
isValidElement,
|
||||||
|
} from './HorizonElement';
|
||||||
|
import {createContext} from '../renderer/components/context/CreateContext';
|
||||||
|
import {lazy} from '../renderer/components/Lazy';
|
||||||
|
import {forwardRef} from '../renderer/components/ForwardRef';
|
||||||
|
import {memo} from '../renderer/components/Memo';
|
||||||
|
import hookMapping from '../renderer/hooks/HookMapping';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useReducer,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from '../renderer/hooks/HookExternal';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Children,
|
||||||
|
createRef,
|
||||||
|
Component,
|
||||||
|
PureComponent,
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
lazy,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useReducer,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
TYPE_FRAGMENT as Fragment,
|
||||||
|
TYPE_PROFILER as Profiler,
|
||||||
|
TYPE_STRICT_MODE as StrictMode,
|
||||||
|
TYPE_SUSPENSE as Suspense,
|
||||||
|
createElement,
|
||||||
|
cloneElement,
|
||||||
|
isValidElement,
|
||||||
|
hookMapping,
|
||||||
|
};
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { TYPE_ELEMENT } from '../renderer/utils/elementType';
|
||||||
|
import ProcessingVNode from '../renderer/vnode/ProcessingVNode';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vtype, 节点的类型,这里固定是element
|
||||||
|
* type,保存dom节点的名称或者组件的函数地址
|
||||||
|
* key key属性
|
||||||
|
* ref ref属性
|
||||||
|
* props 其他常规属性
|
||||||
|
*/
|
||||||
|
export function HorizonElement(type, key, ref, vNode, props) {
|
||||||
|
return {
|
||||||
|
// Horizon元素标识符
|
||||||
|
vtype: TYPE_ELEMENT,
|
||||||
|
|
||||||
|
// 属于元素的内置属性
|
||||||
|
type: type,
|
||||||
|
key: key,
|
||||||
|
ref: ref,
|
||||||
|
props: props,
|
||||||
|
|
||||||
|
// 记录负责创建此元素的组件。
|
||||||
|
_vNode: vNode,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function isValidKey(key) {
|
||||||
|
return key !== 'key' && key !== 'ref' && key !== '__source';
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildElement(isClone, type, setting, ...children) {
|
||||||
|
// setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null
|
||||||
|
const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null);
|
||||||
|
const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null);
|
||||||
|
const props = isClone ? {...type.props} : {};
|
||||||
|
let vNode = isClone ? type._vNode : ProcessingVNode.val;
|
||||||
|
|
||||||
|
if (setting != null) {
|
||||||
|
Object.keys(setting).forEach(k => {
|
||||||
|
if (isValidKey(k)) {
|
||||||
|
props[k] = setting[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (setting.ref !== undefined && isClone) {
|
||||||
|
vNode = ProcessingVNode.val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length) {
|
||||||
|
props.children = children.length === 1 ? children[0] : children;
|
||||||
|
}
|
||||||
|
const element = isClone ? type.type : type;
|
||||||
|
//合并默认属性
|
||||||
|
if (element && element.defaultProps) {
|
||||||
|
mergeDefault(props, element.defaultProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HorizonElement(element, key, ref, vNode, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建Element结构体,供JSX编译时调用
|
||||||
|
export function createElement(type, setting, ...children) {
|
||||||
|
return buildElement(false, type, setting, ...children);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeDefault(sourceObj, defaultObj) {
|
||||||
|
Object.keys(defaultObj).forEach((key) => {
|
||||||
|
if (sourceObj[key] === undefined) {
|
||||||
|
sourceObj[key] = defaultObj[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cloneElement(element, setting, ...children) {
|
||||||
|
return buildElement(true, element, setting, ...children);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测结构体是否为合法的Element
|
||||||
|
export function isValidElement(element) {
|
||||||
|
return !!(element && element.vtype === TYPE_ELEMENT);
|
||||||
|
}
|
Loading…
Reference in New Issue