hs-uix 1.3.0 → 1.4.1
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/dist/datatable.js +39 -28
- package/dist/datatable.mjs +39 -28
- package/dist/form.js +228 -87
- package/dist/form.mjs +228 -87
- package/dist/index.js +267 -115
- package/dist/index.mjs +267 -115
- package/package.json +1 -1
package/dist/form.mjs
CHANGED
|
@@ -59,6 +59,7 @@ var getEmptyValue = (field) => {
|
|
|
59
59
|
case "datetime":
|
|
60
60
|
return void 0;
|
|
61
61
|
case "display":
|
|
62
|
+
case "slot":
|
|
62
63
|
case "crmPropertyList":
|
|
63
64
|
case "crmAssociationPropertyList":
|
|
64
65
|
return void 0;
|
|
@@ -202,7 +203,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
|
|
|
202
203
|
var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
|
|
203
204
|
const includeCustomValidators = options.includeCustomValidators !== false;
|
|
204
205
|
const msg = options.messages || {};
|
|
205
|
-
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
|
|
206
|
+
if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
|
|
206
207
|
const isRequired = resolveRequired(field, allValues);
|
|
207
208
|
const plugin = fieldTypes && fieldTypes[field.type];
|
|
208
209
|
const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
|
|
@@ -425,6 +426,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
425
426
|
// explicit row layout array (overrides columns + columnWidth)
|
|
426
427
|
sections,
|
|
427
428
|
// FormBuilderSection[] — accordion field grouping
|
|
429
|
+
groups,
|
|
430
|
+
// Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
|
|
428
431
|
gap = "sm",
|
|
429
432
|
// gap between fields
|
|
430
433
|
showRequiredIndicator = true,
|
|
@@ -634,7 +637,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
634
637
|
const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
|
|
635
638
|
const vals = {};
|
|
636
639
|
for (const field of fields) {
|
|
637
|
-
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
|
|
640
|
+
if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
|
|
638
641
|
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
639
642
|
for (const item of field.items) {
|
|
640
643
|
const subFields = field.fields(item);
|
|
@@ -668,9 +671,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
668
671
|
const inputDebounceRef = useRef(/* @__PURE__ */ new Map());
|
|
669
672
|
const rowKeyRef = useRef(/* @__PURE__ */ new WeakMap());
|
|
670
673
|
const rowKeyCounterRef = useRef(0);
|
|
674
|
+
const controlledBaselineLockedRef = useRef(false);
|
|
671
675
|
const initialSnapshot = useRef(null);
|
|
672
676
|
if (initialSnapshot.current === null) {
|
|
673
|
-
initialSnapshot.current = deepClone(computeInitialValues());
|
|
677
|
+
initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
|
|
674
678
|
}
|
|
675
679
|
const formValues = values != null ? values : internalValues;
|
|
676
680
|
const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
|
|
@@ -682,6 +686,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
682
686
|
const draftValuesRef = useRef(null);
|
|
683
687
|
formValuesRef.current = formValues;
|
|
684
688
|
formErrorsRef.current = formErrors;
|
|
689
|
+
const syncDirtyBaseline = useCallback((nextValues) => {
|
|
690
|
+
initialSnapshot.current = deepClone(nextValues || {});
|
|
691
|
+
prevAutoSaveValues.current = deepClone(nextValues || {});
|
|
692
|
+
}, []);
|
|
685
693
|
const fieldByName = useMemo(() => {
|
|
686
694
|
const map = /* @__PURE__ */ new Map();
|
|
687
695
|
for (const field of fields) {
|
|
@@ -758,6 +766,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
758
766
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
759
767
|
};
|
|
760
768
|
}, []);
|
|
769
|
+
useEffect(() => {
|
|
770
|
+
if (values == null) return;
|
|
771
|
+
if (controlledBaselineLockedRef.current) return;
|
|
772
|
+
syncDirtyBaseline(values);
|
|
773
|
+
}, [values, syncDirtyBaseline]);
|
|
761
774
|
const isDirty = useMemo(() => {
|
|
762
775
|
return !deepEqual(formValues, initialSnapshot.current);
|
|
763
776
|
}, [formValues]);
|
|
@@ -876,6 +889,50 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
876
889
|
},
|
|
877
890
|
[fieldTypes]
|
|
878
891
|
);
|
|
892
|
+
const setRepeaterSubFieldError = useCallback(
|
|
893
|
+
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
894
|
+
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
895
|
+
const merged = { ...formErrorsRef.current };
|
|
896
|
+
if (errorMessage) {
|
|
897
|
+
merged[key] = errorMessage;
|
|
898
|
+
} else {
|
|
899
|
+
delete merged[key];
|
|
900
|
+
}
|
|
901
|
+
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
902
|
+
const match = k.match(/\[(\d+)\]\./);
|
|
903
|
+
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
904
|
+
return { key: k, row };
|
|
905
|
+
}).sort((a, b) => a.row - b.row);
|
|
906
|
+
if (subErrors.length > 0) {
|
|
907
|
+
const first = subErrors[0];
|
|
908
|
+
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
909
|
+
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
910
|
+
delete merged[fieldName];
|
|
911
|
+
}
|
|
912
|
+
replaceErrors(merged);
|
|
913
|
+
},
|
|
914
|
+
[replaceErrors]
|
|
915
|
+
);
|
|
916
|
+
const expandValidationFields = useCallback(
|
|
917
|
+
(fieldSubset) => {
|
|
918
|
+
const toValidate = fieldSubset || visibleFields;
|
|
919
|
+
const expanded = [];
|
|
920
|
+
for (const field of toValidate) {
|
|
921
|
+
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
922
|
+
for (const item of field.items) {
|
|
923
|
+
for (const subField of field.fields(item)) {
|
|
924
|
+
if (subField.visible && !subField.visible(formValues)) continue;
|
|
925
|
+
expanded.push(subField);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
expanded.push(field);
|
|
931
|
+
}
|
|
932
|
+
return expanded;
|
|
933
|
+
},
|
|
934
|
+
[visibleFields, formValues]
|
|
935
|
+
);
|
|
879
936
|
const validateField = useCallback(
|
|
880
937
|
(name, value) => {
|
|
881
938
|
const field = fieldByName.get(name);
|
|
@@ -895,7 +952,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
895
952
|
);
|
|
896
953
|
const validateVisibleFields = useCallback(
|
|
897
954
|
(fieldSubset) => {
|
|
898
|
-
const toValidate = fieldSubset
|
|
955
|
+
const toValidate = expandValidationFields(fieldSubset);
|
|
899
956
|
const errors = {};
|
|
900
957
|
let hasErrors = false;
|
|
901
958
|
for (const field of toValidate) {
|
|
@@ -915,52 +972,54 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
915
972
|
}
|
|
916
973
|
return { errors, hasErrors };
|
|
917
974
|
},
|
|
918
|
-
[
|
|
975
|
+
[expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
|
|
919
976
|
);
|
|
920
|
-
const
|
|
921
|
-
(
|
|
922
|
-
const field =
|
|
923
|
-
if (!field || field.type === "repeater") return null;
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
977
|
+
const runAsyncValidationTarget = useCallback(
|
|
978
|
+
(target) => {
|
|
979
|
+
const { validationKey, field, value, allValues, applyError } = target || {};
|
|
980
|
+
if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
|
|
981
|
+
const syncError = runValidators(value, field, allValues, fieldTypes, {
|
|
982
|
+
includeCustomValidators: false,
|
|
983
|
+
messages: validationMessages
|
|
984
|
+
});
|
|
985
|
+
const prevController = asyncAbortRef.current.get(validationKey);
|
|
927
986
|
if (prevController) prevController.abort();
|
|
928
|
-
asyncAbortRef.current.delete(
|
|
987
|
+
asyncAbortRef.current.delete(validationKey);
|
|
929
988
|
setValidatingFields((prev) => {
|
|
930
|
-
if (!prev[
|
|
989
|
+
if (!prev[validationKey]) return prev;
|
|
931
990
|
const next = { ...prev };
|
|
932
|
-
delete next[
|
|
991
|
+
delete next[validationKey];
|
|
933
992
|
return next;
|
|
934
993
|
});
|
|
935
994
|
if (syncError) return null;
|
|
936
|
-
const version = (asyncValidationVersionRef.current.get(
|
|
937
|
-
asyncValidationVersionRef.current.set(
|
|
995
|
+
const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
|
|
996
|
+
asyncValidationVersionRef.current.set(validationKey, version);
|
|
938
997
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
939
|
-
if (controller) asyncAbortRef.current.set(
|
|
998
|
+
if (controller) asyncAbortRef.current.set(validationKey, controller);
|
|
940
999
|
let asyncPromises;
|
|
941
1000
|
try {
|
|
942
1001
|
asyncPromises = collectAsyncValidatorPromises(
|
|
943
|
-
|
|
1002
|
+
value,
|
|
944
1003
|
field,
|
|
945
|
-
|
|
1004
|
+
allValues,
|
|
946
1005
|
controller ? { signal: controller.signal } : void 0
|
|
947
1006
|
);
|
|
948
1007
|
} catch (err) {
|
|
949
|
-
|
|
1008
|
+
applyError((err == null ? void 0 : err.message) || "Validation failed");
|
|
950
1009
|
return null;
|
|
951
1010
|
}
|
|
952
1011
|
if (asyncPromises.length === 0) {
|
|
953
|
-
asyncAbortRef.current.delete(
|
|
1012
|
+
asyncAbortRef.current.delete(validationKey);
|
|
954
1013
|
return null;
|
|
955
1014
|
}
|
|
956
1015
|
const validationPromise = Promise.all(asyncPromises).then(
|
|
957
1016
|
(results) => {
|
|
958
|
-
if (asyncValidationVersionRef.current.get(
|
|
959
|
-
asyncValidationRef.current.delete(
|
|
960
|
-
asyncAbortRef.current.delete(
|
|
1017
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
1018
|
+
asyncValidationRef.current.delete(validationKey);
|
|
1019
|
+
asyncAbortRef.current.delete(validationKey);
|
|
961
1020
|
setValidatingFields((prev) => {
|
|
962
1021
|
const next = { ...prev };
|
|
963
|
-
delete next[
|
|
1022
|
+
delete next[validationKey];
|
|
964
1023
|
return next;
|
|
965
1024
|
});
|
|
966
1025
|
let err = null;
|
|
@@ -971,50 +1030,128 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
971
1030
|
break;
|
|
972
1031
|
}
|
|
973
1032
|
}
|
|
974
|
-
|
|
1033
|
+
applyError(err);
|
|
975
1034
|
},
|
|
976
1035
|
(rejection) => {
|
|
977
|
-
if (asyncValidationVersionRef.current.get(
|
|
978
|
-
asyncValidationRef.current.delete(
|
|
979
|
-
asyncAbortRef.current.delete(
|
|
1036
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
1037
|
+
asyncValidationRef.current.delete(validationKey);
|
|
1038
|
+
asyncAbortRef.current.delete(validationKey);
|
|
980
1039
|
setValidatingFields((prev) => {
|
|
981
1040
|
const next = { ...prev };
|
|
982
|
-
delete next[
|
|
1041
|
+
delete next[validationKey];
|
|
983
1042
|
return next;
|
|
984
1043
|
});
|
|
985
1044
|
if (rejection && rejection.name === "AbortError") return;
|
|
986
|
-
|
|
1045
|
+
applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
|
|
987
1046
|
}
|
|
988
1047
|
);
|
|
989
|
-
asyncValidationRef.current.set(
|
|
990
|
-
setValidatingFields((prev) => ({ ...prev, [
|
|
1048
|
+
asyncValidationRef.current.set(validationKey, validationPromise);
|
|
1049
|
+
setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
|
|
991
1050
|
return validationPromise;
|
|
992
1051
|
},
|
|
993
|
-
[
|
|
1052
|
+
[fieldTypes, validationMessages]
|
|
994
1053
|
);
|
|
995
|
-
const
|
|
1054
|
+
const runAsyncValidation = useCallback(
|
|
996
1055
|
(name, value) => {
|
|
997
1056
|
const field = fieldByName.get(name);
|
|
998
|
-
if (!field || field.type === "repeater") return;
|
|
999
|
-
|
|
1057
|
+
if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
|
|
1058
|
+
return runAsyncValidationTarget({
|
|
1059
|
+
validationKey: name,
|
|
1060
|
+
field,
|
|
1061
|
+
value: value != null ? value : formValues[name],
|
|
1062
|
+
allValues: formValues,
|
|
1063
|
+
applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
|
|
1064
|
+
});
|
|
1065
|
+
},
|
|
1066
|
+
[fieldByName, formValues, runAsyncValidationTarget, updateErrors]
|
|
1067
|
+
);
|
|
1068
|
+
const triggerAsyncValidationTarget = useCallback(
|
|
1069
|
+
(target) => {
|
|
1070
|
+
if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
|
|
1071
|
+
const debounceMs = target.field.validateDebounce;
|
|
1000
1072
|
if (debounceMs && debounceMs > 0) {
|
|
1001
|
-
const existing = debounceTimersRef.current.get(
|
|
1073
|
+
const existing = debounceTimersRef.current.get(target.validationKey);
|
|
1002
1074
|
if (existing) clearTimeout(existing);
|
|
1003
1075
|
const timer = setTimeout(() => {
|
|
1004
|
-
debounceTimersRef.current.delete(
|
|
1005
|
-
|
|
1076
|
+
debounceTimersRef.current.delete(target.validationKey);
|
|
1077
|
+
runAsyncValidationTarget(target);
|
|
1006
1078
|
}, debounceMs);
|
|
1007
|
-
debounceTimersRef.current.set(
|
|
1079
|
+
debounceTimersRef.current.set(target.validationKey, timer);
|
|
1008
1080
|
} else {
|
|
1009
|
-
|
|
1081
|
+
runAsyncValidationTarget(target);
|
|
1010
1082
|
}
|
|
1011
1083
|
},
|
|
1012
|
-
[
|
|
1084
|
+
[runAsyncValidationTarget]
|
|
1085
|
+
);
|
|
1086
|
+
const triggerAsyncValidation = useCallback(
|
|
1087
|
+
(name, value) => {
|
|
1088
|
+
const field = fieldByName.get(name);
|
|
1089
|
+
if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
|
|
1090
|
+
triggerAsyncValidationTarget({
|
|
1091
|
+
validationKey: name,
|
|
1092
|
+
field,
|
|
1093
|
+
value: value != null ? value : formValuesRef.current[name],
|
|
1094
|
+
allValues: formValuesRef.current,
|
|
1095
|
+
applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
|
|
1096
|
+
});
|
|
1097
|
+
},
|
|
1098
|
+
[fieldByName, triggerAsyncValidationTarget, updateErrors]
|
|
1099
|
+
);
|
|
1100
|
+
const getAsyncValidationTargets = useCallback(
|
|
1101
|
+
(fieldSubset) => {
|
|
1102
|
+
const toValidate = fieldSubset || visibleFields;
|
|
1103
|
+
const targets = [];
|
|
1104
|
+
for (const field of toValidate) {
|
|
1105
|
+
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
1106
|
+
for (const item of field.items) {
|
|
1107
|
+
for (const subField of field.fields(item)) {
|
|
1108
|
+
if (subField.visible && !subField.visible(formValues)) continue;
|
|
1109
|
+
targets.push({
|
|
1110
|
+
validationKey: subField.name,
|
|
1111
|
+
field: subField,
|
|
1112
|
+
value: formValues[subField.name],
|
|
1113
|
+
allValues: formValues,
|
|
1114
|
+
applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (field.type === "repeater") {
|
|
1121
|
+
const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
|
|
1122
|
+
const subFields = field.fields || [];
|
|
1123
|
+
rows.forEach((row, rowIdx) => {
|
|
1124
|
+
const rowValues = { ...formValues, [field.name]: rows };
|
|
1125
|
+
subFields.forEach((subField) => {
|
|
1126
|
+
if (subField.visible && !subField.visible(rowValues)) return;
|
|
1127
|
+
targets.push({
|
|
1128
|
+
validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
|
|
1129
|
+
field: subField,
|
|
1130
|
+
value: row == null ? void 0 : row[subField.name],
|
|
1131
|
+
allValues: rowValues,
|
|
1132
|
+
applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
targets.push({
|
|
1139
|
+
validationKey: field.name,
|
|
1140
|
+
field,
|
|
1141
|
+
value: formValues[field.name],
|
|
1142
|
+
allValues: formValues,
|
|
1143
|
+
applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
return targets;
|
|
1147
|
+
},
|
|
1148
|
+
[visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
|
|
1013
1149
|
);
|
|
1014
1150
|
const commitValues = useCallback(
|
|
1015
1151
|
(nextValues) => {
|
|
1016
1152
|
formValuesRef.current = nextValues;
|
|
1017
1153
|
if (values != null) {
|
|
1154
|
+
controlledBaselineLockedRef.current = true;
|
|
1018
1155
|
if (onChange) onChange(nextValues);
|
|
1019
1156
|
} else {
|
|
1020
1157
|
setInternalValues(nextValues);
|
|
@@ -1032,7 +1169,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1032
1169
|
[commitValues]
|
|
1033
1170
|
);
|
|
1034
1171
|
const handleFieldChange = useCallback(
|
|
1035
|
-
(name, value) => {
|
|
1172
|
+
(name, value, options = {}) => {
|
|
1173
|
+
const { clearNestedErrors = true } = options;
|
|
1036
1174
|
const newValues = { ...formValuesRef.current, [name]: value };
|
|
1037
1175
|
const queue = [name];
|
|
1038
1176
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1073,9 +1211,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1073
1211
|
if (formErrorsRef.current[name] != null) {
|
|
1074
1212
|
clearedErrors[name] = null;
|
|
1075
1213
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1214
|
+
if (clearNestedErrors) {
|
|
1215
|
+
for (const key of Object.keys(formErrorsRef.current)) {
|
|
1216
|
+
if (key.startsWith(`${name}[`)) {
|
|
1217
|
+
clearedErrors[key] = null;
|
|
1218
|
+
}
|
|
1079
1219
|
}
|
|
1080
1220
|
}
|
|
1081
1221
|
draftValuesRef.current = newValues;
|
|
@@ -1144,7 +1284,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1144
1284
|
replaceErrors(errors);
|
|
1145
1285
|
return;
|
|
1146
1286
|
}
|
|
1147
|
-
const asyncSubmitValidations = allVisibleFields.map((
|
|
1287
|
+
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
1148
1288
|
if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
1149
1289
|
const pendingValidations = [
|
|
1150
1290
|
.../* @__PURE__ */ new Set([
|
|
@@ -1159,6 +1299,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1159
1299
|
const reset = () => {
|
|
1160
1300
|
const fresh = computeInitialValues();
|
|
1161
1301
|
if (values == null) setInternalValues(fresh);
|
|
1302
|
+
controlledBaselineLockedRef.current = false;
|
|
1162
1303
|
replaceErrors({});
|
|
1163
1304
|
initialSnapshot.current = deepClone(fresh);
|
|
1164
1305
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -1166,7 +1307,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1166
1307
|
const rawValues = {};
|
|
1167
1308
|
for (const key of Object.keys(formValues)) {
|
|
1168
1309
|
const f = fieldByName.get(key);
|
|
1169
|
-
if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
|
|
1310
|
+
if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
|
|
1170
1311
|
rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
|
|
1171
1312
|
}
|
|
1172
1313
|
for (const f of fields) {
|
|
@@ -1198,7 +1339,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1198
1339
|
if (controlledLoading == null) setInternalLoading(false);
|
|
1199
1340
|
}
|
|
1200
1341
|
},
|
|
1201
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName,
|
|
1342
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
1202
1343
|
);
|
|
1203
1344
|
const handleNext = useCallback(async () => {
|
|
1204
1345
|
if (!isMultiStep) return;
|
|
@@ -1210,7 +1351,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1210
1351
|
replaceErrors({ ...formErrorsRef.current, ...errors });
|
|
1211
1352
|
return;
|
|
1212
1353
|
}
|
|
1213
|
-
const asyncStepValidations = stepFields.map((
|
|
1354
|
+
const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
1214
1355
|
if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
1215
1356
|
const pendingValidations = [
|
|
1216
1357
|
.../* @__PURE__ */ new Set([
|
|
@@ -1235,7 +1376,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1235
1376
|
} else {
|
|
1236
1377
|
setInternalStep(nextStep);
|
|
1237
1378
|
}
|
|
1238
|
-
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields,
|
|
1379
|
+
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
|
|
1239
1380
|
const handleBack = useCallback(() => {
|
|
1240
1381
|
if (!isMultiStep) return;
|
|
1241
1382
|
const prevStep = Math.max(currentStep - 1, 0);
|
|
@@ -1267,6 +1408,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1267
1408
|
reset: () => {
|
|
1268
1409
|
const fresh = computeInitialValues();
|
|
1269
1410
|
if (values == null) setInternalValues(fresh);
|
|
1411
|
+
controlledBaselineLockedRef.current = false;
|
|
1270
1412
|
replaceErrors({});
|
|
1271
1413
|
initialSnapshot.current = deepClone(fresh);
|
|
1272
1414
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -1279,30 +1421,6 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1279
1421
|
replaceErrors(errors);
|
|
1280
1422
|
}
|
|
1281
1423
|
}));
|
|
1282
|
-
const setRepeaterSubFieldError = useCallback(
|
|
1283
|
-
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
1284
|
-
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
1285
|
-
const merged = { ...formErrorsRef.current };
|
|
1286
|
-
if (errorMessage) {
|
|
1287
|
-
merged[key] = errorMessage;
|
|
1288
|
-
} else {
|
|
1289
|
-
delete merged[key];
|
|
1290
|
-
}
|
|
1291
|
-
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
1292
|
-
const match = k.match(/\[(\d+)\]\./);
|
|
1293
|
-
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
1294
|
-
return { key: k, row };
|
|
1295
|
-
}).sort((a, b) => a.row - b.row);
|
|
1296
|
-
if (subErrors.length > 0) {
|
|
1297
|
-
const first = subErrors[0];
|
|
1298
|
-
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
1299
|
-
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
1300
|
-
delete merged[fieldName];
|
|
1301
|
-
}
|
|
1302
|
-
replaceErrors(merged);
|
|
1303
|
-
},
|
|
1304
|
-
[replaceErrors]
|
|
1305
|
-
);
|
|
1306
1424
|
const renderField = (field) => {
|
|
1307
1425
|
const fieldError = formErrors[field.name] || null;
|
|
1308
1426
|
const rendered = renderFieldInner(field);
|
|
@@ -1317,7 +1435,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1317
1435
|
const isReadOnly = field.readOnly || formReadOnly;
|
|
1318
1436
|
const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
|
|
1319
1437
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
1320
|
-
if (field.type === "display") {
|
|
1438
|
+
if (field.type === "display" || field.type === "slot") {
|
|
1321
1439
|
if (field.render) {
|
|
1322
1440
|
return field.render({
|
|
1323
1441
|
values: formValues,
|
|
@@ -1782,12 +1900,13 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1782
1900
|
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
1783
1901
|
const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
|
|
1784
1902
|
setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
|
|
1903
|
+
return err;
|
|
1785
1904
|
};
|
|
1786
1905
|
const handleSubFieldChange = (rowIdx, subField, subValue) => {
|
|
1787
1906
|
const updated = rows.map(
|
|
1788
1907
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
1789
1908
|
);
|
|
1790
|
-
handleFieldChange(field.name, updated);
|
|
1909
|
+
handleFieldChange(field.name, updated, { clearNestedErrors: false });
|
|
1791
1910
|
if (validateOnChange) {
|
|
1792
1911
|
validateSubField(rowIdx, subField, subValue, updated);
|
|
1793
1912
|
}
|
|
@@ -1797,13 +1916,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1797
1916
|
const nextRows = rows.map(
|
|
1798
1917
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
1799
1918
|
);
|
|
1800
|
-
validateSubField(rowIdx, subField, subValue, nextRows);
|
|
1919
|
+
const err = validateSubField(rowIdx, subField, subValue, nextRows);
|
|
1920
|
+
if (err) return;
|
|
1921
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
|
|
1922
|
+
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
1923
|
+
triggerAsyncValidationTarget({
|
|
1924
|
+
validationKey,
|
|
1925
|
+
field: subField,
|
|
1926
|
+
value: subValue,
|
|
1927
|
+
allValues: rowValues,
|
|
1928
|
+
applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
|
|
1929
|
+
});
|
|
1801
1930
|
};
|
|
1802
1931
|
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ React.createElement(Flex, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
|
|
1803
1932
|
const sfValue = row[sf.name];
|
|
1804
1933
|
const sfLabel = rowIdx === 0 ? sf.label : void 0;
|
|
1805
1934
|
const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
|
|
1806
1935
|
const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
|
|
1936
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
|
|
1807
1937
|
const sfProps = {
|
|
1808
1938
|
name: `${field.name}-${rowIdx}-${sf.name}`,
|
|
1809
1939
|
label: sfLabel,
|
|
@@ -1812,6 +1942,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1812
1942
|
disabled: resolveDisabled(sf, formValues) || isDisabled,
|
|
1813
1943
|
error: !!sfError,
|
|
1814
1944
|
validationMessage: sfError || void 0,
|
|
1945
|
+
...validatingFields[validationKey] ? { loading: true } : {},
|
|
1815
1946
|
...sf.fieldProps || {}
|
|
1816
1947
|
};
|
|
1817
1948
|
let sfElement;
|
|
@@ -1889,7 +2020,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1889
2020
|
const getFieldColSpan = (field) => {
|
|
1890
2021
|
if (field.colSpan != null) return Math.min(field.colSpan, columns);
|
|
1891
2022
|
if (field.width === "full" && columns > 1) return columns;
|
|
1892
|
-
if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
|
|
2023
|
+
if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
|
|
1893
2024
|
return 1;
|
|
1894
2025
|
};
|
|
1895
2026
|
const getDependents = (parentField) => visibleFields.filter(
|
|
@@ -1914,7 +2045,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1914
2045
|
const colSpan = (field) => {
|
|
1915
2046
|
if (field.colSpan != null) return Math.min(field.colSpan, cols);
|
|
1916
2047
|
if (field.width === "full" && cols > 1) return cols;
|
|
1917
|
-
if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
|
|
2048
|
+
if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
|
|
1918
2049
|
return 1;
|
|
1919
2050
|
};
|
|
1920
2051
|
const flushRow = () => {
|
|
@@ -2078,13 +2209,23 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2078
2209
|
const elements = [];
|
|
2079
2210
|
for (let i = 0; i < chunks.length; i++) {
|
|
2080
2211
|
const chunk = chunks[i];
|
|
2081
|
-
|
|
2212
|
+
const opts = chunk.group && groups && groups[chunk.group] || {};
|
|
2213
|
+
const showDivider = opts.showDivider !== false;
|
|
2214
|
+
const showLabel = opts.showLabel !== false;
|
|
2215
|
+
if (i > 0 && showDivider) {
|
|
2082
2216
|
elements.push(/* @__PURE__ */ React.createElement(Divider, { key: `group-div-${i}` }));
|
|
2083
2217
|
}
|
|
2084
|
-
if (chunk.group) {
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2218
|
+
if (chunk.group && showLabel) {
|
|
2219
|
+
if (typeof opts.renderHeader === "function") {
|
|
2220
|
+
const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
|
|
2221
|
+
if (header) elements.push(
|
|
2222
|
+
/* @__PURE__ */ React.createElement(React.Fragment, { key: `group-header-${i}` }, header)
|
|
2223
|
+
);
|
|
2224
|
+
} else {
|
|
2225
|
+
elements.push(
|
|
2226
|
+
/* @__PURE__ */ React.createElement(Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2088
2229
|
}
|
|
2089
2230
|
const chunkElements = renderFn(chunk.fields);
|
|
2090
2231
|
if (Array.isArray(chunkElements)) {
|