use-mask-input 3.8.0 → 3.10.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 (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +55 -7
  3. package/dist/antd.cjs +14 -12
  4. package/dist/antd.cjs.map +1 -1
  5. package/dist/antd.d.cts +3 -2
  6. package/dist/antd.d.ts +3 -2
  7. package/dist/antd.js +6 -4
  8. package/dist/antd.js.map +1 -1
  9. package/dist/{chunk-X5SEJVSB.cjs → chunk-DTC7JTZP.cjs} +78 -26
  10. package/dist/{chunk-X5SEJVSB.cjs.map → chunk-DTC7JTZP.cjs.map} +1 -1
  11. package/dist/{chunk-ICLWBMH4.js → chunk-TVCNC3TP.js} +77 -27
  12. package/dist/{chunk-ICLWBMH4.js.map → chunk-TVCNC3TP.js.map} +1 -1
  13. package/dist/{index-S8txl6uK.d.cts → index-D8KkaDbQ.d.cts} +15 -2
  14. package/dist/{index-S8txl6uK.d.ts → index-D8KkaDbQ.d.ts} +15 -2
  15. package/dist/index.cjs +83 -29
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +17 -4
  18. package/dist/index.d.ts +17 -4
  19. package/dist/index.js +71 -19
  20. package/dist/index.js.map +1 -1
  21. package/package.json +7 -7
  22. package/src/antd/useMaskInputAntd.spec.tsx +22 -0
  23. package/src/antd/useMaskInputAntd.ts +11 -7
  24. package/src/api/index.ts +2 -0
  25. package/src/api/useHookFormMask.ts +4 -1
  26. package/src/api/useMaskInput.spec.tsx +18 -0
  27. package/src/api/useMaskInput.ts +11 -5
  28. package/src/api/useTanStackFormMask.ts +24 -0
  29. package/src/api/withHookFormMask.spec.ts +43 -74
  30. package/src/api/withHookFormMask.ts +18 -10
  31. package/src/api/withMask.spec.ts +16 -0
  32. package/src/api/withMask.ts +11 -8
  33. package/src/api/withTanStackFormMask.spec.ts +76 -0
  34. package/src/api/withTanStackFormMask.ts +73 -0
  35. package/src/core/maskEngine.spec.ts +12 -6
  36. package/src/index.tsx +6 -0
  37. package/src/types/index.ts +19 -1
  38. package/src/utils/index.ts +6 -1
  39. package/src/utils/maskHelpers.ts +44 -1
@@ -0,0 +1,73 @@
1
+ import { applyMaskToElement } from '../core';
2
+ import {
3
+ getUnmaskedValue, makeMaskCacheKey, setPrevRef, setUnmaskedValue,
4
+ } from '../utils';
5
+
6
+ import type { RefCallback } from 'react';
7
+
8
+ import type {
9
+ Mask, Options, TanStackFormInputProps, UseTanStackFormMaskReturn,
10
+ } from '../types';
11
+
12
+ type MaskedRefCallback = RefCallback<HTMLElement | null> & {
13
+ currentElement?: HTMLElement | null;
14
+ };
15
+
16
+ const refCache = new WeakMap<
17
+ RefCallback<HTMLElement | null>,
18
+ Map<string, RefCallback<HTMLElement | null>>
19
+ >();
20
+
21
+ /**
22
+ * Enhances TanStack Form-compatible input props with mask support.
23
+ * Works with objects returned by field.getInputProps().
24
+ */
25
+ export default function withTanStackFormMask<T extends TanStackFormInputProps>(
26
+ inputProps: T,
27
+ mask: Mask,
28
+ options?: Options,
29
+ ): UseTanStackFormMaskReturn<T> {
30
+ const { ref } = inputProps;
31
+
32
+ if (!ref) {
33
+ let currentElement: HTMLElement | null = null;
34
+ const result = {
35
+ ...inputProps,
36
+ ref: ((input: HTMLElement | null) => {
37
+ currentElement = input;
38
+ if (input) applyMaskToElement(input, mask, options);
39
+ }) as RefCallback<HTMLElement | null>,
40
+ } as unknown as UseTanStackFormMaskReturn<T>;
41
+ setUnmaskedValue(result, () => getUnmaskedValue(currentElement));
42
+
43
+ setPrevRef(result, ref);
44
+ return result;
45
+ }
46
+
47
+ if (!refCache.has(ref)) {
48
+ refCache.set(ref, new Map());
49
+ }
50
+
51
+ const maskCache = refCache.get(ref);
52
+ const cacheKey = makeMaskCacheKey(inputProps.name ?? '', mask);
53
+
54
+ if (!maskCache?.has(cacheKey)) {
55
+ const maskedRef = ((input: HTMLElement | null) => {
56
+ maskedRef.currentElement = input;
57
+ if (input) applyMaskToElement(input, mask, options);
58
+ ref(input);
59
+ }) as MaskedRefCallback;
60
+
61
+ maskCache?.set(cacheKey, maskedRef);
62
+ }
63
+
64
+ const maskedRef = maskCache?.get(cacheKey) as MaskedRefCallback | undefined;
65
+ const result = {
66
+ ...inputProps,
67
+ ref: maskedRef,
68
+ } as unknown as UseTanStackFormMaskReturn<T>;
69
+ setUnmaskedValue(result, () => getUnmaskedValue(maskedRef?.currentElement ?? null));
70
+
71
+ setPrevRef(result, ref);
72
+ return result;
73
+ }
@@ -5,6 +5,12 @@ import {
5
5
 
6
6
  import { applyMaskToElement, createMaskInstance } from './maskEngine';
7
7
 
8
+ type MaskInstance = ReturnType<typeof createMaskInstance>;
9
+
10
+ function stubMaskInstance(maskFn: ReturnType<typeof vi.fn>): MaskInstance {
11
+ return { mask: maskFn } as unknown as MaskInstance;
12
+ }
13
+
8
14
  vi.mock('inputmask', () => ({
9
15
  default: vi.fn((options) => ({
10
16
  mask: vi.fn(),
@@ -47,7 +53,7 @@ describe('maskEngine', () => {
47
53
  it('applies mask to input element', () => {
48
54
  const input = document.createElement('input');
49
55
  const maskFn = vi.fn();
50
- vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
56
+ vi.mocked(inputmask).mockImplementation(() => stubMaskInstance(maskFn));
51
57
 
52
58
  applyMaskToElement(input, '999-999');
53
59
 
@@ -57,7 +63,7 @@ describe('maskEngine', () => {
57
63
  it('applies mask to textarea element', () => {
58
64
  const textarea = document.createElement('textarea');
59
65
  const maskFn = vi.fn();
60
- vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
66
+ vi.mocked(inputmask).mockImplementation(() => stubMaskInstance(maskFn));
61
67
 
62
68
  applyMaskToElement(textarea, '999-999');
63
69
 
@@ -69,7 +75,7 @@ describe('maskEngine', () => {
69
75
  const input = document.createElement('input');
70
76
  wrapper.appendChild(input);
71
77
  const maskFn = vi.fn();
72
- vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
78
+ vi.mocked(inputmask).mockImplementation(() => stubMaskInstance(maskFn));
73
79
 
74
80
  applyMaskToElement(wrapper, '999-999');
75
81
 
@@ -79,7 +85,7 @@ describe('maskEngine', () => {
79
85
  it('applies mask to wrapper if no input found inside', () => {
80
86
  const wrapper = document.createElement('div');
81
87
  const maskFn = vi.fn();
82
- vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
88
+ vi.mocked(inputmask).mockImplementation(() => stubMaskInstance(maskFn));
83
89
 
84
90
  applyMaskToElement(wrapper, '999-999');
85
91
 
@@ -88,7 +94,7 @@ describe('maskEngine', () => {
88
94
 
89
95
  it('does nothing if element is null', () => {
90
96
  const maskFn = vi.fn();
91
- vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
97
+ vi.mocked(inputmask).mockImplementation(() => stubMaskInstance(maskFn));
92
98
 
93
99
  applyMaskToElement(null as unknown as HTMLElement, '999-999');
94
100
 
@@ -98,7 +104,7 @@ describe('maskEngine', () => {
98
104
  it('applies mask with custom options', () => {
99
105
  const input = document.createElement('input');
100
106
  const maskFn = vi.fn();
101
- vi.mocked(inputmask).mockReturnValue({ mask: maskFn } as unknown as Inputmask.Instance);
107
+ vi.mocked(inputmask).mockImplementation(() => stubMaskInstance(maskFn));
102
108
 
103
109
  applyMaskToElement(input, '999-999', { placeholder: '_' });
104
110
 
package/src/index.tsx CHANGED
@@ -1,14 +1,20 @@
1
1
  export {
2
2
  useHookFormMask,
3
3
  useMaskInput,
4
+ useTanStackFormMask,
4
5
  withHookFormMask,
5
6
  withMask,
7
+ withTanStackFormMask,
6
8
  } from './api';
7
9
 
8
10
  export type {
9
11
  Input,
10
12
  Mask,
11
13
  Options,
14
+ UnmaskedValueApi,
15
+ UseMaskInputReturn,
16
+ TanStackFormInputProps,
17
+ UseTanStackFormMaskReturn,
12
18
  UseFormRegister,
13
19
  UseFormRegisterReturn,
14
20
  } from './types';
@@ -30,9 +30,27 @@ export type Mask = 'datetime'
30
30
  export type Options = MaskOptions;
31
31
  export type Input = HTMLInputElement | HTMLTextAreaElement | HTMLElement;
32
32
 
33
+ export interface UnmaskedValueApi {
34
+ unmaskedValue: () => string;
35
+ }
36
+
37
+ export type UseMaskInputReturn = RefCallback<HTMLElement | null> & UnmaskedValueApi;
38
+
33
39
  export interface UseHookFormMaskReturn<
34
40
  T extends FieldValues,
35
- > extends UseFormRegisterReturn<Path<T>> {
41
+ > extends UseFormRegisterReturn<Path<T>>, UnmaskedValueApi {
36
42
  ref: RefCallback<HTMLElement | null>;
37
43
  prevRef: RefCallback<HTMLElement | null>;
38
44
  }
45
+
46
+ export interface TanStackFormInputProps {
47
+ name?: string;
48
+ ref?: RefCallback<HTMLElement | null>;
49
+ [key: string]: unknown;
50
+ }
51
+
52
+ export type UseTanStackFormMaskReturn<T extends TanStackFormInputProps = TanStackFormInputProps> =
53
+ Omit<T, 'ref'> & {
54
+ ref: RefCallback<HTMLElement | null>;
55
+ prevRef: RefCallback<HTMLElement | null> | undefined;
56
+ } & UnmaskedValueApi;
@@ -1,4 +1,9 @@
1
1
  export { default as flow } from './flow';
2
2
  export { default as isServer } from './isServer';
3
3
  export { default as moduleInterop } from './moduleInterop';
4
- export { makeMaskCacheKey, setPrevRef } from './maskHelpers';
4
+ export {
5
+ getUnmaskedValue,
6
+ makeMaskCacheKey,
7
+ setPrevRef,
8
+ setUnmaskedValue,
9
+ } from './maskHelpers';
@@ -1,4 +1,6 @@
1
- import type { Mask } from '../types';
1
+ import { findInputElement, resolveInputRef } from '../core/elementResolver';
2
+
3
+ import type { Input, Mask, UnmaskedValueApi } from '../types';
2
4
 
3
5
  /**
4
6
  * Builds a stable string key from a field name and mask, used to cache ref
@@ -20,3 +22,44 @@ export function setPrevRef(result: object, ref: unknown): void {
20
22
  configurable: true,
21
23
  });
22
24
  }
25
+
26
+ function resolveUnmaskedInput(input: Input | null): HTMLInputElement | HTMLTextAreaElement | null {
27
+ const resolved = resolveInputRef(input);
28
+ if (!resolved) return null;
29
+
30
+ const inputElement = findInputElement(resolved);
31
+ if (inputElement) {
32
+ return inputElement as HTMLInputElement | HTMLTextAreaElement;
33
+ }
34
+
35
+ return resolved as HTMLInputElement | HTMLTextAreaElement;
36
+ }
37
+
38
+ export function getUnmaskedValue(input: Input | null): string {
39
+ const element = resolveUnmaskedInput(input);
40
+ if (!element) return '';
41
+
42
+ const inputmask = (element as HTMLInputElement).inputmask as
43
+ | { unmaskedvalue?: () => string }
44
+ | undefined;
45
+
46
+ if (inputmask && typeof inputmask.unmaskedvalue === 'function') {
47
+ return inputmask.unmaskedvalue();
48
+ }
49
+
50
+ return 'value' in element ? element.value : '';
51
+ }
52
+
53
+ export function setUnmaskedValue<T extends object>(
54
+ result: T,
55
+ getter: () => string,
56
+ ): T & UnmaskedValueApi {
57
+ Object.defineProperty(result, 'unmaskedValue', {
58
+ value: getter,
59
+ enumerable: false,
60
+ writable: true,
61
+ configurable: true,
62
+ });
63
+
64
+ return result as T & UnmaskedValueApi;
65
+ }