Stimulus and Input Masks

I’ve been looking to use a Stimulus controller to manage formatting things like currency on the fly without using JQuery. I’m currently attempting to use the IMask library to no avail. I’m certainly open to other options for libraries, but I’m curious if anyone else has done something similar. My current implementation is as follows:

#view
<%= form_with model: payment, html: { data: { controller: "form" } } do |f| %>
...
  <div class='form-group'>
    <%= f.label :amount %>
    <%= f.number_field :amount, class: 'form-control currency', step: 0.01, data: { form_target: "currency", action: "input->form#formatCurrency" } %>
  </div>
...
//form_controller.js
import { Controller } from "@hotwired/stimulus"
import IMask from "imask"

const currencyMaskConfig = {
  mask: '$num',
  blocks: {
    num: {
      mask: Number,
      thousandsSeparator: ',',
      radix: '.'
    }
  }
}

// Connects to data-controller="form"
export default class extends Controller {
  static targets = ["currency"]

  connect() {
    const currencyMasks = this.currencyTargets.map((input) => [input.id, IMask(input, currencyMaskConfig)])
    this.currencyMasks = Object.fromEntries(currencyMasks)
  }

  formatCurrency(event) {
    this.currencyMasks[event.target.id].updateValue()
  }
}

For context, I currently get the following errors in the browser’s console:

The specified value "9,988" cannot be parsed, or is out of range.
Element value was changed outside of mask. Syncronize mask using `mask.updateValue()` to work properly.

We use iMask with stimulus in a controller something like this.

import { Controller } from "@hotwired/stimulus"
import IMask from "imask"

export default class extends Controller {
  static values = { pattern: String };

  connect() {
    this.mask = IMask(this.element, { mask: this.patternValue });
  }

  disconnect() {
    this.mask?.destroy();
  }
}
1 Like