• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19/*
20 * 2021.01.08 - Reconstruct the class 'Vm' and make it more adaptable to framework.
21 * Copyright (c) 2021 Huawei Device Co., Ltd.
22 */
23
24/**
25 * @fileOverview
26 * ViewModel Constructor & definition
27 */
28
29import {
30  extend,
31  Log,
32  removeItem
33} from '../../utils/index';
34import {
35  initState,
36  initBases
37} from '../reactivity/state';
38import {
39  build, FragBlockInterface
40} from './compiler';
41import {
42  set,
43  del
44} from '../reactivity/observer';
45import {
46  watch,
47  initPropsToData
48} from './pageLife';
49import {
50  initEvents,
51  ExternalEvent,
52  Evt
53} from './events';
54import {
55  selector
56} from './selector';
57import Page from '../page/index';
58import Element from '../../vdom/Element';
59import {
60  ComputedInterface,
61  cssType,
62  Props,
63  VmOptions,
64  MediaStatusInterface,
65  ExternalBindingInterface
66} from './vmOptions';
67import {
68  hasOwn
69} from '../util/shared';
70
71/**
72 * VM constructor.
73 * @param {string} type - Type.
74 * @param {null | VmOptions} options - Component options.
75 * @param {Vm} parentVm   which contains __app.
76 * @param {Element | FragBlockInterface} parentEl - root element or frag block.
77 * @param {Object} mergedData - External data.
78 * @param {ExternalEvent} externalEvents - External events.
79 */
80export default class Vm {
81  private $app: any;
82  private __methods: Record<string, (...args: unknown[]) => any>;
83  private __type: string;
84  private __css: cssType;
85  private __vmOptions: VmOptions;
86  private __parent: Vm;
87  private __realParent: Vm;
88  private __computed: ComputedInterface;
89  private __selector: object;
90  private __parentEl: Element | FragBlockInterface;
91  private __app: Page;
92  private __shareData: any;
93  private __data: any;
94  private __props: Props;
95  private __init: boolean;
96  private __valid: boolean;
97  private __visible: boolean;
98  private __ready: boolean;
99  private __rootEl: Element;
100  private __ids: Record<string, {vm: Vm, el: Element}>;
101  private __vmEvents: object;
102  private __childrenVms: Vm[];
103  private __externalBinding: ExternalBindingInterface;
104  private readonly __descriptor: string;
105  private __isHide: boolean;
106  private __mediaStatus: Partial<MediaStatusInterface<string, boolean>>;
107  public $refs: Record<string, Element>;
108  private __slotContext: { content: Record<string, any>, parentVm: Vm };
109  private __counterMapping = new Map();
110
111  constructor(
112    type: string,
113    options: null | VmOptions,
114    parentVm: Vm | any,
115    parentEl: Element | FragBlockInterface,
116    mergedData: object,
117    externalEvents: ExternalEvent
118  ) {
119    this.$app = global.aceapp;
120    this.__parent = parentVm.__realParent ? parentVm.__realParent : parentVm;
121    this.__app = parentVm.__app;
122    parentVm.__childrenVms && parentVm.__childrenVms.push(this);
123
124    if (!options && this.__app.customComponentMap) {
125      options = this.__app.customComponentMap[type];
126    }
127    const data = options.data || {};
128    const shareData = options.shareData || {};
129    this.__vmOptions = options;
130    this.__computed = options.computed;
131    this.__css = options.style || {};
132    this.__selector = selector(this.__css);
133    this.__ids = {};
134    this.$refs = {};
135    this.__vmEvents = {};
136    this.__childrenVms = [];
137    this.__type = type;
138    this.__valid = true;
139    this.__props = [];
140    this.__methods = {};
141
142    // Bind events and lifecycles.
143    initEvents(this, externalEvents);
144
145    Log.debug(
146      `'_innerInit' lifecycle in Vm(${this.__type}) and mergedData = ${JSON.stringify(mergedData)}.`
147    );
148    this.$emit('hook:_innerInit');
149    this.__data = (typeof data === 'function' ? data.apply(this) : data) || {};
150    this.__shareData = (typeof shareData === 'function' ? shareData.apply(this) : shareData) || {};
151    this.__descriptor = options._descriptor;
152    if (global.aceapp && global.aceapp.i18n && global.aceapp.i18n.extend) {
153      global.aceapp.i18n.extend(this);
154    }
155    if (global.aceapp && global.aceapp.dpi && global.aceapp.dpi.extend) {
156      global.aceapp.dpi.extend(this);
157    }
158
159    // MergedData means extras params.
160    if (mergedData) {
161      if (hasOwn(mergedData, 'paramsData') && hasOwn(mergedData, 'dontOverwrite') && mergedData['dontOverwrite'] === false) {
162        dataAccessControl(this, mergedData['paramsData'], this.__app.options && this.__app.options.appCreate);
163        extend(this._data, mergedData['paramsData']);
164      } else {
165        dataAccessControl(this, mergedData, this.__app.options && this.__app.options.appCreate);
166        extend(this._data, mergedData);
167      }
168    }
169
170    initPropsToData(this);
171    initState(this);
172    initBases(this);
173    Log.debug(`"onInit" lifecycle in Vm(${this.__type})`);
174
175    if (mergedData && hasOwn(mergedData, 'paramsData') && hasOwn(mergedData, 'dontOverwrite')) {
176      if (mergedData['dontOverwrite'] === false) {
177        this.$emit('hook:onInit');
178      } else {
179        this.$emitDirect('hook:onInit', mergedData['paramsData']);
180      }
181    } else {
182      this.$emit('hook:onInit');
183    }
184
185    if (!this.__app.doc) {
186      return;
187    }
188    this.__mediaStatus = {};
189    this.__mediaStatus.orientation = this.__app.options.orientation;
190    this.__mediaStatus.width = this.__app.options.width;
191    this.__mediaStatus.height = this.__app.options.height;
192    this.__mediaStatus.resolution = this.__app.options.resolution;
193    this.__mediaStatus['device-type'] = this.__app.options['device-type'];
194    this.__mediaStatus['aspect-ratio'] = this.__app.options['aspect-ratio'];
195    this.__mediaStatus['device-width'] = this.__app.options['device-width'];
196    this.__mediaStatus['device-height'] = this.__app.options['device-height'];
197    this.__mediaStatus['round-screen'] = this.__app.options['round-screen'];
198    this.__mediaStatus['dark-mode'] = this.__app.options['dark-mode'];
199
200    // If there is no parentElement, specify the documentElement.
201    this.__parentEl = parentEl || this.__app.doc.documentElement;
202    build(this);
203  }
204
205  /**
206   * Get the element by id.
207   * @param {string | number} [id] - Element id.
208   * @return {Element} Element object. if get null, return root element.
209   */
210  public $element(id?: string | number): Element {
211    if (id) {
212      if (typeof id !== 'string' && typeof id !== 'number') {
213        Log.warn(`Invalid parameter type: The type of 'id' should be string or number, not ${typeof id}.`);
214        return;
215      }
216      const info: any = this._ids[id];
217      if (info) {
218        return info.el;
219      }
220    } else {
221      return this.__rootEl;
222    }
223  }
224
225  /**
226   * Get the vm by id.
227   * @param {string} id - Vm id.
228   * @return {Vm} Vm object.
229   */
230  public $vm(id: string): Vm {
231    const info = this._ids[id];
232    if (info) {
233      return info.vm;
234    }
235  }
236
237  /**
238   * Get parent Vm of current.
239   */
240  public $parent(): Vm {
241    return this._parent;
242  }
243
244  /**
245   * Get child Vm of current.
246   */
247  public $child(id: string): Vm {
248    if (typeof id !== 'string') {
249      Log.warn(`Invalid parameter type: The type of 'id' should be string, not ${typeof id}.`);
250      return;
251    }
252    return this.$vm(id);
253  }
254
255  /**
256   * Get root element of current.
257   */
258  public $rootElement(): Element {
259    return this.__rootEl;
260  }
261
262  /**
263   * Get root Vm of current.
264   */
265  public $root(): Vm {
266    return getRoot(this);
267  }
268
269  /**
270   * Execution Method.
271   * @param {string} type - Type.
272   * @param {Object} [detail] - May needed for Evt.
273   * @param {*} args - Arg list.
274   * @return {*}
275   */
276  public $emit(type: string, detail?: object, ...args: any[]): any[] {
277    if (typeof type !== 'string') {
278      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
279      return;
280    }
281    const events = this.__vmEvents;
282    const handlerList = events[type];
283    if (handlerList) {
284      const results = [];
285      const evt = new Evt(type, detail);
286      handlerList.forEach((handler) => {
287        results.push(handler.call(this, evt, ...args));
288      });
289      return results;
290    }
291  }
292
293  /**
294   * Execution Method directly.
295   * @param {string} type - Type.
296   * @param {*} args - Arg list.
297   * @return {*}
298   */
299  public $emitDirect(type: string, ...args: any[]): any[] {
300    if (typeof type !== 'string') {
301      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
302      return;
303    }
304    const events = this.__vmEvents;
305    const handlerList = events[type];
306    if (handlerList) {
307      const results = [];
308      handlerList.forEach((handler) => {
309        results.push(handler.call(this, ...args));
310      });
311      return results;
312    }
313  }
314
315  /**
316   * Dispatch events, passing upwards along the parent.
317   * @param {string} type - Type.
318   * @param {Object} [detail] - May needed for Evt.
319   */
320  public $dispatch(type: string, detail?: object): void {
321    if (typeof type !== 'string') {
322      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
323      return;
324    }
325    const evt = new Evt(type, detail);
326    this.$emit(type, evt);
327    if (!evt.hasStopped() && this.__parent && this.__parent.$dispatch) {
328      this.__parent.$dispatch(type, evt);
329    }
330  }
331
332  /**
333   * Broadcast event, which is passed down the subclass.
334   * @param {string} type - Type.
335   * @param {Object} [detail] - May be needed for Evt.
336   */
337  public $broadcast(type: string, detail?: object): void {
338    if (typeof type !== 'string') {
339      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
340      return;
341    }
342    const evt = new Evt(type, detail);
343    this.$emit(type, evt);
344    if (!evt.hasStopped() && this.__childrenVms) {
345      this.__childrenVms.forEach((subVm) => {
346        subVm.$broadcast(type, evt);
347      });
348    }
349  }
350
351  /**
352   * Add the event listener.
353   * @param {string} type - Type.
354   * @param {Function} handler - To add.
355   */
356  public $on(type: string, handler: Function): void {
357    if (typeof type !== 'string') {
358      Log.debug(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
359      return;
360    }
361    if (typeof handler !== 'function') {
362      Log.debug(`Invalid parameter type: The type of 'handler' should be function, not ${typeof handler}.`);
363      return;
364    }
365    const events = this.__vmEvents;
366    const handlerList = events[type] || [];
367    handlerList.push(handler);
368    events[type] = handlerList;
369    if (type === 'hook:onReady' && this.__ready) {
370      this.$emit('hook:onReady');
371    }
372  }
373
374  /**
375   * Remove the event listener.
376   * @param {string} type - Type.
377   * @param {Function} handler - To remove.
378   */
379  public $off(type: string, handler: Function): void {
380    if (typeof type !== 'string') {
381      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
382      return;
383    }
384    if (typeof handler !== 'function') {
385      Log.warn(`Invalid parameter type: The type of 'handler' should be function, not ${typeof handler}.`);
386      return;
387    }
388    const events = this.__vmEvents;
389    if (!handler) {
390      delete events[type];
391      return;
392    }
393    const handlerList = events[type];
394    if (!handlerList) {
395      return;
396    }
397    removeItem(handlerList, handler);
398  }
399
400  /**
401   * Execution element.fireEvent Method.
402   * @param {string} type - Type.
403   * @param {Object} data - needed for Evt.
404   * @param {string} id - Element id.
405   */
406  public $emitElement(type: string, data: object, id: string): void {
407    if (typeof type !== 'string') {
408      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
409      return;
410    }
411    if (typeof id !== 'string') {
412      Log.warn(`Invalid parameter type: The type of 'id' should be string, not ${typeof id}.`);
413      return;
414    }
415    const info = this._ids[id];
416    if (info) {
417      const element = info.el;
418      const evt = new Evt(type, data);
419      element.fireEvent(type, evt, false);
420    } else {
421      Log.warn('The id is invalid, id = ' + id);
422    }
423  }
424
425  /**
426   * Watch a calc function and callback if the calc value changes.
427   * @param {string} data - Data that needed.
428   * @param {Function | string} callback - Callback function.
429   */
430  public $watch(data: string, callback: ((...args: any) => any) | string): void {
431    if (typeof data !== 'string') {
432      Log.warn(`Invalid parameter type: The type of 'data' should be string, not ${typeof data}.`);
433      return;
434    }
435    if (typeof callback !== 'function' && typeof callback !== 'string') {
436      Log.warn(`Invalid parameter type: The type of 'callback' should be function or string, not ${typeof callback}.`);
437      return;
438    }
439    watch(this, data, callback);
440  }
441
442  /**
443   * Set a property on an object.
444   * @param {string} key - Get value by key.
445   * @param {*} value - Property
446   */
447  public $set(key: string, value: any): void {
448    if (typeof key !== 'string') {
449      Log.warn(`Invalid parameter type: The type of 'key' should be string, not ${typeof key}.`);
450      return;
451    }
452    if (key.indexOf('.') !== -1) {
453      _proxySet(this._data, key, value);
454    }
455    set(this._data, key, value);
456  }
457
458  /**
459   * Delete a property and trigger change.
460   * @param {string} key - Get by key.
461   */
462  public $delete(key: string): void {
463    if (typeof key !== 'string') {
464      Log.warn(`Invalid parameter type: The type of 'key' should be string, not ${typeof key}.`);
465      return;
466    }
467    del(this._data, key);
468  }
469
470  /**
471   * Delete Vm object.
472   */
473  public destroy(): void {
474    Log.debug(`[JS Framework] "onDestroy" lifecycle in Vm(${this.__type})`);
475    this.$emit('hook:onDestroy');
476    this.$emit('hook:onDetached');
477    fireNodeDetached(this.__rootEl);
478    this.__valid = false;
479
480    delete this.__app;
481    delete this.__computed;
482    delete this.__css;
483    delete this.__data;
484    delete this.__ids;
485    delete this.__vmOptions;
486    delete this.__parent;
487    delete this.__parentEl;
488    delete this.__rootEl;
489    delete this.$refs;
490
491    // Destroy child vms recursively.
492    if (this.__childrenVms) {
493      let vmCount: number = this.__childrenVms.length;
494      while (vmCount--) {
495        this.destroy.call(this.__childrenVms[vmCount], this.__childrenVms[vmCount]);
496      }
497      delete this.__childrenVms;
498    }
499    delete this.__type;
500    delete this.__vmEvents;
501  }
502
503  /**
504   * $t function.
505   * @param {string} key - Key.
506   * @return {string} - Key.
507   */
508  public $t(key: string): string {
509    return key;
510  }
511
512  /**
513   * $tc function.
514   * @param {string} key - Key.
515   * @return {string} - Key.
516   */
517  public $tc(key: string): string {
518    return key;
519  }
520
521  /**
522   * $r function.
523   * @param {string} key - Key.
524   * @return {string} - Key.
525   */
526  public $r(key: string): string {
527    return key;
528  }
529
530  /**
531   * Methods of this Vm.
532   * @type {Object}
533   * @readonly
534   */
535  public get _methods() {
536    return this.__methods;
537  }
538
539  public $getCounterMapping(key: string): number {
540    return this.__counterMapping.get(key);
541  }
542
543  public $setCounterMapping(key: string, value: number) {
544    this.__counterMapping.set(key, value);
545  }
546
547  public get _counterMapping() {
548    return this.__counterMapping;
549  }
550
551  /**
552   * Type of this Vm.
553   * @type {string}
554   * @readonly
555   */
556  public get _type() {
557    return this.__type;
558  }
559
560  public set _type(newType: string) {
561    this.__type = newType;
562  }
563
564  /**
565   * Css of this Vm.
566   * @type {[key: string]: any}
567   * @readonly
568   */
569  public get _css() {
570    return this.__css;
571  }
572
573  /**
574   * Options of this Vm.
575   * @type {VmOptions}
576   */
577  public get _vmOptions() {
578    return this.__vmOptions;
579  }
580
581  public set _vmOptions(newOptions: VmOptions) {
582    this.__vmOptions = newOptions;
583  }
584
585  /**
586   * Parent of this Vm.
587   * @type {Vm}
588   * @readonly
589   */
590  public get _parent() {
591    return this.__parent;
592  }
593
594  /**
595   * RealParent of this Vm.
596   * @type {Vm}
597   */
598  public get _realParent() {
599    return this.__realParent;
600  }
601
602  public set _realParent(realParent: Vm) {
603    this.__realParent = realParent;
604  }
605
606  /**
607   * Computed of this Vm.
608   * @type {ComputedInterface}
609   */
610  public get computed() {
611    return this.__computed;
612  }
613
614  public set computed(newComputed: ComputedInterface) {
615    this.__computed = newComputed;
616  }
617
618  /**
619   * Selector of this Vm.
620   * @type {Object}
621   * @readonly
622   */
623  public get _selector() {
624    return this.__selector;
625  }
626
627  /**
628   * ParentEl of this Vm.
629   * @type {FragBlockInterface | Element}
630   */
631  public get _parentEl() {
632    return this.__parentEl;
633  }
634
635  public set _parentEl(newParentEl: FragBlockInterface | Element) {
636    this.__parentEl = newParentEl;
637  }
638
639  /**
640   * App of this Vm.
641   * @type {Page}
642   */
643  public get _app() {
644    return this.__app;
645  }
646
647  public set _app(newApp: Page) {
648    this.__app = newApp;
649  }
650
651  /**
652   * ShareData of this Vm.
653   * @type {*}
654   */
655  public get _shareData() {
656    return this.__shareData;
657  }
658
659  public set _shareData(newShareData: object) {
660    this.__shareData = newShareData;
661  }
662
663  /**
664   * Data of this Vm.
665   * @type {*}
666   */
667  public get _data() {
668    return this.__data;
669  }
670
671  public set _data(newData: any) {
672    this.__data = newData;
673  }
674
675  /**
676   * Props of this Vm.
677   * @type {Props}
678   * @readonly
679   */
680  public get _props() {
681    return this.__props;
682  }
683
684  /**
685   * Init of this Vm.
686   * @type {boolean}
687   */
688  public get _init() {
689    return this.__init;
690  }
691
692  public set _init(newInit: boolean) {
693    this.__init = newInit;
694  }
695
696  /**
697   * Valid of this Vm.
698   * @type {boolean}
699   * @readonly
700   */
701  public get _valid() {
702    return this.__valid;
703  }
704
705  /**
706   * Visible of this Vm.
707   * @type {boolean}
708   */
709  public get _visible() {
710    return this.__visible;
711  }
712
713  public set _visible(newVisible) {
714    this.__visible = newVisible;
715  }
716
717  /**
718   * Ready of this Vm.
719   * @type {boolean}
720   */
721  public get _ready() {
722    return this.__ready;
723  }
724
725  public set _ready(newReady: boolean) {
726    this.__ready = newReady;
727  }
728
729  /**
730   * RootEl of this Vm.
731   * @type {Element}
732   */
733  public get _rootEl() {
734    return this.__rootEl;
735  }
736
737  public set _rootEl(newRootEl: Element) {
738    this.__rootEl = newRootEl;
739  }
740
741  /**
742   * Ids of this Vm.
743   * @type {{[key: string]: { vm: Vm, el: Element}}}
744   * @readonly
745   */
746  public get _ids() {
747    return this.__ids;
748  }
749
750  /**
751   * VmEvents of this Vm.
752   * @type {Object}
753   * @readonly
754   */
755  public get _vmEvents() {
756    return this.__vmEvents;
757  }
758
759  /**
760   * children of vm.
761   * @return {Array} - children of Vm.
762   */
763  public get _childrenVms() {
764    return this.__childrenVms;
765  }
766
767  /**
768   * ExternalBinding of this Vm.
769   * @type {ExternalBinding}
770   */
771  public get _externalBinding() {
772    return this.__externalBinding;
773  }
774
775  public set _externalBinding(newExternalBinding: ExternalBindingInterface) {
776    this.__externalBinding = newExternalBinding;
777  }
778
779  /**
780   * Descriptor of this Vm.
781   * @type {string}
782   * @readonly
783   */
784  public get _descriptor() {
785    return this.__descriptor;
786  }
787
788  /**
789   * IsHide of this Vm.
790   * @type {boolean}
791   */
792  public get _isHide() {
793    return this.__isHide;
794  }
795
796  public set _isHide(newIsHide: boolean) {
797    this.__isHide = newIsHide;
798  }
799
800  /**
801   * MediaStatus of this Vm.
802   * @type {MediaStatusInterface<string, boolean>}
803   */
804  public get _mediaStatus() {
805    return this.__mediaStatus;
806  }
807
808  public set _mediaStatus(newMediaStatus: Partial<MediaStatusInterface<string, boolean>>) {
809    this.__mediaStatus = newMediaStatus;
810  }
811
812  /**
813   * slotContext of this Vm.
814   * @type { content: Record<string, any>, parentVm: Vm }
815   */
816  public get _slotContext() {
817    return this.__slotContext;
818  }
819
820  public set _slotContext(newMSoltContext: { content: Record<string, any>, parentVm: Vm }) {
821    this.__slotContext = newMSoltContext;
822  }
823}
824
825/**
826 * Set proxy.
827 * @param {Object} data - Data that needed.
828 * @param {string} key - Get prop by key.
829 * @param {*} value - Property.
830 */
831function _proxySet(data: object, key: string, value: any): void {
832  let tempObj = data;
833  const keys = key.split('.');
834  const len = keys.length;
835  for (let i = 0; i < len; i++) {
836    const prop = keys[i];
837    if (i === len - 1) {
838      set(tempObj, prop, value);
839      tempObj = null;
840      break;
841    }
842    if (tempObj[prop] === null || typeof tempObj[prop] !== 'object' && typeof tempObj[prop] !== 'function') {
843      Log.warn(`Force define property '${prop}' of '${JSON.stringify(tempObj)}' with value '{}', `
844        + `old value is '${tempObj[prop]}'.`);
845      set(tempObj, prop, {});
846    }
847    tempObj = tempObj[prop];
848  }
849}
850
851/**
852 * Control data access.
853 * @param {Vm} vm - Vm object.
854 * @param {Object} mergedData - Merged data.
855 * @param {boolean} external - If has external data.
856 */
857function dataAccessControl(vm: any, mergedData: object, external: boolean): void {
858  if (vm._descriptor && Object.keys(vm._descriptor).length !== 0) {
859    const keys = Object.keys(mergedData);
860    keys.forEach(key => {
861      const desc = vm._descriptor[key];
862      if (!desc || desc.access === 'private' || external && desc.access === 'protected') {
863        Log.error(`(${key}) can not modify`);
864        delete mergedData[key];
865      }
866    });
867  }
868}
869
870/**
871 * Get root Vm.
872 * @param {Vm} vm - Vm object.
873 * @return {Vm} Root vm.
874 */
875function getRoot(vm: any): Vm {
876  const parent = vm._parent;
877  if (!parent) {
878    return vm;
879  }
880  if (parent.__rootVm) {
881    return vm;
882  }
883  return getRoot(parent);
884}
885
886/**
887 * order node and fire detached event.
888 * @param {Element} el - Element object.
889 */
890function fireNodeDetached(el: Element) {
891  if (!el) {
892    return;
893  }
894  if (el.event && el.event['detached']) {
895    el.fireEvent('detached', {});
896  }
897  if (el.children && el.children.length !== 0) {
898    for (const child of el.children) {
899      fireNodeDetached(child as Element);
900    }
901  }
902}
903