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
}