Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
import { isDefined } from "../../../utils/utils";
import { FormContext } from "./FormContext";
import * as Validators from "./Validation/Validators";
 
export const FormField = forwardRef(
  (
    {
      label,
      name,
      children,
      required,
      validate,
      skip,
      allowEmpty,
      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 formField = field.current;
 
        if (!(formField instanceof HTMLElement)) console.log({ formField, value });
 
        if (setValue instanceof Function) {
          setValue(value);
        } else if (formField.type === "checkbox" || formField.type === "radio") {
          formField.checked = value ?? formField.checked;
        } else if (value === null) {
          formField.value = "";
        } else {
          formField.value = value;
        }
 
        const evt = document.createEvent("HTMLEvents");
 
        evt.initEvent("change", true, false);
        formField.dispatchEvent(evt);
      },
      [field],
    );
 
    useEffect(() => {
      const isProtected = skipAutofill && !allowEmpty && field.current.type === "password";
 
      context?.registerField({
        label,
        name,
        validation,
        skip,
        allowEmpty,
        skipAutofill,
        isProtected,
        field: field.current,
        setValue: setValueCallback,
      });
      return () => context?.unregisterField(name);
    }, [field, setValueCallback]);
 
    return children({
      ref: field,
      dependency: dependencyField,
      context,
    });
  },
);