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