@dynamic-field-kit/react 1.5.1 → 1.6.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/CHANGELOG.md +166 -0
- package/README.md +76 -14
- package/dist/index.d.mts +79 -5
- package/dist/index.d.ts +79 -5
- package/dist/index.js +247 -100
- package/dist/index.mjs +262 -110
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -438,24 +438,10 @@ function useFieldRegistry() {
|
|
|
438
438
|
import { jsxs as jsxs3 } from "react/jsx-runtime";
|
|
439
439
|
var DynamicInputInner = ({
|
|
440
440
|
type,
|
|
441
|
-
value,
|
|
442
441
|
onChange,
|
|
443
442
|
onBlur,
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
className,
|
|
447
|
-
description,
|
|
448
|
-
disabled,
|
|
449
|
-
readOnly,
|
|
450
|
-
required,
|
|
451
|
-
touched,
|
|
452
|
-
dirty,
|
|
453
|
-
error,
|
|
454
|
-
id,
|
|
455
|
-
ariaInvalid,
|
|
456
|
-
ariaDescribedBy,
|
|
457
|
-
ariaRequired,
|
|
458
|
-
extraProps
|
|
443
|
+
extraProps,
|
|
444
|
+
...rendererProps
|
|
459
445
|
}) => {
|
|
460
446
|
const registry = useFieldRegistry();
|
|
461
447
|
const Renderer = useMemo(
|
|
@@ -470,23 +456,9 @@ var DynamicInputInner = ({
|
|
|
470
456
|
}
|
|
471
457
|
return React3.createElement(Renderer, {
|
|
472
458
|
...extraProps,
|
|
473
|
-
|
|
459
|
+
...rendererProps,
|
|
474
460
|
onValueChange: onChange,
|
|
475
|
-
onBlur
|
|
476
|
-
label,
|
|
477
|
-
options,
|
|
478
|
-
className,
|
|
479
|
-
description,
|
|
480
|
-
disabled,
|
|
481
|
-
readOnly,
|
|
482
|
-
required,
|
|
483
|
-
touched,
|
|
484
|
-
dirty,
|
|
485
|
-
error,
|
|
486
|
-
id,
|
|
487
|
-
ariaInvalid,
|
|
488
|
-
ariaDescribedBy,
|
|
489
|
-
ariaRequired
|
|
461
|
+
onBlur
|
|
490
462
|
});
|
|
491
463
|
};
|
|
492
464
|
var DynamicInput = /* @__PURE__ */ React3.memo(
|
|
@@ -496,10 +468,8 @@ var DynamicInput_default = DynamicInput;
|
|
|
496
468
|
|
|
497
469
|
// src/components/FieldInput.tsx
|
|
498
470
|
import {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
resolveReadOnly,
|
|
502
|
-
validateField
|
|
471
|
+
buildFieldRendererProps,
|
|
472
|
+
makeFieldId
|
|
503
473
|
} from "@dynamic-field-kit/core";
|
|
504
474
|
import React6, { useCallback as useCallback3 } from "react";
|
|
505
475
|
|
|
@@ -507,18 +477,22 @@ import React6, { useCallback as useCallback3 } from "react";
|
|
|
507
477
|
import {
|
|
508
478
|
canAddGroupItem,
|
|
509
479
|
canRemoveGroupItem,
|
|
510
|
-
createGroupItem
|
|
480
|
+
createGroupItem,
|
|
481
|
+
indexGroupPathMap
|
|
511
482
|
} from "@dynamic-field-kit/core";
|
|
512
|
-
import { useCallback as useCallback2 } from "react";
|
|
483
|
+
import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
513
484
|
|
|
514
485
|
// src/components/MultiFieldInput.tsx
|
|
515
486
|
import {
|
|
516
487
|
applyComputedValues,
|
|
488
|
+
isFieldGroup,
|
|
517
489
|
validateFields
|
|
518
490
|
} from "@dynamic-field-kit/core";
|
|
519
|
-
import {
|
|
491
|
+
import React4, {
|
|
520
492
|
useCallback,
|
|
521
493
|
useEffect as useEffect2,
|
|
494
|
+
useId,
|
|
495
|
+
useImperativeHandle,
|
|
522
496
|
useMemo as useMemo2,
|
|
523
497
|
useRef,
|
|
524
498
|
useState as useState2
|
|
@@ -533,25 +507,39 @@ function resolveLayout(layout) {
|
|
|
533
507
|
}
|
|
534
508
|
return { type: layout.type, config: layout };
|
|
535
509
|
}
|
|
536
|
-
var
|
|
510
|
+
var MultiFieldInputInner = ({
|
|
537
511
|
fieldDescriptions,
|
|
538
512
|
properties,
|
|
539
513
|
onChange,
|
|
540
514
|
layout,
|
|
515
|
+
idPrefix,
|
|
541
516
|
rootData,
|
|
542
517
|
onValidityChange,
|
|
543
|
-
onBlurField
|
|
544
|
-
|
|
518
|
+
onBlurField,
|
|
519
|
+
touched: touchedProp,
|
|
520
|
+
errors: errorsProp,
|
|
521
|
+
onTouchedChange,
|
|
522
|
+
form
|
|
523
|
+
}, ref) => {
|
|
524
|
+
const effectiveProperties = properties ?? form?.data;
|
|
525
|
+
const effectiveOnChange = onChange ?? form?.handleChange;
|
|
526
|
+
const effectiveOnBlurField = onBlurField ?? form?.handleBlur;
|
|
527
|
+
const controlledTouched = touchedProp ?? form?.touched;
|
|
528
|
+
const effectiveErrors = errorsProp ?? form?.errors;
|
|
545
529
|
const [data, setData] = useState2({});
|
|
546
|
-
const [
|
|
547
|
-
|
|
548
|
-
);
|
|
549
|
-
const
|
|
530
|
+
const [internalTouched, setInternalTouched] = useState2({});
|
|
531
|
+
const initialPropertiesRef = useRef(effectiveProperties ?? {});
|
|
532
|
+
const autoId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
533
|
+
const effectiveIdPrefix = idPrefix ?? `dfk-${autoId}`;
|
|
534
|
+
const isTouchedControlled = controlledTouched !== void 0;
|
|
535
|
+
const effectiveTouched = controlledTouched ?? internalTouched;
|
|
550
536
|
useEffect2(() => {
|
|
551
|
-
if (
|
|
552
|
-
setData(
|
|
537
|
+
if (effectiveProperties) {
|
|
538
|
+
setData(
|
|
539
|
+
applyComputedValues(fieldDescriptions, effectiveProperties, rootData)
|
|
540
|
+
);
|
|
553
541
|
}
|
|
554
|
-
}, [
|
|
542
|
+
}, [effectiveProperties]);
|
|
555
543
|
const effectiveRoot = rootData ?? data;
|
|
556
544
|
const visibleFields = useMemo2(
|
|
557
545
|
() => fieldDescriptions.filter(
|
|
@@ -561,16 +549,22 @@ var MultiFieldInput = ({
|
|
|
561
549
|
);
|
|
562
550
|
const dataRef = useRef(data);
|
|
563
551
|
dataRef.current = data;
|
|
564
|
-
const onChangeRef = useRef(
|
|
565
|
-
onChangeRef.current =
|
|
552
|
+
const onChangeRef = useRef(effectiveOnChange);
|
|
553
|
+
onChangeRef.current = effectiveOnChange;
|
|
566
554
|
const fieldDescriptionsRef = useRef(fieldDescriptions);
|
|
567
555
|
fieldDescriptionsRef.current = fieldDescriptions;
|
|
568
556
|
const rootDataRef = useRef(rootData);
|
|
569
557
|
rootDataRef.current = rootData;
|
|
570
558
|
const onValidityChangeRef = useRef(onValidityChange);
|
|
571
559
|
onValidityChangeRef.current = onValidityChange;
|
|
572
|
-
const onBlurFieldRef = useRef(
|
|
573
|
-
onBlurFieldRef.current =
|
|
560
|
+
const onBlurFieldRef = useRef(effectiveOnBlurField);
|
|
561
|
+
onBlurFieldRef.current = effectiveOnBlurField;
|
|
562
|
+
const onTouchedChangeRef = useRef(onTouchedChange);
|
|
563
|
+
onTouchedChangeRef.current = onTouchedChange;
|
|
564
|
+
const isTouchedControlledRef = useRef(isTouchedControlled);
|
|
565
|
+
isTouchedControlledRef.current = isTouchedControlled;
|
|
566
|
+
const effectiveTouchedRef = useRef(effectiveTouched);
|
|
567
|
+
effectiveTouchedRef.current = effectiveTouched;
|
|
574
568
|
useEffect2(() => {
|
|
575
569
|
onValidityChangeRef.current?.(
|
|
576
570
|
validateFields(fieldDescriptions, data, rootData)
|
|
@@ -587,10 +581,39 @@ var MultiFieldInput = ({
|
|
|
587
581
|
setData(next);
|
|
588
582
|
onChangeRef.current?.(next);
|
|
589
583
|
}, []);
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
584
|
+
const markTouched = useCallback((key, isTouched) => {
|
|
585
|
+
const current = effectiveTouchedRef.current;
|
|
586
|
+
if (Boolean(current[key]) === isTouched) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const next = { ...current, [key]: isTouched };
|
|
590
|
+
if (!isTouchedControlledRef.current) {
|
|
591
|
+
effectiveTouchedRef.current = next;
|
|
592
|
+
setInternalTouched(next);
|
|
593
|
+
}
|
|
594
|
+
onTouchedChangeRef.current?.(next);
|
|
593
595
|
}, []);
|
|
596
|
+
const handleBlurField = useCallback(
|
|
597
|
+
(key) => {
|
|
598
|
+
markTouched(key, true);
|
|
599
|
+
onBlurFieldRef.current?.(key);
|
|
600
|
+
},
|
|
601
|
+
[markTouched]
|
|
602
|
+
);
|
|
603
|
+
useImperativeHandle(
|
|
604
|
+
ref,
|
|
605
|
+
() => ({
|
|
606
|
+
resetTouched: () => {
|
|
607
|
+
if (!isTouchedControlledRef.current) {
|
|
608
|
+
effectiveTouchedRef.current = {};
|
|
609
|
+
}
|
|
610
|
+
setInternalTouched({});
|
|
611
|
+
},
|
|
612
|
+
setFieldTouched: (fieldName, isTouched = true) => markTouched(fieldName, isTouched),
|
|
613
|
+
getTouched: () => effectiveTouchedRef.current
|
|
614
|
+
}),
|
|
615
|
+
[markTouched]
|
|
616
|
+
);
|
|
594
617
|
const { type, config } = resolveLayout(layout);
|
|
595
618
|
const Layout = layoutRegistry.get(type);
|
|
596
619
|
if (!Layout) {
|
|
@@ -602,7 +625,10 @@ var MultiFieldInput = ({
|
|
|
602
625
|
fieldDescription: f,
|
|
603
626
|
renderInfos: data,
|
|
604
627
|
rootData: effectiveRoot,
|
|
605
|
-
|
|
628
|
+
idPrefix: effectiveIdPrefix,
|
|
629
|
+
touched: Boolean(effectiveTouched[f.name]),
|
|
630
|
+
touchedMap: isFieldGroup(f) ? effectiveTouched : void 0,
|
|
631
|
+
errors: effectiveErrors,
|
|
606
632
|
dirty: data[f.name] !== initialPropertiesRef.current[f.name],
|
|
607
633
|
onBlurField: handleBlurField,
|
|
608
634
|
onValueChangeField: handleValueChangeField
|
|
@@ -610,14 +636,20 @@ var MultiFieldInput = ({
|
|
|
610
636
|
f.name
|
|
611
637
|
)) });
|
|
612
638
|
};
|
|
639
|
+
var MultiFieldInput = /* @__PURE__ */ React4.forwardRef(MultiFieldInputInner);
|
|
640
|
+
MultiFieldInput.displayName = "MultiFieldInput";
|
|
613
641
|
var MultiFieldInput_default = MultiFieldInput;
|
|
614
642
|
|
|
615
643
|
// src/components/FieldGroupInput.tsx
|
|
616
644
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
645
|
+
var EMPTY_TOUCHED = Object.freeze({});
|
|
617
646
|
var FieldGroupInput = ({
|
|
618
647
|
fieldDescription,
|
|
619
648
|
items,
|
|
620
649
|
rootData,
|
|
650
|
+
errors,
|
|
651
|
+
touched,
|
|
652
|
+
onBlurField,
|
|
621
653
|
onChange
|
|
622
654
|
}) => {
|
|
623
655
|
const {
|
|
@@ -631,6 +663,14 @@ var FieldGroupInput = ({
|
|
|
631
663
|
const addText = addLabel ?? "Add";
|
|
632
664
|
const removeText = removeLabel ?? "Remove";
|
|
633
665
|
const groupName = label ?? fieldDescription.name;
|
|
666
|
+
const errorsByItem = useMemo3(
|
|
667
|
+
() => indexGroupPathMap(errors, fieldDescription.name),
|
|
668
|
+
[errors, fieldDescription.name]
|
|
669
|
+
);
|
|
670
|
+
const touchedByItem = useMemo3(
|
|
671
|
+
() => indexGroupPathMap(touched, fieldDescription.name),
|
|
672
|
+
[touched, fieldDescription.name]
|
|
673
|
+
);
|
|
634
674
|
const handleItemChange = useCallback2(
|
|
635
675
|
(index, next) => {
|
|
636
676
|
const nextItems = items.slice();
|
|
@@ -667,6 +707,9 @@ var FieldGroupInput = ({
|
|
|
667
707
|
fieldDescriptions: fields,
|
|
668
708
|
properties: item,
|
|
669
709
|
rootData,
|
|
710
|
+
errors: errorsByItem?.[index],
|
|
711
|
+
touched: touched === void 0 ? void 0 : touchedByItem?.[index] ?? EMPTY_TOUCHED,
|
|
712
|
+
onBlurField: (key) => onBlurField?.(`${fieldDescription.name}[${index}].${key}`),
|
|
670
713
|
onChange: (next) => handleItemChange(index, next)
|
|
671
714
|
}
|
|
672
715
|
) }),
|
|
@@ -704,12 +747,15 @@ var FieldInputInner = ({
|
|
|
704
747
|
fieldDescription,
|
|
705
748
|
renderInfos,
|
|
706
749
|
rootData,
|
|
750
|
+
idPrefix = "dfk-field",
|
|
707
751
|
touched,
|
|
752
|
+
touchedMap,
|
|
708
753
|
dirty,
|
|
754
|
+
errors,
|
|
709
755
|
onBlurField,
|
|
710
756
|
onValueChangeField
|
|
711
757
|
}) => {
|
|
712
|
-
const { name,
|
|
758
|
+
const { name, fields } = fieldDescription;
|
|
713
759
|
const handleChange = useCallback3(
|
|
714
760
|
(v) => onValueChangeField(v, name),
|
|
715
761
|
[onValueChangeField, name]
|
|
@@ -726,43 +772,27 @@ var FieldInputInner = ({
|
|
|
726
772
|
fieldDescription,
|
|
727
773
|
items,
|
|
728
774
|
rootData,
|
|
775
|
+
errors,
|
|
776
|
+
touched: touchedMap,
|
|
777
|
+
onBlurField,
|
|
729
778
|
onChange: handleChange
|
|
730
779
|
}
|
|
731
780
|
);
|
|
732
781
|
}
|
|
733
|
-
const
|
|
782
|
+
const rendererProps = buildFieldRendererProps({
|
|
734
783
|
fieldDescription,
|
|
735
|
-
renderInfos,
|
|
736
|
-
rootData
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
rootData
|
|
743
|
-
);
|
|
744
|
-
const errors = effectiveDisabled ? [] : validateField(fieldDescription, renderInfos[name], renderInfos, rootData);
|
|
745
|
-
const error = errors.length > 0 ? errors : void 0;
|
|
746
|
-
const fieldId = `dfk-field-${name}`;
|
|
784
|
+
data: renderInfos,
|
|
785
|
+
rootData,
|
|
786
|
+
id: makeFieldId(fieldDescription, idPrefix),
|
|
787
|
+
touched,
|
|
788
|
+
dirty,
|
|
789
|
+
validationErrors: errors === void 0 ? void 0 : errors[name] ?? []
|
|
790
|
+
});
|
|
747
791
|
return /* @__PURE__ */ jsx7(
|
|
748
792
|
DynamicInput_default,
|
|
749
793
|
{
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
label,
|
|
753
|
-
value: renderInfos[name],
|
|
754
|
-
options: resolvedOptionsList,
|
|
755
|
-
className,
|
|
756
|
-
description,
|
|
757
|
-
disabled: effectiveDisabled,
|
|
758
|
-
readOnly,
|
|
759
|
-
required,
|
|
760
|
-
touched,
|
|
761
|
-
dirty,
|
|
762
|
-
error,
|
|
763
|
-
ariaInvalid: Boolean(error),
|
|
764
|
-
ariaRequired: Boolean(required),
|
|
765
|
-
extraProps: props,
|
|
794
|
+
...rendererProps,
|
|
795
|
+
description: rendererProps.description,
|
|
766
796
|
onChange: handleChange,
|
|
767
797
|
onBlur: handleBlur
|
|
768
798
|
}
|
|
@@ -770,7 +800,7 @@ var FieldInputInner = ({
|
|
|
770
800
|
};
|
|
771
801
|
var FieldInput = /* @__PURE__ */ React6.memo(FieldInputInner, (prev, next) => {
|
|
772
802
|
const name = prev.fieldDescription.name;
|
|
773
|
-
return prev.fieldDescription === next.fieldDescription && prev.onValueChangeField === next.onValueChangeField && prev.onBlurField === next.onBlurField && prev.rootData === next.rootData && prev.touched === next.touched && prev.dirty === next.dirty && prev.renderInfos[name] === next.renderInfos[name];
|
|
803
|
+
return prev.fieldDescription === next.fieldDescription && prev.onValueChangeField === next.onValueChangeField && prev.onBlurField === next.onBlurField && prev.rootData === next.rootData && prev.idPrefix === next.idPrefix && prev.touched === next.touched && prev.touchedMap === next.touchedMap && prev.dirty === next.dirty && prev.errors === next.errors && prev.renderInfos[name] === next.renderInfos[name];
|
|
774
804
|
});
|
|
775
805
|
var FieldInput_default = FieldInput;
|
|
776
806
|
|
|
@@ -977,9 +1007,20 @@ var DynamicFormDevTools = ({
|
|
|
977
1007
|
// src/useDynamicForm.ts
|
|
978
1008
|
import {
|
|
979
1009
|
applyComputedValues as applyComputedValues2,
|
|
980
|
-
|
|
1010
|
+
collectFieldPaths,
|
|
1011
|
+
validateFields as validateFields2,
|
|
1012
|
+
validateFieldsAsync
|
|
981
1013
|
} from "@dynamic-field-kit/core";
|
|
982
|
-
import { useCallback as useCallback4, useState as useState4 } from "react";
|
|
1014
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
|
|
1015
|
+
function sameStringMap(left, right) {
|
|
1016
|
+
const keys = Object.keys(left);
|
|
1017
|
+
return keys.length === Object.keys(right).length && keys.every(
|
|
1018
|
+
(key) => left[key]?.length === right[key]?.length && left[key]?.every((value, index) => value === right[key]?.[index])
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
function sameValidationResult(left, right) {
|
|
1022
|
+
return left.valid === right.valid && left.complete === right.complete && left.status === right.status && (left.pending ?? []).join("\0") === (right.pending ?? []).join("\0") && sameStringMap(left.errors, right.errors);
|
|
1023
|
+
}
|
|
983
1024
|
function useDynamicForm({
|
|
984
1025
|
fields,
|
|
985
1026
|
initialValues = {},
|
|
@@ -994,22 +1035,76 @@ function useDynamicForm({
|
|
|
994
1035
|
const [touched, setTouched] = useState4({});
|
|
995
1036
|
const [isSubmitting, setIsSubmitting] = useState4(false);
|
|
996
1037
|
const [isSubmitted, setIsSubmitted] = useState4(false);
|
|
1038
|
+
const [validationResult, setValidationResult] = useState4(
|
|
1039
|
+
() => validateFields2(fields, data)
|
|
1040
|
+
);
|
|
1041
|
+
const [isValidating, setIsValidating] = useState4(false);
|
|
1042
|
+
const validationRunRef = useRef2(0);
|
|
1043
|
+
const validationAbortRef = useRef2(void 0);
|
|
1044
|
+
const submitRunRef = useRef2(0);
|
|
1045
|
+
const submitAbortRef = useRef2(void 0);
|
|
1046
|
+
const dataRef = useRef2(data);
|
|
1047
|
+
dataRef.current = data;
|
|
1048
|
+
const commitSyncResult = useCallback4((res) => {
|
|
1049
|
+
setValidationResult(
|
|
1050
|
+
(previous) => sameValidationResult(previous, res) ? previous : res
|
|
1051
|
+
);
|
|
1052
|
+
return res.valid;
|
|
1053
|
+
}, []);
|
|
1054
|
+
useEffect3(() => {
|
|
1055
|
+
commitSyncResult(validateFields2(fields, data));
|
|
1056
|
+
}, [fields, data, commitSyncResult]);
|
|
1057
|
+
useEffect3(
|
|
1058
|
+
() => () => {
|
|
1059
|
+
validationAbortRef.current?.abort();
|
|
1060
|
+
submitAbortRef.current?.abort();
|
|
1061
|
+
},
|
|
1062
|
+
[]
|
|
1063
|
+
);
|
|
997
1064
|
const validate = useCallback4(() => {
|
|
998
1065
|
const res = validateFields2(fields, data);
|
|
999
1066
|
setErrors(res.errors);
|
|
1000
|
-
return res
|
|
1067
|
+
return commitSyncResult(res);
|
|
1068
|
+
}, [fields, data, commitSyncResult]);
|
|
1069
|
+
const validateAsync = useCallback4(async () => {
|
|
1070
|
+
const run = ++validationRunRef.current;
|
|
1071
|
+
validationAbortRef.current?.abort();
|
|
1072
|
+
const controller = new AbortController();
|
|
1073
|
+
validationAbortRef.current = controller;
|
|
1074
|
+
const snapshot = data;
|
|
1075
|
+
setIsValidating(true);
|
|
1076
|
+
try {
|
|
1077
|
+
const res = await validateFieldsAsync(fields, snapshot, snapshot, {
|
|
1078
|
+
signal: controller.signal
|
|
1079
|
+
});
|
|
1080
|
+
if (run !== validationRunRef.current || dataRef.current !== snapshot) {
|
|
1081
|
+
return res.valid;
|
|
1082
|
+
}
|
|
1083
|
+
setErrors(res.errors);
|
|
1084
|
+
setValidationResult(res);
|
|
1085
|
+
return res.valid;
|
|
1086
|
+
} finally {
|
|
1087
|
+
if (run === validationRunRef.current) {
|
|
1088
|
+
setIsValidating(false);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1001
1091
|
}, [fields, data]);
|
|
1002
1092
|
const handleChange = useCallback4(
|
|
1003
1093
|
(newData) => {
|
|
1004
1094
|
const next = applyComputedValues2(fields, newData);
|
|
1005
1095
|
setData(next);
|
|
1096
|
+
dataRef.current = next;
|
|
1006
1097
|
setIsDirty(true);
|
|
1098
|
+
validationAbortRef.current?.abort();
|
|
1099
|
+
validationRunRef.current += 1;
|
|
1100
|
+
setIsValidating(false);
|
|
1101
|
+
const res = validateFields2(fields, next);
|
|
1102
|
+
commitSyncResult(res);
|
|
1007
1103
|
if (validateOnChange) {
|
|
1008
|
-
const res = validateFields2(fields, next);
|
|
1009
1104
|
setErrors(res.errors);
|
|
1010
1105
|
}
|
|
1011
1106
|
},
|
|
1012
|
-
[fields, validateOnChange]
|
|
1107
|
+
[fields, validateOnChange, commitSyncResult]
|
|
1013
1108
|
);
|
|
1014
1109
|
const setFieldValue = useCallback4(
|
|
1015
1110
|
(name, value) => {
|
|
@@ -1020,26 +1115,39 @@ function useDynamicForm({
|
|
|
1020
1115
|
const setFieldTouched = useCallback4((name, isTouched = true) => {
|
|
1021
1116
|
setTouched((prev) => ({ ...prev, [name]: isTouched }));
|
|
1022
1117
|
}, []);
|
|
1118
|
+
const touchAll = useCallback4(() => {
|
|
1119
|
+
setTouched(
|
|
1120
|
+
Object.fromEntries(
|
|
1121
|
+
collectFieldPaths(fields, data).map((path) => [path, true])
|
|
1122
|
+
)
|
|
1123
|
+
);
|
|
1124
|
+
}, [fields, data]);
|
|
1125
|
+
const resetTouched = useCallback4(() => setTouched({}), []);
|
|
1023
1126
|
const handleBlur = useCallback4(
|
|
1024
1127
|
(fieldName) => {
|
|
1025
1128
|
setFieldTouched(fieldName, true);
|
|
1026
1129
|
if (validateOnBlur) {
|
|
1027
1130
|
const res = validateFields2(fields, data);
|
|
1028
1131
|
setErrors(res.errors);
|
|
1132
|
+
commitSyncResult(res);
|
|
1029
1133
|
}
|
|
1030
1134
|
},
|
|
1031
|
-
[fields, data, validateOnBlur, setFieldTouched]
|
|
1135
|
+
[fields, data, validateOnBlur, setFieldTouched, commitSyncResult]
|
|
1032
1136
|
);
|
|
1033
1137
|
const reset = useCallback4(
|
|
1034
1138
|
(newValues) => {
|
|
1035
1139
|
const seed = newValues ?? initialValues;
|
|
1036
1140
|
const next = applyComputedValues2(fields, seed);
|
|
1037
1141
|
setData(next);
|
|
1142
|
+
dataRef.current = next;
|
|
1038
1143
|
setErrors({});
|
|
1039
1144
|
setIsDirty(false);
|
|
1040
1145
|
setTouched({});
|
|
1041
1146
|
setIsSubmitting(false);
|
|
1042
1147
|
setIsSubmitted(false);
|
|
1148
|
+
validationAbortRef.current?.abort();
|
|
1149
|
+
validationRunRef.current += 1;
|
|
1150
|
+
setIsValidating(false);
|
|
1043
1151
|
},
|
|
1044
1152
|
[fields, initialValues]
|
|
1045
1153
|
);
|
|
@@ -1049,26 +1157,54 @@ function useDynamicForm({
|
|
|
1049
1157
|
e.preventDefault();
|
|
1050
1158
|
}
|
|
1051
1159
|
setIsSubmitting(true);
|
|
1160
|
+
const submitRun = ++submitRunRef.current;
|
|
1052
1161
|
try {
|
|
1053
|
-
|
|
1054
|
-
|
|
1162
|
+
touchAll();
|
|
1163
|
+
const run = ++validationRunRef.current;
|
|
1164
|
+
validationAbortRef.current?.abort();
|
|
1165
|
+
submitAbortRef.current?.abort();
|
|
1166
|
+
const controller = new AbortController();
|
|
1167
|
+
submitAbortRef.current = controller;
|
|
1168
|
+
const snapshot = data;
|
|
1169
|
+
setIsValidating(true);
|
|
1170
|
+
const res = await validateFieldsAsync(fields, snapshot, snapshot, {
|
|
1171
|
+
signal: controller.signal
|
|
1172
|
+
});
|
|
1173
|
+
if (submitRun !== submitRunRef.current) {
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (dataRef.current === snapshot && run === validationRunRef.current) {
|
|
1177
|
+
setErrors(res.errors);
|
|
1178
|
+
setValidationResult(res);
|
|
1179
|
+
} else {
|
|
1180
|
+
const live = validateFields2(fields, dataRef.current);
|
|
1181
|
+
setErrors(live.errors);
|
|
1182
|
+
commitSyncResult(live);
|
|
1183
|
+
}
|
|
1055
1184
|
setIsSubmitted(true);
|
|
1056
1185
|
if (res.valid) {
|
|
1057
|
-
await onValid(
|
|
1186
|
+
await onValid(snapshot);
|
|
1058
1187
|
} else if (onInvalid) {
|
|
1059
1188
|
onInvalid(res.errors);
|
|
1060
1189
|
}
|
|
1061
1190
|
} finally {
|
|
1191
|
+
if (submitRun === submitRunRef.current) {
|
|
1192
|
+
setIsValidating(false);
|
|
1193
|
+
}
|
|
1062
1194
|
setIsSubmitting(false);
|
|
1063
1195
|
}
|
|
1064
1196
|
},
|
|
1065
|
-
[fields, data]
|
|
1197
|
+
[fields, data, touchAll, commitSyncResult]
|
|
1066
1198
|
);
|
|
1067
|
-
const isValid = Object.keys(errors).length === 0;
|
|
1068
1199
|
return {
|
|
1069
1200
|
data,
|
|
1070
1201
|
errors,
|
|
1071
|
-
isValid,
|
|
1202
|
+
isValid: validationResult.valid,
|
|
1203
|
+
isValidating,
|
|
1204
|
+
// Matches Vue and Angular: a run still in flight is not complete, whatever
|
|
1205
|
+
// the last finished pass concluded.
|
|
1206
|
+
isValidationComplete: validationResult.complete && !isValidating,
|
|
1207
|
+
validationStatus: isValidating ? "pending" : validationResult.status,
|
|
1072
1208
|
isDirty,
|
|
1073
1209
|
isSubmitting,
|
|
1074
1210
|
isSubmitted,
|
|
@@ -1076,10 +1212,14 @@ function useDynamicForm({
|
|
|
1076
1212
|
setData,
|
|
1077
1213
|
setFieldValue,
|
|
1078
1214
|
setFieldTouched,
|
|
1215
|
+
setTouched,
|
|
1216
|
+
touchAll,
|
|
1217
|
+
resetTouched,
|
|
1079
1218
|
handleChange,
|
|
1080
1219
|
handleBlur,
|
|
1081
1220
|
reset,
|
|
1082
1221
|
validate,
|
|
1222
|
+
validateAsync,
|
|
1083
1223
|
handleSubmit
|
|
1084
1224
|
};
|
|
1085
1225
|
}
|
|
@@ -1087,34 +1227,46 @@ function useDynamicForm({
|
|
|
1087
1227
|
// src/index.ts
|
|
1088
1228
|
import { FieldRegistry } from "@dynamic-field-kit/core";
|
|
1089
1229
|
import {
|
|
1090
|
-
|
|
1230
|
+
buildFieldRendererProps as buildFieldRendererProps2,
|
|
1231
|
+
makeFieldId as makeFieldId2,
|
|
1232
|
+
FIELD_RENDERER_PROP_KEYS
|
|
1233
|
+
} from "@dynamic-field-kit/core";
|
|
1234
|
+
import {
|
|
1235
|
+
validateField,
|
|
1091
1236
|
validateFieldAsync,
|
|
1092
1237
|
validateFields as validateFields3,
|
|
1093
|
-
validateFieldsAsync,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1238
|
+
validateFieldsAsync as validateFieldsAsync2,
|
|
1239
|
+
collectFieldPaths as collectFieldPaths2,
|
|
1240
|
+
indexGroupPathMap as indexGroupPathMap2,
|
|
1241
|
+
resolveDisabled,
|
|
1242
|
+
resolveReadOnly,
|
|
1243
|
+
resolveOptions,
|
|
1097
1244
|
validators
|
|
1098
1245
|
} from "@dynamic-field-kit/core";
|
|
1099
1246
|
export {
|
|
1100
1247
|
DynamicFormDevTools,
|
|
1101
1248
|
DynamicInput_default as DynamicInput,
|
|
1249
|
+
FIELD_RENDERER_PROP_KEYS,
|
|
1102
1250
|
FieldInput_default as FieldInput,
|
|
1103
1251
|
FieldRegistry,
|
|
1104
1252
|
FieldRegistryProvider,
|
|
1105
1253
|
MultiFieldInput_default as MultiFieldInput,
|
|
1254
|
+
buildFieldRendererProps2 as buildFieldRendererProps,
|
|
1255
|
+
collectFieldPaths2 as collectFieldPaths,
|
|
1106
1256
|
defaultRenderersMap,
|
|
1107
1257
|
fieldRegistry,
|
|
1108
1258
|
getDefaultRenderer,
|
|
1259
|
+
indexGroupPathMap2 as indexGroupPathMap,
|
|
1109
1260
|
layoutRegistry,
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1261
|
+
makeFieldId2 as makeFieldId,
|
|
1262
|
+
resolveDisabled,
|
|
1263
|
+
resolveOptions,
|
|
1264
|
+
resolveReadOnly,
|
|
1113
1265
|
useDynamicForm,
|
|
1114
1266
|
useFieldRegistry,
|
|
1115
|
-
|
|
1267
|
+
validateField,
|
|
1116
1268
|
validateFieldAsync,
|
|
1117
1269
|
validateFields3 as validateFields,
|
|
1118
|
-
validateFieldsAsync,
|
|
1270
|
+
validateFieldsAsync2 as validateFieldsAsync,
|
|
1119
1271
|
validators
|
|
1120
1272
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-field-kit/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "React renderer for dynamic-field-kit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@dynamic-field-kit/core": "^1.
|
|
29
|
+
"@dynamic-field-kit/core": "^1.6.0",
|
|
30
30
|
"@testing-library/dom": "^10.0.0",
|
|
31
|
-
"@testing-library/jest-dom": "^
|
|
31
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
32
32
|
"@testing-library/react": "^16.0.0",
|
|
33
|
-
"@testing-library/user-event": "^14.
|
|
33
|
+
"@testing-library/user-event": "^14.6.7",
|
|
34
34
|
"@types/react": "^19.2.8",
|
|
35
35
|
"@vitest/coverage-v8": "^4.1.11",
|
|
36
36
|
"@vitejs/plugin-react": "^6.1.1",
|