@conform-to/react 1.9.1 → 1.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/README.md +1 -1
- package/dist/future/hooks.d.ts +4 -4
- package/dist/future/hooks.js +6 -7
- package/dist/future/hooks.mjs +6 -7
- package/dist/future/index.d.ts +3 -2
- package/dist/future/index.js +2 -0
- package/dist/future/index.mjs +1 -0
- package/dist/future/memoize.d.ts +49 -0
- package/dist/future/memoize.js +96 -0
- package/dist/future/memoize.mjs +91 -0
- package/dist/future/standard-schema.d.ts +56 -0
- package/dist/future/state.d.ts +7 -6
- package/dist/future/state.js +60 -25
- package/dist/future/state.mjs +60 -26
- package/dist/future/types.d.ts +15 -15
- package/dist/future/util.d.ts +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
Version 1.
|
|
10
|
+
Version 1.10.0 / License MIT / Copyright (c) 2025 Edmund Hung
|
|
11
11
|
|
|
12
12
|
Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards.
|
|
13
13
|
|
package/dist/future/hooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Serialize, type SubmissionResult, serialize } from '@conform-to/dom/future';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
|
-
import type { FormContext,
|
|
3
|
+
import type { FormContext, DefaultMetadata, IntentDispatcher, FormMetadata, Fieldset, FormOptions, FieldName, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef } from './types';
|
|
4
4
|
export declare const FormConfig: import("react").Context<{
|
|
5
5
|
intentName: string;
|
|
6
6
|
observer: {
|
|
@@ -66,7 +66,7 @@ export declare function useConform<ErrorShape, Value = undefined>(formRef: FormR
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function useForm<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = string, Value = undefined>(options: FormOptions<FormShape, ErrorShape, Value>): {
|
|
68
68
|
form: FormMetadata<ErrorShape>;
|
|
69
|
-
fields: Fieldset<FormShape,
|
|
69
|
+
fields: Fieldset<FormShape, DefaultMetadata<ErrorShape>>;
|
|
70
70
|
intent: IntentDispatcher;
|
|
71
71
|
};
|
|
72
72
|
/**
|
|
@@ -79,7 +79,7 @@ export declare function useForm<FormShape extends Record<string, any> = Record<s
|
|
|
79
79
|
* function ErrorSummary() {
|
|
80
80
|
* const form = useFormMetadata();
|
|
81
81
|
*
|
|
82
|
-
* if (
|
|
82
|
+
* if (form.valid) return null;
|
|
83
83
|
*
|
|
84
84
|
* return (
|
|
85
85
|
* <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
|
|
@@ -112,7 +112,7 @@ export declare function useFormMetadata<ErrorShape = string[]>(options?: {
|
|
|
112
112
|
*/
|
|
113
113
|
export declare function useField<FieldShape = any>(name: FieldName<FieldShape>, options?: {
|
|
114
114
|
formId?: string;
|
|
115
|
-
}):
|
|
115
|
+
}): FieldMetadata<FieldShape>;
|
|
116
116
|
/**
|
|
117
117
|
* A React hook that provides an intent dispatcher for programmatic form actions.
|
|
118
118
|
* Intent dispatchers allow you to trigger form operations like validation, field updates,
|
package/dist/future/hooks.js
CHANGED
|
@@ -127,7 +127,7 @@ function useConform(formRef, options) {
|
|
|
127
127
|
}
|
|
128
128
|
}, [formRef, state$1.resetKey]);
|
|
129
129
|
react.useEffect(() => {
|
|
130
|
-
if (!state$1.
|
|
130
|
+
if (!state$1.clientIntendedValue) {
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
133
|
var formElement = dom.getFormElement(formRef);
|
|
@@ -136,9 +136,9 @@ function useConform(formRef, options) {
|
|
|
136
136
|
console.error('Failed to update form value; No form element found');
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
|
-
dom.updateFormValue(formElement, state$1.
|
|
139
|
+
dom.updateFormValue(formElement, state$1.clientIntendedValue, optionsRef.current.serialize);
|
|
140
140
|
lastIntentedValueRef.current = undefined;
|
|
141
|
-
}, [formRef, state$1.
|
|
141
|
+
}, [formRef, state$1.clientIntendedValue, optionsRef]);
|
|
142
142
|
var handleSubmit = react.useCallback(event => {
|
|
143
143
|
var _abortControllerRef$c2, _lastAsyncResultRef$c;
|
|
144
144
|
var abortController = new AbortController();
|
|
@@ -172,9 +172,8 @@ function useConform(formRef, options) {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// Override submission value if the last intended value is not applied yet (i.e. batch updates)
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
submission.payload = (_lastIntentedValueRef = lastIntentedValueRef.current) !== null && _lastIntentedValueRef !== void 0 ? _lastIntentedValueRef : {};
|
|
175
|
+
if (lastIntentedValueRef.current != null) {
|
|
176
|
+
submission.payload = lastIntentedValueRef.current;
|
|
178
177
|
}
|
|
179
178
|
var intendedValue = intent.applyIntent(submission);
|
|
180
179
|
|
|
@@ -432,7 +431,7 @@ function useForm(options) {
|
|
|
432
431
|
* function ErrorSummary() {
|
|
433
432
|
* const form = useFormMetadata();
|
|
434
433
|
*
|
|
435
|
-
* if (
|
|
434
|
+
* if (form.valid) return null;
|
|
436
435
|
*
|
|
437
436
|
* return (
|
|
438
437
|
* <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
|
package/dist/future/hooks.mjs
CHANGED
|
@@ -123,7 +123,7 @@ function useConform(formRef, options) {
|
|
|
123
123
|
}
|
|
124
124
|
}, [formRef, state.resetKey]);
|
|
125
125
|
useEffect(() => {
|
|
126
|
-
if (!state.
|
|
126
|
+
if (!state.clientIntendedValue) {
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
var formElement = getFormElement(formRef);
|
|
@@ -132,9 +132,9 @@ function useConform(formRef, options) {
|
|
|
132
132
|
console.error('Failed to update form value; No form element found');
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
|
-
updateFormValue(formElement, state.
|
|
135
|
+
updateFormValue(formElement, state.clientIntendedValue, optionsRef.current.serialize);
|
|
136
136
|
lastIntentedValueRef.current = undefined;
|
|
137
|
-
}, [formRef, state.
|
|
137
|
+
}, [formRef, state.clientIntendedValue, optionsRef]);
|
|
138
138
|
var handleSubmit = useCallback(event => {
|
|
139
139
|
var _abortControllerRef$c2, _lastAsyncResultRef$c;
|
|
140
140
|
var abortController = new AbortController();
|
|
@@ -168,9 +168,8 @@ function useConform(formRef, options) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// Override submission value if the last intended value is not applied yet (i.e. batch updates)
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
submission.payload = (_lastIntentedValueRef = lastIntentedValueRef.current) !== null && _lastIntentedValueRef !== void 0 ? _lastIntentedValueRef : {};
|
|
171
|
+
if (lastIntentedValueRef.current != null) {
|
|
172
|
+
submission.payload = lastIntentedValueRef.current;
|
|
174
173
|
}
|
|
175
174
|
var intendedValue = applyIntent(submission);
|
|
176
175
|
|
|
@@ -428,7 +427,7 @@ function useForm(options) {
|
|
|
428
427
|
* function ErrorSummary() {
|
|
429
428
|
* const form = useFormMetadata();
|
|
430
429
|
*
|
|
431
|
-
* if (
|
|
430
|
+
* if (form.valid) return null;
|
|
432
431
|
*
|
|
433
432
|
* return (
|
|
434
433
|
* <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
|
package/dist/future/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export type { FormValue,
|
|
1
|
+
export type { FormError, FormValue, Submission, SubmissionResult, } from '@conform-to/dom/future';
|
|
2
2
|
export { parseSubmission, report, isDirty } from '@conform-to/dom/future';
|
|
3
|
-
export type { Control, FormOptions, Fieldset,
|
|
3
|
+
export type { Control, DefaultValue, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, FieldName, Fieldset, IntentDispatcher, } from './types';
|
|
4
4
|
export { FormProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
|
|
5
|
+
export { memoize } from './memoize';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/future/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var future = require('@conform-to/dom/future');
|
|
6
6
|
var hooks = require('./hooks.js');
|
|
7
|
+
var memoize = require('./memoize.js');
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
|
|
@@ -26,3 +27,4 @@ exports.useForm = hooks.useForm;
|
|
|
26
27
|
exports.useFormData = hooks.useFormData;
|
|
27
28
|
exports.useFormMetadata = hooks.useFormMetadata;
|
|
28
29
|
exports.useIntent = hooks.useIntent;
|
|
30
|
+
exports.memoize = memoize.memoize;
|
package/dist/future/index.mjs
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A memoized function with cache clearing capability.
|
|
3
|
+
*/
|
|
4
|
+
export type Memoized<T extends (...args: any) => any> = {
|
|
5
|
+
(this: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T>;
|
|
6
|
+
/** Clears the memoization cache */
|
|
7
|
+
clearCache: () => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Default equality check that compares arguments using Object.is().
|
|
11
|
+
*
|
|
12
|
+
* @param prevArgs - Previous function arguments
|
|
13
|
+
* @param nextArgs - Current function arguments
|
|
14
|
+
* @returns True if all arguments are equal
|
|
15
|
+
*/
|
|
16
|
+
export declare function defaultEqualityCheck(prevArgs: any[], nextArgs: any[]): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Memoizes function calls, caching only the most recent result to prevent redundant async validations.
|
|
19
|
+
*
|
|
20
|
+
* Built-in implementation based on memoize-one with enhanced async support.
|
|
21
|
+
* Can be replaced with other memoization libraries if needed.
|
|
22
|
+
*
|
|
23
|
+
* @param fn - The function to memoize
|
|
24
|
+
* @param isEqual - Custom equality function to compare arguments (defaults to shallow comparison)
|
|
25
|
+
* @returns Memoized function with cache clearing capability
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // Async validation with API call
|
|
30
|
+
* const validateUsername = useMemo(
|
|
31
|
+
* () => memoize(async function isUnique(username: string) {
|
|
32
|
+
* const response = await fetch(`/api/users/${username}`);
|
|
33
|
+
* return response.ok ? null : ['Username is already taken'];
|
|
34
|
+
* }),
|
|
35
|
+
* []
|
|
36
|
+
* );
|
|
37
|
+
*
|
|
38
|
+
* // Usage in form validation
|
|
39
|
+
* async onValidate({ payload, error }) {
|
|
40
|
+
* if (payload.username && !error.fieldErrors.username) {
|
|
41
|
+
* const messages = await validateUsername(value.username);
|
|
42
|
+
* if (messages) error.fieldErrors.username = messages;
|
|
43
|
+
* }
|
|
44
|
+
* return error;
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function memoize<T extends (...args: any) => any>(fn: T, isEqual?: (prevArgs: Parameters<T>, nextArgs: Parameters<T>) => boolean): Memoized<T>;
|
|
49
|
+
//# sourceMappingURL=memoize.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A memoized function with cache clearing capability.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default equality check that compares arguments using Object.is().
|
|
11
|
+
*
|
|
12
|
+
* @param prevArgs - Previous function arguments
|
|
13
|
+
* @param nextArgs - Current function arguments
|
|
14
|
+
* @returns True if all arguments are equal
|
|
15
|
+
*/
|
|
16
|
+
function defaultEqualityCheck(prevArgs, nextArgs) {
|
|
17
|
+
if (prevArgs.length !== nextArgs.length) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
for (var i = 0; i < prevArgs.length; i++) {
|
|
21
|
+
if (!Object.is(prevArgs[i], nextArgs[i])) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Memoizes function calls, caching only the most recent result to prevent redundant async validations.
|
|
30
|
+
*
|
|
31
|
+
* Built-in implementation based on memoize-one with enhanced async support.
|
|
32
|
+
* Can be replaced with other memoization libraries if needed.
|
|
33
|
+
*
|
|
34
|
+
* @param fn - The function to memoize
|
|
35
|
+
* @param isEqual - Custom equality function to compare arguments (defaults to shallow comparison)
|
|
36
|
+
* @returns Memoized function with cache clearing capability
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Async validation with API call
|
|
41
|
+
* const validateUsername = useMemo(
|
|
42
|
+
* () => memoize(async function isUnique(username: string) {
|
|
43
|
+
* const response = await fetch(`/api/users/${username}`);
|
|
44
|
+
* return response.ok ? null : ['Username is already taken'];
|
|
45
|
+
* }),
|
|
46
|
+
* []
|
|
47
|
+
* );
|
|
48
|
+
*
|
|
49
|
+
* // Usage in form validation
|
|
50
|
+
* async onValidate({ payload, error }) {
|
|
51
|
+
* if (payload.username && !error.fieldErrors.username) {
|
|
52
|
+
* const messages = await validateUsername(value.username);
|
|
53
|
+
* if (messages) error.fieldErrors.username = messages;
|
|
54
|
+
* }
|
|
55
|
+
* return error;
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
function memoize(fn) {
|
|
60
|
+
var isEqual = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;
|
|
61
|
+
var cache = null;
|
|
62
|
+
function memoized() {
|
|
63
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
64
|
+
args[_key] = arguments[_key];
|
|
65
|
+
}
|
|
66
|
+
// Check if new arguments match last arguments including the context (this)
|
|
67
|
+
if (cache && cache.this === this && isEqual(cache.args, args)) {
|
|
68
|
+
return cache.result;
|
|
69
|
+
}
|
|
70
|
+
var result = fn.apply(this, args);
|
|
71
|
+
if (result instanceof Promise) {
|
|
72
|
+
result = result.catch(e => {
|
|
73
|
+
// If the promise is rejected, clear the cache so that the next call will re-invoke fn
|
|
74
|
+
cache = null;
|
|
75
|
+
|
|
76
|
+
// Re-throw the exception so that it can be handled by the caller
|
|
77
|
+
throw e;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Update the cache
|
|
82
|
+
cache = {
|
|
83
|
+
this: this,
|
|
84
|
+
args,
|
|
85
|
+
result
|
|
86
|
+
};
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
memoized.clearCache = function clearCache() {
|
|
90
|
+
cache = null;
|
|
91
|
+
};
|
|
92
|
+
return memoized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exports.defaultEqualityCheck = defaultEqualityCheck;
|
|
96
|
+
exports.memoize = memoize;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A memoized function with cache clearing capability.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default equality check that compares arguments using Object.is().
|
|
7
|
+
*
|
|
8
|
+
* @param prevArgs - Previous function arguments
|
|
9
|
+
* @param nextArgs - Current function arguments
|
|
10
|
+
* @returns True if all arguments are equal
|
|
11
|
+
*/
|
|
12
|
+
function defaultEqualityCheck(prevArgs, nextArgs) {
|
|
13
|
+
if (prevArgs.length !== nextArgs.length) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
for (var i = 0; i < prevArgs.length; i++) {
|
|
17
|
+
if (!Object.is(prevArgs[i], nextArgs[i])) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Memoizes function calls, caching only the most recent result to prevent redundant async validations.
|
|
26
|
+
*
|
|
27
|
+
* Built-in implementation based on memoize-one with enhanced async support.
|
|
28
|
+
* Can be replaced with other memoization libraries if needed.
|
|
29
|
+
*
|
|
30
|
+
* @param fn - The function to memoize
|
|
31
|
+
* @param isEqual - Custom equality function to compare arguments (defaults to shallow comparison)
|
|
32
|
+
* @returns Memoized function with cache clearing capability
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // Async validation with API call
|
|
37
|
+
* const validateUsername = useMemo(
|
|
38
|
+
* () => memoize(async function isUnique(username: string) {
|
|
39
|
+
* const response = await fetch(`/api/users/${username}`);
|
|
40
|
+
* return response.ok ? null : ['Username is already taken'];
|
|
41
|
+
* }),
|
|
42
|
+
* []
|
|
43
|
+
* );
|
|
44
|
+
*
|
|
45
|
+
* // Usage in form validation
|
|
46
|
+
* async onValidate({ payload, error }) {
|
|
47
|
+
* if (payload.username && !error.fieldErrors.username) {
|
|
48
|
+
* const messages = await validateUsername(value.username);
|
|
49
|
+
* if (messages) error.fieldErrors.username = messages;
|
|
50
|
+
* }
|
|
51
|
+
* return error;
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function memoize(fn) {
|
|
56
|
+
var isEqual = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;
|
|
57
|
+
var cache = null;
|
|
58
|
+
function memoized() {
|
|
59
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
60
|
+
args[_key] = arguments[_key];
|
|
61
|
+
}
|
|
62
|
+
// Check if new arguments match last arguments including the context (this)
|
|
63
|
+
if (cache && cache.this === this && isEqual(cache.args, args)) {
|
|
64
|
+
return cache.result;
|
|
65
|
+
}
|
|
66
|
+
var result = fn.apply(this, args);
|
|
67
|
+
if (result instanceof Promise) {
|
|
68
|
+
result = result.catch(e => {
|
|
69
|
+
// If the promise is rejected, clear the cache so that the next call will re-invoke fn
|
|
70
|
+
cache = null;
|
|
71
|
+
|
|
72
|
+
// Re-throw the exception so that it can be handled by the caller
|
|
73
|
+
throw e;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update the cache
|
|
78
|
+
cache = {
|
|
79
|
+
this: this,
|
|
80
|
+
args,
|
|
81
|
+
result
|
|
82
|
+
};
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
memoized.clearCache = function clearCache() {
|
|
86
|
+
cache = null;
|
|
87
|
+
};
|
|
88
|
+
return memoized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { defaultEqualityCheck, memoize };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** The Standard Schema interface. */
|
|
2
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
3
|
+
/** The Standard Schema properties. */
|
|
4
|
+
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
|
|
5
|
+
}
|
|
6
|
+
export declare namespace StandardSchemaV1 {
|
|
7
|
+
/** The Standard Schema properties interface. */
|
|
8
|
+
interface Props<Input = unknown, Output = Input> {
|
|
9
|
+
/** The version number of the standard. */
|
|
10
|
+
readonly version: 1;
|
|
11
|
+
/** The vendor name of the schema library. */
|
|
12
|
+
readonly vendor: string;
|
|
13
|
+
/** Validates unknown input values. */
|
|
14
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
15
|
+
/** Inferred types associated with the schema. */
|
|
16
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The result interface of the validate function. */
|
|
19
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
20
|
+
/** The result interface if validation succeeds. */
|
|
21
|
+
interface SuccessResult<Output> {
|
|
22
|
+
/** The typed output value. */
|
|
23
|
+
readonly value: Output;
|
|
24
|
+
/** The non-existent issues. */
|
|
25
|
+
readonly issues?: undefined;
|
|
26
|
+
}
|
|
27
|
+
/** The result interface if validation fails. */
|
|
28
|
+
interface FailureResult {
|
|
29
|
+
/** The issues of failed validation. */
|
|
30
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
31
|
+
}
|
|
32
|
+
/** The issue interface of the failure output. */
|
|
33
|
+
interface Issue {
|
|
34
|
+
/** The error message of the issue. */
|
|
35
|
+
readonly message: string;
|
|
36
|
+
/** The path of the issue, if any. */
|
|
37
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** The path segment interface of the issue. */
|
|
40
|
+
interface PathSegment {
|
|
41
|
+
/** The key representing a path segment. */
|
|
42
|
+
readonly key: PropertyKey;
|
|
43
|
+
}
|
|
44
|
+
/** The Standard Schema types interface. */
|
|
45
|
+
interface Types<Input = unknown, Output = Input> {
|
|
46
|
+
/** The input type of the schema. */
|
|
47
|
+
readonly input: Input;
|
|
48
|
+
/** The output type of the schema. */
|
|
49
|
+
readonly output: Output;
|
|
50
|
+
}
|
|
51
|
+
/** Infers the input type of a Standard Schema. */
|
|
52
|
+
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];
|
|
53
|
+
/** Infers the output type of a Standard Schema. */
|
|
54
|
+
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=standard-schema.d.ts.map
|
package/dist/future/state.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ValidationAttributes, type Serialize } from '@conform-to/dom/future';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DefaultMetadata, FieldMetadata, FieldName, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, ActionHandler } from './types';
|
|
3
3
|
export declare function initializeState<ErrorShape>(): FormState<ErrorShape>;
|
|
4
4
|
/**
|
|
5
5
|
* Updates form state based on action type:
|
|
@@ -25,6 +25,7 @@ export declare function getDefaultListKey(prefix: string, initialValue: Record<s
|
|
|
25
25
|
export declare function getListKey(context: FormContext<any>, name: string): string[];
|
|
26
26
|
export declare function getErrors<ErrorShape>(state: FormState<ErrorShape>, name?: string): ErrorShape[] | undefined;
|
|
27
27
|
export declare function getFieldErrors<ErrorShape>(state: FormState<ErrorShape>, name?: string): Record<string, ErrorShape[]>;
|
|
28
|
+
export declare function isValid(state: FormState<any>, name?: string): boolean;
|
|
28
29
|
/**
|
|
29
30
|
* Gets validation constraint for a field, with fallback to parent array patterns.
|
|
30
31
|
* e.g. "array[0].key" falls back to "array[].key" if specific constraint not found.
|
|
@@ -32,26 +33,26 @@ export declare function getFieldErrors<ErrorShape>(state: FormState<ErrorShape>,
|
|
|
32
33
|
export declare function getConstraint(context: FormContext<any>, name: string): ValidationAttributes | undefined;
|
|
33
34
|
export declare function getFormMetadata<ErrorShape>(context: FormContext<ErrorShape>, options: {
|
|
34
35
|
serialize: Serialize;
|
|
35
|
-
}): FormMetadata<ErrorShape,
|
|
36
|
+
}): FormMetadata<ErrorShape, DefaultMetadata<ErrorShape>>;
|
|
36
37
|
export declare function getField<FieldShape, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
|
|
37
38
|
name: FieldName<FieldShape>;
|
|
38
39
|
serialize: Serialize;
|
|
39
40
|
key?: string;
|
|
40
|
-
}):
|
|
41
|
+
}): FieldMetadata<FieldShape, DefaultMetadata<ErrorShape>>;
|
|
41
42
|
/**
|
|
42
43
|
* Creates a proxy that dynamically generates field objects when properties are accessed.
|
|
43
44
|
*/
|
|
44
45
|
export declare function getFieldset<FieldShape = Record<string, any>, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
|
|
45
46
|
name?: FieldName<FieldShape>;
|
|
46
47
|
serialize: Serialize;
|
|
47
|
-
}): Fieldset<FieldShape,
|
|
48
|
+
}): Fieldset<FieldShape, DefaultMetadata<ErrorShape>>;
|
|
48
49
|
/**
|
|
49
50
|
* Creates an array of field objects for list/array inputs
|
|
50
51
|
*/
|
|
51
52
|
export declare function getFieldList<FieldShape = Array<any>, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
|
|
52
53
|
name: FieldName<FieldShape>;
|
|
53
54
|
serialize: Serialize;
|
|
54
|
-
}):
|
|
55
|
+
}): FieldMetadata<[
|
|
55
56
|
FieldShape
|
|
56
|
-
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown,
|
|
57
|
+
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, DefaultMetadata<ErrorShape>>[];
|
|
57
58
|
//# sourceMappingURL=state.d.ts.map
|
package/dist/future/state.js
CHANGED
|
@@ -10,8 +10,8 @@ function initializeState() {
|
|
|
10
10
|
return {
|
|
11
11
|
resetKey: util.generateUniqueKey(),
|
|
12
12
|
listKeys: {},
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
clientIntendedValue: null,
|
|
14
|
+
serverIntendedValue: null,
|
|
15
15
|
serverError: null,
|
|
16
16
|
clientError: null,
|
|
17
17
|
touchedFields: []
|
|
@@ -33,20 +33,20 @@ function updateState(state, action) {
|
|
|
33
33
|
|
|
34
34
|
// Apply the form error and intended value from the result first
|
|
35
35
|
state = action.type === 'client' ? util.merge(state, {
|
|
36
|
-
|
|
36
|
+
clientIntendedValue: (_action$intendedValue2 = action.intendedValue) !== null && _action$intendedValue2 !== void 0 ? _action$intendedValue2 : state.clientIntendedValue,
|
|
37
|
+
serverIntendedValue: action.intendedValue ? null : state.serverIntendedValue,
|
|
37
38
|
// Update client error only if the error is different from the previous one to minimize unnecessary re-renders
|
|
38
39
|
clientError: typeof action.error !== 'undefined' && !future.deepEqual(state.clientError, action.error) ? action.error : state.clientError,
|
|
39
40
|
// Reset server error if form value is changed
|
|
40
|
-
serverError: typeof action.error !== 'undefined' && !future.deepEqual(state.
|
|
41
|
+
serverError: typeof action.error !== 'undefined' && !future.deepEqual(state.serverIntendedValue, value) ? null : state.serverError
|
|
41
42
|
}) : util.merge(state, {
|
|
42
|
-
intendedValue: action.type === 'initialize' ? value : state.intendedValue,
|
|
43
43
|
// Clear client error to avoid showing stale errors
|
|
44
44
|
clientError: null,
|
|
45
45
|
// Update server error if the error is defined.
|
|
46
46
|
// There is no need to check if the error is different as we are updating other states as well
|
|
47
47
|
serverError: typeof action.error !== 'undefined' ? action.error : state.serverError,
|
|
48
48
|
// Keep track of the value that the serverError is based on
|
|
49
|
-
|
|
49
|
+
serverIntendedValue: !future.deepEqual(state.serverIntendedValue, value) ? value : state.serverIntendedValue
|
|
50
50
|
});
|
|
51
51
|
if (action.type !== 'server' && typeof action.intent !== 'undefined') {
|
|
52
52
|
var _action$intent, _action$ctx$handlers;
|
|
@@ -70,19 +70,19 @@ function updateState(state, action) {
|
|
|
70
70
|
return state;
|
|
71
71
|
}
|
|
72
72
|
function getDefaultValue(context, name) {
|
|
73
|
-
var _ref, _context$state$
|
|
73
|
+
var _ref, _context$state$server;
|
|
74
74
|
var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize;
|
|
75
|
-
var value = future.getValueAtPath((_ref = (_context$state$
|
|
75
|
+
var value = future.getValueAtPath((_ref = (_context$state$server = context.state.serverIntendedValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.clientIntendedValue) !== null && _ref !== void 0 ? _ref : context.defaultValue, name);
|
|
76
76
|
var serializedValue = serialize(value);
|
|
77
77
|
if (typeof serializedValue === 'string') {
|
|
78
78
|
return serializedValue;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
function getDefaultOptions(context, name) {
|
|
82
|
-
var _ref2, _context$state$
|
|
82
|
+
var _ref2, _context$state$server2;
|
|
83
83
|
var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize;
|
|
84
|
-
var value = future.getValueAtPath((_ref2 = (_context$state$
|
|
85
|
-
var serializedValue =
|
|
84
|
+
var value = future.getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverIntendedValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.clientIntendedValue) !== null && _ref2 !== void 0 ? _ref2 : context.defaultValue, name);
|
|
85
|
+
var serializedValue = serialize(value);
|
|
86
86
|
if (Array.isArray(serializedValue) && serializedValue.every(item => typeof item === 'string')) {
|
|
87
87
|
return serializedValue;
|
|
88
88
|
}
|
|
@@ -91,10 +91,10 @@ function getDefaultOptions(context, name) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
function isDefaultChecked(context, name) {
|
|
94
|
-
var _ref3, _context$state$
|
|
94
|
+
var _ref3, _context$state$server3;
|
|
95
95
|
var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize;
|
|
96
|
-
var value = future.getValueAtPath((_ref3 = (_context$state$
|
|
97
|
-
var serializedValue =
|
|
96
|
+
var value = future.getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverIntendedValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.clientIntendedValue) !== null && _ref3 !== void 0 ? _ref3 : context.defaultValue, name);
|
|
97
|
+
var serializedValue = serialize(value);
|
|
98
98
|
return serializedValue === 'on';
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -116,8 +116,8 @@ function getDefaultListKey(prefix, initialValue, name) {
|
|
|
116
116
|
return util.getArrayAtPath(initialValue, name).map((_, index) => "".concat(prefix, "-").concat(future.appendPathSegment(name, index)));
|
|
117
117
|
}
|
|
118
118
|
function getListKey(context, name) {
|
|
119
|
-
var _context$state$listKe, _context$state$listKe2, _context$state$
|
|
120
|
-
return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_context$state$
|
|
119
|
+
var _context$state$listKe, _context$state$listKe2, _ref4, _context$state$server4;
|
|
120
|
+
return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverIntendedValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.clientIntendedValue) !== null && _ref4 !== void 0 ? _ref4 : context.defaultValue, name);
|
|
121
121
|
}
|
|
122
122
|
function getErrors(state, name) {
|
|
123
123
|
var _state$serverError;
|
|
@@ -131,19 +131,53 @@ function getErrors(state, name) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
function getFieldErrors(state, name) {
|
|
134
|
+
var _state$serverError2;
|
|
134
135
|
var result = {};
|
|
136
|
+
var error = (_state$serverError2 = state.serverError) !== null && _state$serverError2 !== void 0 ? _state$serverError2 : state.clientError;
|
|
137
|
+
if (error) {
|
|
138
|
+
var basePath = future.getPathSegments(name);
|
|
139
|
+
for (var field of Object.keys(error.fieldErrors)) {
|
|
140
|
+
var relativePath = future.getRelativePath(field, basePath);
|
|
141
|
+
|
|
142
|
+
// Only include errors for specified field's children
|
|
143
|
+
if (!relativePath || relativePath.length === 0) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
var _error = getErrors(state, field);
|
|
147
|
+
if (typeof _error !== 'undefined') {
|
|
148
|
+
result[future.formatPathSegments(relativePath)] = _error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
function isValid(state, name) {
|
|
155
|
+
var _state$serverError3;
|
|
156
|
+
var error = (_state$serverError3 = state.serverError) !== null && _state$serverError3 !== void 0 ? _state$serverError3 : state.clientError;
|
|
157
|
+
|
|
158
|
+
// If there is no error, it must be valid
|
|
159
|
+
if (!error) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
135
162
|
var basePath = future.getPathSegments(name);
|
|
136
|
-
for (var field of
|
|
137
|
-
|
|
138
|
-
if (
|
|
163
|
+
for (var field of Object.keys(error.fieldErrors)) {
|
|
164
|
+
// When checking a specific field, only check that field and its children
|
|
165
|
+
if (name && !future.getRelativePath(field, basePath)) {
|
|
139
166
|
continue;
|
|
140
167
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
|
|
169
|
+
// If the field is not touched, we don't consider its error
|
|
170
|
+
var _error2 = getErrors(state, field);
|
|
171
|
+
if (_error2) {
|
|
172
|
+
return false;
|
|
144
173
|
}
|
|
145
174
|
}
|
|
146
|
-
|
|
175
|
+
|
|
176
|
+
// Make sure there is no form error when checking the whole form
|
|
177
|
+
if (!name) {
|
|
178
|
+
return !getErrors(state);
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
147
181
|
}
|
|
148
182
|
|
|
149
183
|
/**
|
|
@@ -189,7 +223,7 @@ function getFormMetadata(context, options) {
|
|
|
189
223
|
return isTouched(context.state);
|
|
190
224
|
},
|
|
191
225
|
get valid() {
|
|
192
|
-
return
|
|
226
|
+
return isValid(context.state);
|
|
193
227
|
},
|
|
194
228
|
get invalid() {
|
|
195
229
|
return !this.valid;
|
|
@@ -248,7 +282,7 @@ function getField(context, options) {
|
|
|
248
282
|
return isTouched(context.state, options.name);
|
|
249
283
|
},
|
|
250
284
|
get valid() {
|
|
251
|
-
return
|
|
285
|
+
return isValid(context.state, options.name);
|
|
252
286
|
},
|
|
253
287
|
get invalid() {
|
|
254
288
|
return !this.valid;
|
|
@@ -315,4 +349,5 @@ exports.getListKey = getListKey;
|
|
|
315
349
|
exports.initializeState = initializeState;
|
|
316
350
|
exports.isDefaultChecked = isDefaultChecked;
|
|
317
351
|
exports.isTouched = isTouched;
|
|
352
|
+
exports.isValid = isValid;
|
|
318
353
|
exports.updateState = updateState;
|
package/dist/future/state.mjs
CHANGED
|
@@ -6,8 +6,8 @@ function initializeState() {
|
|
|
6
6
|
return {
|
|
7
7
|
resetKey: generateUniqueKey(),
|
|
8
8
|
listKeys: {},
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
clientIntendedValue: null,
|
|
10
|
+
serverIntendedValue: null,
|
|
11
11
|
serverError: null,
|
|
12
12
|
clientError: null,
|
|
13
13
|
touchedFields: []
|
|
@@ -29,20 +29,20 @@ function updateState(state, action) {
|
|
|
29
29
|
|
|
30
30
|
// Apply the form error and intended value from the result first
|
|
31
31
|
state = action.type === 'client' ? merge(state, {
|
|
32
|
-
|
|
32
|
+
clientIntendedValue: (_action$intendedValue2 = action.intendedValue) !== null && _action$intendedValue2 !== void 0 ? _action$intendedValue2 : state.clientIntendedValue,
|
|
33
|
+
serverIntendedValue: action.intendedValue ? null : state.serverIntendedValue,
|
|
33
34
|
// Update client error only if the error is different from the previous one to minimize unnecessary re-renders
|
|
34
35
|
clientError: typeof action.error !== 'undefined' && !deepEqual(state.clientError, action.error) ? action.error : state.clientError,
|
|
35
36
|
// Reset server error if form value is changed
|
|
36
|
-
serverError: typeof action.error !== 'undefined' && !deepEqual(state.
|
|
37
|
+
serverError: typeof action.error !== 'undefined' && !deepEqual(state.serverIntendedValue, value) ? null : state.serverError
|
|
37
38
|
}) : merge(state, {
|
|
38
|
-
intendedValue: action.type === 'initialize' ? value : state.intendedValue,
|
|
39
39
|
// Clear client error to avoid showing stale errors
|
|
40
40
|
clientError: null,
|
|
41
41
|
// Update server error if the error is defined.
|
|
42
42
|
// There is no need to check if the error is different as we are updating other states as well
|
|
43
43
|
serverError: typeof action.error !== 'undefined' ? action.error : state.serverError,
|
|
44
44
|
// Keep track of the value that the serverError is based on
|
|
45
|
-
|
|
45
|
+
serverIntendedValue: !deepEqual(state.serverIntendedValue, value) ? value : state.serverIntendedValue
|
|
46
46
|
});
|
|
47
47
|
if (action.type !== 'server' && typeof action.intent !== 'undefined') {
|
|
48
48
|
var _action$intent, _action$ctx$handlers;
|
|
@@ -66,19 +66,19 @@ function updateState(state, action) {
|
|
|
66
66
|
return state;
|
|
67
67
|
}
|
|
68
68
|
function getDefaultValue(context, name) {
|
|
69
|
-
var _ref, _context$state$
|
|
69
|
+
var _ref, _context$state$server;
|
|
70
70
|
var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize;
|
|
71
|
-
var value = getValueAtPath((_ref = (_context$state$
|
|
71
|
+
var value = getValueAtPath((_ref = (_context$state$server = context.state.serverIntendedValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.clientIntendedValue) !== null && _ref !== void 0 ? _ref : context.defaultValue, name);
|
|
72
72
|
var serializedValue = serialize$1(value);
|
|
73
73
|
if (typeof serializedValue === 'string') {
|
|
74
74
|
return serializedValue;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
function getDefaultOptions(context, name) {
|
|
78
|
-
var _ref2, _context$state$
|
|
78
|
+
var _ref2, _context$state$server2;
|
|
79
79
|
var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize;
|
|
80
|
-
var value = getValueAtPath((_ref2 = (_context$state$
|
|
81
|
-
var serializedValue =
|
|
80
|
+
var value = getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverIntendedValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.clientIntendedValue) !== null && _ref2 !== void 0 ? _ref2 : context.defaultValue, name);
|
|
81
|
+
var serializedValue = serialize$1(value);
|
|
82
82
|
if (Array.isArray(serializedValue) && serializedValue.every(item => typeof item === 'string')) {
|
|
83
83
|
return serializedValue;
|
|
84
84
|
}
|
|
@@ -87,10 +87,10 @@ function getDefaultOptions(context, name) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
function isDefaultChecked(context, name) {
|
|
90
|
-
var _ref3, _context$state$
|
|
90
|
+
var _ref3, _context$state$server3;
|
|
91
91
|
var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize;
|
|
92
|
-
var value = getValueAtPath((_ref3 = (_context$state$
|
|
93
|
-
var serializedValue =
|
|
92
|
+
var value = getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverIntendedValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.clientIntendedValue) !== null && _ref3 !== void 0 ? _ref3 : context.defaultValue, name);
|
|
93
|
+
var serializedValue = serialize$1(value);
|
|
94
94
|
return serializedValue === 'on';
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -112,8 +112,8 @@ function getDefaultListKey(prefix, initialValue, name) {
|
|
|
112
112
|
return getArrayAtPath(initialValue, name).map((_, index) => "".concat(prefix, "-").concat(appendPathSegment(name, index)));
|
|
113
113
|
}
|
|
114
114
|
function getListKey(context, name) {
|
|
115
|
-
var _context$state$listKe, _context$state$listKe2, _context$state$
|
|
116
|
-
return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_context$state$
|
|
115
|
+
var _context$state$listKe, _context$state$listKe2, _ref4, _context$state$server4;
|
|
116
|
+
return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverIntendedValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.clientIntendedValue) !== null && _ref4 !== void 0 ? _ref4 : context.defaultValue, name);
|
|
117
117
|
}
|
|
118
118
|
function getErrors(state, name) {
|
|
119
119
|
var _state$serverError;
|
|
@@ -127,19 +127,53 @@ function getErrors(state, name) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
function getFieldErrors(state, name) {
|
|
130
|
+
var _state$serverError2;
|
|
130
131
|
var result = {};
|
|
132
|
+
var error = (_state$serverError2 = state.serverError) !== null && _state$serverError2 !== void 0 ? _state$serverError2 : state.clientError;
|
|
133
|
+
if (error) {
|
|
134
|
+
var basePath = getPathSegments(name);
|
|
135
|
+
for (var field of Object.keys(error.fieldErrors)) {
|
|
136
|
+
var relativePath = getRelativePath(field, basePath);
|
|
137
|
+
|
|
138
|
+
// Only include errors for specified field's children
|
|
139
|
+
if (!relativePath || relativePath.length === 0) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
var _error = getErrors(state, field);
|
|
143
|
+
if (typeof _error !== 'undefined') {
|
|
144
|
+
result[formatPathSegments(relativePath)] = _error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
function isValid(state, name) {
|
|
151
|
+
var _state$serverError3;
|
|
152
|
+
var error = (_state$serverError3 = state.serverError) !== null && _state$serverError3 !== void 0 ? _state$serverError3 : state.clientError;
|
|
153
|
+
|
|
154
|
+
// If there is no error, it must be valid
|
|
155
|
+
if (!error) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
131
158
|
var basePath = getPathSegments(name);
|
|
132
|
-
for (var field of
|
|
133
|
-
|
|
134
|
-
if (
|
|
159
|
+
for (var field of Object.keys(error.fieldErrors)) {
|
|
160
|
+
// When checking a specific field, only check that field and its children
|
|
161
|
+
if (name && !getRelativePath(field, basePath)) {
|
|
135
162
|
continue;
|
|
136
163
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
164
|
+
|
|
165
|
+
// If the field is not touched, we don't consider its error
|
|
166
|
+
var _error2 = getErrors(state, field);
|
|
167
|
+
if (_error2) {
|
|
168
|
+
return false;
|
|
140
169
|
}
|
|
141
170
|
}
|
|
142
|
-
|
|
171
|
+
|
|
172
|
+
// Make sure there is no form error when checking the whole form
|
|
173
|
+
if (!name) {
|
|
174
|
+
return !getErrors(state);
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
143
177
|
}
|
|
144
178
|
|
|
145
179
|
/**
|
|
@@ -185,7 +219,7 @@ function getFormMetadata(context, options) {
|
|
|
185
219
|
return isTouched(context.state);
|
|
186
220
|
},
|
|
187
221
|
get valid() {
|
|
188
|
-
return
|
|
222
|
+
return isValid(context.state);
|
|
189
223
|
},
|
|
190
224
|
get invalid() {
|
|
191
225
|
return !this.valid;
|
|
@@ -244,7 +278,7 @@ function getField(context, options) {
|
|
|
244
278
|
return isTouched(context.state, options.name);
|
|
245
279
|
},
|
|
246
280
|
get valid() {
|
|
247
|
-
return
|
|
281
|
+
return isValid(context.state, options.name);
|
|
248
282
|
},
|
|
249
283
|
get invalid() {
|
|
250
284
|
return !this.valid;
|
|
@@ -297,4 +331,4 @@ function getFieldList(context, options) {
|
|
|
297
331
|
});
|
|
298
332
|
}
|
|
299
333
|
|
|
300
|
-
export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, updateState };
|
|
334
|
+
export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, isValid, updateState };
|
package/dist/future/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormError, FormValue, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future';
|
|
2
|
-
import { StandardSchemaV1 } from '
|
|
2
|
+
import type { StandardSchemaV1 } from './standard-schema';
|
|
3
3
|
export type Prettify<T> = {
|
|
4
4
|
[K in keyof T]: T[K];
|
|
5
5
|
} & {};
|
|
@@ -70,10 +70,10 @@ export type DefaultValue<FormShape> = FormShape extends string | number | boolea
|
|
|
70
70
|
export type FormState<ErrorShape> = {
|
|
71
71
|
/** Unique identifier that changes on form reset to trigger reset side effects */
|
|
72
72
|
resetKey: string;
|
|
73
|
-
/** Form values from
|
|
74
|
-
|
|
75
|
-
/** Form values
|
|
76
|
-
|
|
73
|
+
/** Form values from client actions that will be synced to the DOM */
|
|
74
|
+
clientIntendedValue: Record<string, unknown> | null;
|
|
75
|
+
/** Form values from server actions, or submitted values when no server intent exists */
|
|
76
|
+
serverIntendedValue: Record<string, unknown> | null;
|
|
77
77
|
/** Validation errors from server-side processing */
|
|
78
78
|
serverError: FormError<ErrorShape> | null;
|
|
79
79
|
/** Validation errors from client-side validation */
|
|
@@ -241,7 +241,7 @@ export type Combine<T> = {
|
|
|
241
241
|
[K in keyof BaseCombine<T>]: BaseCombine<T>[K];
|
|
242
242
|
};
|
|
243
243
|
/** Field metadata object containing field state, validation attributes, and nested field access methods. */
|
|
244
|
-
export type
|
|
244
|
+
export type FieldMetadata<FieldShape, Metadata extends Record<string, unknown> = DefaultMetadata<unknown>> = Readonly<Metadata & {
|
|
245
245
|
/** Unique key for React list rendering (for array fields). */
|
|
246
246
|
key: string | undefined;
|
|
247
247
|
/** The field name path exactly as provided. */
|
|
@@ -251,17 +251,17 @@ export type Field<FieldShape, Metadata extends Record<string, unknown> = Default
|
|
|
251
251
|
FieldShape
|
|
252
252
|
] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, Metadata>;
|
|
253
253
|
/** Method to get array of fields for list/array fields under this field. */
|
|
254
|
-
getFieldList(): Array<
|
|
254
|
+
getFieldList(): Array<FieldMetadata<[
|
|
255
255
|
FieldShape
|
|
256
256
|
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, Metadata>>;
|
|
257
257
|
}>;
|
|
258
258
|
/** Fieldset object containing all form fields as properties with their respective field metadata. */
|
|
259
259
|
export type Fieldset<FieldShape, // extends Record<string, unknown>,
|
|
260
|
-
|
|
261
|
-
[Key in keyof Combine<FieldShape>]-?:
|
|
260
|
+
Metadata extends Record<string, unknown>> = {
|
|
261
|
+
[Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], Metadata>;
|
|
262
262
|
};
|
|
263
263
|
/** Form-level metadata and state object containing validation status, errors, and field access methods. */
|
|
264
|
-
export type FormMetadata<ErrorShape,
|
|
264
|
+
export type FormMetadata<ErrorShape, Metadata extends Record<string, unknown> = DefaultMetadata<ErrorShape>> = Readonly<{
|
|
265
265
|
/** Unique identifier that changes on form reset */
|
|
266
266
|
key: string;
|
|
267
267
|
/** The form's unique identifier. */
|
|
@@ -291,18 +291,18 @@ export type FormMetadata<ErrorShape, FieldMetadata extends Record<string, unknow
|
|
|
291
291
|
/** The current state of the form */
|
|
292
292
|
context: FormContext<ErrorShape>;
|
|
293
293
|
/** Method to get metadata for a specific field by name. */
|
|
294
|
-
getField<FieldShape>(name: FieldName<FieldShape>):
|
|
294
|
+
getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, Metadata>;
|
|
295
295
|
/** Method to get a fieldset object for nested object fields. */
|
|
296
296
|
getFieldset<FieldShape>(name: FieldName<FieldShape>): Fieldset<[
|
|
297
297
|
FieldShape
|
|
298
|
-
] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown,
|
|
298
|
+
] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, Metadata>;
|
|
299
299
|
/** Method to get an array of field objects for array fields. */
|
|
300
|
-
getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<
|
|
300
|
+
getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
|
|
301
301
|
FieldShape
|
|
302
|
-
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown,
|
|
302
|
+
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, Metadata>>;
|
|
303
303
|
}>;
|
|
304
304
|
/** Default field metadata object containing field state, validation attributes, and accessibility IDs. */
|
|
305
|
-
export type
|
|
305
|
+
export type DefaultMetadata<ErrorShape> = Readonly<ValidationAttributes & {
|
|
306
306
|
/** The field's unique identifier, automatically generated as {formId}-field-{fieldName}. */
|
|
307
307
|
id: string;
|
|
308
308
|
/** Auto-generated ID for associating field descriptions via aria-describedby. */
|
package/dist/future/util.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormError } from '@conform-to/dom/future';
|
|
2
|
-
import { StandardSchemaV1 } from '
|
|
2
|
+
import type { StandardSchemaV1 } from './standard-schema';
|
|
3
3
|
import { ValidateHandler, ValidateResult } from './types';
|
|
4
4
|
export declare function isUndefined(value: unknown): value is undefined;
|
|
5
5
|
export declare function isString(value: unknown): value is string;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Conform view adapter for react",
|
|
4
4
|
"homepage": "https://conform.guide",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.10.0",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"module": "./dist/index.mjs",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"url": "https://github.com/edmundhung/conform/issues"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@conform-to/dom": "1.
|
|
44
|
+
"@conform-to/dom": "1.10.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@babel/core": "^7.17.8",
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"@babel/preset-typescript": "^7.20.2",
|
|
51
51
|
"@rollup/plugin-babel": "^5.3.1",
|
|
52
52
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
|
53
|
-
"@standard-schema/spec": "^1.0.0",
|
|
54
53
|
"@types/react": "^18.2.43",
|
|
55
54
|
"@types/react-dom": "^18.3.5",
|
|
56
55
|
"react": "^18.2.0",
|