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.
- package/CHANGELOG.md +12 -0
- package/README.md +55 -7
- package/dist/antd.cjs +14 -12
- package/dist/antd.cjs.map +1 -1
- package/dist/antd.d.cts +3 -2
- package/dist/antd.d.ts +3 -2
- package/dist/antd.js +6 -4
- package/dist/antd.js.map +1 -1
- package/dist/{chunk-X5SEJVSB.cjs → chunk-DTC7JTZP.cjs} +78 -26
- package/dist/{chunk-X5SEJVSB.cjs.map → chunk-DTC7JTZP.cjs.map} +1 -1
- package/dist/{chunk-ICLWBMH4.js → chunk-TVCNC3TP.js} +77 -27
- package/dist/{chunk-ICLWBMH4.js.map → chunk-TVCNC3TP.js.map} +1 -1
- package/dist/{index-S8txl6uK.d.cts → index-D8KkaDbQ.d.cts} +15 -2
- package/dist/{index-S8txl6uK.d.ts → index-D8KkaDbQ.d.ts} +15 -2
- package/dist/index.cjs +83 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +71 -19
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/antd/useMaskInputAntd.spec.tsx +22 -0
- package/src/antd/useMaskInputAntd.ts +11 -7
- package/src/api/index.ts +2 -0
- package/src/api/useHookFormMask.ts +4 -1
- package/src/api/useMaskInput.spec.tsx +18 -0
- package/src/api/useMaskInput.ts +11 -5
- package/src/api/useTanStackFormMask.ts +24 -0
- package/src/api/withHookFormMask.spec.ts +43 -74
- package/src/api/withHookFormMask.ts +18 -10
- package/src/api/withMask.spec.ts +16 -0
- package/src/api/withMask.ts +11 -8
- package/src/api/withTanStackFormMask.spec.ts +76 -0
- package/src/api/withTanStackFormMask.ts +73 -0
- package/src/core/maskEngine.spec.ts +12 -6
- package/src/index.tsx +6 -0
- package/src/types/index.ts +19 -1
- package/src/utils/index.ts +6 -1
- 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).
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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';
|
package/src/types/index.ts
CHANGED
|
@@ -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;
|
package/src/utils/index.ts
CHANGED
|
@@ -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 {
|
|
4
|
+
export {
|
|
5
|
+
getUnmaskedValue,
|
|
6
|
+
makeMaskCacheKey,
|
|
7
|
+
setPrevRef,
|
|
8
|
+
setUnmaskedValue,
|
|
9
|
+
} from './maskHelpers';
|
package/src/utils/maskHelpers.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
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
|
+
}
|