/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /* * 2021.01.08 - Move element's method from operation.js to Element class. * Copyright (c) 2021 Huawei Device Co., Ltd. */ import { Log } from '../utils/index'; import Node from './Node'; import NativeElementClassFactory from './NativeElementClassFactory'; import Document from './Document'; import { TaskCenter } from '../main/manage/event/TaskCenter'; import { FragBlockInterface, TemplateInterface, compileCustomComponent, targetIsComposed } from '../main/model/compiler'; import Vm from '../main/model'; import { CSS_INHERITANCE } from '../main/app/bundle'; import {interceptCallback} from '../main/manage/event/callbackIntercept'; import {mockWebgl} from '../main/extend/systemplugin/napi/webgl/webgl'; import {mockWebgl2} from '../main/extend/systemplugin/napi/webgl/webgl2'; import { VmOptions } from '../main/model/vmOptions'; /** * Element is a basic class to describe a tree node in vdom. * @extends Node */ class Element extends Node { private _style: any; private _classStyle: any; private _event: any; private _idStyle: any; private _tagStyle: any; private _attrStyle: any; private _tagAndTagStyle: any; private _firstOrLastChildStyle: any; private _universalStyle: any; private _id: string | null; private _classList: any[]; private _block: FragBlockInterface; private _vm: Vm; private _isCustomComponent: boolean; private _inheritedStyle: object; private _target:TemplateInterface; private _hasBefore: boolean; private _hasAfter: boolean; private _isOpen: boolean; protected _children: Node[]; protected _pureChildren: Element[]; protected _role: string; protected _attr: any; protected _dataSet: any; protected _isFirstDyanmicName: boolean; constructor(type = 'div', props:any = {}, isExtended: boolean = false) { super(); const NativeElementClass = NativeElementClassFactory.nativeElementClassMap.get(type); if (NativeElementClass && !isExtended) { if (global.pcPreview && type === 'canvas') { Object.defineProperty(NativeElementClass.prototype, 'getContext', { configurable: true, enumerable: true, get: function moduleGetter() { return (...args: any) => { const taskCenter = this.getTaskCenter(this.docId); if (taskCenter) { // support aceapp callback style args = interceptCallback(args); if (args[0] === 'webgl') { return mockWebgl(); } else if (args[0] === 'webgl2') { return mockWebgl2(); } const ret = taskCenter.send('component', { ref: this.ref, component: type, method: 'getContext' }, args); return ret; } }; } }); } return new NativeElementClass(props); } this._nodeType = Node.NodeType.Element; this._type = type; this._attr = props.attr || {}; this._style = props.style || {}; this._classStyle = props.classStyle || {}; this._event = {}; this._idStyle = {}; this._tagStyle = {}; this._attrStyle = {}; this._tagAndTagStyle = {}; this._firstOrLastChildStyle = {}; this._universalStyle = {}; this._id = null; this._classList = []; this._children = []; this._pureChildren = []; this._isCustomComponent = false; this._inheritedStyle = {}; this._dataSet = {}; this._isFirstDyanmicName = true; } /** * inherit sytle from parent * @type {Object} */ public set inheritedStyle(inheritedStyle: object) { this._inheritedStyle = inheritedStyle; } public get inheritedStyle() { return this._inheritedStyle; } /** * Children array except comment node. * @type {Node[]} */ public set pureChildren(newPureChildren: Element[]) { this._pureChildren = newPureChildren; } public get pureChildren() { return this._pureChildren; } /** * event of element * @type {Node[]} */ public get event() { return this._event; } /** * Children array. * @type {Node[]} */ public set children(newChildren: Node[]) { this._children = newChildren; } public get children() { return this._children; } /** * View model of this Element. * @type {Vm} */ public get vm() { return this._vm; } public set vm(newVm: Vm) { this._vm = newVm; } public get hasBefore() { return this._hasBefore; } public set hasBefore(hasBefore: boolean) { this._hasBefore = hasBefore; } public get hasAfter() { return this._hasAfter; } public set hasAfter(hasAfter: boolean) { this._hasAfter = hasAfter; } public get isOpen() { return this._isOpen; } public set isOpen(isOpen: boolean) { this._isOpen = isOpen; } /** * Class style object of this Element, which keys is style name, and values is style values. * @type {JSON} */ public get classStyle() { return this._classStyle; } /** * Id style object of this Element, which keys is style name, and values is style values. * @type {JSON} */ public get idStyle() { return this._idStyle; } /** * Block in this Element. * @type {FragBlock} */ public get block() { return this._block; } public set block(newBlock: FragBlockInterface) { this._block = newBlock; } /** * Role of this element. * @type {string} */ public set role(role: string) { this._role = role; } public get role() { return this._role; } /** * ID of this element. * @type {string} */ public get id() { return this._id; } public set id(value) { this._id = value; } /** * Class list of this element. * @type {string[]} */ public get classList() { return this._classList; } public set classList(value: string[]) { this._classList = value.slice(0); } /** * Attributes object of this Element. * @type {Object} */ public set attr(attr: any) { this._attr = attr; } public get attr() { return this._attr; } /** * DataSet object of this Element. * @type {Object} */ public set dataSet(dataSet: any) { this._dataSet = dataSet; } public get dataSet() { return this._dataSet; } /** * Flag of whether the element is the root of customeComponent. * @param {bollean} */ public set isCustomComponent(isCustomComponent: boolean) { this._isCustomComponent = isCustomComponent; } public get isCustomComponent() { return this._isCustomComponent; } /** * Style object of this Element. * @type {Object} */ public set style(style: any) { this._style = style; } public get style() { return this._style; } /** * target object of this Element. * @type {Object} */ public set target(target: TemplateInterface) { this._target = target; } public get target() { return this._target; } /** * Get TaskCenter instance by id. * @param {string} id * @return {TaskCenter} TaskCenter */ public getTaskCenter(id: string): TaskCenter | null { const doc: Document = this._ownerDocument; if (doc && doc.taskCenter) { return doc.taskCenter; } return null; } /** * Establish the connection between parent and child node. * @param {Node} child - Target node. */ public linkChild(child: Node): void { child.parentNode = this; if (this._docId) { child.docId = this._docId; child.ownerDocument = this._ownerDocument; if (child.ownerDocument) { child.ownerDocument.nodeMap[child.nodeId] = child; } child.depth = this._depth + 1; } if (child.nodeType === Node.NodeType.Element) { const element = child as Element; element.children.forEach((grandChild: Node) => { element.linkChild(grandChild); }); } } /** * Insert a node into list at the specified index. * @param {Node} target - Target node. * @param {number} newIndex - Target index. * @param {Object} [options] - options of insert method. * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. * @return {number} New index of node. */ public insertIndex(target: Node, newIndex: number, { changeSibling = false, isInPureChildren = false }): number { const list: Node[] = isInPureChildren ? this._pureChildren : this._children; if (newIndex < 0) { newIndex = 0; } const before: Node = list[newIndex - 1]; const after: Node = list[newIndex]; list.splice(newIndex, 0, target); if (changeSibling) { before && (before.nextSibling = target); target.previousSibling = before; target.nextSibling = after; after && (after.previousSibling = target); } return newIndex; } /** * Move the node to a new index in list. * @param {Node} target - Target node. * @param {number} newIndex - Target index. * @param {Object} [options] - options of insert method. * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. * @return {number} New index of node. */ public moveIndex(target: Node, newIndex: number, { changeSibling = false, isInPureChildren = false }): number { const list: Node[] = isInPureChildren ? this._pureChildren : this._children; const index: number = list.indexOf(target); if (index < 0) { return -1; } if (changeSibling) { const before: Node = list[index - 1]; const after: Node = list[index + 1]; before && (before.nextSibling = after); after && (after.previousSibling = before); } list.splice(index, 1); let newIndexAfter = newIndex; if (index <= newIndex) { newIndexAfter = newIndex - 1; } const beforeNew: Node = list[newIndexAfter - 1]; const afterNew: Node = list[newIndexAfter]; list.splice(newIndexAfter, 0, target); if (changeSibling) { if (beforeNew) { beforeNew.nextSibling = target; } target.previousSibling = beforeNew; target.nextSibling = afterNew; if (afterNew) { afterNew.previousSibling = target; } } if (index === newIndexAfter) { return -1; } return newIndex; } /** * Remove the node from list. * @param {Node} target - Target node. * @param {Object} [options] - options of insert method. * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. */ public removeIndex(target, { changeSibling = false, isInPureChildren = false}): void { const list: Node[] = isInPureChildren ? this._pureChildren : this._children; const index: number = list.indexOf(target); if (index < 0) { return; } if (changeSibling) { const before: Node = list[index - 1]; const after: Node = list[index + 1]; before && (before.nextSibling = after); after && (after.previousSibling = before); } list.splice(index, 1); } /** * Get the next sibling element. * @param {Node} node - Target node. * @return {Node} Next node of target node. */ public nextElement(node: Node): Element { while (node) { if (node.nodeType === Node.NodeType.Element) { return node as Element; } node = node.nextSibling; } } /** * Get the previous sibling element. * @param {Node} node - Target node. * @return {Node} Previous node of target node. */ public previousElement(node: Node): Element { while (node) { if (node.nodeType === Node.NodeType.Element) { return node as Element; } node = node.previousSibling; } } /** * Append a child node. * @param {Node} node - Target node. * @return {number} the signal sent by native */ public appendChild(node: Node): void { if (node.parentNode && node.parentNode !== this) { return; } if (!node.parentNode) { this.linkChild(node as Element); this.insertIndex(node, this.children.length, { changeSibling: true }); if (this.docId) { this.registerNode(node); } if (node.nodeType === Node.NodeType.Element) { const element = node as Element; this.insertIndex(element, this.pureChildren.length, { isInPureChildren: true }); this.inheritStyle(node, true); const taskCenter = this.getTaskCenter(this.docId); if (taskCenter) { return taskCenter.send( 'dom', { action: 'addElement' }, [this.ref, element.toJSON(), -1] ); } } } else { this.moveIndex(node, this.children.length, { changeSibling: true }); if (node.nodeType === Node.NodeType.Element) { this.moveIndex(node, this.pureChildren.length, { isInPureChildren: true }); } } } /** * Insert a node before specified node. * @param {Node} node - Target node. * @param {Node} before - The node next to the target position. * @return {number} the signal sent by native */ public insertBefore(node: Node, before: Node): void { if (node.parentNode && node.parentNode !== this) { return; } if (node === before || node.nextSibling && node.nextSibling === before) { return; } // If before is not exist, return. if (this.children.indexOf(before) < 0) { return; } if (!node.parentNode) { this.linkChild(node as Element); this.insertIndex(node, this.children.indexOf(before), { changeSibling: true }); if (this.docId) { this.registerNode(node); } if (node.nodeType === Node.NodeType.Element) { const element = node as Element; const pureBefore = this.nextElement(before); const index = this.insertIndex( element, pureBefore ? this.pureChildren.indexOf(pureBefore) : this.pureChildren.length, { isInPureChildren: true } ); this.inheritStyle(node); const taskCenter = this.getTaskCenter(this.docId); if (taskCenter) { return taskCenter.send( 'dom', { action: 'addElement' }, [this.ref, element.toJSON(), index] ); } } } else { this.moveIndex(node, this.children.indexOf(before), { changeSibling: true }); if (node.nodeType === Node.NodeType.Element) { const pureBefore = this.nextElement(before); this.moveIndex( node, pureBefore ? this.pureChildren.indexOf(pureBefore) : this.pureChildren.length, { isInPureChildren: true} ); } } } /** * Insert a node after specified node. * @param {Node} node - Target node. * @param {Node} after - The node in front of the target position. * @return {number} the signal sent by native */ public insertAfter(node: Node, after: Node) { if (node.parentNode && node.parentNode !== this) { return; } if (node === after || node.previousSibling && node.previousSibling === after) { return; } if (!node.parentNode) { this.linkChild(node as Element); this.insertIndex(node, this.children.indexOf(after) + 1, { changeSibling: true }); if (this.docId) { this.registerNode(node); } if (node.nodeType === Node.NodeType.Element) { const element = node as Element; const index = this.insertIndex( element, this.pureChildren.indexOf(this.previousElement(after)) + 1, { isInPureChildren: true } ); this.inheritStyle(node); const taskCenter = this.getTaskCenter(this.docId); if (taskCenter) { return taskCenter.send( 'dom', { action: 'addElement' }, [this.ref, element.toJSON(), index] ); } } } else { this.moveIndex(node, this.children.indexOf(after) + 1, { changeSibling: true}); if (node.nodeType === Node.NodeType.Element) { this.moveIndex( node, this.pureChildren.indexOf(this.previousElement(after)) + 1, { isInPureChildren: true } ); } } } /** * Remove a child node, and decide whether it should be destroyed. * @param {Node} node - Target node. * @param {boolean} [preserved=false] - If need to keep the target node. */ public removeChild(node: Node, preserved: boolean = false): void { if (node.parentNode) { this.removeIndex(node, { changeSibling: true }); if (node.nodeType === Node.NodeType.Element) { this.removeIndex(node, { isInPureChildren: true}); const taskCenter = this.getTaskCenter(this.docId); if (taskCenter) { taskCenter.send( 'dom', { action: 'removeElement' }, [node.ref] ); } } } if (!preserved) { node.destroy(); } } /** * Clear all child nodes. */ public clear(): void { const taskCenter: TaskCenter = this.getTaskCenter(this._docId); if (taskCenter) { this._pureChildren.forEach(child => { taskCenter.send('dom', { action: 'removeElement' }, [child.ref]); }); } this._children.forEach(node => { node.destroy(); }); this._children.length = 0; this._pureChildren.length = 0; } /** * Set dataSet for an element. * @param {string} key - dataSet name. * @param {string} value - dataSet value. */ public setData(key: string, value: string): void { this.dataSet[key] = value; } /** * Set an attribute, and decide whether the task should be send to native. * @param {string} key - Arribute name. * @param {string | number} value - Arribute value. * @param {boolean} [silent=false] - If use silent mode. */ public setAttr(key: string, value: string | number, silent: boolean = false): void { if (this.attr[key] === value && silent !== false) { return; } this.attr[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateAttrs' }, [this.ref, result]); } if (this._type === 'compontent' && key === 'name') { if (this._isFirstDyanmicName === true) { Log.info('compontent first setAttr name = ' + value); this._isFirstDyanmicName = false; } else { Log.info('compontent second setAttr name,' + value); if (taskCenter) { const node = this._nextSibling; taskCenter.send('dom', { action: 'removeElement' }, [node.ref]); } const parentNode = this._parentNode as Element; const component: VmOptions | null = targetIsComposed(this._vm, value.toString()); const meta = {}; if (component) { compileCustomComponent(this._vm, component, this._target, parentNode, value.toString(), meta); return; } } } } /** * Set a style property, and decide whether the task should be send to native. * @param {string} key - Style name. * @param {string | number} value - Style value. * @param {boolean} [silent=false] - If use silent mode. */ public setStyle(key: string, value: string | number, silent: boolean = false): void { if (this.style[key] === value && silent !== false) { return; } this.style[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this.toStyle()]); if (CSS_INHERITANCE.includes(key)) { this.broadcastStyle(); } } } /** * Set style properties from class. * @param {object} classStyle - Style properties. */ public setClassStyle(classStyle: any): void { let canUpdate: boolean = false; const taskCenter = this.getTaskCenter(this.docId); Object.keys(classStyle).forEach(key => { if (CSS_INHERITANCE.includes(key) && taskCenter) { if (!this.isSameStyle(this.classStyle[key], classStyle[key], key)) { canUpdate = true; } } }); for (const key in this._classStyle) { this._classStyle[key] = ''; } Object.assign(this._classStyle, classStyle); if (taskCenter) { taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this.toStyle()]); if (canUpdate) { this.broadcastStyle(); } } } /** * Set style properties from class. * @param {object} classStyle - Style properties. */ public setCustomFlag(): void { this._isCustomComponent = true; } /** * Set IdStyle properties from class. * @param {string} key - Style name. * @param {string|number} value - Style value. * @param {boolean} [silent=false] - If use silent mode. */ public setIdStyle(key: string, value: string | number, silent: boolean = false): void { if (this._idStyle[key] === value && silent !== false) { return; } // if inline style has define return if (this.style[key]) { return; } this._idStyle[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this._idStyle]); if (CSS_INHERITANCE.includes(key)) { this.broadcastStyle(); } } } /** * Set TagStyle properties from class. * @param {string} key - Style name. * @param {string|number} value - Style value. * @param {boolean} [silent=false] - If use silent mode. */ public setTagStyle(key: string, value: string | number, silent: boolean = false): void { if (this._tagStyle[key] === value && silent !== false) { return; } // If inline id class style has define return. if (this.style[key] || this._idStyle[key] || this._attrStyle[key] || this._classStyle[key] || this._firstOrLastChildStyle[key] || this._tagAndTagStyle[key]) { return; } this._tagStyle[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); } } public setAttrStyle(key: string, value: string | number, silent: boolean = false): void { if (this._attrStyle[key] === value && silent !== false) { return; } // If inline id style define return. if (this.style[key] || this._idStyle[key]) { return; } this._attrStyle[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); } } public setTagAndTagStyle(key: string, value: string | number, silent: boolean = false): void { if (this._tagAndTagStyle[key] === value && silent !== false) { return; } // If inline id class style has define return. if (this.style[key] || this._idStyle[key] || this._attrStyle[key] || this._classStyle[key] || this._firstOrLastChildStyle[key]) { return; } this._tagAndTagStyle[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); } } public setFirstOrLastChildStyle(key: string, value: string | number, silent: boolean = false): void { if (this._firstOrLastChildStyle[key] === value && silent !== false) { return; } // If inline id class style has define return. if (this.style[key] || this._idStyle[key] || this._attrStyle[key]) { return; } this._firstOrLastChildStyle[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); } } public setUniversalStyle(key: string, value: string | number, silent: boolean = false): void { if (this._universalStyle[key] === value && silent !== false) { return; } // If inline id class style has define return. if (this.style[key] || this._idStyle[key] || this._classStyle[key] || this._tagStyle[key] || this._tagAndTagStyle[key]) { return; } this._universalStyle[key] = value; const taskCenter = this.getTaskCenter(this.docId); if (!silent && taskCenter) { const result = {}; result[key] = value; taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); } } /** * Add an event handler. * @param {string} type - Event name. * @param {Function} handler - Event handler. * @param {Object} [params] - Event parameters. */ public addEvent(type: string, handler?: Function, params?: any): void { if (!this._event) { this._event = {}; } if (!this._event[type]) { this._event[type] = { handler, params }; } } /** * Remove an event handler. * @param {string} type - Event name */ public removeEvent(type: string): void { if (this._event && this._event[type]) { delete this._event[type]; } } /** * Fire an event manually. * @param {string} type - Event name. * @param {function} event - Event handler. * @param {boolean} isBubble - Whether or not event bubble * @param {boolean} [options] - Event options * @return {*} anything returned by handler function. */ public fireEvent(type: string, event: any, isBubble?: boolean, options?: any) { Log.debug(`Element#fireEvent, type = ${type}, event = ${event}, isBubble = ${isBubble}, options = ${options}.`); const BUBBLE_EVENTS = [ 'mouse', 'click', 'longpress', 'touchstart', 'touchmove', 'touchend', 'panstart', 'panmove', 'panend', 'horizontalpan', 'verticalpan', 'swipe' ]; let result = null; let isStopPropagation = false; const eventDesc = this._event[type]; if (eventDesc && event) { const handler = eventDesc.handler; event.stopPropagation = () => { isStopPropagation = true; }; if (options && options.params) { result = handler.call(this, event, ...options.params); } else { result = handler.call(this, event); } } if (!isStopPropagation && isBubble && BUBBLE_EVENTS.indexOf(type) !== -1) { if (this._parentNode) { const parentNode = this._parentNode as Element; event.currentTarget = parentNode; parentNode.fireEvent(type, event, isBubble); // no options } } return result; } /** * Get all styles of current element. * @return {object} style */ public toStyle(): any { // Selector Specificity inline > #id > .class > tag > inheritance. const style = Object.assign({}, this._inheritedStyle); this.assignStyle(style, this._universalStyle); this.assignStyle(style, this._tagStyle); this.assignStyle(style, this._tagAndTagStyle); this.assignStyle(style, this._classStyle); this.assignStyle(style, this._attrStyle); this.assignStyle(style, this._firstOrLastChildStyle); this.assignStyle(style, this._idStyle); this.assignStyle(style, this.style); return style; } /** * Assign style. * @param {*} src - Source style object. * @param {*} dest - Target style object. */ public assignStyle(src: any, dest: any): void { if (dest) { const keys = Object.keys(dest); // Margin and padding style: the style should be empty in the first. keys.sort(function(style1, style2) { if (dest[style1] === '') { return 1; } else { return -1; } }); let i = keys.length; while (i--) { const key = keys[i]; const val = dest[key]; if (val) { src[key] = val; } else { if ((val === '' || val === undefined) && src[key]) { return; } src[key] = val; } } } } /** * Convert current element to JSON like object. * @param {boolean} [ignoreChildren=false] - whether to ignore child nodes, default false * @return {JSON} JSON object of this element. */ public toJSON(ignoreChildren = false): JSON { const result: any = { ref: this.ref, type: this._type, attr: this.attr, style: this.toStyle(), customComponent: this._isCustomComponent }; const event = []; for (const type in this._event) { const { params } = this._event[type]; if (!params) { event.push(type); } else { event.push({ type, params }); } } if (event.length) { result.event = event; } if (!ignoreChildren && this._pureChildren.length) { result.children = this._pureChildren.map(child => child.toJSON()); } if (this._id) { result.id = this._id; } return result; } /** * Convert to HML element tag string. * @override * @return {string} hml of this element. */ public toString(): string { const id = this._id !== null ? this._id : ''; return '<' + this._type + ' id =' + id + ' attr=' + JSON.stringify(this.attr) + ' style=' + JSON.stringify(this.toStyle()) + '>' + this.pureChildren.map((child) => child.toString()).join('') + ''; } /** * Destroy this element */ public destroy() { Log.debug(`Element#destroy this._type = ${this._type}.`); if (this._event && this._event['detached']) { this.fireEvent('detached', {}); } this._attr = {}; this._style = {}; this._classStyle = {}; this._event = {}; this._idStyle = {}; this._tagStyle = {}; this._attrStyle = {}; this._tagAndTagStyle = {}; this._firstOrLastChildStyle = {}; this._universalStyle = {}; this._classList.length = 0; if (this.destroyHook) { this.destroyHook(); this.destroyHook = null; } if (this._children) { this._children.forEach((child: Node): void => { child.destroy(); }); this._children.length = 0; } if (this._pureChildren) { this._pureChildren.length = 0; } super.destroy(); } /** * the judgement of whether the inherited style should update * @param {string | Array} oldClassStyle * @param {string | Array} newClassStyle * @param {string} key * @returns {boolean} */ isSameStyle(oldClassStyle: string | any[], newClassStyle: string | any[], key: string) { if (key === 'fontFamily') { if (oldClassStyle[0].fontFamily === newClassStyle[0].fontFamily) { return true; } } else { if (oldClassStyle === newClassStyle) { return true; } } return false; } /** * iterate child node for updating inheritedstyle */ broadcastStyle() { if (this.pureChildren) { for (const child in this.pureChildren) { this.pureChildren[child].setInheritedStyle(); this.pureChildren[child].broadcastStyle(); } } } /** * before update inherited style * clear up the inherited style */ resetInheritedStyle() { this.inheritedStyle = {}; } /** * inherited style from parent */ public setInheritedStyle() { this.resetInheritedStyle(); const parentNode: Element = this.parentNode as Element; parentNode.inheritStyle(this); const taskCenter = this.getTaskCenter(this.docId); if (taskCenter) { taskCenter.send( 'dom', { action: 'updateStyle' }, [this.ref, this.toStyle()] ); } } /** * inherit style of parent. * @return {object} element */ public inheritStyle(node, isFirst = false) { // for first render, save time const allStyle = this.toStyle(); this.setChildStyle(allStyle, node._inheritedStyle); } /** * set inherited style to child * @param {object} parentStyle * @param {object} childStyle * @param {object} node */ public setChildStyle(parentStyle, childStyle) { Object.keys(parentStyle).forEach(key => { if (CSS_INHERITANCE.includes(key)) { childStyle[key] = parentStyle[key]; } }); } private registerNode(node) { const doc = this._ownerDocument; if (doc) { doc.nodeMap[node.nodeId] = node; } } } export default Element;