Match-id-967e808cdb47df39560696171de0c8f743a70f84
This commit is contained in:
parent
c2f173dd60
commit
f7f9956ddc
|
@ -0,0 +1,108 @@
|
||||||
|
import {
|
||||||
|
asyncUpdates, createVNode, getFirstCustomDom,
|
||||||
|
syncUpdates, startUpdate,
|
||||||
|
} from '../renderer/Renderer';
|
||||||
|
import {createPortal} from '../renderer/components/CreatePortal';
|
||||||
|
import {
|
||||||
|
clearContainer, saveContainer,
|
||||||
|
} from './DOMInternalKeys';
|
||||||
|
import type {Container} from './DOMOperator';
|
||||||
|
import {isElement} from './utils/Common';
|
||||||
|
import {listenDelegatedEvents} from '../event/EventBinding';
|
||||||
|
import {findDOMByClassInst} from '../renderer/vnode/VNodeUtils';
|
||||||
|
import {TreeRoot} from '../renderer/vnode/VNodeTags';
|
||||||
|
|
||||||
|
function executeRender(
|
||||||
|
children: any,
|
||||||
|
container: Container,
|
||||||
|
callback?: Function,
|
||||||
|
) {
|
||||||
|
let treeRoot = container._treeRoot;
|
||||||
|
|
||||||
|
if (!treeRoot) {
|
||||||
|
treeRoot = createRoot(children, container, callback);
|
||||||
|
} else { // container被render过
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
const cb = callback;
|
||||||
|
callback = function () {
|
||||||
|
const instance = getFirstCustomDom(treeRoot);
|
||||||
|
cb.call(instance);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 执行更新操作
|
||||||
|
startUpdate(children, treeRoot, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFirstCustomDom(treeRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRoot(children: any, container: Container, callback?: Function) {
|
||||||
|
// 清空容器
|
||||||
|
let child = container.lastChild;
|
||||||
|
while (child) {
|
||||||
|
container.removeChild(child);
|
||||||
|
child = container.lastChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调度器创建根节点,并给容器dom赋vNode结构体
|
||||||
|
const treeRoot = createVNode(TreeRoot, container);
|
||||||
|
saveContainer(container, treeRoot._domRoot);
|
||||||
|
container._treeRoot = treeRoot;
|
||||||
|
|
||||||
|
// 根节点挂接全量事件
|
||||||
|
listenDelegatedEvents(container);
|
||||||
|
|
||||||
|
// 执行回调
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
const cb = callback;
|
||||||
|
callback = function () {
|
||||||
|
const instance = getFirstCustomDom(treeRoot);
|
||||||
|
cb.call(instance);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建VNode树,启动页面绘制
|
||||||
|
syncUpdates(() => {
|
||||||
|
startUpdate(children, treeRoot, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
return treeRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDOMNode(domOrEle: Element): null | Element | Text {
|
||||||
|
if (domOrEle == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通节点
|
||||||
|
if (isElement(domOrEle)) {
|
||||||
|
return domOrEle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// class的实例
|
||||||
|
return findDOMByClassInst(domOrEle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卸载入口
|
||||||
|
function destroy(container: Container) {
|
||||||
|
if (container._treeRoot) {
|
||||||
|
syncUpdates(() => {
|
||||||
|
executeRender(null, container, () => {
|
||||||
|
container._treeRoot = null;
|
||||||
|
clearContainer(container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createPortal,
|
||||||
|
asyncUpdates as unstable_batchedUpdates,
|
||||||
|
findDOMNode,
|
||||||
|
executeRender as render,
|
||||||
|
destroy as unmountComponentAtNode,
|
||||||
|
};
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 文件整体功能:给dom节点赋 VNode 的结构体和事件初始化标记
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {VNode} from '../renderer/Types';
|
||||||
|
import type {
|
||||||
|
Container,
|
||||||
|
Props,
|
||||||
|
} from './DOMOperator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DomComponent,
|
||||||
|
DomText,
|
||||||
|
DomRoot,
|
||||||
|
} from '../renderer/vnode/VNodeTags';
|
||||||
|
|
||||||
|
const suffixKey = new Date().getTime().toString();
|
||||||
|
const prefix = '_horizon';
|
||||||
|
|
||||||
|
const internalKeys = {
|
||||||
|
VNode: `${prefix}VNode@${suffixKey}`,
|
||||||
|
props: `${prefix}Props@${suffixKey}`,
|
||||||
|
container: `${prefix}Container@${suffixKey}`,
|
||||||
|
events: `${prefix}Events@${suffixKey}`,
|
||||||
|
nonDelegatedEvents: `${prefix}NonDelegatedEvents@${suffixKey}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 通过 VNode 实例获取 DOM 节点
|
||||||
|
export function getDom(vNode: VNode): Element | Text | void {
|
||||||
|
const {tag} = vNode;
|
||||||
|
if (tag === DomComponent || tag === DomText) {
|
||||||
|
return vNode.realNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 VNode 属性相关信息挂到 DOM 对象的特定属性上
|
||||||
|
export function saveVNode(
|
||||||
|
vNode: VNode,
|
||||||
|
dom: Element | Text | Container,
|
||||||
|
): void {
|
||||||
|
dom[internalKeys.VNode] = vNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用 DOM 节点,来找其对应的 VNode 实例
|
||||||
|
export function getVNode(dom: Node): VNode | null {
|
||||||
|
const vNode = dom[internalKeys.VNode] || dom[internalKeys.container];
|
||||||
|
if (vNode) {
|
||||||
|
const {tag} = vNode;
|
||||||
|
if (tag === DomComponent || tag === DomText || tag === DomRoot) {
|
||||||
|
return vNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用 DOM 对象,来寻找其对应或者说是最近的 VNode 实例
|
||||||
|
export function getNearestVNode(dom: Node): null | VNode {
|
||||||
|
let vNode = dom[internalKeys.VNode];
|
||||||
|
if (vNode) { // 如果是已经被框架标记过的 DOM 节点,那么直接返回其 VNode 实例
|
||||||
|
return vNode;
|
||||||
|
}
|
||||||
|
// 下面处理的是为被框架标记过的 DOM 节点,向上找其父节点是否被框架标记过
|
||||||
|
let parent = dom.parentNode;
|
||||||
|
let nearVNode = null;
|
||||||
|
while (parent) {
|
||||||
|
vNode = parent[internalKeys.VNode];
|
||||||
|
if (vNode) {
|
||||||
|
nearVNode = vNode;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parent = parent.parentNode;
|
||||||
|
}
|
||||||
|
return nearVNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 vNode 上的属性相关信息
|
||||||
|
export function getVNodeProps(dom: Element | Text): Props {
|
||||||
|
return dom[internalKeys.props] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 DOM 属性相关信息挂到 DOM 对象的特定属性上
|
||||||
|
export function updateVNodeProps(dom: Element | Text, props: Props): void {
|
||||||
|
dom[internalKeys.props] = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEventListeners(dom: EventTarget): Set<string> {
|
||||||
|
let elementListeners = dom[internalKeys.events];
|
||||||
|
if (!elementListeners) {
|
||||||
|
elementListeners = new Set();
|
||||||
|
dom[internalKeys.events] = elementListeners;
|
||||||
|
}
|
||||||
|
return elementListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEventToListenerMap(target: EventTarget): Map<string, EventListener> {
|
||||||
|
let eventsMap = target[internalKeys.nonDelegatedEvents];
|
||||||
|
if (!eventsMap) {
|
||||||
|
eventsMap = target[internalKeys.nonDelegatedEvents] = new Map();
|
||||||
|
}
|
||||||
|
return eventsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveContainer(dom: Container, domRoot: VNode): void {
|
||||||
|
dom[internalKeys.container] = domRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearContainer(dom: Container): void {
|
||||||
|
dom[internalKeys.container] = null;
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
saveVNode,
|
||||||
|
updateVNodeProps,
|
||||||
|
} from './DOMInternalKeys';
|
||||||
|
import {
|
||||||
|
createDom,
|
||||||
|
} from './utils/DomCreator';
|
||||||
|
import {getSelectionInfo, resetSelectionRange, selectionData} from './SelectionRangeHandler';
|
||||||
|
import {isElement, isComment, isDocument, isDocumentFragment} from './utils/Common';
|
||||||
|
import {NSS} from './utils/DomCreator';
|
||||||
|
import {adjustStyleValue} from './DOMPropertiesHandler/StyleHandler';
|
||||||
|
|
||||||
|
import {listenDelegatedEvents} from '../event/EventBinding';
|
||||||
|
import type {VNode} from '../renderer/Types';
|
||||||
|
import {
|
||||||
|
setInitValue,
|
||||||
|
getPropsWithoutValue,
|
||||||
|
updateValue,
|
||||||
|
} from './valueHandler/ValueHandler';
|
||||||
|
import {
|
||||||
|
compareProps,
|
||||||
|
setDomProps, updateDomProps
|
||||||
|
} from './DOMPropertiesHandler/DOMPropertiesHandler';
|
||||||
|
import {isNativeElement, validateProps} from './validators/ValidateProps';
|
||||||
|
import {watchValueChange} from './valueHandler/ValueChangeHandler';
|
||||||
|
import {DomComponent, DomText} from '../renderer/vnode/VNodeTags';
|
||||||
|
import {updateCommonProp} from './DOMPropertiesHandler/UpdateCommonProp';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
autoFocus?: boolean,
|
||||||
|
children?: any,
|
||||||
|
dangerouslySetInnerHTML?: any,
|
||||||
|
disabled?: boolean,
|
||||||
|
hidden?: boolean,
|
||||||
|
style?: { display?: string },
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Container = (Element & { _treeRoot?: VNode }) | (Document & { _treeRoot?: VNode });
|
||||||
|
|
||||||
|
let selectionInfo: null | selectionData = null;
|
||||||
|
|
||||||
|
const types = ['button', 'input', 'select', 'textarea'];
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
if (parent == null || parent === NSS.html) {
|
||||||
|
// 没有父命名空间
|
||||||
|
return Object.keys(NSS).includes(type) ? NSS[type] : NSS.html;
|
||||||
|
}
|
||||||
|
// 默认返回parentNamespace.
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
nextRoot: Container,
|
||||||
|
ctxNamespace: string,
|
||||||
|
type: string): string {
|
||||||
|
let namespace;
|
||||||
|
if (nextRoot) {
|
||||||
|
// 获取并解析根节点容器
|
||||||
|
const root = nextRoot.documentElement;
|
||||||
|
namespace = getRootNS(nextRoot, root, nextRoot);
|
||||||
|
} else {
|
||||||
|
// 获取子节点容器
|
||||||
|
namespace = getChildNS(ctxNamespace, type);
|
||||||
|
}
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareForSubmit(): void {
|
||||||
|
selectionInfo = <selectionData>getSelectionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetAfterSubmit(): void {
|
||||||
|
resetSelectionRange(selectionInfo);
|
||||||
|
selectionInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在内存中创建 DOM 对象
|
||||||
|
* @param tagName 元素的类型
|
||||||
|
* @param props 属性
|
||||||
|
* @param parentNamespace 当前上下文
|
||||||
|
* @param vNode 当前元素对应的 VNode
|
||||||
|
* @returns DOM 对象
|
||||||
|
*/
|
||||||
|
export function newDom(
|
||||||
|
tagName: string,
|
||||||
|
props: Props,
|
||||||
|
parentNamespace: string,
|
||||||
|
vNode: VNode,
|
||||||
|
): Element {
|
||||||
|
const dom: Element = createDom(tagName, props, parentNamespace);
|
||||||
|
// 将 vNode 节点挂到 DOM 对象上
|
||||||
|
saveVNode(vNode, dom);
|
||||||
|
// 将属性挂到 DOM 对象上
|
||||||
|
updateVNodeProps(dom, props);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置节点默认事件、属性
|
||||||
|
export function initDomProps(dom: Element, tagName: string, rawProps: Props): boolean {
|
||||||
|
validateProps(tagName, rawProps);
|
||||||
|
|
||||||
|
// 获取不包括value,defaultValue的属性
|
||||||
|
const props: Object = getPropsWithoutValue(tagName, dom, rawProps);
|
||||||
|
|
||||||
|
// 初始化DOM属性(不包括value,defaultValue)
|
||||||
|
setDomProps(tagName, dom, props);
|
||||||
|
|
||||||
|
if (tagName === 'input' || tagName === 'textarea') {
|
||||||
|
// 增加监听value和checked的set、get方法
|
||||||
|
watchValueChange(dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置dom.value值,触发受控组件的set方法
|
||||||
|
setInitValue(tagName, dom, rawProps);
|
||||||
|
|
||||||
|
return shouldAutoFocus(tagName, rawProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备更新之前进行一系列校验 DOM,寻找属性差异等准备工作
|
||||||
|
export function getPropChangeList(
|
||||||
|
dom: Element,
|
||||||
|
type: string,
|
||||||
|
lastRawProps: Props,
|
||||||
|
nextRawProps: Props,
|
||||||
|
): null | Array<any> {
|
||||||
|
// 校验两个对象的不同
|
||||||
|
validateProps(type, nextRawProps);
|
||||||
|
|
||||||
|
// 重新定义的属性不需要参与对比,被代理的组件需要把这些属性覆盖到props中
|
||||||
|
const oldProps: Object = getPropsWithoutValue(type, dom, lastRawProps);
|
||||||
|
const newProps: Object = getPropsWithoutValue(type, dom, nextRawProps);
|
||||||
|
|
||||||
|
const changeList = compareProps(oldProps, newProps);
|
||||||
|
return changeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTextChild(type: string, props: Props): boolean {
|
||||||
|
if (type === 'textarea') {
|
||||||
|
return true;
|
||||||
|
} else if (type === 'option') {
|
||||||
|
return true;
|
||||||
|
} else if (type === 'noscript') {
|
||||||
|
return true;
|
||||||
|
} else if (typeof props.children === 'string') {
|
||||||
|
return true;
|
||||||
|
} else if (typeof props.children === 'number') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
props.dangerouslySetInnerHTML &&
|
||||||
|
typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||||
|
props.dangerouslySetInnerHTML.__html != null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newTextDom(
|
||||||
|
text: string,
|
||||||
|
processing: VNode,
|
||||||
|
): Text {
|
||||||
|
const textNode: Text = document.createTextNode(text);
|
||||||
|
saveVNode(processing, 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的更新
|
||||||
|
export function submitDomUpdate(tag: number, vNode: VNode) {
|
||||||
|
const newProps = vNode.props;
|
||||||
|
const element: Element = vNode.realNode;
|
||||||
|
|
||||||
|
if (tag === DomComponent) {
|
||||||
|
// DomComponent类型
|
||||||
|
if (element != null) {
|
||||||
|
const type = vNode.type;
|
||||||
|
const changeList = vNode.changeList;
|
||||||
|
vNode.changeList = null;
|
||||||
|
if (changeList !== null) {
|
||||||
|
saveVNode(vNode, element);
|
||||||
|
updateVNodeProps(element, newProps);
|
||||||
|
// 应用diff更新Properties.
|
||||||
|
// 当一个选中的radio改变名称,浏览器使另一个radio的复选框为false.
|
||||||
|
if (type === 'input' && newProps.type === 'radio' && newProps.name != null && newProps.checked != null) {
|
||||||
|
updateCommonProp(element, 'checked', newProps.checked);
|
||||||
|
}
|
||||||
|
const isNativeTag = isNativeElement(type, newProps);
|
||||||
|
updateDomProps(element, changeList, isNativeTag);
|
||||||
|
updateValue(type, element, newProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (tag === DomText) {
|
||||||
|
// text类型
|
||||||
|
element.textContent = newProps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearText(dom: Element): void {
|
||||||
|
dom.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加child元素
|
||||||
|
export function appendChildElement(isContainer: boolean,
|
||||||
|
parent: Element | Container,
|
||||||
|
child: Element | Text): void {
|
||||||
|
if (isContainer && isComment(parent)) {
|
||||||
|
parent.parentNode.insertBefore(child, parent);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入dom元素
|
||||||
|
export function insertDomBefore(
|
||||||
|
isContainer: boolean,
|
||||||
|
parent: Element | Container,
|
||||||
|
child: Element | Text,
|
||||||
|
beforeChild: Element | Text,
|
||||||
|
) {
|
||||||
|
if (isContainer && isComment(parent)) {
|
||||||
|
parent.parentNode.insertBefore(child, beforeChild);
|
||||||
|
} else {
|
||||||
|
parent.insertBefore(child, beforeChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeChildDom(
|
||||||
|
isContainer: boolean,
|
||||||
|
parent: Element | Container,
|
||||||
|
child: Element | Text
|
||||||
|
) {
|
||||||
|
if (isContainer && isComment(parent)) {
|
||||||
|
parent.parentNode.removeChild(child);
|
||||||
|
} else {
|
||||||
|
parent.removeChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏元素
|
||||||
|
export function hideDom(tag: number, element: Element | Text) {
|
||||||
|
if (tag === DomComponent) {
|
||||||
|
// DomComponent类型
|
||||||
|
const {style} = element;
|
||||||
|
if (style.setProperty && typeof style.setProperty === 'function') {
|
||||||
|
style.setProperty('display', 'none', 'important');
|
||||||
|
} else {
|
||||||
|
style.display = 'none';
|
||||||
|
}
|
||||||
|
} else if (tag === DomText) {
|
||||||
|
// text类型
|
||||||
|
element.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不隐藏元素
|
||||||
|
export function unHideDom(tag: number, element: Element | Text, props: Props) {
|
||||||
|
if (tag === DomComponent) {
|
||||||
|
// DomComponent类型
|
||||||
|
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) {
|
||||||
|
// text类型
|
||||||
|
element.textContent = props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearContainer(container: Container): void {
|
||||||
|
if (isElement(container)) {
|
||||||
|
container.textContent = '';
|
||||||
|
}
|
||||||
|
if (isDocument(container) && container.body != null) {
|
||||||
|
container.body.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prePortal(portal: Element): void {
|
||||||
|
listenDelegatedEvents(portal);
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
import {
|
||||||
|
allDelegatedHorizonEvents,
|
||||||
|
} from '../../event/EventCollection';
|
||||||
|
import {updateCommonProp} from './UpdateCommonProp';
|
||||||
|
import {setStyles} from './StyleHandler';
|
||||||
|
import {
|
||||||
|
listenNonDelegatedEvent
|
||||||
|
} from '../../event/EventBinding';
|
||||||
|
import {isEventProp, isNativeElement} from '../validators/ValidateProps';
|
||||||
|
|
||||||
|
// 初始化DOM属性
|
||||||
|
export function setDomProps(
|
||||||
|
tagName: string,
|
||||||
|
dom: Element,
|
||||||
|
props: Object,
|
||||||
|
): void {
|
||||||
|
const isNativeTag = isNativeElement(tagName, props);
|
||||||
|
const keysOfProps = Object.keys(props);
|
||||||
|
|
||||||
|
for (let i = 0; i < keysOfProps.length; i++) {
|
||||||
|
const propName = keysOfProps[i];
|
||||||
|
const propVal = props[propName];
|
||||||
|
|
||||||
|
updateOneProp(dom, propName, propVal, isNativeTag, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 DOM 属性
|
||||||
|
export function updateDomProps(
|
||||||
|
dom: Element,
|
||||||
|
changeList: Array<any>,
|
||||||
|
isNativeTag: boolean,
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < changeList.length; i ++) {
|
||||||
|
const {propName, propVal} = changeList[i];
|
||||||
|
|
||||||
|
updateOneProp(dom, propName, propVal, isNativeTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOneProp(dom, propName, propVal, isNativeTag, isInit = false) {
|
||||||
|
if (propName === 'style') {
|
||||||
|
setStyles(dom, propVal);
|
||||||
|
} else if (propName === 'dangerouslySetInnerHTML') {
|
||||||
|
dom.innerHTML = propVal.__html;
|
||||||
|
} else if (propName === 'children') { // 只处理纯文本子节点,其他children在VNode树中处理
|
||||||
|
if (typeof propVal === 'string' || typeof propVal === 'number') {
|
||||||
|
dom.textContent = String(propVal);
|
||||||
|
}
|
||||||
|
} else if (isEventProp(propName)) {
|
||||||
|
// 事件监听属性处理
|
||||||
|
if (!allDelegatedHorizonEvents.has(propName)) {
|
||||||
|
listenNonDelegatedEvent(propName, dom, propVal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isInit || (isInit && propVal != null)) {
|
||||||
|
updateCommonProp(dom, propName, propVal, isNativeTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找出两个 DOM 属性的差别,生成需要更新的属性集合
|
||||||
|
export function compareProps(
|
||||||
|
oldProps: Object,
|
||||||
|
newProps: Object,
|
||||||
|
): null | Array<any> {
|
||||||
|
let updatesForStyle = {};
|
||||||
|
const toBeDeletedProps = [];
|
||||||
|
const toBeUpdatedProps = [];
|
||||||
|
const keysOfOldProps = Object.keys(oldProps);
|
||||||
|
const keysOfNewProps = Object.keys(newProps);
|
||||||
|
|
||||||
|
// 找到旧属性中需要删除的属性
|
||||||
|
for (let i = 0; i < keysOfOldProps.length; i++) {
|
||||||
|
const propName = keysOfOldProps[i];
|
||||||
|
// 新属性中包含该属性或者该属性为空值的属性不需要处理
|
||||||
|
if (keysOfNewProps.includes(propName) || oldProps[propName] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propName === 'style') {
|
||||||
|
const oldStyle = oldProps[propName];
|
||||||
|
const styleProps = Object.keys(oldStyle);
|
||||||
|
for (let j = 0; j < styleProps.length; j++) {
|
||||||
|
const styleProp = styleProps[j];
|
||||||
|
updatesForStyle[styleProp] = '';
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
propName === 'autoFocus' ||
|
||||||
|
propName === 'children' ||
|
||||||
|
propName === 'dangerouslySetInnerHTML'
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
} else if (isEventProp(propName)) {
|
||||||
|
if (!allDelegatedHorizonEvents.has(propName)) {
|
||||||
|
toBeDeletedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 其它属性都要加入到删除队列里面,等待删除
|
||||||
|
toBeDeletedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历新属性,获取新增和变更属性
|
||||||
|
for (let i = 0; i < keysOfNewProps.length; i++) {
|
||||||
|
const propName = keysOfNewProps[i];
|
||||||
|
const newPropValue = newProps[propName];
|
||||||
|
const oldPropValue = oldProps != null ? oldProps[propName] : undefined;
|
||||||
|
|
||||||
|
if (newPropValue === oldPropValue || (newPropValue == null && oldPropValue == null)) {
|
||||||
|
// 新旧属性值未发生变化,或者新旧属性皆为空值,不需要进行处理
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propName === 'style') {
|
||||||
|
if (oldPropValue) { // 之前 style 属性有设置非空值
|
||||||
|
// 原来有这个 style,但现在没这个 style 了
|
||||||
|
const oldStyleProps = Object.keys(oldPropValue);
|
||||||
|
for (let j = 0; j < oldStyleProps.length; j++) {
|
||||||
|
const styleProp = oldStyleProps[j];
|
||||||
|
if (!newPropValue || !newPropValue.hasOwnProperty(styleProp)) {
|
||||||
|
updatesForStyle[styleProp] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 现在有这个 style,但是和原来不相等
|
||||||
|
const newStyleProps = newPropValue ? Object.keys(newPropValue) : [];
|
||||||
|
for (let j = 0; j < newStyleProps.length; j++) {
|
||||||
|
const styleProp = newStyleProps[j];
|
||||||
|
if (oldPropValue[styleProp] !== newPropValue[styleProp]) {
|
||||||
|
updatesForStyle[styleProp] = newPropValue[styleProp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // 之前未设置 style 属性或者设置了空值
|
||||||
|
if (Object.keys(updatesForStyle).length === 0) {
|
||||||
|
toBeUpdatedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updatesForStyle = newPropValue;
|
||||||
|
}
|
||||||
|
} else if (propName === 'dangerouslySetInnerHTML') {
|
||||||
|
const newHTML = newPropValue ? newPropValue.__html : undefined;
|
||||||
|
const oldHTML = oldPropValue ? oldPropValue.__html : undefined;
|
||||||
|
if (newHTML != null) {
|
||||||
|
if (oldHTML !== newHTML) {
|
||||||
|
toBeUpdatedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: newPropValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (propName === 'children') {
|
||||||
|
if (typeof newPropValue === 'string' || typeof newPropValue === 'number') {
|
||||||
|
toBeUpdatedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: String(newPropValue),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (isEventProp(propName)) {
|
||||||
|
if (!allDelegatedHorizonEvents.has(propName)) {
|
||||||
|
toBeUpdatedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: newPropValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toBeUpdatedProps.push({
|
||||||
|
propName,
|
||||||
|
propVal: newPropValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理style
|
||||||
|
if (Object.keys(updatesForStyle).length > 0) {
|
||||||
|
toBeUpdatedProps.push({
|
||||||
|
propName: 'style',
|
||||||
|
propVal: updatesForStyle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...toBeDeletedProps, ...toBeUpdatedProps];
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* 设置 DOM 节点的 style 属性
|
||||||
|
*/
|
||||||
|
export function setStyles(dom, styles) {
|
||||||
|
if (!styles) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = dom.style;
|
||||||
|
const styleKeys = Object.keys(styles);
|
||||||
|
|
||||||
|
for (let i = 0; i < styleKeys.length; i++) {
|
||||||
|
const styleKey = styleKeys[i];
|
||||||
|
const styleVal = styles[styleKey];
|
||||||
|
|
||||||
|
const validStyleValue = adjustStyleValue(styleKey, styleVal);
|
||||||
|
|
||||||
|
style[styleKey] = validStyleValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. 对空值或布尔值进行适配,转为空字符串
|
||||||
|
* 2. 去掉多余空字符
|
||||||
|
*/
|
||||||
|
export function adjustStyleValue(name, value) {
|
||||||
|
let validValue;
|
||||||
|
|
||||||
|
if (value === '' || value == null || typeof value === 'boolean') {
|
||||||
|
validValue = '';
|
||||||
|
} else {
|
||||||
|
validValue = String(value).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return validValue;
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
getPropDetails,
|
||||||
|
PROPERTY_TYPE,
|
||||||
|
} from '../validators/PropertiesData';
|
||||||
|
import {isInvalidValue} from '../validators/ValidateProps';
|
||||||
|
import {getNamespaceCtx} from '../../renderer/ContextSaver';
|
||||||
|
import {NSS} from '../utils/DomCreator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给 dom 设置属性
|
||||||
|
* attrName 指代码中属性设置的属性名称(如 class)
|
||||||
|
* 多数情况 attrName 仅用作初始 DOM 节点对象使用,而 property 更多用于页面交互
|
||||||
|
*/
|
||||||
|
export function updateCommonProp(dom: Element, attrName: string, value: any, isNativeTag: boolean = true) {
|
||||||
|
const propDetails = getPropDetails(attrName);
|
||||||
|
|
||||||
|
if (isInvalidValue(attrName, value, propDetails, isNativeTag)) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNativeTag || propDetails === null) {
|
||||||
|
// 特殊处理svg的属性,把驼峰式的属性名称转成'-'
|
||||||
|
if (dom.tagName.toLowerCase() === 'svg' || getNamespaceCtx() === NSS.svg) {
|
||||||
|
attrName = convertToLowerCase(attrName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
dom.removeAttribute(attrName);
|
||||||
|
} else {
|
||||||
|
dom.setAttribute(attrName, String(value));
|
||||||
|
}
|
||||||
|
} else if (['checked', 'multiple', 'muted', 'selected'].includes(propDetails.attrName)) {
|
||||||
|
if (value === null) { // 必填属性设置默认值
|
||||||
|
dom[propDetails.attrName] = false;
|
||||||
|
} else {
|
||||||
|
dom[propDetails.attrName] = value;
|
||||||
|
}
|
||||||
|
} else { // 处理其他普通属性
|
||||||
|
if (value === null) {
|
||||||
|
dom.removeAttribute(propDetails.attrName);
|
||||||
|
} else {
|
||||||
|
const {type, attrNS} = propDetails; // 数据类型、固有属性命名空间
|
||||||
|
const attributeName = propDetails.attrName; // 固有属性名
|
||||||
|
let attributeValue;
|
||||||
|
if (type === PROPERTY_TYPE.BOOLEAN) { // 即可以用作标志又可以是属性值的属性
|
||||||
|
attributeValue = '';
|
||||||
|
} else {
|
||||||
|
attributeValue = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrNS) {
|
||||||
|
dom.setAttributeNS(attrNS, attributeName, attributeValue);
|
||||||
|
} else {
|
||||||
|
dom.setAttribute(attributeName, attributeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驼峰 变 “-”
|
||||||
|
function convertToLowerCase(str) {
|
||||||
|
const replacer = (match, char) => {
|
||||||
|
return `-${char.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.replace(/([A-Z])/g, replacer);
|
||||||
|
};
|
|
@ -0,0 +1,163 @@
|
||||||
|
/**
|
||||||
|
* 处理文本框、输入框中框选范围内的数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getIFrameFocusedDom, isText} from './utils/Common';
|
||||||
|
|
||||||
|
import {isElement} from './utils/Common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置聚焦的 textarea 或 input 节点的选择范围
|
||||||
|
* @param dom 需要设置选择范围的 input 或 textarea
|
||||||
|
* @param range 选择范围对象
|
||||||
|
*/
|
||||||
|
function setSelectionRange(dom: HTMLInputElement | HTMLTextAreaElement, range) {
|
||||||
|
const { start, end } = range;
|
||||||
|
let realEnd = end;
|
||||||
|
|
||||||
|
if (realEnd == null) {
|
||||||
|
realEnd = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof dom.setSelectionRange === 'function') {
|
||||||
|
dom.setSelectionRange(start, realEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文本框、输入框中选中的文本的范围
|
||||||
|
* @param dom 需要设置选择范围的 input 或 textarea
|
||||||
|
* @return {start: selectionStart, end: selectionEnd}
|
||||||
|
*/
|
||||||
|
function getSelectionRange(dom: Element | HTMLInputElement | HTMLTextAreaElement | void) {
|
||||||
|
const selectionRange = { start: 0, end: 0 };
|
||||||
|
|
||||||
|
if (!dom) {
|
||||||
|
return selectionRange;
|
||||||
|
}
|
||||||
|
if ('selectionStart' in dom) {
|
||||||
|
// 现代浏览器的 input 或 textarea 有 selectionStart 属性.
|
||||||
|
selectionRange.start = dom.selectionStart;
|
||||||
|
selectionRange.end = dom.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectionRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断第一个节点和另一个节点是否是包含关系
|
||||||
|
function isNodeContainsByTargetNode(targetNode, node) {
|
||||||
|
if (!targetNode || !node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (targetNode === node) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isText(targetNode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isText(node)) {
|
||||||
|
return isNodeContainsByTargetNode(targetNode, node.parentNode);
|
||||||
|
}
|
||||||
|
if (typeof targetNode.contains === 'function') {
|
||||||
|
return targetNode.contains(node); // 该的节点是否为目标节点的后代节点
|
||||||
|
}
|
||||||
|
if (typeof targetNode.compareDocumentPosition === 'function') { // compareDocumentPosition 数值,表示两个节点彼此做比较的位置
|
||||||
|
const CONTAINS_CODE = 16;
|
||||||
|
// 返回 16 代表 第二节点在第一节点内部
|
||||||
|
return targetNode.compareDocumentPosition(node) === CONTAINS_CODE;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInDocument(dom) {
|
||||||
|
if (dom && dom.ownerDocument) {
|
||||||
|
return isNodeContainsByTargetNode(dom.ownerDocument.documentElement, dom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断一个标签是否有设置选择范围的能力
|
||||||
|
export function hasSelectionProperties(dom) {
|
||||||
|
let elementType;
|
||||||
|
|
||||||
|
if (dom && dom.nodeName) {
|
||||||
|
elementType = dom.nodeName.toLowerCase();
|
||||||
|
const validInputType = ['text', 'search', 'tel', 'url', 'password'];
|
||||||
|
|
||||||
|
if (elementType === 'input') {
|
||||||
|
return validInputType.includes(dom.type);
|
||||||
|
} else if (elementType === 'textarea') {
|
||||||
|
return dom.contentEditable === 'true';
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回当前 focus 的元素以及其选中的范围
|
||||||
|
export function getSelectionInfo() {
|
||||||
|
const focusedDom = getIFrameFocusedDom();
|
||||||
|
return {
|
||||||
|
focusedDom,
|
||||||
|
selectionRange: hasSelectionProperties(focusedDom) ? getSelectionRange(focusedDom) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface selectionData {
|
||||||
|
focusedDom: HTMLInputElement | HTMLTextAreaElement | void;
|
||||||
|
selectionRange: {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 防止选择范围内的信息因为节点删除或其他原因导致的信息丢失
|
||||||
|
export function resetSelectionRange(preSelectionRangeData: selectionData) {
|
||||||
|
// 当前 focus 的元素
|
||||||
|
const currentFocusedDom = getIFrameFocusedDom();
|
||||||
|
|
||||||
|
// 先前 focus 的元素
|
||||||
|
const preFocusedDom = preSelectionRangeData.focusedDom;
|
||||||
|
|
||||||
|
if (!preFocusedDom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先前的选择范围信息
|
||||||
|
const preSelectionRange = preSelectionRangeData.selectionRange;
|
||||||
|
|
||||||
|
if (currentFocusedDom !== preFocusedDom && isInDocument(preFocusedDom)) {
|
||||||
|
if (preSelectionRange !== null) {
|
||||||
|
setSelectionRange(preFocusedDom, preSelectionRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动条位置可能会因为一个节点的选中变化位置,需要做处理
|
||||||
|
const ancestors = [];
|
||||||
|
let ancestor = preFocusedDom.parentNode;
|
||||||
|
// 查找先前的 focus 节点的先祖
|
||||||
|
while (ancestor) {
|
||||||
|
if (isElement(ancestor)) { // 是元素节点,就把先祖信息放到先祖数组中
|
||||||
|
// @ts-ignore
|
||||||
|
const {scrollLeft, scrollTop} = ancestor;
|
||||||
|
ancestors.push({
|
||||||
|
dom: ancestor,
|
||||||
|
scrollLeft,
|
||||||
|
scrollTop,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ancestor = ancestor.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行先前 focus 节点的 focus 方法
|
||||||
|
if (typeof preFocusedDom.focus === 'function') {
|
||||||
|
preFocusedDom.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestors.forEach(ancestorInfo => {
|
||||||
|
const ancestorDom = ancestorInfo.dom;
|
||||||
|
ancestorDom.scrollLeft = ancestorInfo.scrollLeft;
|
||||||
|
ancestorDom.scrollTop = ancestorInfo.scrollTop;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue