@timeax/form-palette 0.0.1

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 (109) hide show
  1. package/.scaffold-cache.json +537 -0
  2. package/package.json +42 -0
  3. package/src/.scaffold-cache.json +544 -0
  4. package/src/adapters/axios.ts +117 -0
  5. package/src/adapters/index.ts +91 -0
  6. package/src/adapters/inertia.ts +187 -0
  7. package/src/core/adapter-registry.ts +87 -0
  8. package/src/core/bound/bind-host.ts +14 -0
  9. package/src/core/bound/observe-bound-field.ts +172 -0
  10. package/src/core/bound/wait-for-bound-field.ts +57 -0
  11. package/src/core/context.ts +23 -0
  12. package/src/core/core-provider.tsx +818 -0
  13. package/src/core/core-root.tsx +72 -0
  14. package/src/core/core-shell.tsx +44 -0
  15. package/src/core/errors/error-strip.tsx +71 -0
  16. package/src/core/errors/index.ts +2 -0
  17. package/src/core/errors/map-error-bag.ts +51 -0
  18. package/src/core/errors/map-zod.ts +39 -0
  19. package/src/core/hooks/use-button.ts +220 -0
  20. package/src/core/hooks/use-core-context.ts +20 -0
  21. package/src/core/hooks/use-core-utility.ts +0 -0
  22. package/src/core/hooks/use-core.ts +13 -0
  23. package/src/core/hooks/use-field.ts +497 -0
  24. package/src/core/hooks/use-optional-field.ts +28 -0
  25. package/src/core/index.ts +0 -0
  26. package/src/core/registry/binder-registry.ts +82 -0
  27. package/src/core/registry/field-registry.ts +187 -0
  28. package/src/core/test.tsx +17 -0
  29. package/src/global.d.ts +14 -0
  30. package/src/index.ts +68 -0
  31. package/src/input/index.ts +4 -0
  32. package/src/input/input-field.tsx +854 -0
  33. package/src/input/input-layout-graph.ts +230 -0
  34. package/src/input/input-props.ts +190 -0
  35. package/src/lib/get-global-countries.ts +87 -0
  36. package/src/lib/utils.ts +6 -0
  37. package/src/presets/index.ts +0 -0
  38. package/src/presets/shadcn-preset.ts +0 -0
  39. package/src/presets/shadcn-variants/checkbox.tsx +849 -0
  40. package/src/presets/shadcn-variants/chips.tsx +756 -0
  41. package/src/presets/shadcn-variants/color.tsx +284 -0
  42. package/src/presets/shadcn-variants/custom.tsx +227 -0
  43. package/src/presets/shadcn-variants/date.tsx +796 -0
  44. package/src/presets/shadcn-variants/file.tsx +764 -0
  45. package/src/presets/shadcn-variants/keyvalue.tsx +556 -0
  46. package/src/presets/shadcn-variants/multiselect.tsx +1132 -0
  47. package/src/presets/shadcn-variants/number.tsx +176 -0
  48. package/src/presets/shadcn-variants/password.tsx +737 -0
  49. package/src/presets/shadcn-variants/phone.tsx +628 -0
  50. package/src/presets/shadcn-variants/radio.tsx +578 -0
  51. package/src/presets/shadcn-variants/select.tsx +956 -0
  52. package/src/presets/shadcn-variants/slider.tsx +622 -0
  53. package/src/presets/shadcn-variants/text.tsx +343 -0
  54. package/src/presets/shadcn-variants/textarea.tsx +66 -0
  55. package/src/presets/shadcn-variants/toggle.tsx +218 -0
  56. package/src/presets/shadcn-variants/treeselect.tsx +784 -0
  57. package/src/presets/ui/badge.tsx +46 -0
  58. package/src/presets/ui/button.tsx +60 -0
  59. package/src/presets/ui/calendar.tsx +214 -0
  60. package/src/presets/ui/checkbox.tsx +115 -0
  61. package/src/presets/ui/custom.tsx +0 -0
  62. package/src/presets/ui/dialog.tsx +141 -0
  63. package/src/presets/ui/field.tsx +246 -0
  64. package/src/presets/ui/input-mask.tsx +739 -0
  65. package/src/presets/ui/input-otp.tsx +77 -0
  66. package/src/presets/ui/input.tsx +1011 -0
  67. package/src/presets/ui/label.tsx +22 -0
  68. package/src/presets/ui/number.tsx +1370 -0
  69. package/src/presets/ui/popover.tsx +46 -0
  70. package/src/presets/ui/radio-group.tsx +43 -0
  71. package/src/presets/ui/scroll-area.tsx +56 -0
  72. package/src/presets/ui/select.tsx +190 -0
  73. package/src/presets/ui/separator.tsx +28 -0
  74. package/src/presets/ui/slider.tsx +61 -0
  75. package/src/presets/ui/switch.tsx +32 -0
  76. package/src/presets/ui/textarea.tsx +634 -0
  77. package/src/presets/ui/time-dropdowns.tsx +350 -0
  78. package/src/schema/adapter.ts +217 -0
  79. package/src/schema/core.ts +429 -0
  80. package/src/schema/field-map.ts +0 -0
  81. package/src/schema/field.ts +224 -0
  82. package/src/schema/index.ts +0 -0
  83. package/src/schema/input-field.ts +260 -0
  84. package/src/schema/presets.ts +0 -0
  85. package/src/schema/variant.ts +216 -0
  86. package/src/variants/core/checkbox.tsx +54 -0
  87. package/src/variants/core/chips.tsx +22 -0
  88. package/src/variants/core/color.tsx +16 -0
  89. package/src/variants/core/custom.tsx +18 -0
  90. package/src/variants/core/date.tsx +25 -0
  91. package/src/variants/core/file.tsx +9 -0
  92. package/src/variants/core/keyvalue.tsx +12 -0
  93. package/src/variants/core/multiselect.tsx +28 -0
  94. package/src/variants/core/number.tsx +115 -0
  95. package/src/variants/core/password.tsx +35 -0
  96. package/src/variants/core/phone.tsx +16 -0
  97. package/src/variants/core/radio.tsx +38 -0
  98. package/src/variants/core/select.tsx +15 -0
  99. package/src/variants/core/slider.tsx +55 -0
  100. package/src/variants/core/text.tsx +114 -0
  101. package/src/variants/core/textarea.tsx +22 -0
  102. package/src/variants/core/toggle.tsx +50 -0
  103. package/src/variants/core/treeselect.tsx +11 -0
  104. package/src/variants/helpers/selection-summary.tsx +236 -0
  105. package/src/variants/index.ts +75 -0
  106. package/src/variants/registry.ts +38 -0
  107. package/src/variants/select-shared.ts +0 -0
  108. package/src/variants/shared.ts +126 -0
  109. package/tsconfig.json +14 -0
@@ -0,0 +1,739 @@
1
+ import * as React from "react";
2
+ import { Input } from "@/presets/ui/input";
3
+
4
+ export interface InputMaskChangeEvent {
5
+ originalEvent: React.SyntheticEvent<HTMLInputElement> | Event | undefined;
6
+ value: string;
7
+ stopPropagation(): void;
8
+ preventDefault(): void;
9
+ target: {
10
+ name?: string;
11
+ id?: string;
12
+ value: string;
13
+ };
14
+ }
15
+
16
+ export interface InputMaskCompleteEvent {
17
+ originalEvent: React.SyntheticEvent<HTMLInputElement> | Event;
18
+ value: string;
19
+ }
20
+
21
+ export interface InputMaskRef {
22
+ focus(): void;
23
+ getElement(): HTMLInputElement | null;
24
+ }
25
+
26
+ export interface InputMaskProps
27
+ extends Omit<
28
+ React.InputHTMLAttributes<HTMLInputElement>,
29
+ "onChange" | "value" | "defaultValue"
30
+ > {
31
+ mask: string | null;
32
+ autoClear?: boolean;
33
+ autoFocus?: boolean;
34
+ invalid?: boolean;
35
+ unmask?: boolean;
36
+ slotChar?: string;
37
+ 'data-slot'?: string;
38
+ value?: string | null;
39
+ onChange?: (e: InputMaskChangeEvent) => void;
40
+ onComplete?: (e: InputMaskCompleteEvent) => void;
41
+ }
42
+
43
+ const isEmpty = (val: unknown): boolean =>
44
+ val === null || val === undefined || val === "";
45
+
46
+ const isAndroid = (): boolean =>
47
+ typeof navigator !== "undefined" &&
48
+ /android/i.test(navigator.userAgent || "");
49
+
50
+ const isIOS = (): boolean =>
51
+ typeof navigator !== "undefined" &&
52
+ /(iphone|ipad|ipod)/i.test(navigator.userAgent || "");
53
+
54
+ const isChrome = (): boolean =>
55
+ typeof navigator !== "undefined" &&
56
+ /chrome/i.test(navigator.userAgent || "");
57
+
58
+ const focusEl = (el: HTMLInputElement | null) => {
59
+ if (el && typeof el.focus === "function") {
60
+ el.focus();
61
+ }
62
+ };
63
+
64
+ function useUpdateEffect(effect: React.EffectCallback, deps: React.DependencyList) {
65
+ const mounted = React.useRef(false);
66
+
67
+ React.useEffect(() => {
68
+ if (mounted.current) {
69
+ return effect();
70
+ }
71
+ mounted.current = true;
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, deps);
74
+ }
75
+
76
+ export const InputMask = React.memo(
77
+ React.forwardRef<InputMaskRef, InputMaskProps>((inProps, ref) => {
78
+ // merge defaults with incoming props
79
+ const props = React.useMemo(
80
+ () => ({
81
+ autoClear: true,
82
+ autoFocus: false,
83
+ disabled: false,
84
+ invalid: false,
85
+ readOnly: false,
86
+ required: false,
87
+ slotChar: "_",
88
+ type: "text",
89
+ unmask: false,
90
+ ...inProps,
91
+ }),
92
+ [inProps]
93
+ );
94
+
95
+ const elementRef = React.useRef<HTMLInputElement | null>(null);
96
+ const firstNonMaskPos = React.useRef<number | null>(null);
97
+ const lastRequiredNonMaskPos = React.useRef(0);
98
+ const tests = React.useRef<(RegExp | null)[]>([]);
99
+ const buffer = React.useRef<string[]>([]);
100
+ const len = React.useRef(0);
101
+ const oldVal = React.useRef<string | null>(null);
102
+ const focus = React.useRef(false);
103
+ const focusText = React.useRef<string | null>(null);
104
+ const isValueChecked = React.useRef<boolean | null>(null);
105
+ const partialPosition = React.useRef<number | null>(null);
106
+ const defaultBuffer = React.useRef<string | null>(null);
107
+ const caretTimeoutId = React.useRef<number | null>(null);
108
+ const androidChrome = React.useRef(false);
109
+
110
+ const caret = (first?: number, last?: number) => {
111
+ let range: any;
112
+ let begin: number | null = null;
113
+ let end: number | null = null;
114
+ const inputEl = elementRef.current;
115
+
116
+ if (!inputEl || !inputEl.offsetParent || inputEl !== document.activeElement) {
117
+ return null;
118
+ }
119
+
120
+ if (typeof first === "number") {
121
+ begin = first;
122
+ end = typeof last === "number" ? last : begin;
123
+
124
+ if (inputEl.setSelectionRange) {
125
+ inputEl.setSelectionRange(begin, end);
126
+ } else if ((inputEl as any).createTextRange) {
127
+ range = (inputEl as any).createTextRange();
128
+ range.collapse(true);
129
+ range.moveEnd("character", end);
130
+ range.moveStart("character", begin);
131
+ range.select();
132
+ }
133
+ //@ts-ignore
134
+ } else if (inputEl.setSelectionRange) {
135
+ begin = inputEl.selectionStart ?? 0;
136
+ end = inputEl.selectionEnd ?? begin;
137
+ } else if ((document as any).selection && (document as any).selection.createRange) {
138
+ range = (document as any).selection.createRange();
139
+ begin = 0 - range.duplicate().moveStart("character", -100000);
140
+ end = begin + range.text.length;
141
+ }
142
+
143
+ if (begin === null || end === null) {
144
+ return null;
145
+ }
146
+
147
+ return { begin, end };
148
+ };
149
+
150
+ const getPlaceholder = React.useCallback(
151
+ (i: number): string => {
152
+ const slotChar = props.slotChar ?? "_";
153
+
154
+ if (i < slotChar.length) {
155
+ return slotChar.charAt(i);
156
+ }
157
+
158
+ return slotChar.charAt(0);
159
+ },
160
+ [props.slotChar]
161
+ );
162
+
163
+ const isCompleted = () => {
164
+ const first = firstNonMaskPos.current ?? 0;
165
+
166
+ for (let i = first; i <= lastRequiredNonMaskPos.current; i++) {
167
+ if (tests.current[i] && buffer.current[i] === getPlaceholder(i)) {
168
+ return false;
169
+ }
170
+ }
171
+
172
+ return true;
173
+ };
174
+
175
+ const getValue = () =>
176
+ props.unmask ? getUnmaskedValue() : elementRef.current?.value ?? "";
177
+
178
+ const seekNext = (pos: number) => {
179
+ while (++pos < len.current && !tests.current[pos]) {
180
+ /* loop */
181
+ }
182
+ return pos;
183
+ };
184
+
185
+ const seekPrev = (pos: number) => {
186
+ while (--pos >= 0 && !tests.current[pos]) {
187
+ /* loop */
188
+ }
189
+ return pos;
190
+ };
191
+
192
+ const shiftL = (begin: number, end: number) => {
193
+ if (begin < 0) {
194
+ return;
195
+ }
196
+
197
+ let i: number;
198
+ let j: number;
199
+
200
+ for (i = begin, j = seekNext(end); i < len.current; i++) {
201
+ if (tests.current[i]) {
202
+ if (j < len.current && tests.current[i]!.test(buffer.current[j]!)) {
203
+ buffer.current[i] = buffer.current[j]!;
204
+ buffer.current[j] = getPlaceholder(j);
205
+ } else {
206
+ break;
207
+ }
208
+
209
+ j = seekNext(j);
210
+ }
211
+ }
212
+
213
+ writeBuffer();
214
+ caret(Math.max(firstNonMaskPos.current ?? 0, begin));
215
+ };
216
+
217
+ const shiftR = (pos: number) => {
218
+ let i: number;
219
+ let c: string;
220
+ let j: number;
221
+ let t: string;
222
+
223
+ for (i = pos, c = getPlaceholder(pos); i < len.current; i++) {
224
+ if (tests.current[i]) {
225
+ j = seekNext(i);
226
+ t = buffer.current[i]!;
227
+ buffer.current[i] = c;
228
+ if (j < len.current && tests.current[j]!.test(t)) {
229
+ c = t;
230
+ } else {
231
+ break;
232
+ }
233
+ }
234
+ }
235
+ };
236
+
237
+ const clearBuffer = (start: number, end: number) => {
238
+ for (let i = start; i < end && i < len.current; i++) {
239
+ if (tests.current[i]) {
240
+ buffer.current[i] = getPlaceholder(i);
241
+ }
242
+ }
243
+ };
244
+
245
+ const writeBuffer = () => {
246
+ if (elementRef.current) {
247
+ elementRef.current.value = buffer.current.join("");
248
+ }
249
+ };
250
+
251
+ const checkVal = (allow?: boolean): number => {
252
+ isValueChecked.current = true;
253
+
254
+ const test = elementRef.current?.value ?? "";
255
+ let lastMatch = -1;
256
+ let i: number;
257
+ let c: string;
258
+ let pos: number;
259
+
260
+ for (i = 0, pos = 0; i < len.current; i++) {
261
+ if (tests.current[i]) {
262
+ buffer.current[i] = getPlaceholder(i);
263
+
264
+ while (pos++ < test.length) {
265
+ c = test.charAt(pos - 1);
266
+ if (tests.current[i]!.test(c)) {
267
+ buffer.current[i] = c;
268
+ lastMatch = i;
269
+ break;
270
+ }
271
+ }
272
+
273
+ if (pos > test.length) {
274
+ clearBuffer(i + 1, len.current);
275
+ break;
276
+ }
277
+ } else {
278
+ if (buffer.current[i] === test.charAt(pos)) {
279
+ pos++;
280
+ }
281
+ if (i < (partialPosition.current ?? 0)) {
282
+ lastMatch = i;
283
+ }
284
+ }
285
+ }
286
+
287
+ if (allow) {
288
+ writeBuffer();
289
+ } else if (lastMatch + 1 < (partialPosition.current ?? 0)) {
290
+ if (props.autoClear || buffer.current.join("") === defaultBuffer.current) {
291
+ if (elementRef.current && elementRef.current.value) {
292
+ elementRef.current.value = "";
293
+ }
294
+ clearBuffer(0, len.current);
295
+ } else {
296
+ writeBuffer();
297
+ }
298
+ } else {
299
+ writeBuffer();
300
+ if (elementRef.current) {
301
+ elementRef.current.value = elementRef.current.value.substring(0, lastMatch + 1);
302
+ }
303
+ }
304
+
305
+ return partialPosition.current ? i : (firstNonMaskPos.current ?? 0);
306
+ };
307
+
308
+ const handleAndroidInput = (e: React.SyntheticEvent<HTMLInputElement>) => {
309
+ const inputEl = elementRef.current;
310
+ if (!inputEl) return;
311
+
312
+ const curVal = inputEl.value;
313
+ const pos = caret();
314
+ if (!pos) return;
315
+
316
+ if (oldVal.current && oldVal.current.length > curVal.length) {
317
+ // deletion/backspace
318
+ checkVal(true);
319
+ while (pos.begin > 0 && !tests.current[pos.begin - 1]) {
320
+ pos.begin--;
321
+ }
322
+ if (pos.begin === 0) {
323
+ while (
324
+ pos.begin < (firstNonMaskPos.current ?? 0) &&
325
+ !tests.current[pos.begin]
326
+ ) {
327
+ pos.begin++;
328
+ }
329
+ }
330
+ caret(pos.begin, pos.begin);
331
+ } else {
332
+ checkVal(true);
333
+ while (pos.begin < len.current && !tests.current[pos.begin]) {
334
+ pos.begin++;
335
+ }
336
+ caret(pos.begin, pos.begin);
337
+ }
338
+
339
+ if (props.onComplete && isCompleted()) {
340
+ props.onComplete({
341
+ originalEvent: e,
342
+ value: getValue(),
343
+ });
344
+ }
345
+
346
+ updateModel(e);
347
+ };
348
+
349
+ const onBlur = (e: React.FocusEvent<HTMLInputElement>) => {
350
+ console.log("InputMask onBlur");
351
+ focus.current = false;
352
+ checkVal();
353
+ updateModel(e);
354
+
355
+ if (props.onBlur) {
356
+ props.onBlur(e);
357
+ }
358
+
359
+ if (elementRef.current && elementRef.current.value !== focusText.current) {
360
+ const event = document.createEvent("HTMLEvents");
361
+ event.initEvent("change", true, false);
362
+ elementRef.current.dispatchEvent(event);
363
+ }
364
+ };
365
+
366
+ const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
367
+ if (props.readOnly) {
368
+ return;
369
+ }
370
+
371
+ const k = e.which || e.keyCode;
372
+ let pos: { begin: number; end: number } | null;
373
+ let begin: number;
374
+ let end: number;
375
+
376
+ oldVal.current = elementRef.current?.value ?? null;
377
+
378
+ // backspace, delete, escape
379
+ if (k === 8 || k === 46 || (isIOS() && k === 127)) {
380
+ pos = caret();
381
+ if (!pos) {
382
+ return;
383
+ }
384
+ begin = pos.begin;
385
+ end = pos.end;
386
+
387
+ if (end - begin === 0) {
388
+ begin = k !== 46 ? seekPrev(begin) : (end = seekNext(begin - 1));
389
+ end = k === 46 ? seekNext(end) : end;
390
+ }
391
+
392
+ clearBuffer(begin, end);
393
+ shiftL(begin, end - 1);
394
+ updateModel(e as any);
395
+ e.preventDefault();
396
+ } else if (k === 13) {
397
+ // enter
398
+ onBlur(e as any);
399
+ updateModel(e as any);
400
+ } else if (k === 27) {
401
+ // escape
402
+ if (elementRef.current) {
403
+ elementRef.current.value = focusText.current ?? "";
404
+ }
405
+ caret(0, checkVal());
406
+ updateModel(e as any);
407
+ e.preventDefault();
408
+ }
409
+ };
410
+
411
+ const onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
412
+ if (props.readOnly) {
413
+ return;
414
+ }
415
+
416
+ const pos = caret();
417
+ if (!pos) {
418
+ return;
419
+ }
420
+
421
+ const k = e.which || e.keyCode;
422
+ let p: number;
423
+ let c: string;
424
+ let next: number;
425
+ let completed = false;
426
+
427
+ if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {
428
+ return;
429
+ } else if (k && k !== 13) {
430
+ if (pos.end - pos.begin !== 0) {
431
+ clearBuffer(pos.begin, pos.end);
432
+ shiftL(pos.begin, pos.end - 1);
433
+ }
434
+
435
+ p = seekNext(pos.begin - 1);
436
+ if (p < len.current) {
437
+ c = String.fromCharCode(k);
438
+ if (tests.current[p] && tests.current[p]!.test(c)) {
439
+ shiftR(p);
440
+ buffer.current[p] = c;
441
+ writeBuffer();
442
+
443
+ next = seekNext(p);
444
+ if (isAndroid()) {
445
+ const proxy = () => caret(next);
446
+ setTimeout(proxy, 0);
447
+ } else {
448
+ caret(next);
449
+ }
450
+
451
+ if (pos.begin <= lastRequiredNonMaskPos.current) {
452
+ completed = isCompleted();
453
+ }
454
+ }
455
+ }
456
+
457
+ e.preventDefault();
458
+ }
459
+
460
+ updateModel(e as any);
461
+
462
+ if (props.onComplete && completed) {
463
+ props.onComplete({
464
+ originalEvent: e,
465
+ value: getValue(),
466
+ });
467
+ }
468
+ };
469
+
470
+ const getUnmaskedValue = React.useCallback((): string => {
471
+ const unmaskedBuffer: string[] = [];
472
+
473
+ for (let i = 0; i < buffer.current.length; i++) {
474
+ const c = buffer.current[i]!;
475
+ if (tests.current[i] && c !== getPlaceholder(i)) {
476
+ unmaskedBuffer.push(c);
477
+ }
478
+ }
479
+
480
+ return unmaskedBuffer.join("");
481
+ }, [getPlaceholder]);
482
+
483
+ const updateModel = (e?: React.SyntheticEvent<HTMLInputElement>) => {
484
+ if (!props.onChange) return;
485
+
486
+ const val = props.unmask ? getUnmaskedValue() : e && (e.target as HTMLInputElement)?.value;
487
+
488
+ const normalized = defaultBuffer.current !== val ? (val ?? "") : "";
489
+ console.log("InputMask updateModel:", { val, normalized });
490
+ const payload: InputMaskChangeEvent = {
491
+ originalEvent: e,
492
+ value: normalized,
493
+ stopPropagation: () => {
494
+ (e as any)?.stopPropagation?.();
495
+ },
496
+ preventDefault: () => {
497
+ (e as any)?.preventDefault?.();
498
+ },
499
+ target: {
500
+ name: props.name,
501
+ id: props.id,
502
+ value: normalized,
503
+ },
504
+ };
505
+
506
+ props.onChange(payload);
507
+ };
508
+
509
+ const updateValue = (allow?: boolean): number | undefined => {
510
+ let pos: number | undefined;
511
+
512
+ if (elementRef.current) {
513
+ if (isEmpty(props.value)) {
514
+ elementRef.current.value = "";
515
+ } else {
516
+ elementRef.current.value = props.value ?? "";
517
+ pos = checkVal(allow);
518
+ setTimeout(() => {
519
+ if (elementRef.current) {
520
+ writeBuffer();
521
+ return checkVal(allow);
522
+ }
523
+ }, 10);
524
+ }
525
+
526
+ focusText.current = elementRef.current.value;
527
+ }
528
+
529
+ return pos;
530
+ };
531
+
532
+ const isValueUpdated = React.useCallback(() => {
533
+ const elVal = elementRef.current?.value ?? "";
534
+ return props.unmask
535
+ ? (props.value ?? "") !== getUnmaskedValue()
536
+ : defaultBuffer.current !== elVal && elVal !== (props.value ?? "");
537
+ }, [props.unmask, props.value, getUnmaskedValue]);
538
+
539
+ const init = () => {
540
+ const mask = props.mask;
541
+ if (!mask) return;
542
+
543
+ tests.current = [];
544
+ partialPosition.current = mask.length;
545
+ len.current = mask.length;
546
+ firstNonMaskPos.current = null;
547
+
548
+ const defs: Record<string, string> = {
549
+ "9": "[0-9]",
550
+ a: "[A-Za-z]",
551
+ "*": "[A-Za-z0-9]",
552
+ };
553
+
554
+ androidChrome.current = isChrome() && isAndroid();
555
+ const maskTokens = mask.split("");
556
+
557
+ for (let i = 0; i < maskTokens.length; i++) {
558
+ const c = maskTokens[i]!;
559
+
560
+ if (c === "?") {
561
+ len.current--;
562
+ partialPosition.current = i;
563
+ } else if (defs[c]) {
564
+ tests.current.push(new RegExp(defs[c]!));
565
+ if (firstNonMaskPos.current === null) {
566
+ firstNonMaskPos.current = tests.current.length - 1;
567
+ }
568
+ if (i < (partialPosition.current ?? 0)) {
569
+ lastRequiredNonMaskPos.current = tests.current.length - 1;
570
+ }
571
+ } else {
572
+ tests.current.push(null);
573
+ }
574
+ }
575
+
576
+ buffer.current = [];
577
+
578
+ for (let i = 0; i < maskTokens.length; i++) {
579
+ const c = maskTokens[i]!;
580
+ if (c !== "?") {
581
+ if (defs[c]) {
582
+ buffer.current.push(getPlaceholder(i));
583
+ } else {
584
+ buffer.current.push(c);
585
+ }
586
+ }
587
+ }
588
+
589
+ defaultBuffer.current = buffer.current.join("");
590
+ };
591
+
592
+ const onFocus = (e: React.FocusEvent<HTMLInputElement>) => {
593
+ console.log("InputMask onFocus");
594
+ if (props.readOnly) {
595
+ return;
596
+ }
597
+
598
+ focus.current = true;
599
+ if (caretTimeoutId.current) {
600
+ window.clearTimeout(caretTimeoutId.current);
601
+ }
602
+
603
+ let pos: number;
604
+
605
+ if (elementRef.current) {
606
+ focusText.current = elementRef.current.value;
607
+ } else {
608
+ focusText.current = "";
609
+ }
610
+
611
+ pos = checkVal() || 0;
612
+
613
+ caretTimeoutId.current = window.setTimeout(() => {
614
+ if (elementRef.current !== document.activeElement) {
615
+ return;
616
+ }
617
+
618
+ writeBuffer();
619
+
620
+ if (props.mask && pos === props.mask.replace("?", "").length) {
621
+ caret(0, pos);
622
+ } else {
623
+ caret(pos);
624
+ }
625
+ }, 100);
626
+
627
+ if (props.onFocus) {
628
+ props.onFocus(e);
629
+ }
630
+ };
631
+
632
+ const handleInputChange = (
633
+ e: React.FormEvent<HTMLInputElement>,
634
+ isOnPaste = false
635
+ ) => {
636
+ if (props.readOnly) {
637
+ return;
638
+ }
639
+
640
+ if (!isOnPaste) {
641
+ const pos = checkVal(true);
642
+ caret(pos);
643
+ }
644
+
645
+ updateModel(e as any);
646
+
647
+ if (props.onComplete && isCompleted()) {
648
+ props.onComplete({
649
+ originalEvent: e,
650
+ value: getValue(),
651
+ });
652
+ }
653
+ };
654
+
655
+ const onInput = (event: React.FormEvent<HTMLInputElement>) => {
656
+ androidChrome.current
657
+ ? handleAndroidInput(event as any)
658
+ : handleInputChange(event);
659
+ };
660
+
661
+ React.useImperativeHandle(
662
+ ref,
663
+ () => ({
664
+ focus: () => focusEl(elementRef.current),
665
+ getElement: () => elementRef.current,
666
+ }),
667
+ []
668
+ );
669
+
670
+ React.useEffect(() => {
671
+ if (!elementRef.current) return;
672
+ }, []);
673
+
674
+ React.useEffect(() => {
675
+ init();
676
+ updateValue();
677
+ // eslint-disable-next-line react-hooks/exhaustive-deps
678
+ }, []);
679
+
680
+ useUpdateEffect(() => {
681
+ init();
682
+ const pos = updateValue(true);
683
+ if (typeof pos === "number") {
684
+ caret(pos);
685
+ }
686
+ if (props.unmask) {
687
+ updateModel();
688
+ }
689
+ }, [props.mask, props.unmask]);
690
+
691
+ useUpdateEffect(() => {
692
+ if (isValueUpdated()) {
693
+ updateValue();
694
+ }
695
+ }, [isValueUpdated]);
696
+
697
+ const {
698
+ mask,
699
+ autoClear,
700
+ unmask,
701
+ slotChar,
702
+ onChange,
703
+ onComplete,
704
+ value,
705
+ autoFocus,
706
+ onFocus: i,
707
+ onBlur: j,
708
+ onKeyDown: k,
709
+ onKeyPress: l,
710
+ onInput: m,
711
+ className,
712
+ ...restInputProps
713
+ } = props;
714
+
715
+ return (
716
+ <input
717
+ ref={elementRef}
718
+ autoFocus={autoFocus}
719
+ id={props.id}
720
+ name={props.name}
721
+ style={props.style}
722
+ className={className}
723
+ placeholder={props.placeholder}
724
+ size={props.size}
725
+ maxLength={props.maxLength}
726
+ tabIndex={props.tabIndex}
727
+ onFocus={onFocus}
728
+ onBlur={onBlur}
729
+ onKeyDown={onKeyDown}
730
+ onKeyPress={onKeyPress}
731
+ onInput={onInput}
732
+ onPaste={(e) => handleInputChange(e, true)}
733
+ {...restInputProps}
734
+ />
735
+ );
736
+ })
737
+ );
738
+
739
+ InputMask.displayName = "InputMask";