@conform-to/dom 1.18.0 → 1.19.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.18.0 / License MIT / Copyright (c) 2026 Edmund Hung
10
+ Version 1.19.0 / License MIT / Copyright (c) 2026 Edmund Hung
11
11
 
12
12
  Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards.
13
13
 
package/dist/dom.js CHANGED
@@ -570,8 +570,8 @@ function updateField(element, options) {
570
570
  /**
571
571
  * Triggering react custom change event
572
572
  * Solution based on dom-testing-library
573
- * @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
574
- * @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
573
+ * See https://github.com/facebook/react/issues/10135#issuecomment-401496776
574
+ * See https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
575
575
  */
576
576
  var {
577
577
  set: valueSetter
package/dist/dom.mjs CHANGED
@@ -566,8 +566,8 @@ function updateField(element, options) {
566
566
  /**
567
567
  * Triggering react custom change event
568
568
  * Solution based on dom-testing-library
569
- * @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
570
- * @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
569
+ * See https://github.com/facebook/react/issues/10135#issuecomment-401496776
570
+ * See https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
571
571
  */
572
572
  var {
573
573
  set: valueSetter
@@ -1,18 +1,29 @@
1
- import type { FormValue, FieldName, JsonPrimitive, Serialize, SerializedValue, Submission, SubmissionResult, UnknownObject } from './types';
2
- import type { StandardSchemaIssue } from './standard-schema';
1
+ import type { FormError, FormValue, FieldName, JsonPrimitive, CustomSerialize, Submission, SubmissionResult, UnknownObject, Serialize, StandardSchemaError, CustomError } from './types';
3
2
  export declare const DEFAULT_INTENT_NAME = "__INTENT__";
3
+ /**
4
+ * Returns whether an error payload contains a meaningful value.
5
+ * Empty strings and empty arrays are treated as no error.
6
+ */
7
+ export declare function hasError<ErrorShape>(error: ErrorShape | null | undefined): error is ErrorShape;
8
+ /**
9
+ * Normalizes a form error object by removing empty error payloads such as
10
+ * empty strings and empty arrays.
11
+ *
12
+ * Returns `null` when no form-level or field-level errors remain.
13
+ */
14
+ export declare function normalizeFormError<ErrorShape>(error: CustomError<ErrorShape> | null): FormError<ErrorShape> | null;
4
15
  /**
5
16
  * Construct a form data with the submitter value.
6
17
  * It utilizes the submitter argument on the FormData constructor from modern browsers
7
18
  * with fallback to append the submitter value in case it is not unsupported.
8
19
  *
9
- * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
20
+ * See https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
10
21
  */
11
22
  export declare function getFormData(form: HTMLFormElement, submitter?: HTMLElement | null): FormData;
12
23
  /**
13
24
  * Convert a string path into an array of segments.
14
25
  *
15
- * @example
26
+ * **Example:**
16
27
  * ```js
17
28
  * parsePath("object.key"); // → ['object', 'key']
18
29
  * parsePath("array[0].content"); // → ['array', 0, 'content']
@@ -23,7 +34,7 @@ export declare function parsePath(path: string | undefined): Array<string | numb
23
34
  /**
24
35
  * Returns a formatted name from the path segments based on the dot and bracket notation.
25
36
  *
26
- * @example
37
+ * **Example:**
27
38
  * ```js
28
39
  * formatPath(['object', 'key']); // → "object.key"
29
40
  * formatPath(['array', 0, 'content']); // → "array[0].content"
@@ -43,7 +54,7 @@ export declare function appendPath(path: string | undefined, segment: string | n
43
54
  /**
44
55
  * Returns true if `prefix` is a valid leading path of `name`.
45
56
  *
46
- * @example
57
+ * **Example:**
47
58
  * ```js
48
59
  * isPathPrefix("foo.bar.baz", "foo.bar") // → true
49
60
  * isPathPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
@@ -60,7 +71,7 @@ export declare function isPathPrefix(name: string, prefix: string): boolean;
60
71
  * @param basePath Base path as a dot/bracket string or array of segments
61
72
  * @returns The “tail” segments, or `null` if `fullPath` isn’t nested under `basePath`
62
73
  *
63
- * @example
74
+ * **Example:**
64
75
  * ```js
65
76
  * getRelativePath("foo.bar[0].qux", ["foo","bar"]) // → [0, "qux"]
66
77
  * getRelativePath("a.b.c.d", ["a","b"]) // → ["c","d"]
@@ -84,8 +95,9 @@ export declare function getPathValue(target: unknown, pathOrSegments: string | A
84
95
  * This function structures the form values based on the naming convention.
85
96
  * It also includes all the field names and extracts the intent from the submission.
86
97
  *
87
- * @see https://conform.guide/api/react/future/parseSubmission
88
- * @example
98
+ * See https://conform.guide/api/react/future/parseSubmission
99
+ *
100
+ * **Example:**
89
101
  * ```ts
90
102
  * const formData = new FormData();
91
103
  *
@@ -136,8 +148,9 @@ export declare function parseSubmission(formData: FormData | URLSearchParams, op
136
148
  * file inputs cannot be initialized with files.
137
149
  * You can specify `keepFiles: true` to keep the files if needed.
138
150
  *
139
- * @see https://conform.guide/api/react/future/report
140
- * @example
151
+ * See https://conform.guide/api/react/future/report
152
+ *
153
+ * **Example:**
141
154
  * ```ts
142
155
  * // Report the submission with the field errors
143
156
  * report(submission, {
@@ -161,55 +174,61 @@ export declare function parseSubmission(formData: FormData | URLSearchParams, op
161
174
  * })
162
175
  * ```
163
176
  */
164
- export declare function report<ErrorShape = string>(submission: Submission, options?: {
177
+ export declare function report<ErrorShape>(submission: Submission, options: {
165
178
  keepFiles?: false;
166
- error?: {
167
- issues?: undefined;
168
- formErrors?: ErrorShape[];
169
- fieldErrors?: Record<string, ErrorShape[]>;
170
- } | null;
179
+ error: CustomError<ErrorShape>;
171
180
  value?: Record<string, FormValue> | null;
172
181
  hideFields?: string[];
173
182
  reset?: boolean;
174
183
  }): SubmissionResult<ErrorShape, Exclude<JsonPrimitive | FormDataEntryValue, File>>;
175
- export declare function report<ErrorShape = string>(submission: Submission, options: {
184
+ export declare function report<ErrorShape>(submission: Submission, options: {
176
185
  keepFiles: true;
177
- error?: {
178
- issues?: undefined;
179
- formErrors?: ErrorShape[];
180
- fieldErrors?: Record<string, ErrorShape[]>;
181
- } | null;
186
+ error: CustomError<ErrorShape>;
182
187
  value?: Record<string, FormValue> | null;
183
188
  hideFields?: string[];
184
189
  reset?: boolean;
185
190
  }): SubmissionResult<ErrorShape>;
186
- export declare function report(submission: Submission, options?: {
191
+ export declare function report(submission: Submission, options: {
187
192
  keepFiles?: false;
188
- error?: {
189
- issues: ReadonlyArray<StandardSchemaIssue>;
190
- formErrors?: string[];
191
- fieldErrors?: Record<string, string[]>;
192
- };
193
+ error: StandardSchemaError;
194
+ value?: Record<string, FormValue> | null;
195
+ hideFields?: string[];
196
+ reset?: boolean;
197
+ }): SubmissionResult<string[], Exclude<JsonPrimitive | FormDataEntryValue, File>>;
198
+ export declare function report(submission: Submission, options: {
199
+ keepFiles: true;
200
+ error: StandardSchemaError;
193
201
  value?: Record<string, FormValue> | null;
194
202
  hideFields?: string[];
195
203
  reset?: boolean;
196
- }): SubmissionResult<string, Exclude<JsonPrimitive | FormDataEntryValue, File>>;
204
+ }): SubmissionResult<string[]>;
197
205
  export declare function report(submission: Submission, options?: {
206
+ keepFiles?: false;
207
+ error?: null;
208
+ value?: Record<string, FormValue> | null;
209
+ hideFields?: string[];
210
+ reset?: boolean;
211
+ }): SubmissionResult<never, Exclude<JsonPrimitive | FormDataEntryValue, File>>;
212
+ export declare function report(submission: Submission, options: {
198
213
  keepFiles: true;
199
- error?: {
200
- issues: ReadonlyArray<StandardSchemaIssue>;
201
- formErrors?: string[];
202
- fieldErrors?: Record<string, string[]>;
203
- };
214
+ error?: null;
215
+ value?: Record<string, FormValue> | null;
216
+ hideFields?: string[];
217
+ reset?: boolean;
218
+ }): SubmissionResult<never>;
219
+ export declare function report<ErrorShape>(submission: Submission, options?: {
220
+ keepFiles?: boolean;
221
+ error?: CustomError<ErrorShape> | null;
204
222
  value?: Record<string, FormValue> | null;
205
223
  hideFields?: string[];
206
224
  reset?: boolean;
207
- }): SubmissionResult<string>;
225
+ }): SubmissionResult<ErrorShape>;
208
226
  /**
209
227
  * A utility function that checks whether the current form data differs from the default values.
210
228
  *
211
- * @see https://conform.guide/api/react/future/isDirty
212
- * @example Enable a submit button only if the form is dirty
229
+ * See https://conform.guide/api/react/future/isDirty
230
+ *
231
+ * **Example: Enable a submit button only if the form is dirty**
213
232
  *
214
233
  * ```tsx
215
234
  * const dirty = useFormData(
@@ -251,19 +270,19 @@ formData: FormData | URLSearchParams | FormValue | null, options?: {
251
270
  * - Returned as-is
252
271
  * - boolean:
253
272
  * - true → 'on'
254
- * - false → undefined
273
+ * - false → null
255
274
  * - number / bigint:
256
275
  * - Converted to string using `.toString()`
257
276
  * - Date:
258
277
  * - Converted to UTC datetime string without trailing `Z` (e.g. `2026-01-01T12:00:00.000`)
259
278
  */
260
- serialize?: (value: unknown, defaultSerialize: Serialize) => SerializedValue | null | undefined;
279
+ serialize?: CustomSerialize;
261
280
  /**
262
281
  * A function to exclude specific fields from the comparison.
263
282
  * Useful for ignoring hidden inputs like CSRF tokens or internal fields added by frameworks
264
283
  * (e.g. Next.js uses hidden inputs to support server actions).
265
284
  *
266
- * @example
285
+ * **Example:**
267
286
  * ```ts
268
287
  * isDirty(formData, {
269
288
  * skipEntry: (name) => name === 'csrf-token',
@@ -287,7 +306,7 @@ formData: FormData | URLSearchParams | FormValue | null, options?: {
287
306
  * - Array -> string[] or File[] if all items serialize to the same kind; otherwise undefined
288
307
  * - anything else -> undefined
289
308
  */
290
- export declare function serialize(value: unknown): SerializedValue | null | undefined;
309
+ export declare function defaultSerialize(value: unknown): ReturnType<Serialize>;
291
310
  /**
292
311
  * Recursively serializes a value using the provided serialize function,
293
312
  * collapsing empty leaves (`null`, `''`, empty files) to `undefined`
@@ -299,11 +318,13 @@ export declare function serialize(value: unknown): SerializedValue | null | unde
299
318
  * Single-element arrays where the element is a string or undefined are unwrapped
300
319
  * to handle the case where a multi-value field (e.g. checkboxes) has only one value.
301
320
  */
302
- export declare function normalize(value: unknown, serializeValue?: Serialize): unknown;
321
+ export declare function normalize(value: unknown, serialize?: Serialize, name?: string): unknown;
303
322
  /**
304
323
  * Retrieve a field value from FormData with optional type guards.
305
324
  *
306
- * @example
325
+ * **Example:**
326
+ *
327
+ * ```ts
307
328
  * // Basic field access: return `unknown`
308
329
  * const email = getFieldValue(formData, 'email');
309
330
  * // String type: returns `string`
@@ -318,6 +339,7 @@ export declare function normalize(value: unknown, serializeValue?: Serialize): u
318
339
  * const items = getFieldValue<Item[]>(formData, 'items', { type: 'object', array: true });
319
340
  * // Optional string type: returns `string | undefined`
320
341
  * const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });
342
+ * ```
321
343
  */
322
344
  export declare function getFieldValue<FieldShape extends Array<Record<string, unknown>>>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
323
345
  type: 'object';
package/dist/formdata.js CHANGED
@@ -9,12 +9,48 @@ var standardSchema = require('./standard-schema.js');
9
9
 
10
10
  var DEFAULT_INTENT_NAME = '__INTENT__';
11
11
 
12
+ /**
13
+ * Returns whether an error payload contains a meaningful value.
14
+ * Empty strings and empty arrays are treated as no error.
15
+ */
16
+ function hasError(error) {
17
+ return error != null && error !== '' && (!Array.isArray(error) || error.length > 0);
18
+ }
19
+
20
+ /**
21
+ * Normalizes a form error object by removing empty error payloads such as
22
+ * empty strings and empty arrays.
23
+ *
24
+ * Returns `null` when no form-level or field-level errors remain.
25
+ */
26
+ function normalizeFormError(error) {
27
+ var _error$fieldErrors;
28
+ if (error === null) {
29
+ return null;
30
+ }
31
+ var formErrors = hasError(error.formErrors) ? error.formErrors : null;
32
+ var fieldErrors = Object.entries((_error$fieldErrors = error.fieldErrors) !== null && _error$fieldErrors !== void 0 ? _error$fieldErrors : {}).reduce((result, _ref) => {
33
+ var [name, value] = _ref;
34
+ if (hasError(value)) {
35
+ result[name] = value;
36
+ }
37
+ return result;
38
+ }, {});
39
+ if (formErrors === null && Object.keys(fieldErrors).length === 0) {
40
+ return null;
41
+ }
42
+ return {
43
+ formErrors,
44
+ fieldErrors
45
+ };
46
+ }
47
+
12
48
  /**
13
49
  * Construct a form data with the submitter value.
14
50
  * It utilizes the submitter argument on the FormData constructor from modern browsers
15
51
  * with fallback to append the submitter value in case it is not unsupported.
16
52
  *
17
- * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
53
+ * See https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
18
54
  */
19
55
  function getFormData(form, submitter) {
20
56
  var payload = new FormData(form, submitter);
@@ -37,7 +73,7 @@ function getFormData(form, submitter) {
37
73
  /**
38
74
  * Convert a string path into an array of segments.
39
75
  *
40
- * @example
76
+ * **Example:**
41
77
  * ```js
42
78
  * parsePath("object.key"); // → ['object', 'key']
43
79
  * parsePath("array[0].content"); // → ['array', 0, 'content']
@@ -83,7 +119,7 @@ function parsePath(path) {
83
119
  /**
84
120
  * Returns a formatted name from the path segments based on the dot and bracket notation.
85
121
  *
86
- * @example
122
+ * **Example:**
87
123
  * ```js
88
124
  * formatPath(['object', 'key']); // → "object.key"
89
125
  * formatPath(['array', 0, 'content']); // → "array[0].content"
@@ -103,30 +139,32 @@ function formatPath(segments) {
103
139
  * - segment = `string` ⇒ dot-notation ".prop"
104
140
  */
105
141
  function appendPath(path, segment) {
142
+ var base = path !== null && path !== void 0 ? path : '';
143
+
106
144
  // 1) nothing to append
107
145
  if (typeof segment === 'undefined') {
108
- return path !== null && path !== void 0 ? path : '';
146
+ return base;
109
147
  }
110
148
 
111
149
  // 2) explicit empty-segment => empty bracket
112
150
  if (segment === '') {
113
151
  // even as first segment, "[]" is valid
114
- return "".concat(path, "[]");
152
+ return "".concat(base, "[]");
115
153
  }
116
154
 
117
155
  // 3) numeric index => [n]
118
156
  if (typeof segment === 'number') {
119
- return "".concat(path, "[").concat(segment, "]");
157
+ return "".concat(base, "[").concat(segment, "]");
120
158
  }
121
159
 
122
160
  // 4) non-empty string => .prop (no leading dot if no base)
123
- return path ? "".concat(path, ".").concat(segment) : segment;
161
+ return base ? "".concat(base, ".").concat(segment) : segment;
124
162
  }
125
163
 
126
164
  /**
127
165
  * Returns true if `prefix` is a valid leading path of `name`.
128
166
  *
129
- * @example
167
+ * **Example:**
130
168
  * ```js
131
169
  * isPathPrefix("foo.bar.baz", "foo.bar") // → true
132
170
  * isPathPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
@@ -146,7 +184,7 @@ function isPathPrefix(name, prefix) {
146
184
  * @param basePath Base path as a dot/bracket string or array of segments
147
185
  * @returns The “tail” segments, or `null` if `fullPath` isn’t nested under `basePath`
148
186
  *
149
- * @example
187
+ * **Example:**
150
188
  * ```js
151
189
  * getRelativePath("foo.bar[0].qux", ["foo","bar"]) // → [0, "qux"]
152
190
  * getRelativePath("a.b.c.d", ["a","b"]) // → ["c","d"]
@@ -265,8 +303,9 @@ function isEmptyValue(value) {
265
303
  * This function structures the form values based on the naming convention.
266
304
  * It also includes all the field names and extracts the intent from the submission.
267
305
  *
268
- * @see https://conform.guide/api/react/future/parseSubmission
269
- * @example
306
+ * See https://conform.guide/api/react/future/parseSubmission
307
+ *
308
+ * **Example:**
270
309
  * ```ts
271
310
  * const formData = new FormData();
272
311
  *
@@ -302,7 +341,7 @@ function parseSubmission(formData, options) {
302
341
  var _options$skipEntry;
303
342
  if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
304
343
  var _options$stripEmptyVa;
305
- var _value = formData.getAll(_name);
344
+ var value = formData.getAll(_name);
306
345
  var segments = parsePath(_name);
307
346
 
308
347
  // If the name ends with [], remove the empty segment and keep the full array
@@ -310,19 +349,19 @@ function parseSubmission(formData, options) {
310
349
  if (segments.length > 0 && segments[segments.length - 1] === '') {
311
350
  segments.pop();
312
351
  } else {
313
- _value = _value.length > 1 ? _value : _value[0];
352
+ value = value.length > 1 ? value : value[0];
314
353
  }
315
354
  var stripEmptyValues = (_options$stripEmptyVa = options === null || options === void 0 ? void 0 : options.stripEmptyValues) !== null && _options$stripEmptyVa !== void 0 ? _options$stripEmptyVa : false;
316
355
  if (stripEmptyValues) {
317
356
  // For arrays, filter out individual empty items
318
- if (Array.isArray(_value)) {
319
- _value = _value.filter(item => !isEmptyValue(item));
357
+ if (Array.isArray(value)) {
358
+ value = value.filter(item => !isEmptyValue(item));
320
359
  }
321
- if (isEmptyValue(_value)) {
322
- _value = undefined;
360
+ if (isEmptyValue(value)) {
361
+ value = undefined;
323
362
  }
324
363
  }
325
- setPathValue(submission.payload, segments, _value, {
364
+ setPathValue(submission.payload, segments, value, {
326
365
  silent: true // Avoid errors if the path is invalid
327
366
  });
328
367
  submission.fields.push(_name);
@@ -344,8 +383,9 @@ function parseSubmission(formData, options) {
344
383
  * file inputs cannot be initialized with files.
345
384
  * You can specify `keepFiles: true` to keep the files if needed.
346
385
  *
347
- * @see https://conform.guide/api/react/future/report
348
- * @example
386
+ * See https://conform.guide/api/react/future/report
387
+ *
388
+ * **Example:**
349
389
  * ```ts
350
390
  * // Report the submission with the field errors
351
391
  * report(submission, {
@@ -376,29 +416,16 @@ function report(submission) {
376
416
  var error;
377
417
  if (options.error == null) {
378
418
  error = options.error;
379
- } else {
419
+ } else if ('issues' in options.error) {
380
420
  var _options$error$issues;
381
421
  error = standardSchema.formatIssues((_options$error$issues = options.error.issues) !== null && _options$error$issues !== void 0 ? _options$error$issues : []);
382
- if (options.error.formErrors) {
383
- error.formErrors.push(...options.error.formErrors);
384
- }
385
- if (options.error.fieldErrors) {
386
- for (var [_name2, messages] of Object.entries(options.error.fieldErrors)) {
387
- if (messages.length === 0) {
388
- continue;
389
- }
390
- if (!error.fieldErrors[_name2]) {
391
- error.fieldErrors[_name2] = messages;
392
- } else {
393
- error.fieldErrors[_name2].push(...messages);
394
- }
395
- }
396
- }
422
+ } else {
423
+ error = normalizeFormError(options.error);
397
424
  }
398
425
  var targetValue = typeof options.value === 'undefined' || submission.payload === options.value && !options.reset ? undefined : options.value && !options.keepFiles ? util.stripFiles(options.value) : (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : {};
399
426
  if (options.hideFields) {
400
- for (var _name3 of options.hideFields) {
401
- var path = parsePath(_name3);
427
+ for (var _name2 of options.hideFields) {
428
+ var path = parsePath(_name2);
402
429
  setPathValue(submission.payload, path, undefined);
403
430
  if (targetValue) {
404
431
  setPathValue(targetValue, path, undefined);
@@ -418,8 +445,9 @@ function report(submission) {
418
445
  /**
419
446
  * A utility function that checks whether the current form data differs from the default values.
420
447
  *
421
- * @see https://conform.guide/api/react/future/isDirty
422
- * @example Enable a submit button only if the form is dirty
448
+ * See https://conform.guide/api/react/future/isDirty
449
+ *
450
+ * **Example: Enable a submit button only if the form is dirty**
423
451
  *
424
452
  * ```tsx
425
453
  * const dirty = useFormData(
@@ -450,9 +478,16 @@ formData, options) {
450
478
  intentName: options === null || options === void 0 ? void 0 : options.intentName,
451
479
  skipEntry: options === null || options === void 0 ? void 0 : options.skipEntry
452
480
  }).payload : formData;
453
- var defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;
454
- var serializeValue = options !== null && options !== void 0 && options.serialize ? value => options.serialize(value, serialize) : serialize;
455
- return !util.deepEqual(normalize(formValue, serializeValue), normalize(defaultValue, serializeValue));
481
+ var serialize = (value, context) => {
482
+ if (options !== null && options !== void 0 && options.serialize) {
483
+ return options.serialize(value, {
484
+ name: context.name,
485
+ defaultSerialize
486
+ });
487
+ }
488
+ return defaultSerialize(value);
489
+ };
490
+ return !util.deepEqual(normalize(formValue, serialize), normalize(options === null || options === void 0 ? void 0 : options.defaultValue, serialize));
456
491
  }
457
492
 
458
493
  /**
@@ -470,13 +505,13 @@ formData, options) {
470
505
  * - Array -> string[] or File[] if all items serialize to the same kind; otherwise undefined
471
506
  * - anything else -> undefined
472
507
  */
473
- function serialize(value) {
508
+ function defaultSerialize(value) {
474
509
  function serializePrimitive(value) {
475
510
  if (typeof value === 'string' || value === null) {
476
511
  return value;
477
512
  }
478
513
  if (typeof value === 'boolean') {
479
- return value ? 'on' : '';
514
+ return value ? 'on' : null;
480
515
  }
481
516
  if (typeof value === 'number' || typeof value === 'bigint') {
482
517
  return value.toString();
@@ -537,8 +572,11 @@ function serialize(value) {
537
572
  * to handle the case where a multi-value field (e.g. checkboxes) has only one value.
538
573
  */
539
574
  function normalize(value) {
540
- var serializeValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : serialize;
541
- var data = serializeValue(value);
575
+ var serialize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultSerialize;
576
+ var name = arguments.length > 2 ? arguments[2] : undefined;
577
+ var data = serialize(value, {
578
+ name
579
+ });
542
580
  if (typeof data === 'undefined') {
543
581
  data = value;
544
582
  }
@@ -555,16 +593,16 @@ function normalize(value) {
555
593
  if (data.length === 0) {
556
594
  return undefined;
557
595
  }
558
- var array = data.map(item => normalize(item, serializeValue));
596
+ var array = data.map((item, index) => normalize(item, serialize, appendPath(name, index)));
559
597
  if (array.length === 1 && (typeof array[0] === 'string' || array[0] === undefined)) {
560
598
  return array[0];
561
599
  }
562
600
  return array;
563
601
  }
564
602
  if (util.isPlainObject(data)) {
565
- var entries = Object.entries(data).reduce((list, _ref) => {
566
- var [key, value] = _ref;
567
- var normalizedValue = normalize(value, serializeValue);
603
+ var entries = Object.entries(data).reduce((list, _ref2) => {
604
+ var [key, value] = _ref2;
605
+ var normalizedValue = normalize(value, serialize, appendPath(name, key));
568
606
  if (typeof normalizedValue !== 'undefined') {
569
607
  list.push([key, normalizedValue]);
570
608
  }
@@ -581,7 +619,9 @@ function normalize(value) {
581
619
  /**
582
620
  * Retrieve a field value from FormData with optional type guards.
583
621
  *
584
- * @example
622
+ * **Example:**
623
+ *
624
+ * ```ts
585
625
  * // Basic field access: return `unknown`
586
626
  * const email = getFieldValue(formData, 'email');
587
627
  * // String type: returns `string`
@@ -596,6 +636,7 @@ function normalize(value) {
596
636
  * const items = getFieldValue<Item[]>(formData, 'items', { type: 'object', array: true });
597
637
  * // Optional string type: returns `string | undefined`
598
638
  * const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });
639
+ * ```
599
640
  */
600
641
 
601
642
  function getFieldValue(formData, name, options) {
@@ -649,16 +690,18 @@ function getFieldValue(formData, name, options) {
649
690
 
650
691
  exports.DEFAULT_INTENT_NAME = DEFAULT_INTENT_NAME;
651
692
  exports.appendPath = appendPath;
693
+ exports.defaultSerialize = defaultSerialize;
652
694
  exports.formatPath = formatPath;
653
695
  exports.getFieldValue = getFieldValue;
654
696
  exports.getFormData = getFormData;
655
697
  exports.getPathValue = getPathValue;
656
698
  exports.getRelativePath = getRelativePath;
699
+ exports.hasError = hasError;
657
700
  exports.isDirty = isDirty;
658
701
  exports.isPathPrefix = isPathPrefix;
659
702
  exports.normalize = normalize;
703
+ exports.normalizeFormError = normalizeFormError;
660
704
  exports.parsePath = parsePath;
661
705
  exports.parseSubmission = parseSubmission;
662
706
  exports.report = report;
663
- exports.serialize = serialize;
664
707
  exports.setPathValue = setPathValue;
package/dist/formdata.mjs CHANGED
@@ -5,12 +5,48 @@ import { formatIssues } from './standard-schema.mjs';
5
5
 
6
6
  var DEFAULT_INTENT_NAME = '__INTENT__';
7
7
 
8
+ /**
9
+ * Returns whether an error payload contains a meaningful value.
10
+ * Empty strings and empty arrays are treated as no error.
11
+ */
12
+ function hasError(error) {
13
+ return error != null && error !== '' && (!Array.isArray(error) || error.length > 0);
14
+ }
15
+
16
+ /**
17
+ * Normalizes a form error object by removing empty error payloads such as
18
+ * empty strings and empty arrays.
19
+ *
20
+ * Returns `null` when no form-level or field-level errors remain.
21
+ */
22
+ function normalizeFormError(error) {
23
+ var _error$fieldErrors;
24
+ if (error === null) {
25
+ return null;
26
+ }
27
+ var formErrors = hasError(error.formErrors) ? error.formErrors : null;
28
+ var fieldErrors = Object.entries((_error$fieldErrors = error.fieldErrors) !== null && _error$fieldErrors !== void 0 ? _error$fieldErrors : {}).reduce((result, _ref) => {
29
+ var [name, value] = _ref;
30
+ if (hasError(value)) {
31
+ result[name] = value;
32
+ }
33
+ return result;
34
+ }, {});
35
+ if (formErrors === null && Object.keys(fieldErrors).length === 0) {
36
+ return null;
37
+ }
38
+ return {
39
+ formErrors,
40
+ fieldErrors
41
+ };
42
+ }
43
+
8
44
  /**
9
45
  * Construct a form data with the submitter value.
10
46
  * It utilizes the submitter argument on the FormData constructor from modern browsers
11
47
  * with fallback to append the submitter value in case it is not unsupported.
12
48
  *
13
- * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
49
+ * See https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
14
50
  */
15
51
  function getFormData(form, submitter) {
16
52
  var payload = new FormData(form, submitter);
@@ -33,7 +69,7 @@ function getFormData(form, submitter) {
33
69
  /**
34
70
  * Convert a string path into an array of segments.
35
71
  *
36
- * @example
72
+ * **Example:**
37
73
  * ```js
38
74
  * parsePath("object.key"); // → ['object', 'key']
39
75
  * parsePath("array[0].content"); // → ['array', 0, 'content']
@@ -79,7 +115,7 @@ function parsePath(path) {
79
115
  /**
80
116
  * Returns a formatted name from the path segments based on the dot and bracket notation.
81
117
  *
82
- * @example
118
+ * **Example:**
83
119
  * ```js
84
120
  * formatPath(['object', 'key']); // → "object.key"
85
121
  * formatPath(['array', 0, 'content']); // → "array[0].content"
@@ -99,30 +135,32 @@ function formatPath(segments) {
99
135
  * - segment = `string` ⇒ dot-notation ".prop"
100
136
  */
101
137
  function appendPath(path, segment) {
138
+ var base = path !== null && path !== void 0 ? path : '';
139
+
102
140
  // 1) nothing to append
103
141
  if (typeof segment === 'undefined') {
104
- return path !== null && path !== void 0 ? path : '';
142
+ return base;
105
143
  }
106
144
 
107
145
  // 2) explicit empty-segment => empty bracket
108
146
  if (segment === '') {
109
147
  // even as first segment, "[]" is valid
110
- return "".concat(path, "[]");
148
+ return "".concat(base, "[]");
111
149
  }
112
150
 
113
151
  // 3) numeric index => [n]
114
152
  if (typeof segment === 'number') {
115
- return "".concat(path, "[").concat(segment, "]");
153
+ return "".concat(base, "[").concat(segment, "]");
116
154
  }
117
155
 
118
156
  // 4) non-empty string => .prop (no leading dot if no base)
119
- return path ? "".concat(path, ".").concat(segment) : segment;
157
+ return base ? "".concat(base, ".").concat(segment) : segment;
120
158
  }
121
159
 
122
160
  /**
123
161
  * Returns true if `prefix` is a valid leading path of `name`.
124
162
  *
125
- * @example
163
+ * **Example:**
126
164
  * ```js
127
165
  * isPathPrefix("foo.bar.baz", "foo.bar") // → true
128
166
  * isPathPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
@@ -142,7 +180,7 @@ function isPathPrefix(name, prefix) {
142
180
  * @param basePath Base path as a dot/bracket string or array of segments
143
181
  * @returns The “tail” segments, or `null` if `fullPath` isn’t nested under `basePath`
144
182
  *
145
- * @example
183
+ * **Example:**
146
184
  * ```js
147
185
  * getRelativePath("foo.bar[0].qux", ["foo","bar"]) // → [0, "qux"]
148
186
  * getRelativePath("a.b.c.d", ["a","b"]) // → ["c","d"]
@@ -261,8 +299,9 @@ function isEmptyValue(value) {
261
299
  * This function structures the form values based on the naming convention.
262
300
  * It also includes all the field names and extracts the intent from the submission.
263
301
  *
264
- * @see https://conform.guide/api/react/future/parseSubmission
265
- * @example
302
+ * See https://conform.guide/api/react/future/parseSubmission
303
+ *
304
+ * **Example:**
266
305
  * ```ts
267
306
  * const formData = new FormData();
268
307
  *
@@ -298,7 +337,7 @@ function parseSubmission(formData, options) {
298
337
  var _options$skipEntry;
299
338
  if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
300
339
  var _options$stripEmptyVa;
301
- var _value = formData.getAll(_name);
340
+ var value = formData.getAll(_name);
302
341
  var segments = parsePath(_name);
303
342
 
304
343
  // If the name ends with [], remove the empty segment and keep the full array
@@ -306,19 +345,19 @@ function parseSubmission(formData, options) {
306
345
  if (segments.length > 0 && segments[segments.length - 1] === '') {
307
346
  segments.pop();
308
347
  } else {
309
- _value = _value.length > 1 ? _value : _value[0];
348
+ value = value.length > 1 ? value : value[0];
310
349
  }
311
350
  var stripEmptyValues = (_options$stripEmptyVa = options === null || options === void 0 ? void 0 : options.stripEmptyValues) !== null && _options$stripEmptyVa !== void 0 ? _options$stripEmptyVa : false;
312
351
  if (stripEmptyValues) {
313
352
  // For arrays, filter out individual empty items
314
- if (Array.isArray(_value)) {
315
- _value = _value.filter(item => !isEmptyValue(item));
353
+ if (Array.isArray(value)) {
354
+ value = value.filter(item => !isEmptyValue(item));
316
355
  }
317
- if (isEmptyValue(_value)) {
318
- _value = undefined;
356
+ if (isEmptyValue(value)) {
357
+ value = undefined;
319
358
  }
320
359
  }
321
- setPathValue(submission.payload, segments, _value, {
360
+ setPathValue(submission.payload, segments, value, {
322
361
  silent: true // Avoid errors if the path is invalid
323
362
  });
324
363
  submission.fields.push(_name);
@@ -340,8 +379,9 @@ function parseSubmission(formData, options) {
340
379
  * file inputs cannot be initialized with files.
341
380
  * You can specify `keepFiles: true` to keep the files if needed.
342
381
  *
343
- * @see https://conform.guide/api/react/future/report
344
- * @example
382
+ * See https://conform.guide/api/react/future/report
383
+ *
384
+ * **Example:**
345
385
  * ```ts
346
386
  * // Report the submission with the field errors
347
387
  * report(submission, {
@@ -372,29 +412,16 @@ function report(submission) {
372
412
  var error;
373
413
  if (options.error == null) {
374
414
  error = options.error;
375
- } else {
415
+ } else if ('issues' in options.error) {
376
416
  var _options$error$issues;
377
417
  error = formatIssues((_options$error$issues = options.error.issues) !== null && _options$error$issues !== void 0 ? _options$error$issues : []);
378
- if (options.error.formErrors) {
379
- error.formErrors.push(...options.error.formErrors);
380
- }
381
- if (options.error.fieldErrors) {
382
- for (var [_name2, messages] of Object.entries(options.error.fieldErrors)) {
383
- if (messages.length === 0) {
384
- continue;
385
- }
386
- if (!error.fieldErrors[_name2]) {
387
- error.fieldErrors[_name2] = messages;
388
- } else {
389
- error.fieldErrors[_name2].push(...messages);
390
- }
391
- }
392
- }
418
+ } else {
419
+ error = normalizeFormError(options.error);
393
420
  }
394
421
  var targetValue = typeof options.value === 'undefined' || submission.payload === options.value && !options.reset ? undefined : options.value && !options.keepFiles ? stripFiles(options.value) : (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : {};
395
422
  if (options.hideFields) {
396
- for (var _name3 of options.hideFields) {
397
- var path = parsePath(_name3);
423
+ for (var _name2 of options.hideFields) {
424
+ var path = parsePath(_name2);
398
425
  setPathValue(submission.payload, path, undefined);
399
426
  if (targetValue) {
400
427
  setPathValue(targetValue, path, undefined);
@@ -414,8 +441,9 @@ function report(submission) {
414
441
  /**
415
442
  * A utility function that checks whether the current form data differs from the default values.
416
443
  *
417
- * @see https://conform.guide/api/react/future/isDirty
418
- * @example Enable a submit button only if the form is dirty
444
+ * See https://conform.guide/api/react/future/isDirty
445
+ *
446
+ * **Example: Enable a submit button only if the form is dirty**
419
447
  *
420
448
  * ```tsx
421
449
  * const dirty = useFormData(
@@ -446,9 +474,16 @@ formData, options) {
446
474
  intentName: options === null || options === void 0 ? void 0 : options.intentName,
447
475
  skipEntry: options === null || options === void 0 ? void 0 : options.skipEntry
448
476
  }).payload : formData;
449
- var defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;
450
- var serializeValue = options !== null && options !== void 0 && options.serialize ? value => options.serialize(value, serialize) : serialize;
451
- return !deepEqual(normalize(formValue, serializeValue), normalize(defaultValue, serializeValue));
477
+ var serialize = (value, context) => {
478
+ if (options !== null && options !== void 0 && options.serialize) {
479
+ return options.serialize(value, {
480
+ name: context.name,
481
+ defaultSerialize
482
+ });
483
+ }
484
+ return defaultSerialize(value);
485
+ };
486
+ return !deepEqual(normalize(formValue, serialize), normalize(options === null || options === void 0 ? void 0 : options.defaultValue, serialize));
452
487
  }
453
488
 
454
489
  /**
@@ -466,13 +501,13 @@ formData, options) {
466
501
  * - Array -> string[] or File[] if all items serialize to the same kind; otherwise undefined
467
502
  * - anything else -> undefined
468
503
  */
469
- function serialize(value) {
504
+ function defaultSerialize(value) {
470
505
  function serializePrimitive(value) {
471
506
  if (typeof value === 'string' || value === null) {
472
507
  return value;
473
508
  }
474
509
  if (typeof value === 'boolean') {
475
- return value ? 'on' : '';
510
+ return value ? 'on' : null;
476
511
  }
477
512
  if (typeof value === 'number' || typeof value === 'bigint') {
478
513
  return value.toString();
@@ -533,8 +568,11 @@ function serialize(value) {
533
568
  * to handle the case where a multi-value field (e.g. checkboxes) has only one value.
534
569
  */
535
570
  function normalize(value) {
536
- var serializeValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : serialize;
537
- var data = serializeValue(value);
571
+ var serialize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultSerialize;
572
+ var name = arguments.length > 2 ? arguments[2] : undefined;
573
+ var data = serialize(value, {
574
+ name
575
+ });
538
576
  if (typeof data === 'undefined') {
539
577
  data = value;
540
578
  }
@@ -551,16 +589,16 @@ function normalize(value) {
551
589
  if (data.length === 0) {
552
590
  return undefined;
553
591
  }
554
- var array = data.map(item => normalize(item, serializeValue));
592
+ var array = data.map((item, index) => normalize(item, serialize, appendPath(name, index)));
555
593
  if (array.length === 1 && (typeof array[0] === 'string' || array[0] === undefined)) {
556
594
  return array[0];
557
595
  }
558
596
  return array;
559
597
  }
560
598
  if (isPlainObject(data)) {
561
- var entries = Object.entries(data).reduce((list, _ref) => {
562
- var [key, value] = _ref;
563
- var normalizedValue = normalize(value, serializeValue);
599
+ var entries = Object.entries(data).reduce((list, _ref2) => {
600
+ var [key, value] = _ref2;
601
+ var normalizedValue = normalize(value, serialize, appendPath(name, key));
564
602
  if (typeof normalizedValue !== 'undefined') {
565
603
  list.push([key, normalizedValue]);
566
604
  }
@@ -577,7 +615,9 @@ function normalize(value) {
577
615
  /**
578
616
  * Retrieve a field value from FormData with optional type guards.
579
617
  *
580
- * @example
618
+ * **Example:**
619
+ *
620
+ * ```ts
581
621
  * // Basic field access: return `unknown`
582
622
  * const email = getFieldValue(formData, 'email');
583
623
  * // String type: returns `string`
@@ -592,6 +632,7 @@ function normalize(value) {
592
632
  * const items = getFieldValue<Item[]>(formData, 'items', { type: 'object', array: true });
593
633
  * // Optional string type: returns `string | undefined`
594
634
  * const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });
635
+ * ```
595
636
  */
596
637
 
597
638
  function getFieldValue(formData, name, options) {
@@ -643,4 +684,4 @@ function getFieldValue(formData, name, options) {
643
684
  return value;
644
685
  }
645
686
 
646
- export { DEFAULT_INTENT_NAME, appendPath, formatPath, getFieldValue, getFormData, getPathValue, getRelativePath, isDirty, isPathPrefix, normalize, parsePath, parseSubmission, report, serialize, setPathValue };
687
+ export { DEFAULT_INTENT_NAME, appendPath, defaultSerialize, formatPath, getFieldValue, getFormData, getPathValue, getRelativePath, hasError, isDirty, isPathPrefix, normalize, normalizeFormError, parsePath, parseSubmission, report, setPathValue };
@@ -1,5 +1,5 @@
1
- export type { Serialize, FieldName, FormValue, FormError, Submission, SubmissionResult, ValidationAttributes, } from '../types';
2
- export { DEFAULT_INTENT_NAME, getFormData, isDirty, normalize, parseSubmission, parsePath, formatPath, appendPath, getRelativePath, getPathValue, setPathValue, report, serialize, getFieldValue, } from '../formdata';
1
+ export type { Serialize, CustomSerialize, FieldName, FormValue, FormError, Submission, SubmissionResult, ValidationAttributes, } from '../types';
2
+ export { DEFAULT_INTENT_NAME, getFormData, isDirty, normalize, parseSubmission, parsePath, formatPath, appendPath, getRelativePath, getPathValue, setPathValue, report, defaultSerialize, getFieldValue, normalizeFormError, } from '../formdata';
3
3
  export { isPlainObject, deepEqual } from '../util';
4
4
  export { isFieldElement, isGlobalInstance, updateField, createFileList, createGlobalFormsObserver, dispatchInternalUpdateEvent, focus, change, blur, getFormAction, getFormEncType, getFormMethod, requestSubmit, requestIntent, } from '../dom';
5
5
  export { formatIssues } from '../standard-schema';
@@ -11,6 +11,7 @@ var standardSchema = require('../standard-schema.js');
11
11
 
12
12
  exports.DEFAULT_INTENT_NAME = formdata.DEFAULT_INTENT_NAME;
13
13
  exports.appendPath = formdata.appendPath;
14
+ exports.defaultSerialize = formdata.defaultSerialize;
14
15
  exports.formatPath = formdata.formatPath;
15
16
  exports.getFieldValue = formdata.getFieldValue;
16
17
  exports.getFormData = formdata.getFormData;
@@ -18,10 +19,10 @@ exports.getPathValue = formdata.getPathValue;
18
19
  exports.getRelativePath = formdata.getRelativePath;
19
20
  exports.isDirty = formdata.isDirty;
20
21
  exports.normalize = formdata.normalize;
22
+ exports.normalizeFormError = formdata.normalizeFormError;
21
23
  exports.parsePath = formdata.parsePath;
22
24
  exports.parseSubmission = formdata.parseSubmission;
23
25
  exports.report = formdata.report;
24
- exports.serialize = formdata.serialize;
25
26
  exports.setPathValue = formdata.setPathValue;
26
27
  exports.deepEqual = util.deepEqual;
27
28
  exports.isPlainObject = util.isPlainObject;
@@ -1,4 +1,4 @@
1
- export { DEFAULT_INTENT_NAME, appendPath, formatPath, getFieldValue, getFormData, getPathValue, getRelativePath, isDirty, normalize, parsePath, parseSubmission, report, serialize, setPathValue } from '../formdata.mjs';
1
+ export { DEFAULT_INTENT_NAME, appendPath, defaultSerialize, formatPath, getFieldValue, getFormData, getPathValue, getRelativePath, isDirty, normalize, normalizeFormError, parsePath, parseSubmission, report, setPathValue } from '../formdata.mjs';
2
2
  export { deepEqual, isPlainObject } from '../util.mjs';
3
3
  export { blur, change, createFileList, createGlobalFormsObserver, dispatchInternalUpdateEvent, focus, getFormAction, getFormEncType, getFormMethod, isFieldElement, isGlobalInstance, requestIntent, requestSubmit, updateField } from '../dom.mjs';
4
4
  export { formatIssues } from '../standard-schema.mjs';
@@ -1,15 +1,3 @@
1
- import type { FormError } from './types';
2
- /**
3
- * A widened version of `StandardSchemaV1.Issue`.
4
- *
5
- * The `path` elements and `PropertyKey` fields are loosened to `unknown`
6
- * to stay compatible with Valibot's native issue type.
7
- */
8
- export type StandardSchemaIssue = {
9
- readonly message: string;
10
- readonly path?: ReadonlyArray<unknown | {
11
- key: unknown;
12
- }> | undefined;
13
- };
14
- export declare function formatIssues(issues: Readonly<StandardSchemaIssue[]>): FormError<string>;
1
+ import type { FormError, StandardSchemaIssue } from './types';
2
+ export declare function formatIssues(issues: Readonly<StandardSchemaIssue[]>): FormError<string[]>;
15
3
  //# sourceMappingURL=standard-schema.d.ts.map
@@ -5,16 +5,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var formdata = require('./formdata.js');
6
6
  var util = require('./util.js');
7
7
 
8
- /**
9
- * A widened version of `StandardSchemaV1.Issue`.
10
- *
11
- * The `path` elements and `PropertyKey` fields are loosened to `unknown`
12
- * to stay compatible with Valibot's native issue type.
13
- */
14
-
15
8
  function formatIssues(issues) {
16
9
  var error = {
17
- formErrors: [],
10
+ formErrors: null,
18
11
  fieldErrors: {}
19
12
  };
20
13
  for (var issue of issues) {
@@ -28,6 +21,8 @@ function formatIssues(issues) {
28
21
  })) !== null && _issue$path$map !== void 0 ? _issue$path$map : [];
29
22
  var name = formdata.formatPath(segments !== null && segments !== void 0 ? segments : []);
30
23
  if (!name) {
24
+ var _error$formErrors;
25
+ (_error$formErrors = error.formErrors) !== null && _error$formErrors !== void 0 ? _error$formErrors : error.formErrors = [];
31
26
  error.formErrors.push(issue.message);
32
27
  } else {
33
28
  var _error$fieldErrors, _error$fieldErrors$na;
@@ -1,16 +1,9 @@
1
1
  import { formatPath } from './formdata.mjs';
2
2
  import { isPlainObject } from './util.mjs';
3
3
 
4
- /**
5
- * A widened version of `StandardSchemaV1.Issue`.
6
- *
7
- * The `path` elements and `PropertyKey` fields are loosened to `unknown`
8
- * to stay compatible with Valibot's native issue type.
9
- */
10
-
11
4
  function formatIssues(issues) {
12
5
  var error = {
13
- formErrors: [],
6
+ formErrors: null,
14
7
  fieldErrors: {}
15
8
  };
16
9
  for (var issue of issues) {
@@ -24,6 +17,8 @@ function formatIssues(issues) {
24
17
  })) !== null && _issue$path$map !== void 0 ? _issue$path$map : [];
25
18
  var name = formatPath(segments !== null && segments !== void 0 ? segments : []);
26
19
  if (!name) {
20
+ var _error$formErrors;
21
+ (_error$formErrors = error.formErrors) !== null && _error$formErrors !== void 0 ? _error$formErrors : error.formErrors = [];
27
22
  error.formErrors.push(issue.message);
28
23
  } else {
29
24
  var _error$fieldErrors, _error$fieldErrors$na;
package/dist/types.d.ts CHANGED
@@ -12,15 +12,35 @@ export type FormValue<Type extends JsonPrimitive | FormDataEntryValue = JsonPrim
12
12
  /**
13
13
  * Form error object that contains both form errors and field errors.
14
14
  */
15
- export type FormError<ErrorShape = string> = {
15
+ export type FormError<ErrorShape = string[]> = {
16
16
  /**
17
- * The error of the form.
17
+ * The form-level error payload.
18
+ * Set to `null` when there is no form-level error.
18
19
  */
19
- formErrors: ErrorShape[];
20
+ formErrors: ErrorShape | null;
20
21
  /**
21
- * The field errors based on the field name.
22
+ * Field-level error payloads mapped by field name.
23
+ * Use `null` to explicitly clear a field error.
22
24
  */
23
- fieldErrors: Record<string, ErrorShape[]>;
25
+ fieldErrors: Record<string, ErrorShape | null>;
26
+ };
27
+ /**
28
+ * A widened version of `StandardSchemaV1.Issue`.
29
+ *
30
+ * The `path` elements and `PropertyKey` fields are loosened to `unknown`
31
+ * to stay compatible with Valibot's native issue type.
32
+ */
33
+ export type StandardSchemaIssue = {
34
+ readonly message: string;
35
+ readonly path?: ReadonlyArray<unknown | {
36
+ key: unknown;
37
+ }> | undefined;
38
+ };
39
+ export type StandardSchemaError = Partial<Record<keyof FormError, never>> & {
40
+ issues: ReadonlyArray<StandardSchemaIssue>;
41
+ };
42
+ export type CustomError<ErrorShape> = Partial<FormError<ErrorShape>> & {
43
+ issues?: never;
24
44
  };
25
45
  /**
26
46
  * Structured data parsed from a form submission.
@@ -30,7 +50,7 @@ export type Submission<ValueType extends JsonPrimitive | FormDataEntryValue = Js
30
50
  * The submitted values mapped by field name.
31
51
  * Supports nested names like `user.email` or indexed names like `items[0].id`.
32
52
  *
33
- * @example
53
+ * **Example:**
34
54
  * ```json
35
55
  * {
36
56
  * "username": "johndoe",
@@ -58,7 +78,7 @@ export type Submission<ValueType extends JsonPrimitive | FormDataEntryValue = Js
58
78
  /**
59
79
  * The result of a submission.
60
80
  */
61
- export type SubmissionResult<ErrorShape = string, ValueType extends JsonPrimitive | FormDataEntryValue = JsonPrimitive | FormDataEntryValue> = {
81
+ export type SubmissionResult<ErrorShape = string[], ValueType extends JsonPrimitive | FormDataEntryValue = JsonPrimitive | FormDataEntryValue> = {
62
82
  /**
63
83
  * The original submission data.
64
84
  */
@@ -81,8 +101,9 @@ export type FieldName<FieldShape> = string & {
81
101
  '~shape'?: FieldShape;
82
102
  };
83
103
  /**
84
- * The input attributes related to form field constraints
85
- * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
104
+ * The input attributes related to form field constraints.
105
+ *
106
+ * See https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
86
107
  */
87
108
  export type ValidationAttributes = {
88
109
  required?: boolean | undefined;
@@ -103,23 +124,22 @@ export type Serializable<T> = T extends File ? undefined : T extends Array<infer
103
124
  [K in keyof T]: Serializable<T[K]>;
104
125
  } : T;
105
126
  /**
106
- * Converts an arbitrary value into a {@link SerializedValue}.
127
+ * Converts an arbitrary value into a form value.
107
128
  *
108
129
  * This function is used to prepare field values for submission,
109
130
  * ensuring they are compatible with the browser's `FormData` API.
110
- *
111
- * @param value - The original value to serialize.
112
- * @returns A `SerializedValue` if the input can be represented in `FormData`,
113
- * or `undefined` if it cannot be serialized.
114
131
  */
115
- export type Serialize = (value: unknown) => SerializedValue | null | undefined;
132
+ export type Serialize = (value: unknown, ctx: {
133
+ name: string | undefined;
134
+ }) => FormValue<FormDataEntryValue> | null | undefined;
116
135
  /**
117
- * A value that can be serialized into `FormData`.
118
- *
119
- * - `string` and `File` are supported natively by `FormData`.
120
- * - Arrays allow representing multi-value fields.
136
+ * A custom serializer that can override specific values and delegate everything
137
+ * else back to the default serializer.
121
138
  */
122
- export type SerializedValue = string | string[] | File | File[];
139
+ export type CustomSerialize = (value: unknown, ctx: {
140
+ name: string | undefined;
141
+ defaultSerialize: (value: unknown) => ReturnType<Serialize>;
142
+ }) => FormValue<FormDataEntryValue> | null | undefined;
123
143
  /**
124
144
  * Flatten a discriminated union into a single type with all properties.
125
145
  */
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A set of opinionated helpers built on top of the Constraint Validation API",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "1.18.0",
6
+ "version": "1.19.0",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",