@formality-ui/react 0.2.1 → 0.2.3
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 +90 -1
- package/dist/index.cjs +99 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +99 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -581,7 +581,15 @@ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
|
|
|
581
581
|
record?: Partial<TFieldValues>;
|
|
582
582
|
/** Enable auto-save on field changes */
|
|
583
583
|
autoSave?: boolean;
|
|
584
|
-
/**
|
|
584
|
+
/**
|
|
585
|
+
* Form-level auto-save debounce, used as the fallback for any field whose
|
|
586
|
+
* `InputConfig.debounce` is unset.
|
|
587
|
+
* - `false` — submit immediately (no debounce timer).
|
|
588
|
+
* - `number` — delay auto-save by this many milliseconds (default: 1000).
|
|
589
|
+
*
|
|
590
|
+
* A field can override its own cadence via `InputConfig.debounce`
|
|
591
|
+
* (`false` for immediate, or a number for a per-field delay).
|
|
592
|
+
*/
|
|
585
593
|
debounce?: number | false;
|
|
586
594
|
/** Form-level validation */
|
|
587
595
|
validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -581,7 +581,15 @@ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
|
|
|
581
581
|
record?: Partial<TFieldValues>;
|
|
582
582
|
/** Enable auto-save on field changes */
|
|
583
583
|
autoSave?: boolean;
|
|
584
|
-
/**
|
|
584
|
+
/**
|
|
585
|
+
* Form-level auto-save debounce, used as the fallback for any field whose
|
|
586
|
+
* `InputConfig.debounce` is unset.
|
|
587
|
+
* - `false` — submit immediately (no debounce timer).
|
|
588
|
+
* - `number` — delay auto-save by this many milliseconds (default: 1000).
|
|
589
|
+
*
|
|
590
|
+
* A field can override its own cadence via `InputConfig.debounce`
|
|
591
|
+
* (`false` for immediate, or a number for a per-field delay).
|
|
592
|
+
*/
|
|
585
593
|
debounce?: number | false;
|
|
586
594
|
/** Form-level validation */
|
|
587
595
|
validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
|
package/dist/index.js
CHANGED
|
@@ -179,6 +179,8 @@ function Form({
|
|
|
179
179
|
const pendingChangedFields = useRef(/* @__PURE__ */ new Set());
|
|
180
180
|
const pendingAffectedFields = useRef(/* @__PURE__ */ new Set());
|
|
181
181
|
const executionVersionRef = useRef(0);
|
|
182
|
+
const fieldDebouncersRef = useRef(/* @__PURE__ */ new Map());
|
|
183
|
+
const getOrCreateDebouncedRef = useRef();
|
|
182
184
|
const registerField = useCallback((name) => {
|
|
183
185
|
fieldRegistry.current.add(name);
|
|
184
186
|
setRegisteredFields(new Set(fieldRegistry.current));
|
|
@@ -279,8 +281,11 @@ function Form({
|
|
|
279
281
|
for (const field of affected) {
|
|
280
282
|
pendingAffectedFields.current.add(field);
|
|
281
283
|
}
|
|
282
|
-
|
|
284
|
+
const fieldDebounce = inputConfig?.debounce;
|
|
285
|
+
if (fieldDebounce === false) {
|
|
283
286
|
executeAutoSaveRef.current?.();
|
|
287
|
+
} else if (typeof fieldDebounce === "number") {
|
|
288
|
+
getOrCreateDebouncedRef.current?.(fieldDebounce)();
|
|
284
289
|
} else {
|
|
285
290
|
debouncedSubmitRef.current?.();
|
|
286
291
|
}
|
|
@@ -421,53 +426,60 @@ function Form({
|
|
|
421
426
|
return;
|
|
422
427
|
}
|
|
423
428
|
}
|
|
424
|
-
const formState = methods.formState;
|
|
425
|
-
if (Object.keys(formState.errors).length > 0) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
429
|
const values = methods.getValues();
|
|
429
430
|
await handleSubmit(values);
|
|
430
431
|
}, [methods, handleSubmit, waitForFieldValidation]);
|
|
431
432
|
executeAutoSaveRef.current = executeAutoSave;
|
|
433
|
+
const getOrCreateDebounced = useCallback((ms) => {
|
|
434
|
+
const cached = fieldDebouncersRef.current.get(ms);
|
|
435
|
+
if (cached) return cached;
|
|
436
|
+
const fn = wrapDebounced(() => {
|
|
437
|
+
executeAutoSaveRef.current?.();
|
|
438
|
+
}, ms);
|
|
439
|
+
fieldDebouncersRef.current.set(ms, fn);
|
|
440
|
+
return fn;
|
|
441
|
+
}, []);
|
|
442
|
+
getOrCreateDebouncedRef.current = getOrCreateDebounced;
|
|
432
443
|
useEffect(() => {
|
|
444
|
+
return () => {
|
|
445
|
+
fieldDebouncersRef.current.forEach((fn) => fn.cancel());
|
|
446
|
+
fieldDebouncersRef.current.clear();
|
|
447
|
+
};
|
|
448
|
+
}, [getOrCreateDebounced]);
|
|
449
|
+
const debouncedSubmit = useMemo(() => {
|
|
433
450
|
if (debounceMs === false) {
|
|
434
|
-
|
|
451
|
+
return Object.assign(
|
|
435
452
|
() => {
|
|
436
|
-
|
|
453
|
+
executeAutoSaveRef.current?.();
|
|
437
454
|
},
|
|
438
455
|
{
|
|
439
456
|
cancel: () => {
|
|
440
457
|
},
|
|
441
458
|
// No-op for immediate function
|
|
442
|
-
flush: () =>
|
|
459
|
+
flush: () => executeAutoSaveRef.current?.(),
|
|
443
460
|
// Execute immediately on flush
|
|
444
461
|
pending: () => false
|
|
445
462
|
// Never pending when immediate
|
|
446
463
|
}
|
|
447
464
|
);
|
|
448
|
-
debouncedSubmitRef.current = immediateFn;
|
|
449
|
-
return () => {
|
|
450
|
-
};
|
|
451
465
|
}
|
|
452
|
-
|
|
453
|
-
|
|
466
|
+
return wrapDebounced(() => {
|
|
467
|
+
executeAutoSaveRef.current?.();
|
|
454
468
|
}, debounceMs);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
});
|
|
459
|
-
debouncedSubmitRef.current = fn;
|
|
469
|
+
}, [debounceMs]);
|
|
470
|
+
debouncedSubmitRef.current = debouncedSubmit;
|
|
471
|
+
useEffect(() => {
|
|
460
472
|
return () => {
|
|
461
|
-
|
|
473
|
+
debouncedSubmit.cancel();
|
|
462
474
|
};
|
|
463
|
-
}, [
|
|
475
|
+
}, [debouncedSubmit]);
|
|
464
476
|
const submitImmediate = useCallback(() => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}, [
|
|
477
|
+
const anyPending = debouncedSubmitRef.current?.pending() === true || [...fieldDebouncersRef.current.values()].some((fn) => fn.pending());
|
|
478
|
+
if (!anyPending) return;
|
|
479
|
+
debouncedSubmitRef.current?.cancel();
|
|
480
|
+
fieldDebouncersRef.current.forEach((fn) => fn.cancel());
|
|
481
|
+
executeAutoSaveRef.current?.();
|
|
482
|
+
}, []);
|
|
471
483
|
const unusedFields = useMemo(() => {
|
|
472
484
|
const allFields = Object.keys(config);
|
|
473
485
|
return allFields.filter((name) => !registeredFields.has(name));
|
|
@@ -506,7 +518,7 @@ function Form({
|
|
|
506
518
|
setFieldValidating,
|
|
507
519
|
getFormState,
|
|
508
520
|
onSubmit,
|
|
509
|
-
debouncedSubmit
|
|
521
|
+
debouncedSubmit,
|
|
510
522
|
submitImmediate,
|
|
511
523
|
unusedFields,
|
|
512
524
|
methods
|
|
@@ -525,6 +537,7 @@ function Form({
|
|
|
525
537
|
setFieldValidating,
|
|
526
538
|
getFormState,
|
|
527
539
|
onSubmit,
|
|
540
|
+
debouncedSubmit,
|
|
528
541
|
submitImmediate,
|
|
529
542
|
unusedFields,
|
|
530
543
|
methods
|
|
@@ -539,6 +552,30 @@ function Form({
|
|
|
539
552
|
resolvedTitle
|
|
540
553
|
}) : children }) }) });
|
|
541
554
|
}
|
|
555
|
+
function wrapDebounced(callback, ms) {
|
|
556
|
+
let isPending = false;
|
|
557
|
+
const debounced = debounce(() => {
|
|
558
|
+
isPending = false;
|
|
559
|
+
callback();
|
|
560
|
+
}, ms);
|
|
561
|
+
return Object.assign(
|
|
562
|
+
() => {
|
|
563
|
+
isPending = true;
|
|
564
|
+
debounced();
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
cancel: () => {
|
|
568
|
+
isPending = false;
|
|
569
|
+
debounced.cancel();
|
|
570
|
+
},
|
|
571
|
+
flush: () => {
|
|
572
|
+
isPending = false;
|
|
573
|
+
debounced.flush();
|
|
574
|
+
},
|
|
575
|
+
pending: () => isPending
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
}
|
|
542
579
|
function transformValuesForSubmit(values, config, inputs) {
|
|
543
580
|
const result = {};
|
|
544
581
|
for (const [name, value] of Object.entries(values)) {
|
|
@@ -986,18 +1023,52 @@ function Field({
|
|
|
986
1023
|
}
|
|
987
1024
|
}
|
|
988
1025
|
}, [effectiveSetValue.hasCondition, effectiveSetValue.value, name]);
|
|
1026
|
+
const { providerSelectProps, formSelectProps, fieldSelectProps } = usePropsEvaluation({
|
|
1027
|
+
selectProps: fieldConfig.selectProps,
|
|
1028
|
+
formDefaultFieldProps: formConfig.selectDefaultFieldProps,
|
|
1029
|
+
providerDefaultFieldProps: providerConfig.selectDefaultFieldProps,
|
|
1030
|
+
subscribesTo: fieldConfig.subscribesTo,
|
|
1031
|
+
fieldName: name
|
|
1032
|
+
});
|
|
989
1033
|
const isDisabled = useMemo(() => {
|
|
990
1034
|
if (disabledProp !== void 0) return disabledProp;
|
|
991
1035
|
if (fieldConfig.disabled !== void 0) return fieldConfig.disabled;
|
|
992
1036
|
if (conditionResult.hasDisabledCondition)
|
|
993
1037
|
return conditionResult.disabled ?? false;
|
|
994
1038
|
if (groupContext.state.isDisabled) return true;
|
|
1039
|
+
const propsLayers = [
|
|
1040
|
+
fieldSelectProps,
|
|
1041
|
+
// layer 2: field-level selectProps
|
|
1042
|
+
formSelectProps,
|
|
1043
|
+
// layer 5: form-level selectDefaultFieldProps
|
|
1044
|
+
providerSelectProps,
|
|
1045
|
+
// layer 7: provider-level selectDefaultFieldProps
|
|
1046
|
+
inputConfig.props,
|
|
1047
|
+
// layer 4: input config props
|
|
1048
|
+
fieldConfig.props,
|
|
1049
|
+
// layer 3: field config props
|
|
1050
|
+
formConfig.defaultFieldProps,
|
|
1051
|
+
// layer 6: form-level defaultFieldProps
|
|
1052
|
+
providerConfig.defaultFieldProps
|
|
1053
|
+
// layer 8: provider-level defaultFieldProps
|
|
1054
|
+
];
|
|
1055
|
+
for (const layer of propsLayers) {
|
|
1056
|
+
const layerDisabled = layer?.disabled;
|
|
1057
|
+
if (layerDisabled !== void 0) return Boolean(layerDisabled);
|
|
1058
|
+
}
|
|
995
1059
|
return false;
|
|
996
1060
|
}, [
|
|
997
1061
|
disabledProp,
|
|
998
1062
|
fieldConfig.disabled,
|
|
1063
|
+
fieldConfig.props,
|
|
999
1064
|
conditionResult,
|
|
1000
|
-
groupContext.state.isDisabled
|
|
1065
|
+
groupContext.state.isDisabled,
|
|
1066
|
+
fieldSelectProps,
|
|
1067
|
+
formSelectProps,
|
|
1068
|
+
providerSelectProps,
|
|
1069
|
+
inputConfig.props,
|
|
1070
|
+
formConfig.defaultFieldProps,
|
|
1071
|
+
providerConfig.defaultFieldProps
|
|
1001
1072
|
]);
|
|
1002
1073
|
const isVisible = useMemo(() => {
|
|
1003
1074
|
if (hiddenProp !== void 0) return !hiddenProp;
|
|
@@ -1012,13 +1083,6 @@ function Field({
|
|
|
1012
1083
|
conditionResult,
|
|
1013
1084
|
groupContext.state.isVisible
|
|
1014
1085
|
]);
|
|
1015
|
-
const { providerSelectProps, formSelectProps, fieldSelectProps } = usePropsEvaluation({
|
|
1016
|
-
selectProps: fieldConfig.selectProps,
|
|
1017
|
-
formDefaultFieldProps: formConfig.selectDefaultFieldProps,
|
|
1018
|
-
providerDefaultFieldProps: providerConfig.selectDefaultFieldProps,
|
|
1019
|
-
subscribesTo: fieldConfig.subscribesTo,
|
|
1020
|
-
fieldName: name
|
|
1021
|
-
});
|
|
1022
1086
|
const label = useMemo(() => {
|
|
1023
1087
|
return resolveLabel(name, fieldConfig, fieldSelectProps, restProps);
|
|
1024
1088
|
}, [name, fieldConfig, fieldSelectProps, restProps]);
|