use-mask-input 3.6.0 → 3.7.0
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 +53 -76
- package/README.md +2 -251
- package/dist/antd/index.cjs +67 -0
- package/dist/antd/index.cjs.map +1 -0
- package/dist/antd/index.d.cts +37 -0
- package/dist/antd/index.d.ts +37 -0
- package/dist/antd/index.js +64 -0
- package/dist/antd/index.js.map +1 -0
- package/dist/chunk-4Y2DTPBL.cjs +3841 -0
- package/dist/chunk-4Y2DTPBL.cjs.map +1 -0
- package/dist/chunk-JGOZSJMW.js +3829 -0
- package/dist/chunk-JGOZSJMW.js.map +1 -0
- package/dist/index-F3rlTTTe.d.cts +583 -0
- package/dist/index-F3rlTTTe.d.ts +583 -0
- package/dist/index.cjs +72 -3870
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -585
- package/dist/index.d.ts +48 -585
- package/dist/index.js +72 -3870
- package/dist/index.js.map +1 -1
- package/package.json +27 -21
- package/src/antd/index.ts +9 -0
- package/src/antd/useHookFormMaskAntd.spec.ts +142 -0
- package/src/antd/useHookFormMaskAntd.ts +57 -0
- package/src/antd/useMaskInputAntd-server.spec.tsx +36 -0
- package/src/antd/useMaskInputAntd.spec.tsx +108 -0
- package/src/antd/useMaskInputAntd.ts +77 -0
- 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 +73 -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/core/maskConfig.ts +56 -0
- 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/utils/getMaskOptions.ts +0 -94
- 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,28 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-mask-input",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
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",
|
|
11
18
|
"import": "./dist/index.js",
|
|
12
19
|
"require": "./dist/index.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./antd": {
|
|
22
|
+
"types": "./dist/antd.d.ts",
|
|
23
|
+
"import": "./dist/antd.js",
|
|
24
|
+
"require": "./dist/antd.cjs"
|
|
13
25
|
}
|
|
14
26
|
},
|
|
15
27
|
"engines": {
|
|
16
28
|
"node": ">=16",
|
|
17
29
|
"npm": ">=7"
|
|
18
30
|
},
|
|
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
31
|
"files": [
|
|
27
32
|
"dist",
|
|
28
33
|
"src",
|
|
@@ -36,18 +41,15 @@
|
|
|
36
41
|
"devDependencies": {
|
|
37
42
|
"@eslint/compat": "^1.3.2",
|
|
38
43
|
"@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
44
|
"@stylistic/eslint-plugin": "3.1.0",
|
|
45
|
+
"@testing-library/dom": "^10.4.0",
|
|
46
|
+
"@testing-library/react": "^16.1.0",
|
|
46
47
|
"@types/inputmask": "5.0.7",
|
|
47
48
|
"@types/node": "22",
|
|
48
49
|
"@types/react": ">=17",
|
|
49
50
|
"@types/react-dom": ">=17",
|
|
50
51
|
"@vitest/coverage-v8": "3.2.4",
|
|
52
|
+
"antd": "^6.2.3",
|
|
51
53
|
"eslint": "^9.35.0",
|
|
52
54
|
"eslint-config-airbnb-extended": "^2.2.0",
|
|
53
55
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
@@ -56,16 +58,20 @@
|
|
|
56
58
|
"eslint-plugin-react": "^7.37.5",
|
|
57
59
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
58
60
|
"inputmask": "5.0.10-beta.61",
|
|
61
|
+
"jsdom": "^25.0.1",
|
|
59
62
|
"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
63
|
"tsup": "8.5.0",
|
|
64
64
|
"typescript": "5.1",
|
|
65
65
|
"typescript-eslint": "^8.42.0",
|
|
66
66
|
"vitest": "3.2.4"
|
|
67
67
|
},
|
|
68
|
-
"
|
|
69
|
-
"
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsup",
|
|
70
|
+
"dev": "tsup --watch",
|
|
71
|
+
"lint": "eslint ./src --ext ts,tsx",
|
|
72
|
+
"test": "vitest --dir ./src --run --coverage",
|
|
73
|
+
"type-check": "tsc --noEmit",
|
|
74
|
+
"clean": "rm -rf dist",
|
|
75
|
+
"postbuild": "cp ../../README.md README.md"
|
|
70
76
|
}
|
|
71
|
-
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
beforeEach,
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
it,
|
|
6
|
+
vi,
|
|
7
|
+
} from 'vitest';
|
|
8
|
+
|
|
9
|
+
vi.mock('../core', () => ({
|
|
10
|
+
applyMaskToElement: vi.fn(),
|
|
11
|
+
resolveInputRef: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
import { applyMaskToElement, resolveInputRef } from '../core';
|
|
15
|
+
import useHookFormMaskAntd from './useHookFormMaskAntd';
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
FieldValues,
|
|
19
|
+
UseFormRegister,
|
|
20
|
+
} from 'react-hook-form';
|
|
21
|
+
|
|
22
|
+
describe('useHookFormMaskAntd', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns a function', () => {
|
|
28
|
+
const registerFn = vi.fn(() => ({
|
|
29
|
+
ref: vi.fn(),
|
|
30
|
+
onChange: vi.fn(),
|
|
31
|
+
onBlur: vi.fn(),
|
|
32
|
+
name: 'test',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const maskedRegister = useHookFormMaskAntd(
|
|
36
|
+
registerFn as UseFormRegister<FieldValues>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(typeof maskedRegister).toBe('function');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('throws when registerFn is missing', () => {
|
|
43
|
+
const maskedRegister = useHookFormMaskAntd(
|
|
44
|
+
undefined as unknown as UseFormRegister<FieldValues>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(() => maskedRegister('field' as never, '999-999'))
|
|
48
|
+
.toThrowError('registerFn is required');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('registers field with mask and calls core helpers', () => {
|
|
52
|
+
const inputElement = document.createElement('input');
|
|
53
|
+
const prevRef = vi.fn();
|
|
54
|
+
const registerFn = vi.fn(() => ({
|
|
55
|
+
ref: prevRef,
|
|
56
|
+
onChange: vi.fn(),
|
|
57
|
+
onBlur: vi.fn(),
|
|
58
|
+
name: 'phone',
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
vi.mocked(resolveInputRef).mockReturnValue(inputElement);
|
|
62
|
+
|
|
63
|
+
const maskedRegister = useHookFormMaskAntd(
|
|
64
|
+
registerFn as UseFormRegister<FieldValues>,
|
|
65
|
+
);
|
|
66
|
+
const options = { placeholder: '_' } as never;
|
|
67
|
+
|
|
68
|
+
const result = maskedRegister('phone' as never, '999-999', options);
|
|
69
|
+
|
|
70
|
+
expect(registerFn).toHaveBeenCalledWith('phone', options);
|
|
71
|
+
expect(result.ref).toBeDefined();
|
|
72
|
+
expect(typeof result.ref).toBe('function');
|
|
73
|
+
|
|
74
|
+
const inputRef = { input: inputElement } as unknown as Parameters<
|
|
75
|
+
ReturnType<typeof useHookFormMaskAntd<FieldValues, never>>
|
|
76
|
+
>[1];
|
|
77
|
+
|
|
78
|
+
result.ref(inputRef);
|
|
79
|
+
|
|
80
|
+
expect(resolveInputRef).toHaveBeenCalledWith(inputElement);
|
|
81
|
+
expect(applyMaskToElement).toHaveBeenCalledWith(
|
|
82
|
+
inputElement,
|
|
83
|
+
'999-999',
|
|
84
|
+
options,
|
|
85
|
+
);
|
|
86
|
+
expect(prevRef).toHaveBeenCalledWith(inputElement);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('handles null ref from register', () => {
|
|
90
|
+
const registerFn = vi.fn(() => ({
|
|
91
|
+
ref: undefined,
|
|
92
|
+
onChange: vi.fn(),
|
|
93
|
+
onBlur: vi.fn(),
|
|
94
|
+
name: 'phone',
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
const maskedRegister = useHookFormMaskAntd(
|
|
98
|
+
registerFn as unknown as UseFormRegister<FieldValues>,
|
|
99
|
+
);
|
|
100
|
+
const result = maskedRegister('phone' as never, '999-999');
|
|
101
|
+
|
|
102
|
+
expect(result.ref).toBeDefined();
|
|
103
|
+
|
|
104
|
+
const inputElement = document.createElement('input');
|
|
105
|
+
vi.mocked(resolveInputRef).mockReturnValue(inputElement);
|
|
106
|
+
|
|
107
|
+
const inputRef = { input: inputElement } as unknown as Parameters<
|
|
108
|
+
ReturnType<typeof useHookFormMaskAntd<FieldValues, never>>
|
|
109
|
+
>[1];
|
|
110
|
+
|
|
111
|
+
result.ref(inputRef);
|
|
112
|
+
|
|
113
|
+
expect(applyMaskToElement).toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('preserves register return properties and defines non-enumerable prevRef', () => {
|
|
117
|
+
const onChange = vi.fn();
|
|
118
|
+
const onBlur = vi.fn();
|
|
119
|
+
const prevRef = vi.fn();
|
|
120
|
+
const registerFn = vi.fn(() => ({
|
|
121
|
+
ref: prevRef,
|
|
122
|
+
onChange,
|
|
123
|
+
onBlur,
|
|
124
|
+
name: 'phone',
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
const maskedRegister = useHookFormMaskAntd(
|
|
128
|
+
registerFn as UseFormRegister<FieldValues>,
|
|
129
|
+
);
|
|
130
|
+
const result = maskedRegister('phone' as never, '999-999');
|
|
131
|
+
|
|
132
|
+
expect(result.onChange).toBe(onChange);
|
|
133
|
+
expect(result.onBlur).toBe(onBlur);
|
|
134
|
+
expect(result.name).toBe('phone');
|
|
135
|
+
|
|
136
|
+
const descriptor = Object.getOwnPropertyDescriptor(result, 'prevRef');
|
|
137
|
+
expect(descriptor?.enumerable).toBe(false);
|
|
138
|
+
expect((result as unknown as { prevRef: typeof prevRef }).prevRef)
|
|
139
|
+
.toBe(prevRef);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { applyMaskToElement, resolveInputRef } from '../core';
|
|
2
|
+
|
|
3
|
+
import type { InputRef } from 'antd';
|
|
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
|
+
export type UseHookFormMaskAntdReturn<T extends FieldValues> = Omit<
|
|
14
|
+
UseHookFormMaskReturn<T>,
|
|
15
|
+
'ref'
|
|
16
|
+
> & { ref: RefCallback<InputRef | null> };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ant Design version of useHookFormMask.
|
|
20
|
+
* Creates a masked register that works with Ant Design Input (ref receives InputRef).
|
|
21
|
+
*
|
|
22
|
+
* @template T - The form data type
|
|
23
|
+
* @template D - The register options type
|
|
24
|
+
* @param registerFn - The register function from useForm hook
|
|
25
|
+
* @returns A function that registers a field with mask support for Ant Design Input
|
|
26
|
+
*/
|
|
27
|
+
export default function useHookFormMaskAntd<
|
|
28
|
+
T extends FieldValues, D extends RegisterOptions,
|
|
29
|
+
>(registerFn: UseFormRegister<T>) {
|
|
30
|
+
return (fieldName: Path<T>, mask: Mask, options?: (
|
|
31
|
+
D & Options) | Options | D): UseHookFormMaskAntdReturn<T> => {
|
|
32
|
+
if (!registerFn) throw new Error('registerFn is required');
|
|
33
|
+
|
|
34
|
+
const registerReturn = registerFn(fieldName, options as Options);
|
|
35
|
+
const { ref } = registerReturn as UseHookFormMaskReturn<T>;
|
|
36
|
+
|
|
37
|
+
const refWithMask: RefCallback<InputRef | null> = (inputRef) => {
|
|
38
|
+
const element = inputRef ? resolveInputRef(inputRef.input) : null;
|
|
39
|
+
if (element) applyMaskToElement(element, mask, options as Options);
|
|
40
|
+
if (ref) ref(element);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const result = {
|
|
44
|
+
...registerReturn,
|
|
45
|
+
ref: refWithMask,
|
|
46
|
+
} as UseHookFormMaskAntdReturn<T>;
|
|
47
|
+
|
|
48
|
+
Object.defineProperty(result, 'prevRef', {
|
|
49
|
+
value: ref,
|
|
50
|
+
enumerable: false,
|
|
51
|
+
writable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import {
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
} from 'vitest';
|
|
9
|
+
|
|
10
|
+
vi.mock('../utils/isServer', () => ({
|
|
11
|
+
default: true,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('useMaskInputAntd server-side', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
vi.resetModules();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns no-op function on server', async () => {
|
|
21
|
+
const { default: useMaskInputAntd } = await import('./useMaskInputAntd');
|
|
22
|
+
const { result } = renderHook(
|
|
23
|
+
() => useMaskInputAntd({ mask: '999-999' }),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(typeof result.current).toBe('function');
|
|
27
|
+
|
|
28
|
+
act(() => {
|
|
29
|
+
result.current({ input: document.createElement('input') } as unknown as { input: HTMLElement });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// should do nothing on server
|
|
33
|
+
expect(result.current).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import inputmask from 'inputmask';
|
|
3
|
+
import {
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
it,
|
|
8
|
+
vi,
|
|
9
|
+
} from 'vitest';
|
|
10
|
+
|
|
11
|
+
vi.mock('inputmask', () => ({
|
|
12
|
+
default: vi.fn((options) => ({
|
|
13
|
+
mask: vi.fn(),
|
|
14
|
+
options,
|
|
15
|
+
})),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('../utils/isServer', () => ({
|
|
19
|
+
default: false,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import useMaskInputAntd from './useMaskInputAntd';
|
|
23
|
+
|
|
24
|
+
describe('useMaskInputAntd', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns a ref callback function', () => {
|
|
30
|
+
const { result } = renderHook(() => useMaskInputAntd({ mask: '999-999' }));
|
|
31
|
+
expect(typeof result.current).toBe('function');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('handles null input ref', () => {
|
|
35
|
+
const { result } = renderHook(() => useMaskInputAntd({ mask: '999-999' }));
|
|
36
|
+
|
|
37
|
+
act(() => {
|
|
38
|
+
result.current(null);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(result.current).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('applies mask to element when given InputRef with input element', () => {
|
|
45
|
+
const inputElement = document.createElement('input');
|
|
46
|
+
vi.mocked(inputmask).mockReturnValue({
|
|
47
|
+
mask: vi.fn(),
|
|
48
|
+
} as unknown as Inputmask.Instance);
|
|
49
|
+
|
|
50
|
+
const { result, rerender } = renderHook(
|
|
51
|
+
() => useMaskInputAntd({ mask: '999-999' }),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
act(() => {
|
|
55
|
+
result.current({ input: inputElement } as unknown as { input: HTMLElement });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
rerender();
|
|
59
|
+
|
|
60
|
+
expect(inputmask).toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('works with custom options', () => {
|
|
64
|
+
const inputElement = document.createElement('input');
|
|
65
|
+
vi.mocked(inputmask).mockReturnValue({
|
|
66
|
+
mask: vi.fn(),
|
|
67
|
+
} as unknown as Inputmask.Instance);
|
|
68
|
+
|
|
69
|
+
const { result, rerender } = renderHook(
|
|
70
|
+
() => useMaskInputAntd({
|
|
71
|
+
mask: '999-999',
|
|
72
|
+
options: { placeholder: '_' },
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
act(() => {
|
|
77
|
+
result.current({ input: inputElement } as unknown as { input: HTMLElement });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
rerender();
|
|
81
|
+
|
|
82
|
+
expect(inputmask).toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('accepts register option and applies mask', () => {
|
|
86
|
+
const inputElement = document.createElement('input');
|
|
87
|
+
const register = vi.fn();
|
|
88
|
+
vi.mocked(inputmask).mockReturnValue({
|
|
89
|
+
mask: vi.fn(),
|
|
90
|
+
} as unknown as Inputmask.Instance);
|
|
91
|
+
|
|
92
|
+
const { result, rerender } = renderHook(
|
|
93
|
+
() => useMaskInputAntd({
|
|
94
|
+
mask: '999-999',
|
|
95
|
+
register,
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
act(() => {
|
|
100
|
+
result.current({ input: inputElement } as unknown as { input: HTMLElement });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
rerender();
|
|
104
|
+
|
|
105
|
+
expect(inputmask).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
} from 'react';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createMaskInstance, findInputElement, isHTMLElement, resolveInputRef,
|
|
10
|
+
} from '../core';
|
|
11
|
+
import isServer from '../utils/isServer';
|
|
12
|
+
|
|
13
|
+
import type { InputRef } from 'antd';
|
|
14
|
+
|
|
15
|
+
import type { Mask, Options } from '../types';
|
|
16
|
+
|
|
17
|
+
interface UseMaskInputOptions {
|
|
18
|
+
mask: Mask;
|
|
19
|
+
register?: (element: HTMLElement) => void;
|
|
20
|
+
options?: Options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* React hook for applying input masks to form elements.
|
|
25
|
+
* Works with Ant Design and other wrapped components too.
|
|
26
|
+
*
|
|
27
|
+
* @param props - Configuration object
|
|
28
|
+
* @param props.mask - The mask pattern to apply
|
|
29
|
+
* @param props.register - Optional callback that receives the element
|
|
30
|
+
* @param props.options - Optional mask configuration options
|
|
31
|
+
* @returns A ref callback function to attach to the input element
|
|
32
|
+
*/
|
|
33
|
+
export default function useMaskInputAntd(props: UseMaskInputOptions): (
|
|
34
|
+
input: InputRef | null
|
|
35
|
+
) => void {
|
|
36
|
+
const { mask, register, options } = props;
|
|
37
|
+
const ref = useRef<InputRef | null>(null);
|
|
38
|
+
const maskInput = useMemo(
|
|
39
|
+
() => (isServer ? null : createMaskInstance(mask, options)),
|
|
40
|
+
[mask, options],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const refCallback = useCallback((input: InputRef | null) => {
|
|
44
|
+
if (!input) {
|
|
45
|
+
ref.current = null;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ref.current = resolveInputRef(input.input) as unknown as InputRef;
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (isServer || !ref.current) return;
|
|
54
|
+
|
|
55
|
+
if (!isHTMLElement(ref.current)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const inputElement = findInputElement(ref.current);
|
|
60
|
+
|
|
61
|
+
if (maskInput && inputElement && isHTMLElement(inputElement)) {
|
|
62
|
+
maskInput.mask(inputElement);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (register && isHTMLElement(ref.current)) {
|
|
66
|
+
register(ref.current);
|
|
67
|
+
}
|
|
68
|
+
}, [mask, register, options, maskInput]);
|
|
69
|
+
|
|
70
|
+
if (isServer) {
|
|
71
|
+
return (): void => {
|
|
72
|
+
// server doesn't have dom, so just do nothing
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return refCallback;
|
|
77
|
+
}
|
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
|
+
});
|