@conform-to/react 1.17.1 → 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.mjs
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { objectSpread2 as _objectSpread2, objectWithoutProperties as _objectWithoutProperties } from '../_virtual/_rollupPluginBabelHelpers.mjs';
|
|
3
|
-
import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, deepEqual, change, focus, blur, getFormData, parseSubmission, report,
|
|
4
|
-
import { createContext, useContext, useMemo, useRef, useId, useEffect, useSyncExternalStore, useCallback, useLayoutEffect,
|
|
3
|
+
import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, isGlobalInstance, deepEqual, change, focus, blur, getFormData, dispatchInternalUpdateEvent, parseSubmission, report, requestSubmit, isPlainObject } from '@conform-to/dom/future';
|
|
4
|
+
import { createContext, useContext, useMemo, useRef, useId, useState, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, forwardRef } from 'react';
|
|
5
5
|
import { resolveStandardSchemaResult, resolveValidateResult, appendUniqueItem } from './util.mjs';
|
|
6
6
|
import { isTouched, getFormMetadata, getFieldset, getField, initializeState, updateState } from './state.mjs';
|
|
7
7
|
import { deserializeIntent, applyIntent, intentHandlers, resolveIntent } from './intent.mjs';
|
|
8
|
-
import { cleanupPreservedInputs, preserveInputs, focusFirstInvalidField, getFormElement, createIntentDispatcher,
|
|
8
|
+
import { cleanupPreservedInputs, preserveInputs, focusFirstInvalidField, getFormElement, createIntentDispatcher, deriveDefaultPayload, resolveControlPayload, initializeField, 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 = ["children"]
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
var _excluded = ["children"],
|
|
13
|
+
_excluded2 = ["name", "form", "defaultValue", "hidden"],
|
|
14
|
+
_excluded3 = ["defaultValue", "multiple", "hidden"],
|
|
15
|
+
_excluded4 = ["defaultValue", "hidden"],
|
|
16
|
+
_excluded5 = ["defaultValue", "value", "hidden"],
|
|
17
|
+
_excluded6 = ["defaultValue", "hidden"];
|
|
14
18
|
var INITIAL_KEY = 'INITIAL_KEY';
|
|
15
19
|
var GlobalFormOptionsContext = /*#__PURE__*/createContext({
|
|
16
20
|
intentName: DEFAULT_INTENT_NAME,
|
|
@@ -146,6 +150,10 @@ function useConform(formRef, options) {
|
|
|
146
150
|
var finalResult = applyIntent(result, intent, {
|
|
147
151
|
handlers: intentHandlers
|
|
148
152
|
});
|
|
153
|
+
var formElement = getFormElement(formRef);
|
|
154
|
+
if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) {
|
|
155
|
+
dispatchInternalUpdateEvent(formElement);
|
|
156
|
+
}
|
|
149
157
|
setState(state => updateState(state, _objectSpread2(_objectSpread2({}, finalResult), {}, {
|
|
150
158
|
type,
|
|
151
159
|
intent,
|
|
@@ -161,7 +169,6 @@ function useConform(formRef, options) {
|
|
|
161
169
|
})));
|
|
162
170
|
|
|
163
171
|
// TODO: move on error handler to a new effect
|
|
164
|
-
var formElement = getFormElement(formRef);
|
|
165
172
|
if (formElement && result.error) {
|
|
166
173
|
var _optionsRef$current$o, _optionsRef$current;
|
|
167
174
|
(_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
|
|
@@ -174,6 +181,10 @@ function useConform(formRef, options) {
|
|
|
174
181
|
}, [formRef, optionsRef]);
|
|
175
182
|
if (options.key !== keyRef.current) {
|
|
176
183
|
keyRef.current = options.key;
|
|
184
|
+
var formElement = getFormElement(formRef);
|
|
185
|
+
if (formElement) {
|
|
186
|
+
dispatchInternalUpdateEvent(formElement);
|
|
187
|
+
}
|
|
177
188
|
setState(initializeState({
|
|
178
189
|
defaultValue: options.defaultValue
|
|
179
190
|
}));
|
|
@@ -200,52 +211,56 @@ function useConform(formRef, options) {
|
|
|
200
211
|
}, [formRef, state.resetKey, state.defaultValue, optionsRef]);
|
|
201
212
|
useSafeLayoutEffect(() => {
|
|
202
213
|
if (state.targetValue) {
|
|
203
|
-
var
|
|
204
|
-
if (!
|
|
214
|
+
var _formElement = getFormElement(formRef);
|
|
215
|
+
if (!_formElement) {
|
|
205
216
|
// eslint-disable-next-line no-console
|
|
206
217
|
console.error('Failed to update form value; No form element found');
|
|
207
218
|
return;
|
|
208
219
|
}
|
|
209
|
-
updateFormValue(
|
|
220
|
+
updateFormValue(_formElement, state.targetValue, optionsRef.current.serialize);
|
|
210
221
|
}
|
|
211
222
|
pendingValueRef.current = undefined;
|
|
212
223
|
}, [formRef, state.targetValue, optionsRef]);
|
|
213
224
|
var handleSubmit = useCallback(event => {
|
|
214
|
-
var _abortControllerRef$c2
|
|
225
|
+
var _abortControllerRef$c2;
|
|
215
226
|
var abortController = new AbortController();
|
|
216
227
|
|
|
217
228
|
// Keep track of the abort controller so we can cancel the previous request if a new one is made
|
|
218
229
|
(_abortControllerRef$c2 = abortControllerRef.current) === null || _abortControllerRef$c2 === void 0 || _abortControllerRef$c2.abort('A new submission is made');
|
|
219
230
|
abortControllerRef.current = abortController;
|
|
220
|
-
var formData;
|
|
221
231
|
var result;
|
|
222
232
|
var resolvedValue;
|
|
233
|
+
var formElement = event.currentTarget;
|
|
234
|
+
var submitEvent = getSubmitEvent(event);
|
|
235
|
+
var formData = getFormData(formElement, submitEvent.submitter);
|
|
236
|
+
var submission = parseSubmission(formData, {
|
|
237
|
+
intentName: optionsRef.current.intentName
|
|
238
|
+
});
|
|
223
239
|
|
|
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
|
-
}
|
|
240
|
+
// Patch missing fields in the submission object
|
|
241
|
+
for (var element of formElement.elements) {
|
|
242
|
+
if (isFieldElement(element) && element.name) {
|
|
243
|
+
submission.fields = appendUniqueItem(submission.fields, element.name);
|
|
243
244
|
}
|
|
245
|
+
}
|
|
244
246
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
// Override submission value if the pending value is not applied yet (i.e. batch updates)
|
|
248
|
+
if (pendingValueRef.current !== undefined) {
|
|
249
|
+
submission.payload = pendingValueRef.current;
|
|
250
|
+
}
|
|
251
|
+
var lastAsyncResult = lastAsyncResultRef.current;
|
|
252
|
+
|
|
253
|
+
// Clear the last async result so it won't affect the next submission
|
|
254
|
+
lastAsyncResultRef.current = null;
|
|
255
|
+
if (lastAsyncResult &&
|
|
256
|
+
// Only default submission will be re-submitted after async validation
|
|
257
|
+
!submission.intent &&
|
|
258
|
+
// Ensure the submission payload is the same as the one being validated
|
|
259
|
+
deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) {
|
|
260
|
+
result = lastAsyncResult.result;
|
|
261
|
+
resolvedValue = lastAsyncResult.resolvedValue;
|
|
262
|
+
} else {
|
|
263
|
+
var _optionsRef$current$o2, _optionsRef$current2;
|
|
249
264
|
var value = resolveIntent(submission);
|
|
250
265
|
var submissionResult = report(submission, {
|
|
251
266
|
keepFiles: true,
|
|
@@ -290,16 +305,20 @@ function useConform(formRef, options) {
|
|
|
290
305
|
|
|
291
306
|
// If the form is meant to be submitted and there is no error
|
|
292
307
|
if (submissionResult.error === null && !submission.intent) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
308
|
+
// Keep track of the validated payload and resume submission on the next task.
|
|
309
|
+
// Calling requestSubmit() directly from the async callback, or from a
|
|
310
|
+
// microtask, can still be ignored before the native submission lifecycle
|
|
311
|
+
// has fully settled.
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
if (abortController.signal.aborted) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
lastAsyncResultRef.current = {
|
|
317
|
+
resolvedValue: value,
|
|
318
|
+
result: submissionResult
|
|
319
|
+
};
|
|
320
|
+
requestSubmit(formElement, submitEvent.submitter);
|
|
321
|
+
}, 0);
|
|
303
322
|
}
|
|
304
323
|
}
|
|
305
324
|
});
|
|
@@ -614,7 +633,7 @@ function useIntent(formRef) {
|
|
|
614
633
|
|
|
615
634
|
/**
|
|
616
635
|
* 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
|
|
636
|
+
* This is useful when emulating native input behavior — typically by rendering a hidden base control
|
|
618
637
|
* and syncing it with a custom input.
|
|
619
638
|
*
|
|
620
639
|
* @example
|
|
@@ -622,7 +641,9 @@ function useIntent(formRef) {
|
|
|
622
641
|
* const control = useControl(options);
|
|
623
642
|
* ```
|
|
624
643
|
*/
|
|
625
|
-
|
|
644
|
+
|
|
645
|
+
function useControl() {
|
|
646
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
626
647
|
var {
|
|
627
648
|
observer
|
|
628
649
|
} = useContext(GlobalFormOptionsContext);
|
|
@@ -637,29 +658,40 @@ function useControl(options) {
|
|
|
637
658
|
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
659
|
}
|
|
639
660
|
}), []);
|
|
661
|
+
var [defaultValue, setDefaultValue] = useState(() => deriveDefaultPayload(options));
|
|
662
|
+
var pendingDefaultValueSyncRef = useRef(false);
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Keep defaultValue in sync with external option updates during render.
|
|
666
|
+
* This is required for structural controls where hidden descendants must be
|
|
667
|
+
* rendered in the same cycle as form state updates (e.g. update intents).
|
|
668
|
+
*/
|
|
669
|
+
if (pendingDefaultValueSyncRef.current && inputRef.current && isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) {
|
|
670
|
+
pendingDefaultValueSyncRef.current = false;
|
|
671
|
+
setDefaultValue(() => deriveDefaultPayload(options));
|
|
672
|
+
}
|
|
640
673
|
var eventDispatched = useRef({});
|
|
641
|
-
var
|
|
642
|
-
var snapshotRef = useRef(defaultSnapshot);
|
|
674
|
+
var snapshotRef = useRef(defaultValue);
|
|
643
675
|
var optionsRef = useRef(options);
|
|
644
676
|
useEffect(() => {
|
|
645
677
|
optionsRef.current = options;
|
|
646
678
|
});
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
679
|
+
useEffect(() => observer.onInternalUpdate(event => {
|
|
680
|
+
var input = inputRef.current;
|
|
681
|
+
if (input && input instanceof HTMLFieldSetElement && event.target === input.form) {
|
|
682
|
+
pendingDefaultValueSyncRef.current = true;
|
|
683
|
+
}
|
|
684
|
+
}), [observer]);
|
|
685
|
+
var payloadSnapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => {
|
|
686
|
+
var _inputRef$current;
|
|
652
687
|
var input = event.target;
|
|
653
|
-
if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) {
|
|
688
|
+
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
689
|
callback();
|
|
655
690
|
}
|
|
656
691
|
}), [observer]), () => {
|
|
657
692
|
var input = inputRef.current;
|
|
658
693
|
var prev = snapshotRef.current;
|
|
659
|
-
var next =
|
|
660
|
-
value: getRadioGroupValue(input),
|
|
661
|
-
options: getCheckboxGroupValue(input)
|
|
662
|
-
} : getInputSnapshot(input);
|
|
694
|
+
var next = input ? resolveControlPayload(input) : defaultValue;
|
|
663
695
|
if (deepEqual(prev, next)) {
|
|
664
696
|
return prev;
|
|
665
697
|
}
|
|
@@ -669,7 +701,8 @@ function useControl(options) {
|
|
|
669
701
|
useEffect(() => {
|
|
670
702
|
var createEventListener = listener => {
|
|
671
703
|
return event => {
|
|
672
|
-
|
|
704
|
+
var _inputRef$current2;
|
|
705
|
+
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
706
|
var timer = eventDispatched.current[listener];
|
|
674
707
|
if (timer) {
|
|
675
708
|
clearTimeout(timer);
|
|
@@ -697,10 +730,62 @@ function useControl(options) {
|
|
|
697
730
|
};
|
|
698
731
|
}, []);
|
|
699
732
|
return {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
733
|
+
defaultValue,
|
|
734
|
+
get payload() {
|
|
735
|
+
if (payloadSnapshot != null && 'parse' in options) {
|
|
736
|
+
try {
|
|
737
|
+
return options.parse(payloadSnapshot);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
var payloadText = '';
|
|
740
|
+
try {
|
|
741
|
+
payloadText = JSON.stringify(payloadSnapshot, null, 2);
|
|
742
|
+
} catch (_unused) {
|
|
743
|
+
payloadText = '<unserializable payload>';
|
|
744
|
+
}
|
|
745
|
+
throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), {
|
|
746
|
+
cause: error
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return payloadSnapshot;
|
|
751
|
+
},
|
|
752
|
+
get value() {
|
|
753
|
+
if (payloadSnapshot === null) {
|
|
754
|
+
return '';
|
|
755
|
+
}
|
|
756
|
+
if (typeof payloadSnapshot === 'string') {
|
|
757
|
+
return payloadSnapshot;
|
|
758
|
+
}
|
|
759
|
+
return undefined;
|
|
760
|
+
},
|
|
761
|
+
get checked() {
|
|
762
|
+
if (payloadSnapshot === null) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
var value = 'value' in options && options.value ? options.value : 'on';
|
|
766
|
+
if (payloadSnapshot === value) {
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
return undefined;
|
|
770
|
+
},
|
|
771
|
+
get options() {
|
|
772
|
+
if (payloadSnapshot === null) {
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) {
|
|
776
|
+
return payloadSnapshot;
|
|
777
|
+
}
|
|
778
|
+
return undefined;
|
|
779
|
+
},
|
|
780
|
+
get files() {
|
|
781
|
+
if (payloadSnapshot === null) {
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => isGlobalInstance(item, 'File'))) {
|
|
785
|
+
return payloadSnapshot;
|
|
786
|
+
}
|
|
787
|
+
return undefined;
|
|
788
|
+
},
|
|
704
789
|
formRef,
|
|
705
790
|
register: useCallback(element => {
|
|
706
791
|
if (!element) {
|
|
@@ -714,17 +799,16 @@ function useControl(options) {
|
|
|
714
799
|
element.hidden = true;
|
|
715
800
|
element.removeAttribute('type');
|
|
716
801
|
}
|
|
717
|
-
if (shouldHandleFocus) {
|
|
718
|
-
makeInputFocusable(element);
|
|
719
|
-
}
|
|
720
802
|
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
721
|
-
var _optionsRef$current$v, _optionsRef$current7;
|
|
722
803
|
// React set the value as empty string incorrectly when the value is undefined
|
|
723
804
|
// This make sure the checkbox value falls back to the default value "on" properly
|
|
724
805
|
// @see https://github.com/facebook/react/issues/17590
|
|
725
|
-
|
|
806
|
+
var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on';
|
|
807
|
+
element.value = value;
|
|
726
808
|
}
|
|
727
809
|
initializeField(element, optionsRef.current);
|
|
810
|
+
} else if (element instanceof HTMLFieldSetElement) {
|
|
811
|
+
inputRef.current = element;
|
|
728
812
|
} else {
|
|
729
813
|
var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2;
|
|
730
814
|
var inputs = Array.from(element);
|
|
@@ -734,39 +818,36 @@ function useControl(options) {
|
|
|
734
818
|
throw new Error('You can only register a checkbox or radio group with the same name');
|
|
735
819
|
}
|
|
736
820
|
inputRef.current = inputs;
|
|
737
|
-
|
|
738
|
-
var
|
|
739
|
-
|
|
740
|
-
|
|
821
|
+
if ('defaultValue' in optionsRef.current) {
|
|
822
|
+
for (var input of inputs) {
|
|
823
|
+
var _optionsRef$current7;
|
|
824
|
+
initializeField(input, {
|
|
825
|
+
// We will not be uitlizing defaultChecked / value on checkbox / radio group
|
|
826
|
+
defaultValue: (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.defaultValue
|
|
827
|
+
});
|
|
741
828
|
}
|
|
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
829
|
}
|
|
747
830
|
}
|
|
748
|
-
}, [
|
|
831
|
+
}, []),
|
|
749
832
|
change: useCallback(value => {
|
|
750
833
|
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;
|
|
834
|
+
var element = inputRef.current;
|
|
835
|
+
var isFieldset = element instanceof HTMLFieldSetElement;
|
|
836
|
+
var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value;
|
|
837
|
+
if (isFieldset) {
|
|
838
|
+
// Fieldset mode renders hidden descendant inputs from defaultValue.
|
|
839
|
+
// Flush this update before dispatching events so listeners see the
|
|
840
|
+
// latest form structure in the same input/change cycle.
|
|
841
|
+
flushSync(() => {
|
|
842
|
+
setDefaultValue(serializedValue);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
768
845
|
if (element) {
|
|
769
|
-
change(element,
|
|
846
|
+
change(element, serializedValue, {
|
|
847
|
+
// Sometimes no change is made on the inputs but done through DOM mutation.
|
|
848
|
+
// But we still want to dispatch the event to notify listeners.
|
|
849
|
+
forceDispatch: isFieldset
|
|
850
|
+
});
|
|
770
851
|
}
|
|
771
852
|
}
|
|
772
853
|
if (eventDispatched.current.change) {
|
|
@@ -877,4 +958,146 @@ function useLatest(value) {
|
|
|
877
958
|
return ref;
|
|
878
959
|
}
|
|
879
960
|
|
|
880
|
-
|
|
961
|
+
/**
|
|
962
|
+
* A component that renders hidden base control(s) based on the shape of defaultValue.
|
|
963
|
+
* Used with useControl to sync complex values with form data.
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* ```tsx
|
|
967
|
+
* const control = useControl<{ street: string; city: string }>({
|
|
968
|
+
* defaultValue: { street: '123 Main St', city: 'Anytown' },
|
|
969
|
+
* parse(payload) {
|
|
970
|
+
* if (
|
|
971
|
+
* typeof payload === 'object' &&
|
|
972
|
+
* payload !== null &&
|
|
973
|
+
* 'street' in payload &&
|
|
974
|
+
* 'city' in payload &&
|
|
975
|
+
* typeof payload.street === 'string' &&
|
|
976
|
+
* typeof payload.city === 'string'
|
|
977
|
+
* ) {
|
|
978
|
+
* return payload;
|
|
979
|
+
* }
|
|
980
|
+
*
|
|
981
|
+
* throw new Error('Unexpected payload shape');
|
|
982
|
+
* },
|
|
983
|
+
* });
|
|
984
|
+
*
|
|
985
|
+
* <BaseControl
|
|
986
|
+
* type="fieldset"
|
|
987
|
+
* name="address"
|
|
988
|
+
* ref={control.register}
|
|
989
|
+
* defaultValue={control.defaultValue}
|
|
990
|
+
* />
|
|
991
|
+
* ```
|
|
992
|
+
*/
|
|
993
|
+
var BaseControl = /*#__PURE__*/forwardRef(function BaseControl(props, ref) {
|
|
994
|
+
function formatValue(value) {
|
|
995
|
+
var serialized = serialize(value);
|
|
996
|
+
if (typeof serialized === 'string') {
|
|
997
|
+
return serialized;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// null, undefined, File, or array - fallback to empty string
|
|
1001
|
+
return '';
|
|
1002
|
+
}
|
|
1003
|
+
function renderInput(name, value, form) {
|
|
1004
|
+
if (Array.isArray(value)) {
|
|
1005
|
+
return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form));
|
|
1006
|
+
}
|
|
1007
|
+
if (isPlainObject(value)) {
|
|
1008
|
+
return Object.entries(value).map(_ref5 => {
|
|
1009
|
+
var [key, val] = _ref5;
|
|
1010
|
+
return renderInput("".concat(name, ".").concat(key), val, form);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
return /*#__PURE__*/jsx("input", {
|
|
1014
|
+
name: name,
|
|
1015
|
+
defaultValue: formatValue(value),
|
|
1016
|
+
form: form
|
|
1017
|
+
}, name);
|
|
1018
|
+
}
|
|
1019
|
+
if (props.type === 'fieldset') {
|
|
1020
|
+
var {
|
|
1021
|
+
name,
|
|
1022
|
+
form,
|
|
1023
|
+
defaultValue: _defaultValue,
|
|
1024
|
+
hidden: _hidden = true
|
|
1025
|
+
} = props,
|
|
1026
|
+
fieldsetProps = _objectWithoutProperties(props, _excluded2);
|
|
1027
|
+
return /*#__PURE__*/jsx("fieldset", _objectSpread2(_objectSpread2({}, fieldsetProps), {}, {
|
|
1028
|
+
ref: ref,
|
|
1029
|
+
name: name,
|
|
1030
|
+
form: form,
|
|
1031
|
+
hidden: _hidden,
|
|
1032
|
+
children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null
|
|
1033
|
+
}));
|
|
1034
|
+
}
|
|
1035
|
+
if (props.type === 'select') {
|
|
1036
|
+
var {
|
|
1037
|
+
defaultValue: _defaultValue2,
|
|
1038
|
+
multiple = Array.isArray(_defaultValue2),
|
|
1039
|
+
hidden: _hidden2 = true
|
|
1040
|
+
} = props,
|
|
1041
|
+
selectProps = _objectWithoutProperties(props, _excluded3);
|
|
1042
|
+
if (multiple) {
|
|
1043
|
+
var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)];
|
|
1044
|
+
return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, {
|
|
1045
|
+
ref: ref,
|
|
1046
|
+
defaultValue: defaultOptions,
|
|
1047
|
+
hidden: _hidden2,
|
|
1048
|
+
multiple: true,
|
|
1049
|
+
children: defaultOptions.map((option, index) => /*#__PURE__*/jsx("option", {
|
|
1050
|
+
value: option,
|
|
1051
|
+
children: option
|
|
1052
|
+
}, index))
|
|
1053
|
+
}));
|
|
1054
|
+
}
|
|
1055
|
+
var defaultOption = formatValue(_defaultValue2);
|
|
1056
|
+
return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, {
|
|
1057
|
+
ref: ref,
|
|
1058
|
+
defaultValue: defaultOption,
|
|
1059
|
+
hidden: _hidden2,
|
|
1060
|
+
children: [defaultOption].map((option, index) => /*#__PURE__*/jsx("option", {
|
|
1061
|
+
value: option,
|
|
1062
|
+
children: option
|
|
1063
|
+
}, index))
|
|
1064
|
+
}));
|
|
1065
|
+
}
|
|
1066
|
+
if (props.type === 'textarea') {
|
|
1067
|
+
var {
|
|
1068
|
+
defaultValue: _defaultValue3,
|
|
1069
|
+
hidden: _hidden3 = true
|
|
1070
|
+
} = props,
|
|
1071
|
+
textareaProps = _objectWithoutProperties(props, _excluded4);
|
|
1072
|
+
return /*#__PURE__*/jsx("textarea", _objectSpread2(_objectSpread2({}, textareaProps), {}, {
|
|
1073
|
+
defaultValue: formatValue(_defaultValue3),
|
|
1074
|
+
ref: ref,
|
|
1075
|
+
hidden: _hidden3
|
|
1076
|
+
}));
|
|
1077
|
+
}
|
|
1078
|
+
if (props.type === 'checkbox' || props.type === 'radio') {
|
|
1079
|
+
var {
|
|
1080
|
+
defaultValue: _defaultValue4 = 'on',
|
|
1081
|
+
value = _defaultValue4,
|
|
1082
|
+
hidden: _hidden4 = true
|
|
1083
|
+
} = props,
|
|
1084
|
+
_inputProps = _objectWithoutProperties(props, _excluded5);
|
|
1085
|
+
return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, _inputProps), {}, {
|
|
1086
|
+
ref: ref,
|
|
1087
|
+
value: value,
|
|
1088
|
+
hidden: _hidden4
|
|
1089
|
+
}));
|
|
1090
|
+
}
|
|
1091
|
+
var {
|
|
1092
|
+
defaultValue,
|
|
1093
|
+
hidden = true
|
|
1094
|
+
} = props,
|
|
1095
|
+
inputProps = _objectWithoutProperties(props, _excluded6);
|
|
1096
|
+
return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, inputProps), {}, {
|
|
1097
|
+
ref: ref,
|
|
1098
|
+
defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined,
|
|
1099
|
+
hidden: hidden
|
|
1100
|
+
}));
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
export { BaseControl, FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
|
package/dist/future/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type { FieldName, FormError, FormValue, Submission, SubmissionResult, } from '@conform-to/dom/future';
|
|
2
2
|
export { getFieldValue, parseSubmission, report, isDirty, } from '@conform-to/dom/future';
|
|
3
|
-
export type { Control, DefaultValue, BaseMetadata, BaseFieldMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, CustomSchemaTypes, FormsConfig, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, Fieldset, IntentDispatcher, InferBaseErrorShape, InferCustomFormMetadata, InferCustomFieldMetadata, } from './types';
|
|
3
|
+
export type { Control, ControlOptions, BaseControlProps, DefaultValue, BaseMetadata, BaseFieldMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, CustomSchemaTypes, FormsConfig, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, Fieldset, IntentDispatcher, InferBaseErrorShape, InferCustomFormMetadata, InferCustomFieldMetadata, } from './types';
|
|
4
4
|
export { configureForms } from './forms';
|
|
5
|
-
export { PreserveBoundary, FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
|
|
5
|
+
export { BaseControl, PreserveBoundary, FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
|
|
6
6
|
export { shape } from './util';
|
|
7
7
|
export { memoize } from './memoize';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/future/index.js
CHANGED
|
@@ -27,6 +27,7 @@ Object.defineProperty(exports, 'report', {
|
|
|
27
27
|
get: function () { return future.report; }
|
|
28
28
|
});
|
|
29
29
|
exports.configureForms = forms.configureForms;
|
|
30
|
+
exports.BaseControl = hooks.BaseControl;
|
|
30
31
|
exports.FormOptionsProvider = hooks.FormOptionsProvider;
|
|
31
32
|
exports.FormProvider = hooks.FormProvider;
|
|
32
33
|
exports.PreserveBoundary = hooks.PreserveBoundary;
|
package/dist/future/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getFieldValue, isDirty, parseSubmission, report } from '@conform-to/dom/future';
|
|
2
2
|
export { configureForms } from './forms.mjs';
|
|
3
|
-
export { FormOptionsProvider, FormProvider, PreserveBoundary, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
|
|
3
|
+
export { BaseControl, FormOptionsProvider, FormProvider, PreserveBoundary, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
|
|
4
4
|
export { shape } from './util.mjs';
|
|
5
5
|
export { memoize } from './memoize.mjs';
|