use-mask-input 3.6.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -76
- package/README.md +2 -251
- package/dist/index.cjs +157 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -11
- package/dist/index.d.ts +52 -11
- package/dist/index.js +158 -85
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
- package/src/api/index.ts +4 -0
- package/src/api/useHookFormMask.spec.ts +146 -0
- package/src/api/useHookFormMask.ts +56 -0
- package/src/api/useMaskInput-server.spec.tsx +30 -0
- package/src/api/useMaskInput.spec.tsx +220 -0
- package/src/api/useMaskInput.ts +64 -0
- package/src/api/withHookFormMask.spec.ts +155 -0
- package/src/api/withHookFormMask.ts +54 -0
- package/src/api/withMask.spec.ts +93 -0
- package/src/api/withMask.ts +25 -0
- package/src/core/elementResolver.spec.ts +175 -0
- package/src/core/elementResolver.ts +84 -0
- package/src/core/index.ts +3 -0
- package/src/core/maskConfig.spec.ts +183 -0
- package/src/{utils/getMaskOptions.ts → core/maskConfig.ts} +12 -3
- package/src/core/maskEngine.spec.ts +108 -0
- package/src/core/maskEngine.ts +47 -0
- package/src/index.tsx +12 -5
- package/src/{types.ts → types/index.ts} +13 -0
- package/src/utils/flow.spec.ts +27 -30
- package/src/utils/flow.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/isServer.spec.ts +15 -0
- package/src/utils/moduleInterop.spec.ts +37 -0
- package/src/useHookFormMask.ts +0 -47
- package/src/useMaskInput.ts +0 -41
- package/src/utils/getMaskOptions.spec.ts +0 -126
- package/src/withHookFormMask.ts +0 -34
- package/src/withMask.ts +0 -18
- /package/src/{inputmask.types.ts → types/inputmask.types.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-mask-input",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.1",
|
|
4
|
+
"private": false,
|
|
4
5
|
"description": "A react Hook for build elegant input masks. Compatible with React Hook Form",
|
|
5
6
|
"author": "Eduardo Borges<euduardoborges@gmail.com>",
|
|
6
7
|
"type": "module",
|
|
7
|
-
"repository":
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/eduardoborges/use-mask-input"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"source": "./src/index.tsx",
|
|
8
15
|
"exports": {
|
|
9
16
|
".": {
|
|
10
17
|
"types": "./dist/index.d.ts",
|
|
@@ -16,13 +23,6 @@
|
|
|
16
23
|
"node": ">=16",
|
|
17
24
|
"npm": ">=7"
|
|
18
25
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "./scripts.sh build",
|
|
21
|
-
"dev": "./scripts.sh dev",
|
|
22
|
-
"lint": "./scripts.sh lint",
|
|
23
|
-
"test": "./scripts.sh test",
|
|
24
|
-
"prepare": "./scripts.sh prepare"
|
|
25
|
-
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
28
|
"src",
|
|
@@ -36,13 +36,9 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/compat": "^1.3.2",
|
|
38
38
|
"@eslint/js": "^9.35.0",
|
|
39
|
-
"@semantic-release/changelog": "6.0.3",
|
|
40
|
-
"@semantic-release/commit-analyzer": "13.0.1",
|
|
41
|
-
"@semantic-release/git": "10.0.1",
|
|
42
|
-
"@semantic-release/github": "11.0.5",
|
|
43
|
-
"@semantic-release/npm": "12.0.2",
|
|
44
|
-
"@semantic-release/release-notes-generator": "14.0.3",
|
|
45
39
|
"@stylistic/eslint-plugin": "3.1.0",
|
|
40
|
+
"@testing-library/dom": "^10.4.0",
|
|
41
|
+
"@testing-library/react": "^16.1.0",
|
|
46
42
|
"@types/inputmask": "5.0.7",
|
|
47
43
|
"@types/node": "22",
|
|
48
44
|
"@types/react": ">=17",
|
|
@@ -56,16 +52,20 @@
|
|
|
56
52
|
"eslint-plugin-react": "^7.37.5",
|
|
57
53
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
58
54
|
"inputmask": "5.0.10-beta.61",
|
|
55
|
+
"jsdom": "^25.0.1",
|
|
59
56
|
"react-hook-form": "7.62.0",
|
|
60
|
-
"read-pkg": "9.0.1",
|
|
61
|
-
"semantic-release": "24.2.7",
|
|
62
|
-
"simple-git-hooks": "2.13.1",
|
|
63
57
|
"tsup": "8.5.0",
|
|
64
58
|
"typescript": "5.1",
|
|
65
59
|
"typescript-eslint": "^8.42.0",
|
|
66
60
|
"vitest": "3.2.4"
|
|
67
61
|
},
|
|
68
|
-
"
|
|
69
|
-
"
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"dev": "tsup --watch",
|
|
65
|
+
"lint": "eslint ./src --ext ts,tsx",
|
|
66
|
+
"test": "vitest --dir ./src --run --coverage",
|
|
67
|
+
"type-check": "tsc --noEmit",
|
|
68
|
+
"clean": "rm -rf dist",
|
|
69
|
+
"postbuild": "cp ../../README.md README.md"
|
|
70
70
|
}
|
|
71
|
-
}
|
|
71
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import inputmask from 'inputmask';
|
|
2
|
+
import {
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe, expect, it, vi,
|
|
5
|
+
} from 'vitest';
|
|
6
|
+
|
|
7
|
+
import useHookFormMask from './useHookFormMask';
|
|
8
|
+
|
|
9
|
+
import type { FieldValues, UseFormRegister } from 'react-hook-form';
|
|
10
|
+
|
|
11
|
+
vi.mock('inputmask', () => ({
|
|
12
|
+
default: vi.fn((options) => ({
|
|
13
|
+
mask: vi.fn(),
|
|
14
|
+
options,
|
|
15
|
+
})),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe('useHookFormMask', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns a function', () => {
|
|
24
|
+
const registerFn = vi.fn(() => ({
|
|
25
|
+
ref: vi.fn(),
|
|
26
|
+
prevRef: vi.fn(),
|
|
27
|
+
onChange: vi.fn(),
|
|
28
|
+
onBlur: vi.fn(),
|
|
29
|
+
name: 'test',
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const maskedRegister = useHookFormMask(registerFn as UseFormRegister<FieldValues>);
|
|
33
|
+
expect(typeof maskedRegister).toBe('function');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('registers field with mask', () => {
|
|
37
|
+
const input = document.createElement('input');
|
|
38
|
+
const refCallback = vi.fn();
|
|
39
|
+
const registerFn = vi.fn(() => ({
|
|
40
|
+
ref: refCallback,
|
|
41
|
+
prevRef: vi.fn(),
|
|
42
|
+
onChange: vi.fn(),
|
|
43
|
+
onBlur: vi.fn(),
|
|
44
|
+
name: 'phone',
|
|
45
|
+
}));
|
|
46
|
+
const maskFn = vi.fn();
|
|
47
|
+
vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
|
|
48
|
+
|
|
49
|
+
const maskedRegister = useHookFormMask(registerFn as UseFormRegister<FieldValues>);
|
|
50
|
+
const result = maskedRegister('phone', '999-999');
|
|
51
|
+
|
|
52
|
+
expect(registerFn).toHaveBeenCalledWith('phone', undefined);
|
|
53
|
+
expect(result.ref).toBeDefined();
|
|
54
|
+
expect(typeof result.ref).toBe('function');
|
|
55
|
+
|
|
56
|
+
// call the ref callback
|
|
57
|
+
result.ref?.(input);
|
|
58
|
+
|
|
59
|
+
expect(maskFn).toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('merges register options with mask options', () => {
|
|
63
|
+
const registerFn = vi.fn(() => ({
|
|
64
|
+
ref: vi.fn(),
|
|
65
|
+
prevRef: vi.fn(),
|
|
66
|
+
onChange: vi.fn(),
|
|
67
|
+
onBlur: vi.fn(),
|
|
68
|
+
name: 'phone',
|
|
69
|
+
}));
|
|
70
|
+
const maskFn = vi.fn();
|
|
71
|
+
vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
|
|
72
|
+
|
|
73
|
+
const maskedRegister = useHookFormMask(registerFn as UseFormRegister<FieldValues>);
|
|
74
|
+
maskedRegister('phone', '999-999', { required: true });
|
|
75
|
+
|
|
76
|
+
expect(registerFn).toHaveBeenCalledWith('phone', { required: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('works with alias masks', () => {
|
|
80
|
+
const registerFn = vi.fn(() => ({
|
|
81
|
+
ref: vi.fn(),
|
|
82
|
+
prevRef: vi.fn(),
|
|
83
|
+
onChange: vi.fn(),
|
|
84
|
+
onBlur: vi.fn(),
|
|
85
|
+
name: 'cpf',
|
|
86
|
+
}));
|
|
87
|
+
const maskFn = vi.fn();
|
|
88
|
+
vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
|
|
89
|
+
|
|
90
|
+
const maskedRegister = useHookFormMask(registerFn as UseFormRegister<FieldValues>);
|
|
91
|
+
const result = maskedRegister('cpf', 'cpf');
|
|
92
|
+
|
|
93
|
+
expect(result.ref).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('works with array masks', () => {
|
|
97
|
+
const registerFn = vi.fn(() => ({
|
|
98
|
+
ref: vi.fn(),
|
|
99
|
+
prevRef: vi.fn(),
|
|
100
|
+
onChange: vi.fn(),
|
|
101
|
+
onBlur: vi.fn(),
|
|
102
|
+
name: 'phone',
|
|
103
|
+
}));
|
|
104
|
+
const maskFn = vi.fn();
|
|
105
|
+
vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
|
|
106
|
+
|
|
107
|
+
const maskedRegister = useHookFormMask(registerFn as UseFormRegister<FieldValues>);
|
|
108
|
+
const result = maskedRegister('phone', ['999-999', '9999-9999']);
|
|
109
|
+
|
|
110
|
+
expect(result.ref).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('preserves all register return properties', () => {
|
|
114
|
+
const onChange = vi.fn();
|
|
115
|
+
const onBlur = vi.fn();
|
|
116
|
+
const registerFn = vi.fn(() => ({
|
|
117
|
+
ref: vi.fn(),
|
|
118
|
+
prevRef: vi.fn(),
|
|
119
|
+
onChange,
|
|
120
|
+
onBlur,
|
|
121
|
+
name: 'phone',
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
const maskedRegister = useHookFormMask(registerFn as UseFormRegister<FieldValues>);
|
|
125
|
+
const result = maskedRegister('phone', '999-999');
|
|
126
|
+
|
|
127
|
+
expect(result.onChange).toBe(onChange);
|
|
128
|
+
expect(result.onBlur).toBe(onBlur);
|
|
129
|
+
expect(result.name).toBe('phone');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('handles null ref from register', () => {
|
|
133
|
+
const registerFn = vi.fn(() => ({
|
|
134
|
+
ref: undefined,
|
|
135
|
+
prevRef: vi.fn(),
|
|
136
|
+
onChange: vi.fn(),
|
|
137
|
+
onBlur: vi.fn(),
|
|
138
|
+
name: 'phone',
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
const maskedRegister = useHookFormMask(registerFn as unknown as UseFormRegister<FieldValues>);
|
|
142
|
+
const result = maskedRegister('phone', '999-999');
|
|
143
|
+
|
|
144
|
+
expect(result.ref).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { applyMaskToElement } from '../core';
|
|
2
|
+
import { flow } from '../utils';
|
|
3
|
+
|
|
4
|
+
import type { RefCallback } from 'react';
|
|
5
|
+
import type {
|
|
6
|
+
FieldValues, Path,
|
|
7
|
+
RegisterOptions,
|
|
8
|
+
UseFormRegister,
|
|
9
|
+
} from 'react-hook-form';
|
|
10
|
+
|
|
11
|
+
import type { Mask, Options, UseHookFormMaskReturn } from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a masked version of React Hook Form's register function.
|
|
15
|
+
* Takes react-hook-form's register and adds automatic masking. Like an upgrade.
|
|
16
|
+
*
|
|
17
|
+
* @template T - The form data type
|
|
18
|
+
* @template D - The register options type
|
|
19
|
+
* @param registerFn - The register function from useForm hook
|
|
20
|
+
* @returns A function that registers a field with mask support
|
|
21
|
+
*/
|
|
22
|
+
export default function useHookFormMask<
|
|
23
|
+
T extends FieldValues, D extends RegisterOptions,
|
|
24
|
+
>(registerFn: UseFormRegister<T>) {
|
|
25
|
+
return (fieldName: Path<T>, mask: Mask, options?: (
|
|
26
|
+
D & Options) | Options | D): UseHookFormMaskReturn<T> => {
|
|
27
|
+
if (!registerFn) throw new Error('registerFn is required');
|
|
28
|
+
|
|
29
|
+
const registerReturn = registerFn(fieldName, options as Options);
|
|
30
|
+
const { ref } = registerReturn as UseHookFormMaskReturn<T>;
|
|
31
|
+
|
|
32
|
+
const applyMaskToRef = (_ref: HTMLElement | null) => {
|
|
33
|
+
if (_ref) applyMaskToElement(_ref, mask, options as Options);
|
|
34
|
+
return _ref;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const refWithMask = ref
|
|
38
|
+
? flow(applyMaskToRef, ref)
|
|
39
|
+
: applyMaskToRef;
|
|
40
|
+
|
|
41
|
+
const result = {
|
|
42
|
+
...registerReturn,
|
|
43
|
+
ref: refWithMask as RefCallback<HTMLElement | null>,
|
|
44
|
+
} as UseHookFormMaskReturn<T>;
|
|
45
|
+
|
|
46
|
+
// change prevRef to non-enumerable
|
|
47
|
+
Object.defineProperty(result, 'prevRef', {
|
|
48
|
+
value: ref,
|
|
49
|
+
enumerable: false,
|
|
50
|
+
writable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import {
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe, expect, it, vi,
|
|
5
|
+
} from 'vitest';
|
|
6
|
+
|
|
7
|
+
vi.mock('../utils/isServer', () => ({
|
|
8
|
+
default: true,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('useMaskInput server-side', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
vi.resetModules();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns no-op function on server', async () => {
|
|
18
|
+
const { default: useMaskInput } = await import('./useMaskInput');
|
|
19
|
+
const { result } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
20
|
+
|
|
21
|
+
expect(typeof result.current).toBe('function');
|
|
22
|
+
|
|
23
|
+
act(() => {
|
|
24
|
+
result.current(document.createElement('input'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// should do nothing on server
|
|
28
|
+
expect(result.current).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import inputmask from 'inputmask';
|
|
3
|
+
import {
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe, expect, it, vi,
|
|
6
|
+
} from 'vitest';
|
|
7
|
+
|
|
8
|
+
import useMaskInput from './useMaskInput';
|
|
9
|
+
import * as core from '../core';
|
|
10
|
+
|
|
11
|
+
import type { Input } from '../types';
|
|
12
|
+
|
|
13
|
+
vi.mock('inputmask', () => ({
|
|
14
|
+
default: vi.fn((options) => ({
|
|
15
|
+
mask: vi.fn(),
|
|
16
|
+
options,
|
|
17
|
+
})),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('../utils/isServer', () => ({
|
|
21
|
+
default: false,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('useMaskInput', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns a ref callback function', () => {
|
|
30
|
+
const { result } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
31
|
+
expect(typeof result.current).toBe('function');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('handles null input', () => {
|
|
35
|
+
const { result } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
36
|
+
|
|
37
|
+
act(() => {
|
|
38
|
+
result.current(null);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(result.current).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('handles direct input element', () => {
|
|
45
|
+
const input = document.createElement('input');
|
|
46
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
47
|
+
|
|
48
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
result.current(input);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
rerender();
|
|
55
|
+
|
|
56
|
+
expect(inputmask).toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('handles ref object', () => {
|
|
60
|
+
const input = document.createElement('input');
|
|
61
|
+
const ref = { current: input };
|
|
62
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
63
|
+
|
|
64
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
65
|
+
|
|
66
|
+
act(() => {
|
|
67
|
+
result.current(ref as unknown as Input);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
rerender();
|
|
71
|
+
|
|
72
|
+
expect(inputmask).toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('handles wrapper element with input inside', () => {
|
|
76
|
+
const wrapper = document.createElement('div');
|
|
77
|
+
const input = document.createElement('input');
|
|
78
|
+
wrapper.appendChild(input);
|
|
79
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
80
|
+
|
|
81
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
82
|
+
|
|
83
|
+
act(() => {
|
|
84
|
+
result.current(wrapper);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
rerender();
|
|
88
|
+
|
|
89
|
+
expect(inputmask).toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('handles invalid element in ref', () => {
|
|
93
|
+
const invalidRef = { current: 'not an element' };
|
|
94
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
95
|
+
|
|
96
|
+
const { result } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
97
|
+
|
|
98
|
+
act(() => {
|
|
99
|
+
result.current(invalidRef as unknown as Input);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(result.current).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('handles element that is not HTMLElement in useEffect', () => {
|
|
106
|
+
vi.spyOn(core, 'isHTMLElement').mockReturnValueOnce(false);
|
|
107
|
+
|
|
108
|
+
const invalidElement = { nodeType: 1 } as unknown as Input;
|
|
109
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
110
|
+
|
|
111
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
112
|
+
|
|
113
|
+
act(() => {
|
|
114
|
+
result.current(invalidElement as unknown as Input);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
rerender();
|
|
118
|
+
|
|
119
|
+
expect(result.current).toBeDefined();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('handles wrapper without input inside', () => {
|
|
123
|
+
const wrapper = document.createElement('div');
|
|
124
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
125
|
+
|
|
126
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
127
|
+
|
|
128
|
+
act(() => {
|
|
129
|
+
result.current(wrapper);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
rerender();
|
|
133
|
+
|
|
134
|
+
expect(inputmask).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('works with custom options', () => {
|
|
138
|
+
const input = document.createElement('input');
|
|
139
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
140
|
+
|
|
141
|
+
const { result, rerender } = renderHook(() => useMaskInput({
|
|
142
|
+
mask: '999-999',
|
|
143
|
+
options: { placeholder: '_' },
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
act(() => {
|
|
147
|
+
result.current(input);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
rerender();
|
|
151
|
+
|
|
152
|
+
expect(inputmask).toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('works with alias masks', () => {
|
|
156
|
+
const input = document.createElement('input');
|
|
157
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
158
|
+
|
|
159
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: 'cpf' }));
|
|
160
|
+
|
|
161
|
+
act(() => {
|
|
162
|
+
result.current(input);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
rerender();
|
|
166
|
+
|
|
167
|
+
expect(inputmask).toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('calls register callback when provided', () => {
|
|
171
|
+
const input = document.createElement('input');
|
|
172
|
+
const register = vi.fn();
|
|
173
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
174
|
+
|
|
175
|
+
const { result, rerender } = renderHook(() => useMaskInput({
|
|
176
|
+
mask: '999-999',
|
|
177
|
+
register,
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
act(() => {
|
|
181
|
+
result.current(input);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
rerender();
|
|
185
|
+
|
|
186
|
+
expect(inputmask).toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('handles textarea element', () => {
|
|
190
|
+
const textarea = document.createElement('textarea');
|
|
191
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
192
|
+
|
|
193
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
194
|
+
|
|
195
|
+
act(() => {
|
|
196
|
+
result.current(textarea);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
rerender();
|
|
200
|
+
|
|
201
|
+
expect(inputmask).toHaveBeenCalled();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('handles case where findInputElement returns valid element', () => {
|
|
205
|
+
const wrapper = document.createElement('div');
|
|
206
|
+
const input = document.createElement('input');
|
|
207
|
+
wrapper.appendChild(input);
|
|
208
|
+
vi.mocked(inputmask).mockReturnValue({ mask: vi.fn() } as unknown as Inputmask.Instance);
|
|
209
|
+
|
|
210
|
+
const { result, rerender } = renderHook(() => useMaskInput({ mask: '999-999' }));
|
|
211
|
+
|
|
212
|
+
act(() => {
|
|
213
|
+
result.current(wrapper);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
rerender();
|
|
217
|
+
|
|
218
|
+
expect(inputmask).toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createMaskInstance, findInputElement, isHTMLElement, resolveInputRef,
|
|
5
|
+
} from '../core';
|
|
6
|
+
import isServer from '../utils/isServer';
|
|
7
|
+
|
|
8
|
+
import type { Input, Mask, Options } from '../types';
|
|
9
|
+
|
|
10
|
+
interface UseMaskInputOptions {
|
|
11
|
+
mask: Mask;
|
|
12
|
+
register?: (element: HTMLElement) => void;
|
|
13
|
+
options?: Options;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* React hook for applying input masks to form elements.
|
|
18
|
+
* Works with Ant Design and other wrapped components too.
|
|
19
|
+
*
|
|
20
|
+
* @param props - Configuration object
|
|
21
|
+
* @param props.mask - The mask pattern to apply
|
|
22
|
+
* @param props.register - Optional callback that receives the element
|
|
23
|
+
* @param props.options - Optional mask configuration options
|
|
24
|
+
* @returns A ref callback function to attach to the input element
|
|
25
|
+
*/
|
|
26
|
+
export default function useMaskInput(props: UseMaskInputOptions): ((input: Input | null) => void) {
|
|
27
|
+
const { mask, register, options } = props;
|
|
28
|
+
const ref = useRef<HTMLInputElement | null>(null);
|
|
29
|
+
const maskInput = useMemo(() => createMaskInstance(mask, options), [mask, options]);
|
|
30
|
+
|
|
31
|
+
if (isServer) {
|
|
32
|
+
return (): void => {
|
|
33
|
+
// server doesn't have dom, so just do nothing
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (isServer || !ref.current) return;
|
|
40
|
+
|
|
41
|
+
if (!isHTMLElement(ref.current)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const inputElement = findInputElement(ref.current);
|
|
46
|
+
|
|
47
|
+
if (inputElement && isHTMLElement(inputElement)) {
|
|
48
|
+
maskInput.mask(inputElement);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (register && isHTMLElement(ref.current)) {
|
|
52
|
+
register(ref.current);
|
|
53
|
+
}
|
|
54
|
+
}, [mask, register, options, maskInput, ref]);
|
|
55
|
+
|
|
56
|
+
return (input: Input | null): void => {
|
|
57
|
+
if (!input) {
|
|
58
|
+
ref.current = null;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ref.current = resolveInputRef(input);
|
|
63
|
+
};
|
|
64
|
+
}
|