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