@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 +58 -0
- package/dist/index.d.ts +103 -3
- package/dist/index.js +228 -18
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
407
|
-
|
|
407
|
+
return /* @__PURE__ */ React5.createElement(
|
|
408
|
+
"div",
|
|
408
409
|
{
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
4545
|
-
const lastRenderTimeRef =
|
|
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.
|
|
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/",
|