@conform-to/react 1.13.3 → 1.14.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.
@@ -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,55 @@ 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 BaseSchemaType = StandardSchemaV1<any, any>;
132
+ export type InferInput<Schema> = Schema extends StandardSchemaV1<infer input, any> ? input : unknown;
133
+ export type InferOutput<Schema> = Schema extends StandardSchemaV1<any, infer output> ? output : undefined;
134
+ export type BaseFormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined> = {
126
135
  /** Optional form identifier. If not provided, a unique ID is automatically generated. */
127
- id?: string;
136
+ id?: string | undefined;
128
137
  /** 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>;
138
+ key?: string | undefined;
139
+ /** Server-side submission result for form state synchronization. */
140
+ lastResult?: SubmissionResult<ErrorShape> | null | undefined;
141
+ /** Form submission handler called when the form is submitted with no validation errors. */
142
+ onSubmit?: SubmitHandler<FormShape, NoInfer<ErrorShape>, NoInfer<Value>> | undefined;
132
143
  /** Initial form values. Can be a partial object matching your form structure. */
133
- defaultValue?: NoInfer<DefaultValue<FormShape>>;
144
+ defaultValue?: DefaultValue<FormShape> | undefined;
134
145
  /** HTML validation attributes for fields (required, minLength, pattern, etc.). */
135
- constraint?: Record<string, ValidationAttributes>;
146
+ constraint?: Record<string, ValidationAttributes> | undefined;
136
147
  /**
137
148
  * Determines when validation should run for the first time on a field.
138
149
  * Overrides the global default set by FormOptionsProvider if provided.
139
150
  *
140
151
  * @default Inherits from FormOptionsProvider, or "onSubmit" if not configured
141
152
  */
142
- shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput';
153
+ shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
143
154
  /**
144
155
  * Determines when validation should run again after the field has been validated once.
145
156
  * Overrides the global default set by FormOptionsProvider if provided.
146
157
  *
147
158
  * @default Inherits from FormOptionsProvider, or same as shouldValidate
148
159
  */
149
- shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput';
150
- /** Server-side submission result for form state synchronization. */
151
- lastResult?: SubmissionResult<NoInfer<ErrorShape>> | null;
160
+ shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
152
161
  /** 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>>;
162
+ onError?: ErrorHandler<NoInfer<ErrorShape>> | undefined;
156
163
  /** Input event handler for custom input event logic. */
157
- onInput?: InputHandler;
164
+ onInput?: InputHandler | undefined;
158
165
  /** 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
- } : {
166
+ onBlur?: BlurHandler | undefined;
164
167
  /** 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
- });
168
+ onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>> | undefined;
169
+ };
170
+ 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> = RequireKey<BaseFormOptions<FormShape, ErrorShape, Value, Schema>, RequiredKeys>;
167
171
  export interface FormContext<ErrorShape extends BaseErrorShape = DefaultErrorShape> {
168
172
  /** The form's unique identifier */
169
173
  formId: string;
170
174
  /** Internal form state with validation results and field data */
171
175
  state: FormState<ErrorShape>;
172
- /** Initial form values */
173
- defaultValue: Record<string, any> | null;
174
176
  /** HTML validation attributes for fields */
175
177
  constraint: Record<string, ValidationAttributes> | null;
176
178
  /** Form submission event handler */
@@ -231,11 +233,25 @@ export interface IntentDispatcher<FormShape extends Record<string, any> = Record
231
233
  /**
232
234
  * Specify the index of the item to update if the field is an array.
233
235
  */
234
- index?: FieldShape extends Array<any> ? number : unknown extends FieldShape ? number : never;
236
+ index?: undefined;
235
237
  /**
236
238
  * The new value for the field or fieldset.
237
239
  */
238
240
  value: DefaultValue<FieldShape>;
241
+ } | {
242
+ /**
243
+ * The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
244
+ */
245
+ name: FieldName<FieldShape>;
246
+ /**
247
+ * Specify the index of the item to update if the field is an array.
248
+ */
249
+ index: number;
250
+ /**
251
+ * The new value for the field or fieldset.
252
+ * When index is specified, this should be the item type, not the array type.
253
+ */
254
+ value: unknown extends FieldShape ? any : FieldShape extends Array<infer ItemShape> ? ItemShape : any;
239
255
  }): void;
240
256
  /**
241
257
  * Insert a new item into an array field.
@@ -285,10 +301,12 @@ export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> =
285
301
  }[keyof Dispatcher];
286
302
  export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
287
303
  validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean;
288
- onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | null;
304
+ onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
289
305
  onUpdate?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
290
306
  type: string;
291
307
  payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
308
+ }, {
309
+ reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
292
310
  }>): FormState<ErrorShape>;
293
311
  };
294
312
  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 +384,7 @@ export type BaseMetadata<FieldShape, ErrorShape extends BaseErrorShape> = Valida
366
384
  /** 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
385
  ariaDescribedBy: string | undefined;
368
386
  /** 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>;
387
+ getFieldset<FieldsetShape = keyof NonNullable<FieldShape> extends never ? unknown : FieldShape>(): Fieldset<FieldsetShape, ErrorShape>;
372
388
  /** Method to get array of fields for list/array fields under this field. */
373
389
  getFieldList<FieldItemShape = [FieldShape] extends [
374
390
  Array<infer ItemShape> | null | undefined
@@ -407,6 +423,8 @@ export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape>
407
423
  errors: ErrorShape[] | undefined;
408
424
  /** Object containing errors for all touched fields. */
409
425
  fieldErrors: Record<string, ErrorShape[]>;
426
+ /** The form's initial default values. */
427
+ defaultValue: Record<string, unknown>;
410
428
  /** Form props object for spreading onto the <form> element. */
411
429
  props: Readonly<{
412
430
  id: string;
@@ -420,9 +438,7 @@ export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape>
420
438
  /** Method to get metadata for a specific field by name. */
421
439
  getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, ErrorShape>;
422
440
  /** 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>;
441
+ getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<keyof NonNullable<FieldShape> extends never ? unknown : FieldShape, ErrorShape>;
426
442
  /** Method to get an array of field objects for array fields. */
427
443
  getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
428
444
  FieldShape
@@ -432,7 +448,7 @@ export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
432
448
  error: FormError<ErrorShape> | null;
433
449
  value?: Value;
434
450
  };
435
- export type ValidateContext<Value> = {
451
+ export type ValidateContext<SchemaValue> = {
436
452
  /**
437
453
  * The submitted values mapped by field name.
438
454
  * Supports nested names like `user.email` and indexed names like `items[0].id`.
@@ -463,9 +479,9 @@ export type ValidateContext<Value> = {
463
479
  * The validated value from schema validation. Only defined when a schema is provided
464
480
  * and the validation succeeds. Undefined if no schema is provided or validation fails.
465
481
  */
466
- schemaValue: Value | undefined;
482
+ schemaValue: SchemaValue;
467
483
  };
468
- export type ValidateHandler<ErrorShape, Value> = (ctx: ValidateContext<Value>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
484
+ export type ValidateHandler<ErrorShape, Value, SchemaValue = undefined> = (ctx: ValidateContext<SchemaValue>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
469
485
  ValidateResult<ErrorShape, Value> | undefined,
470
486
  Promise<ValidateResult<ErrorShape, Value>> | undefined
471
487
  ] | undefined;
@@ -486,14 +502,15 @@ export type ErrorContext<ErrorShape> = {
486
502
  export type ErrorHandler<ErrorShape> = (ctx: ErrorContext<ErrorShape>) => void;
487
503
  export type InputHandler = (event: FormInputEvent) => void;
488
504
  export type BlurHandler = (event: FormFocusEvent) => void;
489
- export type SubmitContext<ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = {
505
+ export type SubmitContext<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = {
490
506
  formData: FormData;
491
507
  value: Value;
492
508
  update: (options: {
493
- error?: Partial<FormError<ErrorShape>> | null;
494
- reset?: boolean;
509
+ error?: Partial<FormError<ErrorShape>> | null | undefined;
510
+ value?: FormShape | null | undefined;
511
+ reset?: boolean | undefined;
495
512
  }) => void;
496
513
  };
497
- export type SubmitHandler<ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<ErrorShape, Value>) => void | Promise<void>;
514
+ 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
515
  export {};
499
516
  //# 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.1",
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.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "^7.17.8",