as-varincrdecr.js

import {Component_A} from './basic/as-component.js';
import {Button} from './as-button.js';
import {Input} from './as-input.js';
import {ErrorCode, ExceptionIdMap} from '../exception/exceptionDesc.js';

/**
 * @typedef TComponents.VarIncrDecrProps
 * @prop {object} [options] Additional options for the VarIncrDecr component.
 * Items in this object include:
 * - **responsive** (boolean, default: false): Whether the component should be responsive.
 * @prop {string} [tips] Tooltip text for the component.
 * @prop {Function} [onCreated] Function to be called when the component is created.
 * @prop {Function} [onMounted] Function to be called when the component is mounted.
 * @prop {string} [position] CSS position property.
 * @prop {number} [width] Width of the component.
 * @prop {number} [height] Height of the component.
 * @prop {number} [top] Top position of the component.
 * @prop {number} [left] Left position of the component.
 * @prop {number} [borderRadius] Border radius of the component.
 * @prop {number} [rotation] Rotation angle of the component.
 * @prop {number} [zIndex] Z-index of the component.
 * @prop {string} [border] Border style of the component.
 * @prop {string} [color] Text color of the variable field.
 * @prop {string} [size] Size style template of the component.
 * @prop {object} [font] Font configuration for the variable value.
 * This object controls text appearance:
 * - **fontSize** (number, default: 12): Font size in pixels.
 * - **fontFamily** (string, default: 'Segoe UI'): Font family name.
 * @prop {boolean} [readOnly] If true, the value is displayed and can only be changed by increment/decrement.
 * @prop {boolean} [useBorder] Whether to draw a border around the variable field.
 * @prop {object} [regex] Regular expression configuration for validation.
 * This object defines validation behavior:
 * - **label** (string, default: 'Number'): Description of the regex rule.
 * - **value** (string): Regex pattern used to validate the input.
 * @prop {string|number} [text] Initial text value bound to the variable.
 * @prop {object} [inputVar] Input variable binding configuration.
 * This object configures the bound input variable:
 * - **type** (string): Binding variable type.
 * - **func** (string): Binding mode.
 * - **value** (string): Initial value or variable path used for binding.
 * - **isHidden** (boolean, default: false): Whether this binding is hidden in variable selectors.
 * @prop {Function} [onChange] Callback function when the value changes.
 * @prop {string} [keyboardHelperDesc] Description text shown in the keyboard helper.
 * @prop {string|number} [step] Increment/decrement step applied on each button click.
 * @prop {object} [numRange] Numeric range configuration.
 * This object controls allowed value range:
 * - **min** (number|null): Minimum allowed value.
 * - **max** (number|null): Maximum allowed value.
 * @prop {string} [defaultState] Default state of the component.
 * @prop {string} [dataStruct] Custom data structure metadata for integration with other systems.
 * @memberof TComponents
 */

/**
 * @ignore
 */
const logModule = 'as-varincrdecr';

/**
 * Component connected to a variable together with increment and decrement buttons.
 * This class focuses on the specific properties of the VarIncrDecr component'.
 * Since it inherits from Accessor_A, all basic properties (e.g., height, width) are available but documented in the Accessor_A part.
 * @class TComponents.VarIncrDecr
 * @extends TComponents.Component_A
 * @memberof TComponents
 * @param {HTMLElement} parent - DOM element in which this component is to be inserted
 * @param {TComponents.VarIncrDecrProps} [props] - Properties of the component
 * @property {TComponents.VarIncrDecrProps} _props - Properties of the component
 * @property {Object|null} _bindData - Data binding information
 * @example
 * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
 *   position: 'absolute',
 *   zIndex: 1000,
 * });
 *
 * // Render the component.
 * await varIncrDecr.render();
 */
export class VarIncrDecr extends Component_A {
  constructor(parent, props = {}) {
    super(parent, props);

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

    /**
     * If bound to web data, `this._bindData` will have the format: { type: 'webdata', key: 'xxx' }.
     * If bound to digital signal data, `this._bindData` will have the format: { type: 'digitalsignal', key: 'xxxx' }.
     * If bound to rapid data, `this._bindData` will have the format: { type: 'rapiddata', dataType: 'xxx', module: 'xxx', name: 'xxx', task: 'xxx' }.
     * @type {object|null}
     */
    this._bindData = null;
  }

  /**
   * @returns {string}
   */
  get text() {
    return this.child.var.text;
  }

  /**
   * @description Sets the text property. (recommended to use value instead)
   * @member {string} TComponents.VarIncrDecr#text
   * @instance
   * @param {string} text - The new text value
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * // Sets the text to '100'
   * varIncrDecr.text = '100';
   */
  set text(text) {
    this.value = text;
  }

  /**
   * @returns {string}
   */
  get textX() {
    return this.text;
  }

  /**
   * @description Sets the text property silently.
   * This will update the value but will NOT trigger the `onchange` event.
   * Recommended for internal or programmatic updates, where change notification is not desired.
   * @member {string} TComponents.VarIncrDecr#textX
   * @instance
   * @param {string} text - The new text value
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body);
   * await varIncrDecr.render();
   *
   * // Silent update (onchange will NOT be triggered)
   * varIncrDecr.textX = '100';
   */
  set textX(text) {
    this.valueX = text;
  }

  /**
   * @returns {number}
   */
  get value() {
    const text = this.child.var.text;
    if (typeof text == 'string' && !isNaN(Number(text))) {
      return Number(text);
    }
    return text;
  }

  /**
   * @description Sets the value property. (TBD)
   * @member {number} TComponents.VarIncrDecr#value
   * @instance
   * @param {string|number} value - The new value
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * // Sets the value to 100
   * varIncrDecr.value = 100;
   */
  set value(value) {
    if (this._props.text === value) {
      return;
    }
    if (typeof value == 'string' && !isNaN(Number(value))) {
      this.child.var.text = Number(value);
      return;
    }
    this.child.var.text = value;
  }

  /**
   * @returns {number}
   */
  get valueX() {
    return this.value;
  }

  /**
   * @description Sets the value silently.
   * If the value is numeric (or a numeric string), it will be converted to Number automatically. This will NOT trigger the `onchange` event.
   * Recommended when updating the component state programmatically without notifying listeners.
   * @member {number|string} TComponents.VarIncrDecr#valueX
   * @instance
   * @param {number|string} value - The new value
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body);
   * await varIncrDecr.render();
   *
   * // Silent update (onchange will NOT be triggered)
   * varIncrDecr.valueX = 100;
   */
  set valueX(value) {
    if (this._props.text === value) {
      return;
    }
    if (typeof value == 'string' && !isNaN(Number(value))) {
      this.child.var.textX = Number(value);
    } else {
      this.child.var.textX = value;
    }
    this.commitProps({text: value});
  }

  /**
   *
   * @returns {boolean}
   */
  get useBorder() {
    return this._props.useBorder;
  }

  /**
   * @description Sets the useBorder property.
   * @member {boolean} TComponents.VarIncrDecr#useBorder
   * @instance
   * @param {boolean} b - The new useBorder value
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * varIncrDecr.useBorder = true; // Sets the useBorder to true
   */
  set useBorder(b) {
    this.setProps({useBorder: b});
  }

  /**
   *
   * @returns {number|string}
   */
  get step() {
    return this._props.step;
  }

  /**
   * @description Sets the step value.
   * @member {number} TComponents.VarIncrDecr#step
   * @instance
   * @param {number|string} n - The new step value
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * // Sets the step to 5
   * varIncrDecr.step = 5;
   */
  set step(n) {
    this.setProps({
      step: n,
    });
  }

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

  /**
   * @description Sets the keyboard helper description.
   * @member {string} TComponents.VarIncrDecr#keyboardHelperDesc
   * @instance
   * @param {string} s - The new keyboard helper description
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * varIncrDecr.keyboardHelperDesc = 'Press + or - to increment or decrement the value'; // Sets the keyboard helper description
   */
  set keyboardHelperDesc(s) {
    this.setProps({
      keyboardHelperDesc: s,
    });
  }

  /**
   * @description Returns the default values of class properties (excluding parent properties).
   * @member TComponents.VarIncrDecr#defaultProps
   * @method
   * @protected
   * @returns {TComponents.VarIncrDecrProps}
   */
  defaultProps() {
    return {
      options: {
        responsive: false,
      },
      tips: '',
      // life cycle
      onCreated: '',
      onMounted: '',
      onDispose: '',
      // basic properties: X/Y/W/H/B/R/Z
      position: 'static',
      width: 120,
      height: 32,
      top: 0,
      left: 0,
      borderRadius: 4,
      rotation: 0,
      zIndex: 0,
      // border
      border: '1px solid #dbdbdb',
      // color
      color: 'black',
      // size template
      size: '',
      // font
      font: {
        fontSize: 12,
        fontFamily: 'Segoe UI',
      },
      // Special properties.
      readOnly: false,
      useBorder: true,
      regex: {
        label: 'Number',
        value: '^-?[0-9]+(\\.[0-9]+)?$',
      },
      // Input variable binding properties.
      text: '0',
      inputVar: {
        type: 'num', // 'any' | 'string' | 'num' | 'bool'
        func: 'custom', // 'custom' | 'sync'
        value: '0', // string
        isHidden: false,
      },
      onChange: '',
      // Special properties.
      keyboardHelperDesc: 'No description',
      step: '1',
      numRange: {
        min: null,
        max: null,
      },
      defaultState: 'show_enable',
      dataStruct: '',
    };
  }

  /**
   * @description Initialize the component.
   * @member TComponents.VarIncrDecr#onInit
   * @method
   * @async
   * @returns {void}
   */
  async onInit() {}

  /**
   * @description Handler for change events.
   * @member TComponents.VarIncrDecr~_onChange
   * @method
   * @private
   * @param {any} value - The new value
   * @returns {void}
   */
  async _onChange(value) {
    const oldValue = this._props.text;
    try {
      const numValue = Number(value);
      await this.syncInputData(numValue);
      this.child.var.textX = numValue;
    } catch (e) {
      this.child.var.textX = oldValue;
      Component_A.popupEventError(e, 'syncInputData', logModule);
      return;
    }

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

  /**
   * @description Maps the internal components.
   * @member TComponents.VarIncrDecr#mapComponents
   * @method
   * @returns {object} The mapped components
   */
  mapComponents() {
    const varIns = new Input(this.find('.tc-varincrdecr-var'), {
      options: {
        responsive: true,
      },
      height: 32,
      width: 50,
      border: 'none',
      onChange: this._cbOnChange.bind(this),
      keyboardHelperDesc: this._props.keyboardHelperDesc,
      text: this._props.text,
      regex: this._props.regex,
      font: this._props.font,
    });
    varIns.enabled = this.enabled;

    const incrValueIns = new Button(this.find('.tc-varincrdecr-incr'), {
      options: {
        responsive: true,
      },
      height: 32,
      width: 25,
      backgroundColor: '#ccc',
      onClick: this.cbIncr.bind(this),
      text: '+',
      color: 'black',
      border: 'none',
      borderRadius: 0,
    });
    incrValueIns.enabled = this.enabled;

    const decrValueIns = new Button(this.find('.tc-varincrdecr-decr'), {
      options: {
        responsive: true,
      },
      height: 32,
      width: 25,
      backgroundColor: '#ccc',
      onClick: this.cbDecr.bind(this),
      text: '-',
      color: 'black',
      border: 'none',
      borderRadius: 0,
    });
    decrValueIns.enabled = this.enabled;

    // Bind on-change event.
    // varIns.cbOnChange = this.cbOnChange.bind(this);
    varIns.on('render', () => {
      varIns.container.style.cssText = '';
      varIns.find('.fp-components-input').style.cssText = `color: ${this._props.color};`;
    });

    return {
      var: varIns,
      incrValue: incrValueIns,
      decrValue: decrValueIns,
    };
  }

  /**
   * @description Renders the component.
   * @member TComponents.VarIncrDecr#onRender
   * @method
   * @async
   * @throws {Error}
   * @returns {Promise<void>} A promise that resolves when the component is rendered.
   */
  async onRender() {
    try {
      this.removeAllEventListeners();

      if (this._props.inputVar.func == Component_A.INPUTVAR_FUNC.SYNC) {
        // Parse bind data value.
        this._bindData = Component_A.getBindData(this._props.inputVar.value, this);
      }

      const fpWrapEl = this.find('.fp-components-varincrdecr');
      if (Component_A._isHTMLElement(fpWrapEl)) {
        fpWrapEl.style.border = this._props.useBorder ? this._props.border : 'none';
        fpWrapEl.style.boxSizing = 'border-box';
        fpWrapEl.style.borderRadius = `${this._props.borderRadius}px`;
      }

      // render tc-item style.
      this._renderChildComponent();
      this._addTips();
      Component_A.resolveBindingExpression(this._props.text, this, 'textX');
    } catch (e) {
      // Runtime errors: write specific content to the log, throw error code
      Logger.e(
        logModule,
        ErrorCode.FailedToRunOnRender,
        `Error happens on onRender of VarIncrDecr component ${this.compId}.`,
        e,
      );
      throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
    }
  }

  /**
   * @description Returns the HTML markup for the component.
   * @member TComponents.VarIncrDecr#markup
   * @method
   * @returns {string} The HTML markup
   */
  markup() {
    return /*html*/ `
    <div class="tc-varincrdecr">
      <div class="fp-components-varincrdecr">
        <div class="tc-varincrdecr-decr tc-item"></div>
        <div class="tc-varincrdecr-var tc-item"></div>
        <div class="tc-varincrdecr-incr tc-item"></div>
      </div>
    </div>
    `;
  }

  /**
   * @description Adjusts the layout of the component responsively.
   * @member TComponents.VarIncrDecr~_renderChildComponent
   * @method
   * @private
   * @returns {void}
   */
  _renderChildComponent() {
    // Force remove the child component's width and height.
    this.child.var.container.classList.add('tc-varincrdecr-input');
    this.child.var.container.style.cssText = '';

    this.child.incrValue.container.classList.add('tc-varincrdecr-button');
    this.child.incrValue.container.style.cssText = `0px ${this._props.borderRadius}px ${this._props.borderRadius}px 0px`;

    this.child.decrValue.container.classList.add('tc-varincrdecr-button');
    this.child.decrValue.container.style.cssText = `${this._props.borderRadius}px 0px 0px ${this._props.borderRadius}px`;

    const fontEls = this.all('.fp-components-button-text');
    for (let index = 0; index < fontEls.length; index++) {
      const el = fontEls[index];
      el.style.fontSize = `${this._props.font.fontSize}px`;
      el.style.fontFamily = this._props.font.fontFamily;
      el.style.color = this._props.color;
    }

    const varFont = this.find('.fp-components-input');
    if (Component_A._isHTMLElement(varFont)) {
      varFont.style.fontSize = `${this._props.font.fontSize}px`;
      varFont.style.fontFamily = this._props.font.fontFamily;
      varFont.style.color = this._props.color;
    }
  }

  /**
   * @description Callback function which is called when the button is pressed, it triggers any function registered with {@link onChange() onChange}
   * @member TComponents.VarIncrDecr~_cbOnChange
   * @param {any} value - The new value
   * @private
   * @async
   * @retures {Promise<void>}
   * @param {*} value
   */
  async _cbOnChange(value) {
    return this.cbOnChange(value);
  }

  /**
   * @description Callback function which is called when the button is pressed, it triggers any function registered with {@link onChange() onChange}
   * @member TComponents.VarIncrDecr#cbOnChange
   * @param {any} value - The new value
   * @deprecated This method is deprecated and will be removed in future versions. Please use `_cbOnChange` instead.
   * @private
   * @async
   * @retures {Promise<void>}
   * @throws {Error} If the value exceeds the defined range, an error is thrown and a popup is displayed.
   * @example
   * // Example usage:
   * varIncrDecr.cbOnChange(100); // Sets the value to 100 and triggers onChange event
   */
  async cbOnChange(value) {
    const numValue = Number(value);

    if (this._props.numRange.max != null && numValue > this._props.numRange.max) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {
          cause: `The value cannot exceed the maximum value. Maximum value: ${this._props.numRange.max}`,
        }),
        'onChange',
        logModule,
      );
      this.child.var.text = this._props.numRange.max;
      return;
    }

    if (this._props.numRange.min != null && numValue < this._props.numRange.min) {
      // User operation failed, log detailed error stack information to the console
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {
          cause: `The value cannot be less than the minimum value. Minimum value: ${this._props.numRange.min}`,
        }),
        'onChange',
        logModule,
      );
      this.child.var.text = this._props.numRange.min;
      return;
    }

    this._onChange(value);
  }

  /**
   * @description Callback function to update variable when increment button is clicked.
   * @member TComponents.VarIncrDecr#cbIncr
   * @method
   * @private
   * @async
   * @throws {Error} If the value exceeds the defined range, an error is thrown and a popup is displayed.
   * @returns {Promise<void>}
   */
  async cbIncr() {
    const value = Number(this.child.var.text);
    if (isNaN(value)) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {cause: 'The value is not a valid number.'}),
        'Increment',
        logModule,
      );
      return;
    }

    // fix float number
    const incrVal = typeof this._props.step == 'number' ? this._props.step : Number(this._props.step);
    const decimalPlaces = Math.max(
      (value.toString().split('.')[1] || '').length,
      (incrVal.toString().split('.')[1] || '').length,
    );
    const incrValue = Number((value + incrVal).toFixed(decimalPlaces));
    if (this._props.numRange.max != null && incrValue > this._props.numRange.max) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {
          cause: `The value cannot exceed the maximum value. Maximum value: ${this._props.numRange.max}`,
        }),
        'Increment',
        logModule,
      );
      return;
    }
    if (this._props.numRange.min != null && incrValue < this._props.numRange.min) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {
          cause: `The value cannot be less than the minimum value. Minimum value: ${this._props.numRange.min}`,
        }),
        'Increment',
        logModule,
      );
      return;
    }
    this._onChange(incrValue);
  }

  /**
   * @description Callback function to update variable when decrement button is clicked.
   * @member TComponents.VarIncrDecr#cbDecr
   * @method
   * @private
   * @async
   * @throws {Error} If the value is not a valid number or if it goes below the minimum value, an error is thrown and a popup is displayed.
   * @returns {Promise<void>}
   */
  async cbDecr() {
    const value = Number(this.child.var.text);
    if (isNaN(value)) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {cause: 'The value is not a valid number.'}),
        'Decrement',
        logModule,
      );
      return;
    }
    // fix float number
    const decrVal = typeof this._props.step == 'number' ? this._props.step : Number(this._props.step);
    const decimalPlaces = Math.max(
      (value.toString().split('.')[1] || '').length,
      (decrVal.toString().split('.')[1] || '').length,
    );
    const decrValue = Number((value - decrVal).toFixed(decimalPlaces));
    if (this._props.numRange.min != null && decrValue < this._props.numRange.min) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {
          cause: `The value cannot be less than the minimum value. Minimum value: ${this._props.numRange.min}`,
        }),
        'Decrement',
        logModule,
      );
      return;
    }
    if (this._props.numRange.max != null && decrValue > this._props.numRange.max) {
      Component_A.popupEventError(
        new Error(ErrorCode.FailedToSetNum, {
          cause: `The value cannot exceed the maximum value. Maximum value: ${this._props.numRange.max}`,
        }),
        'Decrement',
        logModule,
      );
      return;
    }

    this._onChange(decrValue);
  }

  /**
   * @returns {Function|undefined}
   */
  get onChange() {
    const fn = Component_A.genFuncTemplate(this._props.onChange, this);
    if (typeof fn == 'function') return fn;
    else return undefined;
  }

  /**
   * @description Sets the `onChange` event handler.
   * The handler can either be a string representing a function to be executed or a function itself.
   * 1. If you are using an arrow function, like `()=>{}`,
   * the `this` property of the scope may not refer to the varIncrDecr object.
   * 2. If you are using string assignment to define code execution,
   * the string should contain `only the body of the code (executable statements)`,
   * not a complete function declaration. Therefore, including function keywords like function or async function is incorrect.
   * - Correct (Statements Only): `xx.onChange = "console.log('Action done.');"`
   * - Incorrect (Function Declaration): `xx.onChange = "function() { console.log('Action done.'); }"`
   * @member {Function} TComponents.VarIncrDecr#onChange
   * @instance
   * @returns {void}
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * // Example 1: Using a string as the handler:
   * varIncrDecr.onChange = "console.log('state changed', this.text);";
   * @example
   * // Example 2: Using a arrow function as the handler:
   * // Note that the `this` context will not refer to the varIncrDecr object
   * varIncrDecr.onChange = () => { console.log('state changed', varIncrDecr.text); };
   * @example
   * // Example 3: Using a common function as the handler:
   * varIncrDecr.onChange = async function() {
   *   console.log('state changed', this.text);
   * };
   */
  set onChange(t) {
    this.setProps({onChange: t});
  }

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

  /**
   * @description Sets the maximum value in the number range.
   * @member {number} TComponents.VarIncrDecr#max
   * @instance
   * @param {number} t - The new maximum value.
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * varIncrDecr.max = 100; // Sets the maximum value to 100
   */
  set max(t) {
    if (typeof t !== 'number') {
      throw new Error(ErrorCode.FailedToSetNum, {
        cause: `The max value must be a number.`,
      });
    }
    if (t <= this.min || t < this.value) {
      throw new Error(ErrorCode.FailedToSetMax, {
        cause: `The max value must be larger than min value`,
      });
    }
    const numRange = {
      min: this._props.numRange.min,
      max: t,
    };
    this.setProps({numRange: numRange});
  }

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

  /**
   * @description Sets the minimum value in the number range.
   * @member {number} TComponents.VarIncrDecr#min
   * @instance
   * @param {number} t - The new minimum value.
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * varIncrDecr.min = 0; // Sets the minimum value to 0
   */
  set min(t) {
    if (typeof t !== 'number') {
      throw new Error(ErrorCode.FailedToSetNum, {
        cause: `The min value must be a number.`,
      });
    }
    if (t >= this.max || t > this.value) {
      throw new Error(ErrorCode.FailedToSetMin, {
        cause: `The min value must be less than max value`,
      });
    }
    const numRange = {
      min: t,
      max: this._props.numRange.max,
    };
    this.setProps({numRange: numRange});
  }

  /**
   * @deprecated Use {@link TComponents.VarIncrDecr#max} instead.
   */
  get maxValue() {
    return this._props.numRange.max;
  }

  /**
   * @deprecated Use {@link TComponents.VarIncrDecr#max} instead.
   * @description Sets the maximum value in the number range.
   * @member {number} TComponents.VarIncrDecr#maxValue
   * @instance
   * @param {number} t - The new maximum value.
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * varIncrDecr.maxValue = 100; // Sets the maximum value to 100
   */
  set maxValue(n) {
    this.setProps({
      numRange: {
        max: n,
      },
    });
  }

  /**
   * @deprecated Use {@link TComponents.VarIncrDecr#min} instead.
   */
  get minValue() {
    return this._props.numRange.min;
  }

  /**
   * @deprecated Use {@link TComponents.VarIncrDecr#min} instead.
   * @description Sets the minimum value in the number range.
   * @member {number} TComponents.VarIncrDecr#min
   * @instance
   * @param {number} t - The new minimum value.
   * @example
   * const varIncrDecr = new TComponents.VarIncrDecr(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * await varIncrDecr.render();
   *
   * varIncrDecr.minValue = 0; // Sets the minimum value to 0
   */
  set minValue(n) {
    this.setProps({
      numRange: {
        min: n,
      },
    });
  }
}

/**
 * @description Add css properties to the component
 * @member TComponents.VarIncrDecr.loadCssClassFromString
 * @method
 * @static
 * @param {string} css - The css string to be loaded into style tag
 * @returns {void}
 * @example
 * TComponents.VarIncrDecr.loadCssClassFromString(`
 *   .tc-varincrdecr {
 *     height: inherit;
 *   }`
 * );
 */
VarIncrDecr.loadCssClassFromString(/*css*/ `
.tc-varincrdecr {
  height: inherit;
  width: 100%;
  min-width: 0px;
  min-height: 0px;
  padding: 0px;
  margin: 0px;
}
 
.tc-varincrdecr .fp-components-varincrdecr-disabled,
.tc-varincrdecr .fp-components-varincrdecr {
  height: 100%;
  width: 100%;
  padding: 0px;
  margin: 0px;
  min-width: 0px;
  min-height: 0px;
  display: flex;
  flex-direction: row;
  border: 1px groove #ccc;
  overflow: hidden;
}

.tc-varincrdecr .fp-components-varincrdecr-disabled{
  cursor:not-allowed !important;
}
.tc-varincrdecr .fp-components-varincrdecr:hover{
  opacity:0.7;
}

.fp-components-varincrdecr .t-component{
padding: 0px !important;
margin: 0px !important;
}

.tc-varincrdecr .fp-components-button,
.tc-varincrdecr .fp-components-button-disabled {
  border: none;
  border-radius: 0px;
  padding: 0px;
  min-width: 30px;
}
 
.tc-varincrdecr .fp-components-input {
  border: none;
  border-radius: 0px;
  min-height: 0px;
  justify-content: center;
}
 
.tc-varincrdecr .tc-container-row {
  align-items: center;
  height: 100%;
  width: 100%;
}
 
.tc-varincrdecr .fp-components-button-text {
  font-weight: bold;
  font-size: 18px;
  line-height: 15px;
}
 
.tc-varincrdecr .tc-item {
  padding: 0px;
}
 
.tc-varincrdecr .tc-item > .t-component {
  height: 100%;
  width: 100%;
  padding:0 !important;
  margin:0 !important;

}
 
.tc-varincrdecr .tc-varincrdecr-incr,
.tc-varincrdecr .tc-varincrdecr-decr {
  height: 100%;
  width: 25%;
}
 
.tc-varincrdecr .tc-varincrdecr-var {
  height: 100%;
  width: 50%;
}
 
.tc-varincrdecr .tc-varincrdecr-button,
.tc-varincrdecr .tc-button,
.tc-varincrdecr .fp-components-button,
.tc-varincrdecr .tc-varincrdecr-input,
.tc-varincrdecr .tc-input {
  height: 100% !important;
  width: 100% !important;
}
 
.tc-varincrdecr .fp-components-input-container {
  height: 100%;
  width: 100%;
  padding-top: 0px;
  padding-bottom: 0px;
}
 
.tc-varincrdecr .fp-components-input {
  display: flex;
  align-items: center;
}
 
`);