Partially Mask Your Input Content with Vue

The requirement is to partial hide input content, for example masked input The normal way of masking input content is using <input type="password">, it utilizes the logic of input content masking provided by browser. However, it does not meet the requirement of show partial masked content + rest unmasked content without losing original unmasked content.

Searching the online resource, no articles have addressed this issue before. Majority of the articles are tutorials on how to do fully masked input content but not partially mask. I decided to come out my own implementation.

Building this component on top of vue, this is the way how it has been done. User cursor position is the first problem, it is necessary to find current cursor position for deletion or insertion value based on user action. How to find where cursor current stops. With selectionstart mdn, browser actually has provided api to achieve this. In order to track the user behavior on input, previous value and current value compared to determine whether it is deletion or insertion.

const preInput = this.preInput;
const index = e.target.selectionStart;
const nowValue = e.target.value;
//compare length of previous current input value
const increment = nowValue.length - preInput.length;

If nowValue is longer, new value will be inserted to the position where it points to

if (increment >= 0) {
 const newStr = nowValue.slice(index - increment, index);
 this.preInput = preInput.slice(0, index - increment) + newStr  
                 preVal.slice(index - increment)
}

on the other hand, if some input are deleted

else if (increment < 0) {
 this.preInput = preInput.slice(0, index) + 
                 preInput.slice(index - increment);
}

with knowing deletion or insertion and get updated value,
the next step is to render new value with predetermined masking rule to render new value on input, let say last 2 characters are need to be shown of string with rest string are masked with '*'

if (nowValue.length > 0) {
 const len = nowValue.length-2 > 0 ? nowVal.length - 2 : 0;
 e.target.value = new Array(len).fill('*').join("") + 
                  nowValue.slice(len);
}

At here, masked value are created with the way of array fill in content with masked symbol, and update with input native property. The original value is stored at preInput. At the end of this circle,

console.log("original content:", this.preInput)

retrieve unmasked original content.

For the whole view,

//v-model needs to be indicate in order for input to receive value
<input v-model="hiddenInput" @input="onInput"/>

onInput(e){
 const preInput = this.preInput;
 const index = e.target.selectionStart;
 const nowValue = e.target.value;
 const increment = nowValue.length - preInput.length;

 if (increment >= 0) {
  const newStr = nowValue.slice(index - increment, index);
  this.preInput = preInput.slice(0, index - increment) + 
                  newStr + preVal.slice(index - increment)
 } else if (increment < 0) {
  this.preInput = preInput.slice(0, index) + 
                  preInput.slice(index - increment);
 }

 if (nowValue.length > 0) {
   //define the mask rule, here is to visualize last two digits
  const len = nowValue.length-2 > 0 ? nowVal.length - 2 : 0;
  e.target.value = new Array(len).fill('*').join("") + 
  nowValue.slice(len);
 }
  //update cursor
  e.target.setSelectionRange(index, index)
}

19