[SalesForce] way to input mask a Salesforce lightning-input component

I am trying to write a custom Lightning Web Component that displays a single lightning-input text component and dynamically masks the input as the user enters it. By "masks" I mean it reformats the user's input as the user types.

For example, the user could type the 9 digits of a U.S. Social Security number and the mask would automatically insert the dashes in the correct places.

I have attempted to do this using third-party JavaScript libraries in two different ways:

  1. Uploaded as a static resource and loaded into the component via loadScript().

  2. Including the third-party library as its own Lightning Web Component and importing it.

In both cases I tried to apply the input mask to the input field inside the renderedCallback hook.

import { loadScript } from 'lightning/platformResourceLoader';
import vanillaTextMask from '@salesforce/resourceurl/vanillaTextMask';

async renderedCallback() {
  await loadScript(this, vanillaTextMask);
  const input = this.template.querySelector('lightning-input');
  vanillaTextMask.maskInput({
    inputElement: input,
    mask: [/\d/, /\d/, /\d/, '-', /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]
  });
}
import maskInput from 'c/textMask';

async renderedCallback() {
  const input = this.template.querySelector('lightning-input');
  maskInput({
    inputElement: input,
    mask: [/\d/, /\d/, /\d/, '-', /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]
  });
}

I've tried multiple libraries (text mask, Inputmask), different values for the loadScript arguments, different querySelector arguments, and a plain input element as opposed to a lightning-input. The best result I've had so far is getting placeholder characters displaying in the input field on focus, ___-__-____, but even in this case, the typing is hindered as the cursor position constantly jumps to the end of the input after each character is typed. The input is not practically usable.

I wonder if the fact that querySelector returns a Proxy object as opposed to the actual DOM element is causing the issue.

Best Answer

Unfortunately I concluded that, since Salesforce gives you a Proxy object back instead of the raw DOM element, it is currently not possible to do any sort of on-the-fly masking of an input value. I settled for a LWC that grabs the value after it has been typed, formats it appropriately, puts it back in the field, and runs validation. When the user focuses on the input field it removes the mask and lets them edit the "raw" value again:

HTML

<template>
    <lightning-input
        type="text"
        pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"
        onfocus={strip}
        onblur={mask}
        onchange={handleChange}
        value={value}
    ></lightning-input>
</template>

JavaScript

const applyMask = input => {
    let value = input.replace(/-/g, '');
    if (value.length >= 6) {
        value = `${value.slice(0, 3)}-${value.slice(3, 5)}-${value.slice(5)}`;
    } else if (value.length >= 4) {
        value = `${value.slice(0, 3)}-${value.slice(3)}`;
    }
    return value;
};

const removeMask = input => input.replace(/-/g, '');

export default class SsnInput extends LightningElement {

    /**
     * ...
     */
    @api value;

    connectedCallback() {
        if (this.value) {
            this.value = applyMask(this.value);
        }
    }

    strip(event) {
      event.target.value = removeMask(event.target.value);
    }

    mask(event) {
        event.target.value = applyMask(event.target.value);
        event.target.reportValidity();
    }

    handleChange(event) {
        ...
    }
}
Related Topic