@conform-to/react 1.9.0 → 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 +11 -11
- 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 +9 -7
- package/dist/future/state.js +88 -35
- package/dist/future/state.mjs +86 -35
- package/dist/future/types.d.ts +33 -19
- package/dist/future/util.d.ts +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -7,23 +7,23 @@
|
|
|
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
|
|
|
14
|
-
|
|
14
|
+
## Getting Started
|
|
15
15
|
|
|
16
16
|
Check out the overview and tutorial at our website https://conform.guide
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Features
|
|
19
19
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
20
|
+
- Full type safety with schema field inference
|
|
21
|
+
- Standard Schema support with enhanced Zod and Valibot integration
|
|
22
|
+
- Progressive enhancement first design with built-in accessibility features
|
|
23
|
+
- Native Server Actions support for Remix and Next.js
|
|
24
|
+
- Built on web standards for flexible composition with other tools
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
## Documentation
|
|
27
27
|
|
|
28
28
|
- Validation: https://conform.guide/validation
|
|
29
29
|
- Nested object and Array: https://conform.guide/complex-structures
|
|
@@ -31,6 +31,6 @@ Check out the overview and tutorial at our website https://conform.guide
|
|
|
31
31
|
- Intent button: https://conform.guide/intent-button
|
|
32
32
|
- Accessibility Guide: https://conform.guide/accessibility
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
## Support
|
|
35
35
|
|
|
36
36
|
To report a bug, please open an issue on the repository at https://github.com/edmundhung/conform. For feature requests and questions, you can post them in the Discussions section.
|
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:
|
|
@@ -23,7 +23,9 @@ export declare function isDefaultChecked(context: FormContext<any>, name: string
|
|
|
23
23
|
export declare function isTouched(state: FormState<any>, name?: string): boolean;
|
|
24
24
|
export declare function getDefaultListKey(prefix: string, initialValue: Record<string, unknown> | null, name: string): string[];
|
|
25
25
|
export declare function getListKey(context: FormContext<any>, name: string): string[];
|
|
26
|
-
export declare function
|
|
26
|
+
export declare function getErrors<ErrorShape>(state: FormState<ErrorShape>, name?: string): ErrorShape[] | undefined;
|
|
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;
|
|
27
29
|
/**
|
|
28
30
|
* Gets validation constraint for a field, with fallback to parent array patterns.
|
|
29
31
|
* e.g. "array[0].key" falls back to "array[].key" if specific constraint not found.
|
|
@@ -31,26 +33,26 @@ export declare function getError<ErrorShape>(state: FormState<ErrorShape>, name?
|
|
|
31
33
|
export declare function getConstraint(context: FormContext<any>, name: string): ValidationAttributes | undefined;
|
|
32
34
|
export declare function getFormMetadata<ErrorShape>(context: FormContext<ErrorShape>, options: {
|
|
33
35
|
serialize: Serialize;
|
|
34
|
-
}): FormMetadata<ErrorShape,
|
|
36
|
+
}): FormMetadata<ErrorShape, DefaultMetadata<ErrorShape>>;
|
|
35
37
|
export declare function getField<FieldShape, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
|
|
36
38
|
name: FieldName<FieldShape>;
|
|
37
39
|
serialize: Serialize;
|
|
38
40
|
key?: string;
|
|
39
|
-
}):
|
|
41
|
+
}): FieldMetadata<FieldShape, DefaultMetadata<ErrorShape>>;
|
|
40
42
|
/**
|
|
41
43
|
* Creates a proxy that dynamically generates field objects when properties are accessed.
|
|
42
44
|
*/
|
|
43
45
|
export declare function getFieldset<FieldShape = Record<string, any>, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
|
|
44
46
|
name?: FieldName<FieldShape>;
|
|
45
47
|
serialize: Serialize;
|
|
46
|
-
}): Fieldset<FieldShape,
|
|
48
|
+
}): Fieldset<FieldShape, DefaultMetadata<ErrorShape>>;
|
|
47
49
|
/**
|
|
48
50
|
* Creates an array of field objects for list/array inputs
|
|
49
51
|
*/
|
|
50
52
|
export declare function getFieldList<FieldShape = Array<any>, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
|
|
51
53
|
name: FieldName<FieldShape>;
|
|
52
54
|
serialize: Serialize;
|
|
53
|
-
}):
|
|
55
|
+
}): FieldMetadata<[
|
|
54
56
|
FieldShape
|
|
55
|
-
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown,
|
|
57
|
+
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, DefaultMetadata<ErrorShape>>[];
|
|
56
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,10 +116,10 @@ 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
|
-
function
|
|
122
|
+
function getErrors(state, name) {
|
|
123
123
|
var _state$serverError;
|
|
124
124
|
var error = (_state$serverError = state.serverError) !== null && _state$serverError !== void 0 ? _state$serverError : state.clientError;
|
|
125
125
|
if (!error || !isTouched(state, name)) {
|
|
@@ -130,6 +130,55 @@ function getError(state, name) {
|
|
|
130
130
|
return errors;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
function getFieldErrors(state, name) {
|
|
134
|
+
var _state$serverError2;
|
|
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
|
+
}
|
|
162
|
+
var basePath = future.getPathSegments(name);
|
|
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)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
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;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
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;
|
|
181
|
+
}
|
|
133
182
|
|
|
134
183
|
/**
|
|
135
184
|
* Gets validation constraint for a field, with fallback to parent array patterns.
|
|
@@ -160,29 +209,24 @@ function getConstraint(context, name) {
|
|
|
160
209
|
}
|
|
161
210
|
function getFormMetadata(context, options) {
|
|
162
211
|
return {
|
|
212
|
+
key: context.state.resetKey,
|
|
163
213
|
id: context.formId,
|
|
214
|
+
errorId: "".concat(context.formId, "-form-error"),
|
|
215
|
+
descriptionId: "".concat(context.formId, "-form-description"),
|
|
164
216
|
get errors() {
|
|
165
|
-
return
|
|
217
|
+
return getErrors(context.state);
|
|
166
218
|
},
|
|
167
219
|
get fieldErrors() {
|
|
168
|
-
|
|
169
|
-
for (var name of context.state.touchedFields) {
|
|
170
|
-
if (!name) {
|
|
171
|
-
// Skip form-level errors
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
var error = getError(context.state, name);
|
|
175
|
-
if (typeof error !== 'undefined') {
|
|
176
|
-
result[name] = error;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return result;
|
|
220
|
+
return getFieldErrors(context.state);
|
|
180
221
|
},
|
|
181
222
|
get touched() {
|
|
182
223
|
return isTouched(context.state);
|
|
183
224
|
},
|
|
225
|
+
get valid() {
|
|
226
|
+
return isValid(context.state);
|
|
227
|
+
},
|
|
184
228
|
get invalid() {
|
|
185
|
-
return
|
|
229
|
+
return !this.valid;
|
|
186
230
|
},
|
|
187
231
|
props: {
|
|
188
232
|
id: context.formId,
|
|
@@ -210,12 +254,13 @@ function getFormMetadata(context, options) {
|
|
|
210
254
|
};
|
|
211
255
|
}
|
|
212
256
|
function getField(context, options) {
|
|
213
|
-
var id = "".concat(context.formId, "-").concat(options.name);
|
|
257
|
+
var id = "".concat(context.formId, "-field-").concat(options.name.replace(/[^a-zA-Z0-9._-]/g, '_'));
|
|
214
258
|
var constraint = getConstraint(context, options.name);
|
|
215
259
|
var metadata = {
|
|
216
260
|
id: id,
|
|
217
261
|
descriptionId: "".concat(id, "-description"),
|
|
218
262
|
errorId: "".concat(id, "-error"),
|
|
263
|
+
formId: context.formId,
|
|
219
264
|
required: constraint === null || constraint === void 0 ? void 0 : constraint.required,
|
|
220
265
|
minLength: constraint === null || constraint === void 0 ? void 0 : constraint.minLength,
|
|
221
266
|
maxLength: constraint === null || constraint === void 0 ? void 0 : constraint.maxLength,
|
|
@@ -236,11 +281,17 @@ function getField(context, options) {
|
|
|
236
281
|
get touched() {
|
|
237
282
|
return isTouched(context.state, options.name);
|
|
238
283
|
},
|
|
284
|
+
get valid() {
|
|
285
|
+
return isValid(context.state, options.name);
|
|
286
|
+
},
|
|
239
287
|
get invalid() {
|
|
240
|
-
return
|
|
288
|
+
return !this.valid;
|
|
241
289
|
},
|
|
242
290
|
get errors() {
|
|
243
|
-
return
|
|
291
|
+
return getErrors(context.state, options.name);
|
|
292
|
+
},
|
|
293
|
+
get fieldErrors() {
|
|
294
|
+
return getFieldErrors(context.state, options.name);
|
|
244
295
|
}
|
|
245
296
|
};
|
|
246
297
|
return Object.assign(metadata, {
|
|
@@ -288,8 +339,9 @@ exports.getConstraint = getConstraint;
|
|
|
288
339
|
exports.getDefaultListKey = getDefaultListKey;
|
|
289
340
|
exports.getDefaultOptions = getDefaultOptions;
|
|
290
341
|
exports.getDefaultValue = getDefaultValue;
|
|
291
|
-
exports.
|
|
342
|
+
exports.getErrors = getErrors;
|
|
292
343
|
exports.getField = getField;
|
|
344
|
+
exports.getFieldErrors = getFieldErrors;
|
|
293
345
|
exports.getFieldList = getFieldList;
|
|
294
346
|
exports.getFieldset = getFieldset;
|
|
295
347
|
exports.getFormMetadata = getFormMetadata;
|
|
@@ -297,4 +349,5 @@ exports.getListKey = getListKey;
|
|
|
297
349
|
exports.initializeState = initializeState;
|
|
298
350
|
exports.isDefaultChecked = isDefaultChecked;
|
|
299
351
|
exports.isTouched = isTouched;
|
|
352
|
+
exports.isValid = isValid;
|
|
300
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,10 +112,10 @@ 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
|
-
function
|
|
118
|
+
function getErrors(state, name) {
|
|
119
119
|
var _state$serverError;
|
|
120
120
|
var error = (_state$serverError = state.serverError) !== null && _state$serverError !== void 0 ? _state$serverError : state.clientError;
|
|
121
121
|
if (!error || !isTouched(state, name)) {
|
|
@@ -126,6 +126,55 @@ function getError(state, name) {
|
|
|
126
126
|
return errors;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
function getFieldErrors(state, name) {
|
|
130
|
+
var _state$serverError2;
|
|
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
|
+
}
|
|
158
|
+
var basePath = getPathSegments(name);
|
|
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)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
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;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
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;
|
|
177
|
+
}
|
|
129
178
|
|
|
130
179
|
/**
|
|
131
180
|
* Gets validation constraint for a field, with fallback to parent array patterns.
|
|
@@ -156,29 +205,24 @@ function getConstraint(context, name) {
|
|
|
156
205
|
}
|
|
157
206
|
function getFormMetadata(context, options) {
|
|
158
207
|
return {
|
|
208
|
+
key: context.state.resetKey,
|
|
159
209
|
id: context.formId,
|
|
210
|
+
errorId: "".concat(context.formId, "-form-error"),
|
|
211
|
+
descriptionId: "".concat(context.formId, "-form-description"),
|
|
160
212
|
get errors() {
|
|
161
|
-
return
|
|
213
|
+
return getErrors(context.state);
|
|
162
214
|
},
|
|
163
215
|
get fieldErrors() {
|
|
164
|
-
|
|
165
|
-
for (var name of context.state.touchedFields) {
|
|
166
|
-
if (!name) {
|
|
167
|
-
// Skip form-level errors
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
var error = getError(context.state, name);
|
|
171
|
-
if (typeof error !== 'undefined') {
|
|
172
|
-
result[name] = error;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return result;
|
|
216
|
+
return getFieldErrors(context.state);
|
|
176
217
|
},
|
|
177
218
|
get touched() {
|
|
178
219
|
return isTouched(context.state);
|
|
179
220
|
},
|
|
221
|
+
get valid() {
|
|
222
|
+
return isValid(context.state);
|
|
223
|
+
},
|
|
180
224
|
get invalid() {
|
|
181
|
-
return
|
|
225
|
+
return !this.valid;
|
|
182
226
|
},
|
|
183
227
|
props: {
|
|
184
228
|
id: context.formId,
|
|
@@ -206,12 +250,13 @@ function getFormMetadata(context, options) {
|
|
|
206
250
|
};
|
|
207
251
|
}
|
|
208
252
|
function getField(context, options) {
|
|
209
|
-
var id = "".concat(context.formId, "-").concat(options.name);
|
|
253
|
+
var id = "".concat(context.formId, "-field-").concat(options.name.replace(/[^a-zA-Z0-9._-]/g, '_'));
|
|
210
254
|
var constraint = getConstraint(context, options.name);
|
|
211
255
|
var metadata = {
|
|
212
256
|
id: id,
|
|
213
257
|
descriptionId: "".concat(id, "-description"),
|
|
214
258
|
errorId: "".concat(id, "-error"),
|
|
259
|
+
formId: context.formId,
|
|
215
260
|
required: constraint === null || constraint === void 0 ? void 0 : constraint.required,
|
|
216
261
|
minLength: constraint === null || constraint === void 0 ? void 0 : constraint.minLength,
|
|
217
262
|
maxLength: constraint === null || constraint === void 0 ? void 0 : constraint.maxLength,
|
|
@@ -232,11 +277,17 @@ function getField(context, options) {
|
|
|
232
277
|
get touched() {
|
|
233
278
|
return isTouched(context.state, options.name);
|
|
234
279
|
},
|
|
280
|
+
get valid() {
|
|
281
|
+
return isValid(context.state, options.name);
|
|
282
|
+
},
|
|
235
283
|
get invalid() {
|
|
236
|
-
return
|
|
284
|
+
return !this.valid;
|
|
237
285
|
},
|
|
238
286
|
get errors() {
|
|
239
|
-
return
|
|
287
|
+
return getErrors(context.state, options.name);
|
|
288
|
+
},
|
|
289
|
+
get fieldErrors() {
|
|
290
|
+
return getFieldErrors(context.state, options.name);
|
|
240
291
|
}
|
|
241
292
|
};
|
|
242
293
|
return Object.assign(metadata, {
|
|
@@ -280,4 +331,4 @@ function getFieldList(context, options) {
|
|
|
280
331
|
});
|
|
281
332
|
}
|
|
282
333
|
|
|
283
|
-
export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue,
|
|
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,26 +251,34 @@ 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
|
+
/** Unique identifier that changes on form reset */
|
|
266
|
+
key: string;
|
|
265
267
|
/** The form's unique identifier. */
|
|
266
268
|
id: string;
|
|
269
|
+
/** Auto-generated ID for associating form descriptions via aria-describedby. */
|
|
270
|
+
descriptionId: string;
|
|
271
|
+
/** Auto-generated ID for associating form errors via aria-describedby. */
|
|
272
|
+
errorId: string;
|
|
267
273
|
/** Whether any field in the form has been touched (through intent.validate() or the shouldValidate option). */
|
|
268
274
|
touched: boolean;
|
|
269
|
-
/** Whether the form currently has
|
|
275
|
+
/** Whether the form currently has no validation errors. */
|
|
276
|
+
valid: boolean;
|
|
277
|
+
/** @deprecated Use `.valid` instead. This was not an intentionl breaking change and would be removed in the next minor version soon */
|
|
270
278
|
invalid: boolean;
|
|
271
279
|
/** Form-level validation errors, if any exist. */
|
|
272
280
|
errors: ErrorShape[] | undefined;
|
|
273
|
-
/** Object containing
|
|
281
|
+
/** Object containing errors for all touched fields. */
|
|
274
282
|
fieldErrors: Record<string, ErrorShape[]>;
|
|
275
283
|
/** Form props object for spreading onto the <form> element. */
|
|
276
284
|
props: Readonly<{
|
|
@@ -283,24 +291,26 @@ export type FormMetadata<ErrorShape, FieldMetadata extends Record<string, unknow
|
|
|
283
291
|
/** The current state of the form */
|
|
284
292
|
context: FormContext<ErrorShape>;
|
|
285
293
|
/** Method to get metadata for a specific field by name. */
|
|
286
|
-
getField<FieldShape>(name: FieldName<FieldShape>):
|
|
294
|
+
getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, Metadata>;
|
|
287
295
|
/** Method to get a fieldset object for nested object fields. */
|
|
288
296
|
getFieldset<FieldShape>(name: FieldName<FieldShape>): Fieldset<[
|
|
289
297
|
FieldShape
|
|
290
|
-
] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown,
|
|
298
|
+
] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, Metadata>;
|
|
291
299
|
/** Method to get an array of field objects for array fields. */
|
|
292
|
-
getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<
|
|
300
|
+
getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
|
|
293
301
|
FieldShape
|
|
294
|
-
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown,
|
|
302
|
+
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, Metadata>>;
|
|
295
303
|
}>;
|
|
296
304
|
/** Default field metadata object containing field state, validation attributes, and accessibility IDs. */
|
|
297
|
-
export type
|
|
298
|
-
/** The field's unique identifier, automatically generated as {formId}-{fieldName}. */
|
|
305
|
+
export type DefaultMetadata<ErrorShape> = Readonly<ValidationAttributes & {
|
|
306
|
+
/** The field's unique identifier, automatically generated as {formId}-field-{fieldName}. */
|
|
299
307
|
id: string;
|
|
300
308
|
/** Auto-generated ID for associating field descriptions via aria-describedby. */
|
|
301
309
|
descriptionId: string;
|
|
302
310
|
/** Auto-generated ID for associating field errors via aria-describedby. */
|
|
303
311
|
errorId: string;
|
|
312
|
+
/** The form's unique identifier for associating field via the `form` attribute. */
|
|
313
|
+
formId: string;
|
|
304
314
|
/** The field's default value as a string. */
|
|
305
315
|
defaultValue: string | undefined;
|
|
306
316
|
/** Default selected options for multi-select fields or checkbox group. */
|
|
@@ -309,10 +319,14 @@ export type DefaultFieldMetadata<ErrorShape> = Readonly<ValidationAttributes & {
|
|
|
309
319
|
defaultChecked: boolean | undefined;
|
|
310
320
|
/** Whether this field has been touched (through intent.validate() or the shouldValidate option). */
|
|
311
321
|
touched: boolean;
|
|
312
|
-
/** Whether this field currently has validation errors. */
|
|
322
|
+
/** Whether this field currently has no validation errors. */
|
|
323
|
+
valid: boolean;
|
|
324
|
+
/** @deprecated Use `.valid` instead. This was not an intentionl breaking change and would be removed in the next minor version soon */
|
|
313
325
|
invalid: boolean;
|
|
314
326
|
/** Array of validation error messages for this field. */
|
|
315
327
|
errors: ErrorShape[] | undefined;
|
|
328
|
+
/** Object containing errors for all touched subfields. */
|
|
329
|
+
fieldErrors: Record<string, ErrorShape[]>;
|
|
316
330
|
}>;
|
|
317
331
|
export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
|
|
318
332
|
error: FormError<ErrorShape> | null;
|
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",
|