@formality-ui/react 0.0.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.
@@ -0,0 +1,860 @@
1
+ import { FieldError, InputConfig, ValidatorsConfig, ErrorMessagesConfig, SelectValue, FormFieldsConfig, FormConfig, FormState, ConditionDescriptor, GroupConfig, ConditionResult } from '@formality-ui/core';
2
+ export { ConditionDescriptor, ConditionResult, ErrorMessagesConfig, FieldConfig, FieldError, FieldState, FormConfig, FormFieldsConfig, FormState, FormalityProviderConfig, GroupConfig, InputConfig, SelectFunction, SelectValue, ValidationResult, ValidatorFunction, ValidatorSpec, ValidatorsConfig } from '@formality-ui/core';
3
+ import * as react from 'react';
4
+ import { ComponentType, ReactNode } from 'react';
5
+ import { ControllerFieldState, UseFormStateReturn, FieldValues, UseFormReturn } from 'react-hook-form';
6
+
7
+ /**
8
+ * Props passed to input template components
9
+ *
10
+ * Templates wrap input components to provide consistent styling,
11
+ * labels, error display, etc.
12
+ */
13
+ interface InputTemplateProps {
14
+ /** The input component to render */
15
+ Field: ComponentType<any>;
16
+ /** Merged props to pass to the component */
17
+ fieldProps: Record<string, unknown>;
18
+ /** Current field state from Controller */
19
+ fieldState: ControllerFieldState;
20
+ /** Current form state from RHF */
21
+ formState: UseFormStateReturn<FieldValues>;
22
+ }
23
+ /**
24
+ * Custom field state with proxy-optimized properties
25
+ *
26
+ * This is the Formality-enhanced field state that includes
27
+ * all necessary properties for expressions and conditions.
28
+ */
29
+ interface CustomFieldState {
30
+ /** Current field value */
31
+ value: unknown;
32
+ /** Has the field been touched (focused then blurred) */
33
+ isTouched: boolean;
34
+ /** Has the value changed from default */
35
+ isDirty: boolean;
36
+ /** Is async validation currently running */
37
+ isValidating: boolean;
38
+ /** Current validation error (if any) */
39
+ error?: FieldError;
40
+ /** Inverse of valid state for convenience */
41
+ invalid: boolean;
42
+ }
43
+ /**
44
+ * Extended form state with Formality-specific additions
45
+ *
46
+ * Combines RHF's form state with proxy-wrapped field states
47
+ * and the original record for expression access.
48
+ */
49
+ interface ExtendedFormState<TFieldValues extends FieldValues = FieldValues> extends UseFormStateReturn<TFieldValues> {
50
+ /** Proxy-wrapped field states for each field */
51
+ fields: Record<string, CustomFieldState>;
52
+ /** Original record passed to Form (for expression access) */
53
+ record: Record<string, unknown>;
54
+ }
55
+ /**
56
+ * Isolated form state for performance-critical subscriptions
57
+ *
58
+ * This is a lightweight version of ExtendedFormState that does NOT
59
+ * subscribe to the entire RHF form state. It only contains:
60
+ * - Fields you explicitly subscribed to
61
+ * - The record object
62
+ *
63
+ * Use this when you need to watch specific fields without causing
64
+ * re-renders when other fields change.
65
+ */
66
+ interface IsolatedFormState {
67
+ /** Proxy-wrapped field states for watched fields only */
68
+ fields: Record<string, CustomFieldState>;
69
+ /** Original record passed to Form (for expression access) */
70
+ record: Record<string, unknown>;
71
+ /** Minimal form-level flags (not reactive) */
72
+ isDirty: boolean;
73
+ isTouched: boolean;
74
+ isValid: boolean;
75
+ isSubmitting: boolean;
76
+ errors: Record<string, unknown>;
77
+ touchedFields: Record<string, unknown>;
78
+ dirtyFields: Record<string, unknown>;
79
+ defaultValues: Record<string, unknown>;
80
+ }
81
+ /**
82
+ * Watcher setter function type
83
+ *
84
+ * Used by fields to receive updates about which other fields are watching them.
85
+ */
86
+ type WatcherSetterFn = React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
87
+ /**
88
+ * Debounced function interface
89
+ *
90
+ * Used for debounced submission and validation.
91
+ */
92
+ interface DebouncedFunction {
93
+ (): void;
94
+ cancel: () => void;
95
+ flush: () => void;
96
+ pending: () => boolean;
97
+ }
98
+
99
+ /**
100
+ * ConfigContextValue - Global configuration provided by FormalityProvider
101
+ *
102
+ * Contains all input types, transformers, validators, and global defaults
103
+ * that apply across all forms in the application.
104
+ */
105
+ interface ConfigContextValue {
106
+ /** Input type definitions (e.g., textField, switch, autocomplete) */
107
+ inputs: Record<string, InputConfig>;
108
+ /** Named formatters for value → display transformation */
109
+ formatters: Record<string, (value: unknown) => unknown>;
110
+ /** Named parsers for input → value transformation */
111
+ parsers: Record<string, (value: unknown) => unknown>;
112
+ /** Named validators and validator factories */
113
+ validators: ValidatorsConfig;
114
+ /** Error message templates by type key */
115
+ errorMessages: ErrorMessagesConfig;
116
+ /** Default template component for all inputs */
117
+ defaultInputTemplate?: ComponentType<InputTemplateProps>;
118
+ /** Named template components for specific input types */
119
+ inputTemplates: Record<string, ComponentType<InputTemplateProps>>;
120
+ /** Default prop name for passSubscriptions (default: 'state') */
121
+ defaultSubscriptionPropName: string;
122
+ /** Static default props for all fields */
123
+ defaultFieldProps: Record<string, unknown>;
124
+ /** Dynamic default props evaluated per-field */
125
+ selectDefaultFieldProps?: SelectValue;
126
+ }
127
+ /**
128
+ * ConfigContext - React context for global Formality configuration
129
+ *
130
+ * Provides access to input types, transformers, validators, and
131
+ * global defaults to all forms and fields in the application.
132
+ */
133
+ declare const ConfigContext: react.Context<ConfigContextValue>;
134
+ /**
135
+ * useConfigContext - Hook to access global configuration
136
+ *
137
+ * @returns The current ConfigContextValue from the nearest FormalityProvider
138
+ */
139
+ declare function useConfigContext(): ConfigContextValue;
140
+
141
+ /**
142
+ * FormContextValue - Form-level configuration and operations
143
+ *
144
+ * Provides field registration, subscription management, and access
145
+ * to form state for all fields within a Form component.
146
+ */
147
+ interface FormContextValue<TFieldValues extends FieldValues = FieldValues> {
148
+ /** Field configurations for this form */
149
+ config: FormFieldsConfig;
150
+ /** Form-level configuration (title, groups, input overrides) */
151
+ formConfig: FormConfig;
152
+ /** Original record passed to Form (for expression access) */
153
+ record?: Record<string, unknown>;
154
+ /**
155
+ * Register a field when it mounts
156
+ * @param name - Field name
157
+ */
158
+ registerField: (name: string) => void;
159
+ /**
160
+ * Unregister a field when it unmounts
161
+ * @param name - Field name
162
+ */
163
+ unregisterField: (name: string) => void;
164
+ /**
165
+ * Add a subscription from subscriber to target field
166
+ * @param target - The field being watched
167
+ * @param subscriber - The field watching
168
+ */
169
+ addSubscription: (target: string, subscriber: string) => void;
170
+ /**
171
+ * Remove a subscription from subscriber to target field
172
+ * @param target - The field being watched
173
+ * @param subscriber - The field watching
174
+ */
175
+ removeSubscription: (target: string, subscriber: string) => void;
176
+ /**
177
+ * Register a setter for a field's watchers state
178
+ * @param name - Field name
179
+ * @param setter - React state setter for watchers
180
+ */
181
+ registerWatcherSetter: (name: string, setter: WatcherSetterFn) => void;
182
+ /**
183
+ * Unregister a watcher setter when field unmounts
184
+ * @param name - Field name
185
+ */
186
+ unregisterWatcherSetter: (name: string) => void;
187
+ /**
188
+ * Programmatically change a field's value
189
+ * @param name - Field name
190
+ * @param value - New value
191
+ */
192
+ changeField: (name: string, value: unknown) => void;
193
+ /**
194
+ * Set a field's validating state
195
+ * @param name - Field name
196
+ * @param isValidating - Whether validation is in progress
197
+ */
198
+ setFieldValidating: (name: string, isValidating: boolean) => void;
199
+ /**
200
+ * Get the current complete form state
201
+ * Used for expression evaluation and condition checking
202
+ */
203
+ getFormState: () => FormState;
204
+ /** Optional submit handler passed to Form */
205
+ onSubmit?: (values: Partial<TFieldValues>) => void | Promise<void>;
206
+ /** Debounced submit for auto-save forms */
207
+ debouncedSubmit: DebouncedFunction;
208
+ /** Immediate submit bypassing debounce */
209
+ submitImmediate: () => void;
210
+ /** Fields in config but not rendered (for config-driven forms) */
211
+ unusedFields: string[];
212
+ /** React Hook Form methods passthrough */
213
+ methods: UseFormReturn<TFieldValues>;
214
+ }
215
+ /**
216
+ * FormContext - React context for form-level state and operations
217
+ *
218
+ * Must be provided by a Form component. No default value as
219
+ * fields cannot function without a parent Form.
220
+ */
221
+ declare const FormContext: react.Context<FormContextValue<FieldValues> | null>;
222
+ /**
223
+ * useFormContext - Hook to access form-level context
224
+ *
225
+ * @throws Error if used outside a Form component
226
+ * @returns The FormContextValue from the nearest Form component
227
+ */
228
+ declare function useFormContext<TFieldValues extends FieldValues = FieldValues>(): FormContextValue<TFieldValues>;
229
+
230
+ /**
231
+ * GroupState - Current computed state for a FieldGroup
232
+ *
233
+ * Contains the resolved disabled/visible states from condition
234
+ * evaluation and the conditions/subscriptions used.
235
+ */
236
+ interface GroupState {
237
+ /** Is the group disabled (propagates to all child fields) */
238
+ isDisabled: boolean;
239
+ /** Is the group visible (propagates to all child fields) */
240
+ isVisible: boolean;
241
+ /** Whether the group has a setValue condition */
242
+ hasSetCondition: boolean;
243
+ /** Value to set on all child fields (from group-level set/selectSet) */
244
+ setValue: unknown;
245
+ /** Conditions from group configuration */
246
+ conditions: ConditionDescriptor[];
247
+ /** Fields this group subscribes to */
248
+ subscriptions: string[];
249
+ }
250
+ /**
251
+ * GroupContextValue - Group-level configuration and state
252
+ *
253
+ * Provides inherited disabled/visible state and subscription
254
+ * information to nested fields and groups.
255
+ */
256
+ interface GroupContextValue {
257
+ /** Current computed state for the group */
258
+ state: GroupState;
259
+ /** Inherited subscriptions from parent groups */
260
+ subscriptions: string[];
261
+ /** Field names inferred from group conditions */
262
+ inferredInputs: string[];
263
+ /** Group configuration */
264
+ config: GroupConfig;
265
+ }
266
+ /**
267
+ * GroupContext - React context for group-level state
268
+ *
269
+ * Provides inherited disabled/visible state from parent groups
270
+ * to child fields and nested groups.
271
+ */
272
+ declare const GroupContext: react.Context<GroupContextValue>;
273
+ /**
274
+ * useGroupContext - Hook to access group-level context
275
+ *
276
+ * @returns The GroupContextValue from the nearest FieldGroup or default
277
+ */
278
+ declare function useGroupContext(): GroupContextValue;
279
+
280
+ /**
281
+ * FormalityProviderProps - Props for the FormalityProvider component
282
+ *
283
+ * Configures input types, transformers, validators, and global defaults
284
+ * for all forms in the application.
285
+ */
286
+ interface FormalityProviderProps {
287
+ /** Child components */
288
+ children: ReactNode;
289
+ /**
290
+ * Input type definitions (REQUIRED)
291
+ *
292
+ * Defines how each input type (e.g., textField, switch, autocomplete)
293
+ * behaves across all forms.
294
+ *
295
+ * @example
296
+ * ```tsx
297
+ * inputs={{
298
+ * textField: { component: TextField, defaultValue: '' },
299
+ * switch: { component: Switch, defaultValue: false },
300
+ * }}
301
+ * ```
302
+ */
303
+ inputs: Record<string, InputConfig>;
304
+ /**
305
+ * Named formatters for value → display transformation
306
+ *
307
+ * Transform form values before displaying in inputs.
308
+ *
309
+ * @example
310
+ * ```tsx
311
+ * formatters={{
312
+ * currency: (v) => `$${Number(v).toFixed(2)}`,
313
+ * uppercase: (v) => String(v).toUpperCase(),
314
+ * }}
315
+ * ```
316
+ */
317
+ formatters?: Record<string, (value: unknown) => unknown>;
318
+ /**
319
+ * Named parsers for input → value transformation
320
+ *
321
+ * Transform user input before storing in form state.
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * parsers={{
326
+ * number: (v) => Number(v) || 0,
327
+ * trim: (v) => String(v).trim(),
328
+ * }}
329
+ * ```
330
+ */
331
+ parsers?: Record<string, (value: unknown) => unknown>;
332
+ /**
333
+ * Named validators and validator factories
334
+ *
335
+ * Define reusable validation logic across forms.
336
+ */
337
+ validators?: ValidatorsConfig;
338
+ /**
339
+ * Error message templates by type key
340
+ *
341
+ * Map validation error types to human-readable messages.
342
+ *
343
+ * @example
344
+ * ```tsx
345
+ * errorMessages={{
346
+ * required: 'This field is required',
347
+ * minLength: 'Must be at least {min} characters',
348
+ * }}
349
+ * ```
350
+ */
351
+ errorMessages?: ErrorMessagesConfig;
352
+ /**
353
+ * Default template component for all inputs
354
+ *
355
+ * Wraps all inputs for consistent styling, labels, error display.
356
+ */
357
+ defaultInputTemplate?: ComponentType<InputTemplateProps>;
358
+ /**
359
+ * Named template components for specific input types
360
+ *
361
+ * Override the default template for specific input types.
362
+ */
363
+ inputTemplates?: Record<string, ComponentType<InputTemplateProps>>;
364
+ /**
365
+ * Default prop name for passSubscriptions (default: 'state')
366
+ *
367
+ * When a field has passSubscriptions: true, subscribed field states
368
+ * are passed using this prop name.
369
+ */
370
+ defaultSubscriptionPropName?: string;
371
+ /**
372
+ * Static default props for all fields
373
+ *
374
+ * Props applied to every field unless overridden.
375
+ */
376
+ defaultFieldProps?: Record<string, unknown>;
377
+ /**
378
+ * Dynamic default props evaluated per-field
379
+ *
380
+ * Expression or function that computes default props.
381
+ */
382
+ selectDefaultFieldProps?: SelectValue;
383
+ }
384
+ /**
385
+ * FormalityProvider - Global configuration provider for Formality
386
+ *
387
+ * Wraps your application to provide input types, transformers, validators,
388
+ * and global defaults to all Formality forms.
389
+ *
390
+ * @example
391
+ * ```tsx
392
+ * import { FormalityProvider } from '@formality-ui/react';
393
+ *
394
+ * const inputs = {
395
+ * textField: { component: TextInput, defaultValue: '' },
396
+ * switch: { component: Toggle, defaultValue: false },
397
+ * };
398
+ *
399
+ * function App() {
400
+ * return (
401
+ * <FormalityProvider
402
+ * inputs={inputs}
403
+ * formatters={{ currency: (v) => `$${v}` }}
404
+ * errorMessages={{ required: 'Required' }}
405
+ * >
406
+ * <MyApp />
407
+ * </FormalityProvider>
408
+ * );
409
+ * }
410
+ * ```
411
+ */
412
+ declare function FormalityProvider({ children, inputs, formatters, parsers, validators, errorMessages, defaultInputTemplate, inputTemplates, defaultSubscriptionPropName, defaultFieldProps, selectDefaultFieldProps, }: FormalityProviderProps): JSX.Element;
413
+
414
+ /**
415
+ * Form component props
416
+ */
417
+ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
418
+ /** Form content - can be static children or render function */
419
+ children: ReactNode | ((api: FormRenderAPI<TFieldValues>) => ReactNode);
420
+ /** Field configurations */
421
+ config: FormFieldsConfig;
422
+ /** Form-level configuration (title, groups, input overrides) */
423
+ formConfig?: FormConfig;
424
+ /** Submit handler */
425
+ onSubmit?: (values: Partial<TFieldValues>) => void | Promise<void>;
426
+ /** Initial record data */
427
+ record?: Partial<TFieldValues>;
428
+ /** Enable auto-save on field changes */
429
+ autoSave?: boolean;
430
+ /** Debounce milliseconds for auto-save (default: 1000) */
431
+ debounce?: number;
432
+ /** Form-level validation */
433
+ validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
434
+ }
435
+ /**
436
+ * API passed to render function children
437
+ */
438
+ interface FormRenderAPI<TFieldValues extends FieldValues = FieldValues> {
439
+ /** Fields in config but not rendered */
440
+ unusedFields: string[];
441
+ /** React Hook Form formState */
442
+ formState: UseFormReturn<TFieldValues>['formState'];
443
+ /** React Hook Form methods */
444
+ methods: UseFormReturn<TFieldValues>;
445
+ /** Resolved form title (static or evaluated) */
446
+ resolvedTitle?: string;
447
+ }
448
+ /**
449
+ * Form component - Core form wrapper with React Hook Form integration
450
+ *
451
+ * Provides:
452
+ * - FormContext for field registration and subscription management
453
+ * - React Hook Form integration with mode: 'onChange'
454
+ * - Auto-save with debounced submission
455
+ * - Config-driven default values
456
+ * - Unused fields tracking for config-driven rendering
457
+ *
458
+ * @example
459
+ * ```tsx
460
+ * <Form
461
+ * config={{
462
+ * name: { type: 'textField', label: 'Name' },
463
+ * email: { type: 'textField', label: 'Email' },
464
+ * }}
465
+ * onSubmit={(values) => console.log(values)}
466
+ * >
467
+ * <Field name="name" />
468
+ * <Field name="email" />
469
+ * <button type="submit">Submit</button>
470
+ * </Form>
471
+ * ```
472
+ */
473
+ declare function Form<TFieldValues extends FieldValues = FieldValues>({ children, config, formConfig, onSubmit, record, autoSave, debounce: debounceMs, validate, }: FormProps<TFieldValues>): JSX.Element;
474
+
475
+ /**
476
+ * Field component props
477
+ */
478
+ interface FieldProps {
479
+ /** Field name (must match a key in Form's config) */
480
+ name: string;
481
+ /** Override the input type from config */
482
+ type?: string;
483
+ /** Override disabled state */
484
+ disabled?: boolean;
485
+ /** Override hidden state (inverse of visible) */
486
+ hidden?: boolean;
487
+ /** Custom render function for advanced use cases */
488
+ children?: ReactNode | ((api: FieldRenderAPI) => ReactNode);
489
+ /** Whether to register this field in Form's field registry (default: true) */
490
+ shouldRegister?: boolean;
491
+ /** Additional props to pass to the input component */
492
+ [key: string]: unknown;
493
+ }
494
+ /**
495
+ * API passed to render function children
496
+ */
497
+ interface FieldRenderAPI {
498
+ /** React Hook Form field state */
499
+ fieldState: ControllerFieldState;
500
+ /** The rendered input component */
501
+ renderedField: ReactNode;
502
+ /** Final merged props passed to input */
503
+ fieldProps: Record<string, unknown>;
504
+ /** Map of fields watching this field */
505
+ watchers: Record<string, boolean>;
506
+ /** React Hook Form form state */
507
+ formState: UseFormStateReturn<FieldValues>;
508
+ }
509
+ /**
510
+ * Field component - Renders a form field with full Formality integration
511
+ *
512
+ * Provides:
513
+ * - React Hook Form Controller integration
514
+ * - Props resolution (8-layer merge)
515
+ * - Condition evaluation (disabled/visible/setValue)
516
+ * - Value transformation (parse/format)
517
+ * - Validation (field + type validators)
518
+ * - Subscription management
519
+ *
520
+ * @example
521
+ * ```tsx
522
+ * // Basic usage
523
+ * <Field name="email" />
524
+ *
525
+ * // Override type
526
+ * <Field name="status" type="select" />
527
+ *
528
+ * // Custom render
529
+ * <Field name="name">
530
+ * {({ renderedField, fieldState }) => (
531
+ * <div className={fieldState.error ? 'has-error' : ''}>
532
+ * {renderedField}
533
+ * </div>
534
+ * )}
535
+ * </Field>
536
+ * ```
537
+ */
538
+ declare function Field({ name, type: typeProp, disabled: disabledProp, hidden: hiddenProp, children, shouldRegister, ...restProps }: FieldProps): JSX.Element | null;
539
+
540
+ /**
541
+ * FieldGroup component props
542
+ */
543
+ interface FieldGroupProps {
544
+ /** Group name (must match a key in FormConfig.groups) */
545
+ name: string;
546
+ /** Fields to render within this group */
547
+ children: ReactNode;
548
+ }
549
+ /**
550
+ * FieldGroup component - Groups fields with shared conditions
551
+ *
552
+ * Provides:
553
+ * - Group-level condition evaluation (disabled/visible)
554
+ * - State propagation to nested fields:
555
+ * - disabled: OR logic (child || parent)
556
+ * - visible: AND logic (child && parent)
557
+ * - Subscription accumulation from parent groups
558
+ *
559
+ * Groups can be nested, with state merging at each level.
560
+ *
561
+ * @example
562
+ * ```tsx
563
+ * // FormConfig
564
+ * formConfig={{
565
+ * groups: {
566
+ * clientSection: {
567
+ * conditions: [
568
+ * { when: 'hasClient', is: false, visible: false }
569
+ * ]
570
+ * }
571
+ * }
572
+ * }}
573
+ *
574
+ * // Usage
575
+ * <Form config={config} formConfig={formConfig}>
576
+ * <Field name="hasClient" />
577
+ * <FieldGroup name="clientSection">
578
+ * <Field name="clientName" />
579
+ * <Field name="clientEmail" />
580
+ * </FieldGroup>
581
+ * </Form>
582
+ * ```
583
+ */
584
+ declare function FieldGroup({ name, children }: FieldGroupProps): JSX.Element;
585
+
586
+ /**
587
+ * UnusedFields component props
588
+ */
589
+ interface UnusedFieldsProps {
590
+ /** Custom render function for each unused field */
591
+ children?: (field: {
592
+ name: string;
593
+ component: ReactNode;
594
+ }) => ReactNode;
595
+ }
596
+ /**
597
+ * UnusedFields component - Renders config-driven fields
598
+ *
599
+ * Renders fields that are declared in the Form's config but have not
600
+ * been explicitly rendered using <Field> components. This enables
601
+ * config-driven forms where not all fields need to be declared in JSX.
602
+ *
603
+ * Fields are tracked via the Form's field registry. When a <Field>
604
+ * component mounts, it registers itself. UnusedFields renders all
605
+ * fields that haven't been registered.
606
+ *
607
+ * @example
608
+ * ```tsx
609
+ * // All fields from config rendered automatically
610
+ * <Form config={config}>
611
+ * <UnusedFields />
612
+ * </Form>
613
+ *
614
+ * // Mix explicit and automatic fields
615
+ * <Form config={config}>
616
+ * <Field name="name" /> {/* Explicit with custom props *\/}
617
+ * <Field name="email" /> {/* Explicit with custom props *\/}
618
+ * <UnusedFields /> {/* Renders remaining fields *\/}
619
+ * </Form>
620
+ *
621
+ * // Custom wrapper for unused fields
622
+ * <Form config={config}>
623
+ * <UnusedFields>
624
+ * {({ name, component }) => (
625
+ * <div key={name} className="auto-field">
626
+ * {component}
627
+ * </div>
628
+ * )}
629
+ * </UnusedFields>
630
+ * </Form>
631
+ * ```
632
+ */
633
+ declare function UnusedFields({ children }: UnusedFieldsProps): JSX.Element;
634
+
635
+ /**
636
+ * Creates a proxy object with lazy property access via Object.defineProperty
637
+ *
638
+ * CRITICAL: This enables React's dependency tracking to only register
639
+ * dependencies on actually accessed properties, not the entire object.
640
+ *
641
+ * Without proxy: accessing fieldState.value creates dependency on entire fieldState
642
+ * With proxy: accessing fieldState.value only creates dependency on value property
643
+ *
644
+ * Performance Impact:
645
+ * - Reduces unnecessary re-renders in condition-heavy forms
646
+ * - Expression evaluation only subscribes to properties actually used
647
+ * - Field states can change without triggering re-renders in unrelated fields
648
+ *
649
+ * Implementation Note:
650
+ * Uses Object.defineProperty (NOT Proxy) because:
651
+ * - Better performance for simple property access
652
+ * - Works with Object.keys() iteration when enumerable: true
653
+ * - Simpler debugging and stack traces
654
+ *
655
+ * @param source - The object to wrap with lazy getters
656
+ * @returns A new object with getter properties that lazily access source
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * const source = { value: 5, isTouched: false };
661
+ * const proxy = makeProxyState(source);
662
+ *
663
+ * // Properties are accessed lazily
664
+ * console.log(proxy.value); // 5
665
+ *
666
+ * // Changes to source reflect in proxy (lazy access)
667
+ * source.value = 10;
668
+ * console.log(proxy.value); // 10
669
+ *
670
+ * // Object.keys still works
671
+ * console.log(Object.keys(proxy)); // ['value', 'isTouched']
672
+ * ```
673
+ */
674
+ declare function makeProxyState<T extends object>(source: T): T;
675
+ /**
676
+ * Creates a nested proxy state, recursively wrapping objects
677
+ *
678
+ * Use this for deeply nested state objects where you want
679
+ * lazy access at multiple levels.
680
+ *
681
+ * @param source - The object to wrap with lazy getters (recursively)
682
+ * @returns A new object with recursive getter properties
683
+ */
684
+ declare function makeDeepProxyState<T extends object>(source: T): T;
685
+
686
+ /**
687
+ * Options for useFormState hook
688
+ */
689
+ interface UseFormStateOptions {
690
+ /**
691
+ * Specific field name(s) to watch.
692
+ * REQUIRED for performance - you must specify which fields you need.
693
+ * Watching all fields defeats the purpose of isolation.
694
+ */
695
+ name: string | string[];
696
+ }
697
+ /**
698
+ * Custom useFormState that provides ISOLATED field subscriptions
699
+ *
700
+ * CRITICAL PERFORMANCE REQUIREMENT:
701
+ * You MUST specify which fields to watch via the `name` option.
702
+ * This hook is designed to prevent the "subscribe to everything" anti-pattern.
703
+ *
704
+ * This hook provides:
705
+ * - Proxy-wrapped field states to prevent unnecessary re-renders
706
+ * - Record property access via lazy getter
707
+ * - Integration with Formality's FormContext
708
+ *
709
+ * Performance Benefits:
710
+ * - Only re-renders when specified fields change
711
+ * - Expression evaluation only creates dependencies on used properties
712
+ * - Field states can update independently without cross-field re-renders
713
+ *
714
+ * @example
715
+ * ```tsx
716
+ * function MyField() {
717
+ * // Watch specific fields only
718
+ * const formState = useFormState({ name: ['firstName', 'lastName'] });
719
+ *
720
+ * // Only re-renders when firstName or lastName change
721
+ * const value = formState.fields.firstName?.value;
722
+ *
723
+ * // Access original record for expressions
724
+ * const recordId = formState.record.id;
725
+ * }
726
+ * ```
727
+ */
728
+ declare function useFormState<TFieldValues extends FieldValues = FieldValues>(options: UseFormStateOptions): IsolatedFormState;
729
+
730
+ interface UseConditionsOptions {
731
+ /** Conditions to evaluate */
732
+ conditions: ConditionDescriptor[];
733
+ /** Explicit field subscriptions */
734
+ subscribesTo?: string[];
735
+ /** Additional props for expression context */
736
+ props?: Record<string, unknown>;
737
+ }
738
+ /**
739
+ * Evaluates conditions against current field values
740
+ *
741
+ * This hook:
742
+ * 1. Infers which fields to watch from conditions
743
+ * 2. Subscribes to those fields via useWatch
744
+ * 3. Evaluates conditions whenever watched values change
745
+ *
746
+ * Implements the condition evaluation rules:
747
+ * - disabled: OR logic (any true = disabled)
748
+ * - visible: AND logic (any false = hidden)
749
+ * - setValue: last matching condition wins
750
+ *
751
+ * @param options - Conditions and subscription config
752
+ * @returns Evaluation result with disabled, visible, and setValue states
753
+ *
754
+ * @example
755
+ * ```tsx
756
+ * const result = useConditions({
757
+ * conditions: [
758
+ * { when: 'signed', is: false, disabled: true },
759
+ * { when: 'archived', truthy: true, visible: false },
760
+ * ],
761
+ * });
762
+ * // result.disabled === true when signed is false
763
+ * // result.visible === false when archived is truthy
764
+ * ```
765
+ */
766
+ declare function useConditions(options: UseConditionsOptions): ConditionResult;
767
+
768
+ interface UsePropsEvaluationOptions {
769
+ /** Dynamic props descriptor to evaluate */
770
+ selectProps?: SelectValue;
771
+ /** Explicit field subscriptions */
772
+ subscribesTo?: string[];
773
+ /** Current field name */
774
+ fieldName: string;
775
+ }
776
+ /**
777
+ * Evaluates selectProps against current field values
778
+ *
779
+ * This hook:
780
+ * 1. Infers which fields to watch from selectProps expressions
781
+ * 2. Subscribes to those fields via useWatch
782
+ * 3. Evaluates selectProps whenever watched values change
783
+ *
784
+ * Handles both expression-based selectProps and function-based selectProps.
785
+ *
786
+ * @param options - selectProps and subscription config
787
+ * @returns Evaluated props object
788
+ *
789
+ * @example
790
+ * ```tsx
791
+ * const props = usePropsEvaluation({
792
+ * selectProps: {
793
+ * options: "client.id ? clientOptions : allOptions",
794
+ * disabled: "!signed",
795
+ * },
796
+ * fieldName: 'contact',
797
+ * });
798
+ * // props.options and props.disabled are evaluated against current state
799
+ * ```
800
+ */
801
+ declare function usePropsEvaluation(options: UsePropsEvaluationOptions): Record<string, unknown>;
802
+
803
+ interface UseInferredInputsOptions {
804
+ /** Dynamic props descriptor to analyze for field references */
805
+ selectProps?: SelectValue;
806
+ /** Conditions to analyze for field references */
807
+ conditions?: ConditionDescriptor[];
808
+ /** Explicit field subscriptions */
809
+ subscribesTo?: string[];
810
+ }
811
+ /**
812
+ * Infers field dependencies from selectProps, conditions, and explicit subscriptions
813
+ *
814
+ * This hook analyzes:
815
+ * - selectProps expressions for field references
816
+ * - condition 'when' fields and 'selectWhen' expressions
817
+ * - explicit subscribesTo declarations
818
+ *
819
+ * Used to automatically determine which fields a Field or FieldGroup
820
+ * should subscribe to for reactive updates.
821
+ *
822
+ * @param options - Sources to analyze for field references
823
+ * @returns Array of unique field names to subscribe to
824
+ *
825
+ * @example
826
+ * ```tsx
827
+ * const subscriptions = useInferredInputs({
828
+ * selectProps: "client.id",
829
+ * conditions: [{ when: 'signed', disabled: true }],
830
+ * subscribesTo: ['contact'],
831
+ * });
832
+ * // → ['client', 'signed', 'contact']
833
+ * ```
834
+ */
835
+ declare function useInferredInputs(options: UseInferredInputsOptions): string[];
836
+
837
+ /**
838
+ * Manages field subscriptions
839
+ *
840
+ * This hook registers the current field as a subscriber to the target fields,
841
+ * and cleans up subscriptions when the component unmounts or subscriptions change.
842
+ *
843
+ * Subscriptions are stored in the Form's inverted index (target → subscribers),
844
+ * which allows target fields to know who is watching them (for optimization).
845
+ *
846
+ * @param fieldName - The subscribing field's name
847
+ * @param subscriptions - Array of field names to subscribe to
848
+ *
849
+ * @example
850
+ * ```tsx
851
+ * // Contact field subscribes to client field
852
+ * useSubscriptions('contact', ['client']);
853
+ *
854
+ * // When client changes, its watchers can be notified
855
+ * // This enables features like passSubscriptions
856
+ * ```
857
+ */
858
+ declare function useSubscriptions(fieldName: string, subscriptions: string[]): void;
859
+
860
+ export { ConfigContext, type ConfigContextValue, type CustomFieldState, type DebouncedFunction, type ExtendedFormState, Field, FieldGroup, type FieldGroupProps, type FieldProps, type FieldRenderAPI, Form, FormContext, type FormContextValue, type FormProps, type FormRenderAPI, FormalityProvider, type FormalityProviderProps, GroupContext, type GroupContextValue, type GroupState, type InputTemplateProps, UnusedFields, type UnusedFieldsProps, type UseFormStateOptions, type WatcherSetterFn, makeDeepProxyState, makeProxyState, useConditions, useConfigContext, useFormContext, useFormState, useGroupContext, useInferredInputs, usePropsEvaluation, useSubscriptions };