@conform-to/react 1.17.0 → 1.18.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 +75 -122
- package/dist/future/dom.mjs +75 -119
- package/dist/future/forms.js +2 -2
- package/dist/future/forms.mjs +2 -2
- package/dist/future/hooks.d.ts +38 -24
- package/dist/future/hooks.js +317 -93
- package/dist/future/hooks.mjs +320 -97
- package/dist/future/index.d.ts +2 -2
- package/dist/future/index.js +1 -0
- package/dist/future/index.mjs +1 -1
- package/dist/future/intent.js +23 -23
- package/dist/future/intent.mjs +25 -25
- package/dist/future/state.d.ts +5 -5
- package/dist/future/state.js +41 -48
- package/dist/future/state.mjs +42 -50
- package/dist/future/types.d.ts +142 -29
- package/dist/future/util.d.ts +2 -2
- package/dist/future/util.js +10 -10
- package/dist/future/util.mjs +10 -10
- package/package.json +4 -3
package/dist/future/hooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FieldName, type FormValue, type Serialize, type SubmissionResult, createGlobalFormsObserver } from '@conform-to/dom/future';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
|
-
import type { FormContext, IntentDispatcher, FormMetadata, Fieldset, GlobalFormOptions, FormOptions, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef, BaseErrorShape, DefaultErrorShape, BaseSchemaType, InferInput, InferOutput } from './types';
|
|
3
|
+
import type { FormContext, IntentDispatcher, FormMetadata, Fieldset, GlobalFormOptions, FormOptions, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef, BaseErrorShape, DefaultErrorShape, BaseSchemaType, InferInput, InferOutput, BaseControlProps, StandardControlOptions, DefaultControlValue, CheckedControlOptions, CustomControlOptions } from './types';
|
|
4
4
|
import { StandardSchemaV1 } from './standard-schema';
|
|
5
5
|
export declare const INITIAL_KEY = "INITIAL_KEY";
|
|
6
6
|
export declare const GlobalFormOptionsContext: import("react").Context<GlobalFormOptions & {
|
|
@@ -194,7 +194,7 @@ export declare function useField<FieldShape = any>(name: FieldName<FieldShape>,
|
|
|
194
194
|
export declare function useIntent<FormShape extends Record<string, any>>(formRef: FormRef): IntentDispatcher<FormShape>;
|
|
195
195
|
/**
|
|
196
196
|
* A React hook that lets you sync the state of an input and dispatch native form events from it.
|
|
197
|
-
* This is useful when emulating native input behavior — typically by rendering a hidden base
|
|
197
|
+
* This is useful when emulating native input behavior — typically by rendering a hidden base control
|
|
198
198
|
* and syncing it with a custom input.
|
|
199
199
|
*
|
|
200
200
|
* @example
|
|
@@ -202,28 +202,9 @@ export declare function useIntent<FormShape extends Record<string, any>>(formRef
|
|
|
202
202
|
* const control = useControl(options);
|
|
203
203
|
* ```
|
|
204
204
|
*/
|
|
205
|
-
export declare function useControl(options
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
* when the input is first registered.
|
|
209
|
-
*/
|
|
210
|
-
defaultValue?: string | string[] | File | File[] | null | undefined;
|
|
211
|
-
/**
|
|
212
|
-
* Whether the base input should be checked by default. It will be applied
|
|
213
|
-
* when the input is first registered.
|
|
214
|
-
*/
|
|
215
|
-
defaultChecked?: boolean | undefined;
|
|
216
|
-
/**
|
|
217
|
-
* The value of a checkbox or radio input when checked. This sets the
|
|
218
|
-
* value attribute of the base input.
|
|
219
|
-
*/
|
|
220
|
-
value?: string;
|
|
221
|
-
/**
|
|
222
|
-
* A callback function that is triggered when the base input is focused.
|
|
223
|
-
* Use this to delegate focus to a custom input.
|
|
224
|
-
*/
|
|
225
|
-
onFocus?: () => void;
|
|
226
|
-
}): Control;
|
|
205
|
+
export declare function useControl<Value, DefaultValue>(options: CustomControlOptions<Value, DefaultValue>): Control<Value, DefaultValue, Value>;
|
|
206
|
+
export declare function useControl<Value extends DefaultControlValue>(options?: StandardControlOptions<Value>): Control<Value>;
|
|
207
|
+
export declare function useControl(options: CheckedControlOptions): Control<boolean, string>;
|
|
227
208
|
/**
|
|
228
209
|
* A React hook that lets you subscribe to the current `FormData` of a form and derive a custom value from it.
|
|
229
210
|
* The selector runs whenever the form's structure or data changes, and the hook re-renders only when the result is deeply different.
|
|
@@ -268,4 +249,37 @@ export declare const useSafeLayoutEffect: typeof useEffect;
|
|
|
268
249
|
* Useful to avoid stale closures in event handlers or async callbacks.
|
|
269
250
|
*/
|
|
270
251
|
export declare function useLatest<Value>(value: Value): import("react").MutableRefObject<Value>;
|
|
252
|
+
/**
|
|
253
|
+
* A component that renders hidden base control(s) based on the shape of defaultValue.
|
|
254
|
+
* Used with useControl to sync complex values with form data.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```tsx
|
|
258
|
+
* const control = useControl<{ street: string; city: string }>({
|
|
259
|
+
* defaultValue: { street: '123 Main St', city: 'Anytown' },
|
|
260
|
+
* parse(payload) {
|
|
261
|
+
* if (
|
|
262
|
+
* typeof payload === 'object' &&
|
|
263
|
+
* payload !== null &&
|
|
264
|
+
* 'street' in payload &&
|
|
265
|
+
* 'city' in payload &&
|
|
266
|
+
* typeof payload.street === 'string' &&
|
|
267
|
+
* typeof payload.city === 'string'
|
|
268
|
+
* ) {
|
|
269
|
+
* return payload;
|
|
270
|
+
* }
|
|
271
|
+
*
|
|
272
|
+
* throw new Error('Unexpected payload shape');
|
|
273
|
+
* },
|
|
274
|
+
* });
|
|
275
|
+
*
|
|
276
|
+
* <BaseControl
|
|
277
|
+
* type="fieldset"
|
|
278
|
+
* name="address"
|
|
279
|
+
* ref={control.register}
|
|
280
|
+
* defaultValue={control.defaultValue}
|
|
281
|
+
* />
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
export declare const BaseControl: import("react").ForwardRefExoticComponent<BaseControlProps & import("react").RefAttributes<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement>>;
|
|
271
285
|
//# sourceMappingURL=hooks.d.ts.map
|
package/dist/future/hooks.js
CHANGED
|
@@ -10,11 +10,15 @@ var util = require('./util.js');
|
|
|
10
10
|
var state = require('./state.js');
|
|
11
11
|
var intent = require('./intent.js');
|
|
12
12
|
var dom = require('./dom.js');
|
|
13
|
+
var reactDom = require('react-dom');
|
|
13
14
|
var jsxRuntime = require('react/jsx-runtime');
|
|
14
15
|
|
|
15
|
-
var _excluded = ["children"]
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
var _excluded = ["children"],
|
|
17
|
+
_excluded2 = ["name", "form", "defaultValue", "hidden"],
|
|
18
|
+
_excluded3 = ["defaultValue", "multiple", "hidden"],
|
|
19
|
+
_excluded4 = ["defaultValue", "hidden"],
|
|
20
|
+
_excluded5 = ["defaultValue", "value", "hidden"],
|
|
21
|
+
_excluded6 = ["defaultValue", "hidden"];
|
|
18
22
|
var INITIAL_KEY = 'INITIAL_KEY';
|
|
19
23
|
var GlobalFormOptionsContext = /*#__PURE__*/react.createContext({
|
|
20
24
|
intentName: future.DEFAULT_INTENT_NAME,
|
|
@@ -150,6 +154,10 @@ function useConform(formRef, options) {
|
|
|
150
154
|
var finalResult = intent.applyIntent(result, intent$1, {
|
|
151
155
|
handlers: intent.intentHandlers
|
|
152
156
|
});
|
|
157
|
+
var formElement = dom.getFormElement(formRef);
|
|
158
|
+
if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) {
|
|
159
|
+
future.dispatchInternalUpdateEvent(formElement);
|
|
160
|
+
}
|
|
153
161
|
setState(state$1 => state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, finalResult), {}, {
|
|
154
162
|
type,
|
|
155
163
|
intent: intent$1,
|
|
@@ -165,7 +173,6 @@ function useConform(formRef, options) {
|
|
|
165
173
|
})));
|
|
166
174
|
|
|
167
175
|
// TODO: move on error handler to a new effect
|
|
168
|
-
var formElement = dom.getFormElement(formRef);
|
|
169
176
|
if (formElement && result.error) {
|
|
170
177
|
var _optionsRef$current$o, _optionsRef$current;
|
|
171
178
|
(_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
|
|
@@ -178,6 +185,10 @@ function useConform(formRef, options) {
|
|
|
178
185
|
}, [formRef, optionsRef]);
|
|
179
186
|
if (options.key !== keyRef.current) {
|
|
180
187
|
keyRef.current = options.key;
|
|
188
|
+
var formElement = dom.getFormElement(formRef);
|
|
189
|
+
if (formElement) {
|
|
190
|
+
future.dispatchInternalUpdateEvent(formElement);
|
|
191
|
+
}
|
|
181
192
|
setState(state.initializeState({
|
|
182
193
|
defaultValue: options.defaultValue
|
|
183
194
|
}));
|
|
@@ -204,52 +215,56 @@ function useConform(formRef, options) {
|
|
|
204
215
|
}, [formRef, state$1.resetKey, state$1.defaultValue, optionsRef]);
|
|
205
216
|
useSafeLayoutEffect(() => {
|
|
206
217
|
if (state$1.targetValue) {
|
|
207
|
-
var
|
|
208
|
-
if (!
|
|
218
|
+
var _formElement = dom.getFormElement(formRef);
|
|
219
|
+
if (!_formElement) {
|
|
209
220
|
// eslint-disable-next-line no-console
|
|
210
221
|
console.error('Failed to update form value; No form element found');
|
|
211
222
|
return;
|
|
212
223
|
}
|
|
213
|
-
dom.updateFormValue(
|
|
224
|
+
dom.updateFormValue(_formElement, state$1.targetValue, optionsRef.current.serialize);
|
|
214
225
|
}
|
|
215
226
|
pendingValueRef.current = undefined;
|
|
216
227
|
}, [formRef, state$1.targetValue, optionsRef]);
|
|
217
228
|
var handleSubmit = react.useCallback(event => {
|
|
218
|
-
var _abortControllerRef$c2
|
|
229
|
+
var _abortControllerRef$c2;
|
|
219
230
|
var abortController = new AbortController();
|
|
220
231
|
|
|
221
232
|
// Keep track of the abort controller so we can cancel the previous request if a new one is made
|
|
222
233
|
(_abortControllerRef$c2 = abortControllerRef.current) === null || _abortControllerRef$c2 === void 0 || _abortControllerRef$c2.abort('A new submission is made');
|
|
223
234
|
abortControllerRef.current = abortController;
|
|
224
|
-
var formData;
|
|
225
235
|
var result;
|
|
226
236
|
var resolvedValue;
|
|
237
|
+
var formElement = event.currentTarget;
|
|
238
|
+
var submitEvent = dom.getSubmitEvent(event);
|
|
239
|
+
var formData = future.getFormData(formElement, submitEvent.submitter);
|
|
240
|
+
var submission = future.parseSubmission(formData, {
|
|
241
|
+
intentName: optionsRef.current.intentName
|
|
242
|
+
});
|
|
227
243
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
resolvedValue = lastAsyncResultRef.current.resolvedValue;
|
|
233
|
-
} else {
|
|
234
|
-
var _optionsRef$current$o2, _optionsRef$current2;
|
|
235
|
-
var formElement = event.currentTarget;
|
|
236
|
-
var submitEvent = dom.getSubmitEvent(event);
|
|
237
|
-
formData = future.getFormData(formElement, submitEvent.submitter);
|
|
238
|
-
var submission = future.parseSubmission(formData, {
|
|
239
|
-
intentName: optionsRef.current.intentName
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// Patch missing fields in the submission object
|
|
243
|
-
for (var element of formElement.elements) {
|
|
244
|
-
if (future.isFieldElement(element) && element.name) {
|
|
245
|
-
submission.fields = util.appendUniqueItem(submission.fields, element.name);
|
|
246
|
-
}
|
|
244
|
+
// Patch missing fields in the submission object
|
|
245
|
+
for (var element of formElement.elements) {
|
|
246
|
+
if (future.isFieldElement(element) && element.name) {
|
|
247
|
+
submission.fields = util.appendUniqueItem(submission.fields, element.name);
|
|
247
248
|
}
|
|
249
|
+
}
|
|
248
250
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
// Override submission value if the pending value is not applied yet (i.e. batch updates)
|
|
252
|
+
if (pendingValueRef.current !== undefined) {
|
|
253
|
+
submission.payload = pendingValueRef.current;
|
|
254
|
+
}
|
|
255
|
+
var lastAsyncResult = lastAsyncResultRef.current;
|
|
256
|
+
|
|
257
|
+
// Clear the last async result so it won't affect the next submission
|
|
258
|
+
lastAsyncResultRef.current = null;
|
|
259
|
+
if (lastAsyncResult &&
|
|
260
|
+
// Only default submission will be re-submitted after async validation
|
|
261
|
+
!submission.intent &&
|
|
262
|
+
// Ensure the submission payload is the same as the one being validated
|
|
263
|
+
future.deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) {
|
|
264
|
+
result = lastAsyncResult.result;
|
|
265
|
+
resolvedValue = lastAsyncResult.resolvedValue;
|
|
266
|
+
} else {
|
|
267
|
+
var _optionsRef$current$o2, _optionsRef$current2;
|
|
253
268
|
var value = intent.resolveIntent(submission);
|
|
254
269
|
var submissionResult = future.report(submission, {
|
|
255
270
|
keepFiles: true,
|
|
@@ -294,16 +309,20 @@ function useConform(formRef, options) {
|
|
|
294
309
|
|
|
295
310
|
// If the form is meant to be submitted and there is no error
|
|
296
311
|
if (submissionResult.error === null && !submission.intent) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
312
|
+
// Keep track of the validated payload and resume submission on the next task.
|
|
313
|
+
// Calling requestSubmit() directly from the async callback, or from a
|
|
314
|
+
// microtask, can still be ignored before the native submission lifecycle
|
|
315
|
+
// has fully settled.
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
if (abortController.signal.aborted) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
lastAsyncResultRef.current = {
|
|
321
|
+
resolvedValue: value,
|
|
322
|
+
result: submissionResult
|
|
323
|
+
};
|
|
324
|
+
future.requestSubmit(formElement, submitEvent.submitter);
|
|
325
|
+
}, 0);
|
|
307
326
|
}
|
|
308
327
|
}
|
|
309
328
|
});
|
|
@@ -618,7 +637,7 @@ function useIntent(formRef) {
|
|
|
618
637
|
|
|
619
638
|
/**
|
|
620
639
|
* A React hook that lets you sync the state of an input and dispatch native form events from it.
|
|
621
|
-
* This is useful when emulating native input behavior — typically by rendering a hidden base
|
|
640
|
+
* This is useful when emulating native input behavior — typically by rendering a hidden base control
|
|
622
641
|
* and syncing it with a custom input.
|
|
623
642
|
*
|
|
624
643
|
* @example
|
|
@@ -626,7 +645,9 @@ function useIntent(formRef) {
|
|
|
626
645
|
* const control = useControl(options);
|
|
627
646
|
* ```
|
|
628
647
|
*/
|
|
629
|
-
|
|
648
|
+
|
|
649
|
+
function useControl() {
|
|
650
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
630
651
|
var {
|
|
631
652
|
observer
|
|
632
653
|
} = react.useContext(GlobalFormOptionsContext);
|
|
@@ -641,29 +662,40 @@ function useControl(options) {
|
|
|
641
662
|
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;
|
|
642
663
|
}
|
|
643
664
|
}), []);
|
|
665
|
+
var [defaultValue, setDefaultValue] = react.useState(() => dom.deriveDefaultPayload(options));
|
|
666
|
+
var pendingDefaultValueSyncRef = react.useRef(false);
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Keep defaultValue in sync with external option updates during render.
|
|
670
|
+
* This is required for structural controls where hidden descendants must be
|
|
671
|
+
* rendered in the same cycle as form state updates (e.g. update intents).
|
|
672
|
+
*/
|
|
673
|
+
if (pendingDefaultValueSyncRef.current && inputRef.current && future.isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) {
|
|
674
|
+
pendingDefaultValueSyncRef.current = false;
|
|
675
|
+
setDefaultValue(() => dom.deriveDefaultPayload(options));
|
|
676
|
+
}
|
|
644
677
|
var eventDispatched = react.useRef({});
|
|
645
|
-
var
|
|
646
|
-
var snapshotRef = react.useRef(defaultSnapshot);
|
|
678
|
+
var snapshotRef = react.useRef(defaultValue);
|
|
647
679
|
var optionsRef = react.useRef(options);
|
|
648
680
|
react.useEffect(() => {
|
|
649
681
|
optionsRef.current = options;
|
|
650
682
|
});
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
683
|
+
react.useEffect(() => observer.onInternalUpdate(event => {
|
|
684
|
+
var input = inputRef.current;
|
|
685
|
+
if (input && input instanceof HTMLFieldSetElement && event.target === input.form) {
|
|
686
|
+
pendingDefaultValueSyncRef.current = true;
|
|
687
|
+
}
|
|
688
|
+
}), [observer]);
|
|
689
|
+
var payloadSnapshot = react.useSyncExternalStore(react.useCallback(callback => observer.onFieldUpdate(event => {
|
|
690
|
+
var _inputRef$current;
|
|
656
691
|
var input = event.target;
|
|
657
|
-
if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) {
|
|
692
|
+
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)) {
|
|
658
693
|
callback();
|
|
659
694
|
}
|
|
660
695
|
}), [observer]), () => {
|
|
661
696
|
var input = inputRef.current;
|
|
662
697
|
var prev = snapshotRef.current;
|
|
663
|
-
var next =
|
|
664
|
-
value: dom.getRadioGroupValue(input),
|
|
665
|
-
options: dom.getCheckboxGroupValue(input)
|
|
666
|
-
} : dom.getInputSnapshot(input);
|
|
698
|
+
var next = input ? dom.resolveControlPayload(input) : defaultValue;
|
|
667
699
|
if (future.deepEqual(prev, next)) {
|
|
668
700
|
return prev;
|
|
669
701
|
}
|
|
@@ -673,7 +705,8 @@ function useControl(options) {
|
|
|
673
705
|
react.useEffect(() => {
|
|
674
706
|
var createEventListener = listener => {
|
|
675
707
|
return event => {
|
|
676
|
-
|
|
708
|
+
var _inputRef$current2;
|
|
709
|
+
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))) {
|
|
677
710
|
var timer = eventDispatched.current[listener];
|
|
678
711
|
if (timer) {
|
|
679
712
|
clearTimeout(timer);
|
|
@@ -701,10 +734,62 @@ function useControl(options) {
|
|
|
701
734
|
};
|
|
702
735
|
}, []);
|
|
703
736
|
return {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
737
|
+
defaultValue,
|
|
738
|
+
get payload() {
|
|
739
|
+
if (payloadSnapshot != null && 'parse' in options) {
|
|
740
|
+
try {
|
|
741
|
+
return options.parse(payloadSnapshot);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
var payloadText = '';
|
|
744
|
+
try {
|
|
745
|
+
payloadText = JSON.stringify(payloadSnapshot, null, 2);
|
|
746
|
+
} catch (_unused) {
|
|
747
|
+
payloadText = '<unserializable payload>';
|
|
748
|
+
}
|
|
749
|
+
throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), {
|
|
750
|
+
cause: error
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return payloadSnapshot;
|
|
755
|
+
},
|
|
756
|
+
get value() {
|
|
757
|
+
if (payloadSnapshot === null) {
|
|
758
|
+
return '';
|
|
759
|
+
}
|
|
760
|
+
if (typeof payloadSnapshot === 'string') {
|
|
761
|
+
return payloadSnapshot;
|
|
762
|
+
}
|
|
763
|
+
return undefined;
|
|
764
|
+
},
|
|
765
|
+
get checked() {
|
|
766
|
+
if (payloadSnapshot === null) {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
var value = 'value' in options && options.value ? options.value : 'on';
|
|
770
|
+
if (payloadSnapshot === value) {
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
return undefined;
|
|
774
|
+
},
|
|
775
|
+
get options() {
|
|
776
|
+
if (payloadSnapshot === null) {
|
|
777
|
+
return [];
|
|
778
|
+
}
|
|
779
|
+
if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) {
|
|
780
|
+
return payloadSnapshot;
|
|
781
|
+
}
|
|
782
|
+
return undefined;
|
|
783
|
+
},
|
|
784
|
+
get files() {
|
|
785
|
+
if (payloadSnapshot === null) {
|
|
786
|
+
return [];
|
|
787
|
+
}
|
|
788
|
+
if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => future.isGlobalInstance(item, 'File'))) {
|
|
789
|
+
return payloadSnapshot;
|
|
790
|
+
}
|
|
791
|
+
return undefined;
|
|
792
|
+
},
|
|
708
793
|
formRef,
|
|
709
794
|
register: react.useCallback(element => {
|
|
710
795
|
if (!element) {
|
|
@@ -718,17 +803,16 @@ function useControl(options) {
|
|
|
718
803
|
element.hidden = true;
|
|
719
804
|
element.removeAttribute('type');
|
|
720
805
|
}
|
|
721
|
-
if (shouldHandleFocus) {
|
|
722
|
-
dom.makeInputFocusable(element);
|
|
723
|
-
}
|
|
724
806
|
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
725
|
-
var _optionsRef$current$v, _optionsRef$current7;
|
|
726
807
|
// React set the value as empty string incorrectly when the value is undefined
|
|
727
808
|
// This make sure the checkbox value falls back to the default value "on" properly
|
|
728
809
|
// @see https://github.com/facebook/react/issues/17590
|
|
729
|
-
|
|
810
|
+
var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on';
|
|
811
|
+
element.value = value;
|
|
730
812
|
}
|
|
731
813
|
dom.initializeField(element, optionsRef.current);
|
|
814
|
+
} else if (element instanceof HTMLFieldSetElement) {
|
|
815
|
+
inputRef.current = element;
|
|
732
816
|
} else {
|
|
733
817
|
var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2;
|
|
734
818
|
var inputs = Array.from(element);
|
|
@@ -738,39 +822,36 @@ function useControl(options) {
|
|
|
738
822
|
throw new Error('You can only register a checkbox or radio group with the same name');
|
|
739
823
|
}
|
|
740
824
|
inputRef.current = inputs;
|
|
741
|
-
|
|
742
|
-
var
|
|
743
|
-
|
|
744
|
-
dom.
|
|
825
|
+
if ('defaultValue' in optionsRef.current) {
|
|
826
|
+
for (var input of inputs) {
|
|
827
|
+
var _optionsRef$current7;
|
|
828
|
+
dom.initializeField(input, {
|
|
829
|
+
// We will not be uitlizing defaultChecked / value on checkbox / radio group
|
|
830
|
+
defaultValue: (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.defaultValue
|
|
831
|
+
});
|
|
745
832
|
}
|
|
746
|
-
dom.initializeField(input, {
|
|
747
|
-
// We will not be uitlizing defaultChecked / value on checkbox / radio group
|
|
748
|
-
defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue
|
|
749
|
-
});
|
|
750
833
|
}
|
|
751
834
|
}
|
|
752
|
-
}, [
|
|
835
|
+
}, []),
|
|
753
836
|
change: react.useCallback(value => {
|
|
754
837
|
if (!eventDispatched.current.change) {
|
|
755
|
-
var
|
|
756
|
-
var
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
// So we will pick the first element that should be checked
|
|
767
|
-
return isChecked;
|
|
768
|
-
default:
|
|
769
|
-
return false;
|
|
770
|
-
}
|
|
771
|
-
}) : inputRef.current;
|
|
838
|
+
var element = inputRef.current;
|
|
839
|
+
var isFieldset = element instanceof HTMLFieldSetElement;
|
|
840
|
+
var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value;
|
|
841
|
+
if (isFieldset) {
|
|
842
|
+
// Fieldset mode renders hidden descendant inputs from defaultValue.
|
|
843
|
+
// Flush this update before dispatching events so listeners see the
|
|
844
|
+
// latest form structure in the same input/change cycle.
|
|
845
|
+
reactDom.flushSync(() => {
|
|
846
|
+
setDefaultValue(serializedValue);
|
|
847
|
+
});
|
|
848
|
+
}
|
|
772
849
|
if (element) {
|
|
773
|
-
future.change(element,
|
|
850
|
+
future.change(element, serializedValue, {
|
|
851
|
+
// Sometimes no change is made on the inputs but done through DOM mutation.
|
|
852
|
+
// But we still want to dispatch the event to notify listeners.
|
|
853
|
+
forceDispatch: isFieldset
|
|
854
|
+
});
|
|
774
855
|
}
|
|
775
856
|
}
|
|
776
857
|
if (eventDispatched.current.change) {
|
|
@@ -881,6 +962,149 @@ function useLatest(value) {
|
|
|
881
962
|
return ref;
|
|
882
963
|
}
|
|
883
964
|
|
|
965
|
+
/**
|
|
966
|
+
* A component that renders hidden base control(s) based on the shape of defaultValue.
|
|
967
|
+
* Used with useControl to sync complex values with form data.
|
|
968
|
+
*
|
|
969
|
+
* @example
|
|
970
|
+
* ```tsx
|
|
971
|
+
* const control = useControl<{ street: string; city: string }>({
|
|
972
|
+
* defaultValue: { street: '123 Main St', city: 'Anytown' },
|
|
973
|
+
* parse(payload) {
|
|
974
|
+
* if (
|
|
975
|
+
* typeof payload === 'object' &&
|
|
976
|
+
* payload !== null &&
|
|
977
|
+
* 'street' in payload &&
|
|
978
|
+
* 'city' in payload &&
|
|
979
|
+
* typeof payload.street === 'string' &&
|
|
980
|
+
* typeof payload.city === 'string'
|
|
981
|
+
* ) {
|
|
982
|
+
* return payload;
|
|
983
|
+
* }
|
|
984
|
+
*
|
|
985
|
+
* throw new Error('Unexpected payload shape');
|
|
986
|
+
* },
|
|
987
|
+
* });
|
|
988
|
+
*
|
|
989
|
+
* <BaseControl
|
|
990
|
+
* type="fieldset"
|
|
991
|
+
* name="address"
|
|
992
|
+
* ref={control.register}
|
|
993
|
+
* defaultValue={control.defaultValue}
|
|
994
|
+
* />
|
|
995
|
+
* ```
|
|
996
|
+
*/
|
|
997
|
+
var BaseControl = /*#__PURE__*/react.forwardRef(function BaseControl(props, ref) {
|
|
998
|
+
function formatValue(value) {
|
|
999
|
+
var serialized = future.serialize(value);
|
|
1000
|
+
if (typeof serialized === 'string') {
|
|
1001
|
+
return serialized;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// null, undefined, File, or array - fallback to empty string
|
|
1005
|
+
return '';
|
|
1006
|
+
}
|
|
1007
|
+
function renderInput(name, value, form) {
|
|
1008
|
+
if (Array.isArray(value)) {
|
|
1009
|
+
return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form));
|
|
1010
|
+
}
|
|
1011
|
+
if (future.isPlainObject(value)) {
|
|
1012
|
+
return Object.entries(value).map(_ref5 => {
|
|
1013
|
+
var [key, val] = _ref5;
|
|
1014
|
+
return renderInput("".concat(name, ".").concat(key), val, form);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
1018
|
+
name: name,
|
|
1019
|
+
defaultValue: formatValue(value),
|
|
1020
|
+
form: form
|
|
1021
|
+
}, name);
|
|
1022
|
+
}
|
|
1023
|
+
if (props.type === 'fieldset') {
|
|
1024
|
+
var {
|
|
1025
|
+
name,
|
|
1026
|
+
form,
|
|
1027
|
+
defaultValue: _defaultValue,
|
|
1028
|
+
hidden: _hidden = true
|
|
1029
|
+
} = props,
|
|
1030
|
+
fieldsetProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded2);
|
|
1031
|
+
return /*#__PURE__*/jsxRuntime.jsx("fieldset", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, fieldsetProps), {}, {
|
|
1032
|
+
ref: ref,
|
|
1033
|
+
name: name,
|
|
1034
|
+
form: form,
|
|
1035
|
+
hidden: _hidden,
|
|
1036
|
+
children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null
|
|
1037
|
+
}));
|
|
1038
|
+
}
|
|
1039
|
+
if (props.type === 'select') {
|
|
1040
|
+
var {
|
|
1041
|
+
defaultValue: _defaultValue2,
|
|
1042
|
+
multiple = Array.isArray(_defaultValue2),
|
|
1043
|
+
hidden: _hidden2 = true
|
|
1044
|
+
} = props,
|
|
1045
|
+
selectProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded3);
|
|
1046
|
+
if (multiple) {
|
|
1047
|
+
var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)];
|
|
1048
|
+
return /*#__PURE__*/jsxRuntime.jsx("select", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, selectProps), {}, {
|
|
1049
|
+
ref: ref,
|
|
1050
|
+
defaultValue: defaultOptions,
|
|
1051
|
+
hidden: _hidden2,
|
|
1052
|
+
multiple: true,
|
|
1053
|
+
children: defaultOptions.map((option, index) => /*#__PURE__*/jsxRuntime.jsx("option", {
|
|
1054
|
+
value: option,
|
|
1055
|
+
children: option
|
|
1056
|
+
}, index))
|
|
1057
|
+
}));
|
|
1058
|
+
}
|
|
1059
|
+
var defaultOption = formatValue(_defaultValue2);
|
|
1060
|
+
return /*#__PURE__*/jsxRuntime.jsx("select", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, selectProps), {}, {
|
|
1061
|
+
ref: ref,
|
|
1062
|
+
defaultValue: defaultOption,
|
|
1063
|
+
hidden: _hidden2,
|
|
1064
|
+
children: [defaultOption].map((option, index) => /*#__PURE__*/jsxRuntime.jsx("option", {
|
|
1065
|
+
value: option,
|
|
1066
|
+
children: option
|
|
1067
|
+
}, index))
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
1070
|
+
if (props.type === 'textarea') {
|
|
1071
|
+
var {
|
|
1072
|
+
defaultValue: _defaultValue3,
|
|
1073
|
+
hidden: _hidden3 = true
|
|
1074
|
+
} = props,
|
|
1075
|
+
textareaProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded4);
|
|
1076
|
+
return /*#__PURE__*/jsxRuntime.jsx("textarea", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, textareaProps), {}, {
|
|
1077
|
+
defaultValue: formatValue(_defaultValue3),
|
|
1078
|
+
ref: ref,
|
|
1079
|
+
hidden: _hidden3
|
|
1080
|
+
}));
|
|
1081
|
+
}
|
|
1082
|
+
if (props.type === 'checkbox' || props.type === 'radio') {
|
|
1083
|
+
var {
|
|
1084
|
+
defaultValue: _defaultValue4 = 'on',
|
|
1085
|
+
value = _defaultValue4,
|
|
1086
|
+
hidden: _hidden4 = true
|
|
1087
|
+
} = props,
|
|
1088
|
+
_inputProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded5);
|
|
1089
|
+
return /*#__PURE__*/jsxRuntime.jsx("input", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, _inputProps), {}, {
|
|
1090
|
+
ref: ref,
|
|
1091
|
+
value: value,
|
|
1092
|
+
hidden: _hidden4
|
|
1093
|
+
}));
|
|
1094
|
+
}
|
|
1095
|
+
var {
|
|
1096
|
+
defaultValue,
|
|
1097
|
+
hidden = true
|
|
1098
|
+
} = props,
|
|
1099
|
+
inputProps = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded6);
|
|
1100
|
+
return /*#__PURE__*/jsxRuntime.jsx("input", _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, inputProps), {}, {
|
|
1101
|
+
ref: ref,
|
|
1102
|
+
defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined,
|
|
1103
|
+
hidden: hidden
|
|
1104
|
+
}));
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
exports.BaseControl = BaseControl;
|
|
884
1108
|
exports.FormContextContext = FormContextContext;
|
|
885
1109
|
exports.FormOptionsProvider = FormOptionsProvider;
|
|
886
1110
|
exports.FormProvider = FormProvider;
|