@rachelallyson/hero-hook-form 2.7.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.9.0] - 2026-01-22
6
+
7
+ ### Added
8
+
9
+ - **Documentation: Memory-Safe Forms Guide**: Comprehensive guide for preventing Cypress memory leaks
10
+ - Complete migration guide from problematic to memory-safe conditional field arrays
11
+ - Working code examples and best practices
12
+ - Troubleshooting section for memory issues
13
+ - Performance optimization recommendations
14
+
15
+ - **Examples: Question Form Demo**: Working demonstration of memory-safe conditional field arrays
16
+ - Interactive example showing MULTIPLE_CHOICE question types
17
+ - Before/after code comparisons
18
+ - Real-world implementation patterns
19
+ - Cypress e2e test coverage
20
+
21
+ - **Enhanced Testing**: Additional test coverage and refinements
22
+ - Question form e2e tests demonstrating memory safety
23
+ - Updated Cypress test patterns for memory optimization
24
+ - Comprehensive validation of conditional field array behavior
25
+
26
+ ### Fixed
27
+
28
+ - **Test File Naming**: Corrected Cypress test file extensions (`.cy.ts` vs `.spec.ts`)
29
+ - **Conditional Rendering**: Improved formatting and error handling in conditional field components
30
+
31
+ ## [2.8.0] - 2026-01-22
32
+
33
+ ### Added
34
+
35
+ - **Field Array Memory Leak Fixes**: Comprehensive solution for Cypress Electron renderer memory issues
36
+ - `FormFieldHelpers.conditionalFieldArray()` - Memory-safe conditional field arrays that prevent register/unregister cycles
37
+ - `alwaysRegistered` prop for `FieldArrayField` - Keeps fields registered but conditionally renders UI
38
+ - `useLazyFieldRegistration` and `useLazyFieldArrayRegistration` hooks - Lazy registration for better initial memory usage
39
+ - `fieldArrayMemory` utilities - Memory cleanup helpers with garbage collection hints
40
+ - Cypress memory optimizations - `experimentalMemoryManagement` and reduced memory retention
41
+
42
+ - **Performance Monitoring**: Enhanced field array performance tracking
43
+ - Memory usage monitoring for field array operations
44
+ - Performance metrics collection for long-running tests
45
+ - Garbage collection suggestions for memory-intensive operations
46
+
47
+ ### Fixed
48
+
49
+ - **Critical Bug Fix: Input Name Attributes**: Fixed missing `name` prop forwarding in `InputField` component
50
+ - `CoercedInput` now properly passes `name={field.name}` to HeroUI Input components
51
+ - Fixes accessibility issues where form inputs had no name attribute in DOM
52
+ - Critical for form automation tools, accessibility compliance, and proper form submission
53
+ - Affects all input types (text, email, password, tel, number, etc.) in ZodForm
54
+
55
+ ### Internal
56
+
57
+ - **Architecture Cleanup: Simplified HeroUI Integration**: Removed redundant dual-build system and `/react` subpath
58
+ - Eliminated duplicate `react/fields/` components and `tsconfig.react.json`
59
+ - Streamlined to single `#ui` alias approach that works with both individual `@heroui/*` packages and `@heroui/react`
60
+ - `@heroui/react` re-exports all components, making separate configurations unnecessary
61
+ - Cleaner codebase with same functionality and flexibility for users
62
+
5
63
  ## [2.7.2] - 2026-01-21
6
64
 
7
65
  ### Fixed
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import React$1, { ComponentProps } from 'react';
2
2
  import { Button } from '@heroui/react';
3
3
  import * as react_hook_form from 'react-hook-form';
4
- import { FieldValues, Path, RegisterOptions, ArrayPath, Control, UseFormReturn, FieldErrors, UseFormProps, SubmitHandler, DefaultValues, UseFormSetError, FieldArrayWithId } from 'react-hook-form';
4
+ import { FieldValues, Path, RegisterOptions, ArrayPath, Control, UseFormReturn, FieldErrors, UseFormProps, SubmitHandler, DefaultValues, UseFormSetError, FieldPath, FieldArrayWithId } from 'react-hook-form';
5
5
  export { UseFormReturn, useFormContext } from 'react-hook-form';
6
6
  import * as zod from 'zod';
7
7
  import { z, ZodSchema } from 'zod';
@@ -197,6 +197,8 @@ interface FieldArrayConfig<TFieldValues extends FieldValues> extends Omit<BaseFo
197
197
  };
198
198
  /** Function to create default item when adding new array item */
199
199
  defaultItem?: () => any;
200
+ /** Whether this field array should always be registered (for conditional rendering) */
201
+ alwaysRegistered?: boolean;
200
202
  /** Custom render function for array items */
201
203
  renderItem?: (props: {
202
204
  /** Item index (0-based) */
@@ -2597,6 +2599,41 @@ declare const FormFieldHelpers: {
2597
2599
  * ```
2598
2600
  */
2599
2601
  conditional: <T extends FieldValues = FieldValues>(name: Path<T>, condition: (formData: Partial<T>) => boolean, field: ZodFormFieldConfig<T>) => ZodFormFieldConfig<T>;
2602
+ /**
2603
+ * Create a conditional field array that avoids memory leaks in Cypress tests.
2604
+ *
2605
+ * This helper creates a field array that is always registered but conditionally
2606
+ * rendered, preventing the register/unregister cycles that cause memory
2607
+ * accumulation in Cypress Electron renderer.
2608
+ *
2609
+ * @param name - The field array name
2610
+ * @param condition - Function that determines if the field array should be visible
2611
+ * @param label - Display label for the field array
2612
+ * @param fields - Field configurations for array items
2613
+ * @param options - Additional field array options
2614
+ *
2615
+ * @example
2616
+ * ```tsx
2617
+ * // Memory-safe conditional field array for multiple choice options
2618
+ * FormFieldHelpers.conditionalFieldArray(
2619
+ * "choices",
2620
+ * (data) => data.questionType === 'MULTIPLE_CHOICE',
2621
+ * "Answer Choices",
2622
+ * [
2623
+ * FormFieldHelpers.input("text", "Choice Text"),
2624
+ * FormFieldHelpers.checkbox("isCorrect", "Correct Answer"),
2625
+ * ]
2626
+ * )
2627
+ * ```
2628
+ */
2629
+ conditionalFieldArray: <T extends FieldValues = FieldValues>(name: ArrayPath<T>, condition: (formData: Partial<T>) => boolean, label: string, fields: ZodFormFieldConfig<T>[], options?: {
2630
+ min?: number;
2631
+ max?: number;
2632
+ addButtonText?: string;
2633
+ removeButtonText?: string;
2634
+ enableReordering?: boolean;
2635
+ defaultItem?: () => any;
2636
+ }) => ZodFormFieldConfig<T>;
2600
2637
  /**
2601
2638
  * Create a content field for headers, questions, or custom content between fields
2602
2639
  *
@@ -3639,6 +3676,33 @@ declare function useTypeInferredForm<T extends FieldValues>(formConfig: {
3639
3676
  fields: ZodFormFieldConfig<T>[];
3640
3677
  }, options?: UseInferredFormOptions<T>): UseFormReturn<T>;
3641
3678
 
3679
+ /**
3680
+ * Hook for lazy field registration to reduce initial memory usage.
3681
+ *
3682
+ * This hook registers fields only when they become active (e.g., when a condition is met),
3683
+ * preventing the memory overhead of always-registered fields while avoiding
3684
+ * register/unregister cycles that cause memory leaks in Cypress.
3685
+ *
3686
+ * @param fieldName - The field name to potentially register
3687
+ * @param shouldRegister - Function that determines if the field should be registered
3688
+ * @param defaultValue - Default value for the field when registered
3689
+ * @param rules - Validation rules for the field
3690
+ */
3691
+ declare function useLazyFieldRegistration<TFieldValues extends FieldValues>(fieldName: FieldPath<TFieldValues>, shouldRegister: () => boolean, defaultValue?: any, rules?: any): {
3692
+ currentValue: undefined;
3693
+ isRegistered: boolean;
3694
+ };
3695
+ /**
3696
+ * Hook for lazy field array registration.
3697
+ *
3698
+ * Similar to useLazyFieldRegistration but specifically for field arrays,
3699
+ * which have more complex registration requirements.
3700
+ */
3701
+ declare function useLazyFieldArrayRegistration<TFieldValues extends FieldValues>(arrayName: FieldPath<TFieldValues>, shouldRegister: () => boolean, defaultValue?: any[]): {
3702
+ currentValue: any;
3703
+ isRegistered: boolean;
3704
+ };
3705
+
3642
3706
  /**
3643
3707
  * Props for the ConditionalField component.
3644
3708
  *
@@ -3718,6 +3782,8 @@ declare function ContentField<TFieldValues extends FieldValues>({ config, form,
3718
3782
  interface FieldArrayFieldProps<TFieldValues extends FieldValues> {
3719
3783
  config: FieldArrayConfig<TFieldValues>;
3720
3784
  className?: string;
3785
+ /** Whether this field array should always be registered (for conditional rendering) */
3786
+ alwaysRegistered?: boolean;
3721
3787
  }
3722
3788
  /**
3723
3789
  * Field array component for dynamic repeating field groups.
@@ -3834,7 +3900,7 @@ interface FieldArrayFieldProps<TFieldValues extends FieldValues> {
3834
3900
  * @see {@link createFieldArrayCustomConfig} for advanced custom rendering
3835
3901
  * @category Fields
3836
3902
  */
3837
- declare function FieldArrayField<TFieldValues extends FieldValues>({ className, config, }: FieldArrayFieldProps<TFieldValues>): React$1.JSX.Element | null;
3903
+ declare function FieldArrayField<TFieldValues extends FieldValues>({ alwaysRegistered, className, config, }: FieldArrayFieldProps<TFieldValues>): React$1.JSX.Element | null;
3838
3904
 
3839
3905
  /**
3840
3906
  * Props for the DynamicSectionField component.
@@ -4126,6 +4192,40 @@ interface CreateFieldArrayCustomConfigOptions<TFieldValues extends FieldValues>
4126
4192
  */
4127
4193
  declare function createFieldArrayCustomConfig<TFieldValues extends FieldValues>(options: CreateFieldArrayCustomConfigOptions<TFieldValues>): CustomFieldConfig<TFieldValues>;
4128
4194
 
4195
+ /**
4196
+ * Memory management utilities for field arrays to prevent memory leaks
4197
+ * in Cypress tests and long-running applications.
4198
+ */
4199
+ /**
4200
+ * Hook to clean up field array memory when component unmounts.
4201
+ * Helps prevent memory accumulation in Cypress Electron renderer.
4202
+ */
4203
+ declare function useFieldArrayMemoryCleanup<TFieldValues extends FieldValues>(arrayName: FieldPath<TFieldValues>): {
4204
+ cleanup: () => void;
4205
+ };
4206
+ /**
4207
+ * Utility to force garbage collection hints for Cypress tests.
4208
+ * Only effective when experimentalMemoryManagement is enabled.
4209
+ */
4210
+ declare function suggestGarbageCollection(): void;
4211
+ /**
4212
+ * Memory-safe field array operations that include cleanup hints.
4213
+ */
4214
+ declare const memorySafeFieldArray: {
4215
+ /**
4216
+ * Add items to a field array with memory management.
4217
+ */
4218
+ addItems: <TFieldValues extends FieldValues>(append: (value: any) => void, items: any[], onProgress?: (addedCount: number) => void) => void;
4219
+ /**
4220
+ * Clear entire field array with memory cleanup.
4221
+ */
4222
+ clearArray: <TFieldValues extends FieldValues>(setValue: (name: FieldPath<TFieldValues>, value: any) => void, arrayName: FieldPath<TFieldValues>) => void;
4223
+ /**
4224
+ * Remove items from a field array with memory cleanup.
4225
+ */
4226
+ removeItems: <TFieldValues extends FieldValues>(remove: (index: number) => void, indices: number[]) => void;
4227
+ };
4228
+
4129
4229
  /**
4130
4230
  * Common validation patterns for forms
4131
4231
  */
@@ -4209,4 +4309,4 @@ declare const validationUtils: {
4209
4309
  }>;
4210
4310
  };
4211
4311
 
4212
- export { AdvancedFieldBuilder, type ArraySyncOptions, type ArraySyncResult, AutocompleteField, type AutocompleteFieldProps, type AutocompleteOption, type BaseFormFieldConfig, BasicFormBuilder, type BooleanFieldConfig, type ButtonDefaults, type CheckboxDefaults, CheckboxField, type CheckboxFieldProps, CheckboxGroupField, type CheckboxGroupFieldConfig, type CheckboxGroupFieldProps, type CommonFieldDefaults, CommonFields, ConditionalField, type ConditionalFieldConfig, type ConditionalFieldProps, type ConditionalValidation, ConfigurableForm, ContentField, type ContentFieldConfig, type CreateFieldArrayCustomConfigOptions, type CustomFieldConfig, DateField, type DateFieldConfig, type DateFieldProps, type DateInputDefaults, type DynamicSectionConfig, DynamicSectionField, type DynamicSectionFieldProps, type EnhancedFormState, FieldArrayBuilder, type FieldArrayConfig, FieldArrayField, type FieldArrayFieldProps, FieldArrayItemBuilder, type FieldBaseProps, type FieldCreationParams, type FieldGroup, FileField, type FileFieldConfig, type FileFieldProps, FontPickerField, type FontPickerFieldConfig, type FontPickerFieldProps, type FormConfig, FormField, type FormFieldConfig, FormFieldHelpers, type FormFieldType, type FormProps, FormProvider, FormStatus, type FormStatusProps, type FormStep, type FormSubmissionState, type FormTestUtils, FormToast, type FormToastProps, type FormValidationError, type HeroHookFormDefaultsConfig, HeroHookFormProvider, type HeroHookFormProviderProps, type InputDefaults, InputField, type InputFieldProps, type RadioFieldConfig, type RadioGroupDefaults, RadioGroupField, type RadioGroupFieldProps, type SelectDefaults, SelectField, type SelectFieldProps, ServerActionForm, type ServerFieldError, type ServerFormError, SimpleForm, type SimpleFormProps, type SliderDefaults, SliderField, type SliderFieldConfig, type SliderFieldProps, type StringArrayFieldConfig, type StringFieldConfig, SubmitButton, type SubmitButtonProps, type SwitchDefaults, SwitchField, type SwitchFieldProps, type TextareaDefaults, TextareaField, type TextareaFieldProps, TypeInferredBuilder, type UseDebouncedValidationOptions, type UseEnhancedFormStateOptions, type UseInferredFormOptions, type ValidationUtils, type WithControl, type WizardFormConfig, ZodForm, type ZodFormConfig, type ZodFormFieldConfig, applyServerErrors, asyncValidation, commonValidations, createAdvancedBuilder, createBasicFormBuilder, createDateSchema, createEmailSchema, createField, createFieldArrayBuilder, createFieldArrayCustomConfig, createFieldArrayItemBuilder, createFileSchema, createFormTestUtils, createFutureDateSchema, createMaxLengthSchema, createMinLengthSchema, createMockFormData, createMockFormErrors, createNestedPathBuilder, createNumberRangeSchema, createOptimizedFieldHandler, createPasswordSchema, createPastDateSchema, createPhoneSchema, createRequiredCheckboxSchema, createRequiredSchema, createTypeInferredBuilder, createUrlSchema, createZodFormConfig, crossFieldValidation, debounce, deepEqual, defineInferredForm, errorMessages, field, getFieldError, getFormErrors, hasFieldError, hasFormErrors, pathToString, serverValidation, shallowEqual, simulateFieldInput, simulateFormSubmission, syncArrays, throttle, useDebouncedFieldValidation, useDebouncedValidation, useEnhancedFormState, useFormHelper, useHeroForm, useHeroHookFormDefaults, useInferredForm, useMemoizedCallback, useMemoizedFieldProps, usePerformanceMonitor, useTypeInferredForm, useZodForm, validationPatterns, validationUtils, waitForFormState };
4312
+ export { AdvancedFieldBuilder, type ArraySyncOptions, type ArraySyncResult, AutocompleteField, type AutocompleteFieldProps, type AutocompleteOption, type BaseFormFieldConfig, BasicFormBuilder, type BooleanFieldConfig, type ButtonDefaults, type CheckboxDefaults, CheckboxField, type CheckboxFieldProps, CheckboxGroupField, type CheckboxGroupFieldConfig, type CheckboxGroupFieldProps, type CommonFieldDefaults, CommonFields, ConditionalField, type ConditionalFieldConfig, type ConditionalFieldProps, type ConditionalValidation, ConfigurableForm, ContentField, type ContentFieldConfig, type CreateFieldArrayCustomConfigOptions, type CustomFieldConfig, DateField, type DateFieldConfig, type DateFieldProps, type DateInputDefaults, type DynamicSectionConfig, DynamicSectionField, type DynamicSectionFieldProps, type EnhancedFormState, FieldArrayBuilder, type FieldArrayConfig, FieldArrayField, type FieldArrayFieldProps, FieldArrayItemBuilder, type FieldBaseProps, type FieldCreationParams, type FieldGroup, FileField, type FileFieldConfig, type FileFieldProps, FontPickerField, type FontPickerFieldConfig, type FontPickerFieldProps, type FormConfig, FormField, type FormFieldConfig, FormFieldHelpers, type FormFieldType, type FormProps, FormProvider, FormStatus, type FormStatusProps, type FormStep, type FormSubmissionState, type FormTestUtils, FormToast, type FormToastProps, type FormValidationError, type HeroHookFormDefaultsConfig, HeroHookFormProvider, type HeroHookFormProviderProps, type InputDefaults, InputField, type InputFieldProps, type RadioFieldConfig, type RadioGroupDefaults, RadioGroupField, type RadioGroupFieldProps, type SelectDefaults, SelectField, type SelectFieldProps, ServerActionForm, type ServerFieldError, type ServerFormError, SimpleForm, type SimpleFormProps, type SliderDefaults, SliderField, type SliderFieldConfig, type SliderFieldProps, type StringArrayFieldConfig, type StringFieldConfig, SubmitButton, type SubmitButtonProps, type SwitchDefaults, SwitchField, type SwitchFieldProps, type TextareaDefaults, TextareaField, type TextareaFieldProps, TypeInferredBuilder, type UseDebouncedValidationOptions, type UseEnhancedFormStateOptions, type UseInferredFormOptions, type ValidationUtils, type WithControl, type WizardFormConfig, ZodForm, type ZodFormConfig, type ZodFormFieldConfig, applyServerErrors, asyncValidation, commonValidations, createAdvancedBuilder, createBasicFormBuilder, createDateSchema, createEmailSchema, createField, createFieldArrayBuilder, createFieldArrayCustomConfig, createFieldArrayItemBuilder, createFileSchema, createFormTestUtils, createFutureDateSchema, createMaxLengthSchema, createMinLengthSchema, createMockFormData, createMockFormErrors, createNestedPathBuilder, createNumberRangeSchema, createOptimizedFieldHandler, createPasswordSchema, createPastDateSchema, createPhoneSchema, createRequiredCheckboxSchema, createRequiredSchema, createTypeInferredBuilder, createUrlSchema, createZodFormConfig, crossFieldValidation, debounce, deepEqual, defineInferredForm, errorMessages, field, getFieldError, getFormErrors, hasFieldError, hasFormErrors, memorySafeFieldArray, pathToString, serverValidation, shallowEqual, simulateFieldInput, simulateFormSubmission, suggestGarbageCollection, syncArrays, throttle, useDebouncedFieldValidation, useDebouncedValidation, useEnhancedFormState, useFieldArrayMemoryCleanup, useFormHelper, useHeroForm, useHeroHookFormDefaults, useInferredForm, useLazyFieldArrayRegistration, useLazyFieldRegistration, useMemoizedCallback, useMemoizedFieldProps, usePerformanceMonitor, useTypeInferredForm, useZodForm, validationPatterns, validationUtils, waitForFormState };
package/dist/index.js CHANGED
@@ -400,22 +400,30 @@ function ConditionalField({
400
400
  const form = useFormContext();
401
401
  const formValues = useWatch({ control });
402
402
  const shouldShow = condition(formValues);
403
- if (!shouldShow) {
403
+ const isAlwaysRegisteredFieldArray = field2 && typeof field2 === "object" && "type" in field2 && field2.type === "fieldArray" && "alwaysRegistered" in field2 && field2.alwaysRegistered === true;
404
+ if (!shouldShow && !isAlwaysRegisteredFieldArray) {
404
405
  return null;
405
406
  }
406
- return /* @__PURE__ */ React5.createElement("div", { className }, /* @__PURE__ */ React5.createElement(
407
- FormField,
407
+ return /* @__PURE__ */ React5.createElement(
408
+ "div",
408
409
  {
409
- config: field2,
410
- form,
411
- submissionState: {
412
- error: void 0,
413
- isSubmitted: false,
414
- isSubmitting: false,
415
- isSuccess: false
410
+ className,
411
+ style: !shouldShow && isAlwaysRegisteredFieldArray ? { display: "none" } : void 0
412
+ },
413
+ /* @__PURE__ */ React5.createElement(
414
+ FormField,
415
+ {
416
+ config: field2,
417
+ form,
418
+ submissionState: {
419
+ error: void 0,
420
+ isSubmitted: false,
421
+ isSubmitting: false,
422
+ isSuccess: false
423
+ }
416
424
  }
417
- }
418
- ));
425
+ )
426
+ );
419
427
  }
420
428
 
421
429
  // src/fields/ContentField.tsx
@@ -530,6 +538,7 @@ import React9 from "react";
530
538
  import { useFieldArray, useFormContext as useFormContext3 } from "react-hook-form";
531
539
  import { Button as Button2 } from "@heroui/react";
532
540
  function FieldArrayField({
541
+ alwaysRegistered = false,
533
542
  className,
534
543
  config
535
544
  }) {
@@ -557,6 +566,9 @@ function FieldArrayField({
557
566
  });
558
567
  const canAdd = fields.length < max;
559
568
  const canRemove = fields.length > min;
569
+ if (alwaysRegistered && fields.length === 0) {
570
+ return null;
571
+ }
560
572
  const getFieldPath = (fieldName, itemIndex) => {
561
573
  return `${String(name)}.${itemIndex}.${fieldName}`;
562
574
  };
@@ -1359,8 +1371,13 @@ function FormFieldComponent({
1359
1371
  );
1360
1372
  }
1361
1373
  }
1374
+ let shouldRenderConditionalField = true;
1362
1375
  if ("condition" in fieldConfig && fieldConfig.condition && !fieldConfig.condition(watchedValues)) {
1363
- return null;
1376
+ if ("field" in fieldConfig && fieldConfig.field && typeof fieldConfig.field === "object" && "type" in fieldConfig.field && fieldConfig.field.type === "fieldArray" && "alwaysRegistered" in fieldConfig.field && fieldConfig.field.alwaysRegistered === true) {
1377
+ shouldRenderConditionalField = true;
1378
+ } else {
1379
+ return null;
1380
+ }
1364
1381
  }
1365
1382
  if ("dependsOn" in fieldConfig && fieldConfig.dependsOn) {
1366
1383
  const dependentValue = get(watchedValues, fieldConfig.dependsOn);
@@ -1614,7 +1631,8 @@ function FormFieldComponent({
1614
1631
  FieldArrayField,
1615
1632
  {
1616
1633
  config: fieldArrayConfig,
1617
- className: fieldArrayConfig.className
1634
+ className: fieldArrayConfig.className,
1635
+ alwaysRegistered: "alwaysRegistered" in fieldConfig ? fieldConfig.alwaysRegistered : false
1618
1636
  }
1619
1637
  );
1620
1638
  }
@@ -3256,6 +3274,57 @@ var FormFieldHelpers = {
3256
3274
  };
3257
3275
  return config;
3258
3276
  },
3277
+ /**
3278
+ * Create a conditional field array that avoids memory leaks in Cypress tests.
3279
+ *
3280
+ * This helper creates a field array that is always registered but conditionally
3281
+ * rendered, preventing the register/unregister cycles that cause memory
3282
+ * accumulation in Cypress Electron renderer.
3283
+ *
3284
+ * @param name - The field array name
3285
+ * @param condition - Function that determines if the field array should be visible
3286
+ * @param label - Display label for the field array
3287
+ * @param fields - Field configurations for array items
3288
+ * @param options - Additional field array options
3289
+ *
3290
+ * @example
3291
+ * ```tsx
3292
+ * // Memory-safe conditional field array for multiple choice options
3293
+ * FormFieldHelpers.conditionalFieldArray(
3294
+ * "choices",
3295
+ * (data) => data.questionType === 'MULTIPLE_CHOICE',
3296
+ * "Answer Choices",
3297
+ * [
3298
+ * FormFieldHelpers.input("text", "Choice Text"),
3299
+ * FormFieldHelpers.checkbox("isCorrect", "Correct Answer"),
3300
+ * ]
3301
+ * )
3302
+ * ```
3303
+ */
3304
+ conditionalFieldArray: (name, condition, label, fields, options) => {
3305
+ const fieldArrayConfig = {
3306
+ addButtonText: options?.addButtonText ?? "Add Item",
3307
+ alwaysRegistered: true,
3308
+ defaultItem: options?.defaultItem,
3309
+ enableReordering: options?.enableReordering ?? false,
3310
+ fields,
3311
+ label,
3312
+ max: options?.max ?? 10,
3313
+ // This prevents register/unregister cycles
3314
+ min: options?.min ?? 0,
3315
+ name,
3316
+ removeButtonText: options?.removeButtonText ?? "Remove",
3317
+ type: "fieldArray"
3318
+ };
3319
+ const config = {
3320
+ condition,
3321
+ field: fieldArrayConfig,
3322
+ name,
3323
+ // ArrayPath extends Path, so this is safe
3324
+ type: "conditional"
3325
+ };
3326
+ return config;
3327
+ },
3259
3328
  /**
3260
3329
  * Create a content field for headers, questions, or custom content between fields
3261
3330
  *
@@ -4475,8 +4544,74 @@ function useTypeInferredForm(formConfig, options = {}) {
4475
4544
  return useInferredForm(formConfig.schema, formConfig.fields, options);
4476
4545
  }
4477
4546
 
4547
+ // src/hooks/useLazyFieldRegistration.ts
4548
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
4549
+ import { useFormContext as useFormContext6 } from "react-hook-form";
4550
+ function useLazyFieldRegistration(fieldName, shouldRegister, defaultValue, rules) {
4551
+ const { register, setValue, unregister, watch } = useFormContext6();
4552
+ const [isRegistered, setIsRegistered] = useState4(false);
4553
+ const hasCheckedRegistration = useRef2(false);
4554
+ const conditionMet = shouldRegister();
4555
+ useEffect3(() => {
4556
+ if (conditionMet && !isRegistered) {
4557
+ register(fieldName, rules);
4558
+ if (defaultValue !== void 0) {
4559
+ setValue(fieldName, defaultValue);
4560
+ }
4561
+ setIsRegistered(true);
4562
+ hasCheckedRegistration.current = true;
4563
+ } else if (!conditionMet && isRegistered && hasCheckedRegistration.current) {
4564
+ unregister(fieldName);
4565
+ setIsRegistered(false);
4566
+ }
4567
+ }, [
4568
+ conditionMet,
4569
+ fieldName,
4570
+ register,
4571
+ unregister,
4572
+ setValue,
4573
+ rules,
4574
+ defaultValue,
4575
+ isRegistered
4576
+ ]);
4577
+ return {
4578
+ currentValue: isRegistered ? watch(fieldName) : void 0,
4579
+ isRegistered
4580
+ };
4581
+ }
4582
+ function useLazyFieldArrayRegistration(arrayName, shouldRegister, defaultValue = []) {
4583
+ const { setValue, watch } = useFormContext6();
4584
+ const [isRegistered, setIsRegistered] = useState4(false);
4585
+ const hasCheckedRegistration = useRef2(false);
4586
+ const conditionMet = shouldRegister();
4587
+ const currentValue = watch(arrayName);
4588
+ useEffect3(() => {
4589
+ if (conditionMet && !isRegistered) {
4590
+ if (!currentValue || !Array.isArray(currentValue)) {
4591
+ setValue(arrayName, defaultValue);
4592
+ }
4593
+ setIsRegistered(true);
4594
+ hasCheckedRegistration.current = true;
4595
+ } else if (!conditionMet && isRegistered && hasCheckedRegistration.current) {
4596
+ setValue(arrayName, []);
4597
+ setIsRegistered(false);
4598
+ }
4599
+ }, [
4600
+ conditionMet,
4601
+ arrayName,
4602
+ setValue,
4603
+ defaultValue,
4604
+ isRegistered,
4605
+ currentValue
4606
+ ]);
4607
+ return {
4608
+ currentValue: isRegistered ? currentValue : [],
4609
+ isRegistered
4610
+ };
4611
+ }
4612
+
4478
4613
  // src/utils/performance.ts
4479
- import { useCallback as useCallback3, useMemo as useMemo2, useRef as useRef2 } from "react";
4614
+ import { useCallback as useCallback3, useMemo as useMemo2, useRef as useRef3 } from "react";
4480
4615
  function debounce(func, delay) {
4481
4616
  let timeoutId;
4482
4617
  return (...args) => {
@@ -4495,7 +4630,7 @@ function throttle(func, limit) {
4495
4630
  };
4496
4631
  }
4497
4632
  function useMemoizedCallback(callback, deps) {
4498
- const callbackRef = useRef2(callback);
4633
+ const callbackRef = useRef3(callback);
4499
4634
  callbackRef.current = callback;
4500
4635
  return useCallback3(
4501
4636
  ((...args) => callbackRef.current(...args)),
@@ -4541,8 +4676,8 @@ function deepEqual(prevProps, nextProps) {
4541
4676
  return true;
4542
4677
  }
4543
4678
  function usePerformanceMonitor(componentName, enabled = process.env.NODE_ENV === "development") {
4544
- const renderCountRef = useRef2(0);
4545
- const lastRenderTimeRef = useRef2(Date.now());
4679
+ const renderCountRef = useRef3(0);
4680
+ const lastRenderTimeRef = useRef3(Date.now());
4546
4681
  if (enabled) {
4547
4682
  renderCountRef.current += 1;
4548
4683
  const now = Date.now();
@@ -4702,6 +4837,76 @@ function createFieldArrayCustomConfig(options) {
4702
4837
  };
4703
4838
  }
4704
4839
 
4840
+ // src/utils/fieldArrayMemory.ts
4841
+ import { useFormContext as useFormContext7 } from "react-hook-form";
4842
+ function useFieldArrayMemoryCleanup(arrayName) {
4843
+ const { reset, unregister } = useFormContext7();
4844
+ const cleanup = () => {
4845
+ try {
4846
+ reset((formValues) => ({
4847
+ ...formValues,
4848
+ [arrayName]: []
4849
+ }));
4850
+ setTimeout(() => {
4851
+ try {
4852
+ unregister(arrayName);
4853
+ } catch (error) {
4854
+ console.debug("Field array cleanup unregister failed:", error);
4855
+ }
4856
+ }, 0);
4857
+ } catch (error) {
4858
+ console.debug("Field array cleanup failed:", error);
4859
+ }
4860
+ };
4861
+ return { cleanup };
4862
+ }
4863
+ function suggestGarbageCollection() {
4864
+ if (typeof window !== "undefined" && "gc" in window) {
4865
+ try {
4866
+ window.gc();
4867
+ } catch (error) {
4868
+ }
4869
+ }
4870
+ }
4871
+ var memorySafeFieldArray = {
4872
+ /**
4873
+ * Add items to a field array with memory management.
4874
+ */
4875
+ addItems: (append, items, onProgress) => {
4876
+ let addedCount = 0;
4877
+ const batchSize = 10;
4878
+ for (let i = 0; i < items.length; i += batchSize) {
4879
+ const batch = items.slice(i, i + batchSize);
4880
+ batch.forEach((item) => {
4881
+ append(item);
4882
+ addedCount++;
4883
+ });
4884
+ if (addedCount % batchSize === 0) {
4885
+ suggestGarbageCollection();
4886
+ onProgress?.(addedCount);
4887
+ }
4888
+ }
4889
+ suggestGarbageCollection();
4890
+ },
4891
+ /**
4892
+ * Clear entire field array with memory cleanup.
4893
+ */
4894
+ clearArray: (setValue, arrayName) => {
4895
+ setValue(arrayName, []);
4896
+ suggestGarbageCollection();
4897
+ },
4898
+ /**
4899
+ * Remove items from a field array with memory cleanup.
4900
+ */
4901
+ removeItems: (remove, indices) => {
4902
+ const sortedIndices = [...indices].sort((a, b) => b - a);
4903
+ sortedIndices.forEach((index) => {
4904
+ remove(index);
4905
+ });
4906
+ suggestGarbageCollection();
4907
+ }
4908
+ };
4909
+
4705
4910
  // src/builders/validation-helpers.ts
4706
4911
  import { z as z4 } from "zod";
4707
4912
  var validationPatterns = {
@@ -4922,21 +5127,26 @@ export {
4922
5127
  getFormErrors,
4923
5128
  hasFieldError,
4924
5129
  hasFormErrors,
5130
+ memorySafeFieldArray,
4925
5131
  pathToString,
4926
5132
  serverValidation,
4927
5133
  shallowEqual,
4928
5134
  simulateFieldInput,
4929
5135
  simulateFormSubmission,
5136
+ suggestGarbageCollection,
4930
5137
  syncArrays,
4931
5138
  throttle,
4932
5139
  useDebouncedFieldValidation,
4933
5140
  useDebouncedValidation,
4934
5141
  useEnhancedFormState,
5142
+ useFieldArrayMemoryCleanup,
4935
5143
  useFormContext5 as useFormContext,
4936
5144
  useFormHelper,
4937
5145
  useHeroForm,
4938
5146
  useHeroHookFormDefaults,
4939
5147
  useInferredForm,
5148
+ useLazyFieldArrayRegistration,
5149
+ useLazyFieldRegistration,
4940
5150
  useMemoizedCallback,
4941
5151
  useMemoizedFieldProps,
4942
5152
  usePerformanceMonitor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rachelallyson/hero-hook-form",
3
- "version": "2.7.2",
3
+ "version": "2.9.0",
4
4
  "description": "Typed form helpers that combine React Hook Form and HeroUI components.",
5
5
  "author": "Rachel Higley",
6
6
  "homepage": "https://rachelallyson.github.io/hero-hook-form/",