UNPKG

@es-react/react

Version:

Hippy react framework

218 lines (196 loc) 6.33 kB
/* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable no-underscore-dangle */ /* eslint-disable no-param-reassign */ import { insertChild, removeChild, moveChild } from '../renderer/render'; import { relativeToRefType } from '../utils/node'; let currentNodeId = 0; function getNodeId() { currentNodeId += 1; // currentNodeId % 10 === 0 is rootView // It's a limitation of iOS SDK. if (currentNodeId % 10 === 0) { currentNodeId += 1; } return currentNodeId; } interface NodeMeta { skipAddToDom?: boolean; component: { name?: string; skipAddToDom?: boolean; }; } class ViewNode { public nodeId: number; // Component meta information, such as native component will use. public meta: NodeMeta = { component: {}, }; // Index number in children, will update at traverseChildren method. public index = 0; // Relation nodes. public childNodes: ViewNode[] = []; public parentNode: ViewNode | null = null; // Will change to be true after insert into Native dom. private mounted = false; public constructor() { // Virtual DOM node id, will used in native to identify. this.nodeId = getNodeId(); } /* istanbul ignore next */ public toString() { return this.constructor.name; } public get isMounted() { return this.mounted; } public set isMounted(isMounted: boolean) { // TODO: Maybe need validation, maybe not. this.mounted = isMounted; } public insertBefore(childNode: ViewNode, referenceNode: ViewNode) { if (!childNode) { throw new Error('Can\'t insert child.'); } if (childNode.meta.skipAddToDom) { return; } if (!referenceNode) { return this.appendChild(childNode); } if (referenceNode.parentNode !== this) { throw new Error('Can\'t insert child, because the reference node has a different parent.'); } if (childNode.parentNode && childNode.parentNode !== this) { throw new Error('Can\'t insert child, because it already has a different parent.'); } const index = this.childNodes.indexOf(referenceNode); childNode.parentNode = this; this.childNodes.splice(index, 0, childNode); return insertChild( this, childNode, { refId: referenceNode.nodeId, relativeToRef: relativeToRefType.BEFORE }, ); } public moveChild(childNode: ViewNode, referenceNode: ViewNode) { if (!childNode) { throw new Error('Can\'t move child.'); } if (childNode.meta.skipAddToDom) { return; } if (!referenceNode) { return this.appendChild(childNode); } if (referenceNode.parentNode !== this) { throw new Error('Can\'t move child, because the reference node has a different parent.'); } if (childNode.parentNode && childNode.parentNode !== this) { throw new Error('Can\'t move child, because it already has a different parent.'); } const oldIndex = this.childNodes.indexOf(childNode); const referenceIndex = this.childNodes.indexOf(referenceNode); // return if the moved index is the same as the previous one if (referenceIndex === oldIndex) { return childNode; } this.childNodes.splice(oldIndex, 1); const newIndex = this.childNodes.indexOf(referenceNode); this.childNodes.splice(newIndex, 0, childNode); return moveChild( this, childNode, { refId: referenceNode.nodeId, relativeToRef: relativeToRefType.BEFORE }, ); } public appendChild(childNode: ViewNode) { if (!childNode) { throw new Error('Can\'t append child.'); } if (childNode.meta.skipAddToDom) { return; } if (childNode.parentNode && childNode.parentNode !== this) { throw new Error('Can\'t append child, because it already has a different parent.'); } childNode.parentNode = this; const referenceIndex = this.childNodes.length - 1; const referenceNode = this.childNodes[referenceIndex]; this.childNodes.push(childNode); insertChild( this, childNode, referenceNode && { refId: referenceNode.nodeId, relativeToRef: relativeToRefType.AFTER }, ); } public removeChild(childNode: ViewNode) { if (!childNode) { throw new Error('Can\'t remove child.'); } if (childNode.meta.skipAddToDom) { return; } if (!childNode.parentNode) { throw new Error('Can\'t remove child, because it has no parent.'); } if (childNode.parentNode !== this) { throw new Error('Can\'t remove child, because it has a different parent.'); } const index = this.childNodes.indexOf(childNode); this.childNodes.splice(index, 1); removeChild(this, childNode); } /** * Find a specific target with condition */ public findChild(condition: Function): ViewNode | null { const yes = condition(this); if (yes) { return this; } if (this.childNodes.length) { for (let i = 0; i < this.childNodes.length; i += 1) { const childNode = this.childNodes[i]; const targetChild = this.findChild.call(childNode, condition); if (targetChild) { return targetChild; } } } return null; } /** * Traverse the children and execute callback * @param callback - callback function * @param refInfo - reference node info */ public traverseChildren(callback: Function, refInfo) { callback(this, refInfo); // Find the children if (this.childNodes.length) { this.childNodes.forEach((childNode) => { this.traverseChildren.call(childNode, callback, {}); }); } } } export default ViewNode;