@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.
package/dist/index.js ADDED
@@ -0,0 +1,1028 @@
1
+ import { createContext, useContext, useMemo, useRef, useState, useCallback, useEffect } from 'react';
2
+ import { jsx, Fragment } from 'react/jsx-runtime';
3
+ import { useForm, FormProvider, useWatch, Controller, useFormContext as useFormContext$1 } from 'react-hook-form';
4
+ import { debounce } from 'lodash-es';
5
+ import { resolveAllInitialValues, buildFormContext, evaluateDescriptor, resolveFormTitle, transformFieldName, extractValueField, inferFieldsFromDescriptor, inferFieldsFromConditions, evaluateConditions, buildFieldContext, resolveInputConfig, resolveLabel, runValidator, resolveErrorMessage, parse, format, mergeFieldProps, sortFieldsByOrder } from '@formality-ui/core';
6
+
7
+ // src/context/ConfigContext.ts
8
+ var defaultConfigContext = {
9
+ inputs: {},
10
+ formatters: {},
11
+ parsers: {},
12
+ validators: {},
13
+ errorMessages: {},
14
+ inputTemplates: {},
15
+ defaultSubscriptionPropName: "state",
16
+ defaultFieldProps: {}
17
+ };
18
+ var ConfigContext = createContext(defaultConfigContext);
19
+ ConfigContext.displayName = "FormalityConfigContext";
20
+ function useConfigContext() {
21
+ return useContext(ConfigContext);
22
+ }
23
+ var FormContext = createContext(null);
24
+ FormContext.displayName = "FormalityFormContext";
25
+ function useFormContext() {
26
+ const context = useContext(FormContext);
27
+ if (!context) {
28
+ throw new Error(
29
+ "useFormContext must be used within a Form component. Make sure your component is wrapped in a <Form> component."
30
+ );
31
+ }
32
+ return context;
33
+ }
34
+ var defaultGroupContext = {
35
+ state: {
36
+ isDisabled: false,
37
+ isVisible: true,
38
+ hasSetCondition: false,
39
+ setValue: void 0,
40
+ conditions: [],
41
+ subscriptions: []
42
+ },
43
+ subscriptions: [],
44
+ inferredInputs: [],
45
+ config: { conditions: [], subscribesTo: [] }
46
+ };
47
+ var GroupContext = createContext(defaultGroupContext);
48
+ GroupContext.displayName = "FormalityGroupContext";
49
+ function useGroupContext() {
50
+ return useContext(GroupContext);
51
+ }
52
+ function FormalityProvider({
53
+ children,
54
+ inputs,
55
+ formatters = {},
56
+ parsers = {},
57
+ validators = {},
58
+ errorMessages = {},
59
+ defaultInputTemplate,
60
+ inputTemplates = {},
61
+ defaultSubscriptionPropName = "state",
62
+ defaultFieldProps = {},
63
+ selectDefaultFieldProps
64
+ }) {
65
+ const contextValue = useMemo(
66
+ () => ({
67
+ inputs,
68
+ formatters,
69
+ parsers,
70
+ validators,
71
+ errorMessages,
72
+ defaultInputTemplate,
73
+ inputTemplates,
74
+ defaultSubscriptionPropName,
75
+ defaultFieldProps,
76
+ selectDefaultFieldProps
77
+ }),
78
+ [
79
+ inputs,
80
+ formatters,
81
+ parsers,
82
+ validators,
83
+ errorMessages,
84
+ defaultInputTemplate,
85
+ inputTemplates,
86
+ defaultSubscriptionPropName,
87
+ defaultFieldProps,
88
+ selectDefaultFieldProps
89
+ ]
90
+ );
91
+ return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: contextValue, children });
92
+ }
93
+
94
+ // src/utils/makeProxyState.ts
95
+ function makeProxyState(source) {
96
+ const result = {};
97
+ for (const key in source) {
98
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
99
+ Object.defineProperty(result, key, {
100
+ get: () => source[key],
101
+ enumerable: true,
102
+ configurable: true
103
+ });
104
+ }
105
+ }
106
+ return result;
107
+ }
108
+ function makeDeepProxyState(source) {
109
+ const result = {};
110
+ for (const key in source) {
111
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
112
+ source[key];
113
+ Object.defineProperty(result, key, {
114
+ get: () => {
115
+ const currentValue = source[key];
116
+ if (currentValue !== null && typeof currentValue === "object" && !Array.isArray(currentValue)) {
117
+ return makeDeepProxyState(currentValue);
118
+ }
119
+ return currentValue;
120
+ },
121
+ enumerable: true,
122
+ configurable: true
123
+ });
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+ var defaultGroupContext2 = {
129
+ state: {
130
+ isDisabled: false,
131
+ isVisible: true,
132
+ hasSetCondition: false,
133
+ setValue: void 0,
134
+ conditions: [],
135
+ subscriptions: []
136
+ },
137
+ subscriptions: [],
138
+ inferredInputs: [],
139
+ config: { conditions: [], subscribesTo: [] }
140
+ };
141
+ function Form({
142
+ children,
143
+ config,
144
+ formConfig = {},
145
+ onSubmit,
146
+ record,
147
+ autoSave = false,
148
+ debounce: debounceMs = 1e3,
149
+ validate
150
+ }) {
151
+ const providerConfig = useConfigContext();
152
+ const mergedInputs = useMemo(() => {
153
+ const formInputs = typeof formConfig.inputs === "function" ? formConfig.inputs(providerConfig.inputs) : formConfig.inputs ?? {};
154
+ const result = { ...providerConfig.inputs };
155
+ for (const [key, override] of Object.entries(formInputs)) {
156
+ if (result[key]) {
157
+ result[key] = { ...result[key], ...override };
158
+ }
159
+ }
160
+ return result;
161
+ }, [providerConfig.inputs, formConfig.inputs]);
162
+ const defaultValues = useMemo(() => {
163
+ return resolveAllInitialValues(config, mergedInputs, record ?? {});
164
+ }, [config, mergedInputs, record]);
165
+ const methods = useForm({
166
+ mode: "onChange",
167
+ defaultValues,
168
+ values: record
169
+ });
170
+ const fieldRegistry = useRef(/* @__PURE__ */ new Set());
171
+ const [registeredFields, setRegisteredFields] = useState(
172
+ /* @__PURE__ */ new Set()
173
+ );
174
+ const invertedSubscriptions = useRef(/* @__PURE__ */ new Map());
175
+ const watcherSetters = useRef(/* @__PURE__ */ new Map());
176
+ const pendingWatcherUpdates = useRef(/* @__PURE__ */ new Map());
177
+ const validatingFields = useRef(/* @__PURE__ */ new Map());
178
+ const pendingChangedFields = useRef(/* @__PURE__ */ new Set());
179
+ const pendingAffectedFields = useRef(/* @__PURE__ */ new Set());
180
+ const executionVersionRef = useRef(0);
181
+ const registerField = useCallback((name) => {
182
+ fieldRegistry.current.add(name);
183
+ setRegisteredFields(new Set(fieldRegistry.current));
184
+ }, []);
185
+ const unregisterField = useCallback((name) => {
186
+ fieldRegistry.current.delete(name);
187
+ setRegisteredFields(new Set(fieldRegistry.current));
188
+ }, []);
189
+ const addSubscription = useCallback((target, subscriber) => {
190
+ if (!invertedSubscriptions.current.has(target)) {
191
+ invertedSubscriptions.current.set(target, /* @__PURE__ */ new Set());
192
+ }
193
+ invertedSubscriptions.current.get(target).add(subscriber);
194
+ const setter = watcherSetters.current.get(target);
195
+ if (setter) {
196
+ setter((prev) => ({ ...prev, [subscriber]: true }));
197
+ } else {
198
+ if (!pendingWatcherUpdates.current.has(target)) {
199
+ pendingWatcherUpdates.current.set(target, /* @__PURE__ */ new Set());
200
+ }
201
+ pendingWatcherUpdates.current.get(target).add(subscriber);
202
+ }
203
+ }, []);
204
+ const removeSubscription = useCallback(
205
+ (target, subscriber) => {
206
+ invertedSubscriptions.current.get(target)?.delete(subscriber);
207
+ const setter = watcherSetters.current.get(target);
208
+ if (setter) {
209
+ setter((prev) => {
210
+ const next = { ...prev };
211
+ delete next[subscriber];
212
+ return next;
213
+ });
214
+ }
215
+ },
216
+ []
217
+ );
218
+ const registerWatcherSetter = useCallback(
219
+ (name, setter) => {
220
+ watcherSetters.current.set(name, setter);
221
+ const pending = pendingWatcherUpdates.current.get(name);
222
+ if (pending?.size) {
223
+ setter((prev) => {
224
+ const next = { ...prev };
225
+ pending.forEach((sub) => {
226
+ next[sub] = true;
227
+ });
228
+ return next;
229
+ });
230
+ pendingWatcherUpdates.current.delete(name);
231
+ }
232
+ },
233
+ []
234
+ );
235
+ const unregisterWatcherSetter = useCallback((name) => {
236
+ watcherSetters.current.delete(name);
237
+ }, []);
238
+ const getAffectedFields = useCallback((changedField) => {
239
+ const affected = /* @__PURE__ */ new Set();
240
+ const toProcess = [changedField];
241
+ while (toProcess.length > 0) {
242
+ const current = toProcess.pop();
243
+ const subscribers = invertedSubscriptions.current.get(current);
244
+ if (subscribers) {
245
+ for (const subscriber of subscribers) {
246
+ if (!affected.has(subscriber)) {
247
+ affected.add(subscriber);
248
+ toProcess.push(subscriber);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return affected;
254
+ }, []);
255
+ const changeField = useCallback(
256
+ (name, value) => {
257
+ if (autoSave) {
258
+ pendingChangedFields.current.add(name);
259
+ const affected = getAffectedFields(name);
260
+ for (const field of affected) {
261
+ pendingAffectedFields.current.add(field);
262
+ }
263
+ debouncedSubmit();
264
+ }
265
+ },
266
+ [autoSave, getAffectedFields]
267
+ );
268
+ const setFieldValidating = useCallback(
269
+ (name, isValidating) => {
270
+ validatingFields.current.set(name, isValidating);
271
+ },
272
+ []
273
+ );
274
+ const getFormState = useCallback(() => {
275
+ const values = methods.getValues();
276
+ const formState = methods.formState;
277
+ const fields = {};
278
+ Object.keys(config).forEach((name) => {
279
+ const fieldState = methods.getFieldState(name, formState);
280
+ fields[name] = makeProxyState({
281
+ value: values[name],
282
+ isTouched: fieldState.isTouched,
283
+ isDirty: fieldState.isDirty,
284
+ isValidating: validatingFields.current.get(name) ?? false,
285
+ error: fieldState.error ? {
286
+ type: fieldState.error.type,
287
+ message: fieldState.error.message
288
+ } : void 0,
289
+ invalid: fieldState.invalid
290
+ });
291
+ });
292
+ return {
293
+ fields,
294
+ record: record ?? {},
295
+ errors: formState.errors,
296
+ defaultValues,
297
+ touchedFields: formState.touchedFields,
298
+ dirtyFields: formState.dirtyFields,
299
+ isDirty: formState.isDirty,
300
+ isTouched: Object.keys(formState.touchedFields).length > 0,
301
+ isValid: formState.isValid,
302
+ isSubmitting: formState.isSubmitting
303
+ };
304
+ }, [methods, config, record, defaultValues]);
305
+ const handleSubmit = useCallback(
306
+ async (values) => {
307
+ for (const [, isValidating] of validatingFields.current) {
308
+ if (isValidating) return;
309
+ }
310
+ if (validate) {
311
+ const errors = await validate(values);
312
+ if (Object.keys(errors).length > 0) {
313
+ Object.entries(errors).forEach(([field, message]) => {
314
+ methods.setError(field, { message });
315
+ });
316
+ return;
317
+ }
318
+ }
319
+ const submitValues = transformValuesForSubmit(values, config, mergedInputs);
320
+ await onSubmit?.(submitValues);
321
+ },
322
+ [validate, methods, onSubmit, config, mergedInputs]
323
+ );
324
+ const debouncedSubmitRef = useRef();
325
+ const waitForFieldValidation = useCallback(
326
+ async (fields, version) => {
327
+ const maxWaitMs = 1e4;
328
+ const pollIntervalMs = 50;
329
+ const startTime = Date.now();
330
+ while (Date.now() - startTime < maxWaitMs) {
331
+ if (executionVersionRef.current !== version) {
332
+ return false;
333
+ }
334
+ const allDone = fields.every(
335
+ (field) => !validatingFields.current.get(field)
336
+ );
337
+ if (allDone) {
338
+ return true;
339
+ }
340
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
341
+ }
342
+ return true;
343
+ },
344
+ []
345
+ );
346
+ const executeAutoSave = useCallback(async () => {
347
+ executionVersionRef.current++;
348
+ const executionVersion = executionVersionRef.current;
349
+ const changedFields = new Set(pendingChangedFields.current);
350
+ const affectedFields = new Set(pendingAffectedFields.current);
351
+ pendingChangedFields.current.clear();
352
+ pendingAffectedFields.current.clear();
353
+ if (changedFields.size === 0) {
354
+ return;
355
+ }
356
+ const fieldsToValidate = [...changedFields, ...affectedFields];
357
+ const validationsComplete = await waitForFieldValidation(
358
+ fieldsToValidate,
359
+ executionVersion
360
+ );
361
+ if (!validationsComplete || executionVersionRef.current !== executionVersion) {
362
+ return;
363
+ }
364
+ if (fieldsToValidate.length > 0) {
365
+ const isValid = await methods.trigger(fieldsToValidate);
366
+ if (executionVersionRef.current !== executionVersion) {
367
+ return;
368
+ }
369
+ if (!isValid) {
370
+ return;
371
+ }
372
+ }
373
+ const postTriggerComplete = await waitForFieldValidation(
374
+ fieldsToValidate,
375
+ executionVersion
376
+ );
377
+ if (!postTriggerComplete || executionVersionRef.current !== executionVersion) {
378
+ return;
379
+ }
380
+ const formState = methods.formState;
381
+ if (Object.keys(formState.errors).length > 0) {
382
+ return;
383
+ }
384
+ const values = methods.getValues();
385
+ await handleSubmit(values);
386
+ }, [methods, handleSubmit, waitForFieldValidation]);
387
+ useEffect(() => {
388
+ const debouncedFn = debounce(() => {
389
+ executeAutoSave();
390
+ }, debounceMs);
391
+ const fn = Object.assign(debouncedFn, {
392
+ pending: () => false
393
+ // lodash debounce handles this internally
394
+ });
395
+ debouncedSubmitRef.current = fn;
396
+ return () => {
397
+ debouncedFn.cancel();
398
+ };
399
+ }, [executeAutoSave, debounceMs]);
400
+ const debouncedSubmit = useCallback(() => {
401
+ debouncedSubmitRef.current?.();
402
+ }, []);
403
+ const submitImmediate = useCallback(() => {
404
+ debouncedSubmitRef.current?.flush();
405
+ }, []);
406
+ const unusedFields = useMemo(() => {
407
+ const allFields = Object.keys(config);
408
+ return allFields.filter((name) => !registeredFields.has(name));
409
+ }, [config, registeredFields]);
410
+ const resolvedTitle = useMemo(() => {
411
+ if (!formConfig.selectTitle && !formConfig.title) {
412
+ return void 0;
413
+ }
414
+ if (formConfig.selectTitle) {
415
+ const formState = getFormState();
416
+ const context = buildFormContext(
417
+ formState.fields,
418
+ formState.record,
419
+ formState.errors,
420
+ formState.defaultValues,
421
+ formState.touchedFields,
422
+ formState.dirtyFields
423
+ );
424
+ const evaluated = evaluateDescriptor(formConfig.selectTitle, context);
425
+ return resolveFormTitle(formConfig.title, evaluated);
426
+ }
427
+ return resolveFormTitle(formConfig.title);
428
+ }, [formConfig.title, formConfig.selectTitle, getFormState]);
429
+ const contextValue = useMemo(
430
+ () => ({
431
+ config,
432
+ formConfig,
433
+ record,
434
+ registerField,
435
+ unregisterField,
436
+ addSubscription,
437
+ removeSubscription,
438
+ registerWatcherSetter,
439
+ unregisterWatcherSetter,
440
+ changeField,
441
+ setFieldValidating,
442
+ getFormState,
443
+ onSubmit,
444
+ debouncedSubmit: debouncedSubmitRef.current,
445
+ submitImmediate,
446
+ unusedFields,
447
+ methods
448
+ }),
449
+ [
450
+ config,
451
+ formConfig,
452
+ record,
453
+ registerField,
454
+ unregisterField,
455
+ addSubscription,
456
+ removeSubscription,
457
+ registerWatcherSetter,
458
+ unregisterWatcherSetter,
459
+ changeField,
460
+ setFieldValidating,
461
+ getFormState,
462
+ onSubmit,
463
+ submitImmediate,
464
+ unusedFields,
465
+ methods
466
+ ]
467
+ );
468
+ const isRenderFunction = typeof children === "function";
469
+ return /* @__PURE__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsx(FormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(GroupContext.Provider, { value: defaultGroupContext2, children: isRenderFunction ? children({
470
+ unusedFields,
471
+ formState: methods.formState,
472
+ methods,
473
+ resolvedTitle
474
+ }) : children }) }) });
475
+ }
476
+ function transformValuesForSubmit(values, config, inputs) {
477
+ const result = {};
478
+ for (const [name, value] of Object.entries(values)) {
479
+ const fieldConfig = config[name];
480
+ const type = fieldConfig?.type ?? "textField";
481
+ const inputConfig = inputs[type];
482
+ if (inputConfig) {
483
+ const submitName = transformFieldName(name, inputConfig.getSubmitField);
484
+ const submitValue = extractValueField(value, inputConfig.valueField);
485
+ result[submitName] = submitValue;
486
+ } else {
487
+ result[name] = value;
488
+ }
489
+ }
490
+ return result;
491
+ }
492
+ function useInferredInputs(options) {
493
+ const { selectProps, conditions = [], subscribesTo = [] } = options;
494
+ return useMemo(() => {
495
+ const inferred = [...subscribesTo];
496
+ if (selectProps) {
497
+ inferred.push(...inferFieldsFromDescriptor(selectProps));
498
+ }
499
+ if (conditions.length > 0) {
500
+ inferred.push(...inferFieldsFromConditions(conditions));
501
+ }
502
+ return [...new Set(inferred)];
503
+ }, [selectProps, conditions, subscribesTo]);
504
+ }
505
+
506
+ // src/hooks/useConditions.ts
507
+ function useConditions(options) {
508
+ const { conditions, subscribesTo, props } = options;
509
+ const { record, methods } = useFormContext();
510
+ const watchFields = useInferredInputs({
511
+ conditions,
512
+ subscribesTo
513
+ });
514
+ const watchedValues = useWatch({
515
+ control: methods.control,
516
+ name: watchFields.length > 0 ? watchFields : []
517
+ });
518
+ const fieldValues = useMemo(() => {
519
+ const values = {};
520
+ if (watchFields.length === 0) {
521
+ return values;
522
+ }
523
+ if (Array.isArray(watchedValues)) {
524
+ watchFields.forEach((field, i) => {
525
+ values[field] = watchedValues[i];
526
+ });
527
+ } else {
528
+ values[watchFields[0]] = watchedValues;
529
+ }
530
+ return values;
531
+ }, [watchFields, watchedValues]);
532
+ const fieldStates = useMemo(() => {
533
+ const states = {};
534
+ if (watchFields.length === 0) {
535
+ return states;
536
+ }
537
+ watchFields.forEach((fieldName) => {
538
+ const fieldState = methods.getFieldState(fieldName);
539
+ states[fieldName] = {
540
+ value: fieldValues[fieldName],
541
+ isTouched: fieldState.isTouched,
542
+ isDirty: fieldState.isDirty,
543
+ error: fieldState.error,
544
+ invalid: fieldState.invalid,
545
+ isValidating: false
546
+ // Not easily available per-field
547
+ };
548
+ });
549
+ return states;
550
+ }, [watchFields, fieldValues, methods]);
551
+ return useMemo(() => {
552
+ if (conditions.length === 0) {
553
+ return {
554
+ disabled: void 0,
555
+ visible: void 0,
556
+ setValue: void 0,
557
+ hasDisabledCondition: false,
558
+ hasVisibleCondition: false,
559
+ hasSetCondition: false
560
+ };
561
+ }
562
+ return evaluateConditions({
563
+ conditions,
564
+ fieldValues,
565
+ fieldStates,
566
+ record,
567
+ props
568
+ });
569
+ }, [conditions, fieldValues, fieldStates, record, props]);
570
+ }
571
+ function usePropsEvaluation(options) {
572
+ const { selectProps, subscribesTo, fieldName } = options;
573
+ const { record, methods } = useFormContext();
574
+ const watchFields = useInferredInputs({
575
+ selectProps,
576
+ subscribesTo
577
+ });
578
+ const watchedValues = useWatch({
579
+ control: methods.control,
580
+ name: watchFields.length > 0 ? watchFields : []
581
+ });
582
+ const formState = useMemo(() => {
583
+ const fields = {};
584
+ if (watchFields.length > 0) {
585
+ const values = watchFields.length === 1 ? { [watchFields[0]]: watchedValues } : watchFields.reduce((acc, field, i) => {
586
+ acc[field] = watchedValues[i];
587
+ return acc;
588
+ }, {});
589
+ watchFields.forEach((name) => {
590
+ fields[name] = makeProxyState({
591
+ value: values[name],
592
+ isTouched: false,
593
+ isDirty: false,
594
+ isValidating: false,
595
+ error: void 0,
596
+ invalid: false
597
+ });
598
+ });
599
+ }
600
+ return {
601
+ fields,
602
+ record: record ?? {},
603
+ errors: {},
604
+ defaultValues: {},
605
+ touchedFields: {},
606
+ dirtyFields: {},
607
+ isDirty: false,
608
+ isTouched: false,
609
+ isValid: true,
610
+ isSubmitting: false
611
+ };
612
+ }, [watchFields, watchedValues, record]);
613
+ return useMemo(() => {
614
+ if (!selectProps) {
615
+ return {};
616
+ }
617
+ if (typeof selectProps === "function") {
618
+ const result2 = selectProps(formState, methods);
619
+ return result2 ?? {};
620
+ }
621
+ const context = buildFieldContext(formState, fieldName);
622
+ const result = evaluateDescriptor(selectProps, context);
623
+ return result ?? {};
624
+ }, [selectProps, formState, methods, fieldName]);
625
+ }
626
+ function useSubscriptions(fieldName, subscriptions) {
627
+ const { addSubscription, removeSubscription } = useFormContext();
628
+ const prevSubscriptionsRef = useRef([]);
629
+ useEffect(() => {
630
+ const prevSubscriptions = prevSubscriptionsRef.current;
631
+ const toRemove = prevSubscriptions.filter(
632
+ (target) => !subscriptions.includes(target)
633
+ );
634
+ const toAdd = subscriptions.filter(
635
+ (target) => !prevSubscriptions.includes(target)
636
+ );
637
+ toRemove.forEach((target) => {
638
+ removeSubscription(target, fieldName);
639
+ });
640
+ toAdd.forEach((target) => {
641
+ addSubscription(target, fieldName);
642
+ });
643
+ prevSubscriptionsRef.current = subscriptions;
644
+ return () => {
645
+ subscriptions.forEach((target) => {
646
+ removeSubscription(target, fieldName);
647
+ });
648
+ };
649
+ }, [fieldName, subscriptions, addSubscription, removeSubscription]);
650
+ }
651
+ function Field({
652
+ name,
653
+ type: typeProp,
654
+ disabled: disabledProp,
655
+ hidden: hiddenProp,
656
+ children,
657
+ shouldRegister = true,
658
+ ...restProps
659
+ }) {
660
+ const {
661
+ config,
662
+ formConfig,
663
+ methods,
664
+ registerField,
665
+ unregisterField,
666
+ registerWatcherSetter,
667
+ unregisterWatcherSetter,
668
+ changeField,
669
+ setFieldValidating
670
+ } = useFormContext();
671
+ const providerConfig = useConfigContext();
672
+ const groupContext = useGroupContext();
673
+ const fieldConfig = config[name] ?? {};
674
+ const type = typeProp ?? fieldConfig.type ?? "textField";
675
+ const inputConfig = useMemo(() => {
676
+ const formInputs = typeof formConfig.inputs === "function" ? formConfig.inputs(providerConfig.inputs) : formConfig.inputs ?? {};
677
+ const mergedInputs = { ...providerConfig.inputs };
678
+ for (const [key, override] of Object.entries(formInputs)) {
679
+ if (mergedInputs[key]) {
680
+ mergedInputs[key] = { ...mergedInputs[key], ...override };
681
+ }
682
+ }
683
+ return resolveInputConfig(type, mergedInputs) ?? { component: "input", defaultValue: "" };
684
+ }, [type, providerConfig.inputs, formConfig.inputs]);
685
+ useEffect(() => {
686
+ if (shouldRegister) {
687
+ registerField(name);
688
+ return () => unregisterField(name);
689
+ }
690
+ }, [name, shouldRegister, registerField, unregisterField]);
691
+ const [watchers, setWatchers] = useState({});
692
+ useEffect(() => {
693
+ registerWatcherSetter(name, setWatchers);
694
+ return () => unregisterWatcherSetter(name);
695
+ }, [name, registerWatcherSetter, unregisterWatcherSetter]);
696
+ const inferredSubscriptions = useInferredInputs({
697
+ selectProps: fieldConfig.selectProps,
698
+ conditions: fieldConfig.conditions,
699
+ subscribesTo: fieldConfig.subscribesTo
700
+ });
701
+ const allSubscriptions = useMemo(() => {
702
+ return [.../* @__PURE__ */ new Set([...inferredSubscriptions, ...groupContext.subscriptions])];
703
+ }, [inferredSubscriptions, groupContext.subscriptions]);
704
+ useSubscriptions(name, allSubscriptions);
705
+ const conditionResult = useConditions({
706
+ conditions: fieldConfig.conditions ?? [],
707
+ subscribesTo: fieldConfig.subscribesTo,
708
+ props: { name }
709
+ });
710
+ const setValueRef = useRef(methods.setValue);
711
+ setValueRef.current = methods.setValue;
712
+ const getValuesRef = useRef(methods.getValues);
713
+ getValuesRef.current = methods.getValues;
714
+ const effectiveSetValue = useMemo(() => {
715
+ if (conditionResult.hasSetCondition) {
716
+ return { hasCondition: true, value: conditionResult.setValue };
717
+ }
718
+ if (groupContext.state.hasSetCondition) {
719
+ return { hasCondition: true, value: groupContext.state.setValue };
720
+ }
721
+ return { hasCondition: false, value: void 0 };
722
+ }, [
723
+ conditionResult.hasSetCondition,
724
+ conditionResult.setValue,
725
+ groupContext.state.hasSetCondition,
726
+ groupContext.state.setValue
727
+ ]);
728
+ useEffect(() => {
729
+ if (effectiveSetValue.hasCondition && effectiveSetValue.value !== void 0) {
730
+ const currentValue = getValuesRef.current(name);
731
+ if (currentValue !== effectiveSetValue.value) {
732
+ setValueRef.current(name, effectiveSetValue.value, {
733
+ shouldValidate: true,
734
+ shouldDirty: true,
735
+ shouldTouch: false
736
+ });
737
+ }
738
+ }
739
+ }, [effectiveSetValue.hasCondition, effectiveSetValue.value, name]);
740
+ const isDisabled = useMemo(() => {
741
+ if (disabledProp !== void 0) return disabledProp;
742
+ if (fieldConfig.disabled !== void 0) return fieldConfig.disabled;
743
+ if (conditionResult.hasDisabledCondition)
744
+ return conditionResult.disabled ?? false;
745
+ if (groupContext.state.isDisabled) return true;
746
+ return false;
747
+ }, [
748
+ disabledProp,
749
+ fieldConfig.disabled,
750
+ conditionResult,
751
+ groupContext.state.isDisabled
752
+ ]);
753
+ const isVisible = useMemo(() => {
754
+ if (hiddenProp !== void 0) return !hiddenProp;
755
+ if (fieldConfig.hidden !== void 0) return !fieldConfig.hidden;
756
+ if (conditionResult.hasVisibleCondition)
757
+ return conditionResult.visible ?? true;
758
+ if (!groupContext.state.isVisible) return false;
759
+ return true;
760
+ }, [hiddenProp, fieldConfig.hidden, conditionResult, groupContext.state.isVisible]);
761
+ const evaluatedSelectProps = usePropsEvaluation({
762
+ selectProps: fieldConfig.selectProps,
763
+ subscribesTo: fieldConfig.subscribesTo,
764
+ fieldName: name
765
+ });
766
+ const label = useMemo(() => {
767
+ return resolveLabel(name, fieldConfig, evaluatedSelectProps, restProps);
768
+ }, [name, fieldConfig, evaluatedSelectProps, restProps]);
769
+ const validationRules = useMemo(() => {
770
+ return {
771
+ ...fieldConfig.rules,
772
+ validate: async (value) => {
773
+ setFieldValidating(name, true);
774
+ try {
775
+ if (fieldConfig.validator) {
776
+ const result = await runValidator(
777
+ fieldConfig.validator,
778
+ value,
779
+ methods.getValues(),
780
+ providerConfig.validators
781
+ );
782
+ if (result !== true && result !== void 0) {
783
+ return resolveErrorMessage(result, providerConfig.errorMessages);
784
+ }
785
+ }
786
+ if (inputConfig.validator) {
787
+ const result = await runValidator(
788
+ inputConfig.validator,
789
+ value,
790
+ methods.getValues(),
791
+ providerConfig.validators
792
+ );
793
+ if (result !== true && result !== void 0) {
794
+ return resolveErrorMessage(result, providerConfig.errorMessages);
795
+ }
796
+ }
797
+ return true;
798
+ } finally {
799
+ setFieldValidating(name, false);
800
+ }
801
+ }
802
+ };
803
+ }, [
804
+ fieldConfig.rules,
805
+ fieldConfig.validator,
806
+ inputConfig.validator,
807
+ methods,
808
+ providerConfig.validators,
809
+ providerConfig.errorMessages,
810
+ name,
811
+ setFieldValidating
812
+ ]);
813
+ const handleChange = useCallback(
814
+ (onChange) => (newValue) => {
815
+ const parsedValue = parse(
816
+ newValue,
817
+ inputConfig.parser,
818
+ providerConfig.parsers
819
+ );
820
+ onChange(parsedValue);
821
+ changeField(name, parsedValue);
822
+ },
823
+ [inputConfig.parser, providerConfig.parsers, changeField, name]
824
+ );
825
+ if (!isVisible) {
826
+ return null;
827
+ }
828
+ return /* @__PURE__ */ jsx(
829
+ Controller,
830
+ {
831
+ control: methods.control,
832
+ name,
833
+ rules: validationRules,
834
+ render: ({ field, fieldState, formState }) => {
835
+ const formattedValue = format(
836
+ field.value,
837
+ inputConfig.formatter,
838
+ providerConfig.formatters
839
+ );
840
+ const finalProps = mergeFieldProps({
841
+ providerDefaultFieldProps: providerConfig.defaultFieldProps,
842
+ providerSelectDefaultFieldProps: {},
843
+ // Evaluated at provider level if needed
844
+ formDefaultFieldProps: formConfig.defaultFieldProps,
845
+ formSelectDefaultFieldProps: {},
846
+ // Evaluated at form level if needed
847
+ inputProps: inputConfig.props,
848
+ fieldConfigProps: fieldConfig.props,
849
+ selectProps: evaluatedSelectProps,
850
+ componentProps: restProps,
851
+ coreProps: {
852
+ name,
853
+ label,
854
+ disabled: isDisabled,
855
+ error: fieldState.error?.message,
856
+ [inputConfig.inputFieldProp ?? "value"]: formattedValue,
857
+ onChange: handleChange(field.onChange),
858
+ onBlur: field.onBlur,
859
+ ref: field.ref
860
+ }
861
+ });
862
+ const Component = inputConfig.component;
863
+ const template = inputConfig.template ?? providerConfig.inputTemplates[type] ?? providerConfig.defaultInputTemplate;
864
+ const TemplateComponent = template;
865
+ const renderedField = TemplateComponent ? /* @__PURE__ */ jsx(
866
+ TemplateComponent,
867
+ {
868
+ Field: Component,
869
+ fieldProps: finalProps,
870
+ fieldState,
871
+ formState
872
+ }
873
+ ) : /* @__PURE__ */ jsx(Component, { ...finalProps });
874
+ if (typeof children === "function") {
875
+ const result = children({
876
+ fieldState,
877
+ renderedField,
878
+ fieldProps: finalProps,
879
+ watchers,
880
+ formState
881
+ });
882
+ return /* @__PURE__ */ jsx(Fragment, { children: result });
883
+ }
884
+ return renderedField;
885
+ }
886
+ }
887
+ );
888
+ }
889
+ function FieldGroup({ name, children }) {
890
+ const { formConfig } = useFormContext();
891
+ const parentContext = useGroupContext();
892
+ const groupConfig = formConfig.groups?.[name] ?? {
893
+ conditions: [],
894
+ subscribesTo: []
895
+ };
896
+ if (process.env.NODE_ENV !== "production" && !formConfig.groups?.[name]) {
897
+ console.warn(
898
+ `FieldGroup: No config found for group "${name}". Make sure to define it in formConfig.groups.`
899
+ );
900
+ }
901
+ const inferredInputs = useInferredInputs({
902
+ conditions: groupConfig.conditions,
903
+ subscribesTo: groupConfig.subscribesTo
904
+ });
905
+ const conditionResult = useConditions({
906
+ conditions: groupConfig.conditions ?? [],
907
+ subscribesTo: groupConfig.subscribesTo
908
+ });
909
+ const mergedState = useMemo(() => {
910
+ const isDisabled = (conditionResult.hasDisabledCondition ? conditionResult.disabled ?? false : false) || parentContext.state.isDisabled;
911
+ const isVisible = (conditionResult.hasVisibleCondition ? conditionResult.visible ?? true : true) && parentContext.state.isVisible;
912
+ const hasSetCondition = conditionResult.hasSetCondition || parentContext.state.hasSetCondition;
913
+ const setValue = conditionResult.hasSetCondition ? conditionResult.setValue : parentContext.state.setValue;
914
+ const conditions = [
915
+ ...parentContext.state.conditions,
916
+ ...groupConfig.conditions ?? []
917
+ ];
918
+ const subscriptions = [
919
+ ...parentContext.state.subscriptions,
920
+ ...groupConfig.subscribesTo ?? []
921
+ ];
922
+ return {
923
+ isDisabled,
924
+ isVisible,
925
+ hasSetCondition,
926
+ setValue,
927
+ conditions,
928
+ subscriptions
929
+ };
930
+ }, [conditionResult, parentContext.state, groupConfig]);
931
+ const contextValue = useMemo(
932
+ () => ({
933
+ state: mergedState,
934
+ subscriptions: mergedState.subscriptions,
935
+ inferredInputs,
936
+ config: groupConfig
937
+ }),
938
+ [mergedState, inferredInputs, groupConfig]
939
+ );
940
+ return /* @__PURE__ */ jsx(GroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
941
+ "span",
942
+ {
943
+ style: { display: mergedState.isVisible ? void 0 : "none" },
944
+ "data-formality-group": name,
945
+ children
946
+ }
947
+ ) });
948
+ }
949
+ function UnusedFields({ children }) {
950
+ const { unusedFields, config } = useFormContext();
951
+ const sortedFields = useMemo(() => {
952
+ return sortFieldsByOrder(unusedFields, config);
953
+ }, [unusedFields, config]);
954
+ if (children) {
955
+ return /* @__PURE__ */ jsx(Fragment, { children: sortedFields.map(
956
+ (name) => children({
957
+ name,
958
+ // CRITICAL: shouldRegister={false} prevents infinite loop (PRD 5.5, 18.9)
959
+ component: /* @__PURE__ */ jsx(Field, { name, shouldRegister: false }, name)
960
+ })
961
+ ) });
962
+ }
963
+ return /* @__PURE__ */ jsx(Fragment, { children: sortedFields.map((name) => (
964
+ // CRITICAL: shouldRegister={false} prevents infinite loop (PRD 5.5, 18.9)
965
+ /* @__PURE__ */ jsx(Field, { name, shouldRegister: false }, name)
966
+ )) });
967
+ }
968
+ function useFormState(options) {
969
+ const rhfContext = useFormContext$1();
970
+ let formalityContext = null;
971
+ try {
972
+ formalityContext = useFormContext();
973
+ } catch {
974
+ }
975
+ const fieldNames = useMemo(() => {
976
+ return Array.isArray(options.name) ? options.name : [options.name];
977
+ }, [options.name]);
978
+ const watchedValues = useWatch({
979
+ control: rhfContext.control,
980
+ name: fieldNames
981
+ });
982
+ const fields = useMemo(() => {
983
+ const result2 = {};
984
+ if (fieldNames.length === 0) {
985
+ return result2;
986
+ }
987
+ const values = fieldNames.length === 1 ? [watchedValues] : watchedValues;
988
+ fieldNames.forEach((name, index) => {
989
+ const value = values[index];
990
+ result2[name] = makeProxyState({
991
+ value,
992
+ isTouched: false,
993
+ isDirty: false,
994
+ isValidating: false,
995
+ error: void 0,
996
+ invalid: false
997
+ });
998
+ });
999
+ return result2;
1000
+ }, [fieldNames, watchedValues]);
1001
+ const result = useMemo(() => {
1002
+ const base = {
1003
+ fields,
1004
+ record: {},
1005
+ // Will be overwritten by defineProperty
1006
+ // Provide minimal form-level state that doesn't require subscriptions
1007
+ isDirty: false,
1008
+ isTouched: false,
1009
+ isValid: true,
1010
+ isSubmitting: false,
1011
+ errors: {},
1012
+ touchedFields: {},
1013
+ dirtyFields: {},
1014
+ defaultValues: {}
1015
+ };
1016
+ Object.defineProperty(base, "record", {
1017
+ get: () => formalityContext?.record ?? {},
1018
+ enumerable: true,
1019
+ configurable: true
1020
+ });
1021
+ return base;
1022
+ }, [fields, formalityContext?.record]);
1023
+ return result;
1024
+ }
1025
+
1026
+ export { ConfigContext, Field, FieldGroup, Form, FormContext, FormalityProvider, GroupContext, UnusedFields, makeDeepProxyState, makeProxyState, useConditions, useConfigContext, useFormContext, useFormState, useGroupContext, useInferredInputs, usePropsEvaluation, useSubscriptions };
1027
+ //# sourceMappingURL=index.js.map
1028
+ //# sourceMappingURL=index.js.map