text-mask / text-mask

Input mask for React, Angular, Ember, Vue, & plain JavaScript
https://text-mask.github.io/text-mask/
The Unlicense
8.27k stars 828 forks source link

Automatically enter decimal for CreateNumberMask add-on? #724

Open skube opened 6 years ago

skube commented 6 years ago

I'm wondering if it's possible to add the ability to have the decimal place auto-entered as an option?

Similar to how some ATM's work when inputing amounts, the decimal is automatically assumed and each numeric input creates the number from the right. For example, to input $20.00, one must enter 2, followed by three zeros only (no need to manually enter the decimal):

$__.__
$__._2
$__.20
$_2.00
$20.00
yohcop commented 6 years ago

I started saying that I needed something similar to type times, but then I though I should give it a try. So here is something close: https://stackblitz.com/edit/react-lycodu

Essentially, specify a mask function, instead of a simple mask array. It's not exactly as you (or I) wanted, especially when only 1 digit is typed, but it's close. It looks like:

$
$1     <- this is awkward, should be $0.01, not $1
$.12
$1.23
$12.34
$123.45

The thing is I don't know yet how to fill the mask from the right. I think that might be possible with a pipe (?)

What I was trying to do for durations:

__:__:__
__:__:_1
__:__:12
__:_1:24
__:12:40
_1:24:00
yohcop commented 6 years ago

Here is something that seems to work, although the code is pretty ugly... https://stackblitz.com/edit/react-wc4dj8

$__._1
$__.12
$_1.23
$12.34
$123.45
import React, { Component } from 'react';
import { render } from 'react-dom';
import MaskedInput from 'react-text-mask';

const maskFn = (input) => {
  var digits = input.split("").filter((c) => c >= '0' && c <= '9');
  if (digits.length < 4) {
    return ['$', /\d/, /\d/, '.', /\d/, /\d/];
  }

  var mask = [];
  for (var i = digits.length - 1; i >= 0; i--) {
    mask.push(/\d/);
    if (i == digits.length - 2) {
      mask.push('.');
    }
  }
  mask.push('$');
  return mask.reverse()
}

const pipe = (conformedValue, config) => {
  console.log(conformedValue, config);
  const _ = config.placeholderChar;
  var digits = config.rawValue.split("").filter((c) => c >= '0' && c <= '9');  
  if (digits.length == 0) {
    return ``;
  }
  if (digits.length == 1) {
    console.log(1)
    return `$${_}${_}.${_}${digits[0]}`;
  }
  const cents = digits.slice(digits.length - 2).join('');
  if (digits.length == 2) {
    return `$${_}${_}.${cents}`;
  }
  const dollars = digits.slice(0, digits.length - 2).join('');
  if (digits.length == 3) {
    return `$${_}${dollars}.${cents}`    
  }
  if (digits.length > 3) {
    return `$${dollars}.${cents}`
  }
  return conformedValue
}

class App extends Component {
  constructor() {
    super();
  }

  render() {
    return (
      <div>
        <MaskedInput
          type="text"
          mask={maskFn}
          placeholder="$"
          keepCharPositions={false}
          pipe={pipe}
        />
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));
larimaza commented 5 years ago

I see there's already an issue for the same request, from 2016: https://github.com/text-mask/text-mask/issues/263.

We could really, really use this feature :)

raphaelm22 commented 3 years ago

I used the @yohcop approach and implemented some options:

const decimalMaskOpt = ({
  thousandsSeparatorSymbol = ",",
  decimalSymbol = ".",
  decimalPlaces = 2,
} = {}) => {
  return (input) => {
    var digits = (input.match(/\d/gi) || []).length;

    if (digits <= decimalPlaces) return Array(decimalPlaces).fill(/\d/);

    if (digits === decimalPlaces + 1) {
      return [/\d/, /\d/, decimalSymbol, ...Array(decimalPlaces).fill(/\d/)];
    }

    var mask = [];
    for (var i = digits - 1; i >= 0; i--) {
      mask.push(/\d/);
      if (i == digits - decimalPlaces) {
        mask.push(decimalSymbol);
      }

      const r = digits - i;
      if (r >= decimalPlaces + 2 && (r - decimalPlaces) % 3 == 0 && i > 0) {
        mask.push(thousandsSeparatorSymbol);
      }
    }

    return mask.reverse();
  };
};
class App extends Component {
  constructor() {
    super();
  }

  render() {
    return (
      <div>
        <MaskedInput
          type="text"
          mask={decimalMaskOpt({ decimalPlaces: 3 })}
          guide={false}
        />
      </div>
    );
  }
}