as-slider-var.js

import {Component_A} from './basic/as-component.js';
import {Slider} from './as-slider.js';
import {ErrorCode} from '../exception/exceptionDesc.js';

/**
 * @typedef TComponents.VarSliderProps
 * @prop {object} [options] General options for the slider behavior.
 * Items in this object include:
 * - **responsive** (boolean, default: false): Whether the slider width should adapt to the container width.
 * @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 {Function} [onPointerRelease] Callback function that is called when the pointer is released after dragging the slider.
 * @prop {Function} [onPointerDown] Callback function that is called while the pointer is dragging the slider.
 * @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} [color] Font color of the description label.
 * @prop {object} [font] Font configuration for the description label.
 * This object controls text appearance:
 * - **fontSize** (number): Font size in pixels.
 * - **fontFamily** (string): Font family name.
 * - **style** (object): Font style configuration, containing `fontStyle`, `fontWeight`, `textDecoration`.
 * @prop {string} [rangeValueColor] Font color of the min/max range values.
 * @prop {object} [rangeValueFont] Font configuration for the min/max range values.
 * This object controls range text appearance:
 * - **fontSize** (number): Font size in pixels.
 * - **fontFamily** (string): Font family name.
 * - **style** (object): Font style configuration, containing `fontStyle`, `fontWeight`, `textDecoration`.
 * @prop {string} [backgroundColor] Background color of the slider container.
 * @prop {string} [border] CSS border style of the slider container.
 * @prop {boolean} [displayLabel] Whether to display the description label.
 * @prop {string} [descrLabel] Text content of the description label.
 * @prop {boolean} [displayValue] Whether to display the current value label.
 * @prop {number} [value] Current value of the slider.
 * @prop {number} [min] Minimum value of the slider range.
 * @prop {number} [max] Maximum value of the slider range.
 * @prop {boolean} [enabled] Whether the slider is enabled and interactive.
 * @prop {string} [activeColor] Color of the active (filled) part of the slider track.
 * @prop {string} [inactiveColor] Color of the inactive part of the slider track.
 * @prop {boolean} [displayTicks] Whether to display tick marks along the track.
 * @prop {number} [tickStep] Step between tick marks and value increments.
 * @prop {object} [inputVar] Input variable binding configuration used in `VarSlider`.
 * This object configures the bound input variable:
 * - **type** (string): Binding variable type. Default is `'group'`.
 * - **func** (string): Binding mode. Default is `'custom'`.
 * - **value** (number|string): Initial value or variable path used for binding.
 * - **isHidden** (boolean, default: false): Whether this binding is hidden in variable selectors.
 * @prop {(string|number)} [text] Value that is parsed and written into the slider `value` in `VarSlider`.
 * @prop {string} [defaultState] Initial state of the component.
 * @prop {string} [dataStruct] Custom data structure metadata for integration with other systems.
 * @memberof TComponents
 */

/**
 * @ignore
 */
const logModule = 'as-slider-var';

/**
 * @description Slider element. Additional callbacks can be added with the {@link TComponents.Slider#onChange|onChange} method.
 * This class focuses on the specific properties of the SliderVar 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.VarSlider
 * @extends TComponents.Slider
 * @memberof TComponents
 * @param {HTMLElement} parent - HTML element that is going to be the parent of the component
 * @param {TComponents.VarSliderProps} props
 * @example
 * const slider = new TComponents.VarSlider(document.body,{
 *  position: 'absolute',
 *   zIndex: 1000,
 * });
 *
 * // Render the component.
 * slider.render();
 */
export class VarSlider extends Slider {
  constructor(parent, props = {}) {
    super(parent, props);

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

    /**
     * If bound to web data, `this._bindData` will have the format: { type: 'webdata', key: 'xxx' }.
     * If bound to group signal data, `this._bindData` will have the format: { type: 'groupsignal', key: 'xxxx' }.
     * If bound to rapid data, `this._bindData` will have the format: { type: 'rapiddata', dataType: 'xxx', module: 'xxx', name: 'xxx', task: 'xxx' }.
     */
    this._bindData;
  }

  /**
   * @description Returns the default values of class properties (excluding parent properties).
   * @member TComponents.VarSlider#defaultProps
   * @method
   * @protected
   * @returns {TComponents.VarSliderProps}
   */
  defaultProps() {
    const parentProps = super.defaultProps();
    return Object.assign({}, parentProps, {
      // Bind variable properties
      inputVar: {
        type: 'group', // 'any' | 'string' | 'num' | 'bool' | 'group'
        func: 'custom', // 'custom' | 'sync'
        value: 30,
        isHidden: false,
      },
      text: '',
      options: {
        responsive: false,
      },
      dataStruct: '',
    });
  }

  /**
   * @description there are something need to do after render once
   * @member TComponents.VarSlider#afterRenderOnce
   * @method
   * @protected
   * @returns {void}
   */
  afterRenderOnce() {
    if (this._props.inputVar.func == 'sync') {
      this._bindData = Component_A.getBindData(this._props.inputVar.value, this);
    }
  }

  /**
   * @description Renders the slider component.
   * @member TComponents.VarSlider#onRender
   * @method
   * @throws {Error} Throws an error if rendering fails.
   * @returns {void}
   */
  onRender() {
    try {
      this.removeAllEventListeners();
      super.onRender();

      this.text = this._props.text;
      Component_A.resolveBindingExpression(this._props.text, this);
    } catch (e) {
      // Runtime errors: write specific content to the log, throw error code
      Logger.e(
        logModule,
        ErrorCode.FailedToRunOnRender,
        `Error happens on onRender of slider component ${this.compId}.`,
        e.toString(),
      );
      throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
    }
  }

  /**
   * @description Pointer up/leave event handler.
   * @member TComponents.VarSlider~_onPointerRelease
   * @method
   * @private
   * @async
   * @param {number} value - The current value of the slider when pointer is released
   * @returns {Promise<void>}
   */
  async _onPointerRelease(value) {
    if (this.enabled) {
      let tempVal = value;
      const oldVal = this._props.text;
      try {
        await this.syncInputData(tempVal);
      } catch (e) {
        // Restore to the previous state.
        this._slider.value = oldVal;
        this.commitProps({text: oldVal});
        Component_A.popupEventError(e, 'syncInputData', logModule);
        return;
      }

      // if (this.props.inputVar.func == Component_A.INPUTVAR_FUNC.CUSTOM) {
      this._slider.value = value;
      this.commitProps({text: value});
      // }

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

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

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

  /**
   * @description Set current text value
   * @member {number} TComponents.VarSlider#text
   * @instance
   * @param {string|number} t - The text value to be set
   * @throws {Error}
   * @example
   * const slider = new TComponents.VarSlider(document.body,{
   *  position: 'absolute',
   *   zIndex: 1000,
   * });
   *
   * // Render the component.
   * slider.render();
   *
   * // Set the text.
   * slider.text = 20;
   */
  set text(t) {
    try {
      if (typeof t === 'number') {
        const validValue = this.checkValueInRange(t);
        this.commitProps({text: validValue, value: validValue});
        this._slider.value = validValue;
      } else if (typeof t === 'string') {
        if (t === '') return;
        let tmp = Number(t);
        if (typeof tmp === 'number' && !isNaN(tmp)) {
          const validValue = this.checkValueInRange(tmp);
          this.commitProps({text: validValue, value: validValue});
          this._slider.value = validValue;
        }
      } else {
        throw new Error(ErrorCode.FailedToSetText);
      }
    } catch (error) {
      Logger.e(logModule, ErrorCode.FailedToSetText, `Invalid text data for component ${this.compId}.`, t);
      throw new Error(ErrorCode.FailedToSetText, {cause: error});
    }
  }

  /**
   * @description  Check if the value is in the range of min and max
   * @member TComponents.VarSlider#checkValueInRange
   * @method
   * @param {number} value - The value to be checked
   * @returns {number} - The valid value within the range
   */
  checkValueInRange(value) {
    if (value > this.max || value < this.min) {
      Logger.w(
        logModule,
        ErrorCode.FailedToSetNum,
        'The value parameter must be between the min value and the max value.',
        value,
      );
      if (value > this.max) {
        return this.max;
      }
      if (value < this.min) {
        return this.min;
      }
    }
    return value;
  }
}

/**
 * @description Add css properties to the component
 * @member TComponents.VarSlider.loadCssClassFromString
 * @method
 * @static
 * @param {string} css - The css string to be loaded into style tag
 * @returns {void}
 * @example
 * TComponents.VarSlider.loadCssClassFromString(`
 * .tc-var-slider {
 *   height: inherit;
 *  }`
 * );
 */
VarSlider.loadCssClassFromString(/*css*/ `
.tc-var-slider {
  height: inherit;
  width: 100%;
  min-width: 0px;
  min-height: 0px;
  padding: 0px;
  margin: 0px;
}
`);