mobx-formly 0.0.1 → 0.0.3
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/dist/form.d.ts +24 -4
- package/dist/form.js +148 -36
- package/dist/types.d.ts +10 -2
- package/dist/utils.js +3 -2
- package/package.json +13 -14
- package/dist/src/form.d.ts +0 -48
- package/dist/src/form.js +0 -261
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -1
- package/dist/src/types.d.ts +0 -88
- package/dist/src/types.js +0 -1
- package/dist/src/utils.d.ts +0 -6
- package/dist/src/utils.js +0 -59
package/dist/form.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Ref } from 'yummies/mobx';
|
|
2
|
-
import type { FieldError, FieldPath, FieldPathValue,
|
|
2
|
+
import type { FieldError, FieldErrors, FieldPath, FieldPathValue, FieldStateTree, FieldValues, FormOptions, RegisterOptions, RegisterReturn, ResetOptions, SetValueConfig, SubmitHandlers } from './types.js';
|
|
3
3
|
export declare class Form<T extends FieldValues = FieldValues> {
|
|
4
4
|
/**
|
|
5
5
|
* Current form values.
|
|
@@ -8,11 +8,20 @@ export declare class Form<T extends FieldValues = FieldValues> {
|
|
|
8
8
|
*/
|
|
9
9
|
values: T;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Validation errors nested by field path.
|
|
12
12
|
*
|
|
13
13
|
* [**Documentation**](https://js2me.github.io/mobx-formly/api/form.html#errors)
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
private readonly errorsByPath;
|
|
16
|
+
private readonly errorPathCounts;
|
|
17
|
+
private readonly errorChildren;
|
|
18
|
+
private readonly errorProxyCache;
|
|
19
|
+
private readonly fieldStatesByPath;
|
|
20
|
+
private readonly fieldStatePathCounts;
|
|
21
|
+
private readonly fieldStateChildren;
|
|
22
|
+
private readonly fieldStateProxyCache;
|
|
23
|
+
/** Validation errors nested by field path. */
|
|
24
|
+
get errors(): FieldErrors<T>;
|
|
16
25
|
/**
|
|
17
26
|
* Field paths whose values differ from their defaults.
|
|
18
27
|
*
|
|
@@ -36,7 +45,8 @@ export declare class Form<T extends FieldValues = FieldValues> {
|
|
|
36
45
|
*
|
|
37
46
|
* [**Documentation**](https://js2me.github.io/mobx-formly/api/form.html#fieldstate)
|
|
38
47
|
*/
|
|
39
|
-
|
|
48
|
+
/** Observable state for each registered field, nested by field path. */
|
|
49
|
+
get fieldState(): FieldStateTree<T>;
|
|
40
50
|
/**
|
|
41
51
|
* Whether a submission is currently running.
|
|
42
52
|
*
|
|
@@ -190,4 +200,14 @@ export declare class Form<T extends FieldValues = FieldValues> {
|
|
|
190
200
|
private ensureFieldState;
|
|
191
201
|
private applyFieldState;
|
|
192
202
|
private applyError;
|
|
203
|
+
private getError;
|
|
204
|
+
private hasError;
|
|
205
|
+
private errorPaths;
|
|
206
|
+
private deleteFieldState;
|
|
207
|
+
private fieldStates;
|
|
208
|
+
private setPathStore;
|
|
209
|
+
private deletePathStore;
|
|
210
|
+
private clearPathStore;
|
|
211
|
+
private addPathToIndex;
|
|
212
|
+
private createPathProxy;
|
|
193
213
|
}
|
package/dist/form.js
CHANGED
|
@@ -9,11 +9,22 @@ export class Form {
|
|
|
9
9
|
*/
|
|
10
10
|
values;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Validation errors nested by field path.
|
|
13
13
|
*
|
|
14
14
|
* [**Documentation**](https://js2me.github.io/mobx-formly/api/form.html#errors)
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
errorsByPath = observable.map();
|
|
17
|
+
errorPathCounts = observable.map();
|
|
18
|
+
errorChildren = new Map();
|
|
19
|
+
errorProxyCache = new Map();
|
|
20
|
+
fieldStatesByPath = observable.map();
|
|
21
|
+
fieldStatePathCounts = observable.map();
|
|
22
|
+
fieldStateChildren = new Map();
|
|
23
|
+
fieldStateProxyCache = new Map();
|
|
24
|
+
/** Validation errors nested by field path. */
|
|
25
|
+
get errors() {
|
|
26
|
+
return this.createPathProxy('', this.errorsByPath, this.errorPathCounts, this.errorChildren, this.errorProxyCache);
|
|
27
|
+
}
|
|
17
28
|
/**
|
|
18
29
|
* Field paths whose values differ from their defaults.
|
|
19
30
|
*
|
|
@@ -37,7 +48,10 @@ export class Form {
|
|
|
37
48
|
*
|
|
38
49
|
* [**Documentation**](https://js2me.github.io/mobx-formly/api/form.html#fieldstate)
|
|
39
50
|
*/
|
|
40
|
-
|
|
51
|
+
/** Observable state for each registered field, nested by field path. */
|
|
52
|
+
get fieldState() {
|
|
53
|
+
return this.createPathProxy('', this.fieldStatesByPath, this.fieldStatePathCounts, this.fieldStateChildren, this.fieldStateProxyCache);
|
|
54
|
+
}
|
|
41
55
|
/**
|
|
42
56
|
* Whether a submission is currently running.
|
|
43
57
|
*
|
|
@@ -91,7 +105,6 @@ export class Form {
|
|
|
91
105
|
this.values = clone((options.values ?? options.defaultValues ?? {}));
|
|
92
106
|
makeObservable(this, {
|
|
93
107
|
values: observable.deep,
|
|
94
|
-
errors: observable.shallow,
|
|
95
108
|
dirtyFields: observable.shallow,
|
|
96
109
|
touchedFields: observable.shallow,
|
|
97
110
|
validatingFields: observable.shallow,
|
|
@@ -100,7 +113,6 @@ export class Form {
|
|
|
100
113
|
isSubmitSuccessful: observable,
|
|
101
114
|
submitCount: observable,
|
|
102
115
|
disabled: computed,
|
|
103
|
-
fieldState: observable.deep,
|
|
104
116
|
isDirty: computed,
|
|
105
117
|
isValid: computed,
|
|
106
118
|
snapshot: computed,
|
|
@@ -134,7 +146,7 @@ export class Form {
|
|
|
134
146
|
*
|
|
135
147
|
* [**Documentation**](https://js2me.github.io/mobx-formly/api/form.html#isvalid)
|
|
136
148
|
*/
|
|
137
|
-
get isValid() { return
|
|
149
|
+
get isValid() { return this.errorsByPath.size === 0; }
|
|
138
150
|
/**
|
|
139
151
|
* Registers a field and returns its ref and event handlers.
|
|
140
152
|
*
|
|
@@ -165,7 +177,7 @@ export class Form {
|
|
|
165
177
|
if (this.disabled)
|
|
166
178
|
return;
|
|
167
179
|
this.markTouched(path);
|
|
168
|
-
if (this.options.mode === 'onBlur' || this.options.mode === 'all' || (
|
|
180
|
+
if (this.options.mode === 'onBlur' || this.options.mode === 'all' || (this.hasError(path) && this.options.reValidateMode === 'onBlur'))
|
|
169
181
|
await this.trigger(path);
|
|
170
182
|
},
|
|
171
183
|
};
|
|
@@ -179,11 +191,11 @@ export class Form {
|
|
|
179
191
|
const path = name;
|
|
180
192
|
this.fieldValidationVersions.set(path, (this.fieldValidationVersions.get(path) ?? 0) + 1);
|
|
181
193
|
deleteAtPath(this.values, path);
|
|
182
|
-
|
|
194
|
+
this.applyError(path, undefined);
|
|
183
195
|
delete this.dirtyFields[path];
|
|
184
196
|
delete this.touchedFields[path];
|
|
185
197
|
delete this.validatingFields[path];
|
|
186
|
-
|
|
198
|
+
this.deleteFieldState(path);
|
|
187
199
|
this.fieldOptions.delete(path);
|
|
188
200
|
this.refs.delete(path);
|
|
189
201
|
}
|
|
@@ -297,10 +309,9 @@ export class Form {
|
|
|
297
309
|
*/
|
|
298
310
|
clearErrors(name) {
|
|
299
311
|
if (!name) {
|
|
300
|
-
this.
|
|
301
|
-
for (const state of
|
|
302
|
-
|
|
303
|
-
this.applyFieldState(state, undefined);
|
|
312
|
+
this.clearPathStore(this.errorsByPath, this.errorPathCounts, this.errorChildren);
|
|
313
|
+
for (const [, state] of this.fieldStates())
|
|
314
|
+
this.applyFieldState(state, undefined);
|
|
304
315
|
return;
|
|
305
316
|
}
|
|
306
317
|
for (const path of Array.isArray(name) ? name : [name])
|
|
@@ -325,11 +336,12 @@ export class Form {
|
|
|
325
336
|
}
|
|
326
337
|
try {
|
|
327
338
|
const schemaErrors = await this.validateSchema();
|
|
328
|
-
const validationPaths = paths ?? [...new Set([...this.fieldOptions.keys(), ...
|
|
339
|
+
const validationPaths = paths ?? [...new Set([...this.fieldOptions.keys(), ...this.errorsByPath.keys(), ...this.errorPaths(schemaErrors)])];
|
|
329
340
|
runInAction(() => {
|
|
330
341
|
for (const path of validationPaths) {
|
|
331
|
-
|
|
332
|
-
|
|
342
|
+
const schemaError = this.getError(schemaErrors, path);
|
|
343
|
+
if (schemaError && this.isValidationCurrent(path, run, fieldVersions)) {
|
|
344
|
+
this.applyError(path, schemaError);
|
|
333
345
|
}
|
|
334
346
|
}
|
|
335
347
|
});
|
|
@@ -339,14 +351,14 @@ export class Form {
|
|
|
339
351
|
if (this.isValidationCurrent(path, run, fieldVersions)) {
|
|
340
352
|
if (ruleError)
|
|
341
353
|
this.applyError(path, ruleError);
|
|
342
|
-
else if (schemaErrors
|
|
343
|
-
this.applyError(path, schemaErrors
|
|
354
|
+
else if (this.getError(schemaErrors, path))
|
|
355
|
+
this.applyError(path, this.getError(schemaErrors, path));
|
|
344
356
|
else
|
|
345
357
|
this.applyError(path, undefined);
|
|
346
358
|
}
|
|
347
359
|
});
|
|
348
360
|
}
|
|
349
|
-
return (paths ??
|
|
361
|
+
return (paths ?? [...this.errorsByPath.keys()]).every((path) => !this.hasError(path));
|
|
350
362
|
}
|
|
351
363
|
finally {
|
|
352
364
|
runInAction(() => {
|
|
@@ -409,7 +421,7 @@ export class Form {
|
|
|
409
421
|
this.validationVersion += 1;
|
|
410
422
|
for (const path of Object.keys(this.validatingFields)) {
|
|
411
423
|
delete this.validatingFields[path];
|
|
412
|
-
const state = this.
|
|
424
|
+
const state = this.fieldStatesByPath.get(path);
|
|
413
425
|
if (state)
|
|
414
426
|
state.isValidating = false;
|
|
415
427
|
}
|
|
@@ -422,10 +434,9 @@ export class Form {
|
|
|
422
434
|
if (!options.keepTouched)
|
|
423
435
|
this.touchedFields = {};
|
|
424
436
|
if (!options.keepErrors) {
|
|
425
|
-
this.
|
|
426
|
-
for (const state of
|
|
427
|
-
|
|
428
|
-
this.applyFieldState(state, undefined);
|
|
437
|
+
this.clearPathStore(this.errorsByPath, this.errorPathCounts, this.errorChildren);
|
|
438
|
+
for (const [, state] of this.fieldStates())
|
|
439
|
+
this.applyFieldState(state, undefined);
|
|
429
440
|
}
|
|
430
441
|
if (!options.keepIsSubmitted) {
|
|
431
442
|
this.isSubmitted = false;
|
|
@@ -433,11 +444,10 @@ export class Form {
|
|
|
433
444
|
}
|
|
434
445
|
if (!options.keepSubmitCount)
|
|
435
446
|
this.submitCount = 0;
|
|
436
|
-
for (const [path, state] of
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
447
|
+
for (const [path, state] of this.fieldStates()) {
|
|
448
|
+
state.isDirty = !!this.dirtyFields[path];
|
|
449
|
+
state.isTouched = !!this.touchedFields[path];
|
|
450
|
+
}
|
|
441
451
|
}
|
|
442
452
|
/**
|
|
443
453
|
* Resets one field to its current default value and clears its state.
|
|
@@ -448,7 +458,7 @@ export class Form {
|
|
|
448
458
|
const path = name;
|
|
449
459
|
this.fieldValidationVersions.set(path, (this.fieldValidationVersions.get(path) ?? 0) + 1);
|
|
450
460
|
setAtPath(this.values, path, clone(getAtPath(this.defaultValues, path)));
|
|
451
|
-
|
|
461
|
+
this.applyError(path, undefined);
|
|
452
462
|
delete this.dirtyFields[path];
|
|
453
463
|
delete this.touchedFields[path];
|
|
454
464
|
delete this.validatingFields[path];
|
|
@@ -481,7 +491,7 @@ export class Form {
|
|
|
481
491
|
this.dirtyFields[path] = true;
|
|
482
492
|
this.ensureFieldState(path).isDirty = !!this.dirtyFields[path];
|
|
483
493
|
}
|
|
484
|
-
shouldValidateOnChange(path) { return this.options.mode === 'onChange' || this.options.mode === 'all' || (
|
|
494
|
+
shouldValidateOnChange(path) { return this.options.mode === 'onChange' || this.options.mode === 'all' || (this.hasError(path) && this.options.reValidateMode === 'onChange'); }
|
|
485
495
|
isValidationCurrent(path, run, fieldVersions) {
|
|
486
496
|
if (this.validationVersion !== run)
|
|
487
497
|
return false;
|
|
@@ -516,8 +526,8 @@ export class Form {
|
|
|
516
526
|
normalizeSchemaErrors(error) {
|
|
517
527
|
return error.issues.reduce((errors, issue) => {
|
|
518
528
|
const path = (issue.path ?? []).map((part) => String(typeof part === 'object' ? part.key : part)).join('.') || 'root';
|
|
519
|
-
if (!errors
|
|
520
|
-
errors
|
|
529
|
+
if (!this.getError(errors, path))
|
|
530
|
+
setAtPath(errors, path, { type: issue.code ?? issue.type ?? 'validation', message: issue.message });
|
|
521
531
|
return errors;
|
|
522
532
|
}, {});
|
|
523
533
|
}
|
|
@@ -553,7 +563,12 @@ export class Form {
|
|
|
553
563
|
return undefined;
|
|
554
564
|
}
|
|
555
565
|
ensureFieldState(path) {
|
|
556
|
-
|
|
566
|
+
const existing = this.fieldStatesByPath.get(path);
|
|
567
|
+
if (existing)
|
|
568
|
+
return existing;
|
|
569
|
+
const state = { invalid: false, isDirty: false, isTouched: false, isValidating: false };
|
|
570
|
+
this.setPathStore(this.fieldStatesByPath, this.fieldStatePathCounts, this.fieldStateChildren, path, state);
|
|
571
|
+
return state;
|
|
557
572
|
}
|
|
558
573
|
applyFieldState(state, error) {
|
|
559
574
|
state.error = error;
|
|
@@ -561,9 +576,106 @@ export class Form {
|
|
|
561
576
|
}
|
|
562
577
|
applyError(path, error) {
|
|
563
578
|
if (error)
|
|
564
|
-
this.
|
|
579
|
+
this.setPathStore(this.errorsByPath, this.errorPathCounts, this.errorChildren, path, error);
|
|
565
580
|
else
|
|
566
|
-
|
|
581
|
+
this.deletePathStore(this.errorsByPath, this.errorPathCounts, this.errorChildren, path);
|
|
567
582
|
this.applyFieldState(this.ensureFieldState(path), error);
|
|
568
583
|
}
|
|
584
|
+
getError(errors, path) {
|
|
585
|
+
const value = getAtPath(errors, path);
|
|
586
|
+
return value && typeof value === 'object' && 'type' in value ? value : undefined;
|
|
587
|
+
}
|
|
588
|
+
hasError(path) { return this.errorsByPath.has(path); }
|
|
589
|
+
errorPaths(errors, base = '') {
|
|
590
|
+
const paths = [];
|
|
591
|
+
for (const [key, value] of Object.entries(errors)) {
|
|
592
|
+
if (value === undefined)
|
|
593
|
+
continue;
|
|
594
|
+
const path = base ? `${base}.${key}` : key;
|
|
595
|
+
if (value && typeof value === 'object' && 'type' in value)
|
|
596
|
+
paths.push(path);
|
|
597
|
+
if (value && typeof value === 'object')
|
|
598
|
+
paths.push(...this.errorPaths(value, path));
|
|
599
|
+
}
|
|
600
|
+
return paths;
|
|
601
|
+
}
|
|
602
|
+
deleteFieldState(path) {
|
|
603
|
+
this.deletePathStore(this.fieldStatesByPath, this.fieldStatePathCounts, this.fieldStateChildren, path);
|
|
604
|
+
}
|
|
605
|
+
fieldStates() {
|
|
606
|
+
return [...this.fieldStatesByPath.entries()];
|
|
607
|
+
}
|
|
608
|
+
setPathStore(store, counts, children, path, value) {
|
|
609
|
+
if (!store.has(path))
|
|
610
|
+
this.addPathToIndex(counts, children, path);
|
|
611
|
+
store.set(path, value);
|
|
612
|
+
}
|
|
613
|
+
deletePathStore(store, counts, children, path) {
|
|
614
|
+
if (!store.delete(path))
|
|
615
|
+
return;
|
|
616
|
+
const parts = path.split('.');
|
|
617
|
+
for (let index = parts.length; index > 0; index -= 1) {
|
|
618
|
+
const current = parts.slice(0, index).join('.');
|
|
619
|
+
const count = (counts.get(current) ?? 1) - 1;
|
|
620
|
+
if (count > 0) {
|
|
621
|
+
counts.set(current, count);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
counts.delete(current);
|
|
625
|
+
const parent = parts.slice(0, index - 1).join('.');
|
|
626
|
+
const siblings = children.get(parent);
|
|
627
|
+
siblings?.delete(parts[index - 1]);
|
|
628
|
+
if (siblings?.size === 0)
|
|
629
|
+
children.delete(parent);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
clearPathStore(store, counts, children) {
|
|
633
|
+
store.clear();
|
|
634
|
+
counts.clear();
|
|
635
|
+
children.clear();
|
|
636
|
+
}
|
|
637
|
+
addPathToIndex(counts, children, path) {
|
|
638
|
+
const parts = path.split('.');
|
|
639
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
640
|
+
const current = parts.slice(0, index).join('.');
|
|
641
|
+
counts.set(current, (counts.get(current) ?? 0) + 1);
|
|
642
|
+
const parent = parts.slice(0, index - 1).join('.');
|
|
643
|
+
let siblings = children.get(parent);
|
|
644
|
+
if (!siblings)
|
|
645
|
+
children.set(parent, siblings = new Set());
|
|
646
|
+
siblings.add(parts[index - 1]);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
createPathProxy(path, store, counts, children, cache) {
|
|
650
|
+
const cached = cache.get(path);
|
|
651
|
+
if (cached)
|
|
652
|
+
return cached;
|
|
653
|
+
const proxy = new Proxy({}, {
|
|
654
|
+
get: (_, property) => {
|
|
655
|
+
if (typeof property !== 'string')
|
|
656
|
+
return undefined;
|
|
657
|
+
const current = store.get(path);
|
|
658
|
+
if (current && property in current)
|
|
659
|
+
return current[property];
|
|
660
|
+
const childPath = path ? `${path}.${property}` : property;
|
|
661
|
+
const value = store.get(childPath);
|
|
662
|
+
if (value && !children.has(childPath))
|
|
663
|
+
return value;
|
|
664
|
+
return counts.has(childPath) ? this.createPathProxy(childPath, store, counts, children, cache) : undefined;
|
|
665
|
+
},
|
|
666
|
+
ownKeys: () => [...Object.keys(store.get(path) ?? {}), ...(children.get(path) ?? [])],
|
|
667
|
+
getOwnPropertyDescriptor: (_, property) => {
|
|
668
|
+
if (typeof property !== 'string')
|
|
669
|
+
return undefined;
|
|
670
|
+
const value = store.get(path);
|
|
671
|
+
if (value && property in value)
|
|
672
|
+
return { configurable: true, enumerable: true, value: value[property] };
|
|
673
|
+
return children.get(path)?.has(property) ? { configurable: true, enumerable: true } : undefined;
|
|
674
|
+
},
|
|
675
|
+
set: () => false,
|
|
676
|
+
deleteProperty: () => false,
|
|
677
|
+
});
|
|
678
|
+
cache.set(path, proxy);
|
|
679
|
+
return proxy;
|
|
680
|
+
}
|
|
569
681
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -49,7 +49,11 @@ export interface FieldError {
|
|
|
49
49
|
type: string;
|
|
50
50
|
message?: string;
|
|
51
51
|
}
|
|
52
|
-
export type FieldErrors<T extends
|
|
52
|
+
export type FieldErrors<T extends object = FieldValues> = {
|
|
53
|
+
[K in keyof T]?: T[K] extends readonly (infer I)[] ? FieldError & Array<I extends object ? FieldErrors<I> : FieldError | undefined> : T[K] extends object ? FieldError & FieldErrors<T[K]> : FieldError;
|
|
54
|
+
} & {
|
|
55
|
+
root?: FieldError;
|
|
56
|
+
};
|
|
53
57
|
export interface FieldState {
|
|
54
58
|
invalid: boolean;
|
|
55
59
|
isDirty: boolean;
|
|
@@ -57,7 +61,11 @@ export interface FieldState {
|
|
|
57
61
|
isValidating: boolean;
|
|
58
62
|
error?: FieldError;
|
|
59
63
|
}
|
|
60
|
-
export type FieldStateTree<T extends
|
|
64
|
+
export type FieldStateTree<T extends object = FieldValues> = {
|
|
65
|
+
[K in keyof T]?: T[K] extends readonly (infer I)[] ? FieldState & Array<I extends object ? FieldStateTree<I> : FieldState | undefined> : T[K] extends object ? FieldState & FieldStateTree<T[K]> : FieldState;
|
|
66
|
+
} & {
|
|
67
|
+
root?: FieldState;
|
|
68
|
+
};
|
|
61
69
|
export interface FormState<T extends FieldValues = FieldValues> {
|
|
62
70
|
errors: FieldErrors<T>;
|
|
63
71
|
dirtyFields: Partial<Record<FieldPath<T>, true>>;
|
package/dist/utils.js
CHANGED
|
@@ -41,8 +41,9 @@ export const deleteAtPath = (target, path) => {
|
|
|
41
41
|
return undefined;
|
|
42
42
|
return value[key];
|
|
43
43
|
}, target);
|
|
44
|
-
if (parent
|
|
45
|
-
|
|
44
|
+
if (!parent || typeof parent !== 'object')
|
|
45
|
+
return;
|
|
46
|
+
delete parent[lastKey];
|
|
46
47
|
};
|
|
47
48
|
export const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
48
49
|
export const extractValue = (eventOrValue) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-formly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Framework-agnostic observable forms for MobX with granular field state and Zod or Valibot validation",
|
|
5
5
|
"homepage": "https://js2me.github.io/mobx-formly/",
|
|
6
6
|
"repository": {
|
|
@@ -19,17 +19,6 @@
|
|
|
19
19
|
"import": "./dist/index.js"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsc -p tsconfig.json",
|
|
24
|
-
"check": "tsc --noEmit -p tsconfig.json",
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"test:performance": "vitest run tests/performance",
|
|
27
|
-
"test:coverage": "vitest run --coverage",
|
|
28
|
-
"test:types": "tsc --noEmit -p tsconfig.types.json",
|
|
29
|
-
"docs:dev": "docusite dev",
|
|
30
|
-
"docs:build": "docusite build",
|
|
31
|
-
"docs:preview": "docusite preview"
|
|
32
|
-
},
|
|
33
22
|
"keywords": [
|
|
34
23
|
"mobx",
|
|
35
24
|
"mobx-form",
|
|
@@ -70,7 +59,6 @@
|
|
|
70
59
|
"access": "public"
|
|
71
60
|
},
|
|
72
61
|
"type": "module",
|
|
73
|
-
"packageManager": "pnpm@11.24.0",
|
|
74
62
|
"dependencies": {
|
|
75
63
|
"mobx": "^6.16.1",
|
|
76
64
|
"yummies": "^7.20.1"
|
|
@@ -84,5 +72,16 @@
|
|
|
84
72
|
"valibot": "^1.4.2",
|
|
85
73
|
"vitest": "^4.1.11",
|
|
86
74
|
"zod": "^4.4.3"
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsc -p tsconfig.json",
|
|
78
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
79
|
+
"test": "vitest run",
|
|
80
|
+
"test:performance": "vitest run tests/performance",
|
|
81
|
+
"test:coverage": "vitest run --coverage",
|
|
82
|
+
"test:types": "tsc --noEmit -p tsconfig.types.json",
|
|
83
|
+
"docs:dev": "docusite dev",
|
|
84
|
+
"docs:build": "docusite build",
|
|
85
|
+
"docs:preview": "docusite preview"
|
|
87
86
|
}
|
|
88
|
-
}
|
|
87
|
+
}
|
package/dist/src/form.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { type Ref } from 'yummies/mobx';
|
|
2
|
-
import type { FieldError, FieldErrors, FieldPath, FieldState, FieldValues, FormOptions, FormState, RegisterOptions, RegisterReturn, ResetOptions, SetValueConfig, SubmitHandlers } from './types.js';
|
|
3
|
-
export declare class Form<T extends FieldValues = FieldValues> {
|
|
4
|
-
values: T;
|
|
5
|
-
errors: FieldErrors<T>;
|
|
6
|
-
dirtyFields: Partial<Record<FieldPath<T>, true>>;
|
|
7
|
-
touchedFields: Partial<Record<FieldPath<T>, true>>;
|
|
8
|
-
validatingFields: Partial<Record<FieldPath<T>, true>>;
|
|
9
|
-
isSubmitting: boolean;
|
|
10
|
-
isSubmitted: boolean;
|
|
11
|
-
isSubmitSuccessful: boolean;
|
|
12
|
-
submitCount: number;
|
|
13
|
-
readonly refs: Map<string, Ref<HTMLElement | null, import("yummies/types").AnyObject>>;
|
|
14
|
-
private readonly defaultValues;
|
|
15
|
-
private readonly options;
|
|
16
|
-
private readonly fieldOptions;
|
|
17
|
-
private readonly listeners;
|
|
18
|
-
constructor(options?: FormOptions<T>);
|
|
19
|
-
get formState(): FormState<T>;
|
|
20
|
-
get isDirty(): boolean;
|
|
21
|
-
get isValid(): boolean;
|
|
22
|
-
register(name: FieldPath<T>, options?: RegisterOptions<T>): RegisterReturn;
|
|
23
|
-
unregister(name: FieldPath<T>): void;
|
|
24
|
-
getValues(): T;
|
|
25
|
-
getValues(name: FieldPath<T>): unknown;
|
|
26
|
-
setValue(name: FieldPath<T>, value: unknown, config?: SetValueConfig): void;
|
|
27
|
-
setError(name: FieldPath<T>, error: FieldError): void;
|
|
28
|
-
clearErrors(name?: FieldPath<T> | FieldPath<T>[]): void;
|
|
29
|
-
getFieldState(name: FieldPath<T>): FieldState;
|
|
30
|
-
trigger(name?: FieldPath<T> | FieldPath<T>[]): Promise<boolean>;
|
|
31
|
-
handleSubmit({ onValid, onInvalid }: SubmitHandlers<T>): () => Promise<void>;
|
|
32
|
-
reset(values?: Partial<T>, options?: ResetOptions): void;
|
|
33
|
-
resetField(name: FieldPath<T>): void;
|
|
34
|
-
setFocus(name: FieldPath<T>): void;
|
|
35
|
-
watch(): T;
|
|
36
|
-
watch(name: FieldPath<T>): unknown;
|
|
37
|
-
subscribe(listener: (values: T, info: {
|
|
38
|
-
name?: string;
|
|
39
|
-
}) => void): () => void;
|
|
40
|
-
private notify;
|
|
41
|
-
private markTouched;
|
|
42
|
-
private updateDirty;
|
|
43
|
-
private shouldValidateOnChange;
|
|
44
|
-
private transformValue;
|
|
45
|
-
private validateSchema;
|
|
46
|
-
private zodErrors;
|
|
47
|
-
private validateRules;
|
|
48
|
-
}
|
package/dist/src/form.js
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { makeAutoObservable, runInAction } from 'mobx';
|
|
2
|
-
import { createRef } from 'yummies/mobx';
|
|
3
|
-
import { clone, deleteAtPath, extractValue, getAtPath, isEqual, setAtPath } from './utils.js';
|
|
4
|
-
export class Form {
|
|
5
|
-
values;
|
|
6
|
-
errors = {};
|
|
7
|
-
dirtyFields = {};
|
|
8
|
-
touchedFields = {};
|
|
9
|
-
validatingFields = {};
|
|
10
|
-
isSubmitting = false;
|
|
11
|
-
isSubmitted = false;
|
|
12
|
-
isSubmitSuccessful = false;
|
|
13
|
-
submitCount = 0;
|
|
14
|
-
refs = new Map();
|
|
15
|
-
defaultValues;
|
|
16
|
-
options;
|
|
17
|
-
fieldOptions = new Map();
|
|
18
|
-
listeners = new Set();
|
|
19
|
-
constructor(options = {}) {
|
|
20
|
-
this.options = {
|
|
21
|
-
...options,
|
|
22
|
-
mode: options.mode ?? 'onSubmit',
|
|
23
|
-
reValidateMode: options.reValidateMode ?? 'onChange',
|
|
24
|
-
disabled: options.disabled ?? false,
|
|
25
|
-
};
|
|
26
|
-
this.defaultValues = clone((options.defaultValues ?? {}));
|
|
27
|
-
this.values = clone((options.values ?? options.defaultValues ?? {}));
|
|
28
|
-
makeAutoObservable(this, { defaultValues: false, options: false, fieldOptions: false, listeners: false, refs: false }, { autoBind: true });
|
|
29
|
-
}
|
|
30
|
-
get formState() {
|
|
31
|
-
return {
|
|
32
|
-
errors: this.errors,
|
|
33
|
-
dirtyFields: this.dirtyFields,
|
|
34
|
-
touchedFields: this.touchedFields,
|
|
35
|
-
validatingFields: this.validatingFields,
|
|
36
|
-
isDirty: Object.keys(this.dirtyFields).length > 0,
|
|
37
|
-
isValid: Object.keys(this.errors).length === 0,
|
|
38
|
-
isValidating: Object.keys(this.validatingFields).length > 0,
|
|
39
|
-
isSubmitting: this.isSubmitting,
|
|
40
|
-
isSubmitted: this.isSubmitted,
|
|
41
|
-
isSubmitSuccessful: this.isSubmitSuccessful,
|
|
42
|
-
submitCount: this.submitCount,
|
|
43
|
-
disabled: this.options.disabled,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
get isDirty() { return this.formState.isDirty; }
|
|
47
|
-
get isValid() { return this.formState.isValid; }
|
|
48
|
-
register(name, options = {}) {
|
|
49
|
-
const path = name;
|
|
50
|
-
this.fieldOptions.set(path, options);
|
|
51
|
-
let ref = this.refs.get(path);
|
|
52
|
-
if (!ref) {
|
|
53
|
-
ref = createRef();
|
|
54
|
-
this.refs.set(path, ref);
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
name: path,
|
|
58
|
-
ref,
|
|
59
|
-
onChange: async (eventOrValue) => {
|
|
60
|
-
const value = this.transformValue(extractValue(eventOrValue), options);
|
|
61
|
-
const shouldValidate = this.shouldValidateOnChange(path);
|
|
62
|
-
this.setValue(path, value, { shouldDirty: true, shouldValidate: false });
|
|
63
|
-
if (shouldValidate)
|
|
64
|
-
await this.trigger(path);
|
|
65
|
-
},
|
|
66
|
-
onBlur: async () => {
|
|
67
|
-
this.markTouched(path);
|
|
68
|
-
if (this.options.mode === 'onBlur' || this.options.mode === 'all' || (path in this.errors && this.options.reValidateMode === 'onBlur'))
|
|
69
|
-
await this.trigger(path);
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
unregister(name) {
|
|
74
|
-
const path = name;
|
|
75
|
-
deleteAtPath(this.values, path);
|
|
76
|
-
delete this.errors[path];
|
|
77
|
-
delete this.dirtyFields[path];
|
|
78
|
-
delete this.touchedFields[path];
|
|
79
|
-
delete this.validatingFields[path];
|
|
80
|
-
this.fieldOptions.delete(path);
|
|
81
|
-
this.refs.delete(path);
|
|
82
|
-
this.notify(path);
|
|
83
|
-
}
|
|
84
|
-
getValues(name) {
|
|
85
|
-
return name === undefined ? clone(this.values) : getAtPath(this.values, name);
|
|
86
|
-
}
|
|
87
|
-
setValue(name, value, config = {}) {
|
|
88
|
-
const path = name;
|
|
89
|
-
setAtPath(this.values, path, value);
|
|
90
|
-
if (config.shouldDirty ?? true)
|
|
91
|
-
this.updateDirty(path);
|
|
92
|
-
if (config.shouldTouch)
|
|
93
|
-
this.markTouched(path);
|
|
94
|
-
this.notify(path);
|
|
95
|
-
if (config.shouldValidate)
|
|
96
|
-
void this.trigger(path);
|
|
97
|
-
}
|
|
98
|
-
setError(name, error) {
|
|
99
|
-
this.errors[name] = error;
|
|
100
|
-
}
|
|
101
|
-
clearErrors(name) {
|
|
102
|
-
if (!name) {
|
|
103
|
-
this.errors = {};
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
for (const path of Array.isArray(name) ? name : [name])
|
|
107
|
-
delete this.errors[path];
|
|
108
|
-
}
|
|
109
|
-
getFieldState(name) {
|
|
110
|
-
const path = name;
|
|
111
|
-
return { invalid: !!this.errors[path], isDirty: !!this.dirtyFields[path], isTouched: !!this.touchedFields[path], error: this.errors[path] };
|
|
112
|
-
}
|
|
113
|
-
async trigger(name) {
|
|
114
|
-
const paths = name ? (Array.isArray(name) ? name : [name]).map(String) : undefined;
|
|
115
|
-
for (const path of paths ?? this.fieldOptions.keys())
|
|
116
|
-
this.validatingFields[path] = true;
|
|
117
|
-
try {
|
|
118
|
-
const schemaErrors = await this.validateSchema();
|
|
119
|
-
const validationPaths = paths ?? [...new Set([...this.fieldOptions.keys(), ...Object.keys(schemaErrors)])];
|
|
120
|
-
runInAction(() => {
|
|
121
|
-
for (const path of validationPaths) {
|
|
122
|
-
const error = schemaErrors[path] ?? this.validateRules(path);
|
|
123
|
-
if (error instanceof Promise)
|
|
124
|
-
continue;
|
|
125
|
-
if (error)
|
|
126
|
-
this.errors[path] = error;
|
|
127
|
-
else
|
|
128
|
-
delete this.errors[path];
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
for (const path of validationPaths) {
|
|
132
|
-
const ruleError = await this.validateRules(path);
|
|
133
|
-
runInAction(() => {
|
|
134
|
-
if (ruleError)
|
|
135
|
-
this.errors[path] = ruleError;
|
|
136
|
-
else if (!schemaErrors[path])
|
|
137
|
-
delete this.errors[path];
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
return (paths ?? Object.keys(this.errors)).every((path) => !this.errors[path]);
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
runInAction(() => {
|
|
144
|
-
for (const path of paths ?? this.fieldOptions.keys())
|
|
145
|
-
delete this.validatingFields[path];
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
handleSubmit({ onValid, onInvalid }) {
|
|
150
|
-
return async () => {
|
|
151
|
-
this.isSubmitting = true;
|
|
152
|
-
this.isSubmitted = true;
|
|
153
|
-
this.submitCount += 1;
|
|
154
|
-
try {
|
|
155
|
-
const valid = await this.trigger();
|
|
156
|
-
if (valid) {
|
|
157
|
-
await onValid(this.getValues(), this);
|
|
158
|
-
runInAction(() => { this.isSubmitSuccessful = true; });
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
await onInvalid?.(this.errors, this);
|
|
162
|
-
runInAction(() => { this.isSubmitSuccessful = false; });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
finally {
|
|
166
|
-
runInAction(() => { this.isSubmitting = false; });
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
reset(values, options = {}) {
|
|
171
|
-
const next = clone((values ?? this.defaultValues));
|
|
172
|
-
this.values = next;
|
|
173
|
-
if (!options.keepDefaultValues && values)
|
|
174
|
-
Object.assign(this.defaultValues, clone(values));
|
|
175
|
-
if (!options.keepDirty)
|
|
176
|
-
this.dirtyFields = {};
|
|
177
|
-
if (!options.keepTouched)
|
|
178
|
-
this.touchedFields = {};
|
|
179
|
-
if (!options.keepErrors)
|
|
180
|
-
this.errors = {};
|
|
181
|
-
if (!options.keepIsSubmitted) {
|
|
182
|
-
this.isSubmitted = false;
|
|
183
|
-
this.isSubmitSuccessful = false;
|
|
184
|
-
}
|
|
185
|
-
if (!options.keepSubmitCount)
|
|
186
|
-
this.submitCount = 0;
|
|
187
|
-
this.notify();
|
|
188
|
-
}
|
|
189
|
-
resetField(name) {
|
|
190
|
-
const path = name;
|
|
191
|
-
setAtPath(this.values, path, clone(getAtPath(this.defaultValues, path)));
|
|
192
|
-
delete this.errors[path];
|
|
193
|
-
delete this.dirtyFields[path];
|
|
194
|
-
delete this.touchedFields[path];
|
|
195
|
-
this.notify(path);
|
|
196
|
-
}
|
|
197
|
-
setFocus(name) { this.refs.get(name)?.current?.focus(); }
|
|
198
|
-
watch(name) { return name === undefined ? this.values : getAtPath(this.values, name); }
|
|
199
|
-
subscribe(listener) {
|
|
200
|
-
this.listeners.add(listener);
|
|
201
|
-
return () => this.listeners.delete(listener);
|
|
202
|
-
}
|
|
203
|
-
notify(name) { for (const listener of this.listeners)
|
|
204
|
-
listener(this.getValues(), { name }); }
|
|
205
|
-
markTouched(path) { this.touchedFields[path] = true; }
|
|
206
|
-
updateDirty(path) { if (isEqual(getAtPath(this.values, path), getAtPath(this.defaultValues, path)))
|
|
207
|
-
delete this.dirtyFields[path];
|
|
208
|
-
else
|
|
209
|
-
this.dirtyFields[path] = true; }
|
|
210
|
-
shouldValidateOnChange(path) { return this.options.mode === 'onChange' || this.options.mode === 'all' || (path in this.errors && this.options.reValidateMode === 'onChange'); }
|
|
211
|
-
transformValue(value, options) {
|
|
212
|
-
if (options.setValueAs)
|
|
213
|
-
return options.setValueAs(value);
|
|
214
|
-
if (options.valueAsNumber)
|
|
215
|
-
return value === '' ? Number.NaN : Number(value);
|
|
216
|
-
if (options.valueAsDate)
|
|
217
|
-
return new Date(String(value));
|
|
218
|
-
return value;
|
|
219
|
-
}
|
|
220
|
-
async validateSchema() {
|
|
221
|
-
if (!this.options.schema)
|
|
222
|
-
return {};
|
|
223
|
-
const result = await this.options.schema.safeParseAsync(this.getValues());
|
|
224
|
-
if (result.success)
|
|
225
|
-
return {};
|
|
226
|
-
return this.zodErrors(result.error);
|
|
227
|
-
}
|
|
228
|
-
zodErrors(error) {
|
|
229
|
-
return error.issues.reduce((errors, issue) => {
|
|
230
|
-
const path = issue.path.map(String).join('.') || 'root';
|
|
231
|
-
if (!errors[path])
|
|
232
|
-
errors[path] = { type: issue.code, message: issue.message };
|
|
233
|
-
return errors;
|
|
234
|
-
}, {});
|
|
235
|
-
}
|
|
236
|
-
async validateRules(path) {
|
|
237
|
-
const rules = this.fieldOptions.get(path);
|
|
238
|
-
if (!rules)
|
|
239
|
-
return undefined;
|
|
240
|
-
const value = getAtPath(this.values, path);
|
|
241
|
-
const fail = (type, rule) => typeof rule === 'string' ? { type, message: rule } : { type };
|
|
242
|
-
if (rules.required && (value === undefined || value === null || value === ''))
|
|
243
|
-
return fail('required', rules.required);
|
|
244
|
-
if (rules.minLength && String(value ?? '').length < rules.minLength.value)
|
|
245
|
-
return { type: 'minLength', message: rules.minLength.message };
|
|
246
|
-
if (rules.maxLength && String(value ?? '').length > rules.maxLength.value)
|
|
247
|
-
return { type: 'maxLength', message: rules.maxLength.message };
|
|
248
|
-
if (rules.min && Number(value) < rules.min.value)
|
|
249
|
-
return { type: 'min', message: rules.min.message };
|
|
250
|
-
if (rules.max && Number(value) > rules.max.value)
|
|
251
|
-
return { type: 'max', message: rules.max.message };
|
|
252
|
-
if (rules.pattern && !rules.pattern.value.test(String(value ?? '')))
|
|
253
|
-
return { type: 'pattern', message: rules.pattern.message };
|
|
254
|
-
if (rules.validate) {
|
|
255
|
-
const result = await rules.validate(value, this.getValues());
|
|
256
|
-
if (result !== true)
|
|
257
|
-
return { type: 'validate', message: typeof result === 'string' ? result : undefined };
|
|
258
|
-
}
|
|
259
|
-
return undefined;
|
|
260
|
-
}
|
|
261
|
-
}
|
package/dist/src/index.d.ts
DELETED
package/dist/src/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Form } from './form.js';
|
package/dist/src/types.d.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { Ref } from 'yummies/mobx';
|
|
2
|
-
import type { ZodType } from 'zod';
|
|
3
|
-
export type FieldValues = Record<string, unknown>;
|
|
4
|
-
/** Dot-separated path into the form value tree, for example `user.email`. */
|
|
5
|
-
export type FieldPath<T extends FieldValues = FieldValues> = string;
|
|
6
|
-
export interface FieldError {
|
|
7
|
-
type: string;
|
|
8
|
-
message?: string;
|
|
9
|
-
}
|
|
10
|
-
export type FieldErrors<T extends FieldValues = FieldValues> = Partial<Record<FieldPath<T>, FieldError>>;
|
|
11
|
-
export interface FieldState {
|
|
12
|
-
invalid: boolean;
|
|
13
|
-
isDirty: boolean;
|
|
14
|
-
isTouched: boolean;
|
|
15
|
-
error?: FieldError;
|
|
16
|
-
}
|
|
17
|
-
export interface FormState<T extends FieldValues = FieldValues> {
|
|
18
|
-
errors: FieldErrors<T>;
|
|
19
|
-
dirtyFields: Partial<Record<FieldPath<T>, true>>;
|
|
20
|
-
touchedFields: Partial<Record<FieldPath<T>, true>>;
|
|
21
|
-
validatingFields: Partial<Record<FieldPath<T>, true>>;
|
|
22
|
-
isDirty: boolean;
|
|
23
|
-
isValid: boolean;
|
|
24
|
-
isValidating: boolean;
|
|
25
|
-
isSubmitting: boolean;
|
|
26
|
-
isSubmitted: boolean;
|
|
27
|
-
isSubmitSuccessful: boolean;
|
|
28
|
-
submitCount: number;
|
|
29
|
-
disabled: boolean;
|
|
30
|
-
}
|
|
31
|
-
export interface RegisterOptions<T extends FieldValues = FieldValues> {
|
|
32
|
-
required?: string | boolean;
|
|
33
|
-
minLength?: {
|
|
34
|
-
value: number;
|
|
35
|
-
message?: string;
|
|
36
|
-
};
|
|
37
|
-
maxLength?: {
|
|
38
|
-
value: number;
|
|
39
|
-
message?: string;
|
|
40
|
-
};
|
|
41
|
-
min?: {
|
|
42
|
-
value: number;
|
|
43
|
-
message?: string;
|
|
44
|
-
};
|
|
45
|
-
max?: {
|
|
46
|
-
value: number;
|
|
47
|
-
message?: string;
|
|
48
|
-
};
|
|
49
|
-
pattern?: {
|
|
50
|
-
value: RegExp;
|
|
51
|
-
message?: string;
|
|
52
|
-
};
|
|
53
|
-
validate?: (value: unknown, values: T) => boolean | string | Promise<boolean | string>;
|
|
54
|
-
valueAsNumber?: boolean;
|
|
55
|
-
valueAsDate?: boolean;
|
|
56
|
-
setValueAs?: (value: unknown) => unknown;
|
|
57
|
-
}
|
|
58
|
-
export interface RegisterReturn {
|
|
59
|
-
name: string;
|
|
60
|
-
ref: Ref<HTMLElement | null>;
|
|
61
|
-
onChange: (eventOrValue: unknown) => Promise<void>;
|
|
62
|
-
onBlur: () => Promise<void>;
|
|
63
|
-
}
|
|
64
|
-
export interface SetValueConfig {
|
|
65
|
-
shouldValidate?: boolean;
|
|
66
|
-
shouldDirty?: boolean;
|
|
67
|
-
shouldTouch?: boolean;
|
|
68
|
-
}
|
|
69
|
-
export interface ResetOptions {
|
|
70
|
-
keepDefaultValues?: boolean;
|
|
71
|
-
keepDirty?: boolean;
|
|
72
|
-
keepTouched?: boolean;
|
|
73
|
-
keepErrors?: boolean;
|
|
74
|
-
keepIsSubmitted?: boolean;
|
|
75
|
-
keepSubmitCount?: boolean;
|
|
76
|
-
}
|
|
77
|
-
export interface FormOptions<T extends FieldValues> {
|
|
78
|
-
defaultValues?: Partial<T>;
|
|
79
|
-
values?: Partial<T>;
|
|
80
|
-
schema?: ZodType<T>;
|
|
81
|
-
mode?: 'onSubmit' | 'onChange' | 'onBlur' | 'all';
|
|
82
|
-
reValidateMode?: 'onChange' | 'onBlur';
|
|
83
|
-
disabled?: boolean;
|
|
84
|
-
}
|
|
85
|
-
export interface SubmitHandlers<T extends FieldValues> {
|
|
86
|
-
onValid: (values: T, form: unknown) => void | Promise<void>;
|
|
87
|
-
onInvalid?: (errors: FieldErrors<T>, form: unknown) => void | Promise<void>;
|
|
88
|
-
}
|
package/dist/src/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/src/utils.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare const clone: <T>(value: T) => T;
|
|
2
|
-
export declare const getAtPath: (source: unknown, path: string) => unknown;
|
|
3
|
-
export declare const setAtPath: (target: Record<string, unknown>, path: string, value: unknown) => void;
|
|
4
|
-
export declare const deleteAtPath: (target: Record<string, unknown>, path: string) => void;
|
|
5
|
-
export declare const isEqual: (a: unknown, b: unknown) => boolean;
|
|
6
|
-
export declare const extractValue: (eventOrValue: unknown) => unknown;
|
package/dist/src/utils.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { isUnsafeProperty } from 'yummies/data';
|
|
2
|
-
export const clone = (value) => {
|
|
3
|
-
if (typeof structuredClone === 'function') {
|
|
4
|
-
try {
|
|
5
|
-
return structuredClone(value);
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
// MobX observable proxies cannot be structured-cloned directly.
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return JSON.parse(JSON.stringify(value));
|
|
12
|
-
};
|
|
13
|
-
export const getAtPath = (source, path) => path.split('.').reduce((value, key) => {
|
|
14
|
-
if (isUnsafeProperty(key) || value === null || value === undefined)
|
|
15
|
-
return undefined;
|
|
16
|
-
return value[key];
|
|
17
|
-
}, source);
|
|
18
|
-
export const setAtPath = (target, path, value) => {
|
|
19
|
-
const keys = path.split('.');
|
|
20
|
-
let current = target;
|
|
21
|
-
for (const key of keys.slice(0, -1)) {
|
|
22
|
-
if (isUnsafeProperty(key))
|
|
23
|
-
return;
|
|
24
|
-
const next = current[key];
|
|
25
|
-
if (!next || typeof next !== 'object' || Array.isArray(next))
|
|
26
|
-
current[key] = {};
|
|
27
|
-
current = current[key];
|
|
28
|
-
}
|
|
29
|
-
const lastKey = keys.at(-1);
|
|
30
|
-
if (lastKey && !isUnsafeProperty(lastKey))
|
|
31
|
-
current[lastKey] = value;
|
|
32
|
-
};
|
|
33
|
-
export const deleteAtPath = (target, path) => {
|
|
34
|
-
const keys = path.split('.');
|
|
35
|
-
const lastKey = keys.pop();
|
|
36
|
-
if (!lastKey || isUnsafeProperty(lastKey))
|
|
37
|
-
return;
|
|
38
|
-
const parent = keys.reduce((value, key) => {
|
|
39
|
-
if (isUnsafeProperty(key) || !value || typeof value !== 'object')
|
|
40
|
-
return undefined;
|
|
41
|
-
return value[key];
|
|
42
|
-
}, target);
|
|
43
|
-
if (parent && typeof parent === 'object')
|
|
44
|
-
delete parent[lastKey];
|
|
45
|
-
};
|
|
46
|
-
export const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
47
|
-
export const extractValue = (eventOrValue) => {
|
|
48
|
-
if (!eventOrValue || typeof eventOrValue !== 'object' || !('target' in eventOrValue))
|
|
49
|
-
return eventOrValue;
|
|
50
|
-
const target = eventOrValue.target;
|
|
51
|
-
if (target.type === 'checkbox')
|
|
52
|
-
return target.checked;
|
|
53
|
-
if (target.type === 'radio')
|
|
54
|
-
return target.checked ? target.value : undefined;
|
|
55
|
-
if (target.multiple && target instanceof HTMLSelectElement) {
|
|
56
|
-
return Array.from(target.selectedOptions, (option) => option.value);
|
|
57
|
-
}
|
|
58
|
-
return target.value;
|
|
59
|
-
};
|