@conform-to/dom 1.17.0 → 1.18.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/dist/form.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { appendPathSegment, getValueAtPath, isPrefix, setValueAtPath, getFormData, getPathSegments, getRelativePath, formatPathSegments } from './formdata.mjs';
2
+ import { appendPath, getPathValue, isPathPrefix, setPathValue, getFormData, parsePath, getRelativePath, formatPath } from './formdata.mjs';
3
3
  import { getFormAction, getFormEncType, getFormMethod, isFieldElement, requestSubmit, isDirtyInput, updateField } from './dom.mjs';
4
4
  import { generateId, clone, isPlainObject, invariant } from './util.mjs';
5
5
  import { serialize, flatten, setListState, setListValue, setState, normalize, INTENT, serializeIntent, root, getSubmissionContext } from './submission.mjs';
@@ -39,7 +39,7 @@ function getDefaultKey(defaultValue, prefix) {
39
39
  var [key, value] = _ref2;
40
40
  if (Array.isArray(value)) {
41
41
  for (var i = 0; i < value.length; i++) {
42
- result[appendPathSegment(key, i)] = generateId();
42
+ result[appendPath(key, i)] = generateId();
43
43
  }
44
44
  }
45
45
  return result;
@@ -72,7 +72,7 @@ function handleIntent(meta, intent, fields, initialized) {
72
72
  validated,
73
73
  value
74
74
  } = intent.payload;
75
- var _name2 = appendPathSegment(intent.payload.name, intent.payload.index);
75
+ var _name2 = appendPath(intent.payload.name, intent.payload.index);
76
76
  if (typeof value !== 'undefined') {
77
77
  updateValue(meta, _name2 !== null && _name2 !== void 0 ? _name2 : '', value);
78
78
  }
@@ -101,8 +101,8 @@ function handleIntent(meta, intent, fields, initialized) {
101
101
  }
102
102
  case 'reset':
103
103
  {
104
- var _name3 = appendPathSegment(intent.payload.name, intent.payload.index);
105
- var _value = getValueAtPath(meta.defaultValue, _name3);
104
+ var _name3 = appendPath(intent.payload.name, intent.payload.index);
105
+ var _value = getPathValue(meta.defaultValue, _name3);
106
106
  updateValue(meta, _name3, _value);
107
107
  if (_name3) {
108
108
  setState(meta.validated, _name3, () => undefined);
@@ -137,7 +137,7 @@ function handleIntent(meta, intent, fields, initialized) {
137
137
  var validatedFields = (_fields$filter = fields === null || fields === void 0 ? void 0 : fields.filter(name => meta.validated[name])) !== null && _fields$filter !== void 0 ? _fields$filter : [];
138
138
  meta.error = Object.entries(meta.error).reduce((result, _ref3) => {
139
139
  var [name, error] = _ref3;
140
- if (meta.validated[name] || validatedFields.some(field => isPrefix(name, field))) {
140
+ if (meta.validated[name] || validatedFields.some(field => isPathPrefix(name, field))) {
141
141
  result[name] = error;
142
142
  }
143
143
  return result;
@@ -155,8 +155,8 @@ function updateValue(meta, name, value) {
155
155
  meta.initialValue = clone(meta.initialValue);
156
156
  meta.value = clone(meta.value);
157
157
  meta.key = clone(meta.key);
158
- setValueAtPath(meta.initialValue, name, () => value);
159
- setValueAtPath(meta.value, name, () => value);
158
+ setPathValue(meta.initialValue, name, () => value);
159
+ setPathValue(meta.value, name, () => value);
160
160
  if (isPlainObject(value) || Array.isArray(value)) {
161
161
  setState(meta.key, name, () => undefined);
162
162
  Object.assign(meta.key, getDefaultKey(value, name));
@@ -181,11 +181,11 @@ function createValueProxy(value) {
181
181
  if (name === '') {
182
182
  return val;
183
183
  }
184
- var path = getPathSegments(name);
185
- var basename = formatPathSegments(path.slice(0, -1));
186
- var key = formatPathSegments(path.slice(-1));
184
+ var path = parsePath(name);
185
+ var basename = formatPath(path.slice(0, -1));
186
+ var key = formatPath(path.slice(-1));
187
187
  var parentValue = proxy[basename];
188
- return getValueAtPath(parentValue, key);
188
+ return getPathValue(parentValue, key);
189
189
  });
190
190
  }
191
191
  function createConstraintProxy(constraint) {
@@ -193,7 +193,7 @@ function createConstraintProxy(constraint) {
193
193
  var _result;
194
194
  var result = constraint[name];
195
195
  if (!result) {
196
- var path = getPathSegments(name);
196
+ var path = parsePath(name);
197
197
  for (var i = path.length - 1; i >= 0; i--) {
198
198
  var segment = path[i];
199
199
 
@@ -206,7 +206,7 @@ function createConstraintProxy(constraint) {
206
206
  break;
207
207
  }
208
208
  }
209
- var alternative = formatPathSegments(path);
209
+ var alternative = formatPath(path);
210
210
  if (name !== alternative) {
211
211
  result = proxy[alternative];
212
212
  }
@@ -217,11 +217,11 @@ function createConstraintProxy(constraint) {
217
217
  function createKeyProxy(key) {
218
218
  return createStateProxy((name, proxy) => {
219
219
  var currentKey = key[name];
220
- var segments = getPathSegments(name);
220
+ var segments = parsePath(name);
221
221
  if (segments.length === 0) {
222
222
  return currentKey;
223
223
  }
224
- var parentKey = proxy[formatPathSegments(segments.slice(0, -1))];
224
+ var parentKey = proxy[formatPath(segments.slice(0, -1))];
225
225
  if (typeof parentKey === 'undefined') {
226
226
  return currentKey;
227
227
  }
@@ -235,7 +235,7 @@ function createValidProxy(error) {
235
235
  return keys.length === 0;
236
236
  }
237
237
  for (var key of keys) {
238
- if (isPrefix(key, name) && typeof error[key] !== 'undefined') {
238
+ if (isPathPrefix(key, name) && typeof error[key] !== 'undefined') {
239
239
  return false;
240
240
  }
241
241
  }
@@ -266,7 +266,7 @@ function shouldNotify(prev, next, cache, scope) {
266
266
  var names = (_scope$name = scope.name) !== null && _scope$name !== void 0 ? _scope$name : [];
267
267
  var list = prefixes.length === 0 ? names : Array.from(new Set([...Object.keys(prev), ...Object.keys(next)]));
268
268
  var _loop = function _loop(_name4) {
269
- if (prefixes.length === 0 || names.includes(_name4) || prefixes.some(prefix => isPrefix(_name4, prefix))) {
269
+ if (prefixes.length === 0 || names.includes(_name4) || prefixes.some(prefix => isPathPrefix(_name4, prefix))) {
270
270
  var _cache$_name;
271
271
  (_cache$_name = cache[_name4]) !== null && _cache$_name !== void 0 ? _cache$_name : cache[_name4] = compareFn(prev[_name4], next[_name4]);
272
272
  if (cache[_name4]) {
@@ -576,13 +576,13 @@ function createFormContext(options) {
576
576
  switch (intent.type) {
577
577
  case 'update':
578
578
  {
579
- var _name5 = appendPathSegment(intent.payload.name, intent.payload.index);
580
- var baseSegments = getPathSegments(_name5);
579
+ var _name5 = appendPath(intent.payload.name, intent.payload.index);
580
+ var baseSegments = parsePath(_name5);
581
581
  for (var element of formElement.elements) {
582
582
  if (isFieldElement(element)) {
583
583
  var paths = getRelativePath(element.name, baseSegments);
584
584
  if (paths) {
585
- var value = getValueAtPath(intent.payload.value, paths);
585
+ var value = getPathValue(intent.payload.value, paths);
586
586
  var inputValue = typeof value === 'string' || Array.isArray(value) && value.every(item => typeof item === 'string') ? value : undefined;
587
587
  if (typeof inputValue !== 'undefined' || _name5 === '' && paths.length > 1) {
588
588
  updateField(element, {
@@ -599,10 +599,10 @@ function createFormContext(options) {
599
599
  }
600
600
  case 'reset':
601
601
  {
602
- var prefix = appendPathSegment(intent.payload.name, intent.payload.index);
602
+ var prefix = appendPath(intent.payload.name, intent.payload.index);
603
603
  for (var _element of formElement.elements) {
604
- if (isFieldElement(_element) && _element.name && isPrefix(_element.name, prefix)) {
605
- var _value2 = getValueAtPath(meta.defaultValue, _element.name);
604
+ if (isFieldElement(_element) && _element.name && isPathPrefix(_element.name, prefix)) {
605
+ var _value2 = getPathValue(meta.defaultValue, _element.name);
606
606
  var defaultValue = typeof _value2 === 'string' || Array.isArray(_value2) && _value2.every(item => typeof item === 'string') ? _value2 : undefined;
607
607
  if (typeof defaultValue === 'undefined' && !_element.dataset.conform && 'defaultValue' in _element && !isDirtyInput(_element)) {
608
608
  continue;
@@ -14,23 +14,23 @@ export declare function getFormData(form: HTMLFormElement, submitter?: HTMLEleme
14
14
  *
15
15
  * @example
16
16
  * ```js
17
- * getPathSegments("object.key"); // → ['object', 'key']
18
- * getPathSegments("array[0].content"); // → ['array', 0, 'content']
19
- * getPathSegments("todos[]"); // → ['todos', '']
17
+ * parsePath("object.key"); // → ['object', 'key']
18
+ * parsePath("array[0].content"); // → ['array', 0, 'content']
19
+ * parsePath("todos[]"); // → ['todos', '']
20
20
  * ```
21
21
  */
22
- export declare function getPathSegments(path: string | undefined): Array<string | number>;
22
+ export declare function parsePath(path: string | undefined): Array<string | number>;
23
23
  /**
24
24
  * Returns a formatted name from the path segments based on the dot and bracket notation.
25
25
  *
26
26
  * @example
27
27
  * ```js
28
- * formatPathSegments(['object', 'key']); // → "object.key"
29
- * formatPathSegments(['array', 0, 'content']); // → "array[0].content"
30
- * formatPathSegments(['todos', '']); // → "todos[]"
28
+ * formatPath(['object', 'key']); // → "object.key"
29
+ * formatPath(['array', 0, 'content']); // → "array[0].content"
30
+ * formatPath(['todos', '']); // → "todos[]"
31
31
  * ```
32
32
  */
33
- export declare function formatPathSegments(segments: Readonly<Array<string | number>>): string;
33
+ export declare function formatPath(segments: Readonly<Array<string | number>>): string;
34
34
  /**
35
35
  * Append one more segment onto an existing path string.
36
36
  *
@@ -39,26 +39,26 @@ export declare function formatPathSegments(segments: Readonly<Array<string | num
39
39
  * - segment = `number` ⇒ bracket notation "[n]"
40
40
  * - segment = `string` ⇒ dot-notation ".prop"
41
41
  */
42
- export declare function appendPathSegment(path: string | undefined, segment: string | number | undefined): string;
42
+ export declare function appendPath(path: string | undefined, segment: string | number | undefined): string;
43
43
  /**
44
44
  * Returns true if `prefix` is a valid leading path of `name`.
45
45
  *
46
46
  * @example
47
47
  * ```js
48
- * isPrefix("foo.bar.baz", "foo.bar") // → true
49
- * isPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
50
- * isPrefix("foo.bar[3].baz", "foo.bar") // → true
51
- * isPrefix("foo.bar[3].baz", "foo.baz") // → false
52
- * isPrefix("foo", "foo.bar") // → false
48
+ * isPathPrefix("foo.bar.baz", "foo.bar") // → true
49
+ * isPathPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
50
+ * isPathPrefix("foo.bar[3].baz", "foo.bar") // → true
51
+ * isPathPrefix("foo.bar[3].baz", "foo.baz") // → false
52
+ * isPathPrefix("foo", "foo.bar") // → false
53
53
  * ```
54
54
  */
55
- export declare function isPrefix(name: string, prefix: string): boolean;
55
+ export declare function isPathPrefix(name: string, prefix: string): boolean;
56
56
  /**
57
- * Return the segments of `fullPathStr` that come after the `baseSegments` prefix.
57
+ * Return the segments of `fullPath` that come after the `basePath` prefix.
58
58
  *
59
- * @param fullPathStr Full path as a dot/bracket string
60
- * @param basePath Base path, already parsed into segments
61
- * @returns The “tail” segments, or `null` if `fullPathStr` isn’t nested under `baseSegments`
59
+ * @param fullPath Full path as a dot/bracket string or array of segments
60
+ * @param basePath Base path as a dot/bracket string or array of segments
61
+ * @returns The “tail” segments, or `null` if `fullPath` isn’t nested under `basePath`
62
62
  *
63
63
  * @example
64
64
  * ```js
@@ -67,18 +67,18 @@ export declare function isPrefix(name: string, prefix: string): boolean;
67
67
  * getRelativePath("foo", ["foo","bar"]) // → null
68
68
  * ```
69
69
  */
70
- export declare function getRelativePath(name: string, basePath: Array<string | number>): Array<string | number> | null;
70
+ export declare function getRelativePath(fullPath: string | Array<string | number>, basePath: string | Array<string | number>): Array<string | number> | null;
71
71
  /**
72
72
  * Assign a value to a target object by following the path segments.
73
73
  */
74
- export declare function setValueAtPath<T extends Record<string, any>>(target: T, pathOrSegments: string | Array<string | number>, valueOrFn: unknown | ((current: unknown) => unknown), options?: {
74
+ export declare function setPathValue<T extends Record<string, any>>(target: T, pathOrSegments: string | Array<string | number>, valueOrFn: unknown | ((current: unknown) => unknown), options?: {
75
75
  clone?: boolean;
76
76
  silent?: boolean;
77
77
  }): T;
78
78
  /**
79
79
  * Retrive the value from a target object by following the path segments.
80
80
  */
81
- export declare function getValueAtPath(target: unknown, pathOrSegments: string | Array<string | number>): unknown;
81
+ export declare function getPathValue(target: unknown, pathOrSegments: string | Array<string | number>): unknown;
82
82
  /**
83
83
  * Parse `FormData` or `URLSearchParams` into a submission object.
84
84
  * This function structures the form values based on the naming convention.
@@ -122,8 +122,11 @@ export declare function parseSubmission(formData: FormData | URLSearchParams, op
122
122
  skipEntry?: (name: string) => boolean;
123
123
  /**
124
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.
125
+ * from the submission payload. Defaults to `false`.
126
+ *
127
+ * @deprecated This option will be removed in a future minor release.
128
+ * If you are using a schema library like Zod or Valibot, our integration
129
+ * already strips empty values for you. There is no need to use this option.
127
130
  */
128
131
  stripEmptyValues?: boolean;
129
132
  }): Submission;
@@ -252,7 +255,7 @@ formData: FormData | URLSearchParams | FormValue | null, options?: {
252
255
  * - number / bigint:
253
256
  * - Converted to string using `.toString()`
254
257
  * - Date:
255
- * - Converted to ISO string using `.toISOString()`
258
+ * - Converted to UTC datetime string without trailing `Z` (e.g. `2026-01-01T12:00:00.000`)
256
259
  */
257
260
  serialize?: (value: unknown, defaultSerialize: Serialize) => SerializedValue | null | undefined;
258
261
  /**
@@ -278,13 +281,25 @@ formData: FormData | URLSearchParams | FormValue | null, options?: {
278
281
  * - null -> '' (empty string)
279
282
  * - boolean -> 'on' | '' (checked semantics)
280
283
  * - number | bigint -> value.toString()
281
- * - Date -> value.toISOString()
284
+ * - Date -> value.toISOString() without trailing `Z`
282
285
  * - File -> File
283
286
  * - FileList -> File[]
284
287
  * - Array -> string[] or File[] if all items serialize to the same kind; otherwise undefined
285
288
  * - anything else -> undefined
286
289
  */
287
290
  export declare function serialize(value: unknown): SerializedValue | null | undefined;
291
+ /**
292
+ * Recursively serializes a value using the provided serialize function,
293
+ * collapsing empty leaves (`null`, `''`, empty files) to `undefined`
294
+ * and removing empty containers (objects with no remaining keys, empty arrays).
295
+ *
296
+ * When serialize returns `undefined` for a value (i.e. it can't be represented
297
+ * as form data), the raw value is kept and recursed into if it's an object or array.
298
+ *
299
+ * Single-element arrays where the element is a string or undefined are unwrapped
300
+ * to handle the case where a multi-value field (e.g. checkboxes) has only one value.
301
+ */
302
+ export declare function normalize(value: unknown, serializeValue?: Serialize): unknown;
288
303
  /**
289
304
  * Retrieve a field value from FormData with optional type guards.
290
305
  *
package/dist/formdata.js CHANGED
@@ -39,12 +39,12 @@ function getFormData(form, submitter) {
39
39
  *
40
40
  * @example
41
41
  * ```js
42
- * getPathSegments("object.key"); // → ['object', 'key']
43
- * getPathSegments("array[0].content"); // → ['array', 0, 'content']
44
- * getPathSegments("todos[]"); // → ['todos', '']
42
+ * parsePath("object.key"); // → ['object', 'key']
43
+ * parsePath("array[0].content"); // → ['array', 0, 'content']
44
+ * parsePath("todos[]"); // → ['todos', '']
45
45
  * ```
46
46
  */
47
- function getPathSegments(path) {
47
+ function parsePath(path) {
48
48
  if (!path) return [];
49
49
  var tokenRegex = /([^.[\]]+)|\[(\d*)\]/g;
50
50
  var segments = [];
@@ -85,13 +85,13 @@ function getPathSegments(path) {
85
85
  *
86
86
  * @example
87
87
  * ```js
88
- * formatPathSegments(['object', 'key']); // → "object.key"
89
- * formatPathSegments(['array', 0, 'content']); // → "array[0].content"
90
- * formatPathSegments(['todos', '']); // → "todos[]"
88
+ * formatPath(['object', 'key']); // → "object.key"
89
+ * formatPath(['array', 0, 'content']); // → "array[0].content"
90
+ * formatPath(['todos', '']); // → "todos[]"
91
91
  * ```
92
92
  */
93
- function formatPathSegments(segments) {
94
- return segments.reduce((path, segment) => appendPathSegment(path, segment), '');
93
+ function formatPath(segments) {
94
+ return segments.reduce((path, segment) => appendPath(path, segment), '');
95
95
  }
96
96
 
97
97
  /**
@@ -102,7 +102,7 @@ function formatPathSegments(segments) {
102
102
  * - segment = `number` ⇒ bracket notation "[n]"
103
103
  * - segment = `string` ⇒ dot-notation ".prop"
104
104
  */
105
- function appendPathSegment(path, segment) {
105
+ function appendPath(path, segment) {
106
106
  // 1) nothing to append
107
107
  if (typeof segment === 'undefined') {
108
108
  return path !== null && path !== void 0 ? path : '';
@@ -128,23 +128,23 @@ function appendPathSegment(path, segment) {
128
128
  *
129
129
  * @example
130
130
  * ```js
131
- * isPrefix("foo.bar.baz", "foo.bar") // → true
132
- * isPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
133
- * isPrefix("foo.bar[3].baz", "foo.bar") // → true
134
- * isPrefix("foo.bar[3].baz", "foo.baz") // → false
135
- * isPrefix("foo", "foo.bar") // → false
131
+ * isPathPrefix("foo.bar.baz", "foo.bar") // → true
132
+ * isPathPrefix("foo.bar[3].baz", "foo.bar[3]") // → true
133
+ * isPathPrefix("foo.bar[3].baz", "foo.bar") // → true
134
+ * isPathPrefix("foo.bar[3].baz", "foo.baz") // → false
135
+ * isPathPrefix("foo", "foo.bar") // → false
136
136
  * ```
137
137
  */
138
- function isPrefix(name, prefix) {
139
- return getRelativePath(name, getPathSegments(prefix)) !== null;
138
+ function isPathPrefix(name, prefix) {
139
+ return getRelativePath(name, parsePath(prefix)) !== null;
140
140
  }
141
141
 
142
142
  /**
143
- * Return the segments of `fullPathStr` that come after the `baseSegments` prefix.
143
+ * Return the segments of `fullPath` that come after the `basePath` prefix.
144
144
  *
145
- * @param fullPathStr Full path as a dot/bracket string
146
- * @param basePath Base path, already parsed into segments
147
- * @returns The “tail” segments, or `null` if `fullPathStr` isn’t nested under `baseSegments`
145
+ * @param fullPath Full path as a dot/bracket string or array of segments
146
+ * @param basePath Base path as a dot/bracket string or array of segments
147
+ * @returns The “tail” segments, or `null` if `fullPath` isn’t nested under `basePath`
148
148
  *
149
149
  * @example
150
150
  * ```js
@@ -153,12 +153,13 @@ function isPrefix(name, prefix) {
153
153
  * getRelativePath("foo", ["foo","bar"]) // → null
154
154
  * ```
155
155
  */
156
- function getRelativePath(name, basePath) {
157
- var fullPath = getPathSegments(name);
156
+ function getRelativePath(fullPath, basePath) {
157
+ var fullPathSegments = typeof fullPath === 'string' ? parsePath(fullPath) : fullPath;
158
+ var basePathSegments = typeof basePath === 'string' ? parsePath(basePath) : basePath;
158
159
 
159
160
  // if full is at least as long *and* starts with the base…
160
- if (fullPath.length >= basePath.length && basePath.every((segment, i) => segment === fullPath[i])) {
161
- return fullPath.slice(basePath.length);
161
+ if (fullPathSegments.length >= basePathSegments.length && basePathSegments.every((segment, i) => segment === fullPathSegments[i])) {
162
+ return fullPathSegments.slice(basePathSegments.length);
162
163
  }
163
164
  return null;
164
165
  }
@@ -166,11 +167,11 @@ function getRelativePath(name, basePath) {
166
167
  /**
167
168
  * Assign a value to a target object by following the path segments.
168
169
  */
169
- function setValueAtPath(target, pathOrSegments, valueOrFn) {
170
+ function setPathValue(target, pathOrSegments, valueOrFn) {
170
171
  var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
171
172
  try {
172
173
  // 1) normalize + validate path
173
- var segments = typeof pathOrSegments === 'string' ? getPathSegments(pathOrSegments) : pathOrSegments;
174
+ var segments = typeof pathOrSegments === 'string' ? parsePath(pathOrSegments) : pathOrSegments;
174
175
  if (segments.length === 0) {
175
176
  throw new Error('Cannot set value at the object root');
176
177
  }
@@ -222,9 +223,9 @@ function setValueAtPath(target, pathOrSegments, valueOrFn) {
222
223
  /**
223
224
  * Retrive the value from a target object by following the path segments.
224
225
  */
225
- function getValueAtPath(target, pathOrSegments) {
226
+ function getPathValue(target, pathOrSegments) {
226
227
  var pointer = target;
227
- var segments = typeof pathOrSegments === 'string' ? getPathSegments(pathOrSegments) : pathOrSegments;
228
+ var segments = typeof pathOrSegments === 'string' ? parsePath(pathOrSegments) : pathOrSegments;
228
229
  for (var segment of segments) {
229
230
  if (segment === '') {
230
231
  throw new Error("Cannot access empty segment \"[]\" in \"".concat(pathOrSegments, "\""));
@@ -302,7 +303,7 @@ function parseSubmission(formData, options) {
302
303
  if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
303
304
  var _options$stripEmptyVa;
304
305
  var _value = formData.getAll(_name);
305
- var segments = getPathSegments(_name);
306
+ var segments = parsePath(_name);
306
307
 
307
308
  // If the name ends with [], remove the empty segment and keep the full array
308
309
  // Otherwise, unwrap single values
@@ -311,9 +312,7 @@ function parseSubmission(formData, options) {
311
312
  } else {
312
313
  _value = _value.length > 1 ? _value : _value[0];
313
314
  }
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;
315
+ var stripEmptyValues = (_options$stripEmptyVa = options === null || options === void 0 ? void 0 : options.stripEmptyValues) !== null && _options$stripEmptyVa !== void 0 ? _options$stripEmptyVa : false;
317
316
  if (stripEmptyValues) {
318
317
  // For arrays, filter out individual empty items
319
318
  if (Array.isArray(_value)) {
@@ -323,7 +322,7 @@ function parseSubmission(formData, options) {
323
322
  _value = undefined;
324
323
  }
325
324
  }
326
- setValueAtPath(submission.payload, segments, _value, {
325
+ setPathValue(submission.payload, segments, _value, {
327
326
  silent: true // Avoid errors if the path is invalid
328
327
  });
329
328
  submission.fields.push(_name);
@@ -399,10 +398,10 @@ function report(submission) {
399
398
  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 : {};
400
399
  if (options.hideFields) {
401
400
  for (var _name3 of options.hideFields) {
402
- var path = getPathSegments(_name3);
403
- setValueAtPath(submission.payload, path, undefined);
401
+ var path = parsePath(_name3);
402
+ setPathValue(submission.payload, path, undefined);
404
403
  if (targetValue) {
405
- setValueAtPath(targetValue, path, undefined);
404
+ setPathValue(targetValue, path, undefined);
406
405
  }
407
406
  }
408
407
  }
@@ -452,58 +451,8 @@ formData, options) {
452
451
  skipEntry: options === null || options === void 0 ? void 0 : options.skipEntry
453
452
  }).payload : formData;
454
453
  var defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;
455
- var serializeData = value => {
456
- if (options !== null && options !== void 0 && options.serialize) {
457
- return options.serialize(value, serialize);
458
- }
459
- return serialize(value);
460
- };
461
- function normalize(data) {
462
- var value = serializeData(data);
463
- if (typeof value === 'undefined') {
464
- value = data;
465
- }
466
-
467
- // Removes empty strings, so that both empty string, empty file, null and undefined are treated as the same
468
- if (value === '' || value === null) {
469
- return undefined;
470
- }
471
- if (dom.isGlobalInstance(value, 'File')) {
472
- // Remove empty File as well, which happens if no File was selected
473
- if (value.name === '' && value.size === 0) {
474
- return undefined;
475
- }
476
-
477
- // If the value is a File, no need to serialize it
478
- return value;
479
- }
480
- if (Array.isArray(value)) {
481
- if (value.length === 0) {
482
- return undefined;
483
- }
484
- var array = value.map(normalize);
485
- if (array.length === 1 && (typeof array[0] === 'string' || array[0] === undefined)) {
486
- return array[0];
487
- }
488
- return array;
489
- }
490
- if (util.isPlainObject(value)) {
491
- var entries = Object.entries(value).reduce((list, _ref) => {
492
- var [key, value] = _ref;
493
- var normalizedValue = normalize(value);
494
- if (typeof normalizedValue !== 'undefined') {
495
- list.push([key, normalizedValue]);
496
- }
497
- return list;
498
- }, []);
499
- if (entries.length === 0) {
500
- return undefined;
501
- }
502
- return Object.fromEntries(entries);
503
- }
504
- return value;
505
- }
506
- return !util.deepEqual(normalize(formValue), normalize(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));
507
456
  }
508
457
 
509
458
  /**
@@ -515,7 +464,7 @@ formData, options) {
515
464
  * - null -> '' (empty string)
516
465
  * - boolean -> 'on' | '' (checked semantics)
517
466
  * - number | bigint -> value.toString()
518
- * - Date -> value.toISOString()
467
+ * - Date -> value.toISOString() without trailing `Z`
519
468
  * - File -> File
520
469
  * - FileList -> File[]
521
470
  * - Array -> string[] or File[] if all items serialize to the same kind; otherwise undefined
@@ -533,7 +482,7 @@ function serialize(value) {
533
482
  return value.toString();
534
483
  }
535
484
  if (value instanceof Date) {
536
- return value.toISOString();
485
+ return value.toISOString().slice(0, -1);
537
486
  }
538
487
  if (dom.isGlobalInstance(value, 'File')) {
539
488
  return value;
@@ -576,6 +525,59 @@ function serialize(value) {
576
525
  return serializePrimitive(value);
577
526
  }
578
527
 
528
+ /**
529
+ * Recursively serializes a value using the provided serialize function,
530
+ * collapsing empty leaves (`null`, `''`, empty files) to `undefined`
531
+ * and removing empty containers (objects with no remaining keys, empty arrays).
532
+ *
533
+ * When serialize returns `undefined` for a value (i.e. it can't be represented
534
+ * as form data), the raw value is kept and recursed into if it's an object or array.
535
+ *
536
+ * Single-element arrays where the element is a string or undefined are unwrapped
537
+ * to handle the case where a multi-value field (e.g. checkboxes) has only one value.
538
+ */
539
+ function normalize(value) {
540
+ var serializeValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : serialize;
541
+ var data = serializeValue(value);
542
+ if (typeof data === 'undefined') {
543
+ data = value;
544
+ }
545
+ if (data === '' || data === null) {
546
+ return undefined;
547
+ }
548
+ if (dom.isGlobalInstance(data, 'File')) {
549
+ if (data.name === '' && data.size === 0) {
550
+ return undefined;
551
+ }
552
+ return data;
553
+ }
554
+ if (Array.isArray(data)) {
555
+ if (data.length === 0) {
556
+ return undefined;
557
+ }
558
+ var array = data.map(item => normalize(item, serializeValue));
559
+ if (array.length === 1 && (typeof array[0] === 'string' || array[0] === undefined)) {
560
+ return array[0];
561
+ }
562
+ return array;
563
+ }
564
+ if (util.isPlainObject(data)) {
565
+ var entries = Object.entries(data).reduce((list, _ref) => {
566
+ var [key, value] = _ref;
567
+ var normalizedValue = normalize(value, serializeValue);
568
+ if (typeof normalizedValue !== 'undefined') {
569
+ list.push([key, normalizedValue]);
570
+ }
571
+ return list;
572
+ }, []);
573
+ if (entries.length === 0) {
574
+ return undefined;
575
+ }
576
+ return Object.fromEntries(entries);
577
+ }
578
+ return data;
579
+ }
580
+
579
581
  /**
580
582
  * Retrieve a field value from FormData with optional type guards.
581
583
  *
@@ -609,11 +611,9 @@ function getFieldValue(formData, name, options) {
609
611
  // Get value based on array option
610
612
  value = array ? formData.getAll(name) : formData.get(name);
611
613
  } else {
612
- // Parse formData and use getValueAtPath
613
- var _submission = parseSubmission(formData, {
614
- stripEmptyValues: false
615
- });
616
- value = getValueAtPath(_submission.payload, name);
614
+ // Parse formData and use getPathValue
615
+ var _submission = parseSubmission(formData);
616
+ value = getPathValue(_submission.payload, name);
617
617
  }
618
618
 
619
619
  // If optional and value is undefined, skip validation and return early
@@ -648,16 +648,17 @@ function getFieldValue(formData, name, options) {
648
648
  }
649
649
 
650
650
  exports.DEFAULT_INTENT_NAME = DEFAULT_INTENT_NAME;
651
- exports.appendPathSegment = appendPathSegment;
652
- exports.formatPathSegments = formatPathSegments;
651
+ exports.appendPath = appendPath;
652
+ exports.formatPath = formatPath;
653
653
  exports.getFieldValue = getFieldValue;
654
654
  exports.getFormData = getFormData;
655
- exports.getPathSegments = getPathSegments;
655
+ exports.getPathValue = getPathValue;
656
656
  exports.getRelativePath = getRelativePath;
657
- exports.getValueAtPath = getValueAtPath;
658
657
  exports.isDirty = isDirty;
659
- exports.isPrefix = isPrefix;
658
+ exports.isPathPrefix = isPathPrefix;
659
+ exports.normalize = normalize;
660
+ exports.parsePath = parsePath;
660
661
  exports.parseSubmission = parseSubmission;
661
662
  exports.report = report;
662
663
  exports.serialize = serialize;
663
- exports.setValueAtPath = setValueAtPath;
664
+ exports.setPathValue = setPathValue;