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
@@ -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,73 @@
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 { Input, Mask, Options } from '../types';
14
+
15
+ interface UseMaskInputOptions {
16
+ mask: Mask;
17
+ register?: (element: HTMLElement) => void;
18
+ options?: Options;
19
+ }
20
+
21
+ /**
22
+ * React hook for applying input masks to form elements.
23
+ * Works with Ant Design and other wrapped components too.
24
+ *
25
+ * @param props - Configuration object
26
+ * @param props.mask - The mask pattern to apply
27
+ * @param props.register - Optional callback that receives the element
28
+ * @param props.options - Optional mask configuration options
29
+ * @returns A ref callback function to attach to the input element
30
+ */
31
+ export default function useMaskInput(props: UseMaskInputOptions): ((input: Input | null) => void) {
32
+ const { mask, register, options } = props;
33
+ const ref = useRef<HTMLInputElement | null>(null);
34
+ const maskInput = useMemo(
35
+ () => (isServer ? null : createMaskInstance(mask, options)),
36
+ [mask, options],
37
+ );
38
+
39
+ const refCallback = useCallback((input: Input | null): void => {
40
+ if (!input) {
41
+ ref.current = null;
42
+ return;
43
+ }
44
+
45
+ ref.current = resolveInputRef(input);
46
+ }, []);
47
+
48
+ useEffect(() => {
49
+ if (isServer || !ref.current) return;
50
+
51
+ if (!isHTMLElement(ref.current)) {
52
+ return;
53
+ }
54
+
55
+ const inputElement = findInputElement(ref.current);
56
+
57
+ if (maskInput && inputElement && isHTMLElement(inputElement)) {
58
+ maskInput.mask(inputElement);
59
+ }
60
+
61
+ if (register && isHTMLElement(ref.current)) {
62
+ register(ref.current);
63
+ }
64
+ }, [mask, register, options, maskInput]);
65
+
66
+ if (isServer) {
67
+ return (): void => {
68
+ // server doesn't have dom, so just do nothing
69
+ };
70
+ }
71
+
72
+ return refCallback;
73
+ }
@@ -0,0 +1,155 @@
1
+ import inputmask from 'inputmask';
2
+ import {
3
+ beforeEach,
4
+ describe, expect, it, vi,
5
+ } from 'vitest';
6
+
7
+ import withHookFormMask from './withHookFormMask';
8
+
9
+ import type { RefCallback } from 'react';
10
+ import type { FieldValues, UseFormRegisterReturn } from 'react-hook-form';
11
+
12
+ import type { UseHookFormMaskReturn } from '../types';
13
+
14
+ vi.mock('inputmask', () => ({
15
+ default: vi.fn((options) => ({
16
+ mask: vi.fn(),
17
+ options,
18
+ })),
19
+ }));
20
+
21
+ describe('withHookFormMask', () => {
22
+ beforeEach(() => {
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ it('returns register object with masked ref', () => {
27
+ const originalRef = vi.fn();
28
+ const register: UseHookFormMaskReturn<FieldValues> = {
29
+ prevRef: vi.fn(),
30
+ ref: originalRef,
31
+ onChange: vi.fn(),
32
+ onBlur: vi.fn(),
33
+ name: 'phone',
34
+ };
35
+ const maskFn = vi.fn();
36
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
37
+
38
+ const result = withHookFormMask(register, '999-999');
39
+
40
+ expect(result.ref).toBeDefined();
41
+ expect(typeof result.ref).toBe('function');
42
+ expect(result.onChange).toBe(register.onChange);
43
+ expect(result.onBlur).toBe(register.onBlur);
44
+ expect(result.name).toBe(register.name);
45
+ });
46
+
47
+ it('applies mask when ref is called', () => {
48
+ const input = document.createElement('input');
49
+ const originalRef = vi.fn();
50
+ const register: UseHookFormMaskReturn<FieldValues> = {
51
+ prevRef: vi.fn(),
52
+ ref: originalRef,
53
+ onChange: vi.fn(),
54
+ onBlur: vi.fn(),
55
+ name: 'phone',
56
+ };
57
+ const maskFn = vi.fn();
58
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
59
+
60
+ const result = withHookFormMask(register, '999-999');
61
+ result.ref?.(input);
62
+
63
+ expect(maskFn).toHaveBeenCalled();
64
+ });
65
+
66
+ it('calls original ref after applying mask', () => {
67
+ const input = document.createElement('input');
68
+ const originalRef = vi.fn();
69
+ const register: UseHookFormMaskReturn<FieldValues> = {
70
+ ref: originalRef,
71
+ prevRef: vi.fn(),
72
+ onChange: vi.fn(),
73
+ onBlur: vi.fn(),
74
+ name: 'phone',
75
+ };
76
+ const maskFn = vi.fn();
77
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
78
+
79
+ const result = withHookFormMask(register, '999-999');
80
+ result.ref?.(input);
81
+
82
+ expect(originalRef).toHaveBeenCalled();
83
+ });
84
+
85
+ it('works with alias masks', () => {
86
+ const input = document.createElement('input');
87
+ const originalRef = vi.fn();
88
+ const register: UseHookFormMaskReturn<FieldValues> = {
89
+ ref: originalRef,
90
+ prevRef: vi.fn(),
91
+ onChange: vi.fn(),
92
+ onBlur: vi.fn(),
93
+ name: 'cpf',
94
+ };
95
+ const maskFn = vi.fn();
96
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
97
+
98
+ const result = withHookFormMask(register, 'cpf');
99
+ result.ref?.(input);
100
+
101
+ expect(maskFn).toHaveBeenCalled();
102
+ });
103
+
104
+ it('works with custom options', () => {
105
+ const input = document.createElement('input');
106
+ const originalRef = vi.fn();
107
+ const register: UseHookFormMaskReturn<FieldValues> = {
108
+ prevRef: vi.fn(),
109
+ ref: originalRef,
110
+ onChange: vi.fn(),
111
+ onBlur: vi.fn(),
112
+ name: 'phone',
113
+ };
114
+ const maskFn = vi.fn();
115
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
116
+
117
+ const result = withHookFormMask(register, '999-999', { placeholder: '_' });
118
+ result.ref?.(input);
119
+
120
+ expect(maskFn).toHaveBeenCalled();
121
+ });
122
+
123
+ it('handles null ref gracefully', () => {
124
+ const register: UseHookFormMaskReturn<FieldValues> = {
125
+ prevRef: null as unknown as RefCallback<HTMLElement | null>,
126
+ ref: null as unknown as RefCallback<HTMLElement | null>,
127
+ onChange: vi.fn(),
128
+ onBlur: vi.fn(),
129
+ name: 'phone',
130
+ };
131
+
132
+ const result = withHookFormMask(register, '999-999');
133
+ expect(result.ref).toBeNull();
134
+ expect(result.onChange).toBe(register.onChange);
135
+ expect(result.onBlur).toBe(register.onBlur);
136
+ });
137
+
138
+ it('handles null input in ref callback', () => {
139
+ const originalRef = vi.fn();
140
+ const register: UseHookFormMaskReturn<FieldValues> = {
141
+ prevRef: vi.fn(),
142
+ ref: originalRef,
143
+ onChange: vi.fn(),
144
+ onBlur: vi.fn(),
145
+ name: 'phone',
146
+ };
147
+ const maskFn = vi.fn();
148
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
149
+
150
+ const result = withHookFormMask(register, '999-999');
151
+ result.ref?.(null as unknown as HTMLElement);
152
+
153
+ expect(maskFn).not.toHaveBeenCalled();
154
+ });
155
+ });
@@ -0,0 +1,54 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { applyMaskToElement } from '../core';
3
+ import { flow } from '../utils';
4
+
5
+ import type { RefCallback } from 'react';
6
+ import type { FieldValues } from 'react-hook-form';
7
+
8
+ import type {
9
+ Mask, Options, UseFormRegisterReturn, UseHookFormMaskReturn,
10
+ } from '../types';
11
+
12
+ /**
13
+ * Enhances a React Hook Form register return object with mask support.
14
+ * Takes an already registered field and adds mask to it.
15
+ * Useful when you registered the field before.
16
+ *
17
+ * @param register - The register return object from React Hook Form
18
+ * @param mask - The mask pattern to apply
19
+ * @param options - Optional mask configuration options
20
+ * @returns A new register return object with mask applied
21
+ */
22
+ export default function withHookFormMask(
23
+ register: UseFormRegisterReturn,
24
+ mask: Mask,
25
+ options?: Options,
26
+ ): UseHookFormMaskReturn<FieldValues> {
27
+ const { ref } = register as UseHookFormMaskReturn<FieldValues>;
28
+
29
+ const applyMaskToRef = (_ref: HTMLElement | null) => {
30
+ if (_ref) applyMaskToElement(_ref, mask, options);
31
+ return _ref;
32
+ };
33
+
34
+ const refWithMask = ref === null
35
+ ? null
36
+ : ref
37
+ ? flow(applyMaskToRef, ref)
38
+ : null;
39
+
40
+ const result = {
41
+ ...register,
42
+ ref: refWithMask as RefCallback<HTMLElement | null>,
43
+ } as UseHookFormMaskReturn<FieldValues>;
44
+
45
+ // change prevRef to non-enumerable
46
+ Object.defineProperty(result, 'prevRef', {
47
+ value: ref,
48
+ enumerable: false,
49
+ writable: true,
50
+ configurable: true,
51
+ });
52
+
53
+ return result;
54
+ }
@@ -0,0 +1,93 @@
1
+ import inputmask from 'inputmask';
2
+ import {
3
+ beforeEach, describe, expect, it, vi,
4
+ } from 'vitest';
5
+
6
+ import withMask from './withMask';
7
+
8
+ vi.mock('inputmask', () => ({
9
+ default: vi.fn((options) => ({
10
+ mask: vi.fn(),
11
+ options,
12
+ })),
13
+ }));
14
+
15
+ vi.mock('../utils/isServer', () => ({
16
+ default: false,
17
+ }));
18
+
19
+ describe('withMask', () => {
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ });
23
+
24
+ it('returns a function', () => {
25
+ const result = withMask('999-999');
26
+ expect(typeof result).toBe('function');
27
+ });
28
+
29
+ it('applies mask to input element', () => {
30
+ const input = document.createElement('input');
31
+ const maskFn = vi.fn();
32
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
33
+
34
+ const refCallback = withMask('999-999');
35
+ refCallback(input);
36
+
37
+ expect(maskFn).toHaveBeenCalledWith(input);
38
+ });
39
+
40
+ it('does nothing if input is null', () => {
41
+ const maskFn = vi.fn();
42
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
43
+
44
+ const refCallback = withMask('999-999');
45
+ refCallback(null);
46
+
47
+ expect(maskFn).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it('does nothing if mask is null', () => {
51
+ const input = document.createElement('input');
52
+ const maskFn = vi.fn();
53
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
54
+
55
+ const refCallback = withMask(null);
56
+ refCallback(input);
57
+
58
+ expect(maskFn).not.toHaveBeenCalled();
59
+ });
60
+
61
+ it('applies mask with custom options', () => {
62
+ const input = document.createElement('input');
63
+ const maskFn = vi.fn();
64
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
65
+
66
+ const refCallback = withMask('999-999', { placeholder: '_' });
67
+ refCallback(input);
68
+
69
+ expect(maskFn).toHaveBeenCalledWith(input);
70
+ });
71
+
72
+ it('works with alias masks', () => {
73
+ const input = document.createElement('input');
74
+ const maskFn = vi.fn();
75
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
76
+
77
+ const refCallback = withMask('cpf');
78
+ refCallback(input);
79
+
80
+ expect(maskFn).toHaveBeenCalledWith(input);
81
+ });
82
+
83
+ it('works with array masks', () => {
84
+ const input = document.createElement('input');
85
+ const maskFn = vi.fn();
86
+ vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
87
+
88
+ const refCallback = withMask(['999-999', '9999-9999']);
89
+ refCallback(input);
90
+
91
+ expect(maskFn).toHaveBeenCalledWith(input);
92
+ });
93
+ });
@@ -0,0 +1,25 @@
1
+ /* eslint-disable import-x/no-extraneous-dependencies */
2
+ import inputmask from 'inputmask';
3
+
4
+ import { getMaskOptions } from '../core/maskConfig';
5
+ import isServer from '../utils/isServer';
6
+ import interopDefaultSync from '../utils/moduleInterop';
7
+
8
+ import type { Input, Mask, Options } from '../types';
9
+
10
+ /**
11
+ * Higher-order function that creates a ref callback for applying input masks.
12
+ * Simple function to apply mask via ref. No hooks, no drama.
13
+ *
14
+ * @param mask - The mask pattern to apply
15
+ * @param options - Optional mask configuration options
16
+ * @returns A ref callback function that applies the mask
17
+ */
18
+ export default function withMask(mask: Mask, options?: Options) {
19
+ return (input: Input | null): void => {
20
+ if (isServer || mask === null || !input) return;
21
+
22
+ const maskInput = interopDefaultSync(inputmask)(getMaskOptions(mask, options));
23
+ maskInput.mask(input as HTMLElement);
24
+ };
25
+ }