@formality-ui/react 0.0.0 → 0.1.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/README.md +250 -32
- package/dist/index.cjs +342 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +250 -19
- package/dist/index.d.ts +250 -19
- package/dist/index.js +342 -78
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,9 @@ function useConfigContext() {
|
|
|
25
25
|
var FormContext = react.createContext(null);
|
|
26
26
|
FormContext.displayName = "FormalityFormContext";
|
|
27
27
|
function useFormContext() {
|
|
28
|
-
const context = react.useContext(
|
|
28
|
+
const context = react.useContext(
|
|
29
|
+
FormContext
|
|
30
|
+
);
|
|
29
31
|
if (!context) {
|
|
30
32
|
throw new Error(
|
|
31
33
|
"useFormContext must be used within a Form component. Make sure your component is wrapped in a <Form> component."
|
|
@@ -111,7 +113,6 @@ function makeDeepProxyState(source) {
|
|
|
111
113
|
const result = {};
|
|
112
114
|
for (const key in source) {
|
|
113
115
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
114
|
-
source[key];
|
|
115
116
|
Object.defineProperty(result, key, {
|
|
116
117
|
get: () => {
|
|
117
118
|
const currentValue = source[key];
|
|
@@ -193,6 +194,11 @@ function Form({
|
|
|
193
194
|
invertedSubscriptions.current.set(target, /* @__PURE__ */ new Set());
|
|
194
195
|
}
|
|
195
196
|
invertedSubscriptions.current.get(target).add(subscriber);
|
|
197
|
+
if (process.env.NODE_ENV !== "production") {
|
|
198
|
+
console.warn(
|
|
199
|
+
`[Formality Subscription] "${subscriber}" added to watch "${target}"`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
196
202
|
const setter = watcherSetters.current.get(target);
|
|
197
203
|
if (setter) {
|
|
198
204
|
setter((prev) => ({ ...prev, [subscriber]: true }));
|
|
@@ -205,7 +211,20 @@ function Form({
|
|
|
205
211
|
}, []);
|
|
206
212
|
const removeSubscription = react.useCallback(
|
|
207
213
|
(target, subscriber) => {
|
|
214
|
+
const subscribers = invertedSubscriptions.current.get(target);
|
|
215
|
+
const subscriptionExists = subscribers?.has(subscriber) ?? false;
|
|
208
216
|
invertedSubscriptions.current.get(target)?.delete(subscriber);
|
|
217
|
+
if (process.env.NODE_ENV !== "production") {
|
|
218
|
+
if (subscriptionExists) {
|
|
219
|
+
console.warn(
|
|
220
|
+
`[Formality Subscription] "${subscriber}" removed from watching "${target}"`
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
console.warn(
|
|
224
|
+
`[Formality Subscription] WARNING: Double-cleanup attempt - "${subscriber}" was not watching "${target}"`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
209
228
|
const setter = watcherSetters.current.get(target);
|
|
210
229
|
if (setter) {
|
|
211
230
|
setter((prev) => {
|
|
@@ -255,14 +274,18 @@ function Form({
|
|
|
255
274
|
return affected;
|
|
256
275
|
}, []);
|
|
257
276
|
const changeField = react.useCallback(
|
|
258
|
-
(name, value) => {
|
|
277
|
+
(name, value, inputConfig) => {
|
|
259
278
|
if (autoSave) {
|
|
260
279
|
pendingChangedFields.current.add(name);
|
|
261
280
|
const affected = getAffectedFields(name);
|
|
262
281
|
for (const field of affected) {
|
|
263
282
|
pendingAffectedFields.current.add(field);
|
|
264
283
|
}
|
|
265
|
-
|
|
284
|
+
if (inputConfig?.debounce === false) {
|
|
285
|
+
executeAutoSaveRef.current?.();
|
|
286
|
+
} else {
|
|
287
|
+
debouncedSubmitRef.current?.();
|
|
288
|
+
}
|
|
266
289
|
}
|
|
267
290
|
},
|
|
268
291
|
[autoSave, getAffectedFields]
|
|
@@ -305,7 +328,7 @@ function Form({
|
|
|
305
328
|
};
|
|
306
329
|
}, [methods, config, record, defaultValues]);
|
|
307
330
|
const handleSubmit = react.useCallback(
|
|
308
|
-
async (values) => {
|
|
331
|
+
async (values, overrideOnSubmit) => {
|
|
309
332
|
for (const [, isValidating] of validatingFields.current) {
|
|
310
333
|
if (isValidating) return;
|
|
311
334
|
}
|
|
@@ -318,12 +341,26 @@ function Form({
|
|
|
318
341
|
return;
|
|
319
342
|
}
|
|
320
343
|
}
|
|
321
|
-
const submitValues = transformValuesForSubmit(
|
|
322
|
-
|
|
344
|
+
const submitValues = transformValuesForSubmit(
|
|
345
|
+
values,
|
|
346
|
+
config,
|
|
347
|
+
mergedInputs
|
|
348
|
+
);
|
|
349
|
+
const sink = overrideOnSubmit ?? onSubmit;
|
|
350
|
+
await sink?.(submitValues);
|
|
323
351
|
},
|
|
324
352
|
[validate, methods, onSubmit, config, mergedInputs]
|
|
325
353
|
);
|
|
354
|
+
const handleRenderSubmit = react.useCallback(
|
|
355
|
+
(userOnSubmit) => {
|
|
356
|
+
return methods.handleSubmit(async (values) => {
|
|
357
|
+
await handleSubmit(values, userOnSubmit);
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
[methods, handleSubmit]
|
|
361
|
+
);
|
|
326
362
|
const debouncedSubmitRef = react.useRef();
|
|
363
|
+
const executeAutoSaveRef = react.useRef();
|
|
327
364
|
const waitForFieldValidation = react.useCallback(
|
|
328
365
|
async (fields, version) => {
|
|
329
366
|
const maxWaitMs = 1e4;
|
|
@@ -355,29 +392,36 @@ function Form({
|
|
|
355
392
|
if (changedFields.size === 0) {
|
|
356
393
|
return;
|
|
357
394
|
}
|
|
358
|
-
const
|
|
395
|
+
const fieldsToWaitFor = [...changedFields, ...affectedFields];
|
|
396
|
+
const fieldsToTrigger = [...affectedFields];
|
|
359
397
|
const validationsComplete = await waitForFieldValidation(
|
|
360
|
-
|
|
398
|
+
fieldsToWaitFor,
|
|
361
399
|
executionVersion
|
|
362
400
|
);
|
|
363
401
|
if (!validationsComplete || executionVersionRef.current !== executionVersion) {
|
|
364
402
|
return;
|
|
365
403
|
}
|
|
366
|
-
|
|
367
|
-
const
|
|
404
|
+
for (const fieldName of changedFields) {
|
|
405
|
+
const fieldState = methods.getFieldState(fieldName);
|
|
406
|
+
if (fieldState.error) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (fieldsToTrigger.length > 0) {
|
|
411
|
+
const isValid = await methods.trigger(fieldsToTrigger);
|
|
368
412
|
if (executionVersionRef.current !== executionVersion) {
|
|
369
413
|
return;
|
|
370
414
|
}
|
|
371
415
|
if (!isValid) {
|
|
372
416
|
return;
|
|
373
417
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
418
|
+
const postTriggerComplete = await waitForFieldValidation(
|
|
419
|
+
fieldsToTrigger,
|
|
420
|
+
executionVersion
|
|
421
|
+
);
|
|
422
|
+
if (!postTriggerComplete || executionVersionRef.current !== executionVersion) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
381
425
|
}
|
|
382
426
|
const formState = methods.formState;
|
|
383
427
|
if (Object.keys(formState.errors).length > 0) {
|
|
@@ -386,7 +430,27 @@ function Form({
|
|
|
386
430
|
const values = methods.getValues();
|
|
387
431
|
await handleSubmit(values);
|
|
388
432
|
}, [methods, handleSubmit, waitForFieldValidation]);
|
|
433
|
+
executeAutoSaveRef.current = executeAutoSave;
|
|
389
434
|
react.useEffect(() => {
|
|
435
|
+
if (debounceMs === false) {
|
|
436
|
+
const immediateFn = Object.assign(
|
|
437
|
+
() => {
|
|
438
|
+
executeAutoSave();
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
cancel: () => {
|
|
442
|
+
},
|
|
443
|
+
// No-op for immediate function
|
|
444
|
+
flush: () => executeAutoSave(),
|
|
445
|
+
// Execute immediately on flush
|
|
446
|
+
pending: () => false
|
|
447
|
+
// Never pending when immediate
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
debouncedSubmitRef.current = immediateFn;
|
|
451
|
+
return () => {
|
|
452
|
+
};
|
|
453
|
+
}
|
|
390
454
|
const debouncedFn = lodashEs.debounce(() => {
|
|
391
455
|
executeAutoSave();
|
|
392
456
|
}, debounceMs);
|
|
@@ -399,12 +463,13 @@ function Form({
|
|
|
399
463
|
debouncedFn.cancel();
|
|
400
464
|
};
|
|
401
465
|
}, [executeAutoSave, debounceMs]);
|
|
402
|
-
const debouncedSubmit = react.useCallback(() => {
|
|
403
|
-
debouncedSubmitRef.current?.();
|
|
404
|
-
}, []);
|
|
405
466
|
const submitImmediate = react.useCallback(() => {
|
|
406
|
-
debouncedSubmitRef.current?.flush
|
|
407
|
-
|
|
467
|
+
if (debouncedSubmitRef.current?.flush) {
|
|
468
|
+
debouncedSubmitRef.current.flush();
|
|
469
|
+
} else {
|
|
470
|
+
executeAutoSave();
|
|
471
|
+
}
|
|
472
|
+
}, [executeAutoSave]);
|
|
408
473
|
const unusedFields = react.useMemo(() => {
|
|
409
474
|
const allFields = Object.keys(config);
|
|
410
475
|
return allFields.filter((name) => !registeredFields.has(name));
|
|
@@ -472,6 +537,7 @@ function Form({
|
|
|
472
537
|
unusedFields,
|
|
473
538
|
formState: methods.formState,
|
|
474
539
|
methods,
|
|
540
|
+
handleSubmit: handleRenderSubmit,
|
|
475
541
|
resolvedTitle
|
|
476
542
|
}) : children }) }) });
|
|
477
543
|
}
|
|
@@ -492,9 +558,21 @@ function transformValuesForSubmit(values, config, inputs) {
|
|
|
492
558
|
return result;
|
|
493
559
|
}
|
|
494
560
|
function useInferredInputs(options) {
|
|
495
|
-
const {
|
|
561
|
+
const {
|
|
562
|
+
selectProps,
|
|
563
|
+
formDefaultFieldProps,
|
|
564
|
+
providerDefaultFieldProps,
|
|
565
|
+
conditions = [],
|
|
566
|
+
subscribesTo = []
|
|
567
|
+
} = options;
|
|
496
568
|
return react.useMemo(() => {
|
|
497
569
|
const inferred = [...subscribesTo];
|
|
570
|
+
if (providerDefaultFieldProps) {
|
|
571
|
+
inferred.push(...core.inferFieldsFromDescriptor(providerDefaultFieldProps));
|
|
572
|
+
}
|
|
573
|
+
if (formDefaultFieldProps) {
|
|
574
|
+
inferred.push(...core.inferFieldsFromDescriptor(formDefaultFieldProps));
|
|
575
|
+
}
|
|
498
576
|
if (selectProps) {
|
|
499
577
|
inferred.push(...core.inferFieldsFromDescriptor(selectProps));
|
|
500
578
|
}
|
|
@@ -502,12 +580,18 @@ function useInferredInputs(options) {
|
|
|
502
580
|
inferred.push(...core.inferFieldsFromConditions(conditions));
|
|
503
581
|
}
|
|
504
582
|
return [...new Set(inferred)];
|
|
505
|
-
}, [
|
|
583
|
+
}, [
|
|
584
|
+
providerDefaultFieldProps,
|
|
585
|
+
formDefaultFieldProps,
|
|
586
|
+
selectProps,
|
|
587
|
+
conditions,
|
|
588
|
+
subscribesTo
|
|
589
|
+
]);
|
|
506
590
|
}
|
|
507
591
|
|
|
508
592
|
// src/hooks/useConditions.ts
|
|
509
593
|
function useConditions(options) {
|
|
510
|
-
const { conditions, subscribesTo, props } = options;
|
|
594
|
+
const { conditions, subscribesTo, props, allFieldsConfig } = options;
|
|
511
595
|
const { record, methods } = useFormContext();
|
|
512
596
|
const watchFields = useInferredInputs({
|
|
513
597
|
conditions,
|
|
@@ -531,12 +615,14 @@ function useConditions(options) {
|
|
|
531
615
|
}
|
|
532
616
|
return values;
|
|
533
617
|
}, [watchFields, watchedValues]);
|
|
534
|
-
const
|
|
618
|
+
const baseFieldStates = react.useMemo(() => {
|
|
535
619
|
const states = {};
|
|
536
|
-
|
|
620
|
+
const currentFieldName = props?.name;
|
|
621
|
+
const allFieldsForPass1 = currentFieldName ? [.../* @__PURE__ */ new Set([...watchFields, currentFieldName])] : watchFields;
|
|
622
|
+
if (allFieldsForPass1.length === 0) {
|
|
537
623
|
return states;
|
|
538
624
|
}
|
|
539
|
-
|
|
625
|
+
allFieldsForPass1.forEach((fieldName) => {
|
|
540
626
|
const fieldState = methods.getFieldState(fieldName);
|
|
541
627
|
states[fieldName] = {
|
|
542
628
|
value: fieldValues[fieldName],
|
|
@@ -546,10 +632,112 @@ function useConditions(options) {
|
|
|
546
632
|
invalid: fieldState.invalid,
|
|
547
633
|
isValidating: false
|
|
548
634
|
// Not easily available per-field
|
|
635
|
+
// ❌ NO disabled property - this is critical for Pass 1 to prevent circular dependency
|
|
549
636
|
};
|
|
550
637
|
});
|
|
551
638
|
return states;
|
|
552
|
-
}, [watchFields, fieldValues, methods]);
|
|
639
|
+
}, [watchFields, fieldValues, methods, props]);
|
|
640
|
+
const disabledStates = react.useMemo(() => {
|
|
641
|
+
const disabled = {};
|
|
642
|
+
const currentFieldName = props?.name;
|
|
643
|
+
const configFields = allFieldsConfig ? Object.keys(allFieldsConfig) : [];
|
|
644
|
+
const allFieldsForPass2 = [.../* @__PURE__ */ new Set([...watchFields, ...configFields])];
|
|
645
|
+
if (currentFieldName && !allFieldsForPass2.includes(currentFieldName)) {
|
|
646
|
+
allFieldsForPass2.push(currentFieldName);
|
|
647
|
+
}
|
|
648
|
+
if (allFieldsForPass2.length === 0) {
|
|
649
|
+
return disabled;
|
|
650
|
+
}
|
|
651
|
+
const allBaseStates = {};
|
|
652
|
+
for (const fieldName of allFieldsForPass2) {
|
|
653
|
+
if (baseFieldStates[fieldName]) {
|
|
654
|
+
allBaseStates[fieldName] = baseFieldStates[fieldName];
|
|
655
|
+
} else {
|
|
656
|
+
const fieldState = methods.getFieldState(fieldName);
|
|
657
|
+
allBaseStates[fieldName] = {
|
|
658
|
+
value: fieldValues[fieldName],
|
|
659
|
+
isTouched: fieldState.isTouched,
|
|
660
|
+
isDirty: fieldState.isDirty,
|
|
661
|
+
error: fieldState.error,
|
|
662
|
+
invalid: fieldState.invalid,
|
|
663
|
+
isValidating: false
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
let currentStates = allBaseStates;
|
|
668
|
+
let hasChanged = true;
|
|
669
|
+
let iteration = 0;
|
|
670
|
+
const maxIterations = 10;
|
|
671
|
+
while (hasChanged && iteration < maxIterations) {
|
|
672
|
+
hasChanged = false;
|
|
673
|
+
iteration++;
|
|
674
|
+
const newDisabled = {};
|
|
675
|
+
for (const fieldName of allFieldsForPass2) {
|
|
676
|
+
const fieldConfig = allFieldsConfig?.[fieldName];
|
|
677
|
+
const fieldConditions = fieldConfig?.conditions ?? [];
|
|
678
|
+
const configDisabled = fieldConfig?.disabled;
|
|
679
|
+
const result = core.evaluateConditions({
|
|
680
|
+
conditions: fieldConditions,
|
|
681
|
+
fieldValues,
|
|
682
|
+
fieldStates: currentStates,
|
|
683
|
+
record,
|
|
684
|
+
props: { name: fieldName }
|
|
685
|
+
});
|
|
686
|
+
newDisabled[fieldName] = configDisabled ?? result.disabled ?? false;
|
|
687
|
+
}
|
|
688
|
+
const newStates = {};
|
|
689
|
+
for (const fieldName of allFieldsForPass2) {
|
|
690
|
+
const baseState = allBaseStates[fieldName];
|
|
691
|
+
if (baseState) {
|
|
692
|
+
newStates[fieldName] = {
|
|
693
|
+
...baseState,
|
|
694
|
+
disabled: newDisabled[fieldName]
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
for (const fieldName of allFieldsForPass2) {
|
|
699
|
+
const oldDisabled = disabled[fieldName];
|
|
700
|
+
const newDisabledValue = newDisabled[fieldName];
|
|
701
|
+
if (oldDisabled !== newDisabledValue) {
|
|
702
|
+
hasChanged = true;
|
|
703
|
+
disabled[fieldName] = newDisabledValue;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
currentStates = newStates;
|
|
707
|
+
}
|
|
708
|
+
return disabled;
|
|
709
|
+
}, [
|
|
710
|
+
watchFields,
|
|
711
|
+
allFieldsConfig,
|
|
712
|
+
fieldValues,
|
|
713
|
+
baseFieldStates,
|
|
714
|
+
record,
|
|
715
|
+
props,
|
|
716
|
+
methods
|
|
717
|
+
]);
|
|
718
|
+
const fieldStates = react.useMemo(() => {
|
|
719
|
+
const states = { ...baseFieldStates };
|
|
720
|
+
Object.entries(disabledStates).forEach(([fieldName, disabled]) => {
|
|
721
|
+
if (states[fieldName]) {
|
|
722
|
+
states[fieldName] = {
|
|
723
|
+
...states[fieldName],
|
|
724
|
+
disabled
|
|
725
|
+
};
|
|
726
|
+
} else {
|
|
727
|
+
const fieldState = methods.getFieldState(fieldName);
|
|
728
|
+
states[fieldName] = {
|
|
729
|
+
value: fieldValues[fieldName],
|
|
730
|
+
isTouched: fieldState.isTouched,
|
|
731
|
+
isDirty: fieldState.isDirty,
|
|
732
|
+
error: fieldState.error,
|
|
733
|
+
invalid: fieldState.invalid,
|
|
734
|
+
isValidating: false,
|
|
735
|
+
disabled
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
return states;
|
|
740
|
+
}, [baseFieldStates, disabledStates, fieldValues, methods]);
|
|
553
741
|
return react.useMemo(() => {
|
|
554
742
|
if (conditions.length === 0) {
|
|
555
743
|
return {
|
|
@@ -571,9 +759,17 @@ function useConditions(options) {
|
|
|
571
759
|
}, [conditions, fieldValues, fieldStates, record, props]);
|
|
572
760
|
}
|
|
573
761
|
function usePropsEvaluation(options) {
|
|
574
|
-
const {
|
|
762
|
+
const {
|
|
763
|
+
selectProps,
|
|
764
|
+
formDefaultFieldProps,
|
|
765
|
+
providerDefaultFieldProps,
|
|
766
|
+
subscribesTo,
|
|
767
|
+
fieldName
|
|
768
|
+
} = options;
|
|
575
769
|
const { record, methods } = useFormContext();
|
|
576
770
|
const watchFields = useInferredInputs({
|
|
771
|
+
providerDefaultFieldProps,
|
|
772
|
+
formDefaultFieldProps,
|
|
577
773
|
selectProps,
|
|
578
774
|
subscribesTo
|
|
579
775
|
});
|
|
@@ -584,10 +780,13 @@ function usePropsEvaluation(options) {
|
|
|
584
780
|
const formState = react.useMemo(() => {
|
|
585
781
|
const fields = {};
|
|
586
782
|
if (watchFields.length > 0) {
|
|
587
|
-
const values =
|
|
588
|
-
acc
|
|
589
|
-
|
|
590
|
-
|
|
783
|
+
const values = Array.isArray(watchedValues) ? watchFields.reduce(
|
|
784
|
+
(acc, field, i) => {
|
|
785
|
+
acc[field] = watchedValues[i];
|
|
786
|
+
return acc;
|
|
787
|
+
},
|
|
788
|
+
{}
|
|
789
|
+
) : watchedValues;
|
|
591
790
|
watchFields.forEach((name) => {
|
|
592
791
|
fields[name] = makeProxyState({
|
|
593
792
|
value: values[name],
|
|
@@ -613,40 +812,75 @@ function usePropsEvaluation(options) {
|
|
|
613
812
|
};
|
|
614
813
|
}, [watchFields, watchedValues, record]);
|
|
615
814
|
return react.useMemo(() => {
|
|
616
|
-
|
|
617
|
-
|
|
815
|
+
let providerResult = {};
|
|
816
|
+
if (providerDefaultFieldProps) {
|
|
817
|
+
if (typeof providerDefaultFieldProps === "function") {
|
|
818
|
+
providerResult = providerDefaultFieldProps(formState, methods) ?? {};
|
|
819
|
+
} else {
|
|
820
|
+
const context = core.buildFieldContext(formState, fieldName);
|
|
821
|
+
providerResult = core.evaluateDescriptor(providerDefaultFieldProps, context) ?? {};
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
let formResult = {};
|
|
825
|
+
if (formDefaultFieldProps) {
|
|
826
|
+
if (typeof formDefaultFieldProps === "function") {
|
|
827
|
+
formResult = formDefaultFieldProps(formState, methods) ?? {};
|
|
828
|
+
} else {
|
|
829
|
+
const context = core.buildFieldContext(formState, fieldName);
|
|
830
|
+
formResult = core.evaluateDescriptor(formDefaultFieldProps, context) ?? {};
|
|
831
|
+
}
|
|
618
832
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
833
|
+
let fieldResult = {};
|
|
834
|
+
if (selectProps) {
|
|
835
|
+
if (typeof selectProps === "function") {
|
|
836
|
+
fieldResult = selectProps(formState, methods) ?? {};
|
|
837
|
+
} else {
|
|
838
|
+
const context = core.buildFieldContext(formState, fieldName);
|
|
839
|
+
fieldResult = core.evaluateDescriptor(selectProps, context) ?? {};
|
|
840
|
+
}
|
|
622
841
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
842
|
+
return {
|
|
843
|
+
providerSelectProps: providerResult,
|
|
844
|
+
formSelectProps: formResult,
|
|
845
|
+
fieldSelectProps: fieldResult
|
|
846
|
+
};
|
|
847
|
+
}, [
|
|
848
|
+
providerDefaultFieldProps,
|
|
849
|
+
formDefaultFieldProps,
|
|
850
|
+
selectProps,
|
|
851
|
+
formState,
|
|
852
|
+
methods,
|
|
853
|
+
fieldName
|
|
854
|
+
]);
|
|
627
855
|
}
|
|
628
856
|
function useSubscriptions(fieldName, subscriptions) {
|
|
629
857
|
const { addSubscription, removeSubscription } = useFormContext();
|
|
630
|
-
const
|
|
858
|
+
const runIdRef = react.useRef(0);
|
|
859
|
+
const runSubscriptionsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
631
860
|
react.useEffect(() => {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
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) => {
|
|
861
|
+
const currentRunId = ++runIdRef.current;
|
|
862
|
+
runSubscriptionsRef.current.set(currentRunId, [...subscriptions]);
|
|
863
|
+
subscriptions.forEach((target) => {
|
|
643
864
|
addSubscription(target, fieldName);
|
|
865
|
+
if (process.env.NODE_ENV !== "production") {
|
|
866
|
+
console.warn(
|
|
867
|
+
`[Formality Subscription] Run ${currentRunId}: "${fieldName}" subscribing to "${target}"`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
644
870
|
});
|
|
645
|
-
prevSubscriptionsRef.current = subscriptions;
|
|
646
871
|
return () => {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
872
|
+
const thisRunSubscriptions = runSubscriptionsRef.current.get(currentRunId);
|
|
873
|
+
if (thisRunSubscriptions) {
|
|
874
|
+
if (process.env.NODE_ENV !== "production") {
|
|
875
|
+
console.warn(
|
|
876
|
+
`[Formality Subscription] Run ${currentRunId}: "${fieldName}" cleaning up [${thisRunSubscriptions.join(", ")}]`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
[...thisRunSubscriptions].reverse().forEach((target) => {
|
|
880
|
+
removeSubscription(target, fieldName);
|
|
881
|
+
});
|
|
882
|
+
runSubscriptionsRef.current.delete(currentRunId);
|
|
883
|
+
}
|
|
650
884
|
};
|
|
651
885
|
}, [fieldName, subscriptions, addSubscription, removeSubscription]);
|
|
652
886
|
}
|
|
@@ -657,6 +891,7 @@ function Field({
|
|
|
657
891
|
hidden: hiddenProp,
|
|
658
892
|
children,
|
|
659
893
|
shouldRegister = true,
|
|
894
|
+
inputConfig: inputConfigProp,
|
|
660
895
|
...restProps
|
|
661
896
|
}) {
|
|
662
897
|
const {
|
|
@@ -676,14 +911,23 @@ function Field({
|
|
|
676
911
|
const type = typeProp ?? fieldConfig.type ?? "textField";
|
|
677
912
|
const inputConfig = react.useMemo(() => {
|
|
678
913
|
const formInputs = typeof formConfig.inputs === "function" ? formConfig.inputs(providerConfig.inputs) : formConfig.inputs ?? {};
|
|
679
|
-
const mergedInputs = {
|
|
914
|
+
const mergedInputs = {
|
|
915
|
+
...providerConfig.inputs
|
|
916
|
+
};
|
|
680
917
|
for (const [key, override] of Object.entries(formInputs)) {
|
|
681
918
|
if (mergedInputs[key]) {
|
|
682
|
-
mergedInputs[key] = {
|
|
919
|
+
mergedInputs[key] = {
|
|
920
|
+
...mergedInputs[key],
|
|
921
|
+
...override
|
|
922
|
+
};
|
|
683
923
|
}
|
|
684
924
|
}
|
|
685
|
-
|
|
686
|
-
|
|
925
|
+
const baseInputConfig = core.resolveInputConfig(type, mergedInputs) ?? {
|
|
926
|
+
component: "input",
|
|
927
|
+
defaultValue: ""
|
|
928
|
+
};
|
|
929
|
+
return inputConfigProp ? { ...baseInputConfig, ...inputConfigProp } : baseInputConfig;
|
|
930
|
+
}, [type, providerConfig.inputs, formConfig.inputs, inputConfigProp]);
|
|
687
931
|
react.useEffect(() => {
|
|
688
932
|
if (shouldRegister) {
|
|
689
933
|
registerField(name);
|
|
@@ -701,13 +945,17 @@ function Field({
|
|
|
701
945
|
subscribesTo: fieldConfig.subscribesTo
|
|
702
946
|
});
|
|
703
947
|
const allSubscriptions = react.useMemo(() => {
|
|
704
|
-
return [
|
|
948
|
+
return [
|
|
949
|
+
.../* @__PURE__ */ new Set([...inferredSubscriptions, ...groupContext.subscriptions])
|
|
950
|
+
];
|
|
705
951
|
}, [inferredSubscriptions, groupContext.subscriptions]);
|
|
706
952
|
useSubscriptions(name, allSubscriptions);
|
|
707
953
|
const conditionResult = useConditions({
|
|
708
954
|
conditions: fieldConfig.conditions ?? [],
|
|
709
955
|
subscribesTo: fieldConfig.subscribesTo,
|
|
710
|
-
props: { name }
|
|
956
|
+
props: { name },
|
|
957
|
+
allFieldsConfig: config
|
|
958
|
+
// Pass all field configs for two-pass evaluation of disabled states
|
|
711
959
|
});
|
|
712
960
|
const setValueRef = react.useRef(methods.setValue);
|
|
713
961
|
setValueRef.current = methods.setValue;
|
|
@@ -759,15 +1007,22 @@ function Field({
|
|
|
759
1007
|
return conditionResult.visible ?? true;
|
|
760
1008
|
if (!groupContext.state.isVisible) return false;
|
|
761
1009
|
return true;
|
|
762
|
-
}, [
|
|
763
|
-
|
|
1010
|
+
}, [
|
|
1011
|
+
hiddenProp,
|
|
1012
|
+
fieldConfig.hidden,
|
|
1013
|
+
conditionResult,
|
|
1014
|
+
groupContext.state.isVisible
|
|
1015
|
+
]);
|
|
1016
|
+
const { providerSelectProps, formSelectProps, fieldSelectProps } = usePropsEvaluation({
|
|
764
1017
|
selectProps: fieldConfig.selectProps,
|
|
1018
|
+
formDefaultFieldProps: formConfig.selectDefaultFieldProps,
|
|
1019
|
+
providerDefaultFieldProps: providerConfig.selectDefaultFieldProps,
|
|
765
1020
|
subscribesTo: fieldConfig.subscribesTo,
|
|
766
1021
|
fieldName: name
|
|
767
1022
|
});
|
|
768
1023
|
const label = react.useMemo(() => {
|
|
769
|
-
return core.resolveLabel(name, fieldConfig,
|
|
770
|
-
}, [name, fieldConfig,
|
|
1024
|
+
return core.resolveLabel(name, fieldConfig, fieldSelectProps, restProps);
|
|
1025
|
+
}, [name, fieldConfig, fieldSelectProps, restProps]);
|
|
771
1026
|
const validationRules = react.useMemo(() => {
|
|
772
1027
|
return {
|
|
773
1028
|
...fieldConfig.rules,
|
|
@@ -820,9 +1075,15 @@ function Field({
|
|
|
820
1075
|
providerConfig.parsers
|
|
821
1076
|
);
|
|
822
1077
|
onChange(parsedValue);
|
|
823
|
-
changeField(name, parsedValue);
|
|
1078
|
+
changeField(name, parsedValue, inputConfig);
|
|
824
1079
|
},
|
|
825
|
-
[
|
|
1080
|
+
[
|
|
1081
|
+
inputConfig.parser,
|
|
1082
|
+
providerConfig.parsers,
|
|
1083
|
+
changeField,
|
|
1084
|
+
name,
|
|
1085
|
+
inputConfig
|
|
1086
|
+
]
|
|
826
1087
|
);
|
|
827
1088
|
if (!isVisible) {
|
|
828
1089
|
return null;
|
|
@@ -841,14 +1102,12 @@ function Field({
|
|
|
841
1102
|
);
|
|
842
1103
|
const finalProps = core.mergeFieldProps({
|
|
843
1104
|
providerDefaultFieldProps: providerConfig.defaultFieldProps,
|
|
844
|
-
providerSelectDefaultFieldProps:
|
|
845
|
-
// Evaluated at provider level if needed
|
|
1105
|
+
providerSelectDefaultFieldProps: providerSelectProps,
|
|
846
1106
|
formDefaultFieldProps: formConfig.defaultFieldProps,
|
|
847
|
-
formSelectDefaultFieldProps:
|
|
848
|
-
// Evaluated at form level if needed
|
|
1107
|
+
formSelectDefaultFieldProps: formSelectProps,
|
|
849
1108
|
inputProps: inputConfig.props,
|
|
850
1109
|
fieldConfigProps: fieldConfig.props,
|
|
851
|
-
selectProps:
|
|
1110
|
+
selectProps: fieldSelectProps,
|
|
852
1111
|
componentProps: restProps,
|
|
853
1112
|
coreProps: {
|
|
854
1113
|
name,
|
|
@@ -1025,6 +1284,11 @@ function useFormState(options) {
|
|
|
1025
1284
|
return result;
|
|
1026
1285
|
}
|
|
1027
1286
|
|
|
1287
|
+
// src/overlays.ts
|
|
1288
|
+
function defineInputs(inputs) {
|
|
1289
|
+
return inputs;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1028
1292
|
exports.ConfigContext = ConfigContext;
|
|
1029
1293
|
exports.Field = Field;
|
|
1030
1294
|
exports.FieldGroup = FieldGroup;
|
|
@@ -1033,6 +1297,7 @@ exports.FormContext = FormContext;
|
|
|
1033
1297
|
exports.FormalityProvider = FormalityProvider;
|
|
1034
1298
|
exports.GroupContext = GroupContext;
|
|
1035
1299
|
exports.UnusedFields = UnusedFields;
|
|
1300
|
+
exports.defineInputs = defineInputs;
|
|
1036
1301
|
exports.makeDeepProxyState = makeDeepProxyState;
|
|
1037
1302
|
exports.makeProxyState = makeProxyState;
|
|
1038
1303
|
exports.useConditions = useConditions;
|