Giter VIP home page Giter VIP logo

v-money3's Introduction

workflow TypeScript

Money Mask for Vue 3

The Mask Money

Introduction

Welcome to v-money3, a versatile money masking solution designed specifically for Vue 3 applications. This library serves as a replacement for the now-abandoned v-money library, ensuring compatibility and functionality for projects transitioning to Vue 3.

Features

  • Arbitrary Precision: Utilize BigInt for precise calculations.
  • Lightweight: Less than 4KB when gzipped.
  • Dependency-free: No external dependencies for seamless integration.
  • Mobile Support: Ensures a consistent experience across devices.
  • Component or Directive Flavors: Choose between component-based or directive-based implementation.
  • Copy/Paste Support: Easily accept input via copy/paste actions.
  • Min/Max Limits: Set minimum and maximum limits for input values.

Arbitrary Precision

In this release, some breaking changes have been introduced. Let's delve into the details:

v-money3 supports arbitrary precision through the use of BigInt. Arbitrary precision is only supported with v-model. When using v-model, ensure the input is provided as a string representation of a number (e.g., '12345.67'). If your precision is set to 2 and you provide a default v-model value of '55', it will be interpreted as '0.55'. To maintain the correct format, ensure you pass '55.00' when using v-model.

For most users, it's advisable to utilize floating-point numbers and adhere to the boundaries of Number. In such cases, employing v-model with the number modifier, or v-model.number, is recommended. However, this limits you to numbers smaller than 2^53 - 1 or 9007199254740991 (approximately nine quadrillion). Refer to MAX_SAFE_INTEGER for more information. For users employing v-model.number, integers and floats are intuitively understood. If your precision is set to 2 and you input a default v-model.number value of 55, it will be interpreted as 55.00. The same applies to 5.5, which will be rendered as 5.50.

More Examples

Browser Target

If you encounter the error message: Big integer literals are not available in the configured target environment, please ensure your browser targets are updated to include at least the following entries:

['es2020', 'safari14']

Can I use bigInt? https://caniuse.com/bigint

More information: #66, #70, #89

Installation

npm i v-money3 --save

Usage

Register Globally (view codesandbox)

import { createApp } from "vue";
import money from 'v-money3'

const app = createApp({/* options */})

// register directive v-money3 and component <money3>
app.use(money)

Only Global Component (view codesandbox)

import { createApp } from "vue";
import { Money3Component } from 'v-money3'

const app = createApp({/* options */})

// register component <money3>
app.component("money3", Money3Component)

Only Global Directive (view codesandbox)

import { createApp } from "vue";
import { Money3Directive } from 'v-money3'

const app = createApp({/* options */})

// register directive v-money3
app.directive('money3', Money3Directive)

Use as component (view codesandbox)

<template>
  <money3 v-model="amount" v-bind="config"></money3> {{ amount }}
</template>

<script>
  import { Money3Component } from 'v-money3'

  export default {
    components: { money3: Money3Component },
    data () {
      return {
        amount: '12345.67',
        config: {
          masked: false,
          prefix: '',
          suffix: '',
          thousands: ',',
          decimal: '.',
          precision: 2,
          disableNegative: false,
          disabled: false,
          min: null,
          max: null,
          allowBlank: false,
          minimumNumberOfCharacters: 0,
          shouldRound: true,
          focusOnRight: false,
        }
      }
    }
  }
</script>

Component v-model number modifier (view codesandbox)

When using v-model, the returned value will always be a string. If the masked property is set to true, it will be formatted accordingly; otherwise, it will be a fixed string representation of a floating-point number. If you require your model to be a floating-point number, utilize the number modifier. For example:

  • v-model="amount" will return a fixed string with a typeof string. For instance: '123456.78'
  • v-model.number="amount" will return a floating-point number with a typeof number. For example: 123456.78
<template>
  <money3 v-model.number="amount" v-bind="config"></money3>
</template>

<script>
  import { Money3Component } from 'v-money3'

  export default {
    components: { money3: Money3Component },
    data () {
      return {
        amount: 12345.67,
        config: { ... }
      }
    }
  }
</script>

Use as directive (view codesandbox)

To ensure proper functionality, you must use v-model.lazy for binding.

<template>
  <input v-model.lazy="amount" v-money3="config" />
</template>

<script>
  import { Money3Directive } from 'v-money3'

  export default {
    data () {
      return {
        amount: '12345.67',
        config: {
          prefix: '',
          suffix: '',
          thousands: ',',
          decimal: '.',
          precision: 2,
          disableNegative: false,
          disabled: false,
          min: null,
          max: null,
          allowBlank: false,
          minimumNumberOfCharacters: 0,
          shouldRound: true,
          focusOnRight: false,
        }
      }
    },
    directives: { money3: Money3Directive }
  }
</script>

By default, directives are only compatible with v-model. It's important to note that using v-model.number directly on directives is not supported. If you need to work with float or integer on directives, you'll need to manually configure the number modifier.

Using config:

modelModifiers: {
  number: true,
}

If you directly bind it, you're perfectly fine as well:

<input :model-modifiers="{ number: true }" v-model.lazy="amount" v-money3="config" />

Properties

property Required Type Default Description
precision true Number 2 How many decimal places
decimal false String "." Decimal separator
thousands false String "," Thousands separator
prefix false String "" Currency symbol followed by a Space, like "R$ "
suffix false String "" Percentage for example: " %"
masked false Boolean false If the component output should include the mask or not
disable-negative false Boolean false Component does not allow negative values
disabled false Boolean false Disable the inner input tag
min false Number null The min value allowed
max false Number null The max value allowed
allow-blank false Boolean false If the field can start blank and be cleared out by user
minimum-number-of-characters false Number 0 The minimum number of characters that the mask should show
should-round false Boolean true Should default values be rounded or sliced
focus-on-right false Boolean false When focus, set the cursor to the far right

Restricted Characters

Numbers 0-9 and the following characters...

  • +
  • -

...are restricted on following properties:

  • decimal
  • thousands
  • prefix
  • suffix

Restricted inputs will be filtered out of the final mask, and a console warning with more information will be displayed.

Don't want to use a package manager?

Use it directly in the browser!

<script src="https://unpkg.com/[email protected]/dist/v-money3.umd.js"></script>

Take a look at issue #15 and also this codesandbox working example.

Use the internal mask functions

import { format, unformat } from 'v-money3';

const config = {
    debug: false,
    masked: false,
    prefix: '',
    suffix: '',
    thousands: ',',
    decimal: '.',
    precision: 2,
    disableNegative: false,
    disabled: false,
    min: null,
    max: null,
    allowBlank: false,
    minimumNumberOfCharacters: 0,
    modelModifiers: {
        number: false,
    },
    shouldRound: true,
    focusOnRight: false,
}

const formatted = format(12345.67, config);
console.log(formatted);
// output string: 'R$ 12.345,67 #'

const unformatted = unformat(formatted, config);
console.log(unformatted);
// output fixed string: '12345.67'

/* ----------------- or ----------------- */

config.modelModifiers = { number: true };

const unformatted = unformat(formatted, config);
console.log(unformatted);
// output float number: 12345.67

Contribution and Feedback

Your contributions and feedback are highly valued! If you encounter any issues or have suggestions for improvement, please feel free to open an issue or submit a pull request on GitHub.

The previous v-money library has been abandoned, prompting the development of v-money3 to accommodate projects migrating to Vue 3.

Happy coding with v-money3!

References

v-money3's People

Contributors

androlgenhald avatar christhofer avatar dependabot[bot] avatar jonathanpmartins avatar joserick avatar marcosgomesneto avatar sambitevidev avatar sougiovn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

v-money3's Issues

No declaration file for module 'v-money3'

I saw in another thread that you were attempting to create definition files for this package, but it seems I'm still getting this issue. Any progress on that?

Could not find a declaration file for module 'v-money3'. '.../node_modules/v-money3/dist/v-money3.umd.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/v-money3` if it exists or add a new declaration (.d.ts) file containing `declare module 'v-money3';`

Help about rawValue Option

Hi,
is there an option like 'rawValue' on the component? If not it would be great!

Somehing that can show the mask on input but take from the variable prefix, sufix, etc.. let it with just the digits..

Big integer literals are not available

I got this error Big integer literals are not available in the configured target environment ("chrome87", "edge88", "es2019", "firefox78", "safari13.1")

  • Vue version: 3.2.31
  • v-money3 version: 3.21.0

image

First digit is considered as integer

The usual behavior of the lib is that every digit inputted, it compound the value from the smallest decimal case and keeps bumping the previous numbers into the bigger cases.

But the last released version seems to have a bug.

Every first digit in my input consider the first digit as the first integer digit and the following inputs follow the normal behavior of compounding the value from the smallest case and bumping the previous numbers.

I noticed it in the latest version: 3.17.4

I'm using the component with v-model.number.
Configuration:

{
  decimal: ',',
  thousands: '.',
  prefix: 'R$ ',
  suffix: '',
  precision: 2,
  masked: false,
  disableNegative: false,
  disabled: false,
  min: Number.MIN_SAFE_INTEGER,
  max: Number.MAX_SAFE_INTEGER,
  allowBlank: false,
  minimumNumberOfCharacters: 0
}

Here follows a gif of me tabbing into the component and only pressing 1.
v-money3bug

[solved] Associate input with a label.

This issue was originally open by @edsonfeimberg in the old repo. vuejs-tips/v-money#104

Following the Mozzila web docs: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label#simple_label_example, you can accomplish this by wrapping the v-money3 component inside a label tag...

<label>
    Price
    <v-money3/>
</label>

Or you can use the for attribute

<label for="custom-id">Price</label>
<v-money3 id="custom-id"/>

The second option wasn't possible. So I created a commit that passes the id attribute down to the native input tag.

Error after updating Vue 3 and Vmoney with TS

Hi, first congratulations for the excellent work you do. Thank you very much!

After updating Vue to Vue version 3.2.31 and V-money 3 to version 3.21.0

It started displaying the following message:

in ./node_modules/v-money3/dist/v-money3.es.js

Module parse failed: Unexpected token (526:30)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| let lastValue = null;
| function change(evt) {

let value2 = evt.target?.value;
| debug(props, "component change() -> evt.target.value", value2);
| if (!(masked.value && !modelModifiers.value.number)) {

@ ./src/main.ts 3:0-43 15:28-43
@multi (webpack)-dev-server/client?http://192.168.1.6:8080&sockPath=/sockjs-node (webpack)/hot/dev-server.js ./src/main.ts

Could you help me, please?

update:model-value is duplicated

Because update:model-value is emitted whenever the input is changed, and the input is changed when being formatted, update:model-value events are duplicated.

Initial value problem

Hi, I was testing this lib, and I see that if my I have the .lazy modifier, my input doesnt set the initial value of the v-model properly, when I remove the .lazy modifier, it inits correctly, is there any major problem of not having the .lazy modifier? the doc says it needs to exist for the binding to work properly, thats not really useful information tho

Cursor goes to wrong position when deleting a digit

I found two cases where cursor goes to wrong position after a digit has been deleted.

Just use the codesandbox example

Case 1:
a) type 123456 (R$ 1.234,56)
b) delete digit 1. Cursor will be positioned between digits 2 and 3

Case 2:
a) type 1234567890 (R$ 12.345.678,90)
b) delete digit 1. Cursor will be positioned between digits 2 and 3

Thanks for you work on this component!

when allowBlank is set last digits are missing

when allowBlank is FALSE

€30.00
10.00%

image

allowBlank is TRUE

€0.30
0.10%

image

image

below is my overall config

{ "decimal": ".", "thousands": ",", "prefix": "€", "suffix": "", "min": 0, "max": 1000000, "precision": 2, "masked": false, "allowBlank": true, "disableNegative": true }

UX - behave like normal input when getting focus

This is not a bug and I'm sure if every one agree, but there is two things I think it would be more user friendly:

  1. When gettting focus through tab key, all content could be selected as a normal input field.

  2. When getting focus through mouse click, cursor could stay near the character that received the click.

Document and/or fix/prevent edge cases.

I was thinking about this a bit today when looking through the code, there are a lot of edge cases but I'm not sure there's a good way to deal with them. Several could be solved by ensuring the value passed to format is either a number or a formatted string, which would allow removing the prefix and suffix correctly, but it's possible for the user to add input in the middle of the prefix or suffix. Adding a selection event handler to prevent the cursor from going into the prefix or suffix could solve that problem well enough at the cost of potentially strange interactions with other JavaScript code modifying input values.

  • A prefix or suffix containing '-' causes the value to always be negative, format checks for the existence of a - in the string to decide if it should be negative, and this happens before removing the prefix and suffix. Even if it's moved after the prefix and suffix removal it would be an issue if the user entered it into the middle of the prefix or suffix.
  • Numbers in the prefix or suffix cause the value to change unpredictably. I tried a suffix of '.00' while keeping precision 2, and it removes the '.00' from the precision formatting, effectively dividing by 100 if you ever enter two 0s in a row. It seems like format can be called recursively in some cases? This specific problem won't occur with reasonable usage, a suffix of '.00' could be useful but precision would likely be set to 0 in that case, but I could also see having a suffix of '000' for a field whose value is stored as thousands or something, which would have the same problem.
  • Setting the decimal to include a digit causes the input to be maxed instantly. Should digits be disallowed from the option, with an error thrown?
  • The thousands option has the same issue, but it only appears once the user enters a large enough number.
  • The onkeydown handler for the directive checking 'Backspace' and 'Delete' seems to cause some weird interactions, but I haven't reproduced it consistently.

`allowBlank = true` should still allow submitting a 0 value

I added a test for this in #34, but it's not working as expected. The demo here currently works as I would expect, if the input is blank and the user types a 0, the input changes to "0.00" (formatted according to the config), but my test currently fails. I'm not sure if this is because the test is broken or if the demo is using older code.

vue3 directive not working

I have created the v-money3 directive and use it on the input element but its not reactive its give bellow error on console

Screenshot 2021-06-11 at 10 07 57 AM

Compiled JS file

Please include compiled JS file, that can be used directly on webpages without NPM

Opt to avoid round precision

toFixed(precision = 0) {
let string = this.toString();
const diff = precision - this.getDecimalPrecision();
// diff bigger than zero pads zeros at the end
if (diff > 0) {
// if it is an integer, add a dot
if (!string.includes('.')) {
string += '.';
}
return string.padEnd(string.length + diff, '0');
}
// diff smaller than zero need to be sliced and rounded
if (diff < 0) {
return this.constructor.round(string, precision);
}
return string;
}

I need to show only 2 precision points from a big decimal number, but it must not round when shrinking the precision.

It'd be a good feature to have and option to avoid rounding and just slice the numbers.

Compiling error (createElementBlock) with Vue versions lower than 3.2.0

Hi Jonathan ... I got that error when I try to run dev.

> node_modules/v-money3/dist/v-money3.es.js:1:124: error: No matching export in "node_modules/vue/dist/vue.runtime.esm-bundler.js" for import "createElementBlock" 1 │ ...withDirectives as r,openBlock as s,createElementBlock as a,mergeProps as l}from"vue";co...
v-money3-error

Could you help me??

Thanks bro!

Export format function

I'd be a nice feature to export the format function, so we can use it as a mask function to display data.

If you don't want to do this right now, I'll open a PR later

Error when I run my test with Jest

When I run my suite's test, this error it's happening (image below)

image

I don't know why happened this problem :/

Now I inserted just arrow function return nothing, just for passed my test.

Config option (prefix and suffix) sharing information when typing in input inside a v-for

I don't have a solution for the problem but I'll leave the code with the bug below, thanks.

Parent Component

<template>
    <div>
        <div v-for="index in teste" :key="index">         
            <auto-numeric-input
                :ViewModel="ViewModel"
                :atualizaTab="true"
                :config="index"
                @atualizarViewModel="atualizarViewModel"
            ></auto-numeric-input> 
            type  a string in input
        </div>

    </div>
</template>
import AutoNumericInput from "./AutoNumericInput.vue";
import { ref } from "vue";
export default {
    components: {
        AutoNumericInput,
    },
    setup() {
        let ViewModel = ref(0);
        let teste = [
            {
                masked: false,
                prefix: "",
                suffix: "#",
                thousands: ",",
                decimal: ".",
                precision: 2,
                disableNegative: false,
                disabled: false,
                min: null,
                max: null,
                allowBlank: false,
                minimumNumberOfCharacters: 0,
            },
            {
                masked: false,
                prefix: "R$",
                suffix: "",
                thousands: ",",
                decimal: ".",
                precision: 2,
                disableNegative: false,
                disabled: false,
                min: null,
                max: null,
                allowBlank: false,
                minimumNumberOfCharacters: 0,
            },
            {
                masked: false,
                prefix: "",
                suffix: "%",
                thousands: ",",
                decimal: ".",
                precision: 2,
                disableNegative: false,
                disabled: false,
                min: null,
                max: null,
                allowBlank: false,
                minimumNumberOfCharacters: 0,
            },
            {
                masked: false,
                prefix: "",
                suffix: "º",
                thousands: ",",
                decimal: ".",
                precision: 2,
                disableNegative: false,
                disabled: false,
                min: null,
                max: null,
                allowBlank: false,
                minimumNumberOfCharacters: 0,
            },
        ];

        let config = ref({
            masked: false,
            prefix: "",
            suffix: "#",
            thousands: ",",
            decimal: ".",
            precision: 2,
            disableNegative: false,
            disabled: false,
            min: null,
            max: null,
            allowBlank: false,
            minimumNumberOfCharacters: 0,
        });
        const atualizarViewModel = (e) => {
            ViewModel.value = e;
        };
        return {
            ViewModel,
            atualizarViewModel,
            config,
            teste,
        };
    },
};

Child Component

<template>
    <money3 v-model="valueInput" @blur="atualizaBlur" v-bind="config"></money3>
</template>
import { Money3Component } from "v-money3";
import { watch, ref } from "vue";

export default {
    components: { money3: Money3Component },
    props: {
        ViewModel: {
            type: [String, Number],
            required: false,
            default: 0,
        },
        atualizaTab: {
            type: Boolean,
            required: false,
            default: false,
        },
        config: {
            type: Object,
            required: true,
            default() {
                return {
                    masked: false,
                    prefix: "$", //$
                    suffix: "#", //#
                    thousands: ",",
                    decimal: ".",
                    precision: 2,
                    disableNegative: false,
                    disabled: false,
                    min: null,
                    max: null,
                    allowBlank: false,
                    minimumNumberOfCharacters: 0,
                };
            },
        },
    },
    emits: ["atualizarViewModel"],
    setup(props, context) {
        let valueInput = ref(0);

        const atualizaBlur = () => {
            if (props.atualizaTab) {
                context.emit("atualizarViewModel", valueInput.value);
            }
        };
        if (!props.atualizaTab) {
            watch(valueInput, () => {
                context.emit("atualizarViewModel", valueInput.value);
            });
        }
        watch(props, () => {
            valueInput.value = props.ViewModel;
        });
        return {
            valueInput,
            atualizaBlur,
        };
    },
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.