@conform-to/dom 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.7.2 / License MIT / Copyright (c) 2024 Edmund Hung
10
+ Version 1.8.1 / License MIT / Copyright (c) 2024 Edmund Hung
11
11
 
12
12
  A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
13
13
 
package/dist/dom.d.ts CHANGED
@@ -53,9 +53,11 @@ export declare function createGlobalFormsObserver(): {
53
53
  export declare function change(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, value: string | string[] | File | File[] | FileList | null): void;
54
54
  export declare function focus(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
55
55
  export declare function blur(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
56
- export declare function normalizeFieldValue(value: unknown): [string[] | null, FileList | null];
56
+ export declare function normalizeStringValues(value: unknown): string[] | undefined;
57
+ export declare function normalizeFileValues(value: unknown): FileList | undefined;
57
58
  /**
58
59
  * Updates the DOM element with the provided value and defaultValue.
60
+ * If the value or defaultValue is undefined, it will keep the current value instead
59
61
  */
60
62
  export declare function updateField(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, options: {
61
63
  value?: unknown;
package/dist/dom.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var formdata = require('./formdata.js');
5
6
  var util = require('./util.js');
6
7
 
7
8
  /**
@@ -121,6 +122,8 @@ function createGlobalFormsObserver() {
121
122
  observer.observe(document.body, {
122
123
  subtree: true,
123
124
  childList: true,
125
+ attributes: true,
126
+ attributeOldValue: true,
124
127
  attributeFilter: ['form', 'name', 'data-conform']
125
128
  });
126
129
  document.addEventListener('input', handleInput);
@@ -187,6 +190,15 @@ function createGlobalFormsObserver() {
187
190
  }));
188
191
  }
189
192
  }
193
+ function getAssociatedFormElement(formId, node) {
194
+ if (formId !== null) {
195
+ return document.forms.namedItem(formId);
196
+ }
197
+ if (node instanceof Element) {
198
+ return node.closest('form');
199
+ }
200
+ return null;
201
+ }
190
202
  function handleMutation(mutations) {
191
203
  var seenForms = new Set();
192
204
  var seenInputs = new Set();
@@ -196,16 +208,27 @@ function createGlobalFormsObserver() {
196
208
  }
197
209
  return node instanceof Element ? Array.from(node.querySelectorAll('input,select,textarea')) : [];
198
210
  };
211
+ var collectForms = node => {
212
+ if (node instanceof HTMLFormElement) {
213
+ return [node];
214
+ }
215
+ return node instanceof Element ? Array.from(node.querySelectorAll('form')) : [];
216
+ };
199
217
  for (var mutation of mutations) {
200
218
  switch (mutation.type) {
201
219
  case 'childList':
202
220
  {
203
221
  var nodes = [...mutation.addedNodes, ...mutation.removedNodes];
204
222
  for (var node of nodes) {
223
+ for (var form of collectForms(node)) {
224
+ seenForms.add(form);
225
+ }
205
226
  for (var input of collectInputs(node)) {
227
+ var _input$form;
206
228
  seenInputs.add(input);
207
- if (input.form) {
208
- seenForms.add(input.form);
229
+ var _form = (_input$form = input.form) !== null && _input$form !== void 0 ? _input$form : getAssociatedFormElement(input.getAttribute('form'), mutation.target);
230
+ if (_form) {
231
+ seenForms.add(_form);
209
232
  }
210
233
  }
211
234
  }
@@ -218,6 +241,12 @@ function createGlobalFormsObserver() {
218
241
  if (mutation.target.form) {
219
242
  seenForms.add(mutation.target.form);
220
243
  }
244
+ if (mutation.attributeName === 'form') {
245
+ var oldForm = getAssociatedFormElement(mutation.oldValue, mutation.target);
246
+ if (oldForm) {
247
+ seenForms.add(oldForm);
248
+ }
249
+ }
221
250
  }
222
251
  break;
223
252
  }
@@ -301,69 +330,68 @@ function blur(element) {
301
330
  }));
302
331
  element.dispatchEvent(new FocusEvent('blur'));
303
332
  }
304
- function normalizeFieldValue(value) {
305
- if (typeof value === 'undefined') {
306
- return [null, null];
307
- }
308
- if (value === null) {
309
- return [[], createFileList([])];
333
+ function normalizeStringValues(value) {
334
+ if (typeof value === 'undefined') return undefined;
335
+ if (value === null) return [];
336
+ if (typeof value === 'string') return [value];
337
+ if (Array.isArray(value) && value.every(v => typeof v === 'string')) {
338
+ return Array.from(value);
310
339
  }
311
- if (typeof value === 'string') {
312
- return [[value], null];
313
- }
314
- if (Array.isArray(value)) {
315
- if (value.every(item => typeof item === 'string')) {
316
- return [Array.from(value), null];
317
- }
318
- if (value.every(item => item instanceof File)) {
319
- return [null, createFileList(value)];
320
- }
321
- }
322
- if (value instanceof FileList) {
323
- return [null, value];
324
- }
325
- if (value instanceof File) {
326
- return [null, createFileList([value])];
340
+ throw new Error('Expected string or string[] value for string based input');
341
+ }
342
+ function normalizeFileValues(value) {
343
+ if (typeof value === 'undefined') return undefined;
344
+ if (value === null) return createFileList([]);
345
+ if (formdata.isGlobalInstance(value, 'File')) return createFileList([value]);
346
+ if (formdata.isGlobalInstance(value, 'FileList')) return value;
347
+ if (Array.isArray(value) && value.every(item => formdata.isGlobalInstance(item, 'File'))) {
348
+ return createFileList(value);
327
349
  }
328
- return [null, null];
350
+ throw new Error('Expected File, FileList or File[] for file input');
329
351
  }
330
352
 
331
353
  /**
332
354
  * Updates the DOM element with the provided value and defaultValue.
355
+ * If the value or defaultValue is undefined, it will keep the current value instead
333
356
  */
334
357
  function updateField(element, options) {
335
358
  var _value$;
336
- var [value, file] = normalizeFieldValue(options.value);
337
- var [defaultValue] = normalizeFieldValue(options.defaultValue);
338
359
  if (isInputElement(element)) {
339
360
  switch (element.type) {
340
361
  case 'file':
341
362
  {
342
- element.files = file;
363
+ var files = normalizeFileValues(options.value);
364
+ if (files) {
365
+ element.files = files;
366
+ }
343
367
  return;
344
368
  }
345
369
  case 'checkbox':
346
370
  case 'radio':
347
371
  {
348
- if (value) {
349
- var checked = value.includes(element.value);
372
+ var _value = normalizeStringValues(options.value);
373
+ var _defaultValue = normalizeStringValues(options.defaultValue);
374
+ if (_value) {
375
+ var checked = _value.includes(element.value);
350
376
  if (element.type === 'checkbox' ? checked !== element.checked : checked) {
351
377
  // Simulate a click to update the checked state
352
378
  element.click();
353
379
  }
354
380
  element.checked = checked;
355
381
  }
356
- if (defaultValue) {
357
- element.defaultChecked = defaultValue.includes(element.value);
382
+ if (_defaultValue) {
383
+ element.defaultChecked = _defaultValue.includes(element.value);
358
384
  }
359
385
  return;
360
386
  }
361
387
  }
362
388
  } else if (isSelectElement(element)) {
363
- var shouldUnselect = value && value.length === 0;
389
+ var _value2 = normalizeStringValues(options.value);
390
+ var _defaultValue2 = normalizeStringValues(options.defaultValue);
391
+ var shouldUnselect = _value2 && _value2.length === 0;
364
392
  for (var option of element.options) {
365
- if (value) {
366
- var index = value.indexOf(option.value);
393
+ if (_value2) {
394
+ var index = _value2.indexOf(option.value);
367
395
  var selected = index > -1;
368
396
 
369
397
  // Update the selected state of the option
@@ -373,11 +401,11 @@ function updateField(element, options) {
373
401
 
374
402
  // Remove the option from the value array
375
403
  if (selected) {
376
- value.splice(index, 1);
404
+ _value2.splice(index, 1);
377
405
  }
378
406
  }
379
- if (defaultValue) {
380
- var _index = defaultValue.indexOf(option.value);
407
+ if (_defaultValue2) {
408
+ var _index = _defaultValue2.indexOf(option.value);
381
409
  var _selected = _index > -1;
382
410
 
383
411
  // Update the selected state of the option
@@ -387,15 +415,15 @@ function updateField(element, options) {
387
415
 
388
416
  // Remove the option from the defaultValue array
389
417
  if (_selected) {
390
- defaultValue.splice(_index, 1);
418
+ _defaultValue2.splice(_index, 1);
391
419
  }
392
420
  }
393
421
  }
394
422
 
395
423
  // We have already removed all selected options from the value and defaultValue array at this point
396
- var missingOptions = new Set([...(value !== null && value !== void 0 ? value : []), ...(defaultValue !== null && defaultValue !== void 0 ? defaultValue : [])]);
424
+ var missingOptions = new Set([...(_value2 !== null && _value2 !== void 0 ? _value2 : []), ...(_defaultValue2 !== null && _defaultValue2 !== void 0 ? _defaultValue2 : [])]);
397
425
  for (var optionValue of missingOptions) {
398
- element.options.add(new Option(optionValue, optionValue, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.includes(optionValue), value === null || value === void 0 ? void 0 : value.includes(optionValue)));
426
+ element.options.add(new Option(optionValue, optionValue, _defaultValue2 === null || _defaultValue2 === void 0 ? void 0 : _defaultValue2.includes(optionValue), _value2 === null || _value2 === void 0 ? void 0 : _value2.includes(optionValue)));
399
427
  }
400
428
 
401
429
  // If the select element is not multiple and the value is an empty array, unset the selected index
@@ -405,6 +433,8 @@ function updateField(element, options) {
405
433
  }
406
434
  return;
407
435
  }
436
+ var value = normalizeStringValues(options.value);
437
+ var defaultValue = normalizeStringValues(options.defaultValue);
408
438
  var inputValue = (_value$ = value === null || value === void 0 ? void 0 : value[0]) !== null && _value$ !== void 0 ? _value$ : '';
409
439
  if (element.value !== inputValue) {
410
440
  /**
@@ -422,12 +452,10 @@ function updateField(element, options) {
422
452
  } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
423
453
  if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
424
454
  prototypeValueSetter.call(element, inputValue);
455
+ } else if (valueSetter) {
456
+ valueSetter.call(element, inputValue);
425
457
  } else {
426
- if (valueSetter) {
427
- valueSetter.call(element, inputValue);
428
- } else {
429
- throw new Error('The given element does not have a value setter');
430
- }
458
+ throw new Error('The given element does not have a value setter');
431
459
  }
432
460
  }
433
461
  if (defaultValue) {
@@ -448,6 +476,7 @@ exports.isFieldElement = isFieldElement;
448
476
  exports.isInputElement = isInputElement;
449
477
  exports.isSelectElement = isSelectElement;
450
478
  exports.isTextAreaElement = isTextAreaElement;
451
- exports.normalizeFieldValue = normalizeFieldValue;
479
+ exports.normalizeFileValues = normalizeFileValues;
480
+ exports.normalizeStringValues = normalizeStringValues;
452
481
  exports.requestSubmit = requestSubmit;
453
482
  exports.updateField = updateField;
package/dist/dom.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { isGlobalInstance } from './formdata.mjs';
1
2
  import { invariant } from './util.mjs';
2
3
 
3
4
  /**
@@ -117,6 +118,8 @@ function createGlobalFormsObserver() {
117
118
  observer.observe(document.body, {
118
119
  subtree: true,
119
120
  childList: true,
121
+ attributes: true,
122
+ attributeOldValue: true,
120
123
  attributeFilter: ['form', 'name', 'data-conform']
121
124
  });
122
125
  document.addEventListener('input', handleInput);
@@ -183,6 +186,15 @@ function createGlobalFormsObserver() {
183
186
  }));
184
187
  }
185
188
  }
189
+ function getAssociatedFormElement(formId, node) {
190
+ if (formId !== null) {
191
+ return document.forms.namedItem(formId);
192
+ }
193
+ if (node instanceof Element) {
194
+ return node.closest('form');
195
+ }
196
+ return null;
197
+ }
186
198
  function handleMutation(mutations) {
187
199
  var seenForms = new Set();
188
200
  var seenInputs = new Set();
@@ -192,16 +204,27 @@ function createGlobalFormsObserver() {
192
204
  }
193
205
  return node instanceof Element ? Array.from(node.querySelectorAll('input,select,textarea')) : [];
194
206
  };
207
+ var collectForms = node => {
208
+ if (node instanceof HTMLFormElement) {
209
+ return [node];
210
+ }
211
+ return node instanceof Element ? Array.from(node.querySelectorAll('form')) : [];
212
+ };
195
213
  for (var mutation of mutations) {
196
214
  switch (mutation.type) {
197
215
  case 'childList':
198
216
  {
199
217
  var nodes = [...mutation.addedNodes, ...mutation.removedNodes];
200
218
  for (var node of nodes) {
219
+ for (var form of collectForms(node)) {
220
+ seenForms.add(form);
221
+ }
201
222
  for (var input of collectInputs(node)) {
223
+ var _input$form;
202
224
  seenInputs.add(input);
203
- if (input.form) {
204
- seenForms.add(input.form);
225
+ var _form = (_input$form = input.form) !== null && _input$form !== void 0 ? _input$form : getAssociatedFormElement(input.getAttribute('form'), mutation.target);
226
+ if (_form) {
227
+ seenForms.add(_form);
205
228
  }
206
229
  }
207
230
  }
@@ -214,6 +237,12 @@ function createGlobalFormsObserver() {
214
237
  if (mutation.target.form) {
215
238
  seenForms.add(mutation.target.form);
216
239
  }
240
+ if (mutation.attributeName === 'form') {
241
+ var oldForm = getAssociatedFormElement(mutation.oldValue, mutation.target);
242
+ if (oldForm) {
243
+ seenForms.add(oldForm);
244
+ }
245
+ }
217
246
  }
218
247
  break;
219
248
  }
@@ -297,69 +326,68 @@ function blur(element) {
297
326
  }));
298
327
  element.dispatchEvent(new FocusEvent('blur'));
299
328
  }
300
- function normalizeFieldValue(value) {
301
- if (typeof value === 'undefined') {
302
- return [null, null];
303
- }
304
- if (value === null) {
305
- return [[], createFileList([])];
329
+ function normalizeStringValues(value) {
330
+ if (typeof value === 'undefined') return undefined;
331
+ if (value === null) return [];
332
+ if (typeof value === 'string') return [value];
333
+ if (Array.isArray(value) && value.every(v => typeof v === 'string')) {
334
+ return Array.from(value);
306
335
  }
307
- if (typeof value === 'string') {
308
- return [[value], null];
309
- }
310
- if (Array.isArray(value)) {
311
- if (value.every(item => typeof item === 'string')) {
312
- return [Array.from(value), null];
313
- }
314
- if (value.every(item => item instanceof File)) {
315
- return [null, createFileList(value)];
316
- }
317
- }
318
- if (value instanceof FileList) {
319
- return [null, value];
320
- }
321
- if (value instanceof File) {
322
- return [null, createFileList([value])];
336
+ throw new Error('Expected string or string[] value for string based input');
337
+ }
338
+ function normalizeFileValues(value) {
339
+ if (typeof value === 'undefined') return undefined;
340
+ if (value === null) return createFileList([]);
341
+ if (isGlobalInstance(value, 'File')) return createFileList([value]);
342
+ if (isGlobalInstance(value, 'FileList')) return value;
343
+ if (Array.isArray(value) && value.every(item => isGlobalInstance(item, 'File'))) {
344
+ return createFileList(value);
323
345
  }
324
- return [null, null];
346
+ throw new Error('Expected File, FileList or File[] for file input');
325
347
  }
326
348
 
327
349
  /**
328
350
  * Updates the DOM element with the provided value and defaultValue.
351
+ * If the value or defaultValue is undefined, it will keep the current value instead
329
352
  */
330
353
  function updateField(element, options) {
331
354
  var _value$;
332
- var [value, file] = normalizeFieldValue(options.value);
333
- var [defaultValue] = normalizeFieldValue(options.defaultValue);
334
355
  if (isInputElement(element)) {
335
356
  switch (element.type) {
336
357
  case 'file':
337
358
  {
338
- element.files = file;
359
+ var files = normalizeFileValues(options.value);
360
+ if (files) {
361
+ element.files = files;
362
+ }
339
363
  return;
340
364
  }
341
365
  case 'checkbox':
342
366
  case 'radio':
343
367
  {
344
- if (value) {
345
- var checked = value.includes(element.value);
368
+ var _value = normalizeStringValues(options.value);
369
+ var _defaultValue = normalizeStringValues(options.defaultValue);
370
+ if (_value) {
371
+ var checked = _value.includes(element.value);
346
372
  if (element.type === 'checkbox' ? checked !== element.checked : checked) {
347
373
  // Simulate a click to update the checked state
348
374
  element.click();
349
375
  }
350
376
  element.checked = checked;
351
377
  }
352
- if (defaultValue) {
353
- element.defaultChecked = defaultValue.includes(element.value);
378
+ if (_defaultValue) {
379
+ element.defaultChecked = _defaultValue.includes(element.value);
354
380
  }
355
381
  return;
356
382
  }
357
383
  }
358
384
  } else if (isSelectElement(element)) {
359
- var shouldUnselect = value && value.length === 0;
385
+ var _value2 = normalizeStringValues(options.value);
386
+ var _defaultValue2 = normalizeStringValues(options.defaultValue);
387
+ var shouldUnselect = _value2 && _value2.length === 0;
360
388
  for (var option of element.options) {
361
- if (value) {
362
- var index = value.indexOf(option.value);
389
+ if (_value2) {
390
+ var index = _value2.indexOf(option.value);
363
391
  var selected = index > -1;
364
392
 
365
393
  // Update the selected state of the option
@@ -369,11 +397,11 @@ function updateField(element, options) {
369
397
 
370
398
  // Remove the option from the value array
371
399
  if (selected) {
372
- value.splice(index, 1);
400
+ _value2.splice(index, 1);
373
401
  }
374
402
  }
375
- if (defaultValue) {
376
- var _index = defaultValue.indexOf(option.value);
403
+ if (_defaultValue2) {
404
+ var _index = _defaultValue2.indexOf(option.value);
377
405
  var _selected = _index > -1;
378
406
 
379
407
  // Update the selected state of the option
@@ -383,15 +411,15 @@ function updateField(element, options) {
383
411
 
384
412
  // Remove the option from the defaultValue array
385
413
  if (_selected) {
386
- defaultValue.splice(_index, 1);
414
+ _defaultValue2.splice(_index, 1);
387
415
  }
388
416
  }
389
417
  }
390
418
 
391
419
  // We have already removed all selected options from the value and defaultValue array at this point
392
- var missingOptions = new Set([...(value !== null && value !== void 0 ? value : []), ...(defaultValue !== null && defaultValue !== void 0 ? defaultValue : [])]);
420
+ var missingOptions = new Set([...(_value2 !== null && _value2 !== void 0 ? _value2 : []), ...(_defaultValue2 !== null && _defaultValue2 !== void 0 ? _defaultValue2 : [])]);
393
421
  for (var optionValue of missingOptions) {
394
- element.options.add(new Option(optionValue, optionValue, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.includes(optionValue), value === null || value === void 0 ? void 0 : value.includes(optionValue)));
422
+ element.options.add(new Option(optionValue, optionValue, _defaultValue2 === null || _defaultValue2 === void 0 ? void 0 : _defaultValue2.includes(optionValue), _value2 === null || _value2 === void 0 ? void 0 : _value2.includes(optionValue)));
395
423
  }
396
424
 
397
425
  // If the select element is not multiple and the value is an empty array, unset the selected index
@@ -401,6 +429,8 @@ function updateField(element, options) {
401
429
  }
402
430
  return;
403
431
  }
432
+ var value = normalizeStringValues(options.value);
433
+ var defaultValue = normalizeStringValues(options.defaultValue);
404
434
  var inputValue = (_value$ = value === null || value === void 0 ? void 0 : value[0]) !== null && _value$ !== void 0 ? _value$ : '';
405
435
  if (element.value !== inputValue) {
406
436
  /**
@@ -418,12 +448,10 @@ function updateField(element, options) {
418
448
  } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
419
449
  if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
420
450
  prototypeValueSetter.call(element, inputValue);
451
+ } else if (valueSetter) {
452
+ valueSetter.call(element, inputValue);
421
453
  } else {
422
- if (valueSetter) {
423
- valueSetter.call(element, inputValue);
424
- } else {
425
- throw new Error('The given element does not have a value setter');
426
- }
454
+ throw new Error('The given element does not have a value setter');
427
455
  }
428
456
  }
429
457
  if (defaultValue) {
@@ -432,4 +460,4 @@ function updateField(element, options) {
432
460
  }
433
461
  }
434
462
 
435
- export { blur, change, createFileList, createGlobalFormsObserver, focus, getFormAction, getFormEncType, getFormMethod, isFieldElement, isInputElement, isSelectElement, isTextAreaElement, normalizeFieldValue, requestSubmit, updateField };
463
+ export { blur, change, createFileList, createGlobalFormsObserver, focus, getFormAction, getFormEncType, getFormMethod, isFieldElement, isInputElement, isSelectElement, isTextAreaElement, normalizeFileValues, normalizeStringValues, requestSubmit, updateField };
@@ -64,6 +64,141 @@ export declare function flatten(data: unknown, options?: {
64
64
  resolve?: (data: unknown) => unknown;
65
65
  prefix?: string;
66
66
  }): Record<string, unknown>;
67
- export declare function deepEqual<Value>(prev: Value, next: Value): boolean;
67
+ export declare function deepEqual(left: unknown, right: unknown): boolean;
68
+ export type JsonPrimitive = string | number | boolean | null;
69
+ /**
70
+ * The form value of a submission. This is usually constructed from a FormData or URLSearchParams.
71
+ * It may contains JSON primitives if the value is updated based on a form intent.
72
+ */
73
+ export type FormValue<Type extends JsonPrimitive | FormDataEntryValue = JsonPrimitive | FormDataEntryValue> = Type | FormValue<Type | null>[] | {
74
+ [key: string]: FormValue<Type>;
75
+ };
76
+ /**
77
+ * The data of a form submission.
78
+ */
79
+ export type Submission<ValueType extends FormDataEntryValue = FormDataEntryValue> = {
80
+ /**
81
+ * The form value structured following the naming convention.
82
+ */
83
+ value: Record<string, FormValue<ValueType>>;
84
+ /**
85
+ * The field names that are included in the FormData or URLSearchParams.
86
+ */
87
+ fields: string[];
88
+ /**
89
+ * The intent of the submission. This is usally included by specifying a name and value on a submit button.
90
+ */
91
+ intent: string | null;
92
+ };
93
+ /**
94
+ * Parse `FormData` or `URLSearchParams` into a submission object.
95
+ * This function structures the form values based on the naming convention.
96
+ * It also includes all the field names and the intent if the `intentName` option is provided.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const formData = new FormData();
101
+ *
102
+ * formData.append('email', 'test@example.com');
103
+ * formData.append('password', 'secret');
104
+ *
105
+ * parseSubmission(formData)
106
+ * // {
107
+ * // value: { email: 'test@example.com', password: 'secret' },
108
+ * // fields: ['email', 'password'],
109
+ * // intent: null,
110
+ * // }
111
+ *
112
+ * // If you have an intent field
113
+ * formData.append('intent', 'login');
114
+ * parseSubmission(formData, { intentName: 'intent' })
115
+ * // {
116
+ * // value: { email: 'test@example.com', password: 'secret' },
117
+ * // fields: ['email', 'password'],
118
+ * // intent: 'login',
119
+ * // }
120
+ * ```
121
+ */
122
+ export declare function parseSubmission(formData: FormData | URLSearchParams, options?: {
123
+ /**
124
+ * The name of the submit button that triggered the form submission.
125
+ * Used to extract the submission's intent.
126
+ */
127
+ intentName?: string;
128
+ /**
129
+ * A filter function that excludes specific entries from being parsed.
130
+ * Return `true` to skip the entry.
131
+ */
132
+ skipEntry?: (name: string) => boolean;
133
+ }): Submission;
134
+ export type ParseSubmissionOptions = Required<Parameters<typeof parseSubmission>>[1];
135
+ export declare function defaultSerialize(value: unknown): FormDataEntryValue | undefined;
136
+ /**
137
+ * A utility function that checks whether the current form data differs from the default values.
138
+ *
139
+ * @see https://conform.guide/api/react/future/isDirty
140
+ * @example Enable a submit button only if the form is dirty
141
+ *
142
+ * ```tsx
143
+ * const dirty = useFormData(
144
+ * formRef,
145
+ * (formData) => isDirty(formData, { defaultValue }) ?? false,
146
+ * );
147
+ *
148
+ * return (
149
+ * <button type="submit" disabled={!dirty}>
150
+ * Save changes
151
+ * </button>
152
+ * );
153
+ * ```
154
+ */
155
+ export declare function isDirty(
156
+ /**
157
+ * The current form data to compare. It can be:
158
+ *
159
+ * - A `FormData` object
160
+ * - A `URLSearchParams` object
161
+ * - A plain object that was parsed from form data (i.e. `submission.payload`)
162
+ */
163
+ formData: FormData | URLSearchParams | FormValue<FormDataEntryValue> | null, options?: {
164
+ /**
165
+ * An object representing the default values of the form to compare against.
166
+ * Defaults to an empty object if not provided.
167
+ */
168
+ defaultValue?: unknown;
169
+ /**
170
+ * The name of the submit button that triggered the submission.
171
+ * It will be excluded from the dirty comparison.
172
+ */
173
+ intentName?: string;
174
+ /**
175
+ * A function to serialize values in defaultValue before comparing them to the form data.
176
+ * If not provided, a default serializer is used that behaves as follows:
177
+ *
178
+ * - string / File:
179
+ * - Returned as-is
180
+ * - boolean:
181
+ * - true → 'on'
182
+ * - false → undefined
183
+ * - number / bigint:
184
+ * - Converted to string using `.toString()`
185
+ * - Date:
186
+ * - Converted to ISO string using `.toISOString()`
187
+ */
188
+ serialize?: (value: unknown, defaultSerialize: (value: unknown) => FormDataEntryValue | undefined) => FormDataEntryValue | undefined;
189
+ /**
190
+ * A function to exclude specific fields from the comparison.
191
+ * Useful for ignoring hidden inputs like CSRF tokens or internal fields added by frameworks
192
+ * (e.g. Next.js uses hidden inputs to support server actions).
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * isDirty(formData, {
197
+ * skipEntry: (name) => name === 'csrf-token',
198
+ * });
199
+ * ```
200
+ */
201
+ skipEntry?: (name: string) => boolean;
202
+ }): boolean | undefined;
68
203
  export {};
69
204
  //# sourceMappingURL=formdata.d.ts.map
package/dist/formdata.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var submission = require('./submission.js');
6
+
5
7
  /**
6
8
  * Construct a form data with the submitter value.
7
9
  * It utilizes the submitter argument on the FormData constructor from modern browsers
@@ -104,11 +106,11 @@ function setValue(target, name, valueFn) {
104
106
  var index = -1;
105
107
  var pointer = target;
106
108
  while (pointer != null && ++index < length) {
107
- var key = paths[index];
109
+ var _key = paths[index];
108
110
  var nextKey = paths[index + 1];
109
- var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, key) && pointer[key] !== null ? pointer[key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
110
- pointer[key] = newValue;
111
- pointer = pointer[key];
111
+ var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, _key) && pointer[_key] !== null ? pointer[_key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[_key]);
112
+ pointer[_key] = newValue;
113
+ pointer = pointer[_key];
112
114
  }
113
115
  }
114
116
 
@@ -195,8 +197,8 @@ function flatten(data) {
195
197
  process(data[i], "".concat(prefix, "[").concat(i, "]"));
196
198
  }
197
199
  } else if (isPlainObject(data)) {
198
- for (var [key, _value] of Object.entries(data)) {
199
- process(_value, prefix ? "".concat(prefix, ".").concat(key) : key);
200
+ for (var [_key2, _value] of Object.entries(data)) {
201
+ process(_value, prefix ? "".concat(prefix, ".").concat(_key2) : _key2);
200
202
  }
201
203
  }
202
204
  }
@@ -206,34 +208,36 @@ function flatten(data) {
206
208
  }
207
209
  return result;
208
210
  }
209
- function deepEqual(prev, next) {
210
- if (prev === next) {
211
+ function deepEqual(left, right) {
212
+ if (Object.is(left, right)) {
211
213
  return true;
212
214
  }
213
- if (!prev || !next) {
215
+ if (left == null || right == null) {
214
216
  return false;
215
217
  }
216
- if (Array.isArray(prev) && Array.isArray(next)) {
217
- if (prev.length !== next.length) {
218
+
219
+ // Compare plain objects
220
+ if (isPlainObject(left) && isPlainObject(right)) {
221
+ var prevKeys = Object.keys(left);
222
+ var nextKeys = Object.keys(right);
223
+ if (prevKeys.length !== nextKeys.length) {
218
224
  return false;
219
225
  }
220
- for (var i = 0; i < prev.length; i++) {
221
- if (!deepEqual(prev[i], next[i])) {
226
+ for (var _key3 of prevKeys) {
227
+ if (!Object.prototype.hasOwnProperty.call(right, _key3) || !deepEqual(left[_key3], right[_key3])) {
222
228
  return false;
223
229
  }
224
230
  }
225
231
  return true;
226
232
  }
227
- if (isPlainObject(prev) && isPlainObject(next)) {
228
- var prevKeys = Object.keys(prev);
229
- var nextKeys = Object.keys(next);
230
- if (prevKeys.length !== nextKeys.length) {
233
+
234
+ // Compare arrays
235
+ if (Array.isArray(left) && Array.isArray(right)) {
236
+ if (left.length !== right.length) {
231
237
  return false;
232
238
  }
233
- for (var key of prevKeys) {
234
- if (!Object.prototype.hasOwnProperty.call(next, key) ||
235
- // @ts-expect-error FIXME
236
- !deepEqual(prev[key], next[key])) {
239
+ for (var i = 0; i < left.length; i++) {
240
+ if (!deepEqual(left[i], right[i])) {
237
241
  return false;
238
242
  }
239
243
  }
@@ -242,7 +246,170 @@ function deepEqual(prev, next) {
242
246
  return false;
243
247
  }
244
248
 
249
+ /**
250
+ * The form value of a submission. This is usually constructed from a FormData or URLSearchParams.
251
+ * It may contains JSON primitives if the value is updated based on a form intent.
252
+ */
253
+
254
+ /**
255
+ * The data of a form submission.
256
+ */
257
+
258
+ /**
259
+ * Parse `FormData` or `URLSearchParams` into a submission object.
260
+ * This function structures the form values based on the naming convention.
261
+ * It also includes all the field names and the intent if the `intentName` option is provided.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * const formData = new FormData();
266
+ *
267
+ * formData.append('email', 'test@example.com');
268
+ * formData.append('password', 'secret');
269
+ *
270
+ * parseSubmission(formData)
271
+ * // {
272
+ * // value: { email: 'test@example.com', password: 'secret' },
273
+ * // fields: ['email', 'password'],
274
+ * // intent: null,
275
+ * // }
276
+ *
277
+ * // If you have an intent field
278
+ * formData.append('intent', 'login');
279
+ * parseSubmission(formData, { intentName: 'intent' })
280
+ * // {
281
+ * // value: { email: 'test@example.com', password: 'secret' },
282
+ * // fields: ['email', 'password'],
283
+ * // intent: 'login',
284
+ * // }
285
+ * ```
286
+ */
287
+ function parseSubmission(formData, options) {
288
+ var _options$intentName;
289
+ var intentName = (_options$intentName = options === null || options === void 0 ? void 0 : options.intentName) !== null && _options$intentName !== void 0 ? _options$intentName : submission.INTENT;
290
+ var submission$1 = {
291
+ value: {},
292
+ fields: [],
293
+ intent: null
294
+ };
295
+ var _loop = function _loop() {
296
+ var _options$skipEntry;
297
+ if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
298
+ var _value2 = formData.getAll(_name);
299
+ setValue(submission$1.value, _name, () => _value2.length > 1 ? _value2 : _value2[0]);
300
+ submission$1.fields.push(_name);
301
+ }
302
+ };
303
+ for (var _name of new Set(formData.keys())) {
304
+ _loop();
305
+ }
306
+ if (intentName) {
307
+ // We take the first value of the intent field if it exists.
308
+ var intent = formData.get(intentName);
309
+ if (typeof intent === 'string') {
310
+ submission$1.intent = intent;
311
+ }
312
+ }
313
+ return submission$1;
314
+ }
315
+ function defaultSerialize(value) {
316
+ if (typeof value === 'string' || isGlobalInstance(value, 'File')) {
317
+ return value;
318
+ }
319
+ if (typeof value === 'boolean') {
320
+ return value ? 'on' : undefined;
321
+ }
322
+ if (value instanceof Date) {
323
+ return value.toISOString();
324
+ }
325
+ return value === null || value === void 0 ? void 0 : value.toString();
326
+ }
327
+
328
+ /**
329
+ * A utility function that checks whether the current form data differs from the default values.
330
+ *
331
+ * @see https://conform.guide/api/react/future/isDirty
332
+ * @example Enable a submit button only if the form is dirty
333
+ *
334
+ * ```tsx
335
+ * const dirty = useFormData(
336
+ * formRef,
337
+ * (formData) => isDirty(formData, { defaultValue }) ?? false,
338
+ * );
339
+ *
340
+ * return (
341
+ * <button type="submit" disabled={!dirty}>
342
+ * Save changes
343
+ * </button>
344
+ * );
345
+ * ```
346
+ */
347
+ function isDirty(
348
+ /**
349
+ * The current form data to compare. It can be:
350
+ *
351
+ * - A `FormData` object
352
+ * - A `URLSearchParams` object
353
+ * - A plain object that was parsed from form data (i.e. `submission.payload`)
354
+ */
355
+ formData, options) {
356
+ var _options$serialize;
357
+ if (!formData) {
358
+ return;
359
+ }
360
+ var formValue = formData instanceof FormData || formData instanceof URLSearchParams ? parseSubmission(formData, {
361
+ intentName: options === null || options === void 0 ? void 0 : options.intentName,
362
+ skipEntry: options === null || options === void 0 ? void 0 : options.skipEntry
363
+ }).value : formData;
364
+ var defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;
365
+ var serialize = (_options$serialize = options === null || options === void 0 ? void 0 : options.serialize) !== null && _options$serialize !== void 0 ? _options$serialize : defaultSerialize;
366
+ function normalize(value) {
367
+ if (Array.isArray(value)) {
368
+ if (value.length === 0) {
369
+ return undefined;
370
+ }
371
+ var array = value.map(normalize);
372
+ if (array.length === 1 && (typeof array[0] === 'string' || array[0] === undefined)) {
373
+ return array[0];
374
+ }
375
+ return array;
376
+ }
377
+ if (isPlainObject(value)) {
378
+ var entries = Object.entries(value).reduce((list, _ref) => {
379
+ var [key, value] = _ref;
380
+ var normalizedValue = normalize(value);
381
+ if (typeof normalizedValue !== 'undefined') {
382
+ list.push([key, normalizedValue]);
383
+ }
384
+ return list;
385
+ }, []);
386
+ if (entries.length === 0) {
387
+ return undefined;
388
+ }
389
+ return Object.fromEntries(entries);
390
+ }
391
+
392
+ // If the value is null or undefined, treat it as undefined
393
+ if (value == null) {
394
+ return undefined;
395
+ }
396
+
397
+ // Removes empty strings, so that bpth empty string and undefined are treated as the same
398
+ if (typeof value === 'string' && value === '') {
399
+ return undefined;
400
+ }
401
+
402
+ // Remove empty File as well, which happens if no File was selected
403
+ if (isGlobalInstance(value, 'File') && value.name === '' && value.size === 0) {
404
+ return undefined;
405
+ }
406
+ return serialize(value, defaultSerialize);
407
+ }
408
+ return !deepEqual(normalize(formValue), normalize(defaultValue));
409
+ }
410
+
245
411
  exports.deepEqual = deepEqual;
412
+ exports.defaultSerialize = defaultSerialize;
246
413
  exports.flatten = flatten;
247
414
  exports.formatName = formatName;
248
415
  exports.formatPaths = formatPaths;
@@ -250,8 +417,10 @@ exports.getChildPaths = getChildPaths;
250
417
  exports.getFormData = getFormData;
251
418
  exports.getPaths = getPaths;
252
419
  exports.getValue = getValue;
420
+ exports.isDirty = isDirty;
253
421
  exports.isGlobalInstance = isGlobalInstance;
254
422
  exports.isPlainObject = isPlainObject;
255
423
  exports.isPrefix = isPrefix;
256
424
  exports.normalize = normalize;
425
+ exports.parseSubmission = parseSubmission;
257
426
  exports.setValue = setValue;
package/dist/formdata.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { INTENT } from './submission.mjs';
2
+
1
3
  /**
2
4
  * Construct a form data with the submitter value.
3
5
  * It utilizes the submitter argument on the FormData constructor from modern browsers
@@ -100,11 +102,11 @@ function setValue(target, name, valueFn) {
100
102
  var index = -1;
101
103
  var pointer = target;
102
104
  while (pointer != null && ++index < length) {
103
- var key = paths[index];
105
+ var _key = paths[index];
104
106
  var nextKey = paths[index + 1];
105
- var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, key) && pointer[key] !== null ? pointer[key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
106
- pointer[key] = newValue;
107
- pointer = pointer[key];
107
+ var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, _key) && pointer[_key] !== null ? pointer[_key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[_key]);
108
+ pointer[_key] = newValue;
109
+ pointer = pointer[_key];
108
110
  }
109
111
  }
110
112
 
@@ -191,8 +193,8 @@ function flatten(data) {
191
193
  process(data[i], "".concat(prefix, "[").concat(i, "]"));
192
194
  }
193
195
  } else if (isPlainObject(data)) {
194
- for (var [key, _value] of Object.entries(data)) {
195
- process(_value, prefix ? "".concat(prefix, ".").concat(key) : key);
196
+ for (var [_key2, _value] of Object.entries(data)) {
197
+ process(_value, prefix ? "".concat(prefix, ".").concat(_key2) : _key2);
196
198
  }
197
199
  }
198
200
  }
@@ -202,34 +204,36 @@ function flatten(data) {
202
204
  }
203
205
  return result;
204
206
  }
205
- function deepEqual(prev, next) {
206
- if (prev === next) {
207
+ function deepEqual(left, right) {
208
+ if (Object.is(left, right)) {
207
209
  return true;
208
210
  }
209
- if (!prev || !next) {
211
+ if (left == null || right == null) {
210
212
  return false;
211
213
  }
212
- if (Array.isArray(prev) && Array.isArray(next)) {
213
- if (prev.length !== next.length) {
214
+
215
+ // Compare plain objects
216
+ if (isPlainObject(left) && isPlainObject(right)) {
217
+ var prevKeys = Object.keys(left);
218
+ var nextKeys = Object.keys(right);
219
+ if (prevKeys.length !== nextKeys.length) {
214
220
  return false;
215
221
  }
216
- for (var i = 0; i < prev.length; i++) {
217
- if (!deepEqual(prev[i], next[i])) {
222
+ for (var _key3 of prevKeys) {
223
+ if (!Object.prototype.hasOwnProperty.call(right, _key3) || !deepEqual(left[_key3], right[_key3])) {
218
224
  return false;
219
225
  }
220
226
  }
221
227
  return true;
222
228
  }
223
- if (isPlainObject(prev) && isPlainObject(next)) {
224
- var prevKeys = Object.keys(prev);
225
- var nextKeys = Object.keys(next);
226
- if (prevKeys.length !== nextKeys.length) {
229
+
230
+ // Compare arrays
231
+ if (Array.isArray(left) && Array.isArray(right)) {
232
+ if (left.length !== right.length) {
227
233
  return false;
228
234
  }
229
- for (var key of prevKeys) {
230
- if (!Object.prototype.hasOwnProperty.call(next, key) ||
231
- // @ts-expect-error FIXME
232
- !deepEqual(prev[key], next[key])) {
235
+ for (var i = 0; i < left.length; i++) {
236
+ if (!deepEqual(left[i], right[i])) {
233
237
  return false;
234
238
  }
235
239
  }
@@ -238,4 +242,166 @@ function deepEqual(prev, next) {
238
242
  return false;
239
243
  }
240
244
 
241
- export { deepEqual, flatten, formatName, formatPaths, getChildPaths, getFormData, getPaths, getValue, isGlobalInstance, isPlainObject, isPrefix, normalize, setValue };
245
+ /**
246
+ * The form value of a submission. This is usually constructed from a FormData or URLSearchParams.
247
+ * It may contains JSON primitives if the value is updated based on a form intent.
248
+ */
249
+
250
+ /**
251
+ * The data of a form submission.
252
+ */
253
+
254
+ /**
255
+ * Parse `FormData` or `URLSearchParams` into a submission object.
256
+ * This function structures the form values based on the naming convention.
257
+ * It also includes all the field names and the intent if the `intentName` option is provided.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * const formData = new FormData();
262
+ *
263
+ * formData.append('email', 'test@example.com');
264
+ * formData.append('password', 'secret');
265
+ *
266
+ * parseSubmission(formData)
267
+ * // {
268
+ * // value: { email: 'test@example.com', password: 'secret' },
269
+ * // fields: ['email', 'password'],
270
+ * // intent: null,
271
+ * // }
272
+ *
273
+ * // If you have an intent field
274
+ * formData.append('intent', 'login');
275
+ * parseSubmission(formData, { intentName: 'intent' })
276
+ * // {
277
+ * // value: { email: 'test@example.com', password: 'secret' },
278
+ * // fields: ['email', 'password'],
279
+ * // intent: 'login',
280
+ * // }
281
+ * ```
282
+ */
283
+ function parseSubmission(formData, options) {
284
+ var _options$intentName;
285
+ var intentName = (_options$intentName = options === null || options === void 0 ? void 0 : options.intentName) !== null && _options$intentName !== void 0 ? _options$intentName : INTENT;
286
+ var submission = {
287
+ value: {},
288
+ fields: [],
289
+ intent: null
290
+ };
291
+ var _loop = function _loop() {
292
+ var _options$skipEntry;
293
+ if (_name !== intentName && !(options !== null && options !== void 0 && (_options$skipEntry = options.skipEntry) !== null && _options$skipEntry !== void 0 && _options$skipEntry.call(options, _name))) {
294
+ var _value2 = formData.getAll(_name);
295
+ setValue(submission.value, _name, () => _value2.length > 1 ? _value2 : _value2[0]);
296
+ submission.fields.push(_name);
297
+ }
298
+ };
299
+ for (var _name of new Set(formData.keys())) {
300
+ _loop();
301
+ }
302
+ if (intentName) {
303
+ // We take the first value of the intent field if it exists.
304
+ var intent = formData.get(intentName);
305
+ if (typeof intent === 'string') {
306
+ submission.intent = intent;
307
+ }
308
+ }
309
+ return submission;
310
+ }
311
+ function defaultSerialize(value) {
312
+ if (typeof value === 'string' || isGlobalInstance(value, 'File')) {
313
+ return value;
314
+ }
315
+ if (typeof value === 'boolean') {
316
+ return value ? 'on' : undefined;
317
+ }
318
+ if (value instanceof Date) {
319
+ return value.toISOString();
320
+ }
321
+ return value === null || value === void 0 ? void 0 : value.toString();
322
+ }
323
+
324
+ /**
325
+ * A utility function that checks whether the current form data differs from the default values.
326
+ *
327
+ * @see https://conform.guide/api/react/future/isDirty
328
+ * @example Enable a submit button only if the form is dirty
329
+ *
330
+ * ```tsx
331
+ * const dirty = useFormData(
332
+ * formRef,
333
+ * (formData) => isDirty(formData, { defaultValue }) ?? false,
334
+ * );
335
+ *
336
+ * return (
337
+ * <button type="submit" disabled={!dirty}>
338
+ * Save changes
339
+ * </button>
340
+ * );
341
+ * ```
342
+ */
343
+ function isDirty(
344
+ /**
345
+ * The current form data to compare. It can be:
346
+ *
347
+ * - A `FormData` object
348
+ * - A `URLSearchParams` object
349
+ * - A plain object that was parsed from form data (i.e. `submission.payload`)
350
+ */
351
+ formData, options) {
352
+ var _options$serialize;
353
+ if (!formData) {
354
+ return;
355
+ }
356
+ var formValue = formData instanceof FormData || formData instanceof URLSearchParams ? parseSubmission(formData, {
357
+ intentName: options === null || options === void 0 ? void 0 : options.intentName,
358
+ skipEntry: options === null || options === void 0 ? void 0 : options.skipEntry
359
+ }).value : formData;
360
+ var defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue;
361
+ var serialize = (_options$serialize = options === null || options === void 0 ? void 0 : options.serialize) !== null && _options$serialize !== void 0 ? _options$serialize : defaultSerialize;
362
+ function normalize(value) {
363
+ if (Array.isArray(value)) {
364
+ if (value.length === 0) {
365
+ return undefined;
366
+ }
367
+ var array = value.map(normalize);
368
+ if (array.length === 1 && (typeof array[0] === 'string' || array[0] === undefined)) {
369
+ return array[0];
370
+ }
371
+ return array;
372
+ }
373
+ if (isPlainObject(value)) {
374
+ var entries = Object.entries(value).reduce((list, _ref) => {
375
+ var [key, value] = _ref;
376
+ var normalizedValue = normalize(value);
377
+ if (typeof normalizedValue !== 'undefined') {
378
+ list.push([key, normalizedValue]);
379
+ }
380
+ return list;
381
+ }, []);
382
+ if (entries.length === 0) {
383
+ return undefined;
384
+ }
385
+ return Object.fromEntries(entries);
386
+ }
387
+
388
+ // If the value is null or undefined, treat it as undefined
389
+ if (value == null) {
390
+ return undefined;
391
+ }
392
+
393
+ // Removes empty strings, so that bpth empty string and undefined are treated as the same
394
+ if (typeof value === 'string' && value === '') {
395
+ return undefined;
396
+ }
397
+
398
+ // Remove empty File as well, which happens if no File was selected
399
+ if (isGlobalInstance(value, 'File') && value.name === '' && value.size === 0) {
400
+ return undefined;
401
+ }
402
+ return serialize(value, defaultSerialize);
403
+ }
404
+ return !deepEqual(normalize(formValue), normalize(defaultValue));
405
+ }
406
+
407
+ export { deepEqual, defaultSerialize, flatten, formatName, formatPaths, getChildPaths, getFormData, getPaths, getValue, isDirty, isGlobalInstance, isPlainObject, isPrefix, normalize, parseSubmission, setValue };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { type Combine, type Constraint, type ControlButtonProps, type FormId, type FieldName, type DefaultValue, type FormValue, type FormOptions, type FormState, type FormContext, type SubscriptionSubject, type SubscriptionScope, createFormContext as unstable_createFormContext, } from './form';
2
2
  export { type FieldElement, isFieldElement, updateField as unstable_updateField, createFileList, createGlobalFormsObserver as unstable_createGlobalFormsObserver, focus as unstable_focus, change as unstable_change, blur as unstable_blur, } from './dom';
3
3
  export { type Submission, type SubmissionResult, type Intent, INTENT, STATE, serializeIntent, parse, } from './submission';
4
- export { getPaths, formatPaths, isPrefix, isGlobalInstance, deepEqual as unstable_deepEqual, } from './formdata';
4
+ export { getFormData, getPaths, formatPaths, isPrefix, isGlobalInstance, deepEqual as unstable_deepEqual, isDirty as unstable_isDirty, } from './formdata';
5
5
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -22,7 +22,9 @@ exports.STATE = submission.STATE;
22
22
  exports.parse = submission.parse;
23
23
  exports.serializeIntent = submission.serializeIntent;
24
24
  exports.formatPaths = formdata.formatPaths;
25
+ exports.getFormData = formdata.getFormData;
25
26
  exports.getPaths = formdata.getPaths;
26
27
  exports.isGlobalInstance = formdata.isGlobalInstance;
27
28
  exports.isPrefix = formdata.isPrefix;
28
29
  exports.unstable_deepEqual = formdata.deepEqual;
30
+ exports.unstable_isDirty = formdata.isDirty;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  export { createFormContext as unstable_createFormContext } from './form.mjs';
2
2
  export { createFileList, isFieldElement, blur as unstable_blur, change as unstable_change, createGlobalFormsObserver as unstable_createGlobalFormsObserver, focus as unstable_focus, updateField as unstable_updateField } from './dom.mjs';
3
3
  export { INTENT, STATE, parse, serializeIntent } from './submission.mjs';
4
- export { formatPaths, getPaths, isGlobalInstance, isPrefix, deepEqual as unstable_deepEqual } from './formdata.mjs';
4
+ export { formatPaths, getFormData, getPaths, isGlobalInstance, isPrefix, deepEqual as unstable_deepEqual, isDirty as unstable_isDirty } from './formdata.mjs';
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.7.2",
6
+ "version": "1.8.1",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",