as-tab.js

import API from '../api/ecosystem-base.js';
import {Menu_A} from './as-menu.js';
import {Component_A} from './basic/as-component.js';
import {FP_Tabcontainer_A} from './fp-ext/fp-tabcontainer-ext.js';
import {ErrorCode} from '../exception/exceptionDesc.js';

/**
 * @ignore
 */
const logModule = 'as-tab-a';

/**
 * @description Properties accepted by the Tab component, defining its appearance, behavior, and lifecycle hooks.
 * This class focuses on the specific properties of the Tab component.
 * Since it inherits from Accessor_A, all basic properties (e.g., height, width) are available but documented in the Accessor_A part.
 * @typedef {object} TComponents.TabProps
 * @prop {object} [options] Additional options for the tab component.
 * Items in this object include:
 * - **responsive** (boolean, default: false): Whether the tab container should be responsive.
 * @prop {string} [tips] Tooltip text for the component.
 * @prop {Function|string} [onCreated] Lifecycle hook invoked after component instantiation.
 * @prop {Function|string} [onMounted] Lifecycle hook invoked after component is attached to the DOM.
 * @prop {string} [position] CSS positioning of the element.
 * @prop {number} [width] Component width.
 * @prop {number} [height] Component height.
 * @prop {number} [top] Top offset in pixels.
 * @prop {number} [left] Left offset in pixels.
 * @prop {number} [rotation] Rotation angle in degrees for visual transform.
 * @prop {number} [borderRadius] Corner radius in pixels.
 * @prop {number} [zIndex] z-index stacking order.
 * @prop {string} [color] Primary color for text and icons.
 * @prop {string} [backgroundColor] Background color for the content area.
 * @prop {object} [font] Font configuration object containing size, family and style settings.
 * This object controls text appearance:
 * - **fontSize** (number, default: 12): Font size in pixels used for labels and text.
 * - **fontFamily** (string, default: 'Segoe UI'): Font family name for text rendering.
 * - **style** (object): Font style object, containing `fontStyle`, `fontWeight`, `textDecoration`.
 * @prop {string} [size] Preset size key for the component.
 * @prop {string} [tabPosition] Position of the tabs relative to content ('top' | 'bottom' | 'left' | 'right').
 * @prop {string} [styleTemplate] Key of a style template to apply.
 * @prop {number} [activeViewIndex] Zero-based index of the currently active tab.
 * @prop {Function|string} [onChange] Callback when the active tab changes.
 * @prop {boolean} [useViewIcon] Whether to show each tab’s icon.
 * @prop {TComponents.ViewProps[]} [views] Array of view items to render as tabs.
 * @prop {string} [defaultState] Default state of the component.
 * @prop {boolean} [expandHeightToParentBottom] Whether to expand the height to the parent's bottom.
 * @memberof TComponents
 */

/**
 * @description Represents a tab component with multiple customizable properties and events.
 * @class TComponents.Tab
 * @memberof TComponents
 * @extends TComponents.Menu_A
 * @param {HTMLElement} parent - HTML element that is going to be the parent of the component.
 * @param {TComponents.TabProps} [props] - The properties object.
 * @example
 * const tab = new TComponents.Tab(document.body, {
 *   position: 'absolute',
 *   width: 200,
 *   height: 200,
 *   zIndex: 1000,
 *   views: [
 *     {
 *       name: 'Tab 1',
 *       icon: 'abb-icon abb-icon-home-house_32',
 *       content: `view_${API.generateUUID()}`,
 *       children: [],
 *     },
 *    {
 *       name: 'Tab 2',
 *       icon: 'abb-icon abb-icon-home-house_32',
 *       content: `view_${API.generateUUID()}`,
 *       children: [],
 *     },
 *   ],
 *   activeViewIndex: 0,
 * });
 *
 * // Render the component.
 * await tab.render();
 */
export class Tab extends Menu_A {
  constructor(parent, props) {
    super(parent, props);

    this.viewId = new Map();

    /**
     * @instance
     * @private
     * @type {TComponents.TabProps}
     */
    this._props;

    /**
     * Use this variable to determine the source of the onchange event.
     * @instance
     * @private
     * @type {string|null}
     */
    this._changeSource = null; // 'user' | 'api'
  }

  /**
   * @description An array of Tab's views, each containing properties
   * like `id`,`name`, `content`, `active`, and `child`.
   * @member {any[]} TComponents.Tab#views
   * @instance
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * if(tab.views.length >= 1){
   *  console.log(tab.views[0].name); // Outputs: 'Tab 1'
   * };
   */
  get views() {
    return this._views;
  }

  /**
   * @deprecated
   */
  get activeTab() {
    const index = this._props.activeViewIndex;
    if (index >= 0) {
      const view = this.views[index];
      return view.name;
    } else {
      return '';
    }
  }

  /**
   * @deprecated Use `activeViewName` instead.
   * @description Sets the active tab by its display name.
   * @member {string} TComponents.Tab#activeTab
   * @instance
   * @param {string} name - The target tab name (display text).
   * If multiple tabs render the same text, the first match is used.
   * @throws {Error} If the tab container is not initialized.
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * tab.activeTab = 'Tab 2'; // Sets the active tab to 'Tab 2'
   */
  set activeTab(name) {
    if (!this.tabContainer) throw new Error('TabContainer is not initialized. Please render the component before use.');
    const index = this.views.findIndex((item) => Component_A.tParse(item.name) === name);
    this.activeViewIndex = index;
  }

  /**
   * @returns {string}
   */
  get activeViewName() {
    const index = this._props.activeViewIndex;
    if (index >= 0) {
      const view = this.views[index];
      return view.name;
    } else {
      return '';
    }
  }

  /**
   * @description Sets the active tab by its display name.
   * @member {string} TComponents.Tab#activeViewName
   * @instance
   * @param {string} name - The target tab name (display text).
   * If multiple tabs render the same text, the first match is used.
   * @throws {Error} If the tab container is not initialized.
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * tab.activeViewName = 'Tab 2'; // Sets the active tab to 'Tab 2'
   */
  set activeViewName(name) {
    if (!this.tabContainer) throw new Error('TabContainer is not initialized. Please render the component before use.');
    const index = this.views.findIndex((item) => Component_A.tParse(item.name) === name);
    this.activeViewIndex = index;
  }

  /**
   * @returns {number}
   */
  get activeViewIndex() {
    return this._props.activeViewIndex;
  }

  /**
   * @description Sets the active tab view index and updates the active tab in the tab container.
   * @member {number} TComponents.Tab#activeViewIndex
   * @instance
   * @param {number} t - The new active view index.
   * @throws {Error} If the new index is invalid or if the active view content is invalid.
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * tab.activeViewIndex = 1; // Sets the active tab to the second tab
   */
  set activeViewIndex(t) {
    const index = this.checkActiveViewIndex(t);
    if (index === null || index === this._props.activeViewIndex) return;
    // To ensure compatibility, this interface will trigger onchange previously.
    if (!this._changeSource) {
      this._changeSource = 'user';
    }
    this.setProps({activeViewIndex: index});
  }

  /**
   * @returns {number}
   */
  get activeViewIndexX() {
    return this._props.activeViewIndex;
  }

  /**
   * @description Sets the active tab view index and updates the active tab in the tab container.
   * This will update the tab container but will NOT trigger the `onchange` event.
   * Recommended for internal or programmatic updates, where change notification is not desired.
   * @member {number} TComponents.Tab#activeViewIndexX
   * @instance
   * @param {number} t - The new active view index.
   * @throws {Error} If the new index is invalid or if the active view content is invalid.
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * tab.activeViewIndexX = 1;
   */
  set activeViewIndexX(t) {
    const index = this.checkActiveViewIndex(t);
    if (index === null || index === this._props.activeViewIndex) return;

    // If it is not triggered by a user, it is considered an API.
    if (!this._changeSource) {
      this._changeSource = 'api';
    }

    this.setProps({activeViewIndex: index});
  }

  /**
   * @returns {string}
   */
  get tabPosition() {
    return this._props.tabPosition;
  }

  /**
   * @description Sets the tab bar position.
   * The tab position can be `top`, `left`, or `right`.
   * @member {string} TComponents.Tab#tabPosition
   * @instance
   * @param {string} t - The new tab position (e.g., "top" | "left" | "right").
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * tab.tabPosition = 'left'; // Sets the tab position to the left
   */
  set tabPosition(t) {
    const positionList = ['top', 'left', 'right'];
    if (!positionList.includes(t)) return;
    this.setProps({tabPosition: t});
  }

  /**
   * @description Returns the default properties of the tab component.
   * @member TComponents.Tab#defaultProps
   * @method
   * @protected
   * @returns {TComponents.TabProps} Default properties.
   */
  defaultProps() {
    return {
      options: {
        responsive: false,
      },
      tips: '',
      onCreated: '',
      onMounted: '',
      onDispose: '',
      position: 'static',
      width: 200,
      height: 200,
      top: 0,
      left: 0,
      rotation: 0,
      borderRadius: 4,
      zIndex: 0,
      color: 'rgba(0,0,0,1)',
      backgroundColor: 'rgba(245,245,245,1)',
      font: {
        fontSize: 12,
        fontFamily: 'Segoe UI',
        style: {
          fontStyle: 'normal',
          fontWeight: 'normal',
          textDecoration: 'none',
        },
      },
      size: '',
      tabPosition: 'top',
      styleTemplate: '',
      activeViewIndex: 0,
      onChange: '',
      useViewIcon: true,
      views: [
        {
          name: Component_A.tParse('Tab 0'),
          content: `View_${API.generateUUID()}`,
          icon: 'abb-icon abb-icon-abb_robot-tool_32',
          children: [],
        },
      ],
      defaultState: 'show_enable',
      expandHeightToParentBottom: false,
    };
  }

  /**
   * @description Initializes the tab component, setting up event handlers and tab container.
   * @member TComponents.Tab#onInit
   * @method
   * @throws {Error} If the component fails to initialize.
   * @returns {void}
   */
  onInit() {
    try {
      // Process the views prior to instantiating the new hamburger object.
      this._initViews();

      // Initialize the tab container component.
      this.tabContainer = new FP_Tabcontainer_A();
      this.tabContainer.onTabClick = this._handleTabClick.bind(this);

      // Initialize views of the tab container.
      this.viewId.clear();
      this.views.forEach((view) => {
        const dom = this._getDom(view.content, view.id, view.name);
        view.id = this.tabContainer.addTab(Component_A.tParse(view.name), dom, view.icon);
        this.viewId.set(view.id, view.name);
      });
    } catch (e) {
      Logger.e(logModule, ErrorCode.FailedToInitComponent, `Failed to initialize Tab component`, e);
      throw ErrorCode.FailedToInitComponent;
    }
  }

  /**
   * @description Returns the HTML markup for the tab component.
   * @member TComponents.Tab#markup
   * @method
   * @returns {string} The HTML markup
   */
  markup() {
    return /*html*/ `<div class="tc-tab"></div>`;
  }

  /**
   * @description Maps components to their identifiers.
   * @member TComponents.Tab#mapComponents
   * @method
   * @returns {object}
   */
  mapComponents() {
    if (!Array.isArray(this._children)) return {};

    for (let i = 0; i < this._children.length; i++) {
      if (i === this._props.activeViewIndex) {
        const instance = this._children[i];
        return {children: instance};
      }
    }

    return {};
  }

  /**
   * @description Renders the tab component, applying styles and attaching event listeners.
   * @member TComponents.Tab#onRender
   * @method
   * @throws {Error} If the component fails to render.
   * @returns {void}
   */
  onRender() {
    try {
      this.removeAllEventListeners();
      this.tabContainer.attachToElement(this.find('.tc-tab'));
      this.tabContainer.position = this._props.tabPosition;
      this.tabContainer.setTabTitle = this._props.title;
      this.tabContainer.borderRadius = this._props.borderRadius;

      const tabbar = this.find('.fp-components-tabcontainer-tabbar');
      const tabbarIcon = this.find('.fp-components-tabcontainer-tabbar-icon');

      if (tabbar) {
        tabbar.style.cssText = `
        font-family: ${this._props.font.fontFamily};
        font-size: ${this._props.font.fontSize}px;
        font-style: ${this._props.font.style.fontStyle};
        font-weight: ${this._props.font.style.fontWeight};
        text-decoration: ${this._props.font.style.textDecoration};
        color: ${this._props.color};`;
      }
      if (tabbarIcon) {
        tabbarIcon.style.cssText = `
          color:${this._props.color};
          font-size: ${this._props.font.fontSize}px;
          font-weight:${this._props.font.style.fontWeight};
        `;
      }

      this._setTabContainerBorder();
      this.container.classList.add('is-container');

      // Set up the tab container active view.
      this._setActiveView();
      // After setting the `activeView` in the component, assign the `onchange` handler.
      // Prevent it from being executed during initialization.
      this.tabContainer.onchange = this._cbOnChange.bind(this);
      this._addTips();
    } catch (e) {
      // Runtime errors: write specific content to the log, throw error code
      Logger.e(
        logModule,
        ErrorCode.FailedToRunOnRender,
        `Error happens on onRender of tab component ${this.compId}.`,
        e,
      );
      throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
    }
  }

  /**
   * @deprecated No longer supported interfaces.
   * @description Gets the properties of the tab component.
   * @member TComponents.Tab#getProps
   * @method
   * @returns {object} The properties object
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * const props = tab.getProps();
   * console.log(props.views); // Outputs the views array with content
   */
  getProps() {
    const tempView = this._props.views;
    const ret = super.getProps();
    ret.views = ret.views.map((view, index) => {
      view.content = tempView[index].content;
      return view;
    });

    return ret;
  }

  /**
   * @todo This is an experimental interface and may change at any time; it is not recommended to use it in the current version.
   * @description Adds a new tab to the component. Experimental interfaces are not recommended for use.
   * @member TComponents.Tab#addTab
   * @method
   * @param {object} tab - The tab object
   * @param {string} tab.name - The name of the tab
   * @param {string} tab.content - The content of the tab
   * @param {object} [tab.extra] - Additional optional properties (e.g. icon, children)
   * @returns {void}
   * @example
   * const tab = new TComponents.Tab(document.body, {
   *   position: 'absolute',
   *   width: 200,
   *   height: 200,
   *   zIndex: 1000,
   *   views: [
   *     {
   *       name: 'Tab 1',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *    {
   *       name: 'Tab 2',
   *       icon: 'abb-icon abb-icon-home-house_32',
   *       content: `view_${API.generateUUID()}`,
   *       children: [],
   *     },
   *   ],
   *   activeViewIndex: 0,
   * });
   *
   * // Render the component.
   * await tab.render();
   *
   * // Add a new tab.
   * const v1 = {name: 'Tab 3', content: 'test-1-001'};
   * tab.addView(v1);
   *
   * @example
   * const div1 = document.createElement('div');
   * div1.textContent = 'Hello world';
   * div1.style.fontSize = '96px';
   * div1.style.fontWeight = 'bold';
   * div1.style.textAlign = 'center';
   *
   * const v2 = {name: 'Tab 4', content: div1};
   * tab.addView(v2);
   */
  addTab(itemOptions) {
    this.addView(itemOptions);

    const lastTab = this._views[this._views.length - 1];

    const dom = this._getDom(lastTab.content, lastTab.id, lastTab.name);
    lastTab.id = this.tabContainer.addTab(Component_A.tParse(lastTab.name), dom, lastTab.icon);
    this.viewId.set(lastTab.id, lastTab.name);
  }

  /**
   * @description Removes a tab by id, content, or name.
   * Lookup priority:
   * 1. `id`
   * 2. `content`
   * 3. `name`
   * @member TComponents.Tab#removeTab
   * @method
   * @param {object} options
   * @param {string} [options.id] - The tab id.
   * @param {string|HTMLElement} [options.content] - The tab content id or content object.
   * @param {string} [options.name] - The tab display name.
   * @returns {void}
   * @example
   * // remove the tab 2.
   * tab.removeTab({name: 'Tab 2'});
   */
  removeTab({id = null, content, name} = {}) {
    const tab = this.findView({id, content, name});
    if (!tab) return;

    this._removeTab(tab);
  }

  /**
   * @description Removes a tab by its index in the views array.
   * @member TComponents.Tab#removeTabByIndex
   * @method
   * @param {number} index - Zero-based tab index.
   * @returns {void}
   * @example
   * tab.removeTabByIndex(1);
   */
  removeTabByIndex(index) {
    if (!this.isValidIndex(index)) return;

    const tab = this._views[index];
    this._removeTab(tab);
  }

  /**
   * @description Shows a tab by matching its name or content.
   * @member TComponents.Tab#showTab
   * @method
   * @param {object} options
   * @param {string} [options.id] - The tab id.
   * @param {string|HTMLElement} [options.content] - The tab content id or content object.
   * @returns {void}
   * @example
   * // Hide the tab 1.
   * tab.hideTabByIndex(0);
   *
   * // Show the tab 1;
   * tab.showTab({name: 'Tab 1'});
   */
  showTab({id = null, name, content}) {
    const tab = this.findView({id, name, content});
    this._showTab(tab);
  }

  /**
   * @description Shows a tab by its index.
   * @member TComponents.Tab#showTabByIndex
   * @method
   * @param {number} index - Zero-based tab index.
   * @returns {void}
   * @example
   * // Hide the tab 1.
   * tab.hideTabByIndex(0);
   *
   * // Show the tab 1;
   * tab.showTabByIndex(0);
   */
  showTabByIndex(index) {
    if (!this.isValidIndex(index)) return;

    const tab = this._views[index];
    this._showTab(tab);
  }

  /**
   * @description Hides a tab by matching its name or content.
   * @member TComponents.Tab#hideTab
   * @method
   * @param {object} options
   * @param {string} [options.id] - The tab id.
   * @param {string|HTMLElement} [options.content] - The tab content id or content object.
   * @returns {void}
   * @example
   * // Hide the tab 2.
   * tab.hideTab({name: 'Tab 2'});
   */
  hideTab({id = null, name, content}) {
    const tab = this.findView({id, name, content});
    this._hideTab(tab);
  }

  /**
   * @description Hides a tab by its index.
   * @member TComponents.Tab#hideTabByIndex
   * @method
   * @param {number} index - Zero-based tab index.
   * @returns {void}
   * @example
   * // Hide the tab 1.
   * tab.hideTabByIndex(0);
   */
  hideTabByIndex(index) {
    if (!this.isValidIndex(index)) return;

    const tab = this._views[index];
    this._hideTab(tab);
  }

  /**
   * @description Internal method to hide a tab and its associated view.
   * @member TComponents.Tab#_hideTab
   * @method
   * @private
   * @param {object} tab - The tab view object to hide.
   * @returns {void}
   */
  _hideTab(tab) {
    if (!tab) return;

    const index = this._views.findIndex((v) => v.id === tab.id);
    if (!this.isValidIndex(index)) return;

    const node = this.getMenuBarItem('.fp-components-tabcontainer-tabbar', index);
    if (node) {
      node.style.display = 'none';
    }

    this.hideView(tab);
  }

  /**
   * @description Internal method to show a tab and its associated view.
   * @member TComponents.Tab#_showTab
   * @method
   * @private
   * @param {object} tab - The tab view object to show.
   * @returns {void}
   */
  _showTab(tab) {
    if (!tab) return;

    const index = this._views.findIndex((v) => v.id === tab.id);
    if (!this.isValidIndex(index)) return;

    const node = this.getMenuBarItem('.fp-components-tabcontainer-tabbar', index);
    if (node) {
      node.style.display = '';
    }

    this.showView(tab);
  }

  /**
   * @description Internal method to remove a tab and its associated view.
   * @member TComponents.Tab#_removeTab
   * @method
   * @private
   * @param {object} tab - The tab view object to remove.
   * @returns {void}
   */
  _removeTab(tab) {
    this.tabContainer.removeTab(tab.id);
    this.removeView(tab);
  }

  /**
   * @description Applies the background color and rounded-corner border to the tab container
   * based on the configured tab position.
   * @member TComponents.Tab#setTabContainerBorder
   * @method
   * @protected
   * @returns {void}
   */
  setTabContainerBorder() {
    this._setTabContainerBorder();
  }

  /**
   * @member TComponents.Tab~_setTabContainerBorder
   * @method
   * @private
   * @returns {void}
   */
  _setTabContainerBorder() {
    const tabContainer = this.find('.fp-components-tabcontainer-content');
    if (Component_A._isHTMLElement(tabContainer)) {
      tabContainer.style.backgroundColor = this._props.backgroundColor;
      switch (this._props.tabPosition) {
        case 'top':
          tabContainer.style.borderRadius = `0px 0px ${this._props.borderRadius}px ${this._props.borderRadius}px`;
          break;
        case 'left':
          tabContainer.style.borderRadius = `0px ${this._props.borderRadius}px ${this._props.borderRadius}px 0px`;
          break;
        case 'right':
          tabContainer.style.borderRadius = `${this._props.borderRadius}px 0px 0px ${this._props.borderRadius}px`;
          break;
        default:
          break;
      }
    }
  }

  /**
   * @description Activates the view at the specified index.
   * @member TComponents.Tab#setActiveView
   * @method
   * @protected
   * @throws {Error} If the tab container is not initialized or the target view cannot be found.
   * @return {void}
   */
  setActiveView() {
    this._setActiveView();
  }

  /**
   * @member TComponents.Tab~_setActiveView
   * @method
   * @private
   * @throws {Error}
   * @return {void}
   */
  _setActiveView() {
    if (!this.tabContainer) throw new Error('Tab container not initialized. Please render the component first.');

    var idx = this._props.activeViewIndex || 0;
    const view = this.views[idx];

    if (view.id === this.tabContainer.activeTab) return;

    this.tabContainer.activeTab = view.id;
  }

  /**
   * @description Handles the tab click event.
   * @member TComponents.Tab#handleTabClick
   * @method
   * @protected
   * @param {Event} e - The event object.
   * @returns {void}
   */
  handleTabClick() {
    this._handleTabClick();
  }

  /**
   * @member TComponents.Tab~_handleTabClick
   * @method
   * @private
   * @param {Event}
   * @returns {void}
   */
  _handleTabClick(e) {
    try {
      // Check if the clicked element is a tab && Remove the event and use a callback instead.
      const strId = e.target && e.target.dataset && e.target.dataset.viewId;
      if (strId) {
        const index = this.views.findIndex((item) => item.content.id === strId);
        if (this._props.activeViewIndex === index) return;
        // Define the origin of the onchange event.
        this._changeSource = 'user';
        // Set up the tab container active view.
        this.activeViewIndex = index;
      }
    } catch (e) {
      Component_A.popupEventError(e, 'tabClick', logModule);
    }
  }

  /**
   * @description Callback for when the view changes.
   * @member TComponents.Tab~_cbOnChange
   * @method
   * @private
   * @param {string} oldView - The old view identifier.
   * @param {string} newView - The new view identifier.
   * @returns {void}
   */
  async _cbOnChange(oldView, newView) {
    // If oldView is invalid, it means the tab container has just been initialized.
    if (!oldView) return;

    // Only trigger the change event on user interaction.
    const source = this._changeSource;
    this._changeSource = null;

    if (source !== 'user') return;

    try {
      var fn = Component_A.genFuncTemplate(this._props.onChange, this);
      fn && (await fn(oldView, newView));
      return;
    } catch (e) {
      Component_A.popupEventError(e, 'onChange', logModule);
    }
  }
}

/**
 * @description Add css properties to the component
 * @alias loadCssClassFromString
 * @member TComponents.Tab.loadCssClassFromString
 * @method
 * @static
 * @param {string} css - The css string to be loaded into style tag
 * @returns {void}
 * @example
 * TComponents.Tab.loadCssClassFromString(`
 *   .tc-tab {
 *     background-color: #f0f0f0;
 *     border: 1px solid #ccc;
 *     border-radius: 4px;
 *   }
 * }`);
 */
Tab.loadCssClassFromString(`
.tc-tab {
  position: absolute;
  height: 100%;
  width: 100%;
  min-width: 0px;
  min-height: 0px;
  padding: 0px;
  margin: 0px;
}

.tc-tab .tabcontainer-top,
.tc-tab .tabcontainer-left,
.tc-tab .tabcontainer-right {
  display: flex;
  overflow: hidden;
}

.tc-tab .tabcontainer-top {
  flex-direction: column;
}

.tc-tab .tabcontainer-left {
  flex-direction: row;
}

.tc-tab .tabbar-left > .fp-components-tabcontainer-activetab-noclose {
  border-right: 2px solid blue;
  margin-right:0;
}

.tc-tab .tabbar-right > .fp-components-tabcontainer-activetab-noclose {
  border-left: 2px solid blue;
}

.tc-tab .tabbar-top > .fp-components-tabcontainer-activetab-noclose {
  border-bottom: 2px solid blue;
}

.tc-tab .tabcontainer-right {
  flex-direction: row-reverse;
}

.tc-tab .fp-components-tabcontainer {
  position: absolute;
  height: 100%;
  width: 100%;
  min-width: 0px;
  min-height: 0px;
  padding: 0px;
  margin: 0px;
}

.tc-tab .fp-components-tabcontainer .fp-components-tabcontainer-tabbar > * {
  background-color: transparent;
  min-width: 0px;
  min-height: 0px;
  max-width: unset;
  max-height: unset;
}

.tc-tab .fp-components-tabcontainer .fp-components-tabcontainer-tabbar {
  position: relative;
  background-color: transparent;
  padding: 0px;
  gap: 8px;
  font-size: inherit;
  -ms-overflow-style: auto !important;
  scrollbar-width: thin !important;
}

.tc-tab .fp-components-tabcontainer .tabbar-top {
  height: 55px;
  width: 100%;
  display: flex;
  flex-direction: row;
  min-height: 0px;
}

.tc-tab .fp-components-tabcontainer .tabbar-left,
.tc-tab .fp-components-tabcontainer .tabbar-right {
  height: 100%;
  width: 60px;
  display: flex;
  flex-direction: column;
}

.tc-tab .fp-components-tabcontainer-activetab {
  font-weight: inherit;
  width: auto;
  height: auto;
  min-height: 0px;
  max-height: 0px;
  display: flex;
  flex-direction: initial;
  flex: none;
  transition: none;
}

.tc-tab .fp-components-tabcontainer-tabbar::-webkit-scrollbar {
  display: block;
  width: 8px;
}

.tc-tab .fp-components-tabcontainer .fp-components-tabcontainer-tabbar-content {
  background-color: transparent;
}

.tc-tab .fp-components-tabcontainer .tabbar-content-top {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  height: 45px;
  width: 140px;
  min-width: 0px;
  max-width: 140px;
}

.tc-tab .fp-components-tabcontainer .tabbar-content-left,
.tc-tab .fp-components-tabcontainer .tabbar-content-right {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 80px;
  width: 60px;
}

.tc-tab .fp-components-tabcontainer .fp-components-tabcontainer-content {
  position: relative;
}

.tc-tab .fp-components-tabcontainer .fp-components-tabcontainer-tabbar-icon {
  height: 20px;
  width: 20px;
  margin: 6px;
  pointer-events: none;
}

.tc-tab .fp-components-tabcontainer .fp-components-tabcontainer-tabbar-text {
  display: block;
  pointer-events: none;
}

.tc-tab .tabbar-text-top {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 80px;
}

.tc-tab .tabbar-text-left,
.tc-tab .tabbar-text-right {
  height: 35px;
  width: auto;
  max-width: 50px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
`);

/**
 * Add disabled css properties to tab-type components.
 */
Tab.loadCssClassFromString(`
.tabcontainer-disabled {
  opacity:0.7;
  cursor: not-allowed !important;
}

.tabcontainer-disabled .fp-components-tabcontainer-tabbar,
.tabcontainer-disabled .fp-components-tabcontainer-content {
  pointer-events: none;
}  
`);