import { forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
|
import { isDefined } from "../../utils/helpers";
|
import { FormContext } from "./FormContext";
|
import * as Validators from "./Validation/Validators";
|
|
export const FormField = forwardRef(
|
(
|
{
|
label,
|
name,
|
children,
|
required,
|
validate,
|
skip,
|
allowEmpty,
|
protectedValue,
|
skipAutofill,
|
setValue,
|
dependency,
|
validators,
|
...props
|
},
|
ref,
|
) => {
|
/**@type {Form} */
|
const context = useContext(FormContext);
|
const [dependencyField, setDependencyField] = useState(null);
|
|
const field = ref ?? useRef();
|
|
const validation = [...(validate ?? [])];
|
|
validators?.forEach?.((validator) => {
|
const [name, value] = validator.split(/:(.+)/).slice(0, 2);
|
const validatorFunc = Validators[name];
|
|
if (isDefined(validatorFunc)) {
|
if (isDefined(value)) {
|
validation.push(validatorFunc(value));
|
} else {
|
validation.push(validatorFunc);
|
}
|
}
|
});
|
|
if (required) validation.push(Validators.required);
|
|
useEffect(() => {
|
if (!context || !dependency) return;
|
|
let field = null;
|
const dep = context.getFieldContext(dependency);
|
|
const handler = () => {
|
props.onDependencyChanged?.(dep.field);
|
};
|
|
if (dep) {
|
dep.field.addEventListener("change", handler);
|
field = dep.field;
|
} else {
|
console.warn(`Dependency field not found ${dependency}`);
|
}
|
|
setDependencyField(field);
|
return () => dep.field.removeEventListener("change", handler);
|
}, [context, field, dependency]);
|
|
const setValueCallback = useCallback(
|
(value) => {
|
if (!field || !field.current) return;
|
|
/**@type {HTMLInputElement|HTMLTextAreaElement} */
|
const input = field.current;
|
|
if (setValue instanceof Function) {
|
setValue(value);
|
} else if (input.type === "checkbox" || input.type === "radio") {
|
input.checked = value ?? input.checked;
|
} else if (value === null) {
|
input.value = "";
|
} else {
|
input.value = value;
|
}
|
|
const evt = document.createEvent("HTMLEvents");
|
|
evt.initEvent("change", false, true);
|
input.dispatchEvent(evt);
|
},
|
[field],
|
);
|
|
useEffect(() => {
|
const isProtected = skipAutofill && (!allowEmpty || protectedValue) && field.current.type === "password";
|
|
context?.registerField({
|
label,
|
name,
|
validation,
|
skip,
|
allowEmpty,
|
skipAutofill,
|
isProtected,
|
protectedValue,
|
field: field.current,
|
setValue: setValueCallback,
|
});
|
return () => context?.unregisterField(name);
|
}, [field, setValueCallback]);
|
|
return children(field, dependencyField, context);
|
},
|
);
|