@conform-to/react 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.16.0 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.17.0 / License MIT / Copyright (c) 2025 Edmund Hung
11
11
 
12
12
  Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards.
13
13
 
package/dist/context.js CHANGED
@@ -189,6 +189,7 @@ function getFieldMetadata(context, subjectRef, stateSnapshot) {
189
189
  case 'pattern':
190
190
  case 'step':
191
191
  case 'multiple':
192
+ case 'accept':
192
193
  return (_state$constraint$nam = state.constraint[name]) === null || _state$constraint$nam === void 0 ? void 0 : _state$constraint$nam[key];
193
194
  case 'getFieldList':
194
195
  {
package/dist/context.mjs CHANGED
@@ -185,6 +185,7 @@ function getFieldMetadata(context, subjectRef, stateSnapshot) {
185
185
  case 'pattern':
186
186
  case 'step':
187
187
  case 'multiple':
188
+ case 'accept':
188
189
  return (_state$constraint$nam = state.constraint[name]) === null || _state$constraint$nam === void 0 ? void 0 : _state$constraint$nam[key];
189
190
  case 'getFieldList':
190
191
  {
@@ -34,4 +34,14 @@ export declare function resetFormValue(form: HTMLFormElement, defaultValue: Reco
34
34
  * Each property access returns a function that submits the intent to the form.
35
35
  */
36
36
  export declare function createIntentDispatcher<FormShape extends Record<string, any>>(formElement: HTMLFormElement | (() => HTMLFormElement | null), intentName: string): IntentDispatcher<FormShape>;
37
+ /**
38
+ * Restores values from preserved inputs and removes them.
39
+ * Called when PreserveBoundary mounts.
40
+ */
41
+ export declare function cleanupPreservedInputs(boundary: HTMLElement, form: HTMLFormElement, name?: string): void;
42
+ /**
43
+ * Clones inputs as hidden elements to preserve their values.
44
+ * Called when PreserveBoundary unmounts.
45
+ */
46
+ export declare function preserveInputs(inputs: Iterable<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>, form: HTMLFormElement, name?: string): void;
37
47
  //# sourceMappingURL=dom.d.ts.map
@@ -237,7 +237,132 @@ function createIntentDispatcher(formElement, intentName) {
237
237
  }
238
238
  });
239
239
  }
240
+ var PERSIST_ATTR = 'data-conform-persist';
241
+ var containerCache = new WeakMap();
240
242
 
243
+ /**
244
+ * Gets or creates a hidden container for persisted inputs.
245
+ * Using a container div instead of appending directly to <form> provides ~10x
246
+ * better performance (form.elements bookkeeping is expensive at scale).
247
+ */
248
+ function getPersistContainer(form) {
249
+ var container = containerCache.get(form);
250
+
251
+ // Verify container is still attached to the form
252
+ if (container && container.parentNode !== form) {
253
+ container = undefined;
254
+ }
255
+ if (!container) {
256
+ container = form.ownerDocument.createElement('div');
257
+ container.setAttribute(PERSIST_ATTR, '');
258
+ container.hidden = true;
259
+ form.appendChild(container);
260
+ containerCache.set(form, container);
261
+ }
262
+ return container;
263
+ }
264
+
265
+ /**
266
+ * Restores values from preserved inputs and removes them.
267
+ * Called when PreserveBoundary mounts.
268
+ */
269
+ function cleanupPreservedInputs(boundary, form, name) {
270
+ var inputs = boundary.querySelectorAll('input,select,textarea');
271
+ var container = getPersistContainer(form);
272
+ for (var input of inputs) {
273
+ if (!future.isFieldElement(input) || !input.name) {
274
+ continue;
275
+ }
276
+
277
+ // For checkbox/radio, match by field name + value (+ boundary name if provided)
278
+ // For other inputs, match by field name only (+ boundary name if provided)
279
+ var isCheckboxOrRadio = input.type === 'checkbox' || input.type === 'radio';
280
+
281
+ // Query the persist container, not the whole form
282
+ var boundarySelector = name ? "[".concat(PERSIST_ATTR, "=\"").concat(name, "\"]") : '';
283
+ var selector = isCheckboxOrRadio ? "".concat(boundarySelector, "[name=\"").concat(input.name, "\"][value=\"").concat(input.value, "\"]") : "".concat(boundarySelector, "[name=\"").concat(input.name, "\"]");
284
+ var persisted = container.querySelector(selector);
285
+ if (persisted) {
286
+ if (input instanceof HTMLInputElement && persisted instanceof HTMLInputElement) {
287
+ if (isCheckboxOrRadio) {
288
+ input.checked = persisted.checked;
289
+ } else if (input.type === 'file') {
290
+ // Restore files from the persisted input (may be empty)
291
+ input.files = persisted.files;
292
+ } else {
293
+ input.value = persisted.value;
294
+ }
295
+ } else if (input instanceof HTMLSelectElement && persisted instanceof HTMLSelectElement) {
296
+ var _loop = function _loop(option) {
297
+ var _persistedOption$sele;
298
+ var persistedOption = Array.from(persisted.options).find(o => o.value === option.value);
299
+ option.selected = (_persistedOption$sele = persistedOption === null || persistedOption === void 0 ? void 0 : persistedOption.selected) !== null && _persistedOption$sele !== void 0 ? _persistedOption$sele : false;
300
+ };
301
+ for (var option of input.options) {
302
+ _loop(option);
303
+ }
304
+ } else if (input instanceof HTMLTextAreaElement && persisted instanceof HTMLTextAreaElement) {
305
+ input.value = persisted.value;
306
+ }
307
+ persisted.remove();
308
+ }
309
+ }
310
+
311
+ // If name is provided, remove any remaining persisted inputs with this name
312
+ // (handles the case where inputs were removed from the boundary)
313
+ if (name) {
314
+ var remainingPersisted = container.querySelectorAll("[".concat(PERSIST_ATTR, "=\"").concat(name, "\"]"));
315
+ remainingPersisted.forEach(el => el.remove());
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Clones inputs as hidden elements to preserve their values.
321
+ * Called when PreserveBoundary unmounts.
322
+ */
323
+ function preserveInputs(inputs, form, name) {
324
+ // Get the persist container once, outside the loop
325
+ var container = getPersistContainer(form);
326
+ for (var input of inputs) {
327
+ if (!future.isFieldElement(input) || !input.name) {
328
+ continue;
329
+ }
330
+
331
+ // Skip unchecked checkbox/radio (they don't contribute to FormData)
332
+ if (input instanceof HTMLInputElement && (input.type === 'checkbox' || input.type === 'radio') && !input.checked) {
333
+ continue;
334
+ }
335
+
336
+ // Clone the input element
337
+ var clone = input.cloneNode(true);
338
+
339
+ // Mark with name if provided, and hide it
340
+ if (name) {
341
+ clone.setAttribute(PERSIST_ATTR, name);
342
+ }
343
+ clone.hidden = true;
344
+
345
+ // Copy dynamic state that cloneNode doesn't preserve
346
+ if (input instanceof HTMLSelectElement) {
347
+ // cloneNode doesn't copy selected state for options
348
+ for (var i = 0; i < input.options.length; i++) {
349
+ var inputOption = input.options[i];
350
+ var cloneOption = clone.options[i];
351
+ if (inputOption && cloneOption) {
352
+ cloneOption.selected = inputOption.selected;
353
+ }
354
+ }
355
+ } else if (input instanceof HTMLInputElement && input.type === 'file') {
356
+ // cloneNode doesn't copy files
357
+ clone.files = input.files;
358
+ }
359
+
360
+ // Append to persist container (faster than appending directly to form)
361
+ container.appendChild(clone);
362
+ }
363
+ }
364
+
365
+ exports.cleanupPreservedInputs = cleanupPreservedInputs;
241
366
  exports.createDefaultSnapshot = createDefaultSnapshot;
242
367
  exports.createIntentDispatcher = createIntentDispatcher;
243
368
  exports.focusFirstInvalidField = focusFirstInvalidField;
@@ -248,5 +373,6 @@ exports.getRadioGroupValue = getRadioGroupValue;
248
373
  exports.getSubmitEvent = getSubmitEvent;
249
374
  exports.initializeField = initializeField;
250
375
  exports.makeInputFocusable = makeInputFocusable;
376
+ exports.preserveInputs = preserveInputs;
251
377
  exports.resetFormValue = resetFormValue;
252
378
  exports.updateFormValue = updateFormValue;
@@ -233,5 +233,129 @@ function createIntentDispatcher(formElement, intentName) {
233
233
  }
234
234
  });
235
235
  }
236
+ var PERSIST_ATTR = 'data-conform-persist';
237
+ var containerCache = new WeakMap();
236
238
 
237
- export { createDefaultSnapshot, createIntentDispatcher, focusFirstInvalidField, getCheckboxGroupValue, getFormElement, getInputSnapshot, getRadioGroupValue, getSubmitEvent, initializeField, makeInputFocusable, resetFormValue, updateFormValue };
239
+ /**
240
+ * Gets or creates a hidden container for persisted inputs.
241
+ * Using a container div instead of appending directly to <form> provides ~10x
242
+ * better performance (form.elements bookkeeping is expensive at scale).
243
+ */
244
+ function getPersistContainer(form) {
245
+ var container = containerCache.get(form);
246
+
247
+ // Verify container is still attached to the form
248
+ if (container && container.parentNode !== form) {
249
+ container = undefined;
250
+ }
251
+ if (!container) {
252
+ container = form.ownerDocument.createElement('div');
253
+ container.setAttribute(PERSIST_ATTR, '');
254
+ container.hidden = true;
255
+ form.appendChild(container);
256
+ containerCache.set(form, container);
257
+ }
258
+ return container;
259
+ }
260
+
261
+ /**
262
+ * Restores values from preserved inputs and removes them.
263
+ * Called when PreserveBoundary mounts.
264
+ */
265
+ function cleanupPreservedInputs(boundary, form, name) {
266
+ var inputs = boundary.querySelectorAll('input,select,textarea');
267
+ var container = getPersistContainer(form);
268
+ for (var input of inputs) {
269
+ if (!isFieldElement(input) || !input.name) {
270
+ continue;
271
+ }
272
+
273
+ // For checkbox/radio, match by field name + value (+ boundary name if provided)
274
+ // For other inputs, match by field name only (+ boundary name if provided)
275
+ var isCheckboxOrRadio = input.type === 'checkbox' || input.type === 'radio';
276
+
277
+ // Query the persist container, not the whole form
278
+ var boundarySelector = name ? "[".concat(PERSIST_ATTR, "=\"").concat(name, "\"]") : '';
279
+ var selector = isCheckboxOrRadio ? "".concat(boundarySelector, "[name=\"").concat(input.name, "\"][value=\"").concat(input.value, "\"]") : "".concat(boundarySelector, "[name=\"").concat(input.name, "\"]");
280
+ var persisted = container.querySelector(selector);
281
+ if (persisted) {
282
+ if (input instanceof HTMLInputElement && persisted instanceof HTMLInputElement) {
283
+ if (isCheckboxOrRadio) {
284
+ input.checked = persisted.checked;
285
+ } else if (input.type === 'file') {
286
+ // Restore files from the persisted input (may be empty)
287
+ input.files = persisted.files;
288
+ } else {
289
+ input.value = persisted.value;
290
+ }
291
+ } else if (input instanceof HTMLSelectElement && persisted instanceof HTMLSelectElement) {
292
+ var _loop = function _loop(option) {
293
+ var _persistedOption$sele;
294
+ var persistedOption = Array.from(persisted.options).find(o => o.value === option.value);
295
+ option.selected = (_persistedOption$sele = persistedOption === null || persistedOption === void 0 ? void 0 : persistedOption.selected) !== null && _persistedOption$sele !== void 0 ? _persistedOption$sele : false;
296
+ };
297
+ for (var option of input.options) {
298
+ _loop(option);
299
+ }
300
+ } else if (input instanceof HTMLTextAreaElement && persisted instanceof HTMLTextAreaElement) {
301
+ input.value = persisted.value;
302
+ }
303
+ persisted.remove();
304
+ }
305
+ }
306
+
307
+ // If name is provided, remove any remaining persisted inputs with this name
308
+ // (handles the case where inputs were removed from the boundary)
309
+ if (name) {
310
+ var remainingPersisted = container.querySelectorAll("[".concat(PERSIST_ATTR, "=\"").concat(name, "\"]"));
311
+ remainingPersisted.forEach(el => el.remove());
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Clones inputs as hidden elements to preserve their values.
317
+ * Called when PreserveBoundary unmounts.
318
+ */
319
+ function preserveInputs(inputs, form, name) {
320
+ // Get the persist container once, outside the loop
321
+ var container = getPersistContainer(form);
322
+ for (var input of inputs) {
323
+ if (!isFieldElement(input) || !input.name) {
324
+ continue;
325
+ }
326
+
327
+ // Skip unchecked checkbox/radio (they don't contribute to FormData)
328
+ if (input instanceof HTMLInputElement && (input.type === 'checkbox' || input.type === 'radio') && !input.checked) {
329
+ continue;
330
+ }
331
+
332
+ // Clone the input element
333
+ var clone = input.cloneNode(true);
334
+
335
+ // Mark with name if provided, and hide it
336
+ if (name) {
337
+ clone.setAttribute(PERSIST_ATTR, name);
338
+ }
339
+ clone.hidden = true;
340
+
341
+ // Copy dynamic state that cloneNode doesn't preserve
342
+ if (input instanceof HTMLSelectElement) {
343
+ // cloneNode doesn't copy selected state for options
344
+ for (var i = 0; i < input.options.length; i++) {
345
+ var inputOption = input.options[i];
346
+ var cloneOption = clone.options[i];
347
+ if (inputOption && cloneOption) {
348
+ cloneOption.selected = inputOption.selected;
349
+ }
350
+ }
351
+ } else if (input instanceof HTMLInputElement && input.type === 'file') {
352
+ // cloneNode doesn't copy files
353
+ clone.files = input.files;
354
+ }
355
+
356
+ // Append to persist container (faster than appending directly to form)
357
+ container.appendChild(clone);
358
+ }
359
+ }
360
+
361
+ export { cleanupPreservedInputs, createDefaultSnapshot, createIntentDispatcher, focusFirstInvalidField, getCheckboxGroupValue, getFormElement, getInputSnapshot, getRadioGroupValue, getSubmitEvent, initializeField, makeInputFocusable, preserveInputs, resetFormValue, updateFormValue };
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
2
3
  import { isFieldElement } from '@conform-to/dom';
3
4
  import { DEFAULT_INTENT_NAME, serialize } from '@conform-to/dom/future';
@@ -15,6 +15,25 @@ export declare function FormProvider(props: {
15
15
  context: FormContext;
16
16
  children: React.ReactNode;
17
17
  }): React.ReactElement;
18
+ /**
19
+ * Preserves form field values when its contents are unmounted.
20
+ * Useful for multi-step forms and virtualized lists.
21
+ *
22
+ * @see https://conform.guide/api/react/future/PreserveBoundary
23
+ */
24
+ export declare function PreserveBoundary(props: {
25
+ /**
26
+ * A unique name for the boundary within the form. Used to ensure proper
27
+ * unmount/remount behavior and to isolate preserved inputs between boundaries.
28
+ */
29
+ name: string;
30
+ /**
31
+ * The id of the form to associate with. Only needed when the boundary
32
+ * is rendered outside the form element.
33
+ */
34
+ form?: string;
35
+ children: React.ReactNode;
36
+ }): React.ReactElement;
18
37
  /**
19
38
  * @deprecated Replaced by the `configureForms` factory API. This will be removed in the next minor version. If you are not ready to migrate, please pin to `v1.16.0`.
20
39
  */
@@ -39,6 +39,47 @@ function FormProvider(props) {
39
39
  });
40
40
  }
41
41
 
42
+ /**
43
+ * Preserves form field values when its contents are unmounted.
44
+ * Useful for multi-step forms and virtualized lists.
45
+ *
46
+ * @see https://conform.guide/api/react/future/PreserveBoundary
47
+ */
48
+ function PreserveBoundary(props) {
49
+ // name is used as key so React properly unmounts/remounts when switching
50
+ // between boundaries. Without it, both sides of a ternary share
51
+ // key={undefined} and React reuses the instance (useId and key prop
52
+ // can't help here). This is why name is required.
53
+ return /*#__PURE__*/jsxRuntime.jsx(PreserveBoundaryImpl, _rollupPluginBabelHelpers.objectSpread2({}, props), props.name);
54
+ }
55
+ function PreserveBoundaryImpl(props) {
56
+ var fieldsetRef = react.useRef(null);
57
+
58
+ // useLayoutEffect to restore values before paint, avoiding flash of default values
59
+ useSafeLayoutEffect(() => {
60
+ var fieldset = fieldsetRef.current;
61
+ if (!fieldset || !fieldset.form) {
62
+ return;
63
+ }
64
+ var form = fieldset.form;
65
+
66
+ // On mount: restore values from preserved inputs
67
+ dom.cleanupPreservedInputs(fieldset, form, props.name);
68
+ return () => {
69
+ // On unmount: preserve input values
70
+ dom.preserveInputs(fieldset.querySelectorAll('input,select,textarea'), form, props.name);
71
+ };
72
+ }, [props.name]);
73
+ return /*#__PURE__*/jsxRuntime.jsx("fieldset", {
74
+ ref: fieldsetRef,
75
+ form: props.form,
76
+ style: {
77
+ display: 'contents'
78
+ },
79
+ children: props.children
80
+ });
81
+ }
82
+
42
83
  /**
43
84
  * @deprecated Replaced by the `configureForms` factory API. This will be removed in the next minor version. If you are not ready to migrate, please pin to `v1.16.0`.
44
85
  */
@@ -77,11 +118,16 @@ function useConform(formRef, options) {
77
118
  resetKey: INITIAL_KEY
78
119
  });
79
120
  if (lastResult) {
80
- state$1 = state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, lastResult), {}, {
121
+ var intent$1 = lastResult.submission.intent ? intent.deserializeIntent(lastResult.submission.intent) : null;
122
+ var result = intent.applyIntent(lastResult, intent$1, {
123
+ handlers: intent.intentHandlers
124
+ });
125
+ state$1 = state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, {
81
126
  type: 'initialize',
82
- intent: lastResult.submission.intent ? intent.deserializeIntent(lastResult.submission.intent) : null,
127
+ intent: intent$1,
83
128
  ctx: {
84
- handlers: intent.actionHandlers,
129
+ handlers: intent.intentHandlers,
130
+ cancelled: result !== lastResult,
85
131
  reset: defaultValue => state.initializeState({
86
132
  defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue,
87
133
  resetKey: INITIAL_KEY
@@ -99,14 +145,17 @@ function useConform(formRef, options) {
99
145
  var lastAsyncResultRef = react.useRef(null);
100
146
  var abortControllerRef = react.useRef(null);
101
147
  var handleSubmission = react.useCallback(function (type, result) {
102
- var _optionsRef$current$o, _optionsRef$current;
103
148
  var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : optionsRef.current;
104
149
  var intent$1 = result.submission.intent ? intent.deserializeIntent(result.submission.intent) : null;
105
- setState(state$1 => state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, {
150
+ var finalResult = intent.applyIntent(result, intent$1, {
151
+ handlers: intent.intentHandlers
152
+ });
153
+ setState(state$1 => state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, finalResult), {}, {
106
154
  type,
107
155
  intent: intent$1,
108
156
  ctx: {
109
- handlers: intent.actionHandlers,
157
+ handlers: intent.intentHandlers,
158
+ cancelled: finalResult !== result,
110
159
  reset(defaultValue) {
111
160
  return state.initializeState({
112
161
  defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue
@@ -117,14 +166,15 @@ function useConform(formRef, options) {
117
166
 
118
167
  // TODO: move on error handler to a new effect
119
168
  var formElement = dom.getFormElement(formRef);
120
- if (!formElement || !result.error) {
121
- return;
169
+ if (formElement && result.error) {
170
+ var _optionsRef$current$o, _optionsRef$current;
171
+ (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
172
+ formElement,
173
+ error: result.error,
174
+ intent: intent$1
175
+ });
122
176
  }
123
- (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
124
- formElement,
125
- error: result.error,
126
- intent: intent$1
127
- });
177
+ return finalResult;
128
178
  }, [formRef, optionsRef]);
129
179
  if (options.key !== keyRef.current) {
130
180
  keyRef.current = options.key;
@@ -200,17 +250,11 @@ function useConform(formRef, options) {
200
250
  if (pendingValueRef.current !== undefined) {
201
251
  submission.payload = pendingValueRef.current;
202
252
  }
203
- var value = intent.applyIntent(submission);
253
+ var value = intent.resolveIntent(submission);
204
254
  var submissionResult = future.report(submission, {
205
255
  keepFiles: true,
206
256
  value
207
257
  });
208
-
209
- // If there is target value, keep track of it as pending value
210
- if (submission.payload !== value) {
211
- var _ref;
212
- pendingValueRef.current = (_ref = value !== null && value !== void 0 ? value : optionsRef.current.defaultValue) !== null && _ref !== void 0 ? _ref : {};
213
- }
214
258
  var validateResult =
215
259
  // Skip validation on form reset
216
260
  value !== undefined ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, {
@@ -237,11 +281,11 @@ function useConform(formRef, options) {
237
281
  }
238
282
  if (typeof asyncResult !== 'undefined') {
239
283
  // Update the form when the validation result is resolved
240
- asyncResult.then(_ref2 => {
284
+ asyncResult.then(_ref => {
241
285
  var {
242
286
  error,
243
287
  value
244
- } = _ref2;
288
+ } = _ref;
245
289
  // Update the form with the validation result
246
290
  // There is no need to flush the update in this case
247
291
  if (!abortController.signal.aborted) {
@@ -249,7 +293,7 @@ function useConform(formRef, options) {
249
293
  handleSubmission('server', submissionResult);
250
294
 
251
295
  // If the form is meant to be submitted and there is no error
252
- if (error === null && !submission.intent) {
296
+ if (submissionResult.error === null && !submission.intent) {
253
297
  var _event = future.createSubmitEvent(submitEvent.submitter);
254
298
 
255
299
  // Keep track of the submit event so we can skip validation on the next submit
@@ -264,15 +308,19 @@ function useConform(formRef, options) {
264
308
  }
265
309
  });
266
310
  }
267
- handleSubmission('client', submissionResult);
311
+ var clientResult = handleSubmission('client', submissionResult);
312
+ if (clientResult.reset || clientResult.targetValue !== undefined) {
313
+ var _ref2, _clientResult$targetV;
314
+ pendingValueRef.current = (_ref2 = (_clientResult$targetV = clientResult.targetValue) !== null && _clientResult$targetV !== void 0 ? _clientResult$targetV : optionsRef.current.defaultValue) !== null && _ref2 !== void 0 ? _ref2 : {};
315
+ }
268
316
  if (
269
317
  // If client validation happens
270
318
  (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && (
271
319
  // Either the form is not meant to be submitted (i.e. intent is present) or there is an error / pending validation
272
- submissionResult.submission.intent || submissionResult.error !== null)) {
320
+ clientResult.submission.intent || clientResult.error !== null)) {
273
321
  event.preventDefault();
274
322
  }
275
- result = submissionResult;
323
+ result = clientResult;
276
324
  }
277
325
 
278
326
  // We might not prevent form submission if server validation is required
@@ -838,6 +886,7 @@ exports.FormOptionsProvider = FormOptionsProvider;
838
886
  exports.FormProvider = FormProvider;
839
887
  exports.GlobalFormOptionsContext = GlobalFormOptionsContext;
840
888
  exports.INITIAL_KEY = INITIAL_KEY;
889
+ exports.PreserveBoundary = PreserveBoundary;
841
890
  exports.useConform = useConform;
842
891
  exports.useControl = useControl;
843
892
  exports.useField = useField;