hs-uix 1.0.0 → 1.0.2
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 +730 -42
- package/dist/form.js +795 -153
- package/dist/form.mjs +795 -153
- package/dist/index.js +795 -153
- package/dist/index.mjs +795 -153
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1075,7 +1075,132 @@ var isValueEmpty = (value, field) => {
|
|
|
1075
1075
|
if ((field.type === "toggle" || field.type === "checkbox") && value === false) return true;
|
|
1076
1076
|
return false;
|
|
1077
1077
|
};
|
|
1078
|
-
var
|
|
1078
|
+
var isPromise = (value) => value && typeof value.then === "function";
|
|
1079
|
+
var isAsyncFunction = (fn) => fn && fn.constructor && fn.constructor.name === "AsyncFunction";
|
|
1080
|
+
var normalizeValidatorResult = (result) => {
|
|
1081
|
+
if (result === true || result === void 0 || result === null || result === false) return null;
|
|
1082
|
+
return String(result);
|
|
1083
|
+
};
|
|
1084
|
+
var isDateValueObject = (value) => isPlainObject(value) && Number.isInteger(value.year) && Number.isInteger(value.month) && Number.isInteger(value.date);
|
|
1085
|
+
var isTimeValueObject = (value) => isPlainObject(value) && Number.isInteger(value.hours) && Number.isInteger(value.minutes);
|
|
1086
|
+
var compareDateValues = (a, b) => {
|
|
1087
|
+
if (a.year !== b.year) return a.year - b.year;
|
|
1088
|
+
if (a.month !== b.month) return a.month - b.month;
|
|
1089
|
+
return a.date - b.date;
|
|
1090
|
+
};
|
|
1091
|
+
var compareTimeValues = (a, b) => {
|
|
1092
|
+
if (a.hours !== b.hours) return a.hours - b.hours;
|
|
1093
|
+
return a.minutes - b.minutes;
|
|
1094
|
+
};
|
|
1095
|
+
var runDefaultFieldValidator = (value, field, allValues) => {
|
|
1096
|
+
const errorPrefix = field.label || field.name;
|
|
1097
|
+
switch (field.type) {
|
|
1098
|
+
case "text":
|
|
1099
|
+
case "password":
|
|
1100
|
+
case "textarea":
|
|
1101
|
+
if (typeof value !== "string") return `${errorPrefix} must be text`;
|
|
1102
|
+
break;
|
|
1103
|
+
case "number":
|
|
1104
|
+
case "stepper":
|
|
1105
|
+
case "currency":
|
|
1106
|
+
if (typeof value !== "number" || Number.isNaN(value)) return `${errorPrefix} must be a number`;
|
|
1107
|
+
break;
|
|
1108
|
+
case "toggle":
|
|
1109
|
+
case "checkbox":
|
|
1110
|
+
if (typeof value !== "boolean") return `${errorPrefix} must be true or false`;
|
|
1111
|
+
break;
|
|
1112
|
+
case "select":
|
|
1113
|
+
case "radioGroup": {
|
|
1114
|
+
const options = resolveOptions(field, allValues);
|
|
1115
|
+
if (options.length > 0 && !options.some((o) => Object.is(o.value, value))) {
|
|
1116
|
+
return `${errorPrefix} has an invalid selection`;
|
|
1117
|
+
}
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
case "multiselect":
|
|
1121
|
+
case "checkboxGroup": {
|
|
1122
|
+
if (!Array.isArray(value)) return `${errorPrefix} must be a list`;
|
|
1123
|
+
const options = resolveOptions(field, allValues);
|
|
1124
|
+
if (options.length > 0) {
|
|
1125
|
+
const validValues = new Set(options.map((o) => o.value));
|
|
1126
|
+
const hasInvalid = value.some((item) => !validValues.has(item));
|
|
1127
|
+
if (hasInvalid) return `${errorPrefix} has an invalid selection`;
|
|
1128
|
+
}
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
case "date":
|
|
1132
|
+
if (!isDateValueObject(value)) return `${errorPrefix} has an invalid date`;
|
|
1133
|
+
break;
|
|
1134
|
+
case "time":
|
|
1135
|
+
if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
|
|
1136
|
+
break;
|
|
1137
|
+
case "datetime": {
|
|
1138
|
+
if (isDateValueObject(value)) break;
|
|
1139
|
+
if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
|
|
1140
|
+
const hasDate = value.date !== void 0;
|
|
1141
|
+
const hasTime = value.time !== void 0;
|
|
1142
|
+
if (!hasDate && !hasTime) return `${errorPrefix} has an invalid date/time`;
|
|
1143
|
+
if (hasDate && !isDateValueObject(value.date)) return `${errorPrefix} has an invalid date`;
|
|
1144
|
+
if (hasTime && !isTimeValueObject(value.time)) return `${errorPrefix} has an invalid time`;
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
case "repeater":
|
|
1148
|
+
if (!Array.isArray(value)) return `${errorPrefix} has invalid rows`;
|
|
1149
|
+
break;
|
|
1150
|
+
default:
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
return null;
|
|
1154
|
+
};
|
|
1155
|
+
var runCustomSyncValidators = (value, field, allValues) => {
|
|
1156
|
+
const validators = Array.isArray(field.validators) ? field.validators : [];
|
|
1157
|
+
for (const validator of validators) {
|
|
1158
|
+
if (isAsyncFunction(validator)) continue;
|
|
1159
|
+
try {
|
|
1160
|
+
const result = validator(value, allValues);
|
|
1161
|
+
if (isPromise(result)) continue;
|
|
1162
|
+
const err = normalizeValidatorResult(result);
|
|
1163
|
+
if (err) return err;
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
return (err == null ? void 0 : err.message) || "Validation failed";
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (field.validate && !isAsyncFunction(field.validate)) {
|
|
1169
|
+
try {
|
|
1170
|
+
const result = field.validate(value, allValues);
|
|
1171
|
+
if (!isPromise(result)) {
|
|
1172
|
+
const err = normalizeValidatorResult(result);
|
|
1173
|
+
if (err) return err;
|
|
1174
|
+
}
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
return (err == null ? void 0 : err.message) || "Validation failed";
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return null;
|
|
1180
|
+
};
|
|
1181
|
+
var collectAsyncValidatorPromises = (value, field, allValues, context) => {
|
|
1182
|
+
const promises = [];
|
|
1183
|
+
const validators = Array.isArray(field.validators) ? field.validators : [];
|
|
1184
|
+
for (const validator of validators) {
|
|
1185
|
+
try {
|
|
1186
|
+
const result = validator(value, allValues, context);
|
|
1187
|
+
if (isPromise(result)) promises.push(result);
|
|
1188
|
+
} catch (err) {
|
|
1189
|
+
promises.push(Promise.reject(err));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (field.validate) {
|
|
1193
|
+
try {
|
|
1194
|
+
const result = field.validate(value, allValues, context);
|
|
1195
|
+
if (isPromise(result)) promises.push(result);
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
promises.push(Promise.reject(err));
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return promises;
|
|
1201
|
+
};
|
|
1202
|
+
var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
|
|
1203
|
+
const includeCustomValidators = options.includeCustomValidators !== false;
|
|
1079
1204
|
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
|
|
1080
1205
|
const isRequired = resolveRequired(field, allValues);
|
|
1081
1206
|
const plugin = fieldTypes && fieldTypes[field.type];
|
|
@@ -1084,6 +1209,10 @@ var runValidators = (value, field, allValues, fieldTypes) => {
|
|
|
1084
1209
|
return `${field.label} is required`;
|
|
1085
1210
|
}
|
|
1086
1211
|
if (empty) return null;
|
|
1212
|
+
if (field.useDefaultValidators !== false) {
|
|
1213
|
+
const typeError = runDefaultFieldValidator(value, field, allValues);
|
|
1214
|
+
if (typeError) return typeError;
|
|
1215
|
+
}
|
|
1087
1216
|
if (field.pattern && typeof value === "string") {
|
|
1088
1217
|
if (!field.pattern.test(value)) {
|
|
1089
1218
|
return field.patternMessage || "Invalid format";
|
|
@@ -1105,10 +1234,25 @@ var runValidators = (value, field, allValues, fieldTypes) => {
|
|
|
1105
1234
|
return `Must be no more than ${field.max}`;
|
|
1106
1235
|
}
|
|
1107
1236
|
}
|
|
1108
|
-
if (field.
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1237
|
+
if (field.type === "date" && isDateValueObject(value)) {
|
|
1238
|
+
if (field.min && isDateValueObject(field.min) && compareDateValues(value, field.min) < 0) {
|
|
1239
|
+
return field.minValidationMessage || "Date is too early";
|
|
1240
|
+
}
|
|
1241
|
+
if (field.max && isDateValueObject(field.max) && compareDateValues(value, field.max) > 0) {
|
|
1242
|
+
return field.maxValidationMessage || "Date is too late";
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (field.type === "time" && isTimeValueObject(value)) {
|
|
1246
|
+
if (field.min && isTimeValueObject(field.min) && compareTimeValues(value, field.min) < 0) {
|
|
1247
|
+
return field.minValidationMessage || "Time is too early";
|
|
1248
|
+
}
|
|
1249
|
+
if (field.max && isTimeValueObject(field.max) && compareTimeValues(value, field.max) > 0) {
|
|
1250
|
+
return field.maxValidationMessage || "Time is too late";
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (includeCustomValidators) {
|
|
1254
|
+
const customError = runCustomSyncValidators(value, field, allValues);
|
|
1255
|
+
if (customError) return customError;
|
|
1112
1256
|
}
|
|
1113
1257
|
return null;
|
|
1114
1258
|
};
|
|
@@ -1120,6 +1264,58 @@ var resolveOptions = (field, allValues) => {
|
|
|
1120
1264
|
if (typeof field.options === "function") return field.options(allValues);
|
|
1121
1265
|
return field.options || [];
|
|
1122
1266
|
};
|
|
1267
|
+
var getDependsOnName = (field) => field.dependsOnConfig && field.dependsOnConfig.field;
|
|
1268
|
+
var getDependsOnDisplay = (field) => field.dependsOnConfig && field.dependsOnConfig.display || "grouped";
|
|
1269
|
+
var getDependsOnLabel = (field) => field.dependsOnConfig && field.dependsOnConfig.label;
|
|
1270
|
+
var getDependsOnMessage = (field) => field.dependsOnConfig && field.dependsOnConfig.message;
|
|
1271
|
+
var getRepeaterErrorKey = (fieldName, rowIdx, subFieldName) => `${fieldName}[${rowIdx}].${subFieldName}`;
|
|
1272
|
+
var isPlainObject = (value) => Object.prototype.toString.call(value) === "[object Object]";
|
|
1273
|
+
var deepEqual = (a, b) => {
|
|
1274
|
+
if (Object.is(a, b)) return true;
|
|
1275
|
+
if (typeof a !== typeof b) return false;
|
|
1276
|
+
if (a == null || b == null) return false;
|
|
1277
|
+
if (Array.isArray(a)) {
|
|
1278
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
1279
|
+
for (let i = 0; i < a.length; i++) {
|
|
1280
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
1281
|
+
}
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
if (a instanceof Date && b instanceof Date) {
|
|
1285
|
+
return a.getTime() === b.getTime();
|
|
1286
|
+
}
|
|
1287
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
1288
|
+
const aKeys = Object.keys(a);
|
|
1289
|
+
const bKeys = Object.keys(b);
|
|
1290
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
1291
|
+
for (const key of aKeys) {
|
|
1292
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
1293
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
1294
|
+
}
|
|
1295
|
+
return true;
|
|
1296
|
+
}
|
|
1297
|
+
return false;
|
|
1298
|
+
};
|
|
1299
|
+
var fieldSetHasErrors = (errors, fields) => {
|
|
1300
|
+
if (!errors || !fields || fields.length === 0) return false;
|
|
1301
|
+
const names = new Set(fields.map((field) => field.name));
|
|
1302
|
+
return Object.keys(errors).some((errorKey) => {
|
|
1303
|
+
const base = errorKey.split("[")[0];
|
|
1304
|
+
return names.has(base);
|
|
1305
|
+
});
|
|
1306
|
+
};
|
|
1307
|
+
var deepClone = (value) => {
|
|
1308
|
+
if (Array.isArray(value)) return value.map(deepClone);
|
|
1309
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
1310
|
+
if (isPlainObject(value)) {
|
|
1311
|
+
const next = {};
|
|
1312
|
+
for (const key of Object.keys(value)) {
|
|
1313
|
+
next[key] = deepClone(value[key]);
|
|
1314
|
+
}
|
|
1315
|
+
return next;
|
|
1316
|
+
}
|
|
1317
|
+
return value;
|
|
1318
|
+
};
|
|
1123
1319
|
var useFormPrefill = (properties, mapping) => {
|
|
1124
1320
|
return (0, import_react2.useMemo)(() => {
|
|
1125
1321
|
if (!properties) return {};
|
|
@@ -1189,28 +1385,30 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1189
1385
|
// validate current step fields before Next
|
|
1190
1386
|
} = props;
|
|
1191
1387
|
const {
|
|
1192
|
-
|
|
1193
|
-
// submit
|
|
1388
|
+
labels,
|
|
1389
|
+
// { submit, cancel, back, next } — i18n label object
|
|
1194
1390
|
submitVariant = "primary",
|
|
1195
1391
|
// submit button variant
|
|
1196
1392
|
showCancel = false,
|
|
1197
1393
|
// show cancel button
|
|
1198
|
-
cancelLabel = "Cancel",
|
|
1199
|
-
// cancel button text
|
|
1200
1394
|
onCancel,
|
|
1201
1395
|
// () => void
|
|
1202
1396
|
submitPosition = "bottom",
|
|
1203
1397
|
// "bottom" | "none"
|
|
1204
1398
|
loading: controlledLoading,
|
|
1205
1399
|
// controlled loading state
|
|
1206
|
-
disabled = false
|
|
1400
|
+
disabled = false,
|
|
1207
1401
|
// disable entire form
|
|
1402
|
+
renderButtons: renderButtonsProp
|
|
1403
|
+
// custom action row renderer
|
|
1208
1404
|
} = props;
|
|
1209
1405
|
const {
|
|
1210
1406
|
columns = 1,
|
|
1211
1407
|
// number of grid columns (1 = full-width stack)
|
|
1212
1408
|
columnWidth,
|
|
1213
1409
|
// AutoGrid columnWidth — responsive layout (overrides columns)
|
|
1410
|
+
maxColumns,
|
|
1411
|
+
// cap number of columns per row in AutoGrid mode
|
|
1214
1412
|
layout,
|
|
1215
1413
|
// explicit row layout array (overrides columns + columnWidth)
|
|
1216
1414
|
sections,
|
|
@@ -1221,6 +1419,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1221
1419
|
// show * on required fields
|
|
1222
1420
|
noFormWrapper = false,
|
|
1223
1421
|
// skip HubSpot <Form> wrapper
|
|
1422
|
+
autoComplete,
|
|
1423
|
+
// form autoComplete attribute
|
|
1424
|
+
formProps,
|
|
1425
|
+
// pass-through props for Form wrapper
|
|
1224
1426
|
fieldTypes
|
|
1225
1427
|
// Record<string, FieldTypePlugin> — custom field type registry
|
|
1226
1428
|
} = props;
|
|
@@ -1231,8 +1433,12 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1231
1433
|
// string — form-level success alert
|
|
1232
1434
|
readOnly: formReadOnly = false,
|
|
1233
1435
|
// boolean — lock all fields
|
|
1234
|
-
readOnlyMessage
|
|
1436
|
+
readOnlyMessage,
|
|
1235
1437
|
// string — warning alert when readOnly
|
|
1438
|
+
alerts,
|
|
1439
|
+
// { addAlert, readOnlyTitle, errorTitle, successTitle }
|
|
1440
|
+
errors: controlledErrors
|
|
1441
|
+
// controlled validation errors
|
|
1236
1442
|
} = props;
|
|
1237
1443
|
const {
|
|
1238
1444
|
onDirtyChange,
|
|
@@ -1240,6 +1446,38 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1240
1446
|
autoSave
|
|
1241
1447
|
// { debounce: number, onAutoSave: (values) => void }
|
|
1242
1448
|
} = props;
|
|
1449
|
+
const submitButtonLabel = (labels == null ? void 0 : labels.submit) || "Submit";
|
|
1450
|
+
const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
|
|
1451
|
+
const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
|
|
1452
|
+
const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
|
|
1453
|
+
const addAlert = alerts == null ? void 0 : alerts.addAlert;
|
|
1454
|
+
const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
|
|
1455
|
+
const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
|
|
1456
|
+
const successTitle = (alerts == null ? void 0 : alerts.successTitle) || "Success";
|
|
1457
|
+
const prevErrorRef = (0, import_react2.useRef)(formError);
|
|
1458
|
+
const prevSuccessRef = (0, import_react2.useRef)(formSuccess);
|
|
1459
|
+
(0, import_react2.useEffect)(() => {
|
|
1460
|
+
if (!addAlert) return;
|
|
1461
|
+
if (formError && formError !== prevErrorRef.current) {
|
|
1462
|
+
addAlert({
|
|
1463
|
+
type: "danger",
|
|
1464
|
+
title: errorTitle,
|
|
1465
|
+
message: typeof formError === "string" ? formError : void 0
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
prevErrorRef.current = formError;
|
|
1469
|
+
}, [addAlert, formError, errorTitle]);
|
|
1470
|
+
(0, import_react2.useEffect)(() => {
|
|
1471
|
+
if (!addAlert) return;
|
|
1472
|
+
if (formSuccess && formSuccess !== prevSuccessRef.current) {
|
|
1473
|
+
addAlert({
|
|
1474
|
+
type: "success",
|
|
1475
|
+
title: successTitle,
|
|
1476
|
+
message: formSuccess
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
prevSuccessRef.current = formSuccess;
|
|
1480
|
+
}, [addAlert, formSuccess, successTitle]);
|
|
1243
1481
|
const computeInitialValues = () => {
|
|
1244
1482
|
const vals = {};
|
|
1245
1483
|
for (const field of fields) {
|
|
@@ -1255,25 +1493,99 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1255
1493
|
const [internalErrors, setInternalErrors] = (0, import_react2.useState)({});
|
|
1256
1494
|
const [internalStep, setInternalStep] = (0, import_react2.useState)(0);
|
|
1257
1495
|
const [internalLoading, setInternalLoading] = (0, import_react2.useState)(false);
|
|
1258
|
-
const [touchedFields, setTouchedFields] = (0, import_react2.useState)({});
|
|
1259
1496
|
const [validatingFields, setValidatingFields] = (0, import_react2.useState)({});
|
|
1260
1497
|
const asyncValidationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1498
|
+
const asyncAbortRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1499
|
+
const asyncValidationVersionRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1261
1500
|
const debounceTimersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1501
|
+
const inputDebounceRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1502
|
+
const rowKeyRef = (0, import_react2.useRef)(/* @__PURE__ */ new WeakMap());
|
|
1503
|
+
const rowKeyCounterRef = (0, import_react2.useRef)(0);
|
|
1262
1504
|
const initialSnapshot = (0, import_react2.useRef)(null);
|
|
1263
1505
|
if (initialSnapshot.current === null) {
|
|
1264
|
-
initialSnapshot.current =
|
|
1506
|
+
initialSnapshot.current = deepClone(computeInitialValues());
|
|
1265
1507
|
}
|
|
1266
1508
|
const formValues = values != null ? values : internalValues;
|
|
1509
|
+
const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
|
|
1267
1510
|
const currentStep = controlledStep != null ? controlledStep : internalStep;
|
|
1268
1511
|
const isLoading = controlledLoading != null ? controlledLoading : internalLoading;
|
|
1269
1512
|
const isMultiStep = Array.isArray(steps) && steps.length > 0;
|
|
1513
|
+
const formValuesRef = (0, import_react2.useRef)(formValues);
|
|
1514
|
+
const formErrorsRef = (0, import_react2.useRef)(formErrors);
|
|
1515
|
+
const draftValuesRef = (0, import_react2.useRef)(null);
|
|
1516
|
+
formValuesRef.current = formValues;
|
|
1517
|
+
formErrorsRef.current = formErrors;
|
|
1518
|
+
const fieldByName = (0, import_react2.useMemo)(() => {
|
|
1519
|
+
const map = /* @__PURE__ */ new Map();
|
|
1520
|
+
for (const field of fields) map.set(field.name, field);
|
|
1521
|
+
return map;
|
|
1522
|
+
}, [fields]);
|
|
1523
|
+
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
1524
|
+
const configWarningsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1525
|
+
const warnConfig = (0, import_react2.useCallback)((message) => {
|
|
1526
|
+
if (!isDev) return;
|
|
1527
|
+
if (configWarningsRef.current.has(message)) return;
|
|
1528
|
+
configWarningsRef.current.add(message);
|
|
1529
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
1530
|
+
console.warn(`[FormBuilder] ${message}`);
|
|
1531
|
+
}
|
|
1532
|
+
}, [isDev]);
|
|
1533
|
+
const replaceErrors = (0, import_react2.useCallback)(
|
|
1534
|
+
(nextErrors) => {
|
|
1535
|
+
if (controlledErrors == null) setInternalErrors(nextErrors);
|
|
1536
|
+
if (onValidationChange) onValidationChange(nextErrors);
|
|
1537
|
+
},
|
|
1538
|
+
[controlledErrors, onValidationChange]
|
|
1539
|
+
);
|
|
1540
|
+
const updateErrors = (0, import_react2.useCallback)(
|
|
1541
|
+
(newErrors) => {
|
|
1542
|
+
const mergeErrors = (base) => {
|
|
1543
|
+
const merged = { ...base, ...newErrors };
|
|
1544
|
+
for (const key of Object.keys(newErrors)) {
|
|
1545
|
+
if (newErrors[key] === null || newErrors[key] === void 0) {
|
|
1546
|
+
delete merged[key];
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return merged;
|
|
1550
|
+
};
|
|
1551
|
+
if (controlledErrors != null) {
|
|
1552
|
+
const merged = mergeErrors(formErrorsRef.current || {});
|
|
1553
|
+
if (onValidationChange) onValidationChange(merged);
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
setInternalErrors((prev) => {
|
|
1557
|
+
const merged = mergeErrors(prev);
|
|
1558
|
+
if (onValidationChange) onValidationChange(merged);
|
|
1559
|
+
return merged;
|
|
1560
|
+
});
|
|
1561
|
+
},
|
|
1562
|
+
[controlledErrors, onValidationChange]
|
|
1563
|
+
);
|
|
1564
|
+
const getFieldEmptyValue = (0, import_react2.useCallback)(
|
|
1565
|
+
(field) => {
|
|
1566
|
+
const plugin = fieldTypes && fieldTypes[field.type];
|
|
1567
|
+
return plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
|
|
1568
|
+
},
|
|
1569
|
+
[fieldTypes]
|
|
1570
|
+
);
|
|
1571
|
+
const getRowKey = (0, import_react2.useCallback)((fieldName, row, index) => {
|
|
1572
|
+
if (!row || typeof row !== "object") return `${fieldName}-idx-${index}`;
|
|
1573
|
+
if (!rowKeyRef.current.has(row)) {
|
|
1574
|
+
rowKeyCounterRef.current += 1;
|
|
1575
|
+
rowKeyRef.current.set(row, `${fieldName}-row-${rowKeyCounterRef.current}`);
|
|
1576
|
+
}
|
|
1577
|
+
return rowKeyRef.current.get(row);
|
|
1578
|
+
}, []);
|
|
1270
1579
|
(0, import_react2.useEffect)(() => {
|
|
1271
1580
|
return () => {
|
|
1272
1581
|
for (const timer of debounceTimersRef.current.values()) clearTimeout(timer);
|
|
1582
|
+
for (const timer of inputDebounceRef.current.values()) clearTimeout(timer);
|
|
1583
|
+
for (const controller of asyncAbortRef.current.values()) controller.abort();
|
|
1584
|
+
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1273
1585
|
};
|
|
1274
1586
|
}, []);
|
|
1275
1587
|
const isDirty = (0, import_react2.useMemo)(() => {
|
|
1276
|
-
return
|
|
1588
|
+
return !deepEqual(formValues, initialSnapshot.current);
|
|
1277
1589
|
}, [formValues]);
|
|
1278
1590
|
const prevDirtyRef = (0, import_react2.useRef)(false);
|
|
1279
1591
|
(0, import_react2.useEffect)(() => {
|
|
@@ -1283,36 +1595,129 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1283
1595
|
}
|
|
1284
1596
|
}, [isDirty, onDirtyChange]);
|
|
1285
1597
|
const autoSaveTimerRef = (0, import_react2.useRef)(null);
|
|
1598
|
+
const autoSaveRef = (0, import_react2.useRef)(autoSave);
|
|
1599
|
+
autoSaveRef.current = autoSave;
|
|
1600
|
+
const prevAutoSaveValues = (0, import_react2.useRef)(deepClone(formValues));
|
|
1286
1601
|
(0, import_react2.useEffect)(() => {
|
|
1287
|
-
|
|
1602
|
+
const cfg = autoSaveRef.current;
|
|
1603
|
+
if (!cfg || !cfg.onAutoSave || !isDirty) return;
|
|
1604
|
+
if (deepEqual(prevAutoSaveValues.current, formValues)) return;
|
|
1605
|
+
prevAutoSaveValues.current = deepClone(formValues);
|
|
1288
1606
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1289
1607
|
autoSaveTimerRef.current = setTimeout(() => {
|
|
1290
1608
|
autoSaveTimerRef.current = null;
|
|
1291
|
-
|
|
1292
|
-
},
|
|
1609
|
+
autoSaveRef.current.onAutoSave(formValues);
|
|
1610
|
+
}, cfg.debounce || 1e3);
|
|
1293
1611
|
return () => {
|
|
1294
1612
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1295
1613
|
};
|
|
1296
|
-
}, [formValues, isDirty
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1614
|
+
}, [formValues, isDirty]);
|
|
1615
|
+
const allVisibleFields = (0, import_react2.useMemo)(() => {
|
|
1616
|
+
return fields.filter((f) => {
|
|
1299
1617
|
if (f.visible && !f.visible(formValues)) return false;
|
|
1300
1618
|
return true;
|
|
1301
1619
|
});
|
|
1620
|
+
}, [fields, formValues]);
|
|
1621
|
+
const visibleFields = (0, import_react2.useMemo)(() => {
|
|
1622
|
+
let filtered = allVisibleFields;
|
|
1302
1623
|
if (isMultiStep && steps[currentStep] && steps[currentStep].fields) {
|
|
1303
1624
|
const stepFieldNames = new Set(steps[currentStep].fields);
|
|
1304
1625
|
filtered = filtered.filter((f) => stepFieldNames.has(f.name));
|
|
1305
1626
|
}
|
|
1306
1627
|
return filtered;
|
|
1307
|
-
}, [
|
|
1628
|
+
}, [allVisibleFields, isMultiStep, steps, currentStep]);
|
|
1629
|
+
(0, import_react2.useEffect)(() => {
|
|
1630
|
+
const nameSet = new Set(fields.map((f) => f.name));
|
|
1631
|
+
if (nameSet.size !== fields.length) {
|
|
1632
|
+
warnConfig("Duplicate field names detected. Field names must be unique.");
|
|
1633
|
+
}
|
|
1634
|
+
for (const field of fields) {
|
|
1635
|
+
const parentName = getDependsOnName(field);
|
|
1636
|
+
if (parentName && !nameSet.has(parentName)) {
|
|
1637
|
+
warnConfig(`Field "${field.name}" depends on missing field "${parentName}".`);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (steps) {
|
|
1641
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1642
|
+
const step = steps[i];
|
|
1643
|
+
if (!step.fields) continue;
|
|
1644
|
+
for (const fieldName of step.fields) {
|
|
1645
|
+
if (!nameSet.has(fieldName)) {
|
|
1646
|
+
warnConfig(`Step ${i + 1} references missing field "${fieldName}".`);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (layout) {
|
|
1652
|
+
for (const row of layout) {
|
|
1653
|
+
for (const entry of row) {
|
|
1654
|
+
const fieldName = typeof entry === "string" ? entry : entry.field;
|
|
1655
|
+
if (!nameSet.has(fieldName)) {
|
|
1656
|
+
warnConfig(`Layout references missing field "${fieldName}".`);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (sections) {
|
|
1662
|
+
for (const section of sections) {
|
|
1663
|
+
for (const fieldName of section.fields || []) {
|
|
1664
|
+
if (!nameSet.has(fieldName)) {
|
|
1665
|
+
warnConfig(`Section "${section.id}" references missing field "${fieldName}".`);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}, [fields, steps, layout, sections, warnConfig]);
|
|
1671
|
+
const validateRepeaterField = (0, import_react2.useCallback)(
|
|
1672
|
+
(field, value, allValues) => {
|
|
1673
|
+
const errors = {};
|
|
1674
|
+
const rows = Array.isArray(value) ? value : [];
|
|
1675
|
+
const subFields = field.fields || [];
|
|
1676
|
+
let firstSubError = null;
|
|
1677
|
+
if (resolveRequired(field, allValues) && rows.length === 0) {
|
|
1678
|
+
const requiredError = `${field.label} is required`;
|
|
1679
|
+
errors[field.name] = requiredError;
|
|
1680
|
+
return { errors, hasErrors: true };
|
|
1681
|
+
}
|
|
1682
|
+
if (typeof field.min === "number" && rows.length < field.min) {
|
|
1683
|
+
errors[field.name] = `Must have at least ${field.min} ${field.min === 1 ? "row" : "rows"}`;
|
|
1684
|
+
} else if (typeof field.max === "number" && rows.length > field.max) {
|
|
1685
|
+
errors[field.name] = `Must have no more than ${field.max} ${field.max === 1 ? "row" : "rows"}`;
|
|
1686
|
+
}
|
|
1687
|
+
rows.forEach((row, rowIdx) => {
|
|
1688
|
+
const rowValues = { ...allValues, [field.name]: rows };
|
|
1689
|
+
subFields.forEach((subField) => {
|
|
1690
|
+
if (subField.visible && !subField.visible(rowValues)) return;
|
|
1691
|
+
const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
|
|
1692
|
+
if (!err) return;
|
|
1693
|
+
const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
|
|
1694
|
+
errors[key] = err;
|
|
1695
|
+
if (!firstSubError) firstSubError = { row: rowIdx + 1, message: err };
|
|
1696
|
+
});
|
|
1697
|
+
});
|
|
1698
|
+
if (!errors[field.name] && firstSubError) {
|
|
1699
|
+
errors[field.name] = `Row ${firstSubError.row}: ${firstSubError.message}`;
|
|
1700
|
+
}
|
|
1701
|
+
return { errors, hasErrors: Object.keys(errors).length > 0 };
|
|
1702
|
+
},
|
|
1703
|
+
[fieldTypes]
|
|
1704
|
+
);
|
|
1308
1705
|
const validateField = (0, import_react2.useCallback)(
|
|
1309
1706
|
(name, value) => {
|
|
1310
|
-
const field =
|
|
1707
|
+
const field = fieldByName.get(name);
|
|
1311
1708
|
if (!field) return null;
|
|
1312
1709
|
if (field.visible && !field.visible(formValues)) return null;
|
|
1710
|
+
if (field.type === "repeater") {
|
|
1711
|
+
const repeaterResult = validateRepeaterField(
|
|
1712
|
+
field,
|
|
1713
|
+
value != null ? value : formValues[name],
|
|
1714
|
+
formValues
|
|
1715
|
+
);
|
|
1716
|
+
return repeaterResult.errors[name] || null;
|
|
1717
|
+
}
|
|
1313
1718
|
return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
|
|
1314
1719
|
},
|
|
1315
|
-
[
|
|
1720
|
+
[fieldByName, formValues, validateRepeaterField, fieldTypes]
|
|
1316
1721
|
);
|
|
1317
1722
|
const validateVisibleFields = (0, import_react2.useCallback)(
|
|
1318
1723
|
(fieldSubset) => {
|
|
@@ -1320,6 +1725,14 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1320
1725
|
const errors = {};
|
|
1321
1726
|
let hasErrors = false;
|
|
1322
1727
|
for (const field of toValidate) {
|
|
1728
|
+
if (field.type === "repeater") {
|
|
1729
|
+
const repeaterResult = validateRepeaterField(field, formValues[field.name], formValues);
|
|
1730
|
+
if (repeaterResult.hasErrors) {
|
|
1731
|
+
Object.assign(errors, repeaterResult.errors);
|
|
1732
|
+
hasErrors = true;
|
|
1733
|
+
}
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1323
1736
|
const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
|
|
1324
1737
|
if (err) {
|
|
1325
1738
|
errors[field.name] = err;
|
|
@@ -1328,64 +1741,87 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1328
1741
|
}
|
|
1329
1742
|
return { errors, hasErrors };
|
|
1330
1743
|
},
|
|
1331
|
-
[visibleFields, formValues]
|
|
1332
|
-
);
|
|
1333
|
-
const updateErrors = (0, import_react2.useCallback)(
|
|
1334
|
-
(newErrors) => {
|
|
1335
|
-
setInternalErrors((prev) => {
|
|
1336
|
-
const merged = { ...prev, ...newErrors };
|
|
1337
|
-
for (const key of Object.keys(merged)) {
|
|
1338
|
-
if (newErrors[key] === null || newErrors[key] === void 0) {
|
|
1339
|
-
delete merged[key];
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
if (onValidationChange) onValidationChange(merged);
|
|
1343
|
-
return merged;
|
|
1344
|
-
});
|
|
1345
|
-
},
|
|
1346
|
-
[onValidationChange]
|
|
1744
|
+
[visibleFields, formValues, validateRepeaterField, fieldTypes]
|
|
1347
1745
|
);
|
|
1348
1746
|
const runAsyncValidation = (0, import_react2.useCallback)(
|
|
1349
1747
|
(name, value) => {
|
|
1350
|
-
const field =
|
|
1351
|
-
if (!field ||
|
|
1748
|
+
const field = fieldByName.get(name);
|
|
1749
|
+
if (!field || field.type === "repeater") return null;
|
|
1352
1750
|
const val = value != null ? value : formValues[name];
|
|
1353
|
-
const syncError = runValidators(val, field, formValues, fieldTypes);
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
(
|
|
1359
|
-
|
|
1751
|
+
const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
|
|
1752
|
+
const prevController = asyncAbortRef.current.get(name);
|
|
1753
|
+
if (prevController) prevController.abort();
|
|
1754
|
+
asyncAbortRef.current.delete(name);
|
|
1755
|
+
setValidatingFields((prev) => {
|
|
1756
|
+
if (!prev[name]) return prev;
|
|
1757
|
+
const next = { ...prev };
|
|
1758
|
+
delete next[name];
|
|
1759
|
+
return next;
|
|
1760
|
+
});
|
|
1761
|
+
if (syncError) return null;
|
|
1762
|
+
const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
|
|
1763
|
+
asyncValidationVersionRef.current.set(name, version);
|
|
1764
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
1765
|
+
if (controller) asyncAbortRef.current.set(name, controller);
|
|
1766
|
+
let asyncPromises;
|
|
1767
|
+
try {
|
|
1768
|
+
asyncPromises = collectAsyncValidatorPromises(
|
|
1769
|
+
val,
|
|
1770
|
+
field,
|
|
1771
|
+
formValues,
|
|
1772
|
+
controller ? { signal: controller.signal } : void 0
|
|
1773
|
+
);
|
|
1774
|
+
} catch (err) {
|
|
1775
|
+
updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1778
|
+
if (asyncPromises.length === 0) {
|
|
1779
|
+
asyncAbortRef.current.delete(name);
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
const validationPromise = Promise.all(asyncPromises).then(
|
|
1783
|
+
(results) => {
|
|
1784
|
+
if (asyncValidationVersionRef.current.get(name) !== version) return;
|
|
1360
1785
|
asyncValidationRef.current.delete(name);
|
|
1786
|
+
asyncAbortRef.current.delete(name);
|
|
1361
1787
|
setValidatingFields((prev) => {
|
|
1362
1788
|
const next = { ...prev };
|
|
1363
1789
|
delete next[name];
|
|
1364
1790
|
return next;
|
|
1365
1791
|
});
|
|
1366
|
-
|
|
1792
|
+
let err = null;
|
|
1793
|
+
for (const result of results) {
|
|
1794
|
+
const normalized = normalizeValidatorResult(result);
|
|
1795
|
+
if (normalized) {
|
|
1796
|
+
err = normalized;
|
|
1797
|
+
break;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1367
1800
|
updateErrors({ [name]: err });
|
|
1368
1801
|
},
|
|
1369
1802
|
(rejection) => {
|
|
1370
|
-
if (
|
|
1803
|
+
if (asyncValidationVersionRef.current.get(name) !== version) return;
|
|
1371
1804
|
asyncValidationRef.current.delete(name);
|
|
1805
|
+
asyncAbortRef.current.delete(name);
|
|
1372
1806
|
setValidatingFields((prev) => {
|
|
1373
1807
|
const next = { ...prev };
|
|
1374
1808
|
delete next[name];
|
|
1375
1809
|
return next;
|
|
1376
1810
|
});
|
|
1811
|
+
if (rejection && rejection.name === "AbortError") return;
|
|
1377
1812
|
updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
|
|
1378
1813
|
}
|
|
1379
1814
|
);
|
|
1380
1815
|
asyncValidationRef.current.set(name, validationPromise);
|
|
1381
1816
|
setValidatingFields((prev) => ({ ...prev, [name]: true }));
|
|
1817
|
+
return validationPromise;
|
|
1382
1818
|
},
|
|
1383
|
-
[
|
|
1819
|
+
[fieldByName, formValues, fieldTypes, updateErrors]
|
|
1384
1820
|
);
|
|
1385
1821
|
const triggerAsyncValidation = (0, import_react2.useCallback)(
|
|
1386
1822
|
(name, value) => {
|
|
1387
|
-
const field =
|
|
1388
|
-
if (!field ||
|
|
1823
|
+
const field = fieldByName.get(name);
|
|
1824
|
+
if (!field || field.type === "repeater") return;
|
|
1389
1825
|
const debounceMs = field.validateDebounce;
|
|
1390
1826
|
if (debounceMs && debounceMs > 0) {
|
|
1391
1827
|
const existing = debounceTimersRef.current.get(name);
|
|
@@ -1399,44 +1835,93 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1399
1835
|
runAsyncValidation(name, value);
|
|
1400
1836
|
}
|
|
1401
1837
|
},
|
|
1402
|
-
[
|
|
1838
|
+
[fieldByName, runAsyncValidation]
|
|
1403
1839
|
);
|
|
1404
|
-
const
|
|
1405
|
-
(
|
|
1840
|
+
const commitValues = (0, import_react2.useCallback)(
|
|
1841
|
+
(nextValues) => {
|
|
1842
|
+
formValuesRef.current = nextValues;
|
|
1406
1843
|
if (values != null) {
|
|
1407
|
-
if (onChange) onChange(
|
|
1844
|
+
if (onChange) onChange(nextValues);
|
|
1408
1845
|
} else {
|
|
1409
|
-
setInternalValues(
|
|
1846
|
+
setInternalValues(nextValues);
|
|
1410
1847
|
}
|
|
1411
1848
|
},
|
|
1412
|
-
[values, onChange
|
|
1849
|
+
[values, onChange]
|
|
1850
|
+
);
|
|
1851
|
+
const setFieldValueSilent = (0, import_react2.useCallback)(
|
|
1852
|
+
(name, value) => {
|
|
1853
|
+
const base = draftValuesRef.current || formValuesRef.current || {};
|
|
1854
|
+
const nextValues = { ...base, [name]: value };
|
|
1855
|
+
draftValuesRef.current = nextValues;
|
|
1856
|
+
commitValues(nextValues);
|
|
1857
|
+
},
|
|
1858
|
+
[commitValues]
|
|
1413
1859
|
);
|
|
1414
1860
|
const handleFieldChange = (0, import_react2.useCallback)(
|
|
1415
1861
|
(name, value) => {
|
|
1416
|
-
const newValues = { ...
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1862
|
+
const newValues = { ...formValuesRef.current, [name]: value };
|
|
1863
|
+
const queue = [name];
|
|
1864
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1865
|
+
const clearedErrors = {};
|
|
1866
|
+
while (queue.length > 0) {
|
|
1867
|
+
const current = queue.shift();
|
|
1868
|
+
if (!current || visited.has(current)) continue;
|
|
1869
|
+
visited.add(current);
|
|
1870
|
+
fields.forEach((dep) => {
|
|
1871
|
+
const parentName = getDependsOnName(dep);
|
|
1872
|
+
if (parentName !== current || dep.name === current) return;
|
|
1873
|
+
if (!dep.options) return;
|
|
1874
|
+
const depOptions = resolveOptions(dep, newValues);
|
|
1875
|
+
const depValue = newValues[dep.name];
|
|
1876
|
+
if (depValue == null || depValue === "") return;
|
|
1877
|
+
const validValues = new Set(depOptions.map((o) => o.value));
|
|
1878
|
+
let nextDepValue = depValue;
|
|
1879
|
+
let changed = false;
|
|
1880
|
+
if (Array.isArray(depValue)) {
|
|
1881
|
+
const filtered = depValue.filter((v) => validValues.has(v));
|
|
1882
|
+
if (filtered.length !== depValue.length) {
|
|
1883
|
+
nextDepValue = filtered;
|
|
1884
|
+
changed = true;
|
|
1885
|
+
}
|
|
1886
|
+
} else if (!validValues.has(depValue)) {
|
|
1887
|
+
nextDepValue = getFieldEmptyValue(dep);
|
|
1888
|
+
changed = true;
|
|
1889
|
+
}
|
|
1890
|
+
if (changed) {
|
|
1891
|
+
newValues[dep.name] = nextDepValue;
|
|
1892
|
+
queue.push(dep.name);
|
|
1893
|
+
if (formErrorsRef.current[dep.name] != null) {
|
|
1894
|
+
clearedErrors[dep.name] = null;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1421
1898
|
}
|
|
1422
|
-
if (
|
|
1423
|
-
|
|
1424
|
-
|
|
1899
|
+
if (formErrorsRef.current[name] != null) {
|
|
1900
|
+
clearedErrors[name] = null;
|
|
1901
|
+
}
|
|
1902
|
+
for (const key of Object.keys(formErrorsRef.current)) {
|
|
1903
|
+
if (key.startsWith(`${name}[`)) {
|
|
1904
|
+
clearedErrors[key] = null;
|
|
1905
|
+
}
|
|
1425
1906
|
}
|
|
1426
|
-
|
|
1907
|
+
draftValuesRef.current = newValues;
|
|
1908
|
+
commitValues(newValues);
|
|
1909
|
+
if (onFieldChange) onFieldChange(name, value, newValues);
|
|
1910
|
+
if (Object.keys(clearedErrors).length > 0) updateErrors(clearedErrors);
|
|
1911
|
+
const field = fieldByName.get(name);
|
|
1427
1912
|
if (field && field.onFieldChange) {
|
|
1428
1913
|
field.onFieldChange(value, newValues, {
|
|
1429
1914
|
setFieldValue: setFieldValueSilent,
|
|
1430
1915
|
setFieldError: (fieldName, message) => updateErrors({ [fieldName]: message })
|
|
1431
1916
|
});
|
|
1432
1917
|
}
|
|
1918
|
+
draftValuesRef.current = null;
|
|
1433
1919
|
},
|
|
1434
|
-
[
|
|
1920
|
+
[fields, getFieldEmptyValue, commitValues, onFieldChange, updateErrors, fieldByName, setFieldValueSilent]
|
|
1435
1921
|
);
|
|
1436
|
-
const inputDebounceRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1437
1922
|
const handleDebouncedFieldChange = (0, import_react2.useCallback)(
|
|
1438
1923
|
(name, value) => {
|
|
1439
|
-
const field =
|
|
1924
|
+
const field = fieldByName.get(name);
|
|
1440
1925
|
const debounceMs = field && field.debounce;
|
|
1441
1926
|
if (debounceMs && debounceMs > 0) {
|
|
1442
1927
|
const existing = inputDebounceRef.current.get(name);
|
|
@@ -1450,58 +1935,59 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1450
1935
|
handleFieldChange(name, value);
|
|
1451
1936
|
}
|
|
1452
1937
|
},
|
|
1453
|
-
[
|
|
1938
|
+
[fieldByName, handleFieldChange]
|
|
1454
1939
|
);
|
|
1455
1940
|
const handleFieldInput = (0, import_react2.useCallback)(
|
|
1456
1941
|
(name, value) => {
|
|
1457
|
-
if (validateOnChange)
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1942
|
+
if (!validateOnChange) return;
|
|
1943
|
+
const err = validateField(name, value);
|
|
1944
|
+
updateErrors({ [name]: err });
|
|
1461
1945
|
},
|
|
1462
1946
|
[validateOnChange, validateField, updateErrors]
|
|
1463
1947
|
);
|
|
1464
1948
|
const handleFieldBlur = (0, import_react2.useCallback)(
|
|
1465
1949
|
(name, value) => {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
}
|
|
1950
|
+
if (!validateOnBlur) return;
|
|
1951
|
+
const resolvedValue = value != null ? value : formValuesRef.current[name];
|
|
1952
|
+
const err = validateField(name, resolvedValue);
|
|
1953
|
+
updateErrors({ [name]: err });
|
|
1954
|
+
if (!err) {
|
|
1955
|
+
triggerAsyncValidation(name, resolvedValue);
|
|
1473
1956
|
}
|
|
1474
1957
|
},
|
|
1475
|
-
[validateOnBlur, validateField, updateErrors,
|
|
1958
|
+
[validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
|
|
1476
1959
|
);
|
|
1477
1960
|
const handleSubmit = (0, import_react2.useCallback)(
|
|
1478
1961
|
async (e) => {
|
|
1479
1962
|
if (e && e.preventDefault) e.preventDefault();
|
|
1480
1963
|
if (validateOnSubmit) {
|
|
1481
|
-
const
|
|
1482
|
-
const { errors, hasErrors } = validateVisibleFields(allVisible);
|
|
1964
|
+
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
1483
1965
|
if (hasErrors) {
|
|
1484
|
-
|
|
1485
|
-
if (onValidationChange) onValidationChange(errors);
|
|
1966
|
+
replaceErrors(errors);
|
|
1486
1967
|
return;
|
|
1487
1968
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1969
|
+
const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
|
|
1970
|
+
if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
1971
|
+
const pendingValidations = [
|
|
1972
|
+
.../* @__PURE__ */ new Set([
|
|
1973
|
+
...asyncSubmitValidations,
|
|
1974
|
+
...Array.from(asyncValidationRef.current.values())
|
|
1975
|
+
])
|
|
1976
|
+
];
|
|
1977
|
+
await Promise.all(pendingValidations);
|
|
1978
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) return;
|
|
1493
1979
|
}
|
|
1494
1980
|
}
|
|
1495
1981
|
const reset = () => {
|
|
1496
1982
|
const fresh = computeInitialValues();
|
|
1497
1983
|
if (values == null) setInternalValues(fresh);
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1984
|
+
replaceErrors({});
|
|
1985
|
+
initialSnapshot.current = deepClone(fresh);
|
|
1986
|
+
prevAutoSaveValues.current = deepClone(fresh);
|
|
1501
1987
|
};
|
|
1502
1988
|
const rawValues = {};
|
|
1503
1989
|
for (const key of Object.keys(formValues)) {
|
|
1504
|
-
const f =
|
|
1990
|
+
const f = fieldByName.get(key);
|
|
1505
1991
|
if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
|
|
1506
1992
|
rawValues[key] = formValues[key];
|
|
1507
1993
|
}
|
|
@@ -1525,25 +2011,34 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1525
2011
|
if (controlledLoading == null) setInternalLoading(false);
|
|
1526
2012
|
}
|
|
1527
2013
|
},
|
|
1528
|
-
[validateOnSubmit,
|
|
2014
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
|
|
1529
2015
|
);
|
|
1530
|
-
const handleNext = (0, import_react2.useCallback)(() => {
|
|
2016
|
+
const handleNext = (0, import_react2.useCallback)(async () => {
|
|
1531
2017
|
if (!isMultiStep) return;
|
|
1532
2018
|
if (validateStepOnNext && steps[currentStep] && steps[currentStep].fields) {
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
);
|
|
2019
|
+
const stepFieldNames = new Set(steps[currentStep].fields);
|
|
2020
|
+
const stepFields = allVisibleFields.filter((f) => stepFieldNames.has(f.name));
|
|
1536
2021
|
const { errors, hasErrors } = validateVisibleFields(stepFields);
|
|
1537
2022
|
if (hasErrors) {
|
|
1538
|
-
|
|
1539
|
-
if (onValidationChange) onValidationChange({ ...internalErrors, ...errors });
|
|
2023
|
+
replaceErrors({ ...formErrorsRef.current, ...errors });
|
|
1540
2024
|
return;
|
|
1541
2025
|
}
|
|
2026
|
+
const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
|
|
2027
|
+
if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2028
|
+
const pendingValidations = [
|
|
2029
|
+
.../* @__PURE__ */ new Set([
|
|
2030
|
+
...asyncStepValidations,
|
|
2031
|
+
...Array.from(asyncValidationRef.current.values())
|
|
2032
|
+
])
|
|
2033
|
+
];
|
|
2034
|
+
await Promise.all(pendingValidations);
|
|
2035
|
+
if (fieldSetHasErrors(formErrorsRef.current, stepFields)) return;
|
|
2036
|
+
}
|
|
1542
2037
|
}
|
|
1543
2038
|
if (steps[currentStep] && steps[currentStep].validate) {
|
|
1544
2039
|
const result = steps[currentStep].validate(formValues);
|
|
1545
2040
|
if (result !== true && result) {
|
|
1546
|
-
|
|
2041
|
+
replaceErrors({ ...formErrorsRef.current, ...result });
|
|
1547
2042
|
return;
|
|
1548
2043
|
}
|
|
1549
2044
|
}
|
|
@@ -1553,7 +2048,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1553
2048
|
} else {
|
|
1554
2049
|
setInternalStep(nextStep);
|
|
1555
2050
|
}
|
|
1556
|
-
}, [isMultiStep, validateStepOnNext, steps, currentStep,
|
|
2051
|
+
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
|
|
1557
2052
|
const handleBack = (0, import_react2.useCallback)(() => {
|
|
1558
2053
|
if (!isMultiStep) return;
|
|
1559
2054
|
const prevStep = Math.max(currentStep - 1, 0);
|
|
@@ -1578,33 +2073,56 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1578
2073
|
(0, import_react2.useImperativeHandle)(ref, () => ({
|
|
1579
2074
|
submit: handleSubmit,
|
|
1580
2075
|
validate: () => {
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
setInternalErrors(errors);
|
|
2076
|
+
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
2077
|
+
replaceErrors(errors);
|
|
1584
2078
|
return { valid: !hasErrors, errors };
|
|
1585
2079
|
},
|
|
1586
2080
|
reset: () => {
|
|
1587
2081
|
const fresh = computeInitialValues();
|
|
1588
2082
|
if (values == null) setInternalValues(fresh);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
2083
|
+
replaceErrors({});
|
|
2084
|
+
initialSnapshot.current = deepClone(fresh);
|
|
2085
|
+
prevAutoSaveValues.current = deepClone(fresh);
|
|
1592
2086
|
},
|
|
1593
2087
|
getValues: () => formValues,
|
|
1594
2088
|
isDirty: () => isDirty,
|
|
1595
2089
|
setFieldValue: (name, value) => handleFieldChange(name, value),
|
|
1596
2090
|
setFieldError: (name, message) => updateErrors({ [name]: message }),
|
|
1597
2091
|
setErrors: (errors) => {
|
|
1598
|
-
|
|
1599
|
-
if (onValidationChange) onValidationChange(errors);
|
|
2092
|
+
replaceErrors(errors);
|
|
1600
2093
|
}
|
|
1601
2094
|
}));
|
|
2095
|
+
const setRepeaterSubFieldError = (0, import_react2.useCallback)(
|
|
2096
|
+
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
2097
|
+
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
2098
|
+
const merged = { ...formErrorsRef.current };
|
|
2099
|
+
if (errorMessage) {
|
|
2100
|
+
merged[key] = errorMessage;
|
|
2101
|
+
} else {
|
|
2102
|
+
delete merged[key];
|
|
2103
|
+
}
|
|
2104
|
+
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
2105
|
+
const match = k.match(/\[(\d+)\]\./);
|
|
2106
|
+
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
2107
|
+
return { key: k, row };
|
|
2108
|
+
}).sort((a, b) => a.row - b.row);
|
|
2109
|
+
if (subErrors.length > 0) {
|
|
2110
|
+
const first = subErrors[0];
|
|
2111
|
+
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
2112
|
+
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
2113
|
+
delete merged[fieldName];
|
|
2114
|
+
}
|
|
2115
|
+
replaceErrors(merged);
|
|
2116
|
+
},
|
|
2117
|
+
[replaceErrors]
|
|
2118
|
+
);
|
|
1602
2119
|
const renderField = (field) => {
|
|
1603
2120
|
const fieldValue = formValues[field.name];
|
|
1604
|
-
const fieldError =
|
|
2121
|
+
const fieldError = formErrors[field.name] || null;
|
|
1605
2122
|
const hasError = !!fieldError;
|
|
1606
2123
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
1607
|
-
const isReadOnly = field.readOnly ||
|
|
2124
|
+
const isReadOnly = field.readOnly || formReadOnly;
|
|
2125
|
+
const isDisabled = disabled || field.disabled || formReadOnly;
|
|
1608
2126
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
1609
2127
|
if (field.type === "display") {
|
|
1610
2128
|
if (field.render) {
|
|
@@ -1663,6 +2181,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1663
2181
|
tooltip: field.tooltip,
|
|
1664
2182
|
required: isRequired,
|
|
1665
2183
|
readOnly: isReadOnly,
|
|
2184
|
+
disabled: isDisabled,
|
|
1666
2185
|
error: hasError,
|
|
1667
2186
|
validationMessage: fieldError || void 0,
|
|
1668
2187
|
...field.loading || validatingFields[field.name] ? { loading: true } : {},
|
|
@@ -1795,6 +2314,9 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1795
2314
|
maxValidationMessage: field.maxValidationMessage,
|
|
1796
2315
|
onChange: (v) => {
|
|
1797
2316
|
handleFieldChange(field.name, { ...fieldValue, date: v, time: timeVal });
|
|
2317
|
+
},
|
|
2318
|
+
onBlur: (v) => {
|
|
2319
|
+
handleFieldBlur(field.name, { ...fieldValue, date: v, time: timeVal });
|
|
1798
2320
|
}
|
|
1799
2321
|
}
|
|
1800
2322
|
)), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -1805,12 +2327,16 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1805
2327
|
description: field.description,
|
|
1806
2328
|
tooltip: field.tooltip,
|
|
1807
2329
|
readOnly: isReadOnly,
|
|
2330
|
+
disabled: isDisabled,
|
|
1808
2331
|
error: hasError,
|
|
1809
2332
|
value: timeVal,
|
|
1810
2333
|
interval: field.interval,
|
|
1811
2334
|
timezone: field.timezone,
|
|
1812
2335
|
onChange: (v) => {
|
|
1813
2336
|
handleFieldChange(field.name, { ...fieldValue, date: dateVal, time: v });
|
|
2337
|
+
},
|
|
2338
|
+
onBlur: (v) => {
|
|
2339
|
+
handleFieldBlur(field.name, { ...fieldValue, date: dateVal, time: v });
|
|
1814
2340
|
}
|
|
1815
2341
|
}
|
|
1816
2342
|
)));
|
|
@@ -1848,6 +2374,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1848
2374
|
textChecked: field.textChecked,
|
|
1849
2375
|
textUnchecked: field.textUnchecked,
|
|
1850
2376
|
readonly: isReadOnly,
|
|
2377
|
+
disabled: isDisabled,
|
|
1851
2378
|
onChange: fieldOnChange,
|
|
1852
2379
|
...field.fieldProps || {}
|
|
1853
2380
|
}
|
|
@@ -1860,6 +2387,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1860
2387
|
checked: !!fieldValue,
|
|
1861
2388
|
description: field.description,
|
|
1862
2389
|
readOnly: isReadOnly,
|
|
2390
|
+
disabled: isDisabled,
|
|
1863
2391
|
inline: field.inline,
|
|
1864
2392
|
variant: field.variant,
|
|
1865
2393
|
onChange: fieldOnChange,
|
|
@@ -1896,61 +2424,140 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1896
2424
|
case "repeater": {
|
|
1897
2425
|
const rows = Array.isArray(fieldValue) ? fieldValue : [];
|
|
1898
2426
|
const subFields = field.fields || [];
|
|
1899
|
-
const minRows = field.min
|
|
1900
|
-
const maxRows = field.max
|
|
1901
|
-
const
|
|
1902
|
-
const
|
|
2427
|
+
const minRows = typeof field.min === "number" ? field.min : 0;
|
|
2428
|
+
const maxRows = typeof field.max === "number" ? field.max : Infinity;
|
|
2429
|
+
const repeaterProps = field.repeaterProps || {};
|
|
2430
|
+
const renderAddControl = repeaterProps.renderAdd;
|
|
2431
|
+
const renderRemoveControl = repeaterProps.renderRemove;
|
|
2432
|
+
const renderMoveUpControl = repeaterProps.renderMoveUp;
|
|
2433
|
+
const renderMoveDownControl = repeaterProps.renderMoveDown;
|
|
2434
|
+
const addLabel = repeaterProps.addLabel || "Add";
|
|
2435
|
+
const removeLabel = repeaterProps.removeLabel || "Remove";
|
|
2436
|
+
const moveUpLabel = repeaterProps.moveUpLabel || "Up";
|
|
2437
|
+
const moveDownLabel = repeaterProps.moveDownLabel || "Down";
|
|
2438
|
+
const canEditRows = !isReadOnly && !isDisabled;
|
|
2439
|
+
const canAdd = rows.length < maxRows && canEditRows;
|
|
2440
|
+
const canRemove = rows.length > minRows && canEditRows;
|
|
2441
|
+
const canReorder = !!repeaterProps.reorderable && canEditRows;
|
|
2442
|
+
const repeaterHasNestedErrors = Object.keys(formErrors).some(
|
|
2443
|
+
(k) => k.startsWith(`${field.name}[`)
|
|
2444
|
+
);
|
|
2445
|
+
const firstNestedErrorKey = Object.keys(formErrors).find(
|
|
2446
|
+
(k) => k.startsWith(`${field.name}[`)
|
|
2447
|
+
);
|
|
2448
|
+
const repeaterErrorMessage = fieldError || (firstNestedErrorKey ? formErrors[firstNestedErrorKey] : null);
|
|
2449
|
+
const repeaterHasError = !!fieldError || repeaterHasNestedErrors;
|
|
1903
2450
|
const addRow = () => {
|
|
1904
2451
|
const emptyRow = {};
|
|
1905
2452
|
for (const sf of subFields) {
|
|
1906
|
-
emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue :
|
|
2453
|
+
emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue : getFieldEmptyValue(sf);
|
|
1907
2454
|
}
|
|
1908
2455
|
handleFieldChange(field.name, [...rows, emptyRow]);
|
|
1909
2456
|
};
|
|
1910
2457
|
const removeRow = (idx) => {
|
|
1911
2458
|
handleFieldChange(field.name, rows.filter((_, i) => i !== idx));
|
|
1912
2459
|
};
|
|
1913
|
-
const
|
|
2460
|
+
const moveRow = (fromIndex, toIndex) => {
|
|
2461
|
+
if (toIndex < 0 || toIndex >= rows.length || toIndex === fromIndex) return;
|
|
2462
|
+
const updated = [...rows];
|
|
2463
|
+
const [moved] = updated.splice(fromIndex, 1);
|
|
2464
|
+
updated.splice(toIndex, 0, moved);
|
|
2465
|
+
handleFieldChange(field.name, updated);
|
|
2466
|
+
};
|
|
2467
|
+
const validateSubField = (rowIdx, subField, subValue, nextRows) => {
|
|
2468
|
+
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
2469
|
+
const err = runValidators(subValue, subField, rowValues, fieldTypes);
|
|
2470
|
+
setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
|
|
2471
|
+
};
|
|
2472
|
+
const handleSubFieldChange = (rowIdx, subField, subValue) => {
|
|
1914
2473
|
const updated = rows.map(
|
|
1915
|
-
(row, i) => i ===
|
|
2474
|
+
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
1916
2475
|
);
|
|
1917
2476
|
handleFieldChange(field.name, updated);
|
|
2477
|
+
if (validateOnChange) {
|
|
2478
|
+
validateSubField(rowIdx, subField, subValue, updated);
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
const handleSubFieldBlur = (rowIdx, subField, subValue) => {
|
|
2482
|
+
if (!validateOnBlur) return;
|
|
2483
|
+
const nextRows = rows.map(
|
|
2484
|
+
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2485
|
+
);
|
|
2486
|
+
validateSubField(rowIdx, subField, subValue, nextRows);
|
|
1918
2487
|
};
|
|
1919
|
-
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: rowIdx, direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
|
|
2488
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
|
|
1920
2489
|
const sfValue = row[sf.name];
|
|
1921
2490
|
const sfLabel = rowIdx === 0 ? sf.label : void 0;
|
|
1922
|
-
const sfOptions = resolveOptions(sf, formValues);
|
|
2491
|
+
const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
|
|
2492
|
+
const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
|
|
1923
2493
|
const sfProps = {
|
|
1924
2494
|
name: `${field.name}-${rowIdx}-${sf.name}`,
|
|
1925
2495
|
label: sfLabel,
|
|
1926
2496
|
placeholder: sf.placeholder,
|
|
1927
|
-
readOnly: isReadOnly,
|
|
2497
|
+
readOnly: sf.readOnly || isReadOnly,
|
|
2498
|
+
disabled: sf.disabled || isDisabled,
|
|
2499
|
+
error: !!sfError,
|
|
2500
|
+
validationMessage: sfError || void 0,
|
|
1928
2501
|
...sf.fieldProps || {}
|
|
1929
2502
|
};
|
|
1930
2503
|
let sfElement;
|
|
1931
2504
|
switch (sf.type) {
|
|
1932
2505
|
case "select":
|
|
1933
|
-
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2506
|
+
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2507
|
+
import_ui_extensions2.Select,
|
|
2508
|
+
{
|
|
2509
|
+
...sfProps,
|
|
2510
|
+
value: sfValue,
|
|
2511
|
+
options: sfOptions,
|
|
2512
|
+
onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
|
|
2513
|
+
onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
|
|
2514
|
+
}
|
|
2515
|
+
);
|
|
1934
2516
|
break;
|
|
1935
2517
|
case "number":
|
|
1936
|
-
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2518
|
+
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2519
|
+
import_ui_extensions2.NumberInput,
|
|
2520
|
+
{
|
|
2521
|
+
...sfProps,
|
|
2522
|
+
value: sfValue,
|
|
2523
|
+
onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
|
|
2524
|
+
onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
|
|
2525
|
+
}
|
|
2526
|
+
);
|
|
1937
2527
|
break;
|
|
1938
2528
|
case "checkbox":
|
|
1939
|
-
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2529
|
+
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2530
|
+
import_ui_extensions2.Checkbox,
|
|
2531
|
+
{
|
|
2532
|
+
...sfProps,
|
|
2533
|
+
checked: !!sfValue,
|
|
2534
|
+
onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
|
|
2535
|
+
onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
|
|
2536
|
+
},
|
|
2537
|
+
sf.label
|
|
2538
|
+
);
|
|
1940
2539
|
break;
|
|
1941
2540
|
default:
|
|
1942
|
-
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2541
|
+
sfElement = /* @__PURE__ */ import_react2.default.createElement(
|
|
2542
|
+
import_ui_extensions2.Input,
|
|
2543
|
+
{
|
|
2544
|
+
...sfProps,
|
|
2545
|
+
value: sfValue || "",
|
|
2546
|
+
onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
|
|
2547
|
+
onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
|
|
2548
|
+
}
|
|
2549
|
+
);
|
|
1943
2550
|
}
|
|
1944
2551
|
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { key: sf.name, flex: 1 }, sfElement);
|
|
1945
|
-
}), canRemove && /* @__PURE__ */ import_react2.default.createElement(
|
|
2552
|
+
}), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "xs" }, canReorder && rowIdx > 0 && (renderMoveUpControl ? renderMoveUpControl({ index: rowIdx, onClick: () => moveRow(rowIdx, rowIdx - 1) }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", size: "sm", onClick: () => moveRow(rowIdx, rowIdx - 1) }, moveUpLabel)), canReorder && rowIdx < rows.length - 1 && (renderMoveDownControl ? renderMoveDownControl({ index: rowIdx, onClick: () => moveRow(rowIdx, rowIdx + 1) }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", size: "sm", onClick: () => moveRow(rowIdx, rowIdx + 1) }, moveDownLabel)), canRemove && (renderRemoveControl ? renderRemoveControl({ index: rowIdx, onClick: () => removeRow(rowIdx) }) : /* @__PURE__ */ import_react2.default.createElement(
|
|
1946
2553
|
import_ui_extensions2.Button,
|
|
1947
2554
|
{
|
|
1948
2555
|
variant: "secondary",
|
|
1949
|
-
size: "
|
|
2556
|
+
size: "md",
|
|
1950
2557
|
onClick: () => removeRow(rowIdx)
|
|
1951
2558
|
},
|
|
1952
|
-
|
|
1953
|
-
))), canAdd && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.
|
|
2559
|
+
removeLabel
|
|
2560
|
+
))))), canAdd && (renderAddControl ? renderAddControl({ onClick: addRow, count: rows.length }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: addRow }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "flush" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "add" }), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, addLabel)))), repeaterHasError && repeaterErrorMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, repeaterErrorMessage));
|
|
1954
2561
|
}
|
|
1955
2562
|
default:
|
|
1956
2563
|
return /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -1970,15 +2577,17 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1970
2577
|
if (field.width === "full" && columns > 1) return columns;
|
|
1971
2578
|
return 1;
|
|
1972
2579
|
};
|
|
1973
|
-
const getDependents = (parentField) => visibleFields.filter(
|
|
1974
|
-
|
|
2580
|
+
const getDependents = (parentField) => visibleFields.filter(
|
|
2581
|
+
(f) => getDependsOnName(f) === parentField.name && f.name !== parentField.name && getDependsOnDisplay(f) === "grouped"
|
|
2582
|
+
);
|
|
2583
|
+
const isDependent = (field) => getDependsOnName(field) && getDependsOnDisplay(field) === "grouped" && visibleFields.some((f) => f.name === getDependsOnName(field) && f.name !== field.name);
|
|
1975
2584
|
const renderDependentGroup = (parentField, dependents) => {
|
|
1976
|
-
const firstWithLabel = dependents.find((f) => f
|
|
1977
|
-
const firstWithMessage = dependents.find((f) => f
|
|
1978
|
-
const groupLabel = firstWithLabel
|
|
1979
|
-
const rawMessage = firstWithMessage
|
|
2585
|
+
const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
|
|
2586
|
+
const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
|
|
2587
|
+
const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
|
|
2588
|
+
const rawMessage = getDependsOnMessage(firstWithMessage);
|
|
1980
2589
|
const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
|
|
1981
|
-
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, tooltipMessage) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info" })))), dependents
|
|
2590
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, tooltipMessage) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info" })))), renderFieldSubset(dependents)));
|
|
1982
2591
|
};
|
|
1983
2592
|
const renderGridLayout = (fieldSubset) => {
|
|
1984
2593
|
const fieldList = fieldSubset || visibleFields;
|
|
@@ -2071,7 +2680,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2071
2680
|
let i = 0;
|
|
2072
2681
|
while (i < fieldList.length) {
|
|
2073
2682
|
const field = fieldList[i];
|
|
2074
|
-
if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !field
|
|
2683
|
+
if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
|
|
2075
2684
|
rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
|
|
2076
2685
|
i += 2;
|
|
2077
2686
|
} else {
|
|
@@ -2107,9 +2716,12 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2107
2716
|
let batch = [];
|
|
2108
2717
|
const flushBatch = () => {
|
|
2109
2718
|
if (batch.length === 0) return;
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2719
|
+
const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
|
|
2720
|
+
for (const chunk of chunks) {
|
|
2721
|
+
elements.push(
|
|
2722
|
+
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: f.name }, renderField(f))))
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2113
2725
|
batch = [];
|
|
2114
2726
|
};
|
|
2115
2727
|
for (const field of fieldList) {
|
|
@@ -2192,7 +2804,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2192
2804
|
);
|
|
2193
2805
|
if (sec.info) {
|
|
2194
2806
|
elements.push(
|
|
2195
|
-
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: sec.id, direction: "row", align: "start", gap: "flush" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, accordion), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, sec.info) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info", size: "sm", screenReaderText: sec.info })))
|
|
2807
|
+
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: sec.id, direction: "row", align: "start", justify: "start", gap: "flush" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, accordion), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { variant: "dark", overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, sec.info) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info", size: "sm", screenReaderText: sec.info })))
|
|
2196
2808
|
);
|
|
2197
2809
|
} else {
|
|
2198
2810
|
elements.push(accordion);
|
|
@@ -2220,8 +2832,30 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2220
2832
|
if (submitPosition === "none" || formReadOnly) return null;
|
|
2221
2833
|
const isLastStep = !isMultiStep || currentStep === steps.length - 1;
|
|
2222
2834
|
const isFirstStep = !isMultiStep || currentStep === 0;
|
|
2835
|
+
const buttonContext = {
|
|
2836
|
+
isMultiStep,
|
|
2837
|
+
isFirstStep,
|
|
2838
|
+
isLastStep,
|
|
2839
|
+
currentStep,
|
|
2840
|
+
totalSteps: isMultiStep ? steps.length : 1,
|
|
2841
|
+
disabled,
|
|
2842
|
+
loading: isLoading,
|
|
2843
|
+
labels: {
|
|
2844
|
+
submit: submitButtonLabel,
|
|
2845
|
+
cancel: cancelButtonLabel,
|
|
2846
|
+
back: backButtonLabel,
|
|
2847
|
+
next: nextButtonLabel
|
|
2848
|
+
},
|
|
2849
|
+
onBack: handleBack,
|
|
2850
|
+
onNext: handleNext,
|
|
2851
|
+
onCancel,
|
|
2852
|
+
onSubmit: handleSubmit
|
|
2853
|
+
};
|
|
2854
|
+
if (renderButtonsProp) {
|
|
2855
|
+
return renderButtonsProp(buttonContext);
|
|
2856
|
+
}
|
|
2223
2857
|
if (isMultiStep) {
|
|
2224
|
-
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: handleBack, disabled },
|
|
2858
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, " "), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ import_react2.default.createElement(
|
|
2225
2859
|
import_ui_extensions2.LoadingButton,
|
|
2226
2860
|
{
|
|
2227
2861
|
variant: submitVariant,
|
|
@@ -2229,10 +2863,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2229
2863
|
onClick: handleSubmit,
|
|
2230
2864
|
disabled
|
|
2231
2865
|
},
|
|
2232
|
-
|
|
2233
|
-
) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "primary", onClick: handleNext, disabled },
|
|
2866
|
+
submitButtonLabel
|
|
2867
|
+
) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
|
|
2234
2868
|
}
|
|
2235
|
-
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: onCancel, disabled },
|
|
2869
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ import_react2.default.createElement(
|
|
2236
2870
|
import_ui_extensions2.LoadingButton,
|
|
2237
2871
|
{
|
|
2238
2872
|
variant: submitVariant,
|
|
@@ -2241,7 +2875,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2241
2875
|
onClick: noFormWrapper ? handleSubmit : void 0,
|
|
2242
2876
|
disabled
|
|
2243
2877
|
},
|
|
2244
|
-
|
|
2878
|
+
submitButtonLabel
|
|
2245
2879
|
));
|
|
2246
2880
|
};
|
|
2247
2881
|
const formContent = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, isMultiStep && showStepIndicator && /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -2250,7 +2884,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2250
2884
|
currentStep,
|
|
2251
2885
|
stepNames: steps.map((s) => s.title)
|
|
2252
2886
|
}
|
|
2253
|
-
), formReadOnly && readOnlyMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title:
|
|
2887
|
+
), formReadOnly && readOnlyMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
|
|
2254
2888
|
values: formValues,
|
|
2255
2889
|
goNext: handleNext,
|
|
2256
2890
|
goBack: handleBack,
|
|
@@ -2262,7 +2896,15 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2262
2896
|
if (noFormWrapper) {
|
|
2263
2897
|
return formContent;
|
|
2264
2898
|
}
|
|
2265
|
-
return /* @__PURE__ */ import_react2.default.createElement(
|
|
2899
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
2900
|
+
import_ui_extensions2.Form,
|
|
2901
|
+
{
|
|
2902
|
+
...formProps || {},
|
|
2903
|
+
onSubmit: handleSubmit,
|
|
2904
|
+
autoComplete
|
|
2905
|
+
},
|
|
2906
|
+
formContent
|
|
2907
|
+
);
|
|
2266
2908
|
});
|
|
2267
2909
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2268
2910
|
0 && (module.exports = {
|