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.
Files changed (58) hide show
  1. package/CHANGELOG.md +53 -76
  2. package/README.md +2 -251
  3. package/dist/antd/index.cjs +67 -0
  4. package/dist/antd/index.cjs.map +1 -0
  5. package/dist/antd/index.d.cts +37 -0
  6. package/dist/antd/index.d.ts +37 -0
  7. package/dist/antd/index.js +64 -0
  8. package/dist/antd/index.js.map +1 -0
  9. package/dist/chunk-4Y2DTPBL.cjs +3841 -0
  10. package/dist/chunk-4Y2DTPBL.cjs.map +1 -0
  11. package/dist/chunk-JGOZSJMW.js +3829 -0
  12. package/dist/chunk-JGOZSJMW.js.map +1 -0
  13. package/dist/index-F3rlTTTe.d.cts +583 -0
  14. package/dist/index-F3rlTTTe.d.ts +583 -0
  15. package/dist/index.cjs +72 -3870
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +48 -585
  18. package/dist/index.d.ts +48 -585
  19. package/dist/index.js +72 -3870
  20. package/dist/index.js.map +1 -1
  21. package/package.json +27 -21
  22. package/src/antd/index.ts +9 -0
  23. package/src/antd/useHookFormMaskAntd.spec.ts +142 -0
  24. package/src/antd/useHookFormMaskAntd.ts +57 -0
  25. package/src/antd/useMaskInputAntd-server.spec.tsx +36 -0
  26. package/src/antd/useMaskInputAntd.spec.tsx +108 -0
  27. package/src/antd/useMaskInputAntd.ts +77 -0
  28. package/src/api/index.ts +4 -0
  29. package/src/api/useHookFormMask.spec.ts +146 -0
  30. package/src/api/useHookFormMask.ts +56 -0
  31. package/src/api/useMaskInput-server.spec.tsx +30 -0
  32. package/src/api/useMaskInput.spec.tsx +220 -0
  33. package/src/api/useMaskInput.ts +73 -0
  34. package/src/api/withHookFormMask.spec.ts +155 -0
  35. package/src/api/withHookFormMask.ts +54 -0
  36. package/src/api/withMask.spec.ts +93 -0
  37. package/src/api/withMask.ts +25 -0
  38. package/src/core/elementResolver.spec.ts +175 -0
  39. package/src/core/elementResolver.ts +84 -0
  40. package/src/core/index.ts +3 -0
  41. package/src/core/maskConfig.spec.ts +183 -0
  42. package/src/core/maskConfig.ts +56 -0
  43. package/src/core/maskEngine.spec.ts +108 -0
  44. package/src/core/maskEngine.ts +47 -0
  45. package/src/index.tsx +12 -5
  46. package/src/{types.ts → types/index.ts} +13 -0
  47. package/src/utils/flow.spec.ts +27 -30
  48. package/src/utils/flow.ts +2 -2
  49. package/src/utils/index.ts +1 -1
  50. package/src/utils/isServer.spec.ts +15 -0
  51. package/src/utils/moduleInterop.spec.ts +37 -0
  52. package/src/useHookFormMask.ts +0 -47
  53. package/src/useMaskInput.ts +0 -41
  54. package/src/utils/getMaskOptions.spec.ts +0 -126
  55. package/src/utils/getMaskOptions.ts +0 -94
  56. package/src/withHookFormMask.ts +0 -34
  57. package/src/withMask.ts +0 -18
  58. /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.6.0",
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": "https://github.com/eduardoborges/use-mask-input",
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
- "simple-git-hooks": {
69
- "pre-commit": "npm run lint && npm run test && npm run build"
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,9 @@
1
+ import useHookFormMaskAntd from './useHookFormMaskAntd';
2
+ import useMaskInputAntd from './useMaskInputAntd';
3
+
4
+ export {
5
+ useHookFormMaskAntd,
6
+ useMaskInputAntd,
7
+ };
8
+
9
+ export type { UseHookFormMaskAntdReturn } from './useHookFormMaskAntd';
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export { default as useMaskInput } from './useMaskInput';
2
+ export { default as useHookFormMask } from './useHookFormMask';
3
+ export { default as withMask } from './withMask';
4
+ export { default as withHookFormMask } from './withHookFormMask';
@@ -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
+ });