@conform-to/react 1.17.1 → 1.19.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 +1 -1
- package/dist/future/dom.d.ts +4 -17
- package/dist/future/dom.js +81 -124
- package/dist/future/dom.mjs +81 -121
- package/dist/future/forms.d.ts +1157 -26
- package/dist/future/forms.js +154 -25
- package/dist/future/forms.mjs +152 -28
- package/dist/future/hooks.d.ts +61 -186
- package/dist/future/hooks.js +336 -433
- package/dist/future/hooks.mjs +340 -430
- package/dist/future/index.d.ts +3 -3
- package/dist/future/index.js +6 -6
- package/dist/future/index.mjs +2 -2
- package/dist/future/intent.js +47 -35
- package/dist/future/intent.mjs +49 -37
- package/dist/future/memoize.d.ts +1 -1
- package/dist/future/memoize.js +1 -1
- package/dist/future/memoize.mjs +1 -1
- package/dist/future/state.d.ts +10 -14
- package/dist/future/state.js +58 -68
- package/dist/future/state.mjs +59 -70
- package/dist/future/types.d.ts +207 -139
- package/dist/future/util.d.ts +8 -12
- package/dist/future/util.js +27 -30
- package/dist/future/util.mjs +24 -27
- package/dist/helpers.d.ts +14 -10
- package/dist/helpers.js +14 -10
- package/dist/helpers.mjs +14 -10
- package/package.json +4 -3
package/dist/future/hooks.mjs
CHANGED
|
@@ -1,45 +1,27 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { objectSpread2 as _objectSpread2, objectWithoutProperties as _objectWithoutProperties } from '../_virtual/_rollupPluginBabelHelpers.mjs';
|
|
3
|
-
import {
|
|
4
|
-
import { createContext, useContext, useMemo,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { createGlobalFormsObserver, isGlobalInstance, deepEqual, isFieldElement, change, focus, blur, getFormData, defaultSerialize, isPlainObject, normalizeFormError, dispatchInternalUpdateEvent, parseSubmission, report, requestSubmit } from '@conform-to/dom/future';
|
|
4
|
+
import { createContext, useRef, useContext, useMemo, useState, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, forwardRef } from 'react';
|
|
5
|
+
import { appendUniqueItem, resolveValidateResult } from './util.mjs';
|
|
6
|
+
import { initializeState, updateState } from './state.mjs';
|
|
7
7
|
import { deserializeIntent, applyIntent, intentHandlers, resolveIntent } from './intent.mjs';
|
|
8
|
-
import { cleanupPreservedInputs, preserveInputs,
|
|
8
|
+
import { cleanupPreservedInputs, preserveInputs, deriveDefaultPayload, resolveControlPayload, initializeField, getFormElement, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs';
|
|
9
|
+
import { flushSync } from 'react-dom';
|
|
9
10
|
import { jsx } from 'react/jsx-runtime';
|
|
10
11
|
|
|
11
|
-
var _excluded = ["
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
var _excluded = ["name", "form", "defaultValue", "hidden"],
|
|
13
|
+
_excluded2 = ["defaultValue", "multiple", "hidden"],
|
|
14
|
+
_excluded3 = ["defaultValue", "hidden"],
|
|
15
|
+
_excluded4 = ["defaultValue", "value", "hidden"],
|
|
16
|
+
_excluded5 = ["defaultValue", "hidden"];
|
|
14
17
|
var INITIAL_KEY = 'INITIAL_KEY';
|
|
15
|
-
var
|
|
16
|
-
intentName: DEFAULT_INTENT_NAME,
|
|
17
|
-
observer: createGlobalFormsObserver(),
|
|
18
|
-
serialize,
|
|
19
|
-
shouldValidate: 'onSubmit'
|
|
20
|
-
});
|
|
21
|
-
var FormContextContext = /*#__PURE__*/createContext([]);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Provides form context to child components.
|
|
25
|
-
* Stacks contexts to support nested forms, with latest context taking priority.
|
|
26
|
-
*/
|
|
27
|
-
function FormProvider(props) {
|
|
28
|
-
var stack = useContext(FormContextContext);
|
|
29
|
-
var value = useMemo(
|
|
30
|
-
// Put the latest form context first to ensure that to be the first one found
|
|
31
|
-
() => [props.context].concat(stack), [stack, props.context]);
|
|
32
|
-
return /*#__PURE__*/jsx(FormContextContext.Provider, {
|
|
33
|
-
value: value,
|
|
34
|
-
children: props.children
|
|
35
|
-
});
|
|
36
|
-
}
|
|
18
|
+
var GlobalFormsObserverContext = /*#__PURE__*/createContext(createGlobalFormsObserver());
|
|
37
19
|
|
|
38
20
|
/**
|
|
39
21
|
* Preserves form field values when its contents are unmounted.
|
|
40
22
|
* Useful for multi-step forms and virtualized lists.
|
|
41
23
|
*
|
|
42
|
-
*
|
|
24
|
+
* See https://conform.guide/api/react/future/PreserveBoundary
|
|
43
25
|
*/
|
|
44
26
|
function PreserveBoundary(props) {
|
|
45
27
|
// name is used as key so React properly unmounts/remounts when switching
|
|
@@ -76,30 +58,6 @@ function PreserveBoundaryImpl(props) {
|
|
|
76
58
|
});
|
|
77
59
|
}
|
|
78
60
|
|
|
79
|
-
/**
|
|
80
|
-
* @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`.
|
|
81
|
-
*/
|
|
82
|
-
function FormOptionsProvider(props) {
|
|
83
|
-
var {
|
|
84
|
-
children
|
|
85
|
-
} = props,
|
|
86
|
-
providedOptions = _objectWithoutProperties(props, _excluded);
|
|
87
|
-
var defaultOptions = useContext(GlobalFormOptionsContext);
|
|
88
|
-
var options = useMemo(() => _objectSpread2(_objectSpread2({}, defaultOptions), providedOptions), [defaultOptions, providedOptions]);
|
|
89
|
-
return /*#__PURE__*/jsx(GlobalFormOptionsContext.Provider, {
|
|
90
|
-
value: options,
|
|
91
|
-
children: children
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
function useFormContext(formId) {
|
|
95
|
-
var contexts = useContext(FormContextContext);
|
|
96
|
-
var context = formId ? contexts.find(context => formId === context.formId) : contexts[0];
|
|
97
|
-
if (!context) {
|
|
98
|
-
throw new Error('No form context found; Have you render a <FormProvider /> with the corresponding form context?');
|
|
99
|
-
}
|
|
100
|
-
return context;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
61
|
/**
|
|
104
62
|
* Core form hook that manages form state, validation, and submission.
|
|
105
63
|
* Handles both sync and async validation, intent dispatching, and DOM updates.
|
|
@@ -142,16 +100,23 @@ function useConform(formRef, options) {
|
|
|
142
100
|
var abortControllerRef = useRef(null);
|
|
143
101
|
var handleSubmission = useCallback(function (type, result) {
|
|
144
102
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : optionsRef.current;
|
|
103
|
+
var normalizedResult = !result.error ? result : _objectSpread2(_objectSpread2({}, result), {}, {
|
|
104
|
+
error: normalizeFormError(result.error)
|
|
105
|
+
});
|
|
145
106
|
var intent = result.submission.intent ? deserializeIntent(result.submission.intent) : null;
|
|
146
|
-
var finalResult = applyIntent(
|
|
107
|
+
var finalResult = applyIntent(normalizedResult, intent, {
|
|
147
108
|
handlers: intentHandlers
|
|
148
109
|
});
|
|
110
|
+
var formElement = getFormElement(formRef);
|
|
111
|
+
if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) {
|
|
112
|
+
dispatchInternalUpdateEvent(formElement);
|
|
113
|
+
}
|
|
149
114
|
setState(state => updateState(state, _objectSpread2(_objectSpread2({}, finalResult), {}, {
|
|
150
115
|
type,
|
|
151
116
|
intent,
|
|
152
117
|
ctx: {
|
|
153
118
|
handlers: intentHandlers,
|
|
154
|
-
cancelled: finalResult !==
|
|
119
|
+
cancelled: finalResult !== normalizedResult,
|
|
155
120
|
reset(defaultValue) {
|
|
156
121
|
return initializeState({
|
|
157
122
|
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue
|
|
@@ -161,12 +126,11 @@ function useConform(formRef, options) {
|
|
|
161
126
|
})));
|
|
162
127
|
|
|
163
128
|
// TODO: move on error handler to a new effect
|
|
164
|
-
|
|
165
|
-
if (formElement && result.error) {
|
|
129
|
+
if (formElement && finalResult.error) {
|
|
166
130
|
var _optionsRef$current$o, _optionsRef$current;
|
|
167
131
|
(_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
|
|
168
132
|
formElement,
|
|
169
|
-
error:
|
|
133
|
+
error: finalResult.error,
|
|
170
134
|
intent
|
|
171
135
|
});
|
|
172
136
|
}
|
|
@@ -174,6 +138,10 @@ function useConform(formRef, options) {
|
|
|
174
138
|
}, [formRef, optionsRef]);
|
|
175
139
|
if (options.key !== keyRef.current) {
|
|
176
140
|
keyRef.current = options.key;
|
|
141
|
+
var formElement = getFormElement(formRef);
|
|
142
|
+
if (formElement) {
|
|
143
|
+
dispatchInternalUpdateEvent(formElement);
|
|
144
|
+
}
|
|
177
145
|
setState(initializeState({
|
|
178
146
|
defaultValue: options.defaultValue
|
|
179
147
|
}));
|
|
@@ -200,52 +168,56 @@ function useConform(formRef, options) {
|
|
|
200
168
|
}, [formRef, state.resetKey, state.defaultValue, optionsRef]);
|
|
201
169
|
useSafeLayoutEffect(() => {
|
|
202
170
|
if (state.targetValue) {
|
|
203
|
-
var
|
|
204
|
-
if (!
|
|
171
|
+
var _formElement = getFormElement(formRef);
|
|
172
|
+
if (!_formElement) {
|
|
205
173
|
// eslint-disable-next-line no-console
|
|
206
174
|
console.error('Failed to update form value; No form element found');
|
|
207
175
|
return;
|
|
208
176
|
}
|
|
209
|
-
updateFormValue(
|
|
177
|
+
updateFormValue(_formElement, state.targetValue, optionsRef.current.serialize);
|
|
210
178
|
}
|
|
211
179
|
pendingValueRef.current = undefined;
|
|
212
180
|
}, [formRef, state.targetValue, optionsRef]);
|
|
213
181
|
var handleSubmit = useCallback(event => {
|
|
214
|
-
var _abortControllerRef$c2
|
|
182
|
+
var _abortControllerRef$c2;
|
|
215
183
|
var abortController = new AbortController();
|
|
216
184
|
|
|
217
185
|
// Keep track of the abort controller so we can cancel the previous request if a new one is made
|
|
218
186
|
(_abortControllerRef$c2 = abortControllerRef.current) === null || _abortControllerRef$c2 === void 0 || _abortControllerRef$c2.abort('A new submission is made');
|
|
219
187
|
abortControllerRef.current = abortController;
|
|
220
|
-
var formData;
|
|
221
188
|
var result;
|
|
222
189
|
var resolvedValue;
|
|
190
|
+
var formElement = event.currentTarget;
|
|
191
|
+
var submitEvent = getSubmitEvent(event);
|
|
192
|
+
var formData = getFormData(formElement, submitEvent.submitter);
|
|
193
|
+
var submission = parseSubmission(formData, {
|
|
194
|
+
intentName: optionsRef.current.intentName
|
|
195
|
+
});
|
|
223
196
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
resolvedValue = lastAsyncResultRef.current.resolvedValue;
|
|
229
|
-
} else {
|
|
230
|
-
var _optionsRef$current$o2, _optionsRef$current2;
|
|
231
|
-
var formElement = event.currentTarget;
|
|
232
|
-
var submitEvent = getSubmitEvent(event);
|
|
233
|
-
formData = getFormData(formElement, submitEvent.submitter);
|
|
234
|
-
var submission = parseSubmission(formData, {
|
|
235
|
-
intentName: optionsRef.current.intentName
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Patch missing fields in the submission object
|
|
239
|
-
for (var element of formElement.elements) {
|
|
240
|
-
if (isFieldElement(element) && element.name) {
|
|
241
|
-
submission.fields = appendUniqueItem(submission.fields, element.name);
|
|
242
|
-
}
|
|
197
|
+
// Patch missing fields in the submission object
|
|
198
|
+
for (var element of formElement.elements) {
|
|
199
|
+
if (isFieldElement(element) && element.name) {
|
|
200
|
+
submission.fields = appendUniqueItem(submission.fields, element.name);
|
|
243
201
|
}
|
|
202
|
+
}
|
|
244
203
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
204
|
+
// Override submission value if the pending value is not applied yet (i.e. batch updates)
|
|
205
|
+
if (pendingValueRef.current !== undefined) {
|
|
206
|
+
submission.payload = pendingValueRef.current;
|
|
207
|
+
}
|
|
208
|
+
var lastAsyncResult = lastAsyncResultRef.current;
|
|
209
|
+
|
|
210
|
+
// Clear the last async result so it won't affect the next submission
|
|
211
|
+
lastAsyncResultRef.current = null;
|
|
212
|
+
if (lastAsyncResult &&
|
|
213
|
+
// Only default submission will be re-submitted after async validation
|
|
214
|
+
!submission.intent &&
|
|
215
|
+
// Ensure the submission payload is the same as the one being validated
|
|
216
|
+
deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) {
|
|
217
|
+
result = lastAsyncResult.result;
|
|
218
|
+
resolvedValue = lastAsyncResult.resolvedValue;
|
|
219
|
+
} else {
|
|
220
|
+
var _optionsRef$current$o2, _optionsRef$current2;
|
|
249
221
|
var value = resolveIntent(submission);
|
|
250
222
|
var submissionResult = report(submission, {
|
|
251
223
|
keepFiles: true,
|
|
@@ -256,7 +228,7 @@ function useConform(formRef, options) {
|
|
|
256
228
|
value !== undefined ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, {
|
|
257
229
|
payload: value,
|
|
258
230
|
error: {
|
|
259
|
-
formErrors:
|
|
231
|
+
formErrors: null,
|
|
260
232
|
fieldErrors: {}
|
|
261
233
|
},
|
|
262
234
|
intent: submission.intent ? deserializeIntent(submission.intent) : null,
|
|
@@ -290,16 +262,20 @@ function useConform(formRef, options) {
|
|
|
290
262
|
|
|
291
263
|
// If the form is meant to be submitted and there is no error
|
|
292
264
|
if (submissionResult.error === null && !submission.intent) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
265
|
+
// Keep track of the validated payload and resume submission on the next task.
|
|
266
|
+
// Calling requestSubmit() directly from the async callback, or from a
|
|
267
|
+
// microtask, can still be ignored before the native submission lifecycle
|
|
268
|
+
// has fully settled.
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
if (abortController.signal.aborted) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
lastAsyncResultRef.current = {
|
|
274
|
+
resolvedValue: value,
|
|
275
|
+
result: submissionResult
|
|
276
|
+
};
|
|
277
|
+
requestSubmit(formElement, submitEvent.submitter);
|
|
278
|
+
}, 0);
|
|
303
279
|
}
|
|
304
280
|
}
|
|
305
281
|
});
|
|
@@ -345,287 +321,20 @@ function useConform(formRef, options) {
|
|
|
345
321
|
return [state, handleSubmit];
|
|
346
322
|
}
|
|
347
323
|
|
|
348
|
-
/**
|
|
349
|
-
* The main React hook for form management. Handles form state, validation, and submission
|
|
350
|
-
* while providing access to form metadata, field objects, and form actions.
|
|
351
|
-
*
|
|
352
|
-
* It can be called in two ways:
|
|
353
|
-
* - **Schema first**: Pass a schema as the first argument for automatic validation with type inference
|
|
354
|
-
* - **Manual configuration**: Pass options with custom `onValidate` handler for manual validation
|
|
355
|
-
*
|
|
356
|
-
* @see https://conform.guide/api/react/future/useForm
|
|
357
|
-
* @example Schema first setup with zod:
|
|
358
|
-
*
|
|
359
|
-
* ```tsx
|
|
360
|
-
* const { form, fields } = useForm(zodSchema, {
|
|
361
|
-
* lastResult,
|
|
362
|
-
* shouldValidate: 'onBlur',
|
|
363
|
-
* });
|
|
364
|
-
*
|
|
365
|
-
* return (
|
|
366
|
-
* <form {...form.props}>
|
|
367
|
-
* <input name={fields.email.name} defaultValue={fields.email.defaultValue} />
|
|
368
|
-
* <div>{fields.email.errors}</div>
|
|
369
|
-
* </form>
|
|
370
|
-
* );
|
|
371
|
-
* ```
|
|
372
|
-
*
|
|
373
|
-
* @example Manual configuration setup with custom validation:
|
|
374
|
-
*
|
|
375
|
-
* ```tsx
|
|
376
|
-
* const { form, fields } = useForm({
|
|
377
|
-
* onValidate({ payload, error }) {
|
|
378
|
-
* if (!payload.email) {
|
|
379
|
-
* error.fieldErrors.email = ['Required'];
|
|
380
|
-
* }
|
|
381
|
-
* return error;
|
|
382
|
-
* }
|
|
383
|
-
* });
|
|
384
|
-
*
|
|
385
|
-
* return (
|
|
386
|
-
* <form {...form.props}>
|
|
387
|
-
* <input name={fields.email.name} defaultValue={fields.email.defaultValue} />
|
|
388
|
-
* <div>{fields.email.errors}</div>
|
|
389
|
-
* </form>
|
|
390
|
-
* );
|
|
391
|
-
* ```
|
|
392
|
-
*/
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @deprecated Use `useForm(schema, options)` instead for better type inference.
|
|
396
|
-
*/
|
|
397
|
-
|
|
398
|
-
function useForm(schemaOrOptions, maybeOptions) {
|
|
399
|
-
var _options$onError;
|
|
400
|
-
var schema;
|
|
401
|
-
var options;
|
|
402
|
-
if (maybeOptions) {
|
|
403
|
-
schema = schemaOrOptions;
|
|
404
|
-
options = maybeOptions;
|
|
405
|
-
} else {
|
|
406
|
-
var fullOptions = schemaOrOptions;
|
|
407
|
-
options = fullOptions;
|
|
408
|
-
schema = fullOptions.schema;
|
|
409
|
-
}
|
|
410
|
-
var {
|
|
411
|
-
id,
|
|
412
|
-
constraint
|
|
413
|
-
} = options;
|
|
414
|
-
var globalOptions = useContext(GlobalFormOptionsContext);
|
|
415
|
-
var optionsRef = useLatest(options);
|
|
416
|
-
var globalOptionsRef = useLatest(globalOptions);
|
|
417
|
-
var fallbackId = useId();
|
|
418
|
-
var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId);
|
|
419
|
-
var [state, handleSubmit] = useConform(formId, _objectSpread2(_objectSpread2({}, options), {}, {
|
|
420
|
-
serialize: globalOptions.serialize,
|
|
421
|
-
intentName: globalOptions.intentName,
|
|
422
|
-
onError: (_options$onError = options.onError) !== null && _options$onError !== void 0 ? _options$onError : focusFirstInvalidField,
|
|
423
|
-
onValidate(ctx) {
|
|
424
|
-
var _options$onValidate, _options$onValidate2, _options;
|
|
425
|
-
if (schema) {
|
|
426
|
-
var standardResult = schema['~standard'].validate(ctx.payload);
|
|
427
|
-
if (standardResult instanceof Promise) {
|
|
428
|
-
return standardResult.then(actualStandardResult => {
|
|
429
|
-
if (typeof options.onValidate === 'function') {
|
|
430
|
-
throw new Error('The "onValidate" handler is not supported when used with asynchronous schema validation.');
|
|
431
|
-
}
|
|
432
|
-
return resolveStandardSchemaResult(actualStandardResult);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
var resolvedResult = resolveStandardSchemaResult(standardResult);
|
|
436
|
-
if (!options.onValidate) {
|
|
437
|
-
return resolvedResult;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Update the schema error in the context
|
|
441
|
-
if (resolvedResult.error) {
|
|
442
|
-
ctx.error = resolvedResult.error;
|
|
443
|
-
}
|
|
444
|
-
ctx.schemaValue = resolvedResult.value;
|
|
445
|
-
var validateResult = resolveValidateResult(options.onValidate(ctx));
|
|
446
|
-
if (validateResult.syncResult) {
|
|
447
|
-
var _validateResult$syncR, _validateResult$syncR2;
|
|
448
|
-
(_validateResult$syncR2 = (_validateResult$syncR = validateResult.syncResult).value) !== null && _validateResult$syncR2 !== void 0 ? _validateResult$syncR2 : _validateResult$syncR.value = resolvedResult.value;
|
|
449
|
-
}
|
|
450
|
-
if (validateResult.asyncResult) {
|
|
451
|
-
validateResult.asyncResult = validateResult.asyncResult.then(result => {
|
|
452
|
-
var _result$value;
|
|
453
|
-
(_result$value = result.value) !== null && _result$value !== void 0 ? _result$value : result.value = resolvedResult.value;
|
|
454
|
-
return result;
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
return [validateResult.syncResult, validateResult.asyncResult];
|
|
458
|
-
}
|
|
459
|
-
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 : {
|
|
460
|
-
// To avoid conform falling back to server validation,
|
|
461
|
-
// if neither schema nor validation handler is provided,
|
|
462
|
-
// we just treat it as a valid client submission
|
|
463
|
-
error: null
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
}));
|
|
467
|
-
var intent = useIntent(formId);
|
|
468
|
-
var context = useMemo(() => ({
|
|
469
|
-
formId,
|
|
470
|
-
state,
|
|
471
|
-
constraint: constraint !== null && constraint !== void 0 ? constraint : null,
|
|
472
|
-
handleSubmit,
|
|
473
|
-
handleInput(event) {
|
|
474
|
-
var _optionsRef$current$o4, _optionsRef$current4, _globalOptionsRef$cur;
|
|
475
|
-
if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
(_optionsRef$current$o4 = (_optionsRef$current4 = optionsRef.current).onInput) === null || _optionsRef$current$o4 === void 0 || _optionsRef$current$o4.call(_optionsRef$current4, _objectSpread2(_objectSpread2({}, event), {}, {
|
|
479
|
-
target: event.target,
|
|
480
|
-
currentTarget: event.target.form
|
|
481
|
-
}));
|
|
482
|
-
if (event.defaultPrevented) {
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
var {
|
|
486
|
-
shouldValidate = globalOptionsRef.current.shouldValidate,
|
|
487
|
-
shouldRevalidate = (_globalOptionsRef$cur = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur !== void 0 ? _globalOptionsRef$cur : shouldValidate
|
|
488
|
-
} = optionsRef.current;
|
|
489
|
-
if (isTouched(state, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
|
|
490
|
-
intent.validate(event.target.name);
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
handleBlur(event) {
|
|
494
|
-
var _optionsRef$current$o5, _optionsRef$current5, _globalOptionsRef$cur2;
|
|
495
|
-
if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
(_optionsRef$current$o5 = (_optionsRef$current5 = optionsRef.current).onBlur) === null || _optionsRef$current$o5 === void 0 || _optionsRef$current$o5.call(_optionsRef$current5, _objectSpread2(_objectSpread2({}, event), {}, {
|
|
499
|
-
target: event.target,
|
|
500
|
-
currentTarget: event.target.form
|
|
501
|
-
}));
|
|
502
|
-
if (event.defaultPrevented) {
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
var {
|
|
506
|
-
shouldValidate = globalOptionsRef.current.shouldValidate,
|
|
507
|
-
shouldRevalidate = (_globalOptionsRef$cur2 = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur2 !== void 0 ? _globalOptionsRef$cur2 : shouldValidate
|
|
508
|
-
} = optionsRef.current;
|
|
509
|
-
if (isTouched(state, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
|
|
510
|
-
intent.validate(event.target.name);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}), [formId, state, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
|
|
514
|
-
var form = useMemo(() => getFormMetadata(context, {
|
|
515
|
-
serialize: globalOptions.serialize,
|
|
516
|
-
extendFieldMetadata: globalOptions.defineCustomMetadata
|
|
517
|
-
}), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
|
|
518
|
-
var fields = useMemo(() => getFieldset(context, {
|
|
519
|
-
serialize: globalOptions.serialize,
|
|
520
|
-
extendFieldMetadata: globalOptions.defineCustomMetadata
|
|
521
|
-
}), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
|
|
522
|
-
return {
|
|
523
|
-
form,
|
|
524
|
-
fields,
|
|
525
|
-
intent
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* A React hook that provides access to form-level metadata and state.
|
|
531
|
-
* Requires `FormProvider` context when used in child components.
|
|
532
|
-
*
|
|
533
|
-
* @see https://conform.guide/api/react/future/useFormMetadata
|
|
534
|
-
* @example
|
|
535
|
-
* ```tsx
|
|
536
|
-
* function ErrorSummary() {
|
|
537
|
-
* const form = useFormMetadata();
|
|
538
|
-
*
|
|
539
|
-
* if (form.valid) return null;
|
|
540
|
-
*
|
|
541
|
-
* return (
|
|
542
|
-
* <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
|
|
543
|
-
* );
|
|
544
|
-
* }
|
|
545
|
-
* ```
|
|
546
|
-
*/
|
|
547
|
-
function useFormMetadata() {
|
|
548
|
-
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
549
|
-
var globalOptions = useContext(GlobalFormOptionsContext);
|
|
550
|
-
var context = useFormContext(options.formId);
|
|
551
|
-
var formMetadata = useMemo(() => getFormMetadata(context, {
|
|
552
|
-
serialize: globalOptions.serialize,
|
|
553
|
-
extendFieldMetadata: globalOptions.defineCustomMetadata
|
|
554
|
-
}), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
|
|
555
|
-
return formMetadata;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* A React hook that provides access to a specific field's metadata and state.
|
|
560
|
-
* Requires `FormProvider` context when used in child components.
|
|
561
|
-
*
|
|
562
|
-
* @see https://conform.guide/api/react/future/useField
|
|
563
|
-
* @example
|
|
564
|
-
* ```tsx
|
|
565
|
-
* function FormField({ name, label }) {
|
|
566
|
-
* const field = useField(name);
|
|
567
|
-
*
|
|
568
|
-
* return (
|
|
569
|
-
* <div>
|
|
570
|
-
* <label htmlFor={field.id}>{label}</label>
|
|
571
|
-
* <input id={field.id} name={field.name} defaultValue={field.defaultValue} />
|
|
572
|
-
* {field.errors && <div>{field.errors.join(', ')}</div>}
|
|
573
|
-
* </div>
|
|
574
|
-
* );
|
|
575
|
-
* }
|
|
576
|
-
* ```
|
|
577
|
-
*/
|
|
578
|
-
function useField(name) {
|
|
579
|
-
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
580
|
-
var globalOptions = useContext(GlobalFormOptionsContext);
|
|
581
|
-
var context = useFormContext(options.formId);
|
|
582
|
-
var field = useMemo(() => getField(context, {
|
|
583
|
-
name,
|
|
584
|
-
serialize: globalOptions.serialize,
|
|
585
|
-
extendFieldMetadata: globalOptions.defineCustomMetadata
|
|
586
|
-
}), [context, name, globalOptions.serialize, globalOptions.defineCustomMetadata]);
|
|
587
|
-
return field;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* A React hook that provides an intent dispatcher for programmatic form actions.
|
|
592
|
-
* Intent dispatchers allow you to trigger form operations like validation, field updates,
|
|
593
|
-
* and array manipulations without manual form submission.
|
|
594
|
-
*
|
|
595
|
-
* @see https://conform.guide/api/react/future/useIntent
|
|
596
|
-
* @example
|
|
597
|
-
* ```tsx
|
|
598
|
-
* function ResetButton() {
|
|
599
|
-
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
600
|
-
* const intent = useIntent(buttonRef);
|
|
601
|
-
*
|
|
602
|
-
* return (
|
|
603
|
-
* <button type="button" ref={buttonRef} onClick={() => intent.reset()}>
|
|
604
|
-
* Reset Form
|
|
605
|
-
* </button>
|
|
606
|
-
* );
|
|
607
|
-
* }
|
|
608
|
-
* ```
|
|
609
|
-
*/
|
|
610
|
-
function useIntent(formRef) {
|
|
611
|
-
var globalOptions = useContext(GlobalFormOptionsContext);
|
|
612
|
-
return useMemo(() => createIntentDispatcher(() => getFormElement(formRef), globalOptions.intentName), [formRef, globalOptions.intentName]);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
324
|
/**
|
|
616
325
|
* A React hook that lets you sync the state of an input and dispatch native form events from it.
|
|
617
|
-
* This is useful when emulating native input behavior — typically by rendering a hidden base
|
|
326
|
+
* This is useful when emulating native input behavior — typically by rendering a hidden base control
|
|
618
327
|
* and syncing it with a custom input.
|
|
619
328
|
*
|
|
620
|
-
*
|
|
329
|
+
* **Example:**
|
|
621
330
|
* ```ts
|
|
622
331
|
* const control = useControl(options);
|
|
623
332
|
* ```
|
|
624
333
|
*/
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
334
|
+
|
|
335
|
+
function useControl() {
|
|
336
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
337
|
+
var observer = useContext(GlobalFormsObserverContext);
|
|
629
338
|
var inputRef = useRef(null);
|
|
630
339
|
var formRef = useMemo(() => ({
|
|
631
340
|
get current() {
|
|
@@ -637,29 +346,40 @@ function useControl(options) {
|
|
|
637
346
|
return Array.isArray(input) ? (_input$0$form = (_input$ = input[0]) === null || _input$ === void 0 ? void 0 : _input$.form) !== null && _input$0$form !== void 0 ? _input$0$form : null : input.form;
|
|
638
347
|
}
|
|
639
348
|
}), []);
|
|
349
|
+
var [defaultValue, setDefaultValue] = useState(() => deriveDefaultPayload(options));
|
|
350
|
+
var pendingDefaultValueSyncRef = useRef(false);
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Keep defaultValue in sync with external option updates during render.
|
|
354
|
+
* This is required for structural controls where hidden descendants must be
|
|
355
|
+
* rendered in the same cycle as form state updates (e.g. update intents).
|
|
356
|
+
*/
|
|
357
|
+
if (pendingDefaultValueSyncRef.current && inputRef.current && isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) {
|
|
358
|
+
pendingDefaultValueSyncRef.current = false;
|
|
359
|
+
setDefaultValue(() => deriveDefaultPayload(options));
|
|
360
|
+
}
|
|
640
361
|
var eventDispatched = useRef({});
|
|
641
|
-
var
|
|
642
|
-
var snapshotRef = useRef(defaultSnapshot);
|
|
362
|
+
var snapshotRef = useRef(defaultValue);
|
|
643
363
|
var optionsRef = useRef(options);
|
|
644
364
|
useEffect(() => {
|
|
645
365
|
optionsRef.current = options;
|
|
646
366
|
});
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
367
|
+
useEffect(() => observer.onInternalUpdate(event => {
|
|
368
|
+
var input = inputRef.current;
|
|
369
|
+
if (input && input instanceof HTMLFieldSetElement && event.target === input.form) {
|
|
370
|
+
pendingDefaultValueSyncRef.current = true;
|
|
371
|
+
}
|
|
372
|
+
}), [observer]);
|
|
373
|
+
var payloadSnapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => {
|
|
374
|
+
var _inputRef$current;
|
|
652
375
|
var input = event.target;
|
|
653
|
-
if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) {
|
|
376
|
+
if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.contains(input)) {
|
|
654
377
|
callback();
|
|
655
378
|
}
|
|
656
379
|
}), [observer]), () => {
|
|
657
380
|
var input = inputRef.current;
|
|
658
381
|
var prev = snapshotRef.current;
|
|
659
|
-
var next =
|
|
660
|
-
value: getRadioGroupValue(input),
|
|
661
|
-
options: getCheckboxGroupValue(input)
|
|
662
|
-
} : getInputSnapshot(input);
|
|
382
|
+
var next = input ? resolveControlPayload(input) : defaultValue;
|
|
663
383
|
if (deepEqual(prev, next)) {
|
|
664
384
|
return prev;
|
|
665
385
|
}
|
|
@@ -669,7 +389,8 @@ function useControl(options) {
|
|
|
669
389
|
useEffect(() => {
|
|
670
390
|
var createEventListener = listener => {
|
|
671
391
|
return event => {
|
|
672
|
-
|
|
392
|
+
var _inputRef$current2;
|
|
393
|
+
if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : event.target instanceof Node && ((_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.contains(event.target))) {
|
|
673
394
|
var timer = eventDispatched.current[listener];
|
|
674
395
|
if (timer) {
|
|
675
396
|
clearTimeout(timer);
|
|
@@ -678,8 +399,8 @@ function useControl(options) {
|
|
|
678
399
|
eventDispatched.current[listener] = undefined;
|
|
679
400
|
});
|
|
680
401
|
if (listener === 'focus') {
|
|
681
|
-
var _optionsRef$
|
|
682
|
-
(_optionsRef$
|
|
402
|
+
var _optionsRef$current4, _optionsRef$current4$;
|
|
403
|
+
(_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 || (_optionsRef$current4$ = _optionsRef$current4.onFocus) === null || _optionsRef$current4$ === void 0 || _optionsRef$current4$.call(_optionsRef$current4);
|
|
683
404
|
}
|
|
684
405
|
}
|
|
685
406
|
};
|
|
@@ -697,10 +418,62 @@ function useControl(options) {
|
|
|
697
418
|
};
|
|
698
419
|
}, []);
|
|
699
420
|
return {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
421
|
+
defaultValue,
|
|
422
|
+
get payload() {
|
|
423
|
+
if (payloadSnapshot != null && 'parse' in options) {
|
|
424
|
+
try {
|
|
425
|
+
return options.parse(payloadSnapshot);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
var payloadText = '';
|
|
428
|
+
try {
|
|
429
|
+
payloadText = JSON.stringify(payloadSnapshot, null, 2);
|
|
430
|
+
} catch (_unused) {
|
|
431
|
+
payloadText = '<unserializable payload>';
|
|
432
|
+
}
|
|
433
|
+
throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), {
|
|
434
|
+
cause: error
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return payloadSnapshot;
|
|
439
|
+
},
|
|
440
|
+
get value() {
|
|
441
|
+
if (payloadSnapshot === null) {
|
|
442
|
+
return '';
|
|
443
|
+
}
|
|
444
|
+
if (typeof payloadSnapshot === 'string') {
|
|
445
|
+
return payloadSnapshot;
|
|
446
|
+
}
|
|
447
|
+
return undefined;
|
|
448
|
+
},
|
|
449
|
+
get checked() {
|
|
450
|
+
if (payloadSnapshot === null) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
var value = 'value' in options && options.value ? options.value : 'on';
|
|
454
|
+
if (payloadSnapshot === value) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
},
|
|
459
|
+
get options() {
|
|
460
|
+
if (payloadSnapshot === null) {
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) {
|
|
464
|
+
return payloadSnapshot;
|
|
465
|
+
}
|
|
466
|
+
return undefined;
|
|
467
|
+
},
|
|
468
|
+
get files() {
|
|
469
|
+
if (payloadSnapshot === null) {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => isGlobalInstance(item, 'File'))) {
|
|
473
|
+
return payloadSnapshot;
|
|
474
|
+
}
|
|
475
|
+
return undefined;
|
|
476
|
+
},
|
|
704
477
|
formRef,
|
|
705
478
|
register: useCallback(element => {
|
|
706
479
|
if (!element) {
|
|
@@ -714,17 +487,16 @@ function useControl(options) {
|
|
|
714
487
|
element.hidden = true;
|
|
715
488
|
element.removeAttribute('type');
|
|
716
489
|
}
|
|
717
|
-
if (shouldHandleFocus) {
|
|
718
|
-
makeInputFocusable(element);
|
|
719
|
-
}
|
|
720
490
|
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
721
|
-
var _optionsRef$current$v, _optionsRef$current7;
|
|
722
491
|
// React set the value as empty string incorrectly when the value is undefined
|
|
723
492
|
// This make sure the checkbox value falls back to the default value "on" properly
|
|
724
|
-
//
|
|
725
|
-
|
|
493
|
+
// See https://github.com/facebook/react/issues/17590
|
|
494
|
+
var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on';
|
|
495
|
+
element.value = value;
|
|
726
496
|
}
|
|
727
497
|
initializeField(element, optionsRef.current);
|
|
498
|
+
} else if (element instanceof HTMLFieldSetElement) {
|
|
499
|
+
inputRef.current = element;
|
|
728
500
|
} else {
|
|
729
501
|
var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2;
|
|
730
502
|
var inputs = Array.from(element);
|
|
@@ -734,39 +506,36 @@ function useControl(options) {
|
|
|
734
506
|
throw new Error('You can only register a checkbox or radio group with the same name');
|
|
735
507
|
}
|
|
736
508
|
inputRef.current = inputs;
|
|
737
|
-
|
|
738
|
-
var
|
|
739
|
-
|
|
740
|
-
|
|
509
|
+
if ('defaultValue' in optionsRef.current) {
|
|
510
|
+
for (var input of inputs) {
|
|
511
|
+
var _optionsRef$current5;
|
|
512
|
+
initializeField(input, {
|
|
513
|
+
// We will not be uitlizing defaultChecked / value on checkbox / radio group
|
|
514
|
+
defaultValue: (_optionsRef$current5 = optionsRef.current) === null || _optionsRef$current5 === void 0 ? void 0 : _optionsRef$current5.defaultValue
|
|
515
|
+
});
|
|
741
516
|
}
|
|
742
|
-
initializeField(input, {
|
|
743
|
-
// We will not be uitlizing defaultChecked / value on checkbox / radio group
|
|
744
|
-
defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue
|
|
745
|
-
});
|
|
746
517
|
}
|
|
747
518
|
}
|
|
748
|
-
}, [
|
|
519
|
+
}, []),
|
|
749
520
|
change: useCallback(value => {
|
|
750
521
|
if (!eventDispatched.current.change) {
|
|
751
|
-
var
|
|
752
|
-
var
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
// So we will pick the first element that should be checked
|
|
763
|
-
return isChecked;
|
|
764
|
-
default:
|
|
765
|
-
return false;
|
|
766
|
-
}
|
|
767
|
-
}) : inputRef.current;
|
|
522
|
+
var element = inputRef.current;
|
|
523
|
+
var isFieldset = element instanceof HTMLFieldSetElement;
|
|
524
|
+
var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value;
|
|
525
|
+
if (isFieldset) {
|
|
526
|
+
// Fieldset mode renders hidden descendant inputs from defaultValue.
|
|
527
|
+
// Flush this update before dispatching events so listeners see the
|
|
528
|
+
// latest form structure in the same input/change cycle.
|
|
529
|
+
flushSync(() => {
|
|
530
|
+
setDefaultValue(serializedValue);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
768
533
|
if (element) {
|
|
769
|
-
change(element,
|
|
534
|
+
change(element, serializedValue, {
|
|
535
|
+
// Sometimes no change is made on the inputs but done through DOM mutation.
|
|
536
|
+
// But we still want to dispatch the event to notify listeners.
|
|
537
|
+
forceDispatch: isFieldset
|
|
538
|
+
});
|
|
770
539
|
}
|
|
771
540
|
}
|
|
772
541
|
if (eventDispatched.current.change) {
|
|
@@ -808,8 +577,9 @@ function useControl(options) {
|
|
|
808
577
|
* Returns `undefined` when the form element is not available (e.g., on SSR or initial client render),
|
|
809
578
|
* unless a `fallback` is provided.
|
|
810
579
|
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
580
|
+
* See https://conform.guide/api/react/future/useFormData
|
|
581
|
+
*
|
|
582
|
+
* **Example:**
|
|
813
583
|
* ```ts
|
|
814
584
|
* const value = useFormData(
|
|
815
585
|
* formRef,
|
|
@@ -819,9 +589,7 @@ function useControl(options) {
|
|
|
819
589
|
*/
|
|
820
590
|
|
|
821
591
|
function useFormData(formRef, select, options) {
|
|
822
|
-
var
|
|
823
|
-
observer
|
|
824
|
-
} = useContext(GlobalFormOptionsContext);
|
|
592
|
+
var observer = useContext(GlobalFormsObserverContext);
|
|
825
593
|
var valueRef = useRef();
|
|
826
594
|
var formDataRef = useRef();
|
|
827
595
|
var value = useSyncExternalStore(useCallback(callback => {
|
|
@@ -877,4 +645,146 @@ function useLatest(value) {
|
|
|
877
645
|
return ref;
|
|
878
646
|
}
|
|
879
647
|
|
|
880
|
-
|
|
648
|
+
/**
|
|
649
|
+
* A component that renders hidden base control(s) based on the shape of defaultValue.
|
|
650
|
+
* Used with useControl to sync complex values with form data.
|
|
651
|
+
*
|
|
652
|
+
* **Example:**
|
|
653
|
+
* ```tsx
|
|
654
|
+
* const control = useControl<{ street: string; city: string }>({
|
|
655
|
+
* defaultValue: { street: '123 Main St', city: 'Anytown' },
|
|
656
|
+
* parse(payload) {
|
|
657
|
+
* if (
|
|
658
|
+
* typeof payload === 'object' &&
|
|
659
|
+
* payload !== null &&
|
|
660
|
+
* 'street' in payload &&
|
|
661
|
+
* 'city' in payload &&
|
|
662
|
+
* typeof payload.street === 'string' &&
|
|
663
|
+
* typeof payload.city === 'string'
|
|
664
|
+
* ) {
|
|
665
|
+
* return payload;
|
|
666
|
+
* }
|
|
667
|
+
*
|
|
668
|
+
* throw new Error('Unexpected payload shape');
|
|
669
|
+
* },
|
|
670
|
+
* });
|
|
671
|
+
*
|
|
672
|
+
* <BaseControl
|
|
673
|
+
* type="fieldset"
|
|
674
|
+
* name="address"
|
|
675
|
+
* ref={control.register}
|
|
676
|
+
* defaultValue={control.defaultValue}
|
|
677
|
+
* />
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
var BaseControl = /*#__PURE__*/forwardRef(function BaseControl(props, ref) {
|
|
681
|
+
function formatValue(value) {
|
|
682
|
+
var serialized = defaultSerialize(value);
|
|
683
|
+
if (typeof serialized === 'string') {
|
|
684
|
+
return serialized;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// null, undefined, File, or array - fallback to empty string
|
|
688
|
+
return '';
|
|
689
|
+
}
|
|
690
|
+
function renderInput(name, value, form) {
|
|
691
|
+
if (Array.isArray(value)) {
|
|
692
|
+
return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form));
|
|
693
|
+
}
|
|
694
|
+
if (isPlainObject(value)) {
|
|
695
|
+
return Object.entries(value).map(_ref5 => {
|
|
696
|
+
var [key, val] = _ref5;
|
|
697
|
+
return renderInput("".concat(name, ".").concat(key), val, form);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return /*#__PURE__*/jsx("input", {
|
|
701
|
+
name: name,
|
|
702
|
+
defaultValue: formatValue(value),
|
|
703
|
+
form: form
|
|
704
|
+
}, name);
|
|
705
|
+
}
|
|
706
|
+
if (props.type === 'fieldset') {
|
|
707
|
+
var {
|
|
708
|
+
name,
|
|
709
|
+
form,
|
|
710
|
+
defaultValue: _defaultValue,
|
|
711
|
+
hidden: _hidden = true
|
|
712
|
+
} = props,
|
|
713
|
+
fieldsetProps = _objectWithoutProperties(props, _excluded);
|
|
714
|
+
return /*#__PURE__*/jsx("fieldset", _objectSpread2(_objectSpread2({}, fieldsetProps), {}, {
|
|
715
|
+
ref: ref,
|
|
716
|
+
name: name,
|
|
717
|
+
form: form,
|
|
718
|
+
hidden: _hidden,
|
|
719
|
+
children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null
|
|
720
|
+
}));
|
|
721
|
+
}
|
|
722
|
+
if (props.type === 'select') {
|
|
723
|
+
var {
|
|
724
|
+
defaultValue: _defaultValue2,
|
|
725
|
+
multiple = Array.isArray(_defaultValue2),
|
|
726
|
+
hidden: _hidden2 = true
|
|
727
|
+
} = props,
|
|
728
|
+
selectProps = _objectWithoutProperties(props, _excluded2);
|
|
729
|
+
if (multiple) {
|
|
730
|
+
var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)];
|
|
731
|
+
return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, {
|
|
732
|
+
ref: ref,
|
|
733
|
+
defaultValue: defaultOptions,
|
|
734
|
+
hidden: _hidden2,
|
|
735
|
+
multiple: true,
|
|
736
|
+
children: defaultOptions.map((option, index) => /*#__PURE__*/jsx("option", {
|
|
737
|
+
value: option,
|
|
738
|
+
children: option
|
|
739
|
+
}, index))
|
|
740
|
+
}));
|
|
741
|
+
}
|
|
742
|
+
var defaultOption = formatValue(_defaultValue2);
|
|
743
|
+
return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, {
|
|
744
|
+
ref: ref,
|
|
745
|
+
defaultValue: defaultOption,
|
|
746
|
+
hidden: _hidden2,
|
|
747
|
+
children: [defaultOption].map((option, index) => /*#__PURE__*/jsx("option", {
|
|
748
|
+
value: option,
|
|
749
|
+
children: option
|
|
750
|
+
}, index))
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
if (props.type === 'textarea') {
|
|
754
|
+
var {
|
|
755
|
+
defaultValue: _defaultValue3,
|
|
756
|
+
hidden: _hidden3 = true
|
|
757
|
+
} = props,
|
|
758
|
+
textareaProps = _objectWithoutProperties(props, _excluded3);
|
|
759
|
+
return /*#__PURE__*/jsx("textarea", _objectSpread2(_objectSpread2({}, textareaProps), {}, {
|
|
760
|
+
defaultValue: formatValue(_defaultValue3),
|
|
761
|
+
ref: ref,
|
|
762
|
+
hidden: _hidden3
|
|
763
|
+
}));
|
|
764
|
+
}
|
|
765
|
+
if (props.type === 'checkbox' || props.type === 'radio') {
|
|
766
|
+
var {
|
|
767
|
+
defaultValue: _defaultValue4 = 'on',
|
|
768
|
+
value = _defaultValue4,
|
|
769
|
+
hidden: _hidden4 = true
|
|
770
|
+
} = props,
|
|
771
|
+
_inputProps = _objectWithoutProperties(props, _excluded4);
|
|
772
|
+
return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, _inputProps), {}, {
|
|
773
|
+
ref: ref,
|
|
774
|
+
value: value,
|
|
775
|
+
hidden: _hidden4
|
|
776
|
+
}));
|
|
777
|
+
}
|
|
778
|
+
var {
|
|
779
|
+
defaultValue,
|
|
780
|
+
hidden = true
|
|
781
|
+
} = props,
|
|
782
|
+
inputProps = _objectWithoutProperties(props, _excluded5);
|
|
783
|
+
return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, inputProps), {}, {
|
|
784
|
+
ref: ref,
|
|
785
|
+
defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined,
|
|
786
|
+
hidden: hidden
|
|
787
|
+
}));
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
export { BaseControl, GlobalFormsObserverContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useFormData, useLatest, useSafeLayoutEffect };
|