ui_elements_numericInput.mjs

/**
### mapp.ui.elements.numericInput()

Exports the numericInput elements method.

@module /ui/elements/numericInput
*/

/**
@function numericInput

@description
Creates a type=text input element with validation checks oninput.

@param {Object} params Parameter for the creation of the input element.
@property {string} [params.placeholder=''] The placeholder for the text input.
@property {string} [params.data_id='numeric-input'] The data id attribute for the input element.

@returns {Object} HTML input element
*/
export default function numericInput(params) {
  params.placeholder ??= '';
  params.data_id ??= 'numeric-input';
  params.numericChecks ??= numericChecks;

  const value = mapp.utils.formatNumericValue(params);
  const width = params.dynamicWidth ? value.length + 1.3 + 'ch' : '100%';
  const style = `text-align: center; width: ${width}`;

  const numericInput = mapp.utils.html.node`<input
    data-id=${params.data_id}
    type="text"
    style=${style}
    placeholder=${params.placeholder}
    value=${value}
    onchange=${(e) => oninput(e, params)}
    oninput=${(e) => oninput(e, params)}>`;

  return numericInput;
}

/**
@function oninput

@description
Creates a type=text input element with validation checks for numeric values.

The value of an associated sliderElement will be updated if validated.

@param {Object} e The event object from input element.
@param {Object} params The config object argument.
@property {Object} e.target The input element.
@property {Object} params.callback A callback method is required.
*/
function oninput(e, params) {
  // Assign stringValue from input target.
  params.stringValue = e.target.value;

  // Assign numeric newValue.
  params.newValue = params.onRangeInput
    ? params.stringValue
    : mapp.utils.unformatStringValue(params);

  if (params.numericChecks(params.newValue, params)) {
    // The numericCheck passes.
    delete params.invalid;
    e.target.classList.remove('invalid');

    // Updated sliderElement value only if validated.
    if (params.sliderElement) {
      // Set styling param for the track.
      params.sliderElement.style.setProperty(
        `--${e.target.dataset.id}`,
        params.newValue,
      );

      // Set value for the range input.
      params.sliderElement.querySelector(`[name=${params.rangeInput}]`).value =
        params.newValue;
    }
  } else {
    // The numericCheck fails.
    params.invalid = true;
    e.target.classList.add('invalid');
  }

  // The invalid input should not be formatted.
  if (params.invalid) return params.callback();

  // Pass valid newValue to callback method.
  params.callback(params.newValue);

  // Re-format the params numeric value (newValue || value) and set as input string value.
  e.target.value = mapp.utils.formatNumericValue(params);

  if (params.dynamicWidth) {
    e.target.style.width = e.target.value.length + 1.3 + 'ch';
  }
  //Mark the onRangeInput to false is the origin of the input call could come from either a slider or input
  params.onRangeInput = false;
}

/**
@function numericChecks

@description
The numericChecks method checks whether a provided numeric value is a number, larger than params.min, and smaller than params.max.

@param {Object} value The numeric value to check.
@param {Object} params The config object argument.
@property {numeric} params.min Value must be larger than min.
@property {numeric} params.max Value must be smaller than max.

@returns {Boolean} true if checks are passed.
*/
function numericChecks(value, params) {
  // Check whether value is a null.
  if (params.onRangeInput && value === null) return false;
  // Check whether value is a number.
  if (isNaN(value)) return false;

  if (params.min && value < params.min) {
    // The value is smaller than min.
    return false;
  }

  if (params.max) {
    return value <= params.max;
  }

  return true;
}