@conform-to/react 1.13.3 → 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.
@@ -6,12 +6,14 @@ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.j
6
6
  var future = require('@conform-to/dom/future');
7
7
  var util = require('./util.js');
8
8
 
9
- function initializeState(resetKey) {
9
+ function initializeState(options) {
10
+ var _options$resetKey, _options$defaultValue;
10
11
  return {
11
- resetKey: resetKey !== null && resetKey !== void 0 ? resetKey : util.generateUniqueKey(),
12
+ resetKey: (_options$resetKey = options === null || options === void 0 ? void 0 : options.resetKey) !== null && _options$resetKey !== void 0 ? _options$resetKey : util.generateUniqueKey(),
12
13
  listKeys: {},
13
- clientIntendedValue: null,
14
- serverIntendedValue: null,
14
+ defaultValue: (_options$defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue) !== null && _options$defaultValue !== void 0 ? _options$defaultValue : {},
15
+ targetValue: null,
16
+ serverValue: null,
15
17
  serverError: null,
16
18
  clientError: null,
17
19
  touchedFields: []
@@ -20,33 +22,35 @@ function initializeState(resetKey) {
20
22
 
21
23
  /**
22
24
  * Updates form state based on action type:
23
- * - Client actions: update intended value and client errors
24
- * - Server actions: update server errors and clear client errors
25
- * - Initialize: set initial intended value
25
+ * - Client actions: update target value and client errors
26
+ * - Server actions: update server errors and clear client errors, with optional target value
27
+ * - Initialize: set initial server value
26
28
  */
27
29
  function updateState(state, action) {
28
- var _action$intendedValue, _action$intendedValue2, _action$intent, _action$ctx$handlers;
29
- if (action.intendedValue === null) {
30
- return action.ctx.reset();
30
+ var _action$targetValue, _action$targetValue2, _action$intent, _action$ctx$handlers;
31
+ if (action.reset) {
32
+ return action.ctx.reset(action.targetValue);
31
33
  }
32
- var value = (_action$intendedValue = action.intendedValue) !== null && _action$intendedValue !== void 0 ? _action$intendedValue : action.submission.payload;
34
+ var value = (_action$targetValue = action.targetValue) !== null && _action$targetValue !== void 0 ? _action$targetValue : action.submission.payload;
33
35
 
34
- // Apply the form error and intended value from the result first
36
+ // Apply the form error and target value from the result first
35
37
  state = action.type === 'client' ? util.merge(state, {
36
- clientIntendedValue: (_action$intendedValue2 = action.intendedValue) !== null && _action$intendedValue2 !== void 0 ? _action$intendedValue2 : state.clientIntendedValue,
37
- serverIntendedValue: action.intendedValue ? null : state.serverIntendedValue,
38
+ targetValue: (_action$targetValue2 = action.targetValue) !== null && _action$targetValue2 !== void 0 ? _action$targetValue2 : state.targetValue,
39
+ serverValue: action.targetValue ? null : state.serverValue,
38
40
  // Update client error only if the error is different from the previous one to minimize unnecessary re-renders
39
41
  clientError: typeof action.error !== 'undefined' && !future.deepEqual(state.clientError, action.error) ? action.error : state.clientError,
40
42
  // Reset server error if form value is changed
41
- serverError: typeof action.error !== 'undefined' && !future.deepEqual(state.serverIntendedValue, value) ? null : state.serverError
43
+ serverError: typeof action.error !== 'undefined' && !future.deepEqual(state.serverValue, value) ? null : state.serverError
42
44
  }) : util.merge(state, {
43
45
  // Clear client error to avoid showing stale errors
44
46
  clientError: null,
45
47
  // Update server error if the error is defined.
46
48
  // There is no need to check if the error is different as we are updating other states as well
47
49
  serverError: typeof action.error !== 'undefined' ? action.error : state.serverError,
50
+ listKeys: action.type === 'server' && action.targetValue ? pruneListKeys(state.listKeys, action.targetValue) : state.listKeys,
51
+ targetValue: action.type === 'server' && action.targetValue ? action.targetValue : state.targetValue,
48
52
  // Keep track of the value that the serverError is based on
49
- serverIntendedValue: !future.deepEqual(state.serverIntendedValue, value) ? value : state.serverIntendedValue
53
+ serverValue: !future.deepEqual(state.serverValue, value) ? value : state.serverValue
50
54
  });
51
55
  // Validate the whole form if no intent is provided (default submission)
52
56
  var intent = (_action$intent = action.intent) !== null && _action$intent !== void 0 ? _action$intent : {
@@ -57,6 +61,9 @@ function updateState(state, action) {
57
61
  var _handler$validatePayl, _handler$validatePayl2;
58
62
  if ((_handler$validatePayl = (_handler$validatePayl2 = handler.validatePayload) === null || _handler$validatePayl2 === void 0 ? void 0 : _handler$validatePayl2.call(handler, intent.payload)) !== null && _handler$validatePayl !== void 0 ? _handler$validatePayl : true) {
59
63
  return handler.onUpdate(state, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, action), {}, {
64
+ ctx: {
65
+ reset: action.ctx.reset
66
+ },
60
67
  intent: {
61
68
  type: intent.type,
62
69
  payload: intent.payload
@@ -66,10 +73,34 @@ function updateState(state, action) {
66
73
  }
67
74
  return state;
68
75
  }
76
+
77
+ /**
78
+ * Removes list keys where array length has changed to force regeneration.
79
+ * Minimizes UI state loss by only invalidating keys when necessary.
80
+ */
81
+ function pruneListKeys(listKeys, targetValue) {
82
+ var result = listKeys;
83
+ for (var [name, keys] of Object.entries(listKeys)) {
84
+ var list = util.getArrayAtPath(targetValue, name);
85
+
86
+ // Reset list keys only if the length has changed
87
+ // to minimize potential UI state loss due to key changes
88
+ if (keys.length !== list.length) {
89
+ // Create a shallow copy to avoid mutating the original object
90
+ if (result === listKeys) {
91
+ result = _rollupPluginBabelHelpers.objectSpread2({}, result);
92
+ }
93
+
94
+ // Remove the list key to force regeneration
95
+ delete result[name];
96
+ }
97
+ }
98
+ return result;
99
+ }
69
100
  function getDefaultValue(context, name) {
70
101
  var _ref, _context$state$server;
71
102
  var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize;
72
- var value = future.getValueAtPath((_ref = (_context$state$server = context.state.serverIntendedValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.clientIntendedValue) !== null && _ref !== void 0 ? _ref : context.defaultValue, name);
103
+ var value = future.getValueAtPath((_ref = (_context$state$server = context.state.serverValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.targetValue) !== null && _ref !== void 0 ? _ref : context.state.defaultValue, name);
73
104
  var serializedValue = serialize(value);
74
105
  if (typeof serializedValue === 'string') {
75
106
  return serializedValue;
@@ -79,7 +110,7 @@ function getDefaultValue(context, name) {
79
110
  function getDefaultOptions(context, name) {
80
111
  var _ref2, _context$state$server2;
81
112
  var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize;
82
- var value = future.getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverIntendedValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.clientIntendedValue) !== null && _ref2 !== void 0 ? _ref2 : context.defaultValue, name);
113
+ var value = future.getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.targetValue) !== null && _ref2 !== void 0 ? _ref2 : context.state.defaultValue, name);
83
114
  var serializedValue = serialize(value);
84
115
  if (Array.isArray(serializedValue) && serializedValue.every(item => typeof item === 'string')) {
85
116
  return serializedValue;
@@ -92,7 +123,7 @@ function getDefaultOptions(context, name) {
92
123
  function isDefaultChecked(context, name) {
93
124
  var _ref3, _context$state$server3;
94
125
  var serialize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : future.serialize;
95
- var value = future.getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverIntendedValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.clientIntendedValue) !== null && _ref3 !== void 0 ? _ref3 : context.defaultValue, name);
126
+ var value = future.getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.targetValue) !== null && _ref3 !== void 0 ? _ref3 : context.state.defaultValue, name);
96
127
  var serializedValue = serialize(value);
97
128
  if (typeof serializedValue === 'string') {
98
129
  return serializedValue === 'on';
@@ -119,7 +150,7 @@ function getDefaultListKey(prefix, initialValue, name) {
119
150
  }
120
151
  function getListKey(context, name) {
121
152
  var _context$state$listKe, _context$state$listKe2, _ref4, _context$state$server4;
122
- return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverIntendedValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.clientIntendedValue) !== null && _ref4 !== void 0 ? _ref4 : context.defaultValue, name);
153
+ return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.targetValue) !== null && _ref4 !== void 0 ? _ref4 : context.state.defaultValue, name);
123
154
  }
124
155
  function getErrors(state, name) {
125
156
  var _state$serverError;
@@ -215,6 +246,7 @@ function getFormMetadata(context, options) {
215
246
  id: context.formId,
216
247
  errorId: "".concat(context.formId, "-form-error"),
217
248
  descriptionId: "".concat(context.formId, "-form-description"),
249
+ defaultValue: context.state.defaultValue,
218
250
  get errors() {
219
251
  return getErrors(context.state);
220
252
  },
@@ -403,4 +435,5 @@ exports.initializeState = initializeState;
403
435
  exports.isDefaultChecked = isDefaultChecked;
404
436
  exports.isTouched = isTouched;
405
437
  exports.isValid = isValid;
438
+ exports.pruneListKeys = pruneListKeys;
406
439
  exports.updateState = updateState;
@@ -2,12 +2,14 @@ import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelH
2
2
  import { getPathSegments, getRelativePath, appendPathSegment, deepEqual, getValueAtPath, formatPathSegments, serialize } from '@conform-to/dom/future';
3
3
  import { generateUniqueKey, merge, getArrayAtPath } from './util.mjs';
4
4
 
5
- function initializeState(resetKey) {
5
+ function initializeState(options) {
6
+ var _options$resetKey, _options$defaultValue;
6
7
  return {
7
- resetKey: resetKey !== null && resetKey !== void 0 ? resetKey : generateUniqueKey(),
8
+ resetKey: (_options$resetKey = options === null || options === void 0 ? void 0 : options.resetKey) !== null && _options$resetKey !== void 0 ? _options$resetKey : generateUniqueKey(),
8
9
  listKeys: {},
9
- clientIntendedValue: null,
10
- serverIntendedValue: null,
10
+ defaultValue: (_options$defaultValue = options === null || options === void 0 ? void 0 : options.defaultValue) !== null && _options$defaultValue !== void 0 ? _options$defaultValue : {},
11
+ targetValue: null,
12
+ serverValue: null,
11
13
  serverError: null,
12
14
  clientError: null,
13
15
  touchedFields: []
@@ -16,33 +18,35 @@ function initializeState(resetKey) {
16
18
 
17
19
  /**
18
20
  * Updates form state based on action type:
19
- * - Client actions: update intended value and client errors
20
- * - Server actions: update server errors and clear client errors
21
- * - Initialize: set initial intended value
21
+ * - Client actions: update target value and client errors
22
+ * - Server actions: update server errors and clear client errors, with optional target value
23
+ * - Initialize: set initial server value
22
24
  */
23
25
  function updateState(state, action) {
24
- var _action$intendedValue, _action$intendedValue2, _action$intent, _action$ctx$handlers;
25
- if (action.intendedValue === null) {
26
- return action.ctx.reset();
26
+ var _action$targetValue, _action$targetValue2, _action$intent, _action$ctx$handlers;
27
+ if (action.reset) {
28
+ return action.ctx.reset(action.targetValue);
27
29
  }
28
- var value = (_action$intendedValue = action.intendedValue) !== null && _action$intendedValue !== void 0 ? _action$intendedValue : action.submission.payload;
30
+ var value = (_action$targetValue = action.targetValue) !== null && _action$targetValue !== void 0 ? _action$targetValue : action.submission.payload;
29
31
 
30
- // Apply the form error and intended value from the result first
32
+ // Apply the form error and target value from the result first
31
33
  state = action.type === 'client' ? merge(state, {
32
- clientIntendedValue: (_action$intendedValue2 = action.intendedValue) !== null && _action$intendedValue2 !== void 0 ? _action$intendedValue2 : state.clientIntendedValue,
33
- serverIntendedValue: action.intendedValue ? null : state.serverIntendedValue,
34
+ targetValue: (_action$targetValue2 = action.targetValue) !== null && _action$targetValue2 !== void 0 ? _action$targetValue2 : state.targetValue,
35
+ serverValue: action.targetValue ? null : state.serverValue,
34
36
  // Update client error only if the error is different from the previous one to minimize unnecessary re-renders
35
37
  clientError: typeof action.error !== 'undefined' && !deepEqual(state.clientError, action.error) ? action.error : state.clientError,
36
38
  // Reset server error if form value is changed
37
- serverError: typeof action.error !== 'undefined' && !deepEqual(state.serverIntendedValue, value) ? null : state.serverError
39
+ serverError: typeof action.error !== 'undefined' && !deepEqual(state.serverValue, value) ? null : state.serverError
38
40
  }) : merge(state, {
39
41
  // Clear client error to avoid showing stale errors
40
42
  clientError: null,
41
43
  // Update server error if the error is defined.
42
44
  // There is no need to check if the error is different as we are updating other states as well
43
45
  serverError: typeof action.error !== 'undefined' ? action.error : state.serverError,
46
+ listKeys: action.type === 'server' && action.targetValue ? pruneListKeys(state.listKeys, action.targetValue) : state.listKeys,
47
+ targetValue: action.type === 'server' && action.targetValue ? action.targetValue : state.targetValue,
44
48
  // Keep track of the value that the serverError is based on
45
- serverIntendedValue: !deepEqual(state.serverIntendedValue, value) ? value : state.serverIntendedValue
49
+ serverValue: !deepEqual(state.serverValue, value) ? value : state.serverValue
46
50
  });
47
51
  // Validate the whole form if no intent is provided (default submission)
48
52
  var intent = (_action$intent = action.intent) !== null && _action$intent !== void 0 ? _action$intent : {
@@ -53,6 +57,9 @@ function updateState(state, action) {
53
57
  var _handler$validatePayl, _handler$validatePayl2;
54
58
  if ((_handler$validatePayl = (_handler$validatePayl2 = handler.validatePayload) === null || _handler$validatePayl2 === void 0 ? void 0 : _handler$validatePayl2.call(handler, intent.payload)) !== null && _handler$validatePayl !== void 0 ? _handler$validatePayl : true) {
55
59
  return handler.onUpdate(state, _objectSpread2(_objectSpread2({}, action), {}, {
60
+ ctx: {
61
+ reset: action.ctx.reset
62
+ },
56
63
  intent: {
57
64
  type: intent.type,
58
65
  payload: intent.payload
@@ -62,10 +69,34 @@ function updateState(state, action) {
62
69
  }
63
70
  return state;
64
71
  }
72
+
73
+ /**
74
+ * Removes list keys where array length has changed to force regeneration.
75
+ * Minimizes UI state loss by only invalidating keys when necessary.
76
+ */
77
+ function pruneListKeys(listKeys, targetValue) {
78
+ var result = listKeys;
79
+ for (var [name, keys] of Object.entries(listKeys)) {
80
+ var list = getArrayAtPath(targetValue, name);
81
+
82
+ // Reset list keys only if the length has changed
83
+ // to minimize potential UI state loss due to key changes
84
+ if (keys.length !== list.length) {
85
+ // Create a shallow copy to avoid mutating the original object
86
+ if (result === listKeys) {
87
+ result = _objectSpread2({}, result);
88
+ }
89
+
90
+ // Remove the list key to force regeneration
91
+ delete result[name];
92
+ }
93
+ }
94
+ return result;
95
+ }
65
96
  function getDefaultValue(context, name) {
66
97
  var _ref, _context$state$server;
67
98
  var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize;
68
- var value = getValueAtPath((_ref = (_context$state$server = context.state.serverIntendedValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.clientIntendedValue) !== null && _ref !== void 0 ? _ref : context.defaultValue, name);
99
+ var value = getValueAtPath((_ref = (_context$state$server = context.state.serverValue) !== null && _context$state$server !== void 0 ? _context$state$server : context.state.targetValue) !== null && _ref !== void 0 ? _ref : context.state.defaultValue, name);
69
100
  var serializedValue = serialize$1(value);
70
101
  if (typeof serializedValue === 'string') {
71
102
  return serializedValue;
@@ -75,7 +106,7 @@ function getDefaultValue(context, name) {
75
106
  function getDefaultOptions(context, name) {
76
107
  var _ref2, _context$state$server2;
77
108
  var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize;
78
- var value = getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverIntendedValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.clientIntendedValue) !== null && _ref2 !== void 0 ? _ref2 : context.defaultValue, name);
109
+ var value = getValueAtPath((_ref2 = (_context$state$server2 = context.state.serverValue) !== null && _context$state$server2 !== void 0 ? _context$state$server2 : context.state.targetValue) !== null && _ref2 !== void 0 ? _ref2 : context.state.defaultValue, name);
79
110
  var serializedValue = serialize$1(value);
80
111
  if (Array.isArray(serializedValue) && serializedValue.every(item => typeof item === 'string')) {
81
112
  return serializedValue;
@@ -88,7 +119,7 @@ function getDefaultOptions(context, name) {
88
119
  function isDefaultChecked(context, name) {
89
120
  var _ref3, _context$state$server3;
90
121
  var serialize$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : serialize;
91
- var value = getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverIntendedValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.clientIntendedValue) !== null && _ref3 !== void 0 ? _ref3 : context.defaultValue, name);
122
+ var value = getValueAtPath((_ref3 = (_context$state$server3 = context.state.serverValue) !== null && _context$state$server3 !== void 0 ? _context$state$server3 : context.state.targetValue) !== null && _ref3 !== void 0 ? _ref3 : context.state.defaultValue, name);
92
123
  var serializedValue = serialize$1(value);
93
124
  if (typeof serializedValue === 'string') {
94
125
  return serializedValue === 'on';
@@ -115,7 +146,7 @@ function getDefaultListKey(prefix, initialValue, name) {
115
146
  }
116
147
  function getListKey(context, name) {
117
148
  var _context$state$listKe, _context$state$listKe2, _ref4, _context$state$server4;
118
- return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverIntendedValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.clientIntendedValue) !== null && _ref4 !== void 0 ? _ref4 : context.defaultValue, name);
149
+ return (_context$state$listKe = (_context$state$listKe2 = context.state.listKeys) === null || _context$state$listKe2 === void 0 ? void 0 : _context$state$listKe2[name]) !== null && _context$state$listKe !== void 0 ? _context$state$listKe : getDefaultListKey(context.state.resetKey, (_ref4 = (_context$state$server4 = context.state.serverValue) !== null && _context$state$server4 !== void 0 ? _context$state$server4 : context.state.targetValue) !== null && _ref4 !== void 0 ? _ref4 : context.state.defaultValue, name);
119
150
  }
120
151
  function getErrors(state, name) {
121
152
  var _state$serverError;
@@ -211,6 +242,7 @@ function getFormMetadata(context, options) {
211
242
  id: context.formId,
212
243
  errorId: "".concat(context.formId, "-form-error"),
213
244
  descriptionId: "".concat(context.formId, "-form-description"),
245
+ defaultValue: context.state.defaultValue,
214
246
  get errors() {
215
247
  return getErrors(context.state);
216
248
  },
@@ -384,4 +416,4 @@ function getFieldList(context, options) {
384
416
  });
385
417
  }
386
418
 
387
- export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, isValid, updateState };
419
+ export { getConstraint, getDefaultListKey, getDefaultOptions, getDefaultValue, getErrors, getField, getFieldErrors, getFieldList, getFieldset, getFormMetadata, getListKey, initializeState, isDefaultChecked, isTouched, isValid, pruneListKeys, updateState };
@@ -6,10 +6,10 @@ export type Prettify<T> = {
6
6
  /** Reference to a form element. Can be either a React ref object or a form ID string. */
7
7
  export type FormRef = React.RefObject<HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null> | string;
8
8
  export type InputSnapshot = {
9
- value?: string;
10
- options?: string[];
11
- checked?: boolean;
12
- files?: File[];
9
+ value?: string | undefined;
10
+ options?: string[] | undefined;
11
+ checked?: boolean | undefined;
12
+ files?: File[] | undefined;
13
13
  };
14
14
  export type Control = {
15
15
  /**
@@ -75,10 +75,12 @@ export type DefaultValue<Shape> = Shape extends Record<string, any> ? {
75
75
  export type FormState<ErrorShape extends BaseErrorShape = DefaultErrorShape> = {
76
76
  /** Unique identifier that changes on form reset to trigger reset side effects */
77
77
  resetKey: string;
78
- /** Form values from client actions that will be synced to the DOM */
79
- clientIntendedValue: Record<string, unknown> | null;
78
+ /** Initial form values */
79
+ defaultValue: Record<string, unknown>;
80
+ /** Form values that will be synced to the DOM */
81
+ targetValue: Record<string, unknown> | null;
80
82
  /** Form values from server actions, or submitted values when no server intent exists */
81
- serverIntendedValue: Record<string, unknown> | null;
83
+ serverValue: Record<string, unknown> | null;
82
84
  /** Validation errors from server-side processing */
83
85
  serverError: FormError<ErrorShape> | null;
84
86
  /** Validation errors from client-side validation */
@@ -122,55 +124,58 @@ export type GlobalFormOptions = {
122
124
  */
123
125
  defineCustomMetadata?: CustomMetadataDefinition;
124
126
  };
125
- export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined> = {
127
+ export type NonPartial<T> = {
128
+ [K in keyof Required<T>]: T[K];
129
+ };
130
+ export type RequireKey<T, K extends keyof T> = Prettify<T & Pick<NonPartial<T>, K>>;
131
+ export type RequireOneOf<T, K extends keyof T> = Prettify<{
132
+ [K in keyof T]-?: RequireKey<T, K>;
133
+ }[K]>;
134
+ export type BaseSchemaType = StandardSchemaV1<any, any>;
135
+ export type InferInput<Schema> = Schema extends StandardSchemaV1<infer input, any> ? input : unknown;
136
+ export type InferOutput<Schema> = Schema extends StandardSchemaV1<any, infer output> ? output : undefined;
137
+ export type BaseFormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined> = {
126
138
  /** Optional form identifier. If not provided, a unique ID is automatically generated. */
127
- id?: string;
139
+ id?: string | undefined;
128
140
  /** Optional key for form state reset. When the key changes, the form resets to its initial state. */
129
- key?: string;
130
- /** Optional standard schema for validation (e.g., Zod, Valibot, Yup). Removes the need for manual onValidate setup. */
131
- schema?: StandardSchemaV1<FormShape, Value>;
141
+ key?: string | undefined;
142
+ /** Server-side submission result for form state synchronization. */
143
+ lastResult?: SubmissionResult<ErrorShape> | null | undefined;
144
+ /** Form submission handler called when the form is submitted with no validation errors. */
145
+ onSubmit?: SubmitHandler<FormShape, NoInfer<ErrorShape>, NoInfer<Value>> | undefined;
132
146
  /** Initial form values. Can be a partial object matching your form structure. */
133
- defaultValue?: NoInfer<DefaultValue<FormShape>>;
147
+ defaultValue?: DefaultValue<FormShape> | undefined;
134
148
  /** HTML validation attributes for fields (required, minLength, pattern, etc.). */
135
- constraint?: Record<string, ValidationAttributes>;
149
+ constraint?: Record<string, ValidationAttributes> | undefined;
136
150
  /**
137
151
  * Determines when validation should run for the first time on a field.
138
152
  * Overrides the global default set by FormOptionsProvider if provided.
139
153
  *
140
154
  * @default Inherits from FormOptionsProvider, or "onSubmit" if not configured
141
155
  */
142
- shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput';
156
+ shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
143
157
  /**
144
158
  * Determines when validation should run again after the field has been validated once.
145
159
  * Overrides the global default set by FormOptionsProvider if provided.
146
160
  *
147
161
  * @default Inherits from FormOptionsProvider, or same as shouldValidate
148
162
  */
149
- shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput';
150
- /** Server-side submission result for form state synchronization. */
151
- lastResult?: SubmissionResult<NoInfer<ErrorShape>> | null;
163
+ shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
152
164
  /** Error handling callback triggered when validation errors occur. By default, it focuses the first invalid field. */
153
- onError?: ErrorHandler<ErrorShape>;
154
- /** Form submission handler called when the form is submitted with no validation errors. */
155
- onSubmit?: SubmitHandler<NoInfer<ErrorShape>, NoInfer<Value>>;
165
+ onError?: ErrorHandler<NoInfer<ErrorShape>> | undefined;
156
166
  /** Input event handler for custom input event logic. */
157
- onInput?: InputHandler;
167
+ onInput?: InputHandler | undefined;
158
168
  /** Blur event handler for custom focus handling logic. */
159
- onBlur?: BlurHandler;
160
- } & (string extends ErrorShape ? {
161
- /** Custom validation handler. Can be skipped if using the schema property, or combined with schema to customize validation errors. */
162
- onValidate?: ValidateHandler<ErrorShape, Value>;
163
- } : {
169
+ onBlur?: BlurHandler | undefined;
164
170
  /** Custom validation handler. Can be skipped if using the schema property, or combined with schema to customize validation errors. */
165
- onValidate: ValidateHandler<ErrorShape, Value>;
166
- });
171
+ onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>> | undefined;
172
+ };
173
+ export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined, RequiredKeys extends keyof BaseFormOptions<FormShape, ErrorShape, Value, Schema> = never> = RequireOneOf<RequireKey<BaseFormOptions<FormShape, ErrorShape, Value, Schema>, RequiredKeys>, 'onSubmit' | 'lastResult'>;
167
174
  export interface FormContext<ErrorShape extends BaseErrorShape = DefaultErrorShape> {
168
175
  /** The form's unique identifier */
169
176
  formId: string;
170
177
  /** Internal form state with validation results and field data */
171
178
  state: FormState<ErrorShape>;
172
- /** Initial form values */
173
- defaultValue: Record<string, any> | null;
174
179
  /** HTML validation attributes for fields */
175
180
  constraint: Record<string, ValidationAttributes> | null;
176
181
  /** Form submission event handler */
@@ -231,11 +236,25 @@ export interface IntentDispatcher<FormShape extends Record<string, any> = Record
231
236
  /**
232
237
  * Specify the index of the item to update if the field is an array.
233
238
  */
234
- index?: FieldShape extends Array<any> ? number : unknown extends FieldShape ? number : never;
239
+ index?: undefined;
235
240
  /**
236
241
  * The new value for the field or fieldset.
237
242
  */
238
243
  value: DefaultValue<FieldShape>;
244
+ } | {
245
+ /**
246
+ * The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
247
+ */
248
+ name: FieldName<FieldShape>;
249
+ /**
250
+ * Specify the index of the item to update if the field is an array.
251
+ */
252
+ index: number;
253
+ /**
254
+ * The new value for the field or fieldset.
255
+ * When index is specified, this should be the item type, not the array type.
256
+ */
257
+ value: unknown extends FieldShape ? any : FieldShape extends Array<infer ItemShape> ? ItemShape : any;
239
258
  }): void;
240
259
  /**
241
260
  * Insert a new item into an array field.
@@ -285,10 +304,12 @@ export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> =
285
304
  }[keyof Dispatcher];
286
305
  export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
287
306
  validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean;
288
- onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | null;
307
+ onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
289
308
  onUpdate?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
290
309
  type: string;
291
310
  payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
311
+ }, {
312
+ reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
292
313
  }>): FormState<ErrorShape>;
293
314
  };
294
315
  type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
@@ -366,9 +387,7 @@ export type BaseMetadata<FieldShape, ErrorShape extends BaseErrorShape> = Valida
366
387
  /** String value for the `aria-describedby` attribute. Contains the errorId when invalid, undefined otherwise. Merge with descriptionId manually if needed (e.g. `${metadata.descriptionId} ${metadata.ariaDescribedBy}`). */
367
388
  ariaDescribedBy: string | undefined;
368
389
  /** Method to get nested fieldset for object fields under this field. */
369
- getFieldset<FieldsetShape = [FieldShape] extends [
370
- Record<string, unknown> | null | undefined
371
- ] ? FieldShape : unknown>(): Fieldset<FieldsetShape, ErrorShape>;
390
+ getFieldset<FieldsetShape = keyof NonNullable<FieldShape> extends never ? unknown : FieldShape>(): Fieldset<FieldsetShape, ErrorShape>;
372
391
  /** Method to get array of fields for list/array fields under this field. */
373
392
  getFieldList<FieldItemShape = [FieldShape] extends [
374
393
  Array<infer ItemShape> | null | undefined
@@ -407,6 +426,8 @@ export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape>
407
426
  errors: ErrorShape[] | undefined;
408
427
  /** Object containing errors for all touched fields. */
409
428
  fieldErrors: Record<string, ErrorShape[]>;
429
+ /** The form's initial default values. */
430
+ defaultValue: Record<string, unknown>;
410
431
  /** Form props object for spreading onto the <form> element. */
411
432
  props: Readonly<{
412
433
  id: string;
@@ -420,9 +441,7 @@ export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape>
420
441
  /** Method to get metadata for a specific field by name. */
421
442
  getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, ErrorShape>;
422
443
  /** Method to get a fieldset object for nested object fields. */
423
- getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<[
424
- FieldShape
425
- ] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, ErrorShape>;
444
+ getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<keyof NonNullable<FieldShape> extends never ? unknown : FieldShape, ErrorShape>;
426
445
  /** Method to get an array of field objects for array fields. */
427
446
  getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
428
447
  FieldShape
@@ -432,7 +451,7 @@ export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
432
451
  error: FormError<ErrorShape> | null;
433
452
  value?: Value;
434
453
  };
435
- export type ValidateContext<Value> = {
454
+ export type ValidateContext<SchemaValue> = {
436
455
  /**
437
456
  * The submitted values mapped by field name.
438
457
  * Supports nested names like `user.email` and indexed names like `items[0].id`.
@@ -463,9 +482,9 @@ export type ValidateContext<Value> = {
463
482
  * The validated value from schema validation. Only defined when a schema is provided
464
483
  * and the validation succeeds. Undefined if no schema is provided or validation fails.
465
484
  */
466
- schemaValue: Value | undefined;
485
+ schemaValue: SchemaValue;
467
486
  };
468
- export type ValidateHandler<ErrorShape, Value> = (ctx: ValidateContext<Value>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
487
+ export type ValidateHandler<ErrorShape, Value, SchemaValue = undefined> = (ctx: ValidateContext<SchemaValue>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
469
488
  ValidateResult<ErrorShape, Value> | undefined,
470
489
  Promise<ValidateResult<ErrorShape, Value>> | undefined
471
490
  ] | undefined;
@@ -486,14 +505,15 @@ export type ErrorContext<ErrorShape> = {
486
505
  export type ErrorHandler<ErrorShape> = (ctx: ErrorContext<ErrorShape>) => void;
487
506
  export type InputHandler = (event: FormInputEvent) => void;
488
507
  export type BlurHandler = (event: FormFocusEvent) => void;
489
- export type SubmitContext<ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = {
508
+ export type SubmitContext<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = {
490
509
  formData: FormData;
491
510
  value: Value;
492
511
  update: (options: {
493
- error?: Partial<FormError<ErrorShape>> | null;
494
- reset?: boolean;
512
+ error?: Partial<FormError<ErrorShape>> | null | undefined;
513
+ value?: FormShape | null | undefined;
514
+ reset?: boolean | undefined;
495
515
  }) => void;
496
516
  };
497
- export type SubmitHandler<ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<ErrorShape, Value>) => void | Promise<void>;
517
+ export type SubmitHandler<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<FormShape, ErrorShape, Value>) => void | Promise<void>;
498
518
  export {};
499
519
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Conform view adapter for react",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "1.13.3",
6
+ "version": "1.14.0",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",
@@ -41,7 +41,7 @@
41
41
  "url": "https://github.com/edmundhung/conform/issues"
42
42
  },
43
43
  "dependencies": {
44
- "@conform-to/dom": "1.13.3"
44
+ "@conform-to/dom": "1.14.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "^7.17.8",