Match-id-967e808cdb47df39560696171de0c8f743a70f84

This commit is contained in:
* 2021-12-22 20:00:43 +08:00 committed by *
parent c2f173dd60
commit f7f9956ddc
7 changed files with 1007 additions and 0 deletions

View File

@ -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,
};

View File

@ -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;
}

View File

@ -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);
// 获取不包括valuedefaultValue的属性
const props: Object = getPropsWithoutValue(tagName, dom, rawProps);
// 初始化DOM属性不包括valuedefaultValue
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);
}

View File

@ -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];
}

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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;
});
}
}