@conform-to/dom 1.13.2 → 1.14.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.13.2 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.14.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
 
package/dist/dom.d.ts CHANGED
@@ -84,11 +84,6 @@ export declare function focus(element: HTMLInputElement | HTMLSelectElement | HT
84
84
  export declare function blur(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
85
85
  export declare function normalizeStringValues(value: unknown): string[] | undefined;
86
86
  export declare function normalizeFileValues(value: unknown): File[] | undefined;
87
- /**
88
- * Retrieves the default value of a form field element by reading the DOM's
89
- * defaultValue, defaultChecked, or defaultSelected properties.
90
- */
91
- export declare function getFieldDefaultValue(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): string | string[] | File | File[] | FileList | null;
92
87
  /**
93
88
  * Updates the DOM element with the provided value and defaultValue.
94
89
  * If the value or defaultValue is undefined, it will keep the current value instead
package/dist/dom.js CHANGED
@@ -405,43 +405,6 @@ function normalizeFileValues(value) {
405
405
  throw new Error('Expected File, FileList or File[] for file input');
406
406
  }
407
407
 
408
- /**
409
- * Retrieves the default value of a form field element by reading the DOM's
410
- * defaultValue, defaultChecked, or defaultSelected properties.
411
- */
412
- function getFieldDefaultValue(element) {
413
- if (isInputElement(element)) {
414
- switch (element.type) {
415
- case 'file':
416
- {
417
- return null;
418
- }
419
- case 'checkbox':
420
- case 'radio':
421
- {
422
- if (element.defaultChecked) {
423
- return element.value;
424
- }
425
- return null;
426
- }
427
- }
428
- } else if (isSelectElement(element)) {
429
- if (element.multiple) {
430
- var values = [];
431
- for (var option of element.options) {
432
- if (option.defaultSelected) {
433
- values.push(option.value);
434
- }
435
- }
436
- return values;
437
- } else {
438
- var selectedOption = Array.from(element.options).find(option => option.defaultSelected);
439
- return selectedOption ? selectedOption.value : null;
440
- }
441
- }
442
- return element.defaultValue;
443
- }
444
-
445
408
  /**
446
409
  * Updates the DOM element with the provided value and defaultValue.
447
410
  * If the value or defaultValue is undefined, it will keep the current value instead
@@ -591,7 +554,6 @@ exports.createFileList = createFileList;
591
554
  exports.createGlobalFormsObserver = createGlobalFormsObserver;
592
555
  exports.createSubmitEvent = createSubmitEvent;
593
556
  exports.focus = focus;
594
- exports.getFieldDefaultValue = getFieldDefaultValue;
595
557
  exports.getFormAction = getFormAction;
596
558
  exports.getFormEncType = getFormEncType;
597
559
  exports.getFormMethod = getFormMethod;
package/dist/dom.mjs CHANGED
@@ -401,43 +401,6 @@ function normalizeFileValues(value) {
401
401
  throw new Error('Expected File, FileList or File[] for file input');
402
402
  }
403
403
 
404
- /**
405
- * Retrieves the default value of a form field element by reading the DOM's
406
- * defaultValue, defaultChecked, or defaultSelected properties.
407
- */
408
- function getFieldDefaultValue(element) {
409
- if (isInputElement(element)) {
410
- switch (element.type) {
411
- case 'file':
412
- {
413
- return null;
414
- }
415
- case 'checkbox':
416
- case 'radio':
417
- {
418
- if (element.defaultChecked) {
419
- return element.value;
420
- }
421
- return null;
422
- }
423
- }
424
- } else if (isSelectElement(element)) {
425
- if (element.multiple) {
426
- var values = [];
427
- for (var option of element.options) {
428
- if (option.defaultSelected) {
429
- values.push(option.value);
430
- }
431
- }
432
- return values;
433
- } else {
434
- var selectedOption = Array.from(element.options).find(option => option.defaultSelected);
435
- return selectedOption ? selectedOption.value : null;
436
- }
437
- }
438
- return element.defaultValue;
439
- }
440
-
441
404
  /**
442
405
  * Updates the DOM element with the provided value and defaultValue.
443
406
  * If the value or defaultValue is undefined, it will keep the current value instead
@@ -581,4 +544,4 @@ function isDirtyInput(element) {
581
544
  return false;
582
545
  }
583
546
 
584
- export { blur, change, createFileList, createGlobalFormsObserver, createSubmitEvent, focus, getFieldDefaultValue, getFormAction, getFormEncType, getFormMethod, isDirtyInput, isFieldElement, isGlobalInstance, isInputElement, isSelectElement, isSubmitter, isTextAreaElement, normalizeFileValues, normalizeStringValues, requestIntent, requestSubmit, updateField };
547
+ export { blur, change, createFileList, createGlobalFormsObserver, createSubmitEvent, focus, getFormAction, getFormEncType, getFormMethod, isDirtyInput, isFieldElement, isGlobalInstance, isInputElement, isSelectElement, isSubmitter, isTextAreaElement, normalizeFileValues, normalizeStringValues, requestIntent, requestSubmit, updateField };
@@ -120,9 +120,15 @@ export declare function parseSubmission(formData: FormData | URLSearchParams, op
120
120
  * Return `true` to skip the entry.
121
121
  */
122
122
  skipEntry?: (name: string) => boolean;
123
+ /**
124
+ * Whether to strip empty values (empty strings, empty files, arrays with all empty values)
125
+ * from the submission payload. Defaults to `true`.
126
+ * Set to `false` to preserve empty values in the payload.
127
+ */
128
+ stripEmptyValues?: boolean;
123
129
  }): Submission;
124
130
  /**
125
- * Creates a SubmissionResult object from a submission, adding validation results and intended values.
131
+ * Creates a SubmissionResult object from a submission, adding validation results and target values.
126
132
  * This function will remove all files in the submission payload by default since
127
133
  * file inputs cannot be initialized with files.
128
134
  * You can specify `keepFiles: true` to keep the files if needed.
@@ -159,7 +165,7 @@ export declare function report<ErrorShape = string>(submission: Submission, opti
159
165
  formErrors?: ErrorShape[];
160
166
  fieldErrors?: Record<string, ErrorShape[]>;
161
167
  } | null;
162
- intendedValue?: Record<string, FormValue> | null;
168
+ value?: Record<string, FormValue> | null;
163
169
  hideFields?: string[];
164
170
  reset?: boolean;
165
171
  }): SubmissionResult<ErrorShape, Exclude<JsonPrimitive | FormDataEntryValue, File>>;
@@ -170,7 +176,7 @@ export declare function report<ErrorShape = string>(submission: Submission, opti
170
176
  formErrors?: ErrorShape[];
171
177
  fieldErrors?: Record<string, ErrorShape[]>;
172
178
  } | null;
173
- intendedValue?: Record<string, FormValue> | null;
179
+ value?: Record<string, FormValue> | null;
174
180
  hideFields?: string[];
175
181
  reset?: boolean;
176
182
  }): SubmissionResult<ErrorShape>;
@@ -181,7 +187,7 @@ export declare function report(submission: Submission, options?: {
181
187
  formErrors?: string[];
182
188
  fieldErrors?: Record<string, string[]>;
183
189
  };
184
- intendedValue?: Record<string, FormValue> | null;
190
+ value?: Record<string, FormValue> | null;
185
191
  hideFields?: string[];
186
192
  reset?: boolean;
187
193
  }): SubmissionResult<string, Exclude<JsonPrimitive | FormDataEntryValue, File>>;
@@ -192,7 +198,7 @@ export declare function report(submission: Submission, options?: {
192
198
  formErrors?: string[];
193
199
  fieldErrors?: Record<string, string[]>;
194
200
  };
195
- intendedValue?: Record<string, FormValue> | null;
201
+ value?: Record<string, FormValue> | null;
196
202
  hideFields?: string[];
197
203
  reset?: boolean;
198
204
  }): SubmissionResult<string>;
package/dist/formdata.js CHANGED
@@ -237,6 +237,28 @@ function getValueAtPath(target, pathOrSegments) {
237
237
  return pointer;
238
238
  }
239
239
 
240
+ /**
241
+ * Check if a form value is considered empty and should be stripped from the submission.
242
+ * A value is empty if:
243
+ * - It's an empty string ""
244
+ * - It's an empty File (size 0 and name "")
245
+ * - It's an array where all items are empty
246
+ */
247
+ function isEmptyValue(value) {
248
+ if (value === '' || value === undefined) {
249
+ return true;
250
+ }
251
+ if (dom.isGlobalInstance(value, 'File')) {
252
+ // Empty File has size 0 and empty name
253
+ return value.size === 0 && value.name === '';
254
+ }
255
+ if (Array.isArray(value)) {
256
+ // If array is empty or all items are empty, consider it empty
257
+ return value.length === 0 || value.every(item => isEmptyValue(item));
258
+ }
259
+ return false;
260
+ }
261
+
240
262
  /**
241
263
  * Parse `FormData` or `URLSearchParams` into a submission object.
242
264
  * This function structures the form values based on the naming convention.
@@ -278,8 +300,30 @@ function parseSubmission(formData, options) {
278
300
  for (var _name of new Set(formData.keys())) {
279
301
  var _options$skipEntry;
280
302
  if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
303
+ var _options$stripEmptyVa;
281
304
  var _value = formData.getAll(_name);
282
- setValueAtPath(submission.payload, _name, _value.length > 1 ? _value : _value[0], {
305
+ var segments = getPathSegments(_name);
306
+
307
+ // If the name ends with [], remove the empty segment and keep the full array
308
+ // Otherwise, unwrap single values
309
+ if (segments.length > 0 && segments[segments.length - 1] === '') {
310
+ segments.pop();
311
+ } else {
312
+ _value = _value.length > 1 ? _value : _value[0];
313
+ }
314
+
315
+ // Check if the value is empty and should be skipped (defaults to true)
316
+ var stripEmptyValues = (_options$stripEmptyVa = options === null || options === void 0 ? void 0 : options.stripEmptyValues) !== null && _options$stripEmptyVa !== void 0 ? _options$stripEmptyVa : true;
317
+ if (stripEmptyValues) {
318
+ // For arrays, filter out individual empty items
319
+ if (Array.isArray(_value)) {
320
+ _value = _value.filter(item => !isEmptyValue(item));
321
+ }
322
+ if (isEmptyValue(_value)) {
323
+ _value = undefined;
324
+ }
325
+ }
326
+ setValueAtPath(submission.payload, segments, _value, {
283
327
  silent: true // Avoid errors if the path is invalid
284
328
  });
285
329
  submission.fields.push(_name);
@@ -296,7 +340,7 @@ function parseSubmission(formData, options) {
296
340
  }
297
341
 
298
342
  /**
299
- * Creates a SubmissionResult object from a submission, adding validation results and intended values.
343
+ * Creates a SubmissionResult object from a submission, adding validation results and target values.
300
344
  * This function will remove all files in the submission payload by default since
301
345
  * file inputs cannot be initialized with files.
302
346
  * You can specify `keepFiles: true` to keep the files if needed.
@@ -328,6 +372,7 @@ function parseSubmission(formData, options) {
328
372
  */
329
373
 
330
374
  function report(submission) {
375
+ var _options$value;
331
376
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
332
377
  var error;
333
378
  if (options.error == null) {
@@ -351,13 +396,13 @@ function report(submission) {
351
396
  }
352
397
  }
353
398
  }
354
- var intendedValue = options.reset ? null : typeof options.intendedValue === 'undefined' || submission.payload === options.intendedValue ? undefined : options.intendedValue && !options.keepFiles ? util.stripFiles(options.intendedValue) : options.intendedValue;
399
+ 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 : {};
355
400
  if (options.hideFields) {
356
401
  for (var _name3 of options.hideFields) {
357
402
  var path = getPathSegments(_name3);
358
403
  setValueAtPath(submission.payload, path, undefined);
359
- if (intendedValue) {
360
- setValueAtPath(intendedValue, path, undefined);
404
+ if (targetValue) {
405
+ setValueAtPath(targetValue, path, undefined);
361
406
  }
362
407
  }
363
408
  }
@@ -365,7 +410,8 @@ function report(submission) {
365
410
  submission: options.keepFiles ? submission : _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission), {}, {
366
411
  payload: util.stripFiles(submission.payload)
367
412
  }),
368
- intendedValue,
413
+ reset: options.reset,
414
+ targetValue,
369
415
  error
370
416
  };
371
417
  }
package/dist/formdata.mjs CHANGED
@@ -233,6 +233,28 @@ function getValueAtPath(target, pathOrSegments) {
233
233
  return pointer;
234
234
  }
235
235
 
236
+ /**
237
+ * Check if a form value is considered empty and should be stripped from the submission.
238
+ * A value is empty if:
239
+ * - It's an empty string ""
240
+ * - It's an empty File (size 0 and name "")
241
+ * - It's an array where all items are empty
242
+ */
243
+ function isEmptyValue(value) {
244
+ if (value === '' || value === undefined) {
245
+ return true;
246
+ }
247
+ if (isGlobalInstance(value, 'File')) {
248
+ // Empty File has size 0 and empty name
249
+ return value.size === 0 && value.name === '';
250
+ }
251
+ if (Array.isArray(value)) {
252
+ // If array is empty or all items are empty, consider it empty
253
+ return value.length === 0 || value.every(item => isEmptyValue(item));
254
+ }
255
+ return false;
256
+ }
257
+
236
258
  /**
237
259
  * Parse `FormData` or `URLSearchParams` into a submission object.
238
260
  * This function structures the form values based on the naming convention.
@@ -274,8 +296,30 @@ function parseSubmission(formData, options) {
274
296
  for (var _name of new Set(formData.keys())) {
275
297
  var _options$skipEntry;
276
298
  if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
299
+ var _options$stripEmptyVa;
277
300
  var _value = formData.getAll(_name);
278
- setValueAtPath(submission.payload, _name, _value.length > 1 ? _value : _value[0], {
301
+ var segments = getPathSegments(_name);
302
+
303
+ // If the name ends with [], remove the empty segment and keep the full array
304
+ // Otherwise, unwrap single values
305
+ if (segments.length > 0 && segments[segments.length - 1] === '') {
306
+ segments.pop();
307
+ } else {
308
+ _value = _value.length > 1 ? _value : _value[0];
309
+ }
310
+
311
+ // Check if the value is empty and should be skipped (defaults to true)
312
+ var stripEmptyValues = (_options$stripEmptyVa = options === null || options === void 0 ? void 0 : options.stripEmptyValues) !== null && _options$stripEmptyVa !== void 0 ? _options$stripEmptyVa : true;
313
+ if (stripEmptyValues) {
314
+ // For arrays, filter out individual empty items
315
+ if (Array.isArray(_value)) {
316
+ _value = _value.filter(item => !isEmptyValue(item));
317
+ }
318
+ if (isEmptyValue(_value)) {
319
+ _value = undefined;
320
+ }
321
+ }
322
+ setValueAtPath(submission.payload, segments, _value, {
279
323
  silent: true // Avoid errors if the path is invalid
280
324
  });
281
325
  submission.fields.push(_name);
@@ -292,7 +336,7 @@ function parseSubmission(formData, options) {
292
336
  }
293
337
 
294
338
  /**
295
- * Creates a SubmissionResult object from a submission, adding validation results and intended values.
339
+ * Creates a SubmissionResult object from a submission, adding validation results and target values.
296
340
  * This function will remove all files in the submission payload by default since
297
341
  * file inputs cannot be initialized with files.
298
342
  * You can specify `keepFiles: true` to keep the files if needed.
@@ -324,6 +368,7 @@ function parseSubmission(formData, options) {
324
368
  */
325
369
 
326
370
  function report(submission) {
371
+ var _options$value;
327
372
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
328
373
  var error;
329
374
  if (options.error == null) {
@@ -347,13 +392,13 @@ function report(submission) {
347
392
  }
348
393
  }
349
394
  }
350
- var intendedValue = options.reset ? null : typeof options.intendedValue === 'undefined' || submission.payload === options.intendedValue ? undefined : options.intendedValue && !options.keepFiles ? stripFiles(options.intendedValue) : options.intendedValue;
395
+ 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 : {};
351
396
  if (options.hideFields) {
352
397
  for (var _name3 of options.hideFields) {
353
398
  var path = getPathSegments(_name3);
354
399
  setValueAtPath(submission.payload, path, undefined);
355
- if (intendedValue) {
356
- setValueAtPath(intendedValue, path, undefined);
400
+ if (targetValue) {
401
+ setValueAtPath(targetValue, path, undefined);
357
402
  }
358
403
  }
359
404
  }
@@ -361,7 +406,8 @@ function report(submission) {
361
406
  submission: options.keepFiles ? submission : _objectSpread2(_objectSpread2({}, submission), {}, {
362
407
  payload: stripFiles(submission.payload)
363
408
  }),
364
- intendedValue,
409
+ reset: options.reset,
410
+ targetValue,
365
411
  error
366
412
  };
367
413
  }
@@ -1,6 +1,6 @@
1
1
  export type { Serialize, FormValue, FormError, Submission, SubmissionResult, ValidationAttributes, } from '../types';
2
2
  export { DEFAULT_INTENT_NAME, getFormData, isDirty, parseSubmission, getPathSegments, formatPathSegments, appendPathSegment, getRelativePath, getValueAtPath, setValueAtPath, report, serialize, } from '../formdata';
3
3
  export { isPlainObject, deepEqual } from '../util';
4
- export { isFieldElement, isGlobalInstance, updateField, createFileList, createSubmitEvent, createGlobalFormsObserver, focus, change, blur, getFieldDefaultValue, getFormAction, getFormEncType, getFormMethod, requestSubmit, requestIntent, } from '../dom';
4
+ export { isFieldElement, isGlobalInstance, updateField, createFileList, createSubmitEvent, createGlobalFormsObserver, focus, change, blur, getFormAction, getFormEncType, getFormMethod, requestSubmit, requestIntent, } from '../dom';
5
5
  export { formatIssues } from '../standard-schema';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -29,7 +29,6 @@ exports.createFileList = dom.createFileList;
29
29
  exports.createGlobalFormsObserver = dom.createGlobalFormsObserver;
30
30
  exports.createSubmitEvent = dom.createSubmitEvent;
31
31
  exports.focus = dom.focus;
32
- exports.getFieldDefaultValue = dom.getFieldDefaultValue;
33
32
  exports.getFormAction = dom.getFormAction;
34
33
  exports.getFormEncType = dom.getFormEncType;
35
34
  exports.getFormMethod = dom.getFormMethod;
@@ -1,4 +1,4 @@
1
1
  export { DEFAULT_INTENT_NAME, appendPathSegment, formatPathSegments, getFormData, getPathSegments, getRelativePath, getValueAtPath, isDirty, parseSubmission, report, serialize, setValueAtPath } from '../formdata.mjs';
2
2
  export { deepEqual, isPlainObject } from '../util.mjs';
3
- export { blur, change, createFileList, createGlobalFormsObserver, createSubmitEvent, focus, getFieldDefaultValue, getFormAction, getFormEncType, getFormMethod, isFieldElement, isGlobalInstance, requestIntent, requestSubmit, updateField } from '../dom.mjs';
3
+ export { blur, change, createFileList, createGlobalFormsObserver, createSubmitEvent, focus, getFormAction, getFormEncType, getFormMethod, isFieldElement, isGlobalInstance, requestIntent, requestSubmit, updateField } from '../dom.mjs';
4
4
  export { formatIssues } from '../standard-schema.mjs';
package/dist/types.d.ts CHANGED
@@ -29,6 +29,21 @@ export type Submission<ValueType extends JsonPrimitive | FormDataEntryValue = Js
29
29
  /**
30
30
  * The submitted values mapped by field name.
31
31
  * Supports nested names like `user.email` or indexed names like `items[0].id`.
32
+ *
33
+ * @example
34
+ * ```json
35
+ * {
36
+ * "username": "johndoe",
37
+ * "address": {
38
+ * "street": "123 Main St",
39
+ * "city": "Anytown"
40
+ * },
41
+ * "items": [
42
+ * { "name": "item1", "quantity": "2" },
43
+ * { "name": "item2", "quantity": "5" }
44
+ * ]
45
+ * }
46
+ * ```
32
47
  */
33
48
  payload: Record<string, FormValue<ValueType>>;
34
49
  /**
@@ -49,27 +64,31 @@ export type SubmissionResult<ErrorShape = string, ValueType extends JsonPrimitiv
49
64
  */
50
65
  submission: Submission<ValueType>;
51
66
  /**
52
- * The intended value of the submission. Defined only when the intended value is different from the submitted value.
67
+ * The target value of the submission. Defined only when the target value is different from the submitted value.
53
68
  */
54
- intendedValue?: Record<string, FormValue<ValueType>> | null;
69
+ targetValue?: Record<string, FormValue<ValueType>>;
55
70
  /**
56
- * Validation errors for `intendedValue` when present, otherwise for the original payload.
71
+ * Validation errors for `targetValue` when present, otherwise for the original payload.
57
72
  */
58
73
  error?: FormError<ErrorShape> | null;
74
+ /**
75
+ * Indicates whether the form should be reset to its initial state.
76
+ */
77
+ reset?: boolean;
59
78
  };
60
79
  /**
61
80
  * The input attributes with related to the Constraint Validation API
62
81
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
63
82
  */
64
83
  export type ValidationAttributes = {
65
- required?: boolean;
66
- minLength?: number;
67
- maxLength?: number;
68
- min?: string | number;
69
- max?: string | number;
70
- step?: string | number;
71
- multiple?: boolean;
72
- pattern?: string;
84
+ required?: boolean | undefined;
85
+ minLength?: number | undefined;
86
+ maxLength?: number | undefined;
87
+ min?: string | number | undefined;
88
+ max?: string | number | undefined;
89
+ step?: string | number | undefined;
90
+ multiple?: boolean | undefined;
91
+ pattern?: string | undefined;
73
92
  };
74
93
  /**
75
94
  * A type helper that makes sure the FormError type is serializable.
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.13.2",
6
+ "version": "1.14.0",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",