hs-uix 1.0.0 → 1.0.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/form.mjs CHANGED
@@ -76,7 +76,132 @@ var isValueEmpty = (value, field) => {
76
76
  if ((field.type === "toggle" || field.type === "checkbox") && value === false) return true;
77
77
  return false;
78
78
  };
79
- var runValidators = (value, field, allValues, fieldTypes) => {
79
+ var isPromise = (value) => value && typeof value.then === "function";
80
+ var isAsyncFunction = (fn) => fn && fn.constructor && fn.constructor.name === "AsyncFunction";
81
+ var normalizeValidatorResult = (result) => {
82
+ if (result === true || result === void 0 || result === null || result === false) return null;
83
+ return String(result);
84
+ };
85
+ var isDateValueObject = (value) => isPlainObject(value) && Number.isInteger(value.year) && Number.isInteger(value.month) && Number.isInteger(value.date);
86
+ var isTimeValueObject = (value) => isPlainObject(value) && Number.isInteger(value.hours) && Number.isInteger(value.minutes);
87
+ var compareDateValues = (a, b) => {
88
+ if (a.year !== b.year) return a.year - b.year;
89
+ if (a.month !== b.month) return a.month - b.month;
90
+ return a.date - b.date;
91
+ };
92
+ var compareTimeValues = (a, b) => {
93
+ if (a.hours !== b.hours) return a.hours - b.hours;
94
+ return a.minutes - b.minutes;
95
+ };
96
+ var runDefaultFieldValidator = (value, field, allValues) => {
97
+ const errorPrefix = field.label || field.name;
98
+ switch (field.type) {
99
+ case "text":
100
+ case "password":
101
+ case "textarea":
102
+ if (typeof value !== "string") return `${errorPrefix} must be text`;
103
+ break;
104
+ case "number":
105
+ case "stepper":
106
+ case "currency":
107
+ if (typeof value !== "number" || Number.isNaN(value)) return `${errorPrefix} must be a number`;
108
+ break;
109
+ case "toggle":
110
+ case "checkbox":
111
+ if (typeof value !== "boolean") return `${errorPrefix} must be true or false`;
112
+ break;
113
+ case "select":
114
+ case "radioGroup": {
115
+ const options = resolveOptions(field, allValues);
116
+ if (options.length > 0 && !options.some((o) => Object.is(o.value, value))) {
117
+ return `${errorPrefix} has an invalid selection`;
118
+ }
119
+ break;
120
+ }
121
+ case "multiselect":
122
+ case "checkboxGroup": {
123
+ if (!Array.isArray(value)) return `${errorPrefix} must be a list`;
124
+ const options = resolveOptions(field, allValues);
125
+ if (options.length > 0) {
126
+ const validValues = new Set(options.map((o) => o.value));
127
+ const hasInvalid = value.some((item) => !validValues.has(item));
128
+ if (hasInvalid) return `${errorPrefix} has an invalid selection`;
129
+ }
130
+ break;
131
+ }
132
+ case "date":
133
+ if (!isDateValueObject(value)) return `${errorPrefix} has an invalid date`;
134
+ break;
135
+ case "time":
136
+ if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
137
+ break;
138
+ case "datetime": {
139
+ if (isDateValueObject(value)) break;
140
+ if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
141
+ const hasDate = value.date !== void 0;
142
+ const hasTime = value.time !== void 0;
143
+ if (!hasDate && !hasTime) return `${errorPrefix} has an invalid date/time`;
144
+ if (hasDate && !isDateValueObject(value.date)) return `${errorPrefix} has an invalid date`;
145
+ if (hasTime && !isTimeValueObject(value.time)) return `${errorPrefix} has an invalid time`;
146
+ break;
147
+ }
148
+ case "repeater":
149
+ if (!Array.isArray(value)) return `${errorPrefix} has invalid rows`;
150
+ break;
151
+ default:
152
+ break;
153
+ }
154
+ return null;
155
+ };
156
+ var runCustomSyncValidators = (value, field, allValues) => {
157
+ const validators = Array.isArray(field.validators) ? field.validators : [];
158
+ for (const validator of validators) {
159
+ if (isAsyncFunction(validator)) continue;
160
+ try {
161
+ const result = validator(value, allValues);
162
+ if (isPromise(result)) continue;
163
+ const err = normalizeValidatorResult(result);
164
+ if (err) return err;
165
+ } catch (err) {
166
+ return (err == null ? void 0 : err.message) || "Validation failed";
167
+ }
168
+ }
169
+ if (field.validate && !isAsyncFunction(field.validate)) {
170
+ try {
171
+ const result = field.validate(value, allValues);
172
+ if (!isPromise(result)) {
173
+ const err = normalizeValidatorResult(result);
174
+ if (err) return err;
175
+ }
176
+ } catch (err) {
177
+ return (err == null ? void 0 : err.message) || "Validation failed";
178
+ }
179
+ }
180
+ return null;
181
+ };
182
+ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
183
+ const promises = [];
184
+ const validators = Array.isArray(field.validators) ? field.validators : [];
185
+ for (const validator of validators) {
186
+ try {
187
+ const result = validator(value, allValues, context);
188
+ if (isPromise(result)) promises.push(result);
189
+ } catch (err) {
190
+ promises.push(Promise.reject(err));
191
+ }
192
+ }
193
+ if (field.validate) {
194
+ try {
195
+ const result = field.validate(value, allValues, context);
196
+ if (isPromise(result)) promises.push(result);
197
+ } catch (err) {
198
+ promises.push(Promise.reject(err));
199
+ }
200
+ }
201
+ return promises;
202
+ };
203
+ var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
204
+ const includeCustomValidators = options.includeCustomValidators !== false;
80
205
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
81
206
  const isRequired = resolveRequired(field, allValues);
82
207
  const plugin = fieldTypes && fieldTypes[field.type];
@@ -85,6 +210,10 @@ var runValidators = (value, field, allValues, fieldTypes) => {
85
210
  return `${field.label} is required`;
86
211
  }
87
212
  if (empty) return null;
213
+ if (field.useDefaultValidators !== false) {
214
+ const typeError = runDefaultFieldValidator(value, field, allValues);
215
+ if (typeError) return typeError;
216
+ }
88
217
  if (field.pattern && typeof value === "string") {
89
218
  if (!field.pattern.test(value)) {
90
219
  return field.patternMessage || "Invalid format";
@@ -106,10 +235,25 @@ var runValidators = (value, field, allValues, fieldTypes) => {
106
235
  return `Must be no more than ${field.max}`;
107
236
  }
108
237
  }
109
- if (field.validate) {
110
- const result = field.validate(value, allValues);
111
- if (result && typeof result.then === "function") return null;
112
- if (result !== true && result) return result;
238
+ if (field.type === "date" && isDateValueObject(value)) {
239
+ if (field.min && isDateValueObject(field.min) && compareDateValues(value, field.min) < 0) {
240
+ return field.minValidationMessage || "Date is too early";
241
+ }
242
+ if (field.max && isDateValueObject(field.max) && compareDateValues(value, field.max) > 0) {
243
+ return field.maxValidationMessage || "Date is too late";
244
+ }
245
+ }
246
+ if (field.type === "time" && isTimeValueObject(value)) {
247
+ if (field.min && isTimeValueObject(field.min) && compareTimeValues(value, field.min) < 0) {
248
+ return field.minValidationMessage || "Time is too early";
249
+ }
250
+ if (field.max && isTimeValueObject(field.max) && compareTimeValues(value, field.max) > 0) {
251
+ return field.maxValidationMessage || "Time is too late";
252
+ }
253
+ }
254
+ if (includeCustomValidators) {
255
+ const customError = runCustomSyncValidators(value, field, allValues);
256
+ if (customError) return customError;
113
257
  }
114
258
  return null;
115
259
  };
@@ -121,6 +265,58 @@ var resolveOptions = (field, allValues) => {
121
265
  if (typeof field.options === "function") return field.options(allValues);
122
266
  return field.options || [];
123
267
  };
268
+ var getDependsOnName = (field) => field.dependsOnConfig && field.dependsOnConfig.field;
269
+ var getDependsOnDisplay = (field) => field.dependsOnConfig && field.dependsOnConfig.display || "grouped";
270
+ var getDependsOnLabel = (field) => field.dependsOnConfig && field.dependsOnConfig.label;
271
+ var getDependsOnMessage = (field) => field.dependsOnConfig && field.dependsOnConfig.message;
272
+ var getRepeaterErrorKey = (fieldName, rowIdx, subFieldName) => `${fieldName}[${rowIdx}].${subFieldName}`;
273
+ var isPlainObject = (value) => Object.prototype.toString.call(value) === "[object Object]";
274
+ var deepEqual = (a, b) => {
275
+ if (Object.is(a, b)) return true;
276
+ if (typeof a !== typeof b) return false;
277
+ if (a == null || b == null) return false;
278
+ if (Array.isArray(a)) {
279
+ if (!Array.isArray(b) || a.length !== b.length) return false;
280
+ for (let i = 0; i < a.length; i++) {
281
+ if (!deepEqual(a[i], b[i])) return false;
282
+ }
283
+ return true;
284
+ }
285
+ if (a instanceof Date && b instanceof Date) {
286
+ return a.getTime() === b.getTime();
287
+ }
288
+ if (isPlainObject(a) && isPlainObject(b)) {
289
+ const aKeys = Object.keys(a);
290
+ const bKeys = Object.keys(b);
291
+ if (aKeys.length !== bKeys.length) return false;
292
+ for (const key of aKeys) {
293
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
294
+ if (!deepEqual(a[key], b[key])) return false;
295
+ }
296
+ return true;
297
+ }
298
+ return false;
299
+ };
300
+ var fieldSetHasErrors = (errors, fields) => {
301
+ if (!errors || !fields || fields.length === 0) return false;
302
+ const names = new Set(fields.map((field) => field.name));
303
+ return Object.keys(errors).some((errorKey) => {
304
+ const base = errorKey.split("[")[0];
305
+ return names.has(base);
306
+ });
307
+ };
308
+ var deepClone = (value) => {
309
+ if (Array.isArray(value)) return value.map(deepClone);
310
+ if (value instanceof Date) return new Date(value.getTime());
311
+ if (isPlainObject(value)) {
312
+ const next = {};
313
+ for (const key of Object.keys(value)) {
314
+ next[key] = deepClone(value[key]);
315
+ }
316
+ return next;
317
+ }
318
+ return value;
319
+ };
124
320
  var useFormPrefill = (properties, mapping) => {
125
321
  return useMemo(() => {
126
322
  if (!properties) return {};
@@ -190,28 +386,30 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
190
386
  // validate current step fields before Next
191
387
  } = props;
192
388
  const {
193
- submitLabel = "Submit",
194
- // submit button text
389
+ labels,
390
+ // { submit, cancel, back, next } — i18n label object
195
391
  submitVariant = "primary",
196
392
  // submit button variant
197
393
  showCancel = false,
198
394
  // show cancel button
199
- cancelLabel = "Cancel",
200
- // cancel button text
201
395
  onCancel,
202
396
  // () => void
203
397
  submitPosition = "bottom",
204
398
  // "bottom" | "none"
205
399
  loading: controlledLoading,
206
400
  // controlled loading state
207
- disabled = false
401
+ disabled = false,
208
402
  // disable entire form
403
+ renderButtons: renderButtonsProp
404
+ // custom action row renderer
209
405
  } = props;
210
406
  const {
211
407
  columns = 1,
212
408
  // number of grid columns (1 = full-width stack)
213
409
  columnWidth,
214
410
  // AutoGrid columnWidth — responsive layout (overrides columns)
411
+ maxColumns,
412
+ // cap number of columns per row in AutoGrid mode
215
413
  layout,
216
414
  // explicit row layout array (overrides columns + columnWidth)
217
415
  sections,
@@ -222,6 +420,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
222
420
  // show * on required fields
223
421
  noFormWrapper = false,
224
422
  // skip HubSpot <Form> wrapper
423
+ autoComplete,
424
+ // form autoComplete attribute
425
+ formProps,
426
+ // pass-through props for Form wrapper
225
427
  fieldTypes
226
428
  // Record<string, FieldTypePlugin> — custom field type registry
227
429
  } = props;
@@ -232,8 +434,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
232
434
  // string — form-level success alert
233
435
  readOnly: formReadOnly = false,
234
436
  // boolean — lock all fields
235
- readOnlyMessage
437
+ readOnlyMessage,
236
438
  // string — warning alert when readOnly
439
+ alerts,
440
+ // { addAlert, readOnlyTitle, errorTitle, successTitle }
441
+ errors: controlledErrors
442
+ // controlled validation errors
237
443
  } = props;
238
444
  const {
239
445
  onDirtyChange,
@@ -241,6 +447,38 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
241
447
  autoSave
242
448
  // { debounce: number, onAutoSave: (values) => void }
243
449
  } = props;
450
+ const submitButtonLabel = (labels == null ? void 0 : labels.submit) || "Submit";
451
+ const cancelButtonLabel = (labels == null ? void 0 : labels.cancel) || "Cancel";
452
+ const backButtonLabel = (labels == null ? void 0 : labels.back) || "Back";
453
+ const nextButtonLabel = (labels == null ? void 0 : labels.next) || "Next";
454
+ const addAlert = alerts == null ? void 0 : alerts.addAlert;
455
+ const readOnlyTitle = (alerts == null ? void 0 : alerts.readOnlyTitle) || "Read Only";
456
+ const errorTitle = (alerts == null ? void 0 : alerts.errorTitle) || "Error";
457
+ const successTitle = (alerts == null ? void 0 : alerts.successTitle) || "Success";
458
+ const prevErrorRef = useRef(formError);
459
+ const prevSuccessRef = useRef(formSuccess);
460
+ useEffect(() => {
461
+ if (!addAlert) return;
462
+ if (formError && formError !== prevErrorRef.current) {
463
+ addAlert({
464
+ type: "danger",
465
+ title: errorTitle,
466
+ message: typeof formError === "string" ? formError : void 0
467
+ });
468
+ }
469
+ prevErrorRef.current = formError;
470
+ }, [addAlert, formError, errorTitle]);
471
+ useEffect(() => {
472
+ if (!addAlert) return;
473
+ if (formSuccess && formSuccess !== prevSuccessRef.current) {
474
+ addAlert({
475
+ type: "success",
476
+ title: successTitle,
477
+ message: formSuccess
478
+ });
479
+ }
480
+ prevSuccessRef.current = formSuccess;
481
+ }, [addAlert, formSuccess, successTitle]);
244
482
  const computeInitialValues = () => {
245
483
  const vals = {};
246
484
  for (const field of fields) {
@@ -256,25 +494,99 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
256
494
  const [internalErrors, setInternalErrors] = useState({});
257
495
  const [internalStep, setInternalStep] = useState(0);
258
496
  const [internalLoading, setInternalLoading] = useState(false);
259
- const [touchedFields, setTouchedFields] = useState({});
260
497
  const [validatingFields, setValidatingFields] = useState({});
261
498
  const asyncValidationRef = useRef(/* @__PURE__ */ new Map());
499
+ const asyncAbortRef = useRef(/* @__PURE__ */ new Map());
500
+ const asyncValidationVersionRef = useRef(/* @__PURE__ */ new Map());
262
501
  const debounceTimersRef = useRef(/* @__PURE__ */ new Map());
502
+ const inputDebounceRef = useRef(/* @__PURE__ */ new Map());
503
+ const rowKeyRef = useRef(/* @__PURE__ */ new WeakMap());
504
+ const rowKeyCounterRef = useRef(0);
263
505
  const initialSnapshot = useRef(null);
264
506
  if (initialSnapshot.current === null) {
265
- initialSnapshot.current = JSON.stringify(computeInitialValues());
507
+ initialSnapshot.current = deepClone(computeInitialValues());
266
508
  }
267
509
  const formValues = values != null ? values : internalValues;
510
+ const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
268
511
  const currentStep = controlledStep != null ? controlledStep : internalStep;
269
512
  const isLoading = controlledLoading != null ? controlledLoading : internalLoading;
270
513
  const isMultiStep = Array.isArray(steps) && steps.length > 0;
514
+ const formValuesRef = useRef(formValues);
515
+ const formErrorsRef = useRef(formErrors);
516
+ const draftValuesRef = useRef(null);
517
+ formValuesRef.current = formValues;
518
+ formErrorsRef.current = formErrors;
519
+ const fieldByName = useMemo(() => {
520
+ const map = /* @__PURE__ */ new Map();
521
+ for (const field of fields) map.set(field.name, field);
522
+ return map;
523
+ }, [fields]);
524
+ const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
525
+ const configWarningsRef = useRef(/* @__PURE__ */ new Set());
526
+ const warnConfig = useCallback((message) => {
527
+ if (!isDev) return;
528
+ if (configWarningsRef.current.has(message)) return;
529
+ configWarningsRef.current.add(message);
530
+ if (typeof console !== "undefined" && console.warn) {
531
+ console.warn(`[FormBuilder] ${message}`);
532
+ }
533
+ }, [isDev]);
534
+ const replaceErrors = useCallback(
535
+ (nextErrors) => {
536
+ if (controlledErrors == null) setInternalErrors(nextErrors);
537
+ if (onValidationChange) onValidationChange(nextErrors);
538
+ },
539
+ [controlledErrors, onValidationChange]
540
+ );
541
+ const updateErrors = useCallback(
542
+ (newErrors) => {
543
+ const mergeErrors = (base) => {
544
+ const merged = { ...base, ...newErrors };
545
+ for (const key of Object.keys(newErrors)) {
546
+ if (newErrors[key] === null || newErrors[key] === void 0) {
547
+ delete merged[key];
548
+ }
549
+ }
550
+ return merged;
551
+ };
552
+ if (controlledErrors != null) {
553
+ const merged = mergeErrors(formErrorsRef.current || {});
554
+ if (onValidationChange) onValidationChange(merged);
555
+ return;
556
+ }
557
+ setInternalErrors((prev) => {
558
+ const merged = mergeErrors(prev);
559
+ if (onValidationChange) onValidationChange(merged);
560
+ return merged;
561
+ });
562
+ },
563
+ [controlledErrors, onValidationChange]
564
+ );
565
+ const getFieldEmptyValue = useCallback(
566
+ (field) => {
567
+ const plugin = fieldTypes && fieldTypes[field.type];
568
+ return plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
569
+ },
570
+ [fieldTypes]
571
+ );
572
+ const getRowKey = useCallback((fieldName, row, index) => {
573
+ if (!row || typeof row !== "object") return `${fieldName}-idx-${index}`;
574
+ if (!rowKeyRef.current.has(row)) {
575
+ rowKeyCounterRef.current += 1;
576
+ rowKeyRef.current.set(row, `${fieldName}-row-${rowKeyCounterRef.current}`);
577
+ }
578
+ return rowKeyRef.current.get(row);
579
+ }, []);
271
580
  useEffect(() => {
272
581
  return () => {
273
582
  for (const timer of debounceTimersRef.current.values()) clearTimeout(timer);
583
+ for (const timer of inputDebounceRef.current.values()) clearTimeout(timer);
584
+ for (const controller of asyncAbortRef.current.values()) controller.abort();
585
+ if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
274
586
  };
275
587
  }, []);
276
588
  const isDirty = useMemo(() => {
277
- return JSON.stringify(formValues) !== initialSnapshot.current;
589
+ return !deepEqual(formValues, initialSnapshot.current);
278
590
  }, [formValues]);
279
591
  const prevDirtyRef = useRef(false);
280
592
  useEffect(() => {
@@ -284,36 +596,129 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
284
596
  }
285
597
  }, [isDirty, onDirtyChange]);
286
598
  const autoSaveTimerRef = useRef(null);
599
+ const autoSaveRef = useRef(autoSave);
600
+ autoSaveRef.current = autoSave;
601
+ const prevAutoSaveValues = useRef(deepClone(formValues));
287
602
  useEffect(() => {
288
- if (!autoSave || !autoSave.onAutoSave || !isDirty) return;
603
+ const cfg = autoSaveRef.current;
604
+ if (!cfg || !cfg.onAutoSave || !isDirty) return;
605
+ if (deepEqual(prevAutoSaveValues.current, formValues)) return;
606
+ prevAutoSaveValues.current = deepClone(formValues);
289
607
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
290
608
  autoSaveTimerRef.current = setTimeout(() => {
291
609
  autoSaveTimerRef.current = null;
292
- autoSave.onAutoSave(formValues);
293
- }, autoSave.debounce || 1e3);
610
+ autoSaveRef.current.onAutoSave(formValues);
611
+ }, cfg.debounce || 1e3);
294
612
  return () => {
295
613
  if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
296
614
  };
297
- }, [formValues, isDirty, autoSave]);
298
- const visibleFields = useMemo(() => {
299
- let filtered = fields.filter((f) => {
615
+ }, [formValues, isDirty]);
616
+ const allVisibleFields = useMemo(() => {
617
+ return fields.filter((f) => {
300
618
  if (f.visible && !f.visible(formValues)) return false;
301
619
  return true;
302
620
  });
621
+ }, [fields, formValues]);
622
+ const visibleFields = useMemo(() => {
623
+ let filtered = allVisibleFields;
303
624
  if (isMultiStep && steps[currentStep] && steps[currentStep].fields) {
304
625
  const stepFieldNames = new Set(steps[currentStep].fields);
305
626
  filtered = filtered.filter((f) => stepFieldNames.has(f.name));
306
627
  }
307
628
  return filtered;
308
- }, [fields, formValues, isMultiStep, steps, currentStep]);
629
+ }, [allVisibleFields, isMultiStep, steps, currentStep]);
630
+ useEffect(() => {
631
+ const nameSet = new Set(fields.map((f) => f.name));
632
+ if (nameSet.size !== fields.length) {
633
+ warnConfig("Duplicate field names detected. Field names must be unique.");
634
+ }
635
+ for (const field of fields) {
636
+ const parentName = getDependsOnName(field);
637
+ if (parentName && !nameSet.has(parentName)) {
638
+ warnConfig(`Field "${field.name}" depends on missing field "${parentName}".`);
639
+ }
640
+ }
641
+ if (steps) {
642
+ for (let i = 0; i < steps.length; i++) {
643
+ const step = steps[i];
644
+ if (!step.fields) continue;
645
+ for (const fieldName of step.fields) {
646
+ if (!nameSet.has(fieldName)) {
647
+ warnConfig(`Step ${i + 1} references missing field "${fieldName}".`);
648
+ }
649
+ }
650
+ }
651
+ }
652
+ if (layout) {
653
+ for (const row of layout) {
654
+ for (const entry of row) {
655
+ const fieldName = typeof entry === "string" ? entry : entry.field;
656
+ if (!nameSet.has(fieldName)) {
657
+ warnConfig(`Layout references missing field "${fieldName}".`);
658
+ }
659
+ }
660
+ }
661
+ }
662
+ if (sections) {
663
+ for (const section of sections) {
664
+ for (const fieldName of section.fields || []) {
665
+ if (!nameSet.has(fieldName)) {
666
+ warnConfig(`Section "${section.id}" references missing field "${fieldName}".`);
667
+ }
668
+ }
669
+ }
670
+ }
671
+ }, [fields, steps, layout, sections, warnConfig]);
672
+ const validateRepeaterField = useCallback(
673
+ (field, value, allValues) => {
674
+ const errors = {};
675
+ const rows = Array.isArray(value) ? value : [];
676
+ const subFields = field.fields || [];
677
+ let firstSubError = null;
678
+ if (resolveRequired(field, allValues) && rows.length === 0) {
679
+ const requiredError = `${field.label} is required`;
680
+ errors[field.name] = requiredError;
681
+ return { errors, hasErrors: true };
682
+ }
683
+ if (typeof field.min === "number" && rows.length < field.min) {
684
+ errors[field.name] = `Must have at least ${field.min} ${field.min === 1 ? "row" : "rows"}`;
685
+ } else if (typeof field.max === "number" && rows.length > field.max) {
686
+ errors[field.name] = `Must have no more than ${field.max} ${field.max === 1 ? "row" : "rows"}`;
687
+ }
688
+ rows.forEach((row, rowIdx) => {
689
+ const rowValues = { ...allValues, [field.name]: rows };
690
+ subFields.forEach((subField) => {
691
+ if (subField.visible && !subField.visible(rowValues)) return;
692
+ const err = runValidators(row == null ? void 0 : row[subField.name], subField, rowValues, fieldTypes);
693
+ if (!err) return;
694
+ const key = getRepeaterErrorKey(field.name, rowIdx, subField.name);
695
+ errors[key] = err;
696
+ if (!firstSubError) firstSubError = { row: rowIdx + 1, message: err };
697
+ });
698
+ });
699
+ if (!errors[field.name] && firstSubError) {
700
+ errors[field.name] = `Row ${firstSubError.row}: ${firstSubError.message}`;
701
+ }
702
+ return { errors, hasErrors: Object.keys(errors).length > 0 };
703
+ },
704
+ [fieldTypes]
705
+ );
309
706
  const validateField = useCallback(
310
707
  (name, value) => {
311
- const field = fields.find((f) => f.name === name);
708
+ const field = fieldByName.get(name);
312
709
  if (!field) return null;
313
710
  if (field.visible && !field.visible(formValues)) return null;
711
+ if (field.type === "repeater") {
712
+ const repeaterResult = validateRepeaterField(
713
+ field,
714
+ value != null ? value : formValues[name],
715
+ formValues
716
+ );
717
+ return repeaterResult.errors[name] || null;
718
+ }
314
719
  return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
315
720
  },
316
- [fields, formValues, fieldTypes]
721
+ [fieldByName, formValues, validateRepeaterField, fieldTypes]
317
722
  );
318
723
  const validateVisibleFields = useCallback(
319
724
  (fieldSubset) => {
@@ -321,6 +726,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
321
726
  const errors = {};
322
727
  let hasErrors = false;
323
728
  for (const field of toValidate) {
729
+ if (field.type === "repeater") {
730
+ const repeaterResult = validateRepeaterField(field, formValues[field.name], formValues);
731
+ if (repeaterResult.hasErrors) {
732
+ Object.assign(errors, repeaterResult.errors);
733
+ hasErrors = true;
734
+ }
735
+ continue;
736
+ }
324
737
  const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
325
738
  if (err) {
326
739
  errors[field.name] = err;
@@ -329,64 +742,87 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
329
742
  }
330
743
  return { errors, hasErrors };
331
744
  },
332
- [visibleFields, formValues]
333
- );
334
- const updateErrors = useCallback(
335
- (newErrors) => {
336
- setInternalErrors((prev) => {
337
- const merged = { ...prev, ...newErrors };
338
- for (const key of Object.keys(merged)) {
339
- if (newErrors[key] === null || newErrors[key] === void 0) {
340
- delete merged[key];
341
- }
342
- }
343
- if (onValidationChange) onValidationChange(merged);
344
- return merged;
345
- });
346
- },
347
- [onValidationChange]
745
+ [visibleFields, formValues, validateRepeaterField, fieldTypes]
348
746
  );
349
747
  const runAsyncValidation = useCallback(
350
748
  (name, value) => {
351
- const field = fields.find((f) => f.name === name);
352
- if (!field || !field.validate) return;
749
+ const field = fieldByName.get(name);
750
+ if (!field || field.type === "repeater") return null;
353
751
  const val = value != null ? value : formValues[name];
354
- const syncError = runValidators(val, field, formValues, fieldTypes);
355
- if (syncError) return;
356
- const result = field.validate(val, formValues);
357
- if (!result || typeof result.then !== "function") return;
358
- const validationPromise = result.then(
359
- (asyncResult) => {
360
- if (asyncValidationRef.current.get(name) !== validationPromise) return;
752
+ const syncError = runValidators(val, field, formValues, fieldTypes, { includeCustomValidators: false });
753
+ const prevController = asyncAbortRef.current.get(name);
754
+ if (prevController) prevController.abort();
755
+ asyncAbortRef.current.delete(name);
756
+ setValidatingFields((prev) => {
757
+ if (!prev[name]) return prev;
758
+ const next = { ...prev };
759
+ delete next[name];
760
+ return next;
761
+ });
762
+ if (syncError) return null;
763
+ const version = (asyncValidationVersionRef.current.get(name) || 0) + 1;
764
+ asyncValidationVersionRef.current.set(name, version);
765
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
766
+ if (controller) asyncAbortRef.current.set(name, controller);
767
+ let asyncPromises;
768
+ try {
769
+ asyncPromises = collectAsyncValidatorPromises(
770
+ val,
771
+ field,
772
+ formValues,
773
+ controller ? { signal: controller.signal } : void 0
774
+ );
775
+ } catch (err) {
776
+ updateErrors({ [name]: (err == null ? void 0 : err.message) || "Validation failed" });
777
+ return null;
778
+ }
779
+ if (asyncPromises.length === 0) {
780
+ asyncAbortRef.current.delete(name);
781
+ return null;
782
+ }
783
+ const validationPromise = Promise.all(asyncPromises).then(
784
+ (results) => {
785
+ if (asyncValidationVersionRef.current.get(name) !== version) return;
361
786
  asyncValidationRef.current.delete(name);
787
+ asyncAbortRef.current.delete(name);
362
788
  setValidatingFields((prev) => {
363
789
  const next = { ...prev };
364
790
  delete next[name];
365
791
  return next;
366
792
  });
367
- const err = asyncResult !== true && asyncResult ? asyncResult : null;
793
+ let err = null;
794
+ for (const result of results) {
795
+ const normalized = normalizeValidatorResult(result);
796
+ if (normalized) {
797
+ err = normalized;
798
+ break;
799
+ }
800
+ }
368
801
  updateErrors({ [name]: err });
369
802
  },
370
803
  (rejection) => {
371
- if (asyncValidationRef.current.get(name) !== validationPromise) return;
804
+ if (asyncValidationVersionRef.current.get(name) !== version) return;
372
805
  asyncValidationRef.current.delete(name);
806
+ asyncAbortRef.current.delete(name);
373
807
  setValidatingFields((prev) => {
374
808
  const next = { ...prev };
375
809
  delete next[name];
376
810
  return next;
377
811
  });
812
+ if (rejection && rejection.name === "AbortError") return;
378
813
  updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
379
814
  }
380
815
  );
381
816
  asyncValidationRef.current.set(name, validationPromise);
382
817
  setValidatingFields((prev) => ({ ...prev, [name]: true }));
818
+ return validationPromise;
383
819
  },
384
- [fields, formValues, updateErrors]
820
+ [fieldByName, formValues, fieldTypes, updateErrors]
385
821
  );
386
822
  const triggerAsyncValidation = useCallback(
387
823
  (name, value) => {
388
- const field = fields.find((f) => f.name === name);
389
- if (!field || !field.validate) return;
824
+ const field = fieldByName.get(name);
825
+ if (!field || field.type === "repeater") return;
390
826
  const debounceMs = field.validateDebounce;
391
827
  if (debounceMs && debounceMs > 0) {
392
828
  const existing = debounceTimersRef.current.get(name);
@@ -400,44 +836,93 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
400
836
  runAsyncValidation(name, value);
401
837
  }
402
838
  },
403
- [fields, runAsyncValidation]
839
+ [fieldByName, runAsyncValidation]
404
840
  );
405
- const setFieldValueSilent = useCallback(
406
- (name, value) => {
841
+ const commitValues = useCallback(
842
+ (nextValues) => {
843
+ formValuesRef.current = nextValues;
407
844
  if (values != null) {
408
- if (onChange) onChange({ ...formValues, [name]: value });
845
+ if (onChange) onChange(nextValues);
409
846
  } else {
410
- setInternalValues((prev) => ({ ...prev, [name]: value }));
847
+ setInternalValues(nextValues);
411
848
  }
412
849
  },
413
- [values, onChange, formValues]
850
+ [values, onChange]
851
+ );
852
+ const setFieldValueSilent = useCallback(
853
+ (name, value) => {
854
+ const base = draftValuesRef.current || formValuesRef.current || {};
855
+ const nextValues = { ...base, [name]: value };
856
+ draftValuesRef.current = nextValues;
857
+ commitValues(nextValues);
858
+ },
859
+ [commitValues]
414
860
  );
415
861
  const handleFieldChange = useCallback(
416
862
  (name, value) => {
417
- const newValues = { ...formValues, [name]: value };
418
- if (values != null) {
419
- if (onChange) onChange(newValues);
420
- } else {
421
- setInternalValues(newValues);
863
+ const newValues = { ...formValuesRef.current, [name]: value };
864
+ const queue = [name];
865
+ const visited = /* @__PURE__ */ new Set();
866
+ const clearedErrors = {};
867
+ while (queue.length > 0) {
868
+ const current = queue.shift();
869
+ if (!current || visited.has(current)) continue;
870
+ visited.add(current);
871
+ fields.forEach((dep) => {
872
+ const parentName = getDependsOnName(dep);
873
+ if (parentName !== current || dep.name === current) return;
874
+ if (!dep.options) return;
875
+ const depOptions = resolveOptions(dep, newValues);
876
+ const depValue = newValues[dep.name];
877
+ if (depValue == null || depValue === "") return;
878
+ const validValues = new Set(depOptions.map((o) => o.value));
879
+ let nextDepValue = depValue;
880
+ let changed = false;
881
+ if (Array.isArray(depValue)) {
882
+ const filtered = depValue.filter((v) => validValues.has(v));
883
+ if (filtered.length !== depValue.length) {
884
+ nextDepValue = filtered;
885
+ changed = true;
886
+ }
887
+ } else if (!validValues.has(depValue)) {
888
+ nextDepValue = getFieldEmptyValue(dep);
889
+ changed = true;
890
+ }
891
+ if (changed) {
892
+ newValues[dep.name] = nextDepValue;
893
+ queue.push(dep.name);
894
+ if (formErrorsRef.current[dep.name] != null) {
895
+ clearedErrors[dep.name] = null;
896
+ }
897
+ }
898
+ });
422
899
  }
423
- if (onFieldChange) onFieldChange(name, value, newValues);
424
- if (internalErrors[name]) {
425
- updateErrors({ [name]: null });
900
+ if (formErrorsRef.current[name] != null) {
901
+ clearedErrors[name] = null;
902
+ }
903
+ for (const key of Object.keys(formErrorsRef.current)) {
904
+ if (key.startsWith(`${name}[`)) {
905
+ clearedErrors[key] = null;
906
+ }
426
907
  }
427
- const field = fields.find((f) => f.name === name);
908
+ draftValuesRef.current = newValues;
909
+ commitValues(newValues);
910
+ if (onFieldChange) onFieldChange(name, value, newValues);
911
+ if (Object.keys(clearedErrors).length > 0) updateErrors(clearedErrors);
912
+ const field = fieldByName.get(name);
428
913
  if (field && field.onFieldChange) {
429
914
  field.onFieldChange(value, newValues, {
430
915
  setFieldValue: setFieldValueSilent,
431
916
  setFieldError: (fieldName, message) => updateErrors({ [fieldName]: message })
432
917
  });
433
918
  }
919
+ draftValuesRef.current = null;
434
920
  },
435
- [formValues, values, onChange, onFieldChange, internalErrors, updateErrors, fields, setFieldValueSilent]
921
+ [fields, getFieldEmptyValue, commitValues, onFieldChange, updateErrors, fieldByName, setFieldValueSilent]
436
922
  );
437
- const inputDebounceRef = useRef(/* @__PURE__ */ new Map());
438
923
  const handleDebouncedFieldChange = useCallback(
439
924
  (name, value) => {
440
- const field = fields.find((f) => f.name === name);
925
+ const field = fieldByName.get(name);
441
926
  const debounceMs = field && field.debounce;
442
927
  if (debounceMs && debounceMs > 0) {
443
928
  const existing = inputDebounceRef.current.get(name);
@@ -451,26 +936,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
451
936
  handleFieldChange(name, value);
452
937
  }
453
938
  },
454
- [fields, handleFieldChange]
939
+ [fieldByName, handleFieldChange]
455
940
  );
456
941
  const handleFieldInput = useCallback(
457
942
  (name, value) => {
458
- if (validateOnChange) {
459
- const err = validateField(name, value);
460
- updateErrors({ [name]: err });
461
- }
943
+ if (!validateOnChange) return;
944
+ const err = validateField(name, value);
945
+ updateErrors({ [name]: err });
462
946
  },
463
947
  [validateOnChange, validateField, updateErrors]
464
948
  );
465
949
  const handleFieldBlur = useCallback(
466
950
  (name, value) => {
467
- setTouchedFields((prev) => ({ ...prev, [name]: true }));
468
- if (validateOnBlur) {
469
- const err = validateField(name, value != null ? value : formValues[name]);
470
- updateErrors({ [name]: err });
471
- if (!err) {
472
- triggerAsyncValidation(name, value != null ? value : formValues[name]);
473
- }
951
+ if (!validateOnBlur) return;
952
+ const resolvedValue = value != null ? value : formValues[name];
953
+ const err = validateField(name, resolvedValue);
954
+ updateErrors({ [name]: err });
955
+ if (!err) {
956
+ triggerAsyncValidation(name, resolvedValue);
474
957
  }
475
958
  },
476
959
  [validateOnBlur, validateField, updateErrors, formValues, triggerAsyncValidation]
@@ -479,30 +962,33 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
479
962
  async (e) => {
480
963
  if (e && e.preventDefault) e.preventDefault();
481
964
  if (validateOnSubmit) {
482
- const allVisible = fields.filter((f) => !f.visible || f.visible(formValues));
483
- const { errors, hasErrors } = validateVisibleFields(allVisible);
965
+ const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
484
966
  if (hasErrors) {
485
- setInternalErrors(errors);
486
- if (onValidationChange) onValidationChange(errors);
967
+ replaceErrors(errors);
487
968
  return;
488
969
  }
489
- if (asyncValidationRef.current.size > 0) {
490
- await Promise.all(asyncValidationRef.current.values());
491
- const currentErrors = { ...internalErrors };
492
- const hasAsyncErrors = Object.keys(currentErrors).length > 0;
493
- if (hasAsyncErrors) return;
970
+ const asyncSubmitValidations = allVisibleFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
971
+ if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
972
+ const pendingValidations = [
973
+ .../* @__PURE__ */ new Set([
974
+ ...asyncSubmitValidations,
975
+ ...Array.from(asyncValidationRef.current.values())
976
+ ])
977
+ ];
978
+ await Promise.all(pendingValidations);
979
+ if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) return;
494
980
  }
495
981
  }
496
982
  const reset = () => {
497
983
  const fresh = computeInitialValues();
498
984
  if (values == null) setInternalValues(fresh);
499
- setInternalErrors({});
500
- setTouchedFields({});
501
- initialSnapshot.current = JSON.stringify(fresh);
985
+ replaceErrors({});
986
+ initialSnapshot.current = deepClone(fresh);
987
+ prevAutoSaveValues.current = deepClone(fresh);
502
988
  };
503
989
  const rawValues = {};
504
990
  for (const key of Object.keys(formValues)) {
505
- const f = fields.find((fd) => fd.name === key);
991
+ const f = fieldByName.get(key);
506
992
  if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
507
993
  rawValues[key] = formValues[key];
508
994
  }
@@ -526,25 +1012,34 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
526
1012
  if (controlledLoading == null) setInternalLoading(false);
527
1013
  }
528
1014
  },
529
- [validateOnSubmit, fields, formValues, validateVisibleFields, onValidationChange, onSubmit, values, controlledLoading, internalErrors, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess]
1015
+ [validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, runAsyncValidation]
530
1016
  );
531
- const handleNext = useCallback(() => {
1017
+ const handleNext = useCallback(async () => {
532
1018
  if (!isMultiStep) return;
533
1019
  if (validateStepOnNext && steps[currentStep] && steps[currentStep].fields) {
534
- const stepFields = fields.filter(
535
- (f) => steps[currentStep].fields.includes(f.name) && (!f.visible || f.visible(formValues))
536
- );
1020
+ const stepFieldNames = new Set(steps[currentStep].fields);
1021
+ const stepFields = allVisibleFields.filter((f) => stepFieldNames.has(f.name));
537
1022
  const { errors, hasErrors } = validateVisibleFields(stepFields);
538
1023
  if (hasErrors) {
539
- setInternalErrors((prev) => ({ ...prev, ...errors }));
540
- if (onValidationChange) onValidationChange({ ...internalErrors, ...errors });
1024
+ replaceErrors({ ...formErrorsRef.current, ...errors });
541
1025
  return;
542
1026
  }
1027
+ const asyncStepValidations = stepFields.map((field) => runAsyncValidation(field.name, formValues[field.name])).filter(Boolean);
1028
+ if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
1029
+ const pendingValidations = [
1030
+ .../* @__PURE__ */ new Set([
1031
+ ...asyncStepValidations,
1032
+ ...Array.from(asyncValidationRef.current.values())
1033
+ ])
1034
+ ];
1035
+ await Promise.all(pendingValidations);
1036
+ if (fieldSetHasErrors(formErrorsRef.current, stepFields)) return;
1037
+ }
543
1038
  }
544
1039
  if (steps[currentStep] && steps[currentStep].validate) {
545
1040
  const result = steps[currentStep].validate(formValues);
546
1041
  if (result !== true && result) {
547
- setInternalErrors((prev) => ({ ...prev, ...result }));
1042
+ replaceErrors({ ...formErrorsRef.current, ...result });
548
1043
  return;
549
1044
  }
550
1045
  }
@@ -554,7 +1049,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
554
1049
  } else {
555
1050
  setInternalStep(nextStep);
556
1051
  }
557
- }, [isMultiStep, validateStepOnNext, steps, currentStep, fields, formValues, validateVisibleFields, onValidationChange, internalErrors, controlledStep, onStepChange]);
1052
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, runAsyncValidation]);
558
1053
  const handleBack = useCallback(() => {
559
1054
  if (!isMultiStep) return;
560
1055
  const prevStep = Math.max(currentStep - 1, 0);
@@ -579,33 +1074,56 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
579
1074
  useImperativeHandle(ref, () => ({
580
1075
  submit: handleSubmit,
581
1076
  validate: () => {
582
- const allVisible = fields.filter((f) => !f.visible || f.visible(formValues));
583
- const { errors, hasErrors } = validateVisibleFields(allVisible);
584
- setInternalErrors(errors);
1077
+ const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
1078
+ replaceErrors(errors);
585
1079
  return { valid: !hasErrors, errors };
586
1080
  },
587
1081
  reset: () => {
588
1082
  const fresh = computeInitialValues();
589
1083
  if (values == null) setInternalValues(fresh);
590
- setInternalErrors({});
591
- setTouchedFields({});
592
- initialSnapshot.current = JSON.stringify(fresh);
1084
+ replaceErrors({});
1085
+ initialSnapshot.current = deepClone(fresh);
1086
+ prevAutoSaveValues.current = deepClone(fresh);
593
1087
  },
594
1088
  getValues: () => formValues,
595
1089
  isDirty: () => isDirty,
596
1090
  setFieldValue: (name, value) => handleFieldChange(name, value),
597
1091
  setFieldError: (name, message) => updateErrors({ [name]: message }),
598
1092
  setErrors: (errors) => {
599
- setInternalErrors(errors);
600
- if (onValidationChange) onValidationChange(errors);
1093
+ replaceErrors(errors);
601
1094
  }
602
1095
  }));
1096
+ const setRepeaterSubFieldError = useCallback(
1097
+ (fieldName, rowIdx, subFieldName, errorMessage) => {
1098
+ const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
1099
+ const merged = { ...formErrorsRef.current };
1100
+ if (errorMessage) {
1101
+ merged[key] = errorMessage;
1102
+ } else {
1103
+ delete merged[key];
1104
+ }
1105
+ const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
1106
+ const match = k.match(/\[(\d+)\]\./);
1107
+ const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
1108
+ return { key: k, row };
1109
+ }).sort((a, b) => a.row - b.row);
1110
+ if (subErrors.length > 0) {
1111
+ const first = subErrors[0];
1112
+ merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
1113
+ } else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
1114
+ delete merged[fieldName];
1115
+ }
1116
+ replaceErrors(merged);
1117
+ },
1118
+ [replaceErrors]
1119
+ );
603
1120
  const renderField = (field) => {
604
1121
  const fieldValue = formValues[field.name];
605
- const fieldError = internalErrors[field.name] || null;
1122
+ const fieldError = formErrors[field.name] || null;
606
1123
  const hasError = !!fieldError;
607
1124
  const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
608
- const isReadOnly = field.readOnly || disabled || formReadOnly;
1125
+ const isReadOnly = field.readOnly || formReadOnly;
1126
+ const isDisabled = disabled || field.disabled || formReadOnly;
609
1127
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
610
1128
  if (field.type === "display") {
611
1129
  if (field.render) {
@@ -664,6 +1182,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
664
1182
  tooltip: field.tooltip,
665
1183
  required: isRequired,
666
1184
  readOnly: isReadOnly,
1185
+ disabled: isDisabled,
667
1186
  error: hasError,
668
1187
  validationMessage: fieldError || void 0,
669
1188
  ...field.loading || validatingFields[field.name] ? { loading: true } : {},
@@ -796,6 +1315,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
796
1315
  maxValidationMessage: field.maxValidationMessage,
797
1316
  onChange: (v) => {
798
1317
  handleFieldChange(field.name, { ...fieldValue, date: v, time: timeVal });
1318
+ },
1319
+ onBlur: (v) => {
1320
+ handleFieldBlur(field.name, { ...fieldValue, date: v, time: timeVal });
799
1321
  }
800
1322
  }
801
1323
  )), /* @__PURE__ */ React.createElement(Box, { flex: 1 }, /* @__PURE__ */ React.createElement(
@@ -806,12 +1328,16 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
806
1328
  description: field.description,
807
1329
  tooltip: field.tooltip,
808
1330
  readOnly: isReadOnly,
1331
+ disabled: isDisabled,
809
1332
  error: hasError,
810
1333
  value: timeVal,
811
1334
  interval: field.interval,
812
1335
  timezone: field.timezone,
813
1336
  onChange: (v) => {
814
1337
  handleFieldChange(field.name, { ...fieldValue, date: dateVal, time: v });
1338
+ },
1339
+ onBlur: (v) => {
1340
+ handleFieldBlur(field.name, { ...fieldValue, date: dateVal, time: v });
815
1341
  }
816
1342
  }
817
1343
  )));
@@ -849,6 +1375,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
849
1375
  textChecked: field.textChecked,
850
1376
  textUnchecked: field.textUnchecked,
851
1377
  readonly: isReadOnly,
1378
+ disabled: isDisabled,
852
1379
  onChange: fieldOnChange,
853
1380
  ...field.fieldProps || {}
854
1381
  }
@@ -861,6 +1388,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
861
1388
  checked: !!fieldValue,
862
1389
  description: field.description,
863
1390
  readOnly: isReadOnly,
1391
+ disabled: isDisabled,
864
1392
  inline: field.inline,
865
1393
  variant: field.variant,
866
1394
  onChange: fieldOnChange,
@@ -897,61 +1425,140 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
897
1425
  case "repeater": {
898
1426
  const rows = Array.isArray(fieldValue) ? fieldValue : [];
899
1427
  const subFields = field.fields || [];
900
- const minRows = field.min || 0;
901
- const maxRows = field.max || Infinity;
902
- const canAdd = rows.length < maxRows && !isReadOnly;
903
- const canRemove = rows.length > minRows && !isReadOnly;
1428
+ const minRows = typeof field.min === "number" ? field.min : 0;
1429
+ const maxRows = typeof field.max === "number" ? field.max : Infinity;
1430
+ const repeaterProps = field.repeaterProps || {};
1431
+ const renderAddControl = repeaterProps.renderAdd;
1432
+ const renderRemoveControl = repeaterProps.renderRemove;
1433
+ const renderMoveUpControl = repeaterProps.renderMoveUp;
1434
+ const renderMoveDownControl = repeaterProps.renderMoveDown;
1435
+ const addLabel = repeaterProps.addLabel || "Add";
1436
+ const removeLabel = repeaterProps.removeLabel || "Remove";
1437
+ const moveUpLabel = repeaterProps.moveUpLabel || "Up";
1438
+ const moveDownLabel = repeaterProps.moveDownLabel || "Down";
1439
+ const canEditRows = !isReadOnly && !isDisabled;
1440
+ const canAdd = rows.length < maxRows && canEditRows;
1441
+ const canRemove = rows.length > minRows && canEditRows;
1442
+ const canReorder = !!repeaterProps.reorderable && canEditRows;
1443
+ const repeaterHasNestedErrors = Object.keys(formErrors).some(
1444
+ (k) => k.startsWith(`${field.name}[`)
1445
+ );
1446
+ const firstNestedErrorKey = Object.keys(formErrors).find(
1447
+ (k) => k.startsWith(`${field.name}[`)
1448
+ );
1449
+ const repeaterErrorMessage = fieldError || (firstNestedErrorKey ? formErrors[firstNestedErrorKey] : null);
1450
+ const repeaterHasError = !!fieldError || repeaterHasNestedErrors;
904
1451
  const addRow = () => {
905
1452
  const emptyRow = {};
906
1453
  for (const sf of subFields) {
907
- emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue : getEmptyValue(sf);
1454
+ emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue : getFieldEmptyValue(sf);
908
1455
  }
909
1456
  handleFieldChange(field.name, [...rows, emptyRow]);
910
1457
  };
911
1458
  const removeRow = (idx) => {
912
1459
  handleFieldChange(field.name, rows.filter((_, i) => i !== idx));
913
1460
  };
914
- const updateRow = (idx, subName, subValue) => {
1461
+ const moveRow = (fromIndex, toIndex) => {
1462
+ if (toIndex < 0 || toIndex >= rows.length || toIndex === fromIndex) return;
1463
+ const updated = [...rows];
1464
+ const [moved] = updated.splice(fromIndex, 1);
1465
+ updated.splice(toIndex, 0, moved);
1466
+ handleFieldChange(field.name, updated);
1467
+ };
1468
+ const validateSubField = (rowIdx, subField, subValue, nextRows) => {
1469
+ const rowValues = { ...formValues, [field.name]: nextRows };
1470
+ const err = runValidators(subValue, subField, rowValues, fieldTypes);
1471
+ setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
1472
+ };
1473
+ const handleSubFieldChange = (rowIdx, subField, subValue) => {
915
1474
  const updated = rows.map(
916
- (row, i) => i === idx ? { ...row, [subName]: subValue } : row
1475
+ (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
917
1476
  );
918
1477
  handleFieldChange(field.name, updated);
1478
+ if (validateOnChange) {
1479
+ validateSubField(rowIdx, subField, subValue, updated);
1480
+ }
1481
+ };
1482
+ const handleSubFieldBlur = (rowIdx, subField, subValue) => {
1483
+ if (!validateOnBlur) return;
1484
+ const nextRows = rows.map(
1485
+ (row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
1486
+ );
1487
+ validateSubField(rowIdx, subField, subValue, nextRows);
919
1488
  };
920
- 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: rowIdx, direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
1489
+ 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) => {
921
1490
  const sfValue = row[sf.name];
922
1491
  const sfLabel = rowIdx === 0 ? sf.label : void 0;
923
- const sfOptions = resolveOptions(sf, formValues);
1492
+ const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
1493
+ const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
924
1494
  const sfProps = {
925
1495
  name: `${field.name}-${rowIdx}-${sf.name}`,
926
1496
  label: sfLabel,
927
1497
  placeholder: sf.placeholder,
928
- readOnly: isReadOnly,
1498
+ readOnly: sf.readOnly || isReadOnly,
1499
+ disabled: sf.disabled || isDisabled,
1500
+ error: !!sfError,
1501
+ validationMessage: sfError || void 0,
929
1502
  ...sf.fieldProps || {}
930
1503
  };
931
1504
  let sfElement;
932
1505
  switch (sf.type) {
933
1506
  case "select":
934
- sfElement = /* @__PURE__ */ React.createElement(Select, { ...sfProps, value: sfValue, options: sfOptions, onChange: (v) => updateRow(rowIdx, sf.name, v) });
1507
+ sfElement = /* @__PURE__ */ React.createElement(
1508
+ Select,
1509
+ {
1510
+ ...sfProps,
1511
+ value: sfValue,
1512
+ options: sfOptions,
1513
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
1514
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
1515
+ }
1516
+ );
935
1517
  break;
936
1518
  case "number":
937
- sfElement = /* @__PURE__ */ React.createElement(NumberInput, { ...sfProps, value: sfValue, onChange: (v) => updateRow(rowIdx, sf.name, v) });
1519
+ sfElement = /* @__PURE__ */ React.createElement(
1520
+ NumberInput,
1521
+ {
1522
+ ...sfProps,
1523
+ value: sfValue,
1524
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
1525
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
1526
+ }
1527
+ );
938
1528
  break;
939
1529
  case "checkbox":
940
- sfElement = /* @__PURE__ */ React.createElement(Checkbox, { ...sfProps, checked: !!sfValue, onChange: (v) => updateRow(rowIdx, sf.name, v) }, sf.label);
1530
+ sfElement = /* @__PURE__ */ React.createElement(
1531
+ Checkbox,
1532
+ {
1533
+ ...sfProps,
1534
+ checked: !!sfValue,
1535
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
1536
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
1537
+ },
1538
+ sf.label
1539
+ );
941
1540
  break;
942
1541
  default:
943
- sfElement = /* @__PURE__ */ React.createElement(Input, { ...sfProps, value: sfValue || "", onChange: (v) => updateRow(rowIdx, sf.name, v) });
1542
+ sfElement = /* @__PURE__ */ React.createElement(
1543
+ Input,
1544
+ {
1545
+ ...sfProps,
1546
+ value: sfValue || "",
1547
+ onChange: (v) => handleSubFieldChange(rowIdx, sf, v),
1548
+ onBlur: (v) => handleSubFieldBlur(rowIdx, sf, v)
1549
+ }
1550
+ );
944
1551
  }
945
1552
  return /* @__PURE__ */ React.createElement(Box, { key: sf.name, flex: 1 }, sfElement);
946
- }), canRemove && /* @__PURE__ */ React.createElement(
1553
+ }), /* @__PURE__ */ React.createElement(Inline, { gap: "xs" }, canReorder && rowIdx > 0 && (renderMoveUpControl ? renderMoveUpControl({ index: rowIdx, onClick: () => moveRow(rowIdx, rowIdx - 1) }) : /* @__PURE__ */ React.createElement(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__ */ React.createElement(Button, { variant: "secondary", size: "sm", onClick: () => moveRow(rowIdx, rowIdx + 1) }, moveDownLabel)), canRemove && (renderRemoveControl ? renderRemoveControl({ index: rowIdx, onClick: () => removeRow(rowIdx) }) : /* @__PURE__ */ React.createElement(
947
1554
  Button,
948
1555
  {
949
1556
  variant: "secondary",
950
- size: "xs",
1557
+ size: "md",
951
1558
  onClick: () => removeRow(rowIdx)
952
1559
  },
953
- "Remove"
954
- ))), canAdd && /* @__PURE__ */ React.createElement(Button, { variant: "secondary", size: "sm", onClick: addRow }, "+ Add"), hasError && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, fieldError));
1560
+ removeLabel
1561
+ ))))), canAdd && (renderAddControl ? renderAddControl({ onClick: addRow, count: rows.length }) : /* @__PURE__ */ React.createElement(Link, { onClick: addRow }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "flush" }, /* @__PURE__ */ React.createElement(Icon, { name: "add" }), /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, addLabel)))), repeaterHasError && repeaterErrorMessage && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, repeaterErrorMessage));
955
1562
  }
956
1563
  default:
957
1564
  return /* @__PURE__ */ React.createElement(
@@ -971,15 +1578,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
971
1578
  if (field.width === "full" && columns > 1) return columns;
972
1579
  return 1;
973
1580
  };
974
- const getDependents = (parentField) => visibleFields.filter((f) => f.dependsOn === parentField.name && f.name !== parentField.name);
975
- const isDependent = (field) => field.dependsOn && visibleFields.some((f) => f.name === field.dependsOn && f.name !== field.name);
1581
+ const getDependents = (parentField) => visibleFields.filter(
1582
+ (f) => getDependsOnName(f) === parentField.name && f.name !== parentField.name && getDependsOnDisplay(f) === "grouped"
1583
+ );
1584
+ const isDependent = (field) => getDependsOnName(field) && getDependsOnDisplay(field) === "grouped" && visibleFields.some((f) => f.name === getDependsOnName(field) && f.name !== field.name);
976
1585
  const renderDependentGroup = (parentField, dependents) => {
977
- const firstWithLabel = dependents.find((f) => f.dependsOnLabel) || dependents[0];
978
- const firstWithMessage = dependents.find((f) => f.dependsOnMessage) || dependents[0];
979
- const groupLabel = firstWithLabel.dependsOnLabel || "Dependent properties";
980
- const rawMessage = firstWithMessage.dependsOnMessage;
1586
+ const firstWithLabel = dependents.find((f) => getDependsOnLabel(f)) || dependents[0];
1587
+ const firstWithMessage = dependents.find((f) => getDependsOnMessage(f)) || dependents[0];
1588
+ const groupLabel = getDependsOnLabel(firstWithLabel) || "Dependent properties";
1589
+ const rawMessage = getDependsOnMessage(firstWithMessage);
981
1590
  const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
982
- return /* @__PURE__ */ React.createElement(Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ React.createElement(Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React.createElement(Tooltip, null, tooltipMessage) }, /* @__PURE__ */ React.createElement(Icon, { name: "info" })))), dependents.map((dep) => /* @__PURE__ */ React.createElement(React.Fragment, { key: dep.name }, renderField(dep)))));
1591
+ return /* @__PURE__ */ React.createElement(Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ React.createElement(Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React.createElement(Tooltip, null, tooltipMessage) }, /* @__PURE__ */ React.createElement(Icon, { name: "info" })))), renderFieldSubset(dependents)));
983
1592
  };
984
1593
  const renderGridLayout = (fieldSubset) => {
985
1594
  const fieldList = fieldSubset || visibleFields;
@@ -1072,7 +1681,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1072
1681
  let i = 0;
1073
1682
  while (i < fieldList.length) {
1074
1683
  const field = fieldList[i];
1075
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !field.dependsOn) {
1684
+ if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
1076
1685
  rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
1077
1686
  i += 2;
1078
1687
  } else {
@@ -1108,9 +1717,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1108
1717
  let batch = [];
1109
1718
  const flushBatch = () => {
1110
1719
  if (batch.length === 0) return;
1111
- elements.push(
1112
- /* @__PURE__ */ React.createElement(AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ React.createElement(React.Fragment, { key: f.name }, renderField(f))))
1113
- );
1720
+ const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
1721
+ for (const chunk of chunks) {
1722
+ elements.push(
1723
+ /* @__PURE__ */ React.createElement(AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ React.createElement(React.Fragment, { key: f.name }, renderField(f))))
1724
+ );
1725
+ }
1114
1726
  batch = [];
1115
1727
  };
1116
1728
  for (const field of fieldList) {
@@ -1193,7 +1805,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1193
1805
  );
1194
1806
  if (sec.info) {
1195
1807
  elements.push(
1196
- /* @__PURE__ */ React.createElement(Flex, { key: sec.id, direction: "row", align: "start", gap: "flush" }, /* @__PURE__ */ React.createElement(Box, { flex: 1 }, accordion), /* @__PURE__ */ React.createElement(Link, { overlay: /* @__PURE__ */ React.createElement(Tooltip, null, sec.info) }, /* @__PURE__ */ React.createElement(Icon, { name: "info", size: "sm", screenReaderText: sec.info })))
1808
+ /* @__PURE__ */ React.createElement(Flex, { key: sec.id, direction: "row", align: "start", justify: "start", gap: "flush" }, /* @__PURE__ */ React.createElement(Box, { flex: 1 }, accordion), /* @__PURE__ */ React.createElement(Link, { variant: "dark", overlay: /* @__PURE__ */ React.createElement(Tooltip, null, sec.info) }, /* @__PURE__ */ React.createElement(Icon, { name: "info", size: "sm", screenReaderText: sec.info })))
1197
1809
  );
1198
1810
  } else {
1199
1811
  elements.push(accordion);
@@ -1221,8 +1833,30 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1221
1833
  if (submitPosition === "none" || formReadOnly) return null;
1222
1834
  const isLastStep = !isMultiStep || currentStep === steps.length - 1;
1223
1835
  const isFirstStep = !isMultiStep || currentStep === 0;
1836
+ const buttonContext = {
1837
+ isMultiStep,
1838
+ isFirstStep,
1839
+ isLastStep,
1840
+ currentStep,
1841
+ totalSteps: isMultiStep ? steps.length : 1,
1842
+ disabled,
1843
+ loading: isLoading,
1844
+ labels: {
1845
+ submit: submitButtonLabel,
1846
+ cancel: cancelButtonLabel,
1847
+ back: backButtonLabel,
1848
+ next: nextButtonLabel
1849
+ },
1850
+ onBack: handleBack,
1851
+ onNext: handleNext,
1852
+ onCancel,
1853
+ onSubmit: handleSubmit
1854
+ };
1855
+ if (renderButtonsProp) {
1856
+ return renderButtonsProp(buttonContext);
1857
+ }
1224
1858
  if (isMultiStep) {
1225
- return /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: handleBack, disabled }, "Back") : showCancel ? /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: onCancel, disabled }, cancelLabel) : /* @__PURE__ */ React.createElement(Text, null, " "), /* @__PURE__ */ React.createElement(Inline, { gap: "small" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ React.createElement(
1859
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: handleBack, disabled }, backButtonLabel) : showCancel ? /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel) : /* @__PURE__ */ React.createElement(Text, null, " "), /* @__PURE__ */ React.createElement(Inline, { gap: "small" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ React.createElement(
1226
1860
  LoadingButton,
1227
1861
  {
1228
1862
  variant: submitVariant,
@@ -1230,10 +1864,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1230
1864
  onClick: handleSubmit,
1231
1865
  disabled
1232
1866
  },
1233
- submitLabel
1234
- ) : /* @__PURE__ */ React.createElement(Button, { variant: "primary", onClick: handleNext, disabled }, "Next")));
1867
+ submitButtonLabel
1868
+ ) : /* @__PURE__ */ React.createElement(Button, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
1235
1869
  }
1236
- return /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: onCancel, disabled }, cancelLabel), /* @__PURE__ */ React.createElement(
1870
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ React.createElement(
1237
1871
  LoadingButton,
1238
1872
  {
1239
1873
  variant: submitVariant,
@@ -1242,7 +1876,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1242
1876
  onClick: noFormWrapper ? handleSubmit : void 0,
1243
1877
  disabled
1244
1878
  },
1245
- submitLabel
1879
+ submitButtonLabel
1246
1880
  ));
1247
1881
  };
1248
1882
  const formContent = /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, isMultiStep && showStepIndicator && /* @__PURE__ */ React.createElement(
@@ -1251,7 +1885,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1251
1885
  currentStep,
1252
1886
  stepNames: steps.map((s) => s.title)
1253
1887
  }
1254
- ), formReadOnly && readOnlyMessage && /* @__PURE__ */ React.createElement(Alert, { title: "Read Only", variant: "warning" }, readOnlyMessage), formError && /* @__PURE__ */ React.createElement(Alert, { title: "Error", variant: "danger" }, typeof formError === "string" ? formError : void 0), formSuccess && /* @__PURE__ */ React.createElement(Alert, { title: "Success", variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
1888
+ ), formReadOnly && readOnlyMessage && /* @__PURE__ */ React.createElement(Alert, { title: readOnlyTitle, variant: "warning" }, readOnlyMessage), !addAlert && formError && /* @__PURE__ */ React.createElement(Alert, { title: errorTitle, variant: "danger" }, typeof formError === "string" ? formError : void 0), !addAlert && formSuccess && /* @__PURE__ */ React.createElement(Alert, { title: successTitle, variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
1255
1889
  values: formValues,
1256
1890
  goNext: handleNext,
1257
1891
  goBack: handleBack,
@@ -1263,7 +1897,15 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1263
1897
  if (noFormWrapper) {
1264
1898
  return formContent;
1265
1899
  }
1266
- return /* @__PURE__ */ React.createElement(Form, { onSubmit: handleSubmit, autoComplete: props.autoComplete }, formContent);
1900
+ return /* @__PURE__ */ React.createElement(
1901
+ Form,
1902
+ {
1903
+ ...formProps || {},
1904
+ onSubmit: handleSubmit,
1905
+ autoComplete
1906
+ },
1907
+ formContent
1908
+ );
1267
1909
  });
1268
1910
  export {
1269
1911
  FormBuilder,