Skip to content

Commit a87ea75

Browse files
authored
🔀 Merge pull request #65 from evanyu3479/feat/issue-51/allow-empty-value
🆕 add allowEmpty prop
2 parents c41574d + 69a0128 commit a87ea75

6 files changed

+67
-50
lines changed

package-lock.json

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "simple-mask-money",
3-
"version": "4.0.7",
3+
"version": "4.1.0",
44
"private": false,
55
"description": "Simple money mask developed with pure JavaScript. To run on Client Side and Server Side",
66
"types": "./lib/simple-mask-money.d.ts",

src/get-base-configuration.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const configuration: SimpleMaskMoneyConfiguration = {
1313
suffix : '',
1414
thousandsSeparator: '.',
1515
cursor : 'end',
16+
allowEmpty : false,
1617
};
1718

1819
function getBaseConfiguration(

src/set-mask.ts

+37-27
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import getBaseConfiguration from 'src/get-base-configuration';
88
const numbers = '0123456789'.split('');
99

1010
/**
11-
* It applies a mask to an input element, formatting its value as a currency.
12-
* It takes an input element and an optional configuration object as parameters.
13-
* The function listens for keyboard events on the input element and updates its value accordingly.
11+
* It applies a mask to an input element, formatting its value as a currency.
12+
* It takes an input element and an optional configuration object as parameters.
13+
* The function listens for keyboard events on the input element and updates its value accordingly.
1414
* It also handles caret positioning and allows for undoing changes. The function returns a method to remove the mask from the input element.
15-
*
15+
*
1616
* @remarks
1717
* This method is part of the {@link https://github.com/codermarcos/simple-mask-money/ | SimpleMaskMoney} to see the full documentation check {@link https://github.com/codermarcos/simple-mask-money/tree/main/examples/4.x.x#simplemaskmoneysetmask | SimpleMaskMoney.setMask}
1818
*
@@ -24,23 +24,23 @@ const numbers = '0123456789'.split('');
2424
* Here's an example using from cdn with CSSSelector:
2525
* ```html
2626
* <script src=""></script>
27-
*
27+
*
2828
* <input id="my-input" />
29-
*
29+
*
3030
* <script>
3131
* const remove = SimpleMaskMoney.setMask('#my-input');
32-
* remove(); // To remove the mask and listeners
32+
* remove(); // To remove the mask and listeners
3333
* </script>
3434
* ```
35-
*
35+
*
3636
* @example
3737
* Here's an example using from npm to React with CSSSelector:
3838
* ```jsx
3939
* import { setMask } from 'simple-mask-money';
40-
*
40+
*
4141
* function InputMoney() {
4242
* useEffect(() => setMask('#my-input'), []);
43-
*
43+
*
4444
* return <input id="my-input" />;
4545
* }
4646
* ```
@@ -66,6 +66,7 @@ function setMask(
6666
prefix,
6767
suffix,
6868
cursor,
69+
allowEmpty,
6970
} = currentConfiguration;
7071

7172
if (typeof document === 'undefined') return () => void 0;
@@ -114,7 +115,7 @@ function setMask(
114115
[actionName] = action;
115116
const [, actionParams] = action;
116117
const [start, end] = actionParams;
117-
118+
118119
// Add or remove characters
119120
const characteresRemoved = characteres.splice(...(actionParams as [number, number]));
120121

@@ -132,9 +133,9 @@ function setMask(
132133
let isNegative = false;
133134

134135
for (let character; (character = characteres.pop()); ) {
135-
if (character === '-') {
136+
if (character === '-') {
136137
isNegative = true;
137-
continue;
138+
continue;
138139
}
139140

140141
if (character === decimalSeparator && decimalSeparatorAdded && trimExtraDecimals) {
@@ -145,7 +146,7 @@ function setMask(
145146

146147
if (fractionDigitsNumbers.length < result.length)
147148
thousandsCounter += result.length - fractionDigitsNumbers.length;
148-
149+
149150
result = [
150151
decimalSeparator,
151152
...fractionDigitsNumbers,
@@ -169,9 +170,9 @@ function setMask(
169170
}
170171

171172
result.unshift(character);
172-
173+
173174
if (result.length !== fractionDigits || decimalSeparatorAdded) continue;
174-
175+
175176
result.unshift(decimalSeparator);
176177
decimalSeparatorAdded = true;
177178
}
@@ -186,7 +187,7 @@ function setMask(
186187
result = [completer, decimalSeparator, result.join('').padStart(fractionDigits, completer)];
187188
else if (result.length === fractionDigits + decimalSeparator.length) // ,00
188189
result.unshift(completer);
189-
190+
190191
if (isNegative)
191192
result[negativeSignAfter ? 'push' : 'unshift']('-');
192193

@@ -200,12 +201,12 @@ function setMask(
200201
let position = positionDefault;
201202

202203
if (cursor === 'move' && force)
203-
position = typeof force[1] === 'number'
204+
position = typeof force[1] === 'number'
204205
? [force[0], force[1]] as const
205206
: [force[0], force[0]] as const;
206207

207208
element.setSelectionRange(...position);
208-
209+
209210
return position;
210211
};
211212

@@ -214,11 +215,11 @@ function setMask(
214215
const initialValue = formatToMask(element.value.split(''), true);
215216

216217
let lastValue = initialValue;
217-
218+
218219
const onKeyDown = (e: KeyboardEvent) => {
219220
beforeFormat?.(element.value);
220221
const lastPositionToNumber = getLastPositionToNumber();
221-
222+
222223
let start = element.selectionStart ?? lastPositionToNumber;
223224
let end = element.selectionEnd ?? lastPositionToNumber;
224225

@@ -227,12 +228,12 @@ function setMask(
227228

228229
// Undo to first value
229230
if (e.ctrlKey && e.key === 'z') return triggerInputChanges(initialValue);
230-
231+
231232
// Allow move caret after or before the prefix or suffix
232233
if (cursor === 'move' && (
233234
(e.key === 'ArrowLeft' && start > firstPositionToNumber) ||
234235
(e.key === 'ArrowRight' && start < lastPositionToNumber)
235-
)) return;
236+
)) return;
236237

237238
e.preventDefault();
238239

@@ -251,6 +252,11 @@ function setMask(
251252
// No allow erase the prefix
252253
if (isBackspace && start === 0) return;
253254

255+
if (allowEmpty && isBackspace && element.value.length <= prefix.length + 1) {
256+
triggerInputChanges(prefix);
257+
return;
258+
}
259+
254260
const characteres = element.value.split('');
255261

256262
const length = Math.abs(end - start);
@@ -275,7 +281,7 @@ function setMask(
275281
start = start - characteresRemoved <= firstPositionToNumber ? firstPositionToNumber : start - characteresRemoved;
276282
end = end - characteresRemoved <= firstPositionToNumber ? firstPositionToNumber : end - characteresRemoved;
277283
}
278-
284+
279285
triggerInputChanges(newValue, [start, end]);
280286
};
281287

@@ -302,14 +308,18 @@ function setMask(
302308

303309
// Only set position if is on prefix or suffix
304310
if (!position) return;
305-
311+
306312
setCaretPosition(position);
307313
};
308314

309315
element.addEventListener('keydown', onKeyDown);
310316
document.addEventListener('selectionchange', onSelectionChange);
311317

312-
triggerInputChanges(initialValue);
318+
if (allowEmpty && initialValue === `${prefix}0`) {
319+
triggerInputChanges('');
320+
} else {
321+
triggerInputChanges(initialValue);
322+
}
313323

314324
const removeMask = (): void => {
315325
element.removeEventListener('keydown', onKeyDown);
@@ -330,7 +340,7 @@ function setMask(
330340
export default setMask;
331341
/**
332342
* Check the {@link https://github.com/codermarcos/simple-mask-money/tree/main/examples/4.x.x#SimpleMaskMoney.setMask | SimpleMaskMoney.setMask} method to get more information about this type
333-
*
343+
*
334344
* @remarks
335345
* This type is part of the {@link https://github.com/codermarcos/simple-mask-money/ | SimpleMaskMoney} to see the full documentation check {@link https://github.com/codermarcos/simple-mask-money/tree/main/examples/4.x.x#SimpleMaskMoney.setMask | SimpleMaskMoney.setMask}
336346
*/

src/types.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@
22
* This is the full configuration for the mask.
33
*
44
* @interface SimpleMaskMoneyConfiguration
5-
*
5+
*
66
* @field {(value: string) => void} afterFormat is used for get the value when the mask is already applied
77
* @field {(value: string) => string} beforeFormat is used for get the value when the mask will be applied
8-
*
8+
*
99
* @field {boolean} allowNegative is used for define if allow values less than zero
1010
* @field {boolean} negativeSignAfter is used for define if negative sign should be after the number
11-
*
11+
*
1212
* @field {string} decimalSeparator is used for define the separator of decimals digits
1313
* @field {string} thousandsSeparator is used for define the separator of thousands digits
14-
*
14+
*
1515
* @field {boolean} fixed is used for define if your value can be empty or always should have value
16-
*
16+
*
1717
* @field {number} fractionDigits is used for define the quantity of decimals digits
18-
*
18+
*
1919
* @field {string} prefix is used for define a string that always precedes its value
2020
* @field {string} suffix is used for define a string that always follows its value
21-
*
21+
*
2222
* @field {'move' | 'end'} cursor is used for define the position of the cursor when the user focus the input
2323
*/
2424
export type SimpleMaskMoneyConfiguration = {
2525
/** Called after format the value when the mask is already applied */
26-
afterFormat?(value: string): void;
26+
afterFormat?(value: string): void;
2727
/** Called before format the value when the mask will be applied */
2828
beforeFormat?(value: string): string;
2929

@@ -47,14 +47,17 @@ export type SimpleMaskMoneyConfiguration = {
4747
prefix: string;
4848
/** **Default:** `''` - This string always follows its value */
4949
suffix: string;
50-
50+
5151
/** **Default:** `'end'` - Define the position of the cursor when the user focus the input */
5252
cursor: 'move' | 'end';
53+
54+
/** **Default:** `false` - Define if allow empty value */
55+
allowEmpty: boolean;
5356
}
5457

5558
export type OptionalSimpleMaskMoneyConfiguration = Partial<SimpleMaskMoneyConfiguration>;
5659

5760
export type HTMLInputElementMasked = HTMLInputElement & {
58-
/** This method remove the input mask */
59-
removeMask?: () => void;
61+
/** This method remove the input mask */
62+
removeMask?: () => void;
6063
};

tests/get-base-configuration.test.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { describe, expect, it } from '@jest/globals';
33
import getBaseConfiguration from 'src/get-base-configuration';
44

55
describe(
6-
'getBaseConfiguration',
6+
'getBaseConfiguration',
77
() => {
88

99
it(
10-
'should have base configuration',
10+
'should have base configuration',
1111
() => {
12-
expect(getBaseConfiguration()).toEqual({
13-
allowNegative : false,
12+
expect(getBaseConfiguration()).toEqual({
13+
allowNegative : false,
1414
negativeSignAfter : false,
1515
decimalSeparator : ',',
1616
fixed : true,
@@ -19,29 +19,30 @@ describe(
1919
suffix : '',
2020
thousandsSeparator : '.',
2121
cursor : 'end',
22+
allowEmpty : false,
2223
});
2324
},
2425
);
2526

2627
it(
27-
'should have base configuration',
28+
'should have base configuration',
2829
() => {
2930
const fractionDigits = 4;
3031
const decimalSeparator = '.';
3132
const prefix = 'R$';
3233
const allowNegative = true;
3334
const cursor = 'move';
3435

35-
const result = getBaseConfiguration({
36-
allowNegative,
37-
decimalSeparator,
36+
const result = getBaseConfiguration({
37+
allowNegative,
38+
decimalSeparator,
3839
fractionDigits,
3940
prefix,
4041
cursor
4142
});
4243

43-
expect(result).toEqual({
44-
allowNegative,
44+
expect(result).toEqual({
45+
allowNegative,
4546
negativeSignAfter : false,
4647
decimalSeparator,
4748
fixed : true,
@@ -50,6 +51,7 @@ describe(
5051
prefix,
5152
thousandsSeparator : '.',
5253
cursor,
54+
allowEmpty : false,
5355
});
5456
},
5557
);

0 commit comments

Comments
 (0)