@conform-to/react 1.15.1 → 1.16.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 CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.15.1 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.16.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
 
@@ -0,0 +1,43 @@
1
+ import { FieldName } from '@conform-to/dom';
2
+ import { StandardSchemaV1 } from './standard-schema';
3
+ import { FormRef, FormsConfig, FormContext, FormMetadata, FormOptions, Fieldset, FieldMetadata, InferOutput, InferInput, IntentDispatcher } from './types';
4
+ export declare function configureForms<BaseErrorShape = string, BaseSchema = StandardSchemaV1, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}>(config?: Partial<FormsConfig<BaseErrorShape, BaseSchema, CustomFormMetadata, CustomFieldMetadata>>): {
5
+ FormProvider: (props: {
6
+ context: FormContext<BaseErrorShape>;
7
+ children: React.ReactNode;
8
+ }) => React.ReactElement;
9
+ useForm: {
10
+ <Schema extends BaseSchema, ErrorShape extends BaseErrorShape = BaseErrorShape, Value = InferOutput<Schema>>(schema: Schema, options: FormOptions<InferInput<Schema> extends Record<string, any> ? InferInput<Schema> : never, ErrorShape, Value, Schema, string extends ErrorShape ? never : "onValidate">): {
11
+ form: FormMetadata<ErrorShape, CustomFormMetadata, CustomFieldMetadata>;
12
+ fields: Fieldset<InferInput<Schema>, ErrorShape, CustomFieldMetadata>;
13
+ intent: IntentDispatcher<InferInput<Schema> extends Record<string, any> ? InferInput<Schema> : never>;
14
+ };
15
+ <FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = BaseErrorShape, Value = undefined>(options: FormOptions<FormShape, ErrorShape, Value, undefined, undefined extends Value ? "onValidate" : never> & {
16
+ /**
17
+ * @deprecated Use `useForm(schema, options)` instead for better type inference.
18
+ *
19
+ * Optional standard schema for validation (e.g., Zod, Valibot, Yup).
20
+ * Removes the need for manual onValidate setup.
21
+ */
22
+ schema: StandardSchemaV1<FormShape, Value>;
23
+ }): {
24
+ form: FormMetadata<ErrorShape, CustomFormMetadata, CustomFieldMetadata>;
25
+ fields: Fieldset<FormShape, ErrorShape, CustomFieldMetadata>;
26
+ intent: IntentDispatcher<FormShape>;
27
+ };
28
+ <FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = BaseErrorShape, Value = undefined>(options: FormOptions<FormShape, ErrorShape, Value, undefined, "onValidate">): {
29
+ form: FormMetadata<ErrorShape, CustomFormMetadata, CustomFieldMetadata>;
30
+ fields: Fieldset<FormShape, ErrorShape, CustomFieldMetadata>;
31
+ intent: IntentDispatcher<FormShape>;
32
+ };
33
+ };
34
+ useFormMetadata: (options?: {
35
+ formId?: string;
36
+ }) => FormMetadata<BaseErrorShape, CustomFormMetadata, CustomFieldMetadata>;
37
+ useField: <FieldShape = any>(name: FieldName<FieldShape>, options?: {
38
+ formId?: string;
39
+ }) => FieldMetadata<FieldShape, BaseErrorShape, CustomFieldMetadata>;
40
+ useIntent: <FormShape extends Record<string, any>>(formRef: FormRef) => IntentDispatcher<FormShape>;
41
+ config: FormsConfig<BaseErrorShape, BaseSchema, CustomFormMetadata, CustomFieldMetadata>;
42
+ };
43
+ //# sourceMappingURL=forms.d.ts.map
@@ -0,0 +1,322 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
6
+ var dom$1 = require('@conform-to/dom');
7
+ var future = require('@conform-to/dom/future');
8
+ var react = require('react');
9
+ var dom = require('./dom.js');
10
+ var hooks = require('./hooks.js');
11
+ var state = require('./state.js');
12
+ var util = require('./util.js');
13
+ var jsxRuntime = require('react/jsx-runtime');
14
+
15
+ function configureForms() {
16
+ var _config$intentName, _config$serialize, _config$shouldValidat, _ref, _config$shouldRevalid, _config$isSchema, _config$validateSchem;
17
+ var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
18
+ /**
19
+ * Global configuration with defaults applied
20
+ */
21
+ var globalConfig = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), {}, {
22
+ intentName: (_config$intentName = config.intentName) !== null && _config$intentName !== void 0 ? _config$intentName : future.DEFAULT_INTENT_NAME,
23
+ serialize: (_config$serialize = config.serialize) !== null && _config$serialize !== void 0 ? _config$serialize : future.serialize,
24
+ shouldValidate: (_config$shouldValidat = config.shouldValidate) !== null && _config$shouldValidat !== void 0 ? _config$shouldValidat : 'onSubmit',
25
+ shouldRevalidate: (_ref = (_config$shouldRevalid = config.shouldRevalidate) !== null && _config$shouldRevalid !== void 0 ? _config$shouldRevalid : config.shouldValidate) !== null && _ref !== void 0 ? _ref : 'onSubmit',
26
+ isSchema: (_config$isSchema = config.isSchema) !== null && _config$isSchema !== void 0 ? _config$isSchema : util.isStandardSchemaV1,
27
+ validateSchema: (_config$validateSchem = config.validateSchema) !== null && _config$validateSchem !== void 0 ? _config$validateSchem : util.validateStandardSchemaV1
28
+ });
29
+
30
+ /**
31
+ * React context
32
+ */
33
+ var ReactFormContext = /*#__PURE__*/react.createContext([]);
34
+
35
+ /**
36
+ * Provides form context to child components.
37
+ * Stacks contexts to support nested forms, with latest context taking priority.
38
+ */
39
+ function FormProvider(props) {
40
+ var stack = react.useContext(ReactFormContext);
41
+ var value = react.useMemo(
42
+ // Put the latest form context first to ensure that to be the first one found
43
+ () => [props.context].concat(stack), [stack, props.context]);
44
+ return /*#__PURE__*/jsxRuntime.jsx(ReactFormContext.Provider, {
45
+ value: value,
46
+ children: props.children
47
+ });
48
+ }
49
+ function useFormContext(formId) {
50
+ var contexts = react.useContext(ReactFormContext);
51
+ var context = formId ? contexts.find(context => formId === context.formId) : contexts[0];
52
+ if (!context) {
53
+ throw new Error('No form context found. ' + 'Wrap your component with <FormProvider context={form.context}> ' + 'where `form` is returned from useForm().');
54
+ }
55
+ return context;
56
+ }
57
+
58
+ /**
59
+ * The main React hook for form management. Handles form state, validation, and submission
60
+ * while providing access to form metadata, field objects, and form actions.
61
+ *
62
+ * It can be called in two ways:
63
+ * - **Schema first**: Pass a schema as the first argument for automatic validation with type inference
64
+ * - **Manual configuration**: Pass options with custom `onValidate` handler for manual validation
65
+ *
66
+ * @see https://conform.guide/api/react/future/useForm
67
+ * @example Schema first setup with zod:
68
+ *
69
+ * ```tsx
70
+ * const { form, fields } = useForm(zodSchema, {
71
+ * lastResult,
72
+ * shouldValidate: 'onBlur',
73
+ * });
74
+ *
75
+ * return (
76
+ * <form {...form.props}>
77
+ * <input name={fields.email.name} defaultValue={fields.email.defaultValue} />
78
+ * <div>{fields.email.errors}</div>
79
+ * </form>
80
+ * );
81
+ * ```
82
+ *
83
+ * @example Manual configuration setup with custom validation:
84
+ *
85
+ * ```tsx
86
+ * const { form, fields } = useForm({
87
+ * onValidate({ payload, error }) {
88
+ * if (!payload.email) {
89
+ * error.fieldErrors.email = ['Required'];
90
+ * }
91
+ * return error;
92
+ * }
93
+ * });
94
+ *
95
+ * return (
96
+ * <form {...form.props}>
97
+ * <input name={fields.email.name} defaultValue={fields.email.defaultValue} />
98
+ * <div>{fields.email.errors}</div>
99
+ * </form>
100
+ * );
101
+ * ```
102
+ */
103
+
104
+ /**
105
+ * @deprecated Use `useForm(schema, options)` instead for better type inference.
106
+ */
107
+
108
+ function useForm(schemaOrOptions, maybeOptions) {
109
+ var _options$constraint, _globalConfig$getCons, _options$id, _options$onError;
110
+ var schema;
111
+ var options;
112
+ if (globalConfig.isSchema(schemaOrOptions)) {
113
+ schema = schemaOrOptions;
114
+ options = maybeOptions !== null && maybeOptions !== void 0 ? maybeOptions : {};
115
+ } else {
116
+ options = schemaOrOptions;
117
+ }
118
+ var constraint = (_options$constraint = options.constraint) !== null && _options$constraint !== void 0 ? _options$constraint : schema ? (_globalConfig$getCons = globalConfig.getConstraints) === null || _globalConfig$getCons === void 0 ? void 0 : _globalConfig$getCons.call(globalConfig, schema) : undefined;
119
+ var optionsRef = hooks.useLatest(options);
120
+ var fallbackId = react.useId();
121
+ var formId = (_options$id = options.id) !== null && _options$id !== void 0 ? _options$id : "form-".concat(fallbackId);
122
+ var [state$1, handleSubmit] = hooks.useConform(formId, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, {
123
+ serialize: globalConfig.serialize,
124
+ intentName: globalConfig.intentName,
125
+ onError: (_options$onError = options.onError) !== null && _options$onError !== void 0 ? _options$onError : dom.focusFirstInvalidField,
126
+ onValidate(ctx) {
127
+ var _options$onValidate, _options$onValidate2, _options;
128
+ if (schema) {
129
+ var schemaResult = globalConfig.validateSchema(schema, ctx.payload, options.schemaOptions);
130
+ if (schemaResult instanceof Promise) {
131
+ return schemaResult.then(resolvedResult => {
132
+ if (typeof options.onValidate === 'function') {
133
+ throw new Error('The "onValidate" handler is not supported when used with asynchronous schema validation.');
134
+ }
135
+ return resolvedResult;
136
+ });
137
+ }
138
+ if (!options.onValidate) {
139
+ return schemaResult;
140
+ }
141
+
142
+ // Update the schema error in the context
143
+ if (schemaResult.error) {
144
+ ctx.error = schemaResult.error;
145
+ }
146
+ var schemaValue = schemaResult.value;
147
+ ctx.schemaValue = schemaValue;
148
+ var validateResult = util.resolveValidateResult(options.onValidate(ctx));
149
+ if (validateResult.syncResult) {
150
+ var _validateResult$syncR, _validateResult$syncR2;
151
+ (_validateResult$syncR2 = (_validateResult$syncR = validateResult.syncResult).value) !== null && _validateResult$syncR2 !== void 0 ? _validateResult$syncR2 : _validateResult$syncR.value = schemaValue;
152
+ }
153
+ if (validateResult.asyncResult) {
154
+ validateResult.asyncResult = validateResult.asyncResult.then(result => {
155
+ var _result$value;
156
+ (_result$value = result.value) !== null && _result$value !== void 0 ? _result$value : result.value = schemaValue;
157
+ return result;
158
+ });
159
+ }
160
+ return [validateResult.syncResult, validateResult.asyncResult];
161
+ }
162
+ return (_options$onValidate = (_options$onValidate2 = (_options = options).onValidate) === null || _options$onValidate2 === void 0 ? void 0 : _options$onValidate2.call(_options, ctx)) !== null && _options$onValidate !== void 0 ? _options$onValidate : {
163
+ // To avoid conform falling back to server validation,
164
+ // if neither schema nor validation handler is provided,
165
+ // we just treat it as a valid client submission
166
+ error: null
167
+ };
168
+ }
169
+ }));
170
+ var intent = useIntent(formId);
171
+ var context = react.useMemo(() => ({
172
+ formId,
173
+ state: state$1,
174
+ constraint: constraint !== null && constraint !== void 0 ? constraint : null,
175
+ handleSubmit,
176
+ handleInput(event) {
177
+ var _optionsRef$current$o, _optionsRef$current, _optionsRef$current$s, _ref2, _optionsRef$current$s2;
178
+ if (!dom$1.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) {
179
+ return;
180
+ }
181
+ (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onInput) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, event), {}, {
182
+ target: event.target,
183
+ currentTarget: event.target.form
184
+ }));
185
+ if (event.defaultPrevented) {
186
+ return;
187
+ }
188
+ var shouldValidate = (_optionsRef$current$s = optionsRef.current.shouldValidate) !== null && _optionsRef$current$s !== void 0 ? _optionsRef$current$s : globalConfig.shouldValidate;
189
+ var shouldRevalidate = (_ref2 = (_optionsRef$current$s2 = optionsRef.current.shouldRevalidate) !== null && _optionsRef$current$s2 !== void 0 ? _optionsRef$current$s2 : optionsRef.current.shouldValidate) !== null && _ref2 !== void 0 ? _ref2 : globalConfig.shouldRevalidate;
190
+ if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
191
+ intent.validate(event.target.name);
192
+ }
193
+ },
194
+ handleBlur(event) {
195
+ var _optionsRef$current$o2, _optionsRef$current2, _optionsRef$current$s3, _ref3, _optionsRef$current$s4;
196
+ if (!dom$1.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) {
197
+ return;
198
+ }
199
+ (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onBlur) === null || _optionsRef$current$o2 === void 0 || _optionsRef$current$o2.call(_optionsRef$current2, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, event), {}, {
200
+ target: event.target,
201
+ currentTarget: event.target.form
202
+ }));
203
+ if (event.defaultPrevented) {
204
+ return;
205
+ }
206
+ var shouldValidate = (_optionsRef$current$s3 = optionsRef.current.shouldValidate) !== null && _optionsRef$current$s3 !== void 0 ? _optionsRef$current$s3 : globalConfig.shouldValidate;
207
+ var shouldRevalidate = (_ref3 = (_optionsRef$current$s4 = optionsRef.current.shouldRevalidate) !== null && _optionsRef$current$s4 !== void 0 ? _optionsRef$current$s4 : optionsRef.current.shouldValidate) !== null && _ref3 !== void 0 ? _ref3 : globalConfig.shouldRevalidate;
208
+ if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
209
+ intent.validate(event.target.name);
210
+ }
211
+ }
212
+ }), [formId, state$1, constraint, handleSubmit, intent, optionsRef]);
213
+ var form = react.useMemo(() => state.getFormMetadata(context, {
214
+ serialize: globalConfig.serialize,
215
+ extendFormMetadata: globalConfig.extendFormMetadata,
216
+ extendFieldMetadata: globalConfig.extendFieldMetadata
217
+ }), [context]);
218
+ var fields = react.useMemo(() => state.getFieldset(context, {
219
+ serialize: globalConfig.serialize,
220
+ extendFieldMetadata: globalConfig.extendFieldMetadata
221
+ }), [context]);
222
+ return {
223
+ form,
224
+ fields,
225
+ intent
226
+ };
227
+ }
228
+
229
+ /**
230
+ * A React hook that provides access to form-level metadata and state.
231
+ * Requires `FormProvider` context when used in child components.
232
+ *
233
+ * @see https://conform.guide/api/react/future/useFormMetadata
234
+ * @example
235
+ * ```tsx
236
+ * function ErrorSummary() {
237
+ * const form = useFormMetadata();
238
+ *
239
+ * if (form.valid) return null;
240
+ *
241
+ * return (
242
+ * <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
243
+ * );
244
+ * }
245
+ * ```
246
+ */
247
+ function useFormMetadata() {
248
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
249
+ var context = useFormContext(options.formId);
250
+ var formMetadata = react.useMemo(() => state.getFormMetadata(context, {
251
+ serialize: globalConfig.serialize,
252
+ extendFormMetadata: globalConfig.extendFormMetadata,
253
+ extendFieldMetadata: globalConfig.extendFieldMetadata
254
+ }), [context]);
255
+ return formMetadata;
256
+ }
257
+
258
+ /**
259
+ * A React hook that provides access to a specific field's metadata and state.
260
+ * Requires `FormProvider` context when used in child components.
261
+ *
262
+ * @see https://conform.guide/api/react/future/useField
263
+ * @example
264
+ * ```tsx
265
+ * function FormField({ name, label }) {
266
+ * const field = useField(name);
267
+ *
268
+ * return (
269
+ * <div>
270
+ * <label htmlFor={field.id}>{label}</label>
271
+ * <input id={field.id} name={field.name} defaultValue={field.defaultValue} />
272
+ * {field.errors && <div>{field.errors.join(', ')}</div>}
273
+ * </div>
274
+ * );
275
+ * }
276
+ * ```
277
+ */
278
+ function useField(name) {
279
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
280
+ var context = useFormContext(options.formId);
281
+ var field = react.useMemo(() => state.getField(context, {
282
+ name,
283
+ serialize: globalConfig.serialize,
284
+ extendFieldMetadata: globalConfig.extendFieldMetadata
285
+ }), [context, name]);
286
+ return field;
287
+ }
288
+
289
+ /**
290
+ * A React hook that provides an intent dispatcher for programmatic form actions.
291
+ * Intent dispatchers allow you to trigger form operations like validation, field updates,
292
+ * and array manipulations without manual form submission.
293
+ *
294
+ * @see https://conform.guide/api/react/future/useIntent
295
+ * @example
296
+ * ```tsx
297
+ * function ResetButton() {
298
+ * const buttonRef = useRef<HTMLButtonElement>(null);
299
+ * const intent = useIntent(buttonRef);
300
+ *
301
+ * return (
302
+ * <button type="button" ref={buttonRef} onClick={() => intent.reset()}>
303
+ * Reset Form
304
+ * </button>
305
+ * );
306
+ * }
307
+ * ```
308
+ */
309
+ function useIntent(formRef) {
310
+ return react.useMemo(() => dom.createIntentDispatcher(() => dom.getFormElement(formRef), globalConfig.intentName), [formRef]);
311
+ }
312
+ return {
313
+ FormProvider,
314
+ useForm,
315
+ useFormMetadata,
316
+ useField,
317
+ useIntent,
318
+ config: globalConfig
319
+ };
320
+ }
321
+
322
+ exports.configureForms = configureForms;