11'use client' ;
22/* eslint-disable react-hooks/exhaustive-deps */
33/* eslint-disable react/hook-use-state */
4+
5+ /**
6+ * Field component for form input fields with validation and state management
7+ * Supports both controlled and uncontrolled modes with flexible event handling
8+ */
9+
410import { Slot } from '@radix-ui/react-slot' ;
511import type { ReactElement } from 'react' ;
612import { useEffect , useId , useRef , useState } from 'react' ;
713import type { AllPathsKeys } from 'skyroc-type-utils' ;
814import { capitalize , getEventValue , isEqual , isNil , omitUndefined , toArray } from 'skyroc-utils' ;
915
10- import { ChangeTag } from '../../form-core/event' ;
11- import type { StoreValue } from '../../form-core/types' ;
16+ import type { EventArgs , StoreValue } from '../../form-core/types' ;
1217import type { Rule } from '../../form-core/validation' ;
13- import type { EventArgs } from '../../types/shared-types' ;
1418import type { InternalFormInstance } from '../hooks/FieldContext' ;
1519import { useFieldContext } from '../hooks/FieldContext' ;
1620
1721export type FieldProps < Values > = {
22+ /** Child element to render as the form field */
1823 children ?: ReactElement ;
24+ /** Control mode: 'controlled' for React controlled components, 'uncontrolled' for DOM-based */
1925 controlMode ?: 'controlled' | 'uncontrolled' ;
26+ /** Custom function to extract value from event arguments */
2027 getValueFromEvent ?: ( ...args : EventArgs ) => StoreValue ;
28+ /** Function to transform value before passing to child component */
2129 getValueProps ?: ( value : StoreValue ) => StoreValue ;
30+ /** Initial value for the field */
2231 initialValue ?: StoreValue ;
32+ /** Field name path in the form */
2333 name : AllPathsKeys < Values > ;
34+ /** Function to normalize/transform the value after change */
2435 normalize ?: ( value : StoreValue , prevValue : StoreValue , allValues : Values ) => StoreValue ;
36+ /** Whether to preserve field value after component unmount */
2537 preserve ?: boolean ;
38+ /** Validation rules for the field */
2639 rules ?: Rule [ ] ;
40+ /** Event name that triggers value change (default: 'onChange') */
2741 trigger ?: string ;
42+ /** Custom function to update uncontrolled component value */
2843 unControlledValueChange ?: ( ref : any , newValue : StoreValue ) => void ;
44+ /** Event name(s) that trigger validation */
2945 validateTrigger ?: string | string [ ] | false ;
46+ /** Name of the prop to pass the field value (default: 'value') */
3047 valuePropName ?: string ;
3148} & Record < string , any > ;
3249
50+ /**
51+ * Field component that wraps form input elements with state management and validation
52+ * Supports both controlled and uncontrolled modes with flexible customization options
53+ *
54+ * @example
55+ * ```tsx
56+ * // Basic usage with validation
57+ * <Form>
58+ * <Field
59+ * name="email"
60+ * rules={[
61+ * { required: true, message: 'Email is required' },
62+ * { type: 'email', message: 'Invalid email format' }
63+ * ]}
64+ * >
65+ * <Input placeholder="Enter your email" />
66+ * </Field>
67+ * </Form>
68+ * ```
69+ *
70+ * @example
71+ * ```tsx
72+ * // Controlled mode with custom validation trigger
73+ * <Field
74+ * name="password"
75+ * controlMode="controlled"
76+ * validateTrigger={['onChange', 'onBlur']}
77+ * rules={[
78+ * { required: true, message: 'Password is required' },
79+ * { min: 8, message: 'Password must be at least 8 characters' }
80+ * ]}
81+ * >
82+ * <Input type="password" placeholder="Enter password" />
83+ * </Field>
84+ * ```
85+ *
86+ * @example
87+ * ```tsx
88+ * // Custom value extraction and normalization
89+ * <Field
90+ * name="phone"
91+ * getValueFromEvent={(e) => e.target.value.replace(/\D/g, '')}
92+ * normalize={(value) => {
93+ * // Format phone number: (123) 456-7890
94+ * const cleaned = value.replace(/\D/g, '');
95+ * const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/);
96+ * return match ? `(${match[1]}) ${match[2]}-${match[3]}` : value;
97+ * }}
98+ * rules={[{ required: true, message: 'Phone number is required' }]}
99+ * >
100+ * <Input placeholder="(123) 456-7890" />
101+ * </Field>
102+ * ```
103+ */
33104function Field < Values = any > ( props : FieldProps < Values > ) {
105+ // Destructure props with default values
34106 const {
35107 children,
36108 controlMode = 'uncontrolled' ,
@@ -47,16 +119,22 @@ function Field<Values = any>(props: FieldProps<Values>) {
47119 ...rest
48120 } = props ;
49121
122+ // State for forcing re-renders in controlled mode
50123 const [ _ , forceUpdate ] = useState ( { } ) ;
51124
125+ // Get form context to access form methods
52126 const fieldContext = useFieldContext < Values > ( ) ;
53127
128+ // Track if value was changed by normalization
54129 const normalizedChangedRef = useRef ( false ) ;
55130
131+ // Unique key for React reconciliation
56132 const key = useId ( ) ;
57133
134+ // Reference to the child component
58135 const cref = useRef < any > ( null ) ;
59136
137+ // Extract form instance methods
60138 const {
61139 getFieldsValue,
62140 getFieldValue,
@@ -66,19 +144,25 @@ function Field<Values = any>(props: FieldProps<Values>) {
66144 validateTrigger : fieldValidateTrigger
67145 } = fieldContext as unknown as InternalFormInstance < Values > ;
68146
147+ // Get internal hooks for field registration and rule setting
69148 const { registerField, setFieldRules } = getInternalHooks ( ) ;
70149
150+ // Determine if field should be controlled
71151 const isControlled = controlMode === 'controlled' ;
72152
153+ // Merge field-level and form-level validation triggers
73154 const mergedValidateTrigger = validateTrigger || fieldValidateTrigger ;
74155
156+ // Convert validation triggers to array format
75157 const validateTriggerList : string [ ] = toArray ( mergedValidateTrigger ) ;
76158
159+ // Helper function to create validation trigger handlers
77160 const make =
78161 ( evt : string ) =>
79162 ( ..._args : any [ ] ) =>
80163 validateField ( name , { trigger : evt } ) ;
81164
165+ // Create handlers for validation triggers that are not the main trigger
82166 const restValidateTriggerList = validateTriggerList
83167 . filter ( item => item !== trigger )
84168 . reduce (
@@ -90,23 +174,30 @@ function Field<Values = any>(props: FieldProps<Values>) {
90174 { } as Record < string , ( ...args : any [ ] ) => void >
91175 ) ;
92176
177+ // Get current field value or use initial value as fallback
93178 const value = getFieldValue ( name ) || initialValue ;
94179
180+ // Prepare value props based on control mode
181+ // Controlled: use 'value' prop, Uncontrolled: use 'defaultValue' prop
95182 const valueProps = isControlled ? { [ valuePropName ] : value } : { [ `default${ capitalize ( valuePropName ) } ` ] : value } ;
96183
184+ // Create controlled props with change handler
97185 const controlledProps = omitUndefined ( {
98186 [ trigger ] : name
99187 ? ( ...args : any [ ] ) => {
100188 let newValue : StoreValue ;
101189
190+ // Get current value for comparison
102191 const oldValue = getFieldValue ( name ) ;
103192
193+ // Extract new value from event using custom or default extractor
104194 if ( getValueFromEvent ) {
105195 newValue = getValueFromEvent ( ...args ) ;
106196 } else {
107197 newValue = getEventValue ( valuePropName , ...args ) ;
108198 }
109199
200+ // Apply normalization if provided
110201 if ( normalize ) {
111202 const norm = normalize ( newValue , oldValue , getFieldsValue ( ) as Values ) ;
112203
@@ -116,10 +207,12 @@ function Field<Values = any>(props: FieldProps<Values>) {
116207 }
117208 }
118209
210+ // Update field value if it changed
119211 if ( newValue !== oldValue ) {
120212 setFieldValue ( name , newValue ) ;
121213 }
122214
215+ // Trigger validation if this event is a validation trigger
123216 if ( validateTriggerList . includes ( trigger ) ) {
124217 validateField ( name , { trigger } ) ;
125218 }
@@ -128,19 +221,24 @@ function Field<Values = any>(props: FieldProps<Values>) {
128221 } ) ;
129222
130223 useEffect ( ( ) => {
224+ // Register field with form store
131225 const unregister = registerField ( {
132226 changeValue : newValue => {
133227 if ( isControlled ) {
228+ // Force re-render for controlled components
134229 forceUpdate ( { } ) ;
135230 return ;
136231 }
232+ // Handle uncontrolled component value updates
137233 const el = cref . current ;
138234
139235 if ( ! el ) return ;
140236
141237 if ( unControlledValueChange ) {
238+ // Use custom value update function
142239 unControlledValueChange ( el , newValue ) ;
143240 } else {
241+ // Default: update DOM element value directly
144242 el . value = isNil ( newValue ) ? '' : ( newValue as any ) ;
145243 }
146244 } ,
@@ -149,21 +247,24 @@ function Field<Values = any>(props: FieldProps<Values>) {
149247 preserve
150248 } ) ;
151249
250+ // Set validation rules for this field
152251 setFieldRules ( name , rules ) ;
153252
253+ // Cleanup: unregister field when component unmounts
154254 return ( ) => {
155255 unregister ( ) ;
156256 } ;
157257 } , [ ] ) ;
158258
259+ // Render child component with all necessary props
159260 return (
160261 < Slot
161262 key = { key }
162- { ...( valueProps as any ) }
163- { ...controlledProps }
164- { ...restValidateTriggerList }
165- { ...rest }
166- ref = { cref }
263+ { ...( valueProps as any ) } // Value props (value or defaultValue)
264+ { ...controlledProps } // Change handler props
265+ { ...restValidateTriggerList } // Additional validation trigger handlers
266+ { ...rest } // Other props passed to Field
267+ ref = { cref } // Reference to child component
167268 >
168269 { children }
169270 </ Slot >
0 commit comments