@conform-to/react 1.13.2 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,10 +5,12 @@ import { createContext, useContext, useMemo, useId, useRef, useEffect, useSyncEx
5
5
  import { resolveStandardSchemaResult, resolveValidateResult, appendUniqueItem } from './util.mjs';
6
6
  import { isTouched, getFormMetadata, getFieldset, getField, initializeState, updateState } from './state.mjs';
7
7
  import { deserializeIntent, actionHandlers, applyIntent } from './intent.mjs';
8
- import { focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, updateFormValue, getSubmitEvent } from './dom.mjs';
8
+ import { focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs';
9
9
  import { jsx } from 'react/jsx-runtime';
10
10
 
11
11
  var _excluded = ["children"];
12
+ // Static reset key for consistent hydration during Next.js prerendering
13
+ // See: https://nextjs.org/docs/messages/next-prerender-current-time-client
12
14
  var INITIAL_KEY = 'INITIAL_KEY';
13
15
  var GlobalFormOptionsContext = /*#__PURE__*/createContext({
14
16
  intentName: DEFAULT_INTENT_NAME,
@@ -62,14 +64,20 @@ function useConform(formRef, options) {
62
64
  lastResult
63
65
  } = options;
64
66
  var [state, setState] = useState(() => {
65
- var state = initializeState(INITIAL_KEY);
67
+ var state = initializeState({
68
+ defaultValue: options.defaultValue,
69
+ resetKey: INITIAL_KEY
70
+ });
66
71
  if (lastResult) {
67
72
  state = updateState(state, _objectSpread2(_objectSpread2({}, lastResult), {}, {
68
73
  type: 'initialize',
69
74
  intent: lastResult.submission.intent ? deserializeIntent(lastResult.submission.intent) : null,
70
75
  ctx: {
71
76
  handlers: actionHandlers,
72
- reset: () => state
77
+ reset: defaultValue => initializeState({
78
+ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue,
79
+ resetKey: INITIAL_KEY
80
+ })
73
81
  }
74
82
  }));
75
83
  }
@@ -79,22 +87,27 @@ function useConform(formRef, options) {
79
87
  var resetKeyRef = useRef(state.resetKey);
80
88
  var optionsRef = useLatest(options);
81
89
  var lastResultRef = useRef(lastResult);
82
- var lastIntentedValueRef = useRef();
90
+ var pendingValueRef = useRef();
83
91
  var lastAsyncResultRef = useRef(null);
84
92
  var abortControllerRef = useRef(null);
85
- var handleSubmission = useCallback((type, result) => {
93
+ var handleSubmission = useCallback(function (type, result) {
86
94
  var _optionsRef$current$o, _optionsRef$current;
95
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : optionsRef.current;
87
96
  var intent = result.submission.intent ? deserializeIntent(result.submission.intent) : null;
88
97
  setState(state => updateState(state, _objectSpread2(_objectSpread2({}, result), {}, {
89
98
  type,
90
99
  intent,
91
100
  ctx: {
92
101
  handlers: actionHandlers,
93
- reset() {
94
- return initializeState();
102
+ reset(defaultValue) {
103
+ return initializeState({
104
+ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue
105
+ });
95
106
  }
96
107
  }
97
108
  })));
109
+
110
+ // TODO: move on error handler to a new effect
98
111
  var formElement = getFormElement(formRef);
99
112
  if (!formElement || !result.error) {
100
113
  return;
@@ -105,6 +118,15 @@ function useConform(formRef, options) {
105
118
  intent
106
119
  });
107
120
  }, [formRef, optionsRef]);
121
+ if (options.key !== keyRef.current) {
122
+ keyRef.current = options.key;
123
+ setState(initializeState({
124
+ defaultValue: options.defaultValue
125
+ }));
126
+ } else if (lastResult && lastResult !== lastResultRef.current) {
127
+ lastResultRef.current = lastResult;
128
+ handleSubmission('server', lastResult, options);
129
+ }
108
130
  useEffect(() => {
109
131
  return () => {
110
132
  var _abortControllerRef$c;
@@ -112,42 +134,28 @@ function useConform(formRef, options) {
112
134
  (_abortControllerRef$c = abortControllerRef.current) === null || _abortControllerRef$c === void 0 || _abortControllerRef$c.abort('The component is unmounted');
113
135
  };
114
136
  }, []);
115
- useEffect(() => {
116
- // To avoid re-applying the same result twice
117
- if (lastResult && lastResult !== lastResultRef.current) {
118
- handleSubmission('server', lastResult);
119
- lastResultRef.current = lastResult;
120
- }
121
- }, [lastResult, handleSubmission]);
122
- useEffect(() => {
123
- // Reset the form state if the form key changes
124
- if (options.key !== keyRef.current) {
125
- keyRef.current = options.key;
126
- setState(initializeState());
127
- }
128
- }, [options.key]);
129
- useEffect(() => {
137
+ useSafeLayoutEffect(() => {
130
138
  var formElement = getFormElement(formRef);
131
139
 
132
140
  // Reset the form values if the reset key changes
133
141
  if (formElement && state.resetKey !== resetKeyRef.current) {
134
142
  resetKeyRef.current = state.resetKey;
135
- formElement.reset();
136
- }
137
- }, [formRef, state.resetKey]);
138
- useEffect(() => {
139
- if (!state.clientIntendedValue) {
140
- return;
143
+ resetFormValue(formElement, state.defaultValue, optionsRef.current.serialize);
144
+ pendingValueRef.current = undefined;
141
145
  }
142
- var formElement = getFormElement(formRef);
143
- if (!formElement) {
144
- // eslint-disable-next-line no-console
145
- console.error('Failed to update form value; No form element found');
146
- return;
146
+ }, [formRef, state.resetKey, state.defaultValue, optionsRef]);
147
+ useSafeLayoutEffect(() => {
148
+ if (state.targetValue) {
149
+ var formElement = getFormElement(formRef);
150
+ if (!formElement) {
151
+ // eslint-disable-next-line no-console
152
+ console.error('Failed to update form value; No form element found');
153
+ return;
154
+ }
155
+ updateFormValue(formElement, state.targetValue, optionsRef.current.serialize);
147
156
  }
148
- updateFormValue(formElement, state.clientIntendedValue, optionsRef.current.serialize);
149
- lastIntentedValueRef.current = undefined;
150
- }, [formRef, state.clientIntendedValue, optionsRef]);
157
+ pendingValueRef.current = undefined;
158
+ }, [formRef, state.targetValue, optionsRef]);
151
159
  var handleSubmit = useCallback(event => {
152
160
  var _abortControllerRef$c2, _lastAsyncResultRef$c;
153
161
  var abortController = new AbortController();
@@ -180,22 +188,25 @@ function useConform(formRef, options) {
180
188
  }
181
189
  }
182
190
 
183
- // Override submission value if the last intended value is not applied yet (i.e. batch updates)
184
- if (lastIntentedValueRef.current != null) {
185
- submission.payload = lastIntentedValueRef.current;
191
+ // Override submission value if the pending value is not applied yet (i.e. batch updates)
192
+ if (pendingValueRef.current !== undefined) {
193
+ submission.payload = pendingValueRef.current;
186
194
  }
187
- var intendedValue = applyIntent(submission);
188
-
189
- // Update the last intended value in case there will be another intent dispatched
190
- lastIntentedValueRef.current = intendedValue === submission.payload ? undefined : intendedValue;
195
+ var value = applyIntent(submission);
191
196
  var submissionResult = report(submission, {
192
197
  keepFiles: true,
193
- intendedValue
198
+ value
194
199
  });
200
+
201
+ // If there is target value, keep track of it as pending value
202
+ if (submission.payload !== value) {
203
+ var _ref;
204
+ pendingValueRef.current = (_ref = value !== null && value !== void 0 ? value : optionsRef.current.defaultValue) !== null && _ref !== void 0 ? _ref : {};
205
+ }
195
206
  var validateResult =
196
207
  // Skip validation on form reset
197
- intendedValue !== null ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, {
198
- payload: intendedValue,
208
+ value !== undefined ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, {
209
+ payload: value,
199
210
  error: {
200
211
  formErrors: [],
201
212
  fieldErrors: {}
@@ -218,11 +229,11 @@ function useConform(formRef, options) {
218
229
  }
219
230
  if (typeof asyncResult !== 'undefined') {
220
231
  // Update the form when the validation result is resolved
221
- asyncResult.then(_ref => {
232
+ asyncResult.then(_ref2 => {
222
233
  var {
223
234
  error,
224
235
  value
225
- } = _ref;
236
+ } = _ref2;
226
237
  // Update the form with the validation result
227
238
  // There is no need to flush the update in this case
228
239
  if (!abortController.signal.aborted) {
@@ -286,11 +297,32 @@ function useConform(formRef, options) {
286
297
  * The main React hook for form management. Handles form state, validation, and submission
287
298
  * while providing access to form metadata, field objects, and form actions.
288
299
  *
300
+ * It can be called in two ways:
301
+ * - **Schema first**: Pass a schema as the first argument for automatic validation with type inference
302
+ * - **Manual configuration**: Pass options with custom `onValidate` handler for manual validation
303
+ *
289
304
  * @see https://conform.guide/api/react/future/useForm
290
- * @example
305
+ * @example Schema first setup with zod:
306
+ *
307
+ * ```tsx
308
+ * const { form, fields } = useForm(zodSchema, {
309
+ * lastResult,
310
+ * shouldValidate: 'onBlur',
311
+ * });
312
+ *
313
+ * return (
314
+ * <form {...form.props}>
315
+ * <input name={fields.email.name} defaultValue={fields.email.defaultValue} />
316
+ * <div>{fields.email.errors}</div>
317
+ * </form>
318
+ * );
319
+ * ```
320
+ *
321
+ * @example Manual configuration setup with custom validation:
322
+ *
291
323
  * ```tsx
292
324
  * const { form, fields } = useForm({
293
- * onValidate({ payload, error }) {
325
+ * onValidate({ payload, error }) {
294
326
  * if (!payload.email) {
295
327
  * error.fieldErrors.email = ['Required'];
296
328
  * }
@@ -306,11 +338,25 @@ function useConform(formRef, options) {
306
338
  * );
307
339
  * ```
308
340
  */
309
- function useForm(options) {
310
- var _optionsRef$current$o4;
341
+
342
+ /**
343
+ * @deprecated Use `useForm(schema, options)` instead for better type inference.
344
+ */
345
+
346
+ function useForm(schemaOrOptions, maybeOptions) {
347
+ var _options$onError;
348
+ var schema;
349
+ var options;
350
+ if (maybeOptions) {
351
+ schema = schemaOrOptions;
352
+ options = maybeOptions;
353
+ } else {
354
+ var fullOptions = schemaOrOptions;
355
+ options = fullOptions;
356
+ schema = fullOptions.schema;
357
+ }
311
358
  var {
312
359
  id,
313
- defaultValue,
314
360
  constraint
315
361
  } = options;
316
362
  var globalOptions = useContext(GlobalFormOptionsContext);
@@ -321,11 +367,11 @@ function useForm(options) {
321
367
  var [state, handleSubmit] = useConform(formId, _objectSpread2(_objectSpread2({}, options), {}, {
322
368
  serialize: globalOptions.serialize,
323
369
  intentName: globalOptions.intentName,
324
- onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : focusFirstInvalidField,
370
+ onError: (_options$onError = options.onError) !== null && _options$onError !== void 0 ? _options$onError : focusFirstInvalidField,
325
371
  onValidate(ctx) {
326
- var _options$onValidate, _options$onValidate2;
327
- if (options.schema) {
328
- var standardResult = options.schema['~standard'].validate(ctx.payload);
372
+ var _options$onValidate, _options$onValidate2, _options;
373
+ if (schema) {
374
+ var standardResult = schema['~standard'].validate(ctx.payload);
329
375
  if (standardResult instanceof Promise) {
330
376
  return standardResult.then(actualStandardResult => {
331
377
  if (typeof options.onValidate === 'function') {
@@ -358,7 +404,7 @@ function useForm(options) {
358
404
  }
359
405
  return [validateResult.syncResult, validateResult.asyncResult];
360
406
  }
361
- return (_options$onValidate = (_options$onValidate2 = options.onValidate) === null || _options$onValidate2 === void 0 ? void 0 : _options$onValidate2.call(options, ctx)) !== null && _options$onValidate !== void 0 ? _options$onValidate : {
407
+ return (_options$onValidate = (_options$onValidate2 = (_options = options).onValidate) === null || _options$onValidate2 === void 0 ? void 0 : _options$onValidate2.call(_options, ctx)) !== null && _options$onValidate !== void 0 ? _options$onValidate : {
362
408
  // To avoid conform falling back to server validation,
363
409
  // if neither schema nor validation handler is provided,
364
410
  // we just treat it as a valid client submission
@@ -370,15 +416,14 @@ function useForm(options) {
370
416
  var context = useMemo(() => ({
371
417
  formId,
372
418
  state,
373
- defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : null,
374
419
  constraint: constraint !== null && constraint !== void 0 ? constraint : null,
375
- handleSubmit: handleSubmit,
420
+ handleSubmit,
376
421
  handleInput(event) {
377
- var _optionsRef$current$o5, _optionsRef$current4, _globalOptionsRef$cur;
422
+ var _optionsRef$current$o4, _optionsRef$current4, _globalOptionsRef$cur;
378
423
  if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
379
424
  return;
380
425
  }
381
- (_optionsRef$current$o5 = (_optionsRef$current4 = optionsRef.current).onInput) === null || _optionsRef$current$o5 === void 0 || _optionsRef$current$o5.call(_optionsRef$current4, _objectSpread2(_objectSpread2({}, event), {}, {
426
+ (_optionsRef$current$o4 = (_optionsRef$current4 = optionsRef.current).onInput) === null || _optionsRef$current$o4 === void 0 || _optionsRef$current$o4.call(_optionsRef$current4, _objectSpread2(_objectSpread2({}, event), {}, {
382
427
  target: event.target,
383
428
  currentTarget: event.target.form
384
429
  }));
@@ -394,11 +439,11 @@ function useForm(options) {
394
439
  }
395
440
  },
396
441
  handleBlur(event) {
397
- var _optionsRef$current$o6, _optionsRef$current5, _globalOptionsRef$cur2;
442
+ var _optionsRef$current$o5, _optionsRef$current5, _globalOptionsRef$cur2;
398
443
  if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
399
444
  return;
400
445
  }
401
- (_optionsRef$current$o6 = (_optionsRef$current5 = optionsRef.current).onBlur) === null || _optionsRef$current$o6 === void 0 || _optionsRef$current$o6.call(_optionsRef$current5, _objectSpread2(_objectSpread2({}, event), {}, {
446
+ (_optionsRef$current$o5 = (_optionsRef$current5 = optionsRef.current).onBlur) === null || _optionsRef$current$o5 === void 0 || _optionsRef$current$o5.call(_optionsRef$current5, _objectSpread2(_objectSpread2({}, event), {}, {
402
447
  target: event.target,
403
448
  currentTarget: event.target.form
404
449
  }));
@@ -413,7 +458,7 @@ function useForm(options) {
413
458
  intent.validate(event.target.name);
414
459
  }
415
460
  }
416
- }), [formId, state, defaultValue, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
461
+ }), [formId, state, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
417
462
  var form = useMemo(() => getFormMetadata(context, {
418
463
  serialize: globalOptions.serialize,
419
464
  customize: globalOptions.defineCustomMetadata
@@ -610,6 +655,13 @@ function useControl(options) {
610
655
  inputRef.current = null;
611
656
  } else if (isFieldElement(element)) {
612
657
  inputRef.current = element;
658
+
659
+ // Conform excludes hidden type inputs by default when updating form values
660
+ // Fix that by using the hidden attribute instead
661
+ if (element.type === 'hidden') {
662
+ element.hidden = true;
663
+ element.removeAttribute('type');
664
+ }
613
665
  if (shouldHandleFocus) {
614
666
  makeInputFocusable(element);
615
667
  }
@@ -718,16 +770,16 @@ function useFormData(formRef, select, options) {
718
770
  var formElement = getFormElement(formRef);
719
771
  if (formElement) {
720
772
  var formData = getFormData(formElement);
721
- formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? formData : new URLSearchParams(Array.from(formData).map(_ref2 => {
722
- var [key, value] = _ref2;
773
+ formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? formData : new URLSearchParams(Array.from(formData).map(_ref3 => {
774
+ var [key, value] = _ref3;
723
775
  return [key, value.toString()];
724
776
  }));
725
777
  }
726
778
  var unsubscribe = observer.onFormUpdate(event => {
727
779
  if (event.target === getFormElement(formRef)) {
728
780
  var _formData = getFormData(event.target, event.submitter);
729
- formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? _formData : new URLSearchParams(Array.from(_formData).map(_ref3 => {
730
- var [key, value] = _ref3;
781
+ formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? _formData : new URLSearchParams(Array.from(_formData).map(_ref4 => {
782
+ var [key, value] = _ref4;
731
783
  return [key, value.toString()];
732
784
  }));
733
785
  callback();
@@ -14,7 +14,7 @@ export declare function deserializeIntent(value: string): UnknownIntent;
14
14
  */
15
15
  export declare function applyIntent(submission: Submission, options?: {
16
16
  handlers?: Record<string, ActionHandler>;
17
- }): Record<string, FormValue> | null;
17
+ }): Record<string, FormValue> | undefined;
18
18
  export declare function insertItem<Item>(list: Array<Item>, item: Item, index: number): void;
19
19
  export declare function removeItem(list: Array<unknown>, index: number): void;
20
20
  export declare function reorderItems(list: Array<unknown>, fromIndex: number, toIndex: number): void;
@@ -96,20 +96,17 @@ var actionHandlers = {
96
96
  return util.isOptional(options, future.isPlainObject) && (util.isUndefined(options === null || options === void 0 ? void 0 : options.defaultValue) || util.isNullable(options === null || options === void 0 ? void 0 : options.defaultValue, future.isPlainObject));
97
97
  },
98
98
  onApply(_, options) {
99
- var {
100
- defaultValue
101
- } = options !== null && options !== void 0 ? options : {};
102
- return defaultValue !== null && defaultValue !== void 0 ? defaultValue : defaultValue === null ? {} : null;
99
+ if ((options === null || options === void 0 ? void 0 : options.defaultValue) === null) {
100
+ return {};
101
+ }
102
+ return options === null || options === void 0 ? void 0 : options.defaultValue;
103
103
  },
104
104
  onUpdate(_, _ref) {
105
- var _intent$payload;
106
105
  var {
107
- intent
106
+ targetValue,
107
+ ctx
108
108
  } = _ref;
109
- var defaultValue = (_intent$payload = intent.payload) === null || _intent$payload === void 0 ? void 0 : _intent$payload.defaultValue;
110
- return util.merge(state.initializeState(), {
111
- serverIntendedValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : defaultValue === null ? {} : null
112
- });
109
+ return ctx.reset(targetValue);
113
110
  }
114
111
  },
115
112
  validate: {
@@ -117,13 +114,13 @@ var actionHandlers = {
117
114
  return util.isOptional(name, util.isString);
118
115
  },
119
116
  onUpdate(state, _ref2) {
120
- var _intent$payload2;
117
+ var _intent$payload;
121
118
  var {
122
119
  submission,
123
120
  intent,
124
121
  error
125
122
  } = _ref2;
126
- var name = (_intent$payload2 = intent.payload) !== null && _intent$payload2 !== void 0 ? _intent$payload2 : '';
123
+ var name = (_intent$payload = intent.payload) !== null && _intent$payload !== void 0 ? _intent$payload : '';
127
124
  var basePath = future.getPathSegments(name);
128
125
  var allFields = error ?
129
126
  // Consider fields / fieldset with errors as touched too
@@ -147,8 +144,7 @@ var actionHandlers = {
147
144
  onApply(value, options) {
148
145
  var _options$value;
149
146
  var name = future.appendPathSegment(options.name, options.index);
150
- var newValue = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null;
151
- return util.updateValueAtPath(value, name, newValue);
147
+ return util.updateValueAtPath(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null);
152
148
  },
153
149
  onUpdate(state, _ref3) {
154
150
  var {
@@ -1,7 +1,7 @@
1
1
  import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
2
2
  import { getPathSegments, getRelativePath, isPlainObject, appendPathSegment } from '@conform-to/dom/future';
3
- import { isOptional, isUndefined, isNullable, merge, appendUniqueItem, updateValueAtPath, isString, getArrayAtPath, createPathIndexUpdater, compactMap, generateUniqueKey, isNumber, transformKeys } from './util.mjs';
4
- import { initializeState, getDefaultListKey } from './state.mjs';
3
+ import { isOptional, isUndefined, isNullable, appendUniqueItem, merge, updateValueAtPath, isString, getArrayAtPath, createPathIndexUpdater, compactMap, generateUniqueKey, isNumber, transformKeys } from './util.mjs';
4
+ import { getDefaultListKey } from './state.mjs';
5
5
 
6
6
  /**
7
7
  * Serializes intent to string format: "type" or "type(payload)".
@@ -92,20 +92,17 @@ var actionHandlers = {
92
92
  return isOptional(options, isPlainObject) && (isUndefined(options === null || options === void 0 ? void 0 : options.defaultValue) || isNullable(options === null || options === void 0 ? void 0 : options.defaultValue, isPlainObject));
93
93
  },
94
94
  onApply(_, options) {
95
- var {
96
- defaultValue
97
- } = options !== null && options !== void 0 ? options : {};
98
- return defaultValue !== null && defaultValue !== void 0 ? defaultValue : defaultValue === null ? {} : null;
95
+ if ((options === null || options === void 0 ? void 0 : options.defaultValue) === null) {
96
+ return {};
97
+ }
98
+ return options === null || options === void 0 ? void 0 : options.defaultValue;
99
99
  },
100
100
  onUpdate(_, _ref) {
101
- var _intent$payload;
102
101
  var {
103
- intent
102
+ targetValue,
103
+ ctx
104
104
  } = _ref;
105
- var defaultValue = (_intent$payload = intent.payload) === null || _intent$payload === void 0 ? void 0 : _intent$payload.defaultValue;
106
- return merge(initializeState(), {
107
- serverIntendedValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : defaultValue === null ? {} : null
108
- });
105
+ return ctx.reset(targetValue);
109
106
  }
110
107
  },
111
108
  validate: {
@@ -113,13 +110,13 @@ var actionHandlers = {
113
110
  return isOptional(name, isString);
114
111
  },
115
112
  onUpdate(state, _ref2) {
116
- var _intent$payload2;
113
+ var _intent$payload;
117
114
  var {
118
115
  submission,
119
116
  intent,
120
117
  error
121
118
  } = _ref2;
122
- var name = (_intent$payload2 = intent.payload) !== null && _intent$payload2 !== void 0 ? _intent$payload2 : '';
119
+ var name = (_intent$payload = intent.payload) !== null && _intent$payload !== void 0 ? _intent$payload : '';
123
120
  var basePath = getPathSegments(name);
124
121
  var allFields = error ?
125
122
  // Consider fields / fieldset with errors as touched too
@@ -143,8 +140,7 @@ var actionHandlers = {
143
140
  onApply(value, options) {
144
141
  var _options$value;
145
142
  var name = appendPathSegment(options.name, options.index);
146
- var newValue = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null;
147
- return updateValueAtPath(value, name, newValue);
143
+ return updateValueAtPath(value, name, (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : name === '' ? {} : null);
148
144
  },
149
145
  onUpdate(state, _ref3) {
150
146
  var {
@@ -1,16 +1,24 @@
1
1
  import { type ValidationAttributes, type Serialize } from '@conform-to/dom/future';
2
2
  import type { FieldMetadata, FieldName, Fieldset, FormContext, FormMetadata, FormState, FormAction, UnknownIntent, ActionHandler, CustomMetadataDefinition } from './types';
3
- export declare function initializeState<ErrorShape>(resetKey?: string): FormState<ErrorShape>;
3
+ export declare function initializeState<ErrorShape>(options?: {
4
+ defaultValue?: Record<string, unknown> | null | undefined;
5
+ resetKey?: string | undefined;
6
+ }): FormState<ErrorShape>;
4
7
  /**
5
8
  * Updates form state based on action type:
6
- * - Client actions: update intended value and client errors
7
- * - Server actions: update server errors and clear client errors
8
- * - Initialize: set initial intended value
9
+ * - Client actions: update target value and client errors
10
+ * - Server actions: update server errors and clear client errors, with optional target value
11
+ * - Initialize: set initial server value
9
12
  */
10
13
  export declare function updateState<ErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, UnknownIntent | null, {
11
14
  handlers: Record<string, ActionHandler>;
12
- reset: () => FormState<ErrorShape>;
15
+ reset: (defaultValue?: Record<string, unknown> | null | undefined) => FormState<ErrorShape>;
13
16
  }>): FormState<ErrorShape>;
17
+ /**
18
+ * Removes list keys where array length has changed to force regeneration.
19
+ * Minimizes UI state loss by only invalidating keys when necessary.
20
+ */
21
+ export declare function pruneListKeys(listKeys: Record<string, string[]>, targetValue: Record<string, unknown>): Record<string, string[]>;
14
22
  export declare function getDefaultValue(context: FormContext<any>, name: string, serialize?: Serialize): string;
15
23
  export declare function getDefaultOptions(context: FormContext<any>, name: string, serialize?: Serialize): string[];
16
24
  export declare function isDefaultChecked(context: FormContext<any>, name: string, serialize?: Serialize): boolean;
@@ -32,30 +40,30 @@ export declare function isValid(state: FormState<any>, name?: string): boolean;
32
40
  */
33
41
  export declare function getConstraint(context: FormContext<any>, name: string): ValidationAttributes | undefined;
34
42
  export declare function getFormMetadata<ErrorShape>(context: FormContext<ErrorShape>, options?: {
35
- serialize?: Serialize;
36
- customize?: CustomMetadataDefinition;
43
+ serialize?: Serialize | undefined;
44
+ customize?: CustomMetadataDefinition | undefined;
37
45
  }): FormMetadata<ErrorShape>;
38
46
  export declare function getField<FieldShape, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
39
47
  name: FieldName<FieldShape>;
40
- serialize?: Serialize;
41
- customize?: CustomMetadataDefinition;
42
- key?: string;
48
+ serialize?: Serialize | undefined;
49
+ customize?: CustomMetadataDefinition | undefined;
50
+ key?: string | undefined;
43
51
  }): FieldMetadata<FieldShape, ErrorShape>;
44
52
  /**
45
53
  * Creates a proxy that dynamically generates field objects when properties are accessed.
46
54
  */
47
55
  export declare function getFieldset<FieldShape = Record<string, any>, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
48
- name?: FieldName<FieldShape>;
49
- serialize?: Serialize;
50
- customize?: CustomMetadataDefinition;
56
+ name?: FieldName<FieldShape> | undefined;
57
+ serialize?: Serialize | undefined;
58
+ customize?: CustomMetadataDefinition | undefined;
51
59
  }): Fieldset<FieldShape, ErrorShape>;
52
60
  /**
53
61
  * Creates an array of field objects for list/array inputs
54
62
  */
55
63
  export declare function getFieldList<FieldShape = Array<any>, ErrorShape = string>(context: FormContext<ErrorShape>, options: {
56
64
  name: FieldName<FieldShape>;
57
- serialize?: Serialize;
58
- customize?: CustomMetadataDefinition;
65
+ serialize?: Serialize | undefined;
66
+ customize?: CustomMetadataDefinition | undefined;
59
67
  }): FieldMetadata<[
60
68
  FieldShape
61
69
  ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, ErrorShape>[];