constructor(component, options, data) {
    super(Object.assign({
      renderMode: 'form',
      attachMode: 'full',
      noDefaults: false
    }, options || {}));

    // Restore the component id.
    if (component && component.id) {
      this.id = component.id;
    }

    /**
     * Determines if this component has a condition assigned to it.
     * @type {null}
     * @private
     */
    this._hasCondition = null;

    /**
     * The row index for this component.
     */
    this._rowIndex = undefined;

    /**
     * References to dom elements
     */
    this.refs = {};

    // Allow global override for any component JSON.
    if (
      component &&
      this.options.components &&
      this.options.components[component.type]
    ) {
      _.merge(component, this.options.components[component.type]);
    }

    /**
     * An array of all the children components errors.
     */
    this.childErrors = [];

    /**
     * Last validation errors that have occured.
     */
    this._errors = [];
    this._visibleErrors = [];

    /**
     * The Form.io component JSON schema.
     * @type {*}
     */
    this.component = this.mergeSchema(component || {});

    // Add the id to the component.
    this.component.id = this.id;

    this.afterComponentAssign();

    // Save off the original component to be used in logic.
    this.originalComponent = fastCloneDeep(this.component);

    /**
     * If the component has been attached
     */
    this.attached = false;

    /**
     * If the component has been rendered
     */
    this.rendered = false;

    /**
     * The data object in which this component resides.
     * @type {*}
     */
    this._data = data || {};

    /**
     * Tool tip text after processing
     * @type {string}
     */
    this.tooltip = '';

    /**
     * The row path of this component.
     * @type {number}
     */
    this.row = this.options.row;

    /**
     * Points to a flat map of child components (if applicable).
     * @type {object}
     */
    this.childComponentsMap = {};

    /**
     * Determines if this component is disabled, or not.
     * @type {boolean}
     */
    this._disabled = boolValue(this.component.disabled) ? this.component.disabled : false;

    /**
     * Points to the root component, usually the FormComponent.
     * @type {Component}
     */
    this.root = this.options.root || this;
    this.localRoot = this.options.localRoot || this;

    /**
     * If this input has been input and provided value.
     * @type {boolean}
     */
    this.pristine = true;

    /**
     * Points to the parent component.
     * @type {Component}
     */
    this.parent = this.options.parent;

    /**
     * The component paths for this component.
     * @type {import('@formio/core').ComponentPaths} - The component paths.
     */
    this.paths = FormioUtils.getComponentPaths(this.component, this.parent?.component, {
      ...this.parent?.paths,
      dataIndex: this.options.rowIndex === undefined ? this.parent?.paths?.dataIndex : this.options.rowIndex
    });
    this.options.name = this.options.name || 'data';

    this._path = '';

    // Needs for Nextgen Rules Engine
    this.resetCaches();

    /**
     * Determines if this component is visible, or not.
     */
    this._parentVisible = this.options.hasOwnProperty('parentVisible') ? this.options.parentVisible : true;
    this._visible = this._parentVisible && (this.hasCondition() ? !this.conditionallyHidden() : !this.component.hidden);
    this._parentDisabled = false;

    /**
     * The reference attribute name for this component
     */
    this._referenceAttributeName = 'ref';

    /**
     * Used to trigger a new change in this component.
     * @type {Function} - Call to trigger a change in this component.
     */
    let changes = [];
    let lastChanged = null;
    let triggerArgs = [];
    const _triggerChange = _.debounce((...args) => {
      if (this.root) {
        this.root.changing = false;
      }
      triggerArgs = [];
      if (!args[1] && lastChanged) {
        // Set the changed component if one isn't provided.
        args[1] = lastChanged;
      }
      if (_.isEmpty(args[0]) && lastChanged) {
        // Set the flags if it is empty and lastChanged exists.
        args[0] = lastChanged.flags;
      }
      lastChanged = null;
      args[3] = changes;
      const retVal = this.onChange(...args);
      changes = [];
      return retVal;
    }, 100);
    this.triggerChange = (...args) => {
      if (args[1]) {
        // Make sure that during the debounce that we always track lastChanged component, even if they
        // don't provide one later.
        lastChanged = args[1];
        changes.push(lastChanged);
      }
      if (this.root) {
        this.root.changing = true;
      }
      if (args.length) {
        triggerArgs = args;
      }
      return _triggerChange(...triggerArgs);
    };

    /**
     * Used to trigger a redraw event within this component.
     * @type {Function}
     */
    this.triggerRedraw = _.debounce(this.redraw.bind(this), 100);

    /**
     * list of attached tooltips
     * @type {Array}
     */
    this.tooltips = [];

    /**
     * List of attached addons
     * @type {Array}
     */
    this.addons = [];

    // To force this component to be invalid.
    this.invalid = false;

    if (this.component) {
      this.type = this.component.type;
      if (this.allowData && this.key) {
        this.options.name += `[${this.key}]`;
        // If component is visible or not set to clear on hide, set the default value.
        if (!this.shouldConditionallyClear()) {
          if (!this.hasValue()) {
            if (this.shouldAddDefaultValue) {
              this.dataValue = this.defaultValue;
            }
          }
          else {
            // Ensure the dataValue is set.
            /* eslint-disable  no-self-assign */
            this.dataValue = this.dataValue;
            /* eslint-enable  no-self-assign */
          }
        }
      }

      /**
       * The element information for creating the input element.
       * @type {*}
       */
      this.info = this.elementInfo();
    }

    // Allow anyone to hook into the component creation.
    this.hook('component');

    if (!this.options.skipInit) {
      if (typeof this.beforeInit === 'function') {
        this.beforeInit();
      }
      this.init();
    }
  }
Initialize a new Component.
@param {object} component - The component JSON you wish to initialize.
@param {object} options - The options for this component.
@param {object} data - The global data submission object this component will belong.