@formality-ui/react 0.2.2 → 0.2.4
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 +143 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +143 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -202,10 +202,12 @@ declare function defineInputs<T extends Record<string, ReactInputConfig>>(inputs
|
|
|
202
202
|
* `<Field>` renders your input component via React Hook Form's `<Controller>`.
|
|
203
203
|
* At runtime Formality merges a `coreProps` bundle onto the component (name,
|
|
204
204
|
* value, onChange, onBlur, and — as a React-special key — `ref`). The three
|
|
205
|
-
* members below are the **injected-props contract**: `formState`
|
|
206
|
-
* templates and render-prop children
|
|
207
|
-
*
|
|
208
|
-
*
|
|
205
|
+
* members below are the **injected-props contract**: `formState` always
|
|
206
|
+
* reaches templates and render-prop children, and reaches plain components
|
|
207
|
+
* that have opted into Formality state via `provideState` /
|
|
208
|
+
* `passSubscriptions`; `state` (subscribed field state) and a top-level
|
|
209
|
+
* `forwardRef` key are delivered at runtime by `<Field>` (see "Runtime
|
|
210
|
+
* delivery" below).
|
|
209
211
|
*
|
|
210
212
|
* **Destructure before forwarding.** Component authors MUST destructure
|
|
211
213
|
* `state`, `formState`, and `forwardRef` OUT of props before spreading the
|
package/dist/index.d.ts
CHANGED
|
@@ -202,10 +202,12 @@ declare function defineInputs<T extends Record<string, ReactInputConfig>>(inputs
|
|
|
202
202
|
* `<Field>` renders your input component via React Hook Form's `<Controller>`.
|
|
203
203
|
* At runtime Formality merges a `coreProps` bundle onto the component (name,
|
|
204
204
|
* value, onChange, onBlur, and — as a React-special key — `ref`). The three
|
|
205
|
-
* members below are the **injected-props contract**: `formState`
|
|
206
|
-
* templates and render-prop children
|
|
207
|
-
*
|
|
208
|
-
*
|
|
205
|
+
* members below are the **injected-props contract**: `formState` always
|
|
206
|
+
* reaches templates and render-prop children, and reaches plain components
|
|
207
|
+
* that have opted into Formality state via `provideState` /
|
|
208
|
+
* `passSubscriptions`; `state` (subscribed field state) and a top-level
|
|
209
|
+
* `forwardRef` key are delivered at runtime by `<Field>` (see "Runtime
|
|
210
|
+
* delivery" below).
|
|
209
211
|
*
|
|
210
212
|
* **Destructure before forwarding.** Component authors MUST destructure
|
|
211
213
|
* `state`, `formState`, and `forwardRef` OUT of props before spreading the
|
package/dist/index.js
CHANGED
|
@@ -430,22 +430,15 @@ function Form({
|
|
|
430
430
|
await handleSubmit(values);
|
|
431
431
|
}, [methods, handleSubmit, waitForFieldValidation]);
|
|
432
432
|
executeAutoSaveRef.current = executeAutoSave;
|
|
433
|
-
const getOrCreateDebounced = useCallback(
|
|
434
|
-
(ms)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// lodash debounce tracks pending internally
|
|
443
|
-
});
|
|
444
|
-
fieldDebouncersRef.current.set(ms, fn);
|
|
445
|
-
return fn;
|
|
446
|
-
},
|
|
447
|
-
[]
|
|
448
|
-
);
|
|
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
|
+
}, []);
|
|
449
442
|
getOrCreateDebouncedRef.current = getOrCreateDebounced;
|
|
450
443
|
useEffect(() => {
|
|
451
444
|
return () => {
|
|
@@ -470,13 +463,9 @@ function Form({
|
|
|
470
463
|
}
|
|
471
464
|
);
|
|
472
465
|
}
|
|
473
|
-
|
|
466
|
+
return wrapDebounced(() => {
|
|
474
467
|
executeAutoSaveRef.current?.();
|
|
475
468
|
}, debounceMs);
|
|
476
|
-
return Object.assign(debouncedFn, {
|
|
477
|
-
pending: () => false
|
|
478
|
-
// lodash debounce handles this internally
|
|
479
|
-
});
|
|
480
469
|
}, [debounceMs]);
|
|
481
470
|
debouncedSubmitRef.current = debouncedSubmit;
|
|
482
471
|
useEffect(() => {
|
|
@@ -485,7 +474,11 @@ function Form({
|
|
|
485
474
|
};
|
|
486
475
|
}, [debouncedSubmit]);
|
|
487
476
|
const submitImmediate = useCallback(() => {
|
|
488
|
-
debouncedSubmitRef.current?.
|
|
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?.();
|
|
489
482
|
}, []);
|
|
490
483
|
const unusedFields = useMemo(() => {
|
|
491
484
|
const allFields = Object.keys(config);
|
|
@@ -559,6 +552,30 @@ function Form({
|
|
|
559
552
|
resolvedTitle
|
|
560
553
|
}) : children }) }) });
|
|
561
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
|
+
}
|
|
562
579
|
function transformValuesForSubmit(values, config, inputs) {
|
|
563
580
|
const result = {};
|
|
564
581
|
for (const [name, value] of Object.entries(values)) {
|
|
@@ -1006,18 +1023,52 @@ function Field({
|
|
|
1006
1023
|
}
|
|
1007
1024
|
}
|
|
1008
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
|
+
});
|
|
1009
1033
|
const isDisabled = useMemo(() => {
|
|
1010
1034
|
if (disabledProp !== void 0) return disabledProp;
|
|
1011
1035
|
if (fieldConfig.disabled !== void 0) return fieldConfig.disabled;
|
|
1012
1036
|
if (conditionResult.hasDisabledCondition)
|
|
1013
1037
|
return conditionResult.disabled ?? false;
|
|
1014
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
|
+
}
|
|
1015
1059
|
return false;
|
|
1016
1060
|
}, [
|
|
1017
1061
|
disabledProp,
|
|
1018
1062
|
fieldConfig.disabled,
|
|
1063
|
+
fieldConfig.props,
|
|
1019
1064
|
conditionResult,
|
|
1020
|
-
groupContext.state.isDisabled
|
|
1065
|
+
groupContext.state.isDisabled,
|
|
1066
|
+
fieldSelectProps,
|
|
1067
|
+
formSelectProps,
|
|
1068
|
+
providerSelectProps,
|
|
1069
|
+
inputConfig.props,
|
|
1070
|
+
formConfig.defaultFieldProps,
|
|
1071
|
+
providerConfig.defaultFieldProps
|
|
1021
1072
|
]);
|
|
1022
1073
|
const isVisible = useMemo(() => {
|
|
1023
1074
|
if (hiddenProp !== void 0) return !hiddenProp;
|
|
@@ -1032,16 +1083,43 @@ function Field({
|
|
|
1032
1083
|
conditionResult,
|
|
1033
1084
|
groupContext.state.isVisible
|
|
1034
1085
|
]);
|
|
1035
|
-
const { providerSelectProps, formSelectProps, fieldSelectProps } = usePropsEvaluation({
|
|
1036
|
-
selectProps: fieldConfig.selectProps,
|
|
1037
|
-
formDefaultFieldProps: formConfig.selectDefaultFieldProps,
|
|
1038
|
-
providerDefaultFieldProps: providerConfig.selectDefaultFieldProps,
|
|
1039
|
-
subscribesTo: fieldConfig.subscribesTo,
|
|
1040
|
-
fieldName: name
|
|
1041
|
-
});
|
|
1042
1086
|
const label = useMemo(() => {
|
|
1043
1087
|
return resolveLabel(name, fieldConfig, fieldSelectProps, restProps);
|
|
1044
1088
|
}, [name, fieldConfig, fieldSelectProps, restProps]);
|
|
1089
|
+
const passSubscriptionsEnabled = fieldConfig.passSubscriptions === true;
|
|
1090
|
+
const provideStateEnabled = fieldConfig.provideState === true;
|
|
1091
|
+
const subscribedWatchNames = useMemo(
|
|
1092
|
+
() => passSubscriptionsEnabled && allSubscriptions.length > 0 ? allSubscriptions : [],
|
|
1093
|
+
[passSubscriptionsEnabled, allSubscriptions]
|
|
1094
|
+
);
|
|
1095
|
+
const subscribedValues = useWatch({
|
|
1096
|
+
control: methods.control,
|
|
1097
|
+
name: subscribedWatchNames.length > 0 ? subscribedWatchNames : []
|
|
1098
|
+
});
|
|
1099
|
+
const subscribedState = useMemo(() => {
|
|
1100
|
+
if (!passSubscriptionsEnabled || subscribedWatchNames.length === 0) {
|
|
1101
|
+
return void 0;
|
|
1102
|
+
}
|
|
1103
|
+
const result = {};
|
|
1104
|
+
const values = Array.isArray(subscribedValues) ? subscribedValues : [subscribedValues];
|
|
1105
|
+
subscribedWatchNames.forEach((fieldName, i) => {
|
|
1106
|
+
const fs = methods.getFieldState(fieldName);
|
|
1107
|
+
result[fieldName] = makeProxyState({
|
|
1108
|
+
value: values[i],
|
|
1109
|
+
isTouched: fs.isTouched,
|
|
1110
|
+
isDirty: fs.isDirty,
|
|
1111
|
+
isValidating: fs.isValidating,
|
|
1112
|
+
error: fs.error,
|
|
1113
|
+
invalid: fs.invalid
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
1116
|
+
return result;
|
|
1117
|
+
}, [
|
|
1118
|
+
passSubscriptionsEnabled,
|
|
1119
|
+
subscribedWatchNames,
|
|
1120
|
+
subscribedValues,
|
|
1121
|
+
methods
|
|
1122
|
+
]);
|
|
1045
1123
|
const validationRules = useMemo(() => {
|
|
1046
1124
|
return {
|
|
1047
1125
|
...fieldConfig.rules,
|
|
@@ -1119,6 +1197,24 @@ function Field({
|
|
|
1119
1197
|
inputConfig.formatter,
|
|
1120
1198
|
providerConfig.formatters
|
|
1121
1199
|
);
|
|
1200
|
+
const stateInjection = {};
|
|
1201
|
+
if (provideStateEnabled) {
|
|
1202
|
+
stateInjection[providerConfig.defaultSubscriptionPropName] = makeProxyState({
|
|
1203
|
+
value: field.value,
|
|
1204
|
+
isTouched: fieldState.isTouched,
|
|
1205
|
+
isDirty: fieldState.isDirty,
|
|
1206
|
+
isValidating: fieldState.isValidating,
|
|
1207
|
+
error: fieldState.error,
|
|
1208
|
+
invalid: fieldState.invalid
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
if (passSubscriptionsEnabled && subscribedState) {
|
|
1212
|
+
const subsPropName = fieldConfig.passSubscriptionsAs ?? providerConfig.defaultSubscriptionPropName;
|
|
1213
|
+
stateInjection[subsPropName] = subscribedState;
|
|
1214
|
+
}
|
|
1215
|
+
if (provideStateEnabled || passSubscriptionsEnabled) {
|
|
1216
|
+
stateInjection.formState = formState;
|
|
1217
|
+
}
|
|
1122
1218
|
const finalProps = mergeFieldProps({
|
|
1123
1219
|
providerDefaultFieldProps: providerConfig.defaultFieldProps,
|
|
1124
1220
|
providerSelectDefaultFieldProps: providerSelectProps,
|
|
@@ -1136,7 +1232,8 @@ function Field({
|
|
|
1136
1232
|
[inputConfig.inputFieldProp ?? "value"]: formattedValue,
|
|
1137
1233
|
onChange: handleChange(field.onChange),
|
|
1138
1234
|
onBlur: field.onBlur,
|
|
1139
|
-
forwardRef: field.ref
|
|
1235
|
+
forwardRef: field.ref,
|
|
1236
|
+
...stateInjection
|
|
1140
1237
|
}
|
|
1141
1238
|
});
|
|
1142
1239
|
const Component = inputConfig.component;
|
|
@@ -1155,10 +1252,22 @@ function Field({
|
|
|
1155
1252
|
}
|
|
1156
1253
|
);
|
|
1157
1254
|
} else if (isHostComponent) {
|
|
1158
|
-
const
|
|
1255
|
+
const subsPropName = fieldConfig.passSubscriptionsAs ?? providerConfig.defaultSubscriptionPropName;
|
|
1256
|
+
const strippedHostProps = {};
|
|
1257
|
+
const nonDomKeys = /* @__PURE__ */ new Set([
|
|
1258
|
+
"forwardRef",
|
|
1259
|
+
"formState",
|
|
1260
|
+
"state",
|
|
1261
|
+
subsPropName
|
|
1262
|
+
]);
|
|
1263
|
+
for (const [key, value] of Object.entries(finalProps)) {
|
|
1264
|
+
if (!nonDomKeys.has(key)) {
|
|
1265
|
+
strippedHostProps[key] = value;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1159
1268
|
renderedField = createElement(inputConfig.component, {
|
|
1160
|
-
...
|
|
1161
|
-
ref:
|
|
1269
|
+
...strippedHostProps,
|
|
1270
|
+
ref: finalProps.forwardRef
|
|
1162
1271
|
});
|
|
1163
1272
|
} else {
|
|
1164
1273
|
renderedField = /* @__PURE__ */ jsx(Component, { ...finalProps });
|