@conform-to/react 1.9.1 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.9.1 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.10.1 / 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
 
@@ -1,6 +1,7 @@
1
1
  import { type Serialize, type SubmissionResult, serialize } from '@conform-to/dom/future';
2
2
  import { useEffect } from 'react';
3
- import type { FormContext, DefaultFieldMetadata, IntentDispatcher, FormMetadata, Fieldset, FormOptions, FieldName, Field, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef } from './types';
3
+ import type { FormContext, DefaultMetadata, IntentDispatcher, FormMetadata, Fieldset, FormOptions, FieldName, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef } from './types';
4
+ export declare const INITIAL_KEY = "INITIAL_KEY";
4
5
  export declare const FormConfig: import("react").Context<{
5
6
  intentName: string;
6
7
  observer: {
@@ -66,7 +67,7 @@ export declare function useConform<ErrorShape, Value = undefined>(formRef: FormR
66
67
  */
67
68
  export declare function useForm<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = string, Value = undefined>(options: FormOptions<FormShape, ErrorShape, Value>): {
68
69
  form: FormMetadata<ErrorShape>;
69
- fields: Fieldset<FormShape, DefaultFieldMetadata<ErrorShape>>;
70
+ fields: Fieldset<FormShape, DefaultMetadata<ErrorShape>>;
70
71
  intent: IntentDispatcher;
71
72
  };
72
73
  /**
@@ -79,7 +80,7 @@ export declare function useForm<FormShape extends Record<string, any> = Record<s
79
80
  * function ErrorSummary() {
80
81
  * const form = useFormMetadata();
81
82
  *
82
- * if (!form.invalid) return null;
83
+ * if (form.valid) return null;
83
84
  *
84
85
  * return (
85
86
  * <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
@@ -112,7 +113,7 @@ export declare function useFormMetadata<ErrorShape = string[]>(options?: {
112
113
  */
113
114
  export declare function useField<FieldShape = any>(name: FieldName<FieldShape>, options?: {
114
115
  formId?: string;
115
- }): Field<FieldShape>;
116
+ }): FieldMetadata<FieldShape>;
116
117
  /**
117
118
  * A React hook that provides an intent dispatcher for programmatic form actions.
118
119
  * Intent dispatchers allow you to trigger form operations like validation, field updates,
@@ -12,6 +12,7 @@ var intent = require('./intent.js');
12
12
  var dom = require('./dom.js');
13
13
  var jsxRuntime = require('react/jsx-runtime');
14
14
 
15
+ var INITIAL_KEY = 'INITIAL_KEY';
15
16
  var FormConfig = /*#__PURE__*/react.createContext({
16
17
  intentName: future.DEFAULT_INTENT_NAME,
17
18
  observer: future.createGlobalFormsObserver(),
@@ -51,7 +52,7 @@ function useConform(formRef, options) {
51
52
  lastResult
52
53
  } = options;
53
54
  var [state$1, setState] = react.useState(() => {
54
- var state$1 = state.initializeState();
55
+ var state$1 = state.initializeState(INITIAL_KEY);
55
56
  if (lastResult) {
56
57
  state$1 = state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, lastResult), {}, {
57
58
  type: 'initialize',
@@ -127,7 +128,7 @@ function useConform(formRef, options) {
127
128
  }
128
129
  }, [formRef, state$1.resetKey]);
129
130
  react.useEffect(() => {
130
- if (!state$1.intendedValue) {
131
+ if (!state$1.clientIntendedValue) {
131
132
  return;
132
133
  }
133
134
  var formElement = dom.getFormElement(formRef);
@@ -136,9 +137,9 @@ function useConform(formRef, options) {
136
137
  console.error('Failed to update form value; No form element found');
137
138
  return;
138
139
  }
139
- dom.updateFormValue(formElement, state$1.intendedValue, optionsRef.current.serialize);
140
+ dom.updateFormValue(formElement, state$1.clientIntendedValue, optionsRef.current.serialize);
140
141
  lastIntentedValueRef.current = undefined;
141
- }, [formRef, state$1.intendedValue, optionsRef]);
142
+ }, [formRef, state$1.clientIntendedValue, optionsRef]);
142
143
  var handleSubmit = react.useCallback(event => {
143
144
  var _abortControllerRef$c2, _lastAsyncResultRef$c;
144
145
  var abortController = new AbortController();
@@ -172,9 +173,8 @@ function useConform(formRef, options) {
172
173
  }
173
174
 
174
175
  // Override submission value if the last intended value is not applied yet (i.e. batch updates)
175
- if (typeof lastIntentedValueRef.current !== 'undefined') {
176
- var _lastIntentedValueRef;
177
- submission.payload = (_lastIntentedValueRef = lastIntentedValueRef.current) !== null && _lastIntentedValueRef !== void 0 ? _lastIntentedValueRef : {};
176
+ if (lastIntentedValueRef.current != null) {
177
+ submission.payload = lastIntentedValueRef.current;
178
178
  }
179
179
  var intendedValue = intent.applyIntent(submission);
180
180
 
@@ -432,7 +432,7 @@ function useForm(options) {
432
432
  * function ErrorSummary() {
433
433
  * const form = useFormMetadata();
434
434
  *
435
- * if (!form.invalid) return null;
435
+ * if (form.valid) return null;
436
436
  *
437
437
  * return (
438
438
  * <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
@@ -747,6 +747,7 @@ function useLatest(value) {
747
747
  exports.Form = Form;
748
748
  exports.FormConfig = FormConfig;
749
749
  exports.FormProvider = FormProvider;
750
+ exports.INITIAL_KEY = INITIAL_KEY;
750
751
  exports.useConform = useConform;
751
752
  exports.useControl = useControl;
752
753
  exports.useField = useField;
@@ -8,6 +8,7 @@ import { deserializeIntent, actionHandlers, applyIntent } from './intent.mjs';
8
8
  import { focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, updateFormValue, getSubmitEvent } from './dom.mjs';
9
9
  import { jsx } from 'react/jsx-runtime';
10
10
 
11
+ var INITIAL_KEY = 'INITIAL_KEY';
11
12
  var FormConfig = /*#__PURE__*/createContext({
12
13
  intentName: DEFAULT_INTENT_NAME,
13
14
  observer: createGlobalFormsObserver(),
@@ -47,7 +48,7 @@ function useConform(formRef, options) {
47
48
  lastResult
48
49
  } = options;
49
50
  var [state, setState] = useState(() => {
50
- var state = initializeState();
51
+ var state = initializeState(INITIAL_KEY);
51
52
  if (lastResult) {
52
53
  state = updateState(state, _objectSpread2(_objectSpread2({}, lastResult), {}, {
53
54
  type: 'initialize',
@@ -123,7 +124,7 @@ function useConform(formRef, options) {
123
124
  }
124
125
  }, [formRef, state.resetKey]);
125
126
  useEffect(() => {
126
- if (!state.intendedValue) {
127
+ if (!state.clientIntendedValue) {
127
128
  return;
128
129
  }
129
130
  var formElement = getFormElement(formRef);
@@ -132,9 +133,9 @@ function useConform(formRef, options) {
132
133
  console.error('Failed to update form value; No form element found');
133
134
  return;
134
135
  }
135
- updateFormValue(formElement, state.intendedValue, optionsRef.current.serialize);
136
+ updateFormValue(formElement, state.clientIntendedValue, optionsRef.current.serialize);
136
137
  lastIntentedValueRef.current = undefined;
137
- }, [formRef, state.intendedValue, optionsRef]);
138
+ }, [formRef, state.clientIntendedValue, optionsRef]);
138
139
  var handleSubmit = useCallback(event => {
139
140
  var _abortControllerRef$c2, _lastAsyncResultRef$c;
140
141
  var abortController = new AbortController();
@@ -168,9 +169,8 @@ function useConform(formRef, options) {
168
169
  }
169
170
 
170
171
  // Override submission value if the last intended value is not applied yet (i.e. batch updates)
171
- if (typeof lastIntentedValueRef.current !== 'undefined') {
172
- var _lastIntentedValueRef;
173
- submission.payload = (_lastIntentedValueRef = lastIntentedValueRef.current) !== null && _lastIntentedValueRef !== void 0 ? _lastIntentedValueRef : {};
172
+ if (lastIntentedValueRef.current != null) {
173
+ submission.payload = lastIntentedValueRef.current;
174
174
  }
175
175
  var intendedValue = applyIntent(submission);
176
176
 
@@ -428,7 +428,7 @@ function useForm(options) {
428
428
  * function ErrorSummary() {
429
429
  * const form = useFormMetadata();
430
430
  *
431
- * if (!form.invalid) return null;
431
+ * if (form.valid) return null;
432
432
  *
433
433
  * return (
434
434
  * <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
@@ -740,4 +740,4 @@ function useLatest(value) {
740
740
  return ref;
741
741
  }
742
742
 
743
- export { Form, FormConfig, FormProvider, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
743
+ export { Form, FormConfig, FormProvider, INITIAL_KEY, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
@@ -1,5 +1,6 @@
1
- export type { FormValue, FormError } from '@conform-to/dom/future';
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, DefaultValue } from './types';
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
@@ -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;
@@ -1,2 +1,3 @@
1
1
  export { isDirty, parseSubmission, report } from '@conform-to/dom/future';
2
2
  export { FormProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
3
+ export { memoize } from './memoize.mjs';
@@ -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
@@ -1,6 +1,6 @@
1
1
  import { type ValidationAttributes, type Serialize } from '@conform-to/dom/future';
2
- import type { DefaultFieldMetadata, Field, FieldName, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, ActionHandler } from './types';
3
- export declare function initializeState<ErrorShape>(): FormState<ErrorShape>;
2
+ import type { DefaultMetadata, FieldMetadata, FieldName, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, ActionHandler } from './types';
3
+ export declare function initializeState<ErrorShape>(resetKey?: string): FormState<ErrorShape>;
4
4
  /**
5
5
  * Updates form state based on action type:
6
6
  * - Client actions: update intended value and client errors
@@ -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, DefaultFieldMetadata<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
- }): Field<FieldShape, DefaultFieldMetadata<ErrorShape>>;
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, DefaultFieldMetadata<ErrorShape>>;
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
- }): Field<[
55
+ }): FieldMetadata<[
55
56
  FieldShape
56
- ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, DefaultFieldMetadata<ErrorShape>>[];
57
+ ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, DefaultMetadata<ErrorShape>>[];
57
58
  //# sourceMappingURL=state.d.ts.map
@@ -6,12 +6,12 @@ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.j
6
6
  var future = require('@conform-to/dom/future');
7
7
  var util = require('./util.js');
8
8
 
9
- function initializeState() {
9
+ function initializeState(resetKey) {
10
10
  return {
11
- resetKey: util.generateUniqueKey(),
11
+ resetKey: resetKey !== null && resetKey !== void 0 ? resetKey : util.generateUniqueKey(),
12
12
  listKeys: {},
13
- intendedValue: null,
14
- serverValidatedValue: null,
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
- intendedValue: !action.intent ? value : (_action$intendedValue2 = action.intendedValue) !== null && _action$intendedValue2 !== void 0 ? _action$intendedValue2 : state.intendedValue,
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.serverValidatedValue, value) ? null : state.serverError
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
- serverValidatedValue: typeof action.error !== 'undefined' ? value : state.serverValidatedValue
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$intend;
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$intend = context.state.intendedValue) !== null && _context$state$intend !== void 0 ? _context$state$intend : context.defaultValue) !== null && _ref !== void 0 ? _ref : {}, name);
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$intend2;
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$intend2 = context.state.intendedValue) !== null && _context$state$intend2 !== void 0 ? _context$state$intend2 : context.defaultValue) !== null && _ref2 !== void 0 ? _ref2 : {}, name);
85
- var serializedValue = typeof value !== 'undefined' ? serialize(value) : undefined;
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$intend3;
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$intend3 = context.state.intendedValue) !== null && _context$state$intend3 !== void 0 ? _context$state$intend3 : context.defaultValue) !== null && _ref3 !== void 0 ? _ref3 : {}, name);
97
- var serializedValue = typeof value !== 'undefined' ? serialize(value) : undefined;
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$intend4;
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$intend4 = context.state.intendedValue) !== null && _context$state$intend4 !== void 0 ? _context$state$intend4 : context.defaultValue, name);
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 state.touchedFields) {
137
- var relativePath = future.getRelativePath(field, basePath);
138
- if (!relativePath || relativePath.length === 0) {
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
- var error = getErrors(state, field);
142
- if (typeof error !== 'undefined') {
143
- result[future.formatPathSegments(relativePath)] = error;
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
- return result;
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 typeof getErrors(context.state) === 'undefined';
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 typeof getErrors(context.state, options.name) === 'undefined';
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;
@@ -2,12 +2,12 @@ import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelH
2
2
  import { getPathSegments, getRelativePath, appendPathSegment, deepEqual, getValueAtPath, formatPathSegments, serialize } from '@conform-to/dom/future';
3
3
  import { generateUniqueKey, merge, getArrayAtPath } from './util.mjs';
4
4
 
5
- function initializeState() {
5
+ function initializeState(resetKey) {
6
6
  return {
7
- resetKey: generateUniqueKey(),
7
+ resetKey: resetKey !== null && resetKey !== void 0 ? resetKey : generateUniqueKey(),
8
8
  listKeys: {},
9
- intendedValue: null,
10
- serverValidatedValue: null,
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
- intendedValue: !action.intent ? value : (_action$intendedValue2 = action.intendedValue) !== null && _action$intendedValue2 !== void 0 ? _action$intendedValue2 : state.intendedValue,
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.serverValidatedValue, value) ? null : state.serverError
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
- serverValidatedValue: typeof action.error !== 'undefined' ? value : state.serverValidatedValue
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$intend;
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$intend = context.state.intendedValue) !== null && _context$state$intend !== void 0 ? _context$state$intend : context.defaultValue) !== null && _ref !== void 0 ? _ref : {}, name);
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$intend2;
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$intend2 = context.state.intendedValue) !== null && _context$state$intend2 !== void 0 ? _context$state$intend2 : context.defaultValue) !== null && _ref2 !== void 0 ? _ref2 : {}, name);
81
- var serializedValue = typeof value !== 'undefined' ? serialize$1(value) : undefined;
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$intend3;
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$intend3 = context.state.intendedValue) !== null && _context$state$intend3 !== void 0 ? _context$state$intend3 : context.defaultValue) !== null && _ref3 !== void 0 ? _ref3 : {}, name);
93
- var serializedValue = typeof value !== 'undefined' ? serialize$1(value) : undefined;
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$intend4;
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$intend4 = context.state.intendedValue) !== null && _context$state$intend4 !== void 0 ? _context$state$intend4 : context.defaultValue, name);
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 state.touchedFields) {
133
- var relativePath = getRelativePath(field, basePath);
134
- if (!relativePath || relativePath.length === 0) {
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
- var error = getErrors(state, field);
138
- if (typeof error !== 'undefined') {
139
- result[formatPathSegments(relativePath)] = error;
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
- return result;
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 typeof getErrors(context.state) === 'undefined';
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 typeof getErrors(context.state, options.name) === 'undefined';
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 };
@@ -1,5 +1,5 @@
1
1
  import type { FormError, FormValue, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future';
2
- import { StandardSchemaV1 } from '@standard-schema/spec';
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 user intent actions (validate, update, insert, remove, etc.) */
74
- intendedValue: Record<string, unknown> | null;
75
- /** Form values that have been validated on the server */
76
- serverValidatedValue: Record<string, unknown> | null;
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 Field<FieldShape, Metadata extends Record<string, unknown> = DefaultFieldMetadata<unknown>> = Readonly<Metadata & {
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<Field<[
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
- FieldMetadata extends Record<string, unknown>> = {
261
- [Key in keyof Combine<FieldShape>]-?: Field<Combine<FieldShape>[Key], FieldMetadata>;
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, FieldMetadata extends Record<string, unknown> = DefaultFieldMetadata<ErrorShape>> = Readonly<{
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. */
@@ -274,7 +274,7 @@ export type FormMetadata<ErrorShape, FieldMetadata extends Record<string, unknow
274
274
  touched: boolean;
275
275
  /** Whether the form currently has no validation errors. */
276
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 */
277
+ /** @deprecated Use `.valid` instead. This was not an intentional breaking change and would be removed in the next minor version soon */
278
278
  invalid: boolean;
279
279
  /** Form-level validation errors, if any exist. */
280
280
  errors: ErrorShape[] | undefined;
@@ -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>): Field<FieldShape, FieldMetadata>;
294
+ getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, Metadata>;
295
295
  /** Method to get a fieldset object for nested object fields. */
296
- getFieldset<FieldShape>(name: FieldName<FieldShape>): Fieldset<[
296
+ getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<[
297
297
  FieldShape
298
- ] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, FieldMetadata>;
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<Field<[
300
+ getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
301
301
  FieldShape
302
- ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, FieldMetadata>>;
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 DefaultFieldMetadata<ErrorShape> = Readonly<ValidationAttributes & {
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. */
@@ -1,5 +1,5 @@
1
1
  import type { FormError } from '@conform-to/dom/future';
2
- import { StandardSchemaV1 } from '@standard-schema/spec';
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.9.1",
6
+ "version": "1.10.1",
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.9.1"
44
+ "@conform-to/dom": "1.10.1"
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",