@conform-to/react 1.9.0 → 1.9.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,23 +7,23 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.9.0 / License MIT / Copyright (c) 2024 Edmund Hung
10
+ Version 1.9.1 / License MIT / Copyright (c) 2025 Edmund Hung
11
11
 
12
- A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
12
+ Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards.
13
13
 
14
- # Getting Started
14
+ ## Getting Started
15
15
 
16
16
  Check out the overview and tutorial at our website https://conform.guide
17
17
 
18
- # Features
18
+ ## Features
19
19
 
20
- - Progressive enhancement first APIs
21
- - Type-safe field inference
22
- - Fine-grained subscription
23
- - Built-in accessibility helpers
24
- - Automatic type coercion with Zod
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
- # Documentation
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
- # Support
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.
@@ -23,7 +23,8 @@ 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 getError<ErrorShape>(state: FormState<ErrorShape>, name?: string): ErrorShape[] | undefined;
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[]>;
27
28
  /**
28
29
  * Gets validation constraint for a field, with fallback to parent array patterns.
29
30
  * e.g. "array[0].key" falls back to "array[].key" if specific constraint not found.
@@ -119,7 +119,7 @@ function getListKey(context, name) {
119
119
  var _context$state$listKe, _context$state$listKe2, _context$state$intend4;
120
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);
121
121
  }
122
- function getError(state, name) {
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,21 @@ function getError(state, name) {
130
130
  return errors;
131
131
  }
132
132
  }
133
+ function getFieldErrors(state, name) {
134
+ var result = {};
135
+ 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) {
139
+ continue;
140
+ }
141
+ var error = getErrors(state, field);
142
+ if (typeof error !== 'undefined') {
143
+ result[future.formatPathSegments(relativePath)] = error;
144
+ }
145
+ }
146
+ return result;
147
+ }
133
148
 
134
149
  /**
135
150
  * Gets validation constraint for a field, with fallback to parent array patterns.
@@ -160,29 +175,24 @@ function getConstraint(context, name) {
160
175
  }
161
176
  function getFormMetadata(context, options) {
162
177
  return {
178
+ key: context.state.resetKey,
163
179
  id: context.formId,
180
+ errorId: "".concat(context.formId, "-form-error"),
181
+ descriptionId: "".concat(context.formId, "-form-description"),
164
182
  get errors() {
165
- return getError(context.state);
183
+ return getErrors(context.state);
166
184
  },
167
185
  get fieldErrors() {
168
- var result = {};
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;
186
+ return getFieldErrors(context.state);
180
187
  },
181
188
  get touched() {
182
189
  return isTouched(context.state);
183
190
  },
191
+ get valid() {
192
+ return typeof getErrors(context.state) === 'undefined';
193
+ },
184
194
  get invalid() {
185
- return typeof getError(context.state) !== 'undefined';
195
+ return !this.valid;
186
196
  },
187
197
  props: {
188
198
  id: context.formId,
@@ -210,12 +220,13 @@ function getFormMetadata(context, options) {
210
220
  };
211
221
  }
212
222
  function getField(context, options) {
213
- var id = "".concat(context.formId, "-").concat(options.name);
223
+ var id = "".concat(context.formId, "-field-").concat(options.name.replace(/[^a-zA-Z0-9._-]/g, '_'));
214
224
  var constraint = getConstraint(context, options.name);
215
225
  var metadata = {
216
226
  id: id,
217
227
  descriptionId: "".concat(id, "-description"),
218
228
  errorId: "".concat(id, "-error"),
229
+ formId: context.formId,
219
230
  required: constraint === null || constraint === void 0 ? void 0 : constraint.required,
220
231
  minLength: constraint === null || constraint === void 0 ? void 0 : constraint.minLength,
221
232
  maxLength: constraint === null || constraint === void 0 ? void 0 : constraint.maxLength,
@@ -236,11 +247,17 @@ function getField(context, options) {
236
247
  get touched() {
237
248
  return isTouched(context.state, options.name);
238
249
  },
250
+ get valid() {
251
+ return typeof getErrors(context.state, options.name) === 'undefined';
252
+ },
239
253
  get invalid() {
240
- return typeof getError(context.state, options.name) !== 'undefined';
254
+ return !this.valid;
241
255
  },
242
256
  get errors() {
243
- return getError(context.state, options.name);
257
+ return getErrors(context.state, options.name);
258
+ },
259
+ get fieldErrors() {
260
+ return getFieldErrors(context.state, options.name);
244
261
  }
245
262
  };
246
263
  return Object.assign(metadata, {
@@ -288,8 +305,9 @@ exports.getConstraint = getConstraint;
288
305
  exports.getDefaultListKey = getDefaultListKey;
289
306
  exports.getDefaultOptions = getDefaultOptions;
290
307
  exports.getDefaultValue = getDefaultValue;
291
- exports.getError = getError;
308
+ exports.getErrors = getErrors;
292
309
  exports.getField = getField;
310
+ exports.getFieldErrors = getFieldErrors;
293
311
  exports.getFieldList = getFieldList;
294
312
  exports.getFieldset = getFieldset;
295
313
  exports.getFormMetadata = getFormMetadata;
@@ -115,7 +115,7 @@ function getListKey(context, name) {
115
115
  var _context$state$listKe, _context$state$listKe2, _context$state$intend4;
116
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);
117
117
  }
118
- function getError(state, name) {
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,21 @@ function getError(state, name) {
126
126
  return errors;
127
127
  }
128
128
  }
129
+ function getFieldErrors(state, name) {
130
+ var result = {};
131
+ var basePath = getPathSegments(name);
132
+ for (var field of state.touchedFields) {
133
+ var relativePath = getRelativePath(field, basePath);
134
+ if (!relativePath || relativePath.length === 0) {
135
+ continue;
136
+ }
137
+ var error = getErrors(state, field);
138
+ if (typeof error !== 'undefined') {
139
+ result[formatPathSegments(relativePath)] = error;
140
+ }
141
+ }
142
+ return result;
143
+ }
129
144
 
130
145
  /**
131
146
  * Gets validation constraint for a field, with fallback to parent array patterns.
@@ -156,29 +171,24 @@ function getConstraint(context, name) {
156
171
  }
157
172
  function getFormMetadata(context, options) {
158
173
  return {
174
+ key: context.state.resetKey,
159
175
  id: context.formId,
176
+ errorId: "".concat(context.formId, "-form-error"),
177
+ descriptionId: "".concat(context.formId, "-form-description"),
160
178
  get errors() {
161
- return getError(context.state);
179
+ return getErrors(context.state);
162
180
  },
163
181
  get fieldErrors() {
164
- var result = {};
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;
182
+ return getFieldErrors(context.state);
176
183
  },
177
184
  get touched() {
178
185
  return isTouched(context.state);
179
186
  },
187
+ get valid() {
188
+ return typeof getErrors(context.state) === 'undefined';
189
+ },
180
190
  get invalid() {
181
- return typeof getError(context.state) !== 'undefined';
191
+ return !this.valid;
182
192
  },
183
193
  props: {
184
194
  id: context.formId,
@@ -206,12 +216,13 @@ function getFormMetadata(context, options) {
206
216
  };
207
217
  }
208
218
  function getField(context, options) {
209
- var id = "".concat(context.formId, "-").concat(options.name);
219
+ var id = "".concat(context.formId, "-field-").concat(options.name.replace(/[^a-zA-Z0-9._-]/g, '_'));
210
220
  var constraint = getConstraint(context, options.name);
211
221
  var metadata = {
212
222
  id: id,
213
223
  descriptionId: "".concat(id, "-description"),
214
224
  errorId: "".concat(id, "-error"),
225
+ formId: context.formId,
215
226
  required: constraint === null || constraint === void 0 ? void 0 : constraint.required,
216
227
  minLength: constraint === null || constraint === void 0 ? void 0 : constraint.minLength,
217
228
  maxLength: constraint === null || constraint === void 0 ? void 0 : constraint.maxLength,
@@ -232,11 +243,17 @@ function getField(context, options) {
232
243
  get touched() {
233
244
  return isTouched(context.state, options.name);
234
245
  },
246
+ get valid() {
247
+ return typeof getErrors(context.state, options.name) === 'undefined';
248
+ },
235
249
  get invalid() {
236
- return typeof getError(context.state, options.name) !== 'undefined';
250
+ return !this.valid;
237
251
  },
238
252
  get errors() {
239
- return getError(context.state, options.name);
253
+ return getErrors(context.state, options.name);
254
+ },
255
+ get fieldErrors() {
256
+ return getFieldErrors(context.state, options.name);
240
257
  }
241
258
  };
242
259
  return Object.assign(metadata, {
@@ -280,4 +297,4 @@ function getFieldList(context, options) {
280
297
  });
281
298
  }
282
299
 
283
- export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getError, getField, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, updateState };
300
+ export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, updateState };
@@ -262,15 +262,23 @@ FieldMetadata extends Record<string, unknown>> = {
262
262
  };
263
263
  /** Form-level metadata and state object containing validation status, errors, and field access methods. */
264
264
  export type FormMetadata<ErrorShape, FieldMetadata extends Record<string, unknown> = DefaultFieldMetadata<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 any validation errors. */
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 field-specific validation errors for all validated fields. */
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<{
@@ -295,12 +303,14 @@ export type FormMetadata<ErrorShape, FieldMetadata extends Record<string, unknow
295
303
  }>;
296
304
  /** Default field metadata object containing field state, validation attributes, and accessibility IDs. */
297
305
  export type DefaultFieldMetadata<ErrorShape> = Readonly<ValidationAttributes & {
298
- /** The field's unique identifier, automatically generated as {formId}-{fieldName}. */
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/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.0",
6
+ "version": "1.9.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.0"
44
+ "@conform-to/dom": "1.9.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "^7.17.8",