@firecms/formex 3.0.0-3.0.0-beta.4.pre.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 FireCMS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # Formex - React Form Library
2
+
3
+ Formex is a lightweight, flexible library designed to simplify form handling within React applications. By leveraging React's powerful context and hooks features, Formex allows for efficient form state management with minimal boilerplate code.
4
+
5
+ ## Features
6
+
7
+ - Lightweight and easy to integrate
8
+ - Supports custom field components
9
+ - Built-in validation handling
10
+ - Provides both field-level and form-level state management
11
+
12
+ ## Installation
13
+
14
+ To install Formex, you can use either npm or yarn:
15
+
16
+ ```sh
17
+ npm install your-formex-package-name
18
+
19
+ # or if you're using yarn
20
+
21
+ yarn add your-formex-package-name
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ To get started with Formex, you first need to create your form context and form controller using the `useCreateFormex` hook. Then, you can structure your form using the `<Field />` components provided by Formex.
27
+
28
+ ### Step 1: Create your form controller
29
+
30
+ ```jsx
31
+ import React from 'react';
32
+ import { useCreateFormex } from 'formex-library';
33
+
34
+ const MyForm = () => {
35
+ const formController = useCreateFormex({
36
+ initialValues: {
37
+ name: '',
38
+ email: '',
39
+ },
40
+ // Optionally add a validation function
41
+ // validation: values => {
42
+ // const errors = {};
43
+ // if (!values.name) errors.name = 'Name is required';
44
+ // return errors;
45
+ // },
46
+ onSubmit: (values) => {
47
+ console.log('Form Submitted:', values);
48
+ },
49
+ });
50
+
51
+ return (
52
+ <form onSubmit={formController.handleSubmit}>
53
+ {/* Field components go here */}
54
+ </form>
55
+ );
56
+ };
57
+ ```
58
+
59
+ ### Step 2: Use the `<Field />` component
60
+
61
+ ```jsx
62
+ import { Field } from 'formex-library';
63
+
64
+ // Inside your form component
65
+ <Field name="name">
66
+ {({ field }) => (
67
+ <input
68
+ {...field}
69
+ placeholder="Your name"
70
+ />
71
+ )}
72
+ </Field>
73
+
74
+ <Field name="email">
75
+ {({ field }) => (
76
+ <input
77
+ {...field}
78
+ type="email"
79
+ placeholder="Your email"
80
+ />
81
+ )}
82
+ </Field>
83
+
84
+ <button type="submit">Submit</button>
85
+ ```
86
+
87
+ ### Handling Submissions
88
+
89
+ Wrap your form inputs and submit button within a form element and pass the `submitForm` method from your form controller to the form's `onSubmit` event:
90
+
91
+ ```jsx
92
+ <form onSubmit={formController.handleSubmit}>
93
+ {/* Fields and submit button */}
94
+ </form>
95
+ ```
96
+
97
+ ## API Reference
98
+
99
+ ### `useCreateFormex`
100
+
101
+ Hook to create a form controller.
102
+
103
+ **Parameters**
104
+
105
+ - `initialValues`: An object with your form's initial values.
106
+ - `initialErrors` (optional): An object for any initial validation errors.
107
+ - `validation` (optional): A function for validating form data.
108
+ - `validateOnChange` (optional): If `true`, validates fields whenever they change.
109
+ - `onSubmit`: A function that fires when the form is submitted.
110
+
111
+
112
+ ### `<Field />`
113
+
114
+ A component used to render individual form fields.
115
+
116
+ **Props**
117
+
118
+ - `name`: The name of the form field.
119
+ - `as` (optional): The component or HTML tag that should be rendered. Defaults to `"input"`.
120
+ - `children`: A function that returns the field input component. Receives field props as its parameter.
121
+
122
+ **Example**
123
+
124
+ ```jsx
125
+ <Field name="username">
126
+ {({ field }) => <input {...field} />}
127
+ </Field>
128
+ ```
129
+
130
+ ## Customization
131
+
132
+ Formex is designed to be flexible. You can create custom field components, use any validation library, or integrate with UI component libraries.
133
+
134
+ ### Using with UI Libraries
135
+
136
+ ```jsx
137
+ import { Field } from 'formex-library';
138
+ import { TextField } from 'some-ui-library';
139
+
140
+ <Field name="username">
141
+ {({ field }) => (
142
+ <TextField {...field} label="Username" />
143
+ )}
144
+ </Field>
145
+ ```
146
+
147
+ ### Custom Validation
148
+
149
+ Leverage the `validation` function in `useCreateFormex` to integrate any validation logic or library.
150
+
151
+ ```jsx
152
+ const validate = values => {
153
+ const errors = {};
154
+ if (!values.email.includes('@')) {
155
+ errors.email = 'Invalid email';
156
+ }
157
+ return errors;
158
+ };
159
+ ```
160
+
161
+ ## Conclusion
162
+
163
+ Formex provides a simple yet powerful way to manage forms in React applications. It reduces the amount of boilerplate code needed and offers flexibility to work with custom components and validation strategies. Whether you are building simple or complex forms, Formex can help streamline your form management process.
164
+
165
+ For further examples and advanced usage, refer to the Formex documentation or source code.
@@ -0,0 +1,53 @@
1
+ import * as React from "react";
2
+ import { FormexController } from "./types";
3
+ export interface FieldInputProps<Value> {
4
+ /** Value of the field */
5
+ value: Value;
6
+ /** Name of the field */
7
+ name: string;
8
+ /** Multiple select? */
9
+ multiple?: boolean;
10
+ /** Is the field checked? */
11
+ checked?: boolean;
12
+ /** Change event handler */
13
+ onChange: (event: React.SyntheticEvent) => void;
14
+ /** Blur event handler */
15
+ onBlur: (event: React.FocusEvent) => void;
16
+ }
17
+ export interface FormexFieldProps<Value = any, FormValues extends object = any> {
18
+ field: FieldInputProps<Value>;
19
+ form: FormexController<FormValues>;
20
+ }
21
+ export type FieldValidator = (value: any) => string | void | Promise<string | void>;
22
+ export interface FieldConfig<Value, C extends React.ElementType | undefined = undefined> {
23
+ /**
24
+ * Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component.
25
+ */
26
+ as?: C | string | React.ForwardRefExoticComponent<any>;
27
+ /**
28
+ * Children render function <Field name>{props => ...}</Field>)
29
+ */
30
+ children?: ((props: FormexFieldProps<Value>) => React.ReactNode) | React.ReactNode;
31
+ /**
32
+ * Validate a single field value independently
33
+ */
34
+ /**
35
+ * Used for 'select' and related input types.
36
+ */
37
+ multiple?: boolean;
38
+ /**
39
+ * Field name
40
+ */
41
+ name: string;
42
+ /** HTML input type */
43
+ type?: string;
44
+ /** Field value */
45
+ value?: any;
46
+ /** Inner ref */
47
+ innerRef?: (instance: any) => void;
48
+ }
49
+ export type FieldProps<T, C extends React.ElementType | undefined> = {
50
+ as?: C;
51
+ } & (C extends React.ElementType ? (React.ComponentProps<C> & FieldConfig<T, C>) : FieldConfig<T, C>);
52
+ export declare function Field<T, C extends React.ElementType | undefined = undefined>({ validate, name, children, as: is, // `as` is reserved in typescript lol
53
+ className, ...props }: FieldProps<T, C>): any;
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { FormexController } from "./types";
3
+ export declare const useFormex: <T extends object>() => FormexController<T>;
4
+ export declare const Formex: React.Provider<FormexController<any>>;
@@ -0,0 +1,5 @@
1
+ export * from "./Field";
2
+ export * from "./Formex";
3
+ export * from "./types";
4
+ export * from "./utils";
5
+ export * from "./useCreateFormex";
@@ -0,0 +1,172 @@
1
+ import * as x from "react";
2
+ import b, { useContext as Q, useState as h, useEffect as U } from "react";
3
+ import T from "react-fast-compare";
4
+ const D = b.createContext({}), X = () => Q(D), nt = D.Provider, st = (t) => Array.isArray(t) && t.length === 0, M = (t) => typeof t == "function", y = (t) => t !== null && typeof t == "object", Y = (t) => String(Math.floor(Number(t))) === t, ot = (t) => Object.prototype.toString.call(t) === "[object String]", ct = (t) => t !== t, ut = (t) => x.Children.count(t) === 0, it = (t) => y(t) && M(t.then), at = (t) => t && y(t) && y(t.target);
5
+ function lt(t) {
6
+ if (t = t || (typeof document < "u" ? document : void 0), typeof t > "u")
7
+ return null;
8
+ try {
9
+ return t.activeElement || t.body;
10
+ } catch {
11
+ return t.body;
12
+ }
13
+ }
14
+ function F(t, a, n, u = 0) {
15
+ const r = O(a);
16
+ for (; t && u < r.length; )
17
+ t = t[r[u++]];
18
+ return u !== r.length && !t || t === void 0 ? n : t;
19
+ }
20
+ function Z(t, a, n) {
21
+ const u = V(t);
22
+ let r = u, e = 0;
23
+ const i = O(a);
24
+ for (; e < i.length - 1; e++) {
25
+ const c = i[e], f = F(t, i.slice(0, e + 1));
26
+ if (f && (y(f) || Array.isArray(f)))
27
+ r = r[c] = V(f);
28
+ else {
29
+ const d = i[e + 1];
30
+ r = r[c] = Y(d) && Number(d) >= 0 ? [] : {};
31
+ }
32
+ }
33
+ return (e === 0 ? t : r)[i[e]] === n ? t : (n === void 0 ? delete r[i[e]] : r[i[e]] = n, e === 0 && n === void 0 && delete u[i[e]], u);
34
+ }
35
+ function j(t, a, n = /* @__PURE__ */ new WeakMap(), u = {}) {
36
+ for (const r of Object.keys(t)) {
37
+ const e = t[r];
38
+ y(e) ? n.get(e) || (n.set(e, !0), u[r] = Array.isArray(e) ? [] : {}, j(e, a, n, u[r])) : u[r] = a;
39
+ }
40
+ return u;
41
+ }
42
+ function V(t) {
43
+ return Array.isArray(t) ? [...t] : typeof t == "object" && t !== null ? { ...t } : t;
44
+ }
45
+ function O(t) {
46
+ return Array.isArray(t) ? t : t.replace(/\[(\d+)]/g, ".$1").replace(/^\./, "").replace(/\.$/, "").split(".");
47
+ }
48
+ function ft({
49
+ validate: t,
50
+ name: a,
51
+ children: n,
52
+ as: u,
53
+ // `as` is reserved in typescript lol
54
+ // component,
55
+ className: r,
56
+ ...e
57
+ }) {
58
+ const i = X(), c = tt({ name: a, ...e }, i);
59
+ if (M(n))
60
+ return n({ field: c, form: i });
61
+ const f = u || "input";
62
+ if (typeof f == "string") {
63
+ const { innerRef: d, ...p } = e;
64
+ return x.createElement(
65
+ f,
66
+ { ref: d, ...c, ...p, className: r },
67
+ n
68
+ );
69
+ }
70
+ return x.createElement(f, { ...c, ...e, className: r }, n);
71
+ }
72
+ const tt = (t, a) => {
73
+ const n = y(t), u = n ? t.name : t, r = F(a.values, u), e = {
74
+ name: u,
75
+ value: r,
76
+ onChange: a.handleChange,
77
+ onBlur: a.handleBlur
78
+ };
79
+ if (n) {
80
+ const {
81
+ type: i,
82
+ value: c,
83
+ // value is special for checkboxes
84
+ as: f,
85
+ multiple: d
86
+ } = t;
87
+ i === "checkbox" ? c === void 0 ? e.checked = !!r : (e.checked = !!(Array.isArray(r) && ~r.indexOf(c)), e.value = c) : i === "radio" ? (e.checked = r === c, e.value = c) : f === "select" && d && (e.value = e.value || [], e.multiple = !0);
88
+ }
89
+ return e;
90
+ };
91
+ function dt({ initialValues: t, initialErrors: a, validation: n, validateOnChange: u = !1, onSubmit: r, validateOnInitialRender: e = !1 }) {
92
+ const i = b.useRef(t), c = b.useRef(t), [f, d] = h(t), [p, R] = h({}), [k, g] = h(a ?? {}), [_, A] = h(!1), [I, C] = h(0), [$, S] = h(!1), [q, w] = h(!1);
93
+ U(() => {
94
+ e && E();
95
+ }, []);
96
+ const W = (s) => {
97
+ c.current = s, d(s), A(T(i.current, s));
98
+ }, E = async () => {
99
+ w(!0);
100
+ const s = c.current, o = await n?.(s);
101
+ return g(o ?? {}), w(!1), o;
102
+ }, N = (s, o, l) => {
103
+ const m = Z(c.current, s, o);
104
+ c.current = m, d(m), T(F(i.current, s), o) || A(!0), l && E();
105
+ }, z = (s, o) => {
106
+ const l = { ...k };
107
+ o ? l[s] = o : delete l[s], g(l);
108
+ }, v = (s, o, l) => {
109
+ const m = { ...p };
110
+ m[s] = o, R(m), l && E();
111
+ }, G = (s) => {
112
+ const o = s.target, l = o.type === "checkbox" ? o.checked : o.value, m = o.name;
113
+ N(m, l, u), v(m, !0);
114
+ }, H = (s) => {
115
+ const l = s.target.name;
116
+ v(l, !0);
117
+ }, J = async (s) => {
118
+ s?.preventDefault(), s?.stopPropagation(), S(!0), C(I + 1);
119
+ const o = await n?.(c.current);
120
+ o && Object.keys(o).length > 0 ? g(o) : (g({}), await r?.(c.current, B.current)), S(!1);
121
+ }, K = (s) => {
122
+ const {
123
+ submitCount: o,
124
+ values: l,
125
+ errors: m,
126
+ touched: L
127
+ } = s ?? {};
128
+ i.current = l ?? t, c.current = l ?? t, d(l ?? t), g(m ?? {}), R(L ?? {}), A(!1), C(o ?? 0);
129
+ }, P = {
130
+ values: f,
131
+ initialValues: i.current,
132
+ handleChange: G,
133
+ isSubmitting: $,
134
+ setSubmitting: S,
135
+ setValues: W,
136
+ setFieldValue: N,
137
+ errors: k,
138
+ setFieldError: z,
139
+ touched: p,
140
+ setFieldTouched: v,
141
+ dirty: _,
142
+ setDirty: A,
143
+ handleSubmit: J,
144
+ submitCount: I,
145
+ setSubmitCount: C,
146
+ handleBlur: H,
147
+ validate: E,
148
+ isValidating: q,
149
+ resetForm: K
150
+ }, B = b.useRef(P);
151
+ return B.current = P, P;
152
+ }
153
+ export {
154
+ ft as Field,
155
+ nt as Formex,
156
+ lt as getActiveElement,
157
+ F as getIn,
158
+ st as isEmptyArray,
159
+ ut as isEmptyChildren,
160
+ M as isFunction,
161
+ at as isInputEvent,
162
+ Y as isInteger,
163
+ ct as isNaN,
164
+ y as isObject,
165
+ it as isPromise,
166
+ ot as isString,
167
+ Z as setIn,
168
+ j as setNestedObjectValues,
169
+ dt as useCreateFormex,
170
+ X as useFormex
171
+ };
172
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/Formex.tsx","../src/utils.ts","../src/Field.tsx","../src/useCreateFormex.tsx"],"sourcesContent":["import React, { useContext } from \"react\";\nimport { FormexController } from \"./types\";\n\nconst FormexContext = React.createContext<FormexController<any>>({} as any);\n\nexport const useFormex = <T extends object>() => useContext<FormexController<T>>(FormexContext);\n\nexport const Formex = FormexContext.Provider;\n","import * as React from \"react\";\n\n/** @private is the value an empty array? */\nexport const isEmptyArray = (value?: any) =>\n Array.isArray(value) && value.length === 0;\n\n/** @private is the given object a Function? */\nexport const isFunction = (obj: any): obj is Function =>\n typeof obj === \"function\";\n\n/** @private is the given object an Object? */\nexport const isObject = (obj: any): obj is Object =>\n obj !== null && typeof obj === \"object\";\n\n/** @private is the given object an integer? */\nexport const isInteger = (obj: any): boolean =>\n String(Math.floor(Number(obj))) === obj;\n\n/** @private is the given object a string? */\nexport const isString = (obj: any): obj is string =>\n Object.prototype.toString.call(obj) === \"[object String]\";\n\n/** @private is the given object a NaN? */\n// eslint-disable-next-line no-self-compare\nexport const isNaN = (obj: any): boolean => obj !== obj;\n\n/** @private Does a React component have exactly 0 children? */\nexport const isEmptyChildren = (children: any): boolean =>\n React.Children.count(children) === 0;\n\n/** @private is the given object/value a promise? */\nexport const isPromise = (value: any): value is PromiseLike<any> =>\n isObject(value) && isFunction(value.then);\n\n/** @private is the given object/value a type of synthetic event? */\nexport const isInputEvent = (value: any): value is React.SyntheticEvent<any> =>\n value && isObject(value) && isObject(value.target);\n\n/**\n * Same as document.activeElement but wraps in a try-catch block. In IE it is\n * not safe to call document.activeElement if there is nothing focused.\n *\n * The activeElement will be null only if the document or document body is not\n * yet defined.\n *\n * @param {?Document} doc Defaults to current document.\n * @return {Element | null}\n * @see https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/dom/getActiveElement.js\n */\nexport function getActiveElement(doc?: Document): Element | null {\n doc = doc || (typeof document !== \"undefined\" ? document : undefined);\n if (typeof doc === \"undefined\") {\n return null;\n }\n try {\n return doc.activeElement || doc.body;\n } catch (e) {\n return doc.body;\n }\n}\n\n/**\n * Deeply get a value from an object via its path.\n */\nexport function getIn(\n obj: any,\n key: string | string[],\n def?: any,\n p = 0\n) {\n const path = toPath(key);\n while (obj && p < path.length) {\n obj = obj[path[p++]];\n }\n\n // check if path is not in the end\n if (p !== path.length && !obj) {\n return def;\n }\n\n return obj === undefined ? def : obj;\n}\n\nexport function setIn(obj: any, path: string, value: any): any {\n const res: any = clone(obj); // this keeps inheritance when obj is a class\n let resVal: any = res;\n let i = 0;\n const pathArray = toPath(path);\n\n for (; i < pathArray.length - 1; i++) {\n const currentPath: string = pathArray[i];\n const currentObj: any = getIn(obj, pathArray.slice(0, i + 1));\n\n if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {\n resVal = resVal[currentPath] = clone(currentObj);\n } else {\n const nextPath: string = pathArray[i + 1];\n resVal = resVal[currentPath] =\n isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};\n }\n }\n\n // Return original object if new value is the same as current\n if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {\n return obj;\n }\n\n if (value === undefined) {\n delete resVal[pathArray[i]];\n } else {\n resVal[pathArray[i]] = value;\n }\n\n // If the path array has a single element, the loop did not run.\n // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.\n if (i === 0 && value === undefined) {\n delete res[pathArray[i]];\n }\n\n return res;\n}\n\n/**\n * Recursively a set the same value for all keys and arrays nested object, cloning\n * @param object\n * @param value\n * @param visited\n * @param response\n */\nexport function setNestedObjectValues<T>(\n object: any,\n value: any,\n visited: any = new WeakMap(),\n response: any = {}\n): T {\n for (const k of Object.keys(object)) {\n const val = object[k];\n if (isObject(val)) {\n if (!visited.get(val)) {\n visited.set(val, true);\n // In order to keep array values consistent for both dot path and\n // bracket syntax, we need to check if this is an array so that\n // this will output { friends: [true] } and not { friends: { \"0\": true } }\n response[k] = Array.isArray(val) ? [] : {};\n setNestedObjectValues(val, value, visited, response[k]);\n }\n } else {\n response[k] = value;\n }\n }\n\n return response;\n}\n\nfunction clone(value: any) {\n if (Array.isArray(value)) {\n return [...value];\n } else if (typeof value === \"object\" && value !== null) {\n return { ...value };\n } else {\n return value; // This is for primitive types which do not need cloning.\n }\n}\n\nfunction toPath(value: string | string[]) {\n if (Array.isArray(value)) return value; // Already in path array form.\n // Replace brackets with dots, remove leading/trailing dots, then split by dot.\n return value.replace(/\\[(\\d+)]/g, \".$1\").replace(/^\\./, \"\").replace(/\\.$/, \"\").split(\".\");\n}\n","import * as React from \"react\";\nimport { useFormex } from \"./Formex\";\nimport { getIn, isFunction, isObject } from \"./utils\";\nimport { FormexController } from \"./types\";\n\nexport interface FieldInputProps<Value> {\n /** Value of the field */\n value: Value;\n /** Name of the field */\n name: string;\n /** Multiple select? */\n multiple?: boolean;\n /** Is the field checked? */\n checked?: boolean;\n /** Change event handler */\n onChange: (event: React.SyntheticEvent) => void,\n /** Blur event handler */\n onBlur: (event: React.FocusEvent) => void,\n}\n\nexport interface FormexFieldProps<Value = any, FormValues extends object = any> {\n field: FieldInputProps<Value>;\n form: FormexController<FormValues>;\n}\n\nexport type FieldValidator = (\n value: any\n) => string | void | Promise<string | void>;\n\nexport interface FieldConfig<Value, C extends React.ElementType | undefined = undefined> {\n\n /**\n * Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component.\n */\n as?:\n | C\n | string\n | React.ForwardRefExoticComponent<any>;\n\n /**\n * Children render function <Field name>{props => ...}</Field>)\n */\n children?: ((props: FormexFieldProps<Value>) => React.ReactNode) | React.ReactNode;\n\n /**\n * Validate a single field value independently\n */\n // validate?: FieldValidator;\n\n /**\n * Used for 'select' and related input types.\n */\n multiple?: boolean;\n\n /**\n * Field name\n */\n name: string;\n\n /** HTML input type */\n type?: string;\n\n /** Field value */\n value?: any;\n\n /** Inner ref */\n innerRef?: (instance: any) => void;\n\n}\n\nexport type FieldProps<T, C extends React.ElementType | undefined> = {\n as?: C;\n} & (C extends React.ElementType ? (React.ComponentProps<C> & FieldConfig<T, C>) : FieldConfig<T, C>);\n\nexport function Field<T, C extends React.ElementType | undefined = undefined>({\n validate,\n name,\n children,\n as: is, // `as` is reserved in typescript lol\n // component,\n className,\n ...props\n }: FieldProps<T, C>) {\n const formex = useFormex();\n\n const field = getFieldProps({ name, ...props }, formex);\n\n if (isFunction(children)) {\n return children({ field, form: formex });\n }\n\n // if (component) {\n // if (typeof component === \"string\") {\n // const { innerRef, ...rest } = props;\n // return React.createElement(\n // component,\n // { ref: innerRef, ...field, ...rest, className },\n // children\n // );\n // }\n // return React.createElement(\n // component,\n // { field, form: formex, ...props, className },\n // children\n // );\n // }\n\n // default to input here so we can check for both `as` and `children` above\n const asElement = is || \"input\";\n\n if (typeof asElement === \"string\") {\n const { innerRef, ...rest } = props;\n return React.createElement(\n asElement,\n { ref: innerRef, ...field, ...rest, className },\n children\n );\n }\n\n return React.createElement(asElement, { ...field, ...props, className }, children);\n}\n\nconst getFieldProps = (nameOrOptions: string | FieldConfig<any>, formex: FormexController<any>): FieldInputProps<any> => {\n const isAnObject = isObject(nameOrOptions);\n const name = isAnObject\n ? (nameOrOptions as FieldConfig<any>).name\n : nameOrOptions;\n const valueState = getIn(formex.values, name);\n\n const field: FieldInputProps<any> = {\n name,\n value: valueState,\n onChange: formex.handleChange,\n onBlur: formex.handleBlur,\n };\n if (isAnObject) {\n const {\n type,\n value: valueProp, // value is special for checkboxes\n as: is,\n multiple,\n } = nameOrOptions as FieldConfig<any>;\n\n if (type === \"checkbox\") {\n if (valueProp === undefined) {\n field.checked = !!valueState;\n } else {\n field.checked = !!(\n Array.isArray(valueState) && ~valueState.indexOf(valueProp)\n );\n field.value = valueProp;\n }\n } else if (type === \"radio\") {\n field.checked = valueState === valueProp;\n field.value = valueProp;\n } else if (is === \"select\" && multiple) {\n field.value = field.value || [];\n field.multiple = true;\n }\n }\n return field;\n};\n","import React, { FormEvent, useEffect, useState } from \"react\";\nimport { getIn, setIn } from \"./utils\";\nimport equal from \"react-fast-compare\"\n\nimport { FormexController, FormexResetProps } from \"./types\";\n\nexport function useCreateFormex<T extends object>({ initialValues, initialErrors, validation, validateOnChange = false, onSubmit, validateOnInitialRender = false }: {\n initialValues: T,\n initialErrors?: Record<string, string>,\n validateOnChange?: boolean,\n validateOnInitialRender?: boolean,\n validation?: (values: T) => Record<string, string> | Promise<Record<string, string>> | undefined | void,\n onSubmit?: (values: T, controller: FormexController<T>) => void | Promise<void>\n}): FormexController<T> {\n\n const initialValuesRef = React.useRef<T>(initialValues);\n const valuesRef = React.useRef<T>(initialValues);\n\n const [values, setValuesInner] = useState<T>(initialValues);\n const [touchedState, setTouchedState] = useState<Record<string, boolean>>({});\n const [errors, setErrors] = useState<Record<string, string>>(initialErrors ?? {});\n const [dirty, setDirty] = useState(false);\n const [submitCount, setSubmitCount] = useState(0);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isValidating, setIsValidating] = useState(false);\n\n useEffect(() => {\n if (validateOnInitialRender) {\n validate();\n }\n }, []);\n\n const setValues = (newValues: T) => {\n valuesRef.current = newValues;\n setValuesInner(newValues);\n setDirty(equal(initialValuesRef.current, newValues));\n }\n\n const validate = async () => {\n setIsValidating(true);\n const values = valuesRef.current;\n const validationErrors = await validation?.(values);\n setErrors(validationErrors ?? {});\n setIsValidating(false);\n return validationErrors;\n }\n\n const setFieldValue = (key: string, value: any, shouldValidate?: boolean) => {\n const newValues = setIn(valuesRef.current, key, value);\n valuesRef.current = newValues;\n setValuesInner(newValues);\n if (!equal(getIn(initialValuesRef.current, key), value)) {\n setDirty(true);\n }\n if (shouldValidate) {\n validate();\n }\n }\n\n const setFieldError = (key: string, error: string | undefined) => {\n const newErrors = { ...errors };\n if (error) {\n newErrors[key] = error;\n } else {\n delete newErrors[key];\n }\n setErrors(newErrors);\n }\n\n const setFieldTouched = (key: string, touched: boolean, shouldValidate?: boolean | undefined) => {\n const newTouched = { ...touchedState };\n newTouched[key] = touched;\n setTouchedState(newTouched);\n if (shouldValidate) {\n validate();\n }\n }\n\n const handleChange = (event: React.SyntheticEvent) => {\n const target = event.target as HTMLInputElement;\n const value = target.type === \"checkbox\" ? target.checked : target.value;\n const name = target.name;\n setFieldValue(name, value, validateOnChange);\n setFieldTouched(name, true);\n }\n\n const handleBlur = (event: React.FocusEvent) => {\n const target = event.target as HTMLInputElement;\n const name = target.name;\n setFieldTouched(name, true);\n }\n\n const submit = async (e?: FormEvent<HTMLFormElement>) => {\n e?.preventDefault();\n e?.stopPropagation();\n setIsSubmitting(true);\n setSubmitCount(submitCount + 1);\n const validationErrors = await validation?.(valuesRef.current);\n if (validationErrors && Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n } else {\n setErrors({});\n await onSubmit?.(valuesRef.current, controllerRef.current);\n }\n setIsSubmitting(false);\n }\n\n const resetForm = (props?: FormexResetProps<T>) => {\n const {\n submitCount: submitCountProp,\n values: valuesProp,\n errors: errorsProp,\n touched: touchedProp\n } = props ?? {};\n initialValuesRef.current = valuesProp ?? initialValues;\n valuesRef.current = valuesProp ?? initialValues;\n setValuesInner(valuesProp ?? initialValues);\n setErrors(errorsProp ?? {});\n setTouchedState(touchedProp ?? {});\n setDirty(false);\n setSubmitCount(submitCountProp ?? 0);\n }\n\n const controller: FormexController<T> = {\n values,\n initialValues: initialValuesRef.current,\n handleChange,\n isSubmitting,\n setSubmitting: setIsSubmitting,\n setValues,\n setFieldValue,\n errors,\n setFieldError,\n touched: touchedState,\n setFieldTouched,\n dirty,\n setDirty,\n handleSubmit: submit,\n submitCount,\n setSubmitCount,\n handleBlur,\n validate,\n isValidating,\n resetForm\n };\n\n const controllerRef = React.useRef<FormexController<T>>(controller);\n controllerRef.current = controller;\n return controller\n}\n"],"names":["FormexContext","React","useFormex","useContext","Formex","isEmptyArray","value","isFunction","obj","isObject","isInteger","isString","isNaN","isEmptyChildren","children","isPromise","isInputEvent","getActiveElement","doc","getIn","key","def","p","path","toPath","setIn","res","clone","resVal","i","pathArray","currentPath","currentObj","nextPath","setNestedObjectValues","object","visited","response","k","val","Field","validate","name","is","className","props","formex","field","getFieldProps","asElement","innerRef","rest","nameOrOptions","isAnObject","valueState","type","valueProp","multiple","useCreateFormex","initialValues","initialErrors","validation","validateOnChange","onSubmit","validateOnInitialRender","initialValuesRef","valuesRef","values","setValuesInner","useState","touchedState","setTouchedState","errors","setErrors","dirty","setDirty","submitCount","setSubmitCount","isSubmitting","setIsSubmitting","isValidating","setIsValidating","useEffect","setValues","newValues","equal","validationErrors","setFieldValue","shouldValidate","setFieldError","error","newErrors","setFieldTouched","touched","newTouched","handleChange","event","target","handleBlur","submit","e","controllerRef","resetForm","submitCountProp","valuesProp","errorsProp","touchedProp","controller"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(s,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("react"),require("react-fast-compare")):typeof define=="function"&&define.amd?define(["exports","react","react-fast-compare"],f):(s=typeof globalThis<"u"?globalThis:s||self,f(s.Formex={},s.React,s.equal))})(this,function(s,f,k){"use strict";function x(e){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const n in e)if(n!=="default"){const c=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(a,n,c.get?c:{enumerable:!0,get:()=>e[n]})}}return a.default=e,Object.freeze(a)}const F=x(f),N=f.createContext({}),w=()=>f.useContext(N),G=N.Provider,H=e=>Array.isArray(e)&&e.length===0,v=e=>typeof e=="function",h=e=>e!==null&&typeof e=="object",T=e=>String(Math.floor(Number(e)))===e,J=e=>Object.prototype.toString.call(e)==="[object String]",K=e=>e!==e,L=e=>F.Children.count(e)===0,Q=e=>h(e)&&v(e.then),U=e=>e&&h(e)&&h(e.target);function X(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function b(e,a,n,c=0){const r=V(a);for(;e&&c<r.length;)e=e[r[c++]];return c!==r.length&&!e||e===void 0?n:e}function j(e,a,n){const c=M(e);let r=c,t=0;const l=V(a);for(;t<l.length-1;t++){const u=l[t],m=b(e,l.slice(0,t+1));if(m&&(h(m)||Array.isArray(m)))r=r[u]=M(m);else{const y=l[t+1];r=r[u]=T(y)&&Number(y)>=0?[]:{}}}return(t===0?e:r)[l[t]]===n?e:(n===void 0?delete r[l[t]]:r[l[t]]=n,t===0&&n===void 0&&delete c[l[t]],c)}function D(e,a,n=new WeakMap,c={}){for(const r of Object.keys(e)){const t=e[r];h(t)?n.get(t)||(n.set(t,!0),c[r]=Array.isArray(t)?[]:{},D(t,a,n,c[r])):c[r]=a}return c}function M(e){return Array.isArray(e)?[...e]:typeof e=="object"&&e!==null?{...e}:e}function V(e){return Array.isArray(e)?e:e.replace(/\[(\d+)]/g,".$1").replace(/^\./,"").replace(/\.$/,"").split(".")}function Y({validate:e,name:a,children:n,as:c,className:r,...t}){const l=w(),u=Z({name:a,...t},l);if(v(n))return n({field:u,form:l});const m=c||"input";if(typeof m=="string"){const{innerRef:y,...p}=t;return F.createElement(m,{ref:y,...u,...p,className:r},n)}return F.createElement(m,{...u,...t,className:r},n)}const Z=(e,a)=>{const n=h(e),c=n?e.name:e,r=b(a.values,c),t={name:c,value:r,onChange:a.handleChange,onBlur:a.handleBlur};if(n){const{type:l,value:u,as:m,multiple:y}=e;l==="checkbox"?u===void 0?t.checked=!!r:(t.checked=!!(Array.isArray(r)&&~r.indexOf(u)),t.value=u):l==="radio"?(t.checked=r===u,t.value=u):m==="select"&&y&&(t.value=t.value||[],t.multiple=!0)}return t};function R({initialValues:e,initialErrors:a,validation:n,validateOnChange:c=!1,onSubmit:r,validateOnInitialRender:t=!1}){const l=f.useRef(e),u=f.useRef(e),[m,y]=f.useState(e),[p,B]=f.useState({}),[_,S]=f.useState(a??{}),[ee,A]=f.useState(!1),[q,P]=f.useState(0),[te,C]=f.useState(!1),[ne,$]=f.useState(!1);f.useEffect(()=>{t&&E()},[]);const re=o=>{u.current=o,y(o),A(k(l.current,o))},E=async()=>{$(!0);const o=u.current,i=await n?.(o);return S(i??{}),$(!1),i},z=(o,i,d)=>{const g=j(u.current,o,i);u.current=g,y(g),k(b(l.current,o),i)||A(!0),d&&E()},se=(o,i)=>{const d={..._};i?d[o]=i:delete d[o],S(d)},I=(o,i,d)=>{const g={...p};g[o]=i,B(g),d&&E()},ce=o=>{const i=o.target,d=i.type==="checkbox"?i.checked:i.value,g=i.name;z(g,d,c),I(g,!0)},oe=o=>{const d=o.target.name;I(d,!0)},ie=async o=>{o?.preventDefault(),o?.stopPropagation(),C(!0),P(q+1);const i=await n?.(u.current);i&&Object.keys(i).length>0?S(i):(S({}),await r?.(u.current,W.current)),C(!1)},ue=o=>{const{submitCount:i,values:d,errors:g,touched:ae}=o??{};l.current=d??e,u.current=d??e,y(d??e),S(g??{}),B(ae??{}),A(!1),P(i??0)},O={values:m,initialValues:l.current,handleChange:ce,isSubmitting:te,setSubmitting:C,setValues:re,setFieldValue:z,errors:_,setFieldError:se,touched:p,setFieldTouched:I,dirty:ee,setDirty:A,handleSubmit:ie,submitCount:q,setSubmitCount:P,handleBlur:oe,validate:E,isValidating:ne,resetForm:ue},W=f.useRef(O);return W.current=O,O}s.Field=Y,s.Formex=G,s.getActiveElement=X,s.getIn=b,s.isEmptyArray=H,s.isEmptyChildren=L,s.isFunction=v,s.isInputEvent=U,s.isInteger=T,s.isNaN=K,s.isObject=h,s.isPromise=Q,s.isString=J,s.setIn=j,s.setNestedObjectValues=D,s.useCreateFormex=R,s.useFormex=w,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/Formex.tsx","../src/utils.ts","../src/Field.tsx","../src/useCreateFormex.tsx"],"sourcesContent":["import React, { useContext } from \"react\";\nimport { FormexController } from \"./types\";\n\nconst FormexContext = React.createContext<FormexController<any>>({} as any);\n\nexport const useFormex = <T extends object>() => useContext<FormexController<T>>(FormexContext);\n\nexport const Formex = FormexContext.Provider;\n","import * as React from \"react\";\n\n/** @private is the value an empty array? */\nexport const isEmptyArray = (value?: any) =>\n Array.isArray(value) && value.length === 0;\n\n/** @private is the given object a Function? */\nexport const isFunction = (obj: any): obj is Function =>\n typeof obj === \"function\";\n\n/** @private is the given object an Object? */\nexport const isObject = (obj: any): obj is Object =>\n obj !== null && typeof obj === \"object\";\n\n/** @private is the given object an integer? */\nexport const isInteger = (obj: any): boolean =>\n String(Math.floor(Number(obj))) === obj;\n\n/** @private is the given object a string? */\nexport const isString = (obj: any): obj is string =>\n Object.prototype.toString.call(obj) === \"[object String]\";\n\n/** @private is the given object a NaN? */\n// eslint-disable-next-line no-self-compare\nexport const isNaN = (obj: any): boolean => obj !== obj;\n\n/** @private Does a React component have exactly 0 children? */\nexport const isEmptyChildren = (children: any): boolean =>\n React.Children.count(children) === 0;\n\n/** @private is the given object/value a promise? */\nexport const isPromise = (value: any): value is PromiseLike<any> =>\n isObject(value) && isFunction(value.then);\n\n/** @private is the given object/value a type of synthetic event? */\nexport const isInputEvent = (value: any): value is React.SyntheticEvent<any> =>\n value && isObject(value) && isObject(value.target);\n\n/**\n * Same as document.activeElement but wraps in a try-catch block. In IE it is\n * not safe to call document.activeElement if there is nothing focused.\n *\n * The activeElement will be null only if the document or document body is not\n * yet defined.\n *\n * @param {?Document} doc Defaults to current document.\n * @return {Element | null}\n * @see https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/dom/getActiveElement.js\n */\nexport function getActiveElement(doc?: Document): Element | null {\n doc = doc || (typeof document !== \"undefined\" ? document : undefined);\n if (typeof doc === \"undefined\") {\n return null;\n }\n try {\n return doc.activeElement || doc.body;\n } catch (e) {\n return doc.body;\n }\n}\n\n/**\n * Deeply get a value from an object via its path.\n */\nexport function getIn(\n obj: any,\n key: string | string[],\n def?: any,\n p = 0\n) {\n const path = toPath(key);\n while (obj && p < path.length) {\n obj = obj[path[p++]];\n }\n\n // check if path is not in the end\n if (p !== path.length && !obj) {\n return def;\n }\n\n return obj === undefined ? def : obj;\n}\n\nexport function setIn(obj: any, path: string, value: any): any {\n const res: any = clone(obj); // this keeps inheritance when obj is a class\n let resVal: any = res;\n let i = 0;\n const pathArray = toPath(path);\n\n for (; i < pathArray.length - 1; i++) {\n const currentPath: string = pathArray[i];\n const currentObj: any = getIn(obj, pathArray.slice(0, i + 1));\n\n if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {\n resVal = resVal[currentPath] = clone(currentObj);\n } else {\n const nextPath: string = pathArray[i + 1];\n resVal = resVal[currentPath] =\n isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};\n }\n }\n\n // Return original object if new value is the same as current\n if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {\n return obj;\n }\n\n if (value === undefined) {\n delete resVal[pathArray[i]];\n } else {\n resVal[pathArray[i]] = value;\n }\n\n // If the path array has a single element, the loop did not run.\n // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.\n if (i === 0 && value === undefined) {\n delete res[pathArray[i]];\n }\n\n return res;\n}\n\n/**\n * Recursively a set the same value for all keys and arrays nested object, cloning\n * @param object\n * @param value\n * @param visited\n * @param response\n */\nexport function setNestedObjectValues<T>(\n object: any,\n value: any,\n visited: any = new WeakMap(),\n response: any = {}\n): T {\n for (const k of Object.keys(object)) {\n const val = object[k];\n if (isObject(val)) {\n if (!visited.get(val)) {\n visited.set(val, true);\n // In order to keep array values consistent for both dot path and\n // bracket syntax, we need to check if this is an array so that\n // this will output { friends: [true] } and not { friends: { \"0\": true } }\n response[k] = Array.isArray(val) ? [] : {};\n setNestedObjectValues(val, value, visited, response[k]);\n }\n } else {\n response[k] = value;\n }\n }\n\n return response;\n}\n\nfunction clone(value: any) {\n if (Array.isArray(value)) {\n return [...value];\n } else if (typeof value === \"object\" && value !== null) {\n return { ...value };\n } else {\n return value; // This is for primitive types which do not need cloning.\n }\n}\n\nfunction toPath(value: string | string[]) {\n if (Array.isArray(value)) return value; // Already in path array form.\n // Replace brackets with dots, remove leading/trailing dots, then split by dot.\n return value.replace(/\\[(\\d+)]/g, \".$1\").replace(/^\\./, \"\").replace(/\\.$/, \"\").split(\".\");\n}\n","import * as React from \"react\";\nimport { useFormex } from \"./Formex\";\nimport { getIn, isFunction, isObject } from \"./utils\";\nimport { FormexController } from \"./types\";\n\nexport interface FieldInputProps<Value> {\n /** Value of the field */\n value: Value;\n /** Name of the field */\n name: string;\n /** Multiple select? */\n multiple?: boolean;\n /** Is the field checked? */\n checked?: boolean;\n /** Change event handler */\n onChange: (event: React.SyntheticEvent) => void,\n /** Blur event handler */\n onBlur: (event: React.FocusEvent) => void,\n}\n\nexport interface FormexFieldProps<Value = any, FormValues extends object = any> {\n field: FieldInputProps<Value>;\n form: FormexController<FormValues>;\n}\n\nexport type FieldValidator = (\n value: any\n) => string | void | Promise<string | void>;\n\nexport interface FieldConfig<Value, C extends React.ElementType | undefined = undefined> {\n\n /**\n * Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component.\n */\n as?:\n | C\n | string\n | React.ForwardRefExoticComponent<any>;\n\n /**\n * Children render function <Field name>{props => ...}</Field>)\n */\n children?: ((props: FormexFieldProps<Value>) => React.ReactNode) | React.ReactNode;\n\n /**\n * Validate a single field value independently\n */\n // validate?: FieldValidator;\n\n /**\n * Used for 'select' and related input types.\n */\n multiple?: boolean;\n\n /**\n * Field name\n */\n name: string;\n\n /** HTML input type */\n type?: string;\n\n /** Field value */\n value?: any;\n\n /** Inner ref */\n innerRef?: (instance: any) => void;\n\n}\n\nexport type FieldProps<T, C extends React.ElementType | undefined> = {\n as?: C;\n} & (C extends React.ElementType ? (React.ComponentProps<C> & FieldConfig<T, C>) : FieldConfig<T, C>);\n\nexport function Field<T, C extends React.ElementType | undefined = undefined>({\n validate,\n name,\n children,\n as: is, // `as` is reserved in typescript lol\n // component,\n className,\n ...props\n }: FieldProps<T, C>) {\n const formex = useFormex();\n\n const field = getFieldProps({ name, ...props }, formex);\n\n if (isFunction(children)) {\n return children({ field, form: formex });\n }\n\n // if (component) {\n // if (typeof component === \"string\") {\n // const { innerRef, ...rest } = props;\n // return React.createElement(\n // component,\n // { ref: innerRef, ...field, ...rest, className },\n // children\n // );\n // }\n // return React.createElement(\n // component,\n // { field, form: formex, ...props, className },\n // children\n // );\n // }\n\n // default to input here so we can check for both `as` and `children` above\n const asElement = is || \"input\";\n\n if (typeof asElement === \"string\") {\n const { innerRef, ...rest } = props;\n return React.createElement(\n asElement,\n { ref: innerRef, ...field, ...rest, className },\n children\n );\n }\n\n return React.createElement(asElement, { ...field, ...props, className }, children);\n}\n\nconst getFieldProps = (nameOrOptions: string | FieldConfig<any>, formex: FormexController<any>): FieldInputProps<any> => {\n const isAnObject = isObject(nameOrOptions);\n const name = isAnObject\n ? (nameOrOptions as FieldConfig<any>).name\n : nameOrOptions;\n const valueState = getIn(formex.values, name);\n\n const field: FieldInputProps<any> = {\n name,\n value: valueState,\n onChange: formex.handleChange,\n onBlur: formex.handleBlur,\n };\n if (isAnObject) {\n const {\n type,\n value: valueProp, // value is special for checkboxes\n as: is,\n multiple,\n } = nameOrOptions as FieldConfig<any>;\n\n if (type === \"checkbox\") {\n if (valueProp === undefined) {\n field.checked = !!valueState;\n } else {\n field.checked = !!(\n Array.isArray(valueState) && ~valueState.indexOf(valueProp)\n );\n field.value = valueProp;\n }\n } else if (type === \"radio\") {\n field.checked = valueState === valueProp;\n field.value = valueProp;\n } else if (is === \"select\" && multiple) {\n field.value = field.value || [];\n field.multiple = true;\n }\n }\n return field;\n};\n","import React, { FormEvent, useEffect, useState } from \"react\";\nimport { getIn, setIn } from \"./utils\";\nimport equal from \"react-fast-compare\"\n\nimport { FormexController, FormexResetProps } from \"./types\";\n\nexport function useCreateFormex<T extends object>({ initialValues, initialErrors, validation, validateOnChange = false, onSubmit, validateOnInitialRender = false }: {\n initialValues: T,\n initialErrors?: Record<string, string>,\n validateOnChange?: boolean,\n validateOnInitialRender?: boolean,\n validation?: (values: T) => Record<string, string> | Promise<Record<string, string>> | undefined | void,\n onSubmit?: (values: T, controller: FormexController<T>) => void | Promise<void>\n}): FormexController<T> {\n\n const initialValuesRef = React.useRef<T>(initialValues);\n const valuesRef = React.useRef<T>(initialValues);\n\n const [values, setValuesInner] = useState<T>(initialValues);\n const [touchedState, setTouchedState] = useState<Record<string, boolean>>({});\n const [errors, setErrors] = useState<Record<string, string>>(initialErrors ?? {});\n const [dirty, setDirty] = useState(false);\n const [submitCount, setSubmitCount] = useState(0);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isValidating, setIsValidating] = useState(false);\n\n useEffect(() => {\n if (validateOnInitialRender) {\n validate();\n }\n }, []);\n\n const setValues = (newValues: T) => {\n valuesRef.current = newValues;\n setValuesInner(newValues);\n setDirty(equal(initialValuesRef.current, newValues));\n }\n\n const validate = async () => {\n setIsValidating(true);\n const values = valuesRef.current;\n const validationErrors = await validation?.(values);\n setErrors(validationErrors ?? {});\n setIsValidating(false);\n return validationErrors;\n }\n\n const setFieldValue = (key: string, value: any, shouldValidate?: boolean) => {\n const newValues = setIn(valuesRef.current, key, value);\n valuesRef.current = newValues;\n setValuesInner(newValues);\n if (!equal(getIn(initialValuesRef.current, key), value)) {\n setDirty(true);\n }\n if (shouldValidate) {\n validate();\n }\n }\n\n const setFieldError = (key: string, error: string | undefined) => {\n const newErrors = { ...errors };\n if (error) {\n newErrors[key] = error;\n } else {\n delete newErrors[key];\n }\n setErrors(newErrors);\n }\n\n const setFieldTouched = (key: string, touched: boolean, shouldValidate?: boolean | undefined) => {\n const newTouched = { ...touchedState };\n newTouched[key] = touched;\n setTouchedState(newTouched);\n if (shouldValidate) {\n validate();\n }\n }\n\n const handleChange = (event: React.SyntheticEvent) => {\n const target = event.target as HTMLInputElement;\n const value = target.type === \"checkbox\" ? target.checked : target.value;\n const name = target.name;\n setFieldValue(name, value, validateOnChange);\n setFieldTouched(name, true);\n }\n\n const handleBlur = (event: React.FocusEvent) => {\n const target = event.target as HTMLInputElement;\n const name = target.name;\n setFieldTouched(name, true);\n }\n\n const submit = async (e?: FormEvent<HTMLFormElement>) => {\n e?.preventDefault();\n e?.stopPropagation();\n setIsSubmitting(true);\n setSubmitCount(submitCount + 1);\n const validationErrors = await validation?.(valuesRef.current);\n if (validationErrors && Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n } else {\n setErrors({});\n await onSubmit?.(valuesRef.current, controllerRef.current);\n }\n setIsSubmitting(false);\n }\n\n const resetForm = (props?: FormexResetProps<T>) => {\n const {\n submitCount: submitCountProp,\n values: valuesProp,\n errors: errorsProp,\n touched: touchedProp\n } = props ?? {};\n initialValuesRef.current = valuesProp ?? initialValues;\n valuesRef.current = valuesProp ?? initialValues;\n setValuesInner(valuesProp ?? initialValues);\n setErrors(errorsProp ?? {});\n setTouchedState(touchedProp ?? {});\n setDirty(false);\n setSubmitCount(submitCountProp ?? 0);\n }\n\n const controller: FormexController<T> = {\n values,\n initialValues: initialValuesRef.current,\n handleChange,\n isSubmitting,\n setSubmitting: setIsSubmitting,\n setValues,\n setFieldValue,\n errors,\n setFieldError,\n touched: touchedState,\n setFieldTouched,\n dirty,\n setDirty,\n handleSubmit: submit,\n submitCount,\n setSubmitCount,\n handleBlur,\n validate,\n isValidating,\n resetForm\n };\n\n const controllerRef = React.useRef<FormexController<T>>(controller);\n controllerRef.current = controller;\n return controller\n}\n"],"names":["FormexContext","React","useFormex","useContext","Formex","isEmptyArray","value","isFunction","obj","isObject","isInteger","isString","isNaN","isEmptyChildren","children","isPromise","isInputEvent","getActiveElement","doc","getIn","key","def","p","path","toPath","setIn","res","clone","resVal","i","pathArray","currentPath","currentObj","nextPath","setNestedObjectValues","object","visited","response","k","val","Field","validate","name","is","className","props","formex","field","getFieldProps","asElement","innerRef","rest","nameOrOptions","isAnObject","valueState","type","valueProp","multiple","useCreateFormex","initialValues","initialErrors","validation","validateOnChange","onSubmit","validateOnInitialRender","initialValuesRef","valuesRef","values","setValuesInner","useState","touchedState","setTouchedState","errors","setErrors","dirty","setDirty","submitCount","setSubmitCount","isSubmitting","setIsSubmitting","isValidating","setIsValidating","useEffect","setValues","newValues","equal","validationErrors","setFieldValue","shouldValidate","setFieldError","error","newErrors","setFieldTouched","touched","newTouched","handleChange","event","target","handleBlur","submit","e","controllerRef","resetForm","submitCountProp","valuesProp","errorsProp","touchedProp","controller"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import React, { FormEvent } from "react";
2
+ export type FormexController<T extends object> = {
3
+ values: T;
4
+ initialValues: T;
5
+ setValues: (values: T) => void;
6
+ setFieldValue: (key: string, value: any, shouldValidate?: boolean) => void;
7
+ touched: Record<string, boolean>;
8
+ setFieldTouched: (key: string, touched: boolean, shouldValidate?: boolean) => void;
9
+ dirty: boolean;
10
+ setDirty: (dirty: boolean) => void;
11
+ setSubmitCount: (submitCount: number) => void;
12
+ errors: Record<string, string>;
13
+ setFieldError: (key: string, error?: string) => void;
14
+ handleChange: (event: React.SyntheticEvent) => void;
15
+ handleBlur: (event: React.FocusEvent) => void;
16
+ handleSubmit: (event?: FormEvent<HTMLFormElement>) => void;
17
+ validate: () => void;
18
+ resetForm: (props?: FormexResetProps<T>) => void;
19
+ submitCount: number;
20
+ isSubmitting: boolean;
21
+ setSubmitting: (isSubmitting: boolean) => void;
22
+ isValidating: boolean;
23
+ };
24
+ export type FormexResetProps<T extends object> = {
25
+ values?: T;
26
+ submitCount?: number;
27
+ errors?: Record<string, string>;
28
+ touched?: Record<string, boolean>;
29
+ };
@@ -0,0 +1,9 @@
1
+ import { FormexController } from "./types";
2
+ export declare function useCreateFormex<T extends object>({ initialValues, initialErrors, validation, validateOnChange, onSubmit, validateOnInitialRender }: {
3
+ initialValues: T;
4
+ initialErrors?: Record<string, string>;
5
+ validateOnChange?: boolean;
6
+ validateOnInitialRender?: boolean;
7
+ validation?: (values: T) => Record<string, string> | Promise<Record<string, string>> | undefined | void;
8
+ onSubmit?: (values: T, controller: FormexController<T>) => void | Promise<void>;
9
+ }): FormexController<T>;
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ /** @private is the value an empty array? */
3
+ export declare const isEmptyArray: (value?: any) => boolean;
4
+ /** @private is the given object a Function? */
5
+ export declare const isFunction: (obj: any) => obj is Function;
6
+ /** @private is the given object an Object? */
7
+ export declare const isObject: (obj: any) => obj is Object;
8
+ /** @private is the given object an integer? */
9
+ export declare const isInteger: (obj: any) => boolean;
10
+ /** @private is the given object a string? */
11
+ export declare const isString: (obj: any) => obj is string;
12
+ /** @private is the given object a NaN? */
13
+ export declare const isNaN: (obj: any) => boolean;
14
+ /** @private Does a React component have exactly 0 children? */
15
+ export declare const isEmptyChildren: (children: any) => boolean;
16
+ /** @private is the given object/value a promise? */
17
+ export declare const isPromise: (value: any) => value is PromiseLike<any>;
18
+ /** @private is the given object/value a type of synthetic event? */
19
+ export declare const isInputEvent: (value: any) => value is React.SyntheticEvent<any, Event>;
20
+ /**
21
+ * Same as document.activeElement but wraps in a try-catch block. In IE it is
22
+ * not safe to call document.activeElement if there is nothing focused.
23
+ *
24
+ * The activeElement will be null only if the document or document body is not
25
+ * yet defined.
26
+ *
27
+ * @param {?Document} doc Defaults to current document.
28
+ * @return {Element | null}
29
+ * @see https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/dom/getActiveElement.js
30
+ */
31
+ export declare function getActiveElement(doc?: Document): Element | null;
32
+ /**
33
+ * Deeply get a value from an object via its path.
34
+ */
35
+ export declare function getIn(obj: any, key: string | string[], def?: any, p?: number): any;
36
+ export declare function setIn(obj: any, path: string, value: any): any;
37
+ /**
38
+ * Recursively a set the same value for all keys and arrays nested object, cloning
39
+ * @param object
40
+ * @param value
41
+ * @param visited
42
+ * @param response
43
+ */
44
+ export declare function setNestedObjectValues<T>(object: any, value: any, visited?: any, response?: any): T;
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@firecms/formex",
3
+ "type": "module",
4
+ "version": "3.0.0-3.0.0-beta.4.pre.1.0",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "keywords": [
9
+ "firebase",
10
+ "cms",
11
+ "admin",
12
+ "admin panel",
13
+ "firebase panel",
14
+ "firestore",
15
+ "headless",
16
+ "headless cms",
17
+ "content manager"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.es.js",
22
+ "require": "./dist/index.umd.js",
23
+ "types": "./dist/index.d.ts"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "packageManager": "yarn@4.1.0",
28
+ "main": "./dist/index.umd.js",
29
+ "module": "./dist/index.es.js",
30
+ "types": "dist/index.d.ts",
31
+ "source": "src/index.ts",
32
+ "peerDependencies": {
33
+ "firebase": "^10.7.1",
34
+ "react": "^18.2.0",
35
+ "react-dom": "^18.2.0"
36
+ },
37
+ "dependencies": {
38
+ "react-fast-compare": "^3.2.2"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.11.30",
42
+ "@types/react": "^18.2.67",
43
+ "@types/react-dom": "^18.2.22",
44
+ "@typescript-eslint/parser": "^7.3.1",
45
+ "eslint": "^8.57.0",
46
+ "eslint-config-standard": "^17.1.0",
47
+ "eslint-plugin-import": "^2.29.1",
48
+ "eslint-plugin-n": "^16.6.2",
49
+ "eslint-plugin-promise": "^6.1.1",
50
+ "eslint-plugin-react": "^7.34.1",
51
+ "eslint-plugin-react-hooks": "^4.6.0",
52
+ "typescript": "^5.4.2",
53
+ "vite": "^5.1.6"
54
+ },
55
+ "scripts": {
56
+ "dev": "vite",
57
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
58
+ "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
59
+ },
60
+ "files": [
61
+ "dist",
62
+ "src",
63
+ "tailwind.config.js",
64
+ "bin"
65
+ ],
66
+ "eslintConfig": {
67
+ "extends": [
68
+ "react-app",
69
+ "react-app/jest"
70
+ ]
71
+ },
72
+ "gitHead": "bfe875c02132b5763ac0c9d8c8a885c791bb5a24"
73
+ }
package/src/Field.tsx ADDED
@@ -0,0 +1,162 @@
1
+ import * as React from "react";
2
+ import { useFormex } from "./Formex";
3
+ import { getIn, isFunction, isObject } from "./utils";
4
+ import { FormexController } from "./types";
5
+
6
+ export interface FieldInputProps<Value> {
7
+ /** Value of the field */
8
+ value: Value;
9
+ /** Name of the field */
10
+ name: string;
11
+ /** Multiple select? */
12
+ multiple?: boolean;
13
+ /** Is the field checked? */
14
+ checked?: boolean;
15
+ /** Change event handler */
16
+ onChange: (event: React.SyntheticEvent) => void,
17
+ /** Blur event handler */
18
+ onBlur: (event: React.FocusEvent) => void,
19
+ }
20
+
21
+ export interface FormexFieldProps<Value = any, FormValues extends object = any> {
22
+ field: FieldInputProps<Value>;
23
+ form: FormexController<FormValues>;
24
+ }
25
+
26
+ export type FieldValidator = (
27
+ value: any
28
+ ) => string | void | Promise<string | void>;
29
+
30
+ export interface FieldConfig<Value, C extends React.ElementType | undefined = undefined> {
31
+
32
+ /**
33
+ * Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component.
34
+ */
35
+ as?:
36
+ | C
37
+ | string
38
+ | React.ForwardRefExoticComponent<any>;
39
+
40
+ /**
41
+ * Children render function <Field name>{props => ...}</Field>)
42
+ */
43
+ children?: ((props: FormexFieldProps<Value>) => React.ReactNode) | React.ReactNode;
44
+
45
+ /**
46
+ * Validate a single field value independently
47
+ */
48
+ // validate?: FieldValidator;
49
+
50
+ /**
51
+ * Used for 'select' and related input types.
52
+ */
53
+ multiple?: boolean;
54
+
55
+ /**
56
+ * Field name
57
+ */
58
+ name: string;
59
+
60
+ /** HTML input type */
61
+ type?: string;
62
+
63
+ /** Field value */
64
+ value?: any;
65
+
66
+ /** Inner ref */
67
+ innerRef?: (instance: any) => void;
68
+
69
+ }
70
+
71
+ export type FieldProps<T, C extends React.ElementType | undefined> = {
72
+ as?: C;
73
+ } & (C extends React.ElementType ? (React.ComponentProps<C> & FieldConfig<T, C>) : FieldConfig<T, C>);
74
+
75
+ export function Field<T, C extends React.ElementType | undefined = undefined>({
76
+ validate,
77
+ name,
78
+ children,
79
+ as: is, // `as` is reserved in typescript lol
80
+ // component,
81
+ className,
82
+ ...props
83
+ }: FieldProps<T, C>) {
84
+ const formex = useFormex();
85
+
86
+ const field = getFieldProps({ name, ...props }, formex);
87
+
88
+ if (isFunction(children)) {
89
+ return children({ field, form: formex });
90
+ }
91
+
92
+ // if (component) {
93
+ // if (typeof component === "string") {
94
+ // const { innerRef, ...rest } = props;
95
+ // return React.createElement(
96
+ // component,
97
+ // { ref: innerRef, ...field, ...rest, className },
98
+ // children
99
+ // );
100
+ // }
101
+ // return React.createElement(
102
+ // component,
103
+ // { field, form: formex, ...props, className },
104
+ // children
105
+ // );
106
+ // }
107
+
108
+ // default to input here so we can check for both `as` and `children` above
109
+ const asElement = is || "input";
110
+
111
+ if (typeof asElement === "string") {
112
+ const { innerRef, ...rest } = props;
113
+ return React.createElement(
114
+ asElement,
115
+ { ref: innerRef, ...field, ...rest, className },
116
+ children
117
+ );
118
+ }
119
+
120
+ return React.createElement(asElement, { ...field, ...props, className }, children);
121
+ }
122
+
123
+ const getFieldProps = (nameOrOptions: string | FieldConfig<any>, formex: FormexController<any>): FieldInputProps<any> => {
124
+ const isAnObject = isObject(nameOrOptions);
125
+ const name = isAnObject
126
+ ? (nameOrOptions as FieldConfig<any>).name
127
+ : nameOrOptions;
128
+ const valueState = getIn(formex.values, name);
129
+
130
+ const field: FieldInputProps<any> = {
131
+ name,
132
+ value: valueState,
133
+ onChange: formex.handleChange,
134
+ onBlur: formex.handleBlur,
135
+ };
136
+ if (isAnObject) {
137
+ const {
138
+ type,
139
+ value: valueProp, // value is special for checkboxes
140
+ as: is,
141
+ multiple,
142
+ } = nameOrOptions as FieldConfig<any>;
143
+
144
+ if (type === "checkbox") {
145
+ if (valueProp === undefined) {
146
+ field.checked = !!valueState;
147
+ } else {
148
+ field.checked = !!(
149
+ Array.isArray(valueState) && ~valueState.indexOf(valueProp)
150
+ );
151
+ field.value = valueProp;
152
+ }
153
+ } else if (type === "radio") {
154
+ field.checked = valueState === valueProp;
155
+ field.value = valueProp;
156
+ } else if (is === "select" && multiple) {
157
+ field.value = field.value || [];
158
+ field.multiple = true;
159
+ }
160
+ }
161
+ return field;
162
+ };
package/src/Formex.tsx ADDED
@@ -0,0 +1,8 @@
1
+ import React, { useContext } from "react";
2
+ import { FormexController } from "./types";
3
+
4
+ const FormexContext = React.createContext<FormexController<any>>({} as any);
5
+
6
+ export const useFormex = <T extends object>() => useContext<FormexController<T>>(FormexContext);
7
+
8
+ export const Formex = FormexContext.Provider;
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./Field";
2
+ export * from "./Formex";
3
+ export * from "./types";
4
+ export * from "./utils";
5
+ export * from "./useCreateFormex";
package/src/types.ts ADDED
@@ -0,0 +1,31 @@
1
+ import React, { FormEvent } from "react";
2
+
3
+ export type FormexController<T extends object> = {
4
+ values: T;
5
+ initialValues: T;
6
+ setValues: (values: T) => void;
7
+ setFieldValue: (key: string, value: any, shouldValidate?: boolean) => void;
8
+ touched: Record<string, boolean>;
9
+ setFieldTouched: (key: string, touched: boolean, shouldValidate?: boolean) => void;
10
+ dirty: boolean;
11
+ setDirty: (dirty: boolean) => void;
12
+ setSubmitCount: (submitCount: number) => void;
13
+ errors: Record<string, string>;
14
+ setFieldError: (key: string, error?: string) => void;
15
+ handleChange: (event: React.SyntheticEvent) => void,
16
+ handleBlur: (event: React.FocusEvent) => void,
17
+ handleSubmit: (event?: FormEvent<HTMLFormElement>) => void;
18
+ validate: () => void;
19
+ resetForm: (props?: FormexResetProps<T>) => void;
20
+ submitCount: number;
21
+ isSubmitting: boolean;
22
+ setSubmitting: (isSubmitting: boolean) => void;
23
+ isValidating: boolean;
24
+ }
25
+
26
+ export type FormexResetProps<T extends object> = {
27
+ values?: T;
28
+ submitCount?: number;
29
+ errors?: Record<string, string>;
30
+ touched?: Record<string, boolean>;
31
+ };
@@ -0,0 +1,150 @@
1
+ import React, { FormEvent, useEffect, useState } from "react";
2
+ import { getIn, setIn } from "./utils";
3
+ import equal from "react-fast-compare"
4
+
5
+ import { FormexController, FormexResetProps } from "./types";
6
+
7
+ export function useCreateFormex<T extends object>({ initialValues, initialErrors, validation, validateOnChange = false, onSubmit, validateOnInitialRender = false }: {
8
+ initialValues: T,
9
+ initialErrors?: Record<string, string>,
10
+ validateOnChange?: boolean,
11
+ validateOnInitialRender?: boolean,
12
+ validation?: (values: T) => Record<string, string> | Promise<Record<string, string>> | undefined | void,
13
+ onSubmit?: (values: T, controller: FormexController<T>) => void | Promise<void>
14
+ }): FormexController<T> {
15
+
16
+ const initialValuesRef = React.useRef<T>(initialValues);
17
+ const valuesRef = React.useRef<T>(initialValues);
18
+
19
+ const [values, setValuesInner] = useState<T>(initialValues);
20
+ const [touchedState, setTouchedState] = useState<Record<string, boolean>>({});
21
+ const [errors, setErrors] = useState<Record<string, string>>(initialErrors ?? {});
22
+ const [dirty, setDirty] = useState(false);
23
+ const [submitCount, setSubmitCount] = useState(0);
24
+ const [isSubmitting, setIsSubmitting] = useState(false);
25
+ const [isValidating, setIsValidating] = useState(false);
26
+
27
+ useEffect(() => {
28
+ if (validateOnInitialRender) {
29
+ validate();
30
+ }
31
+ }, []);
32
+
33
+ const setValues = (newValues: T) => {
34
+ valuesRef.current = newValues;
35
+ setValuesInner(newValues);
36
+ setDirty(equal(initialValuesRef.current, newValues));
37
+ }
38
+
39
+ const validate = async () => {
40
+ setIsValidating(true);
41
+ const values = valuesRef.current;
42
+ const validationErrors = await validation?.(values);
43
+ setErrors(validationErrors ?? {});
44
+ setIsValidating(false);
45
+ return validationErrors;
46
+ }
47
+
48
+ const setFieldValue = (key: string, value: any, shouldValidate?: boolean) => {
49
+ const newValues = setIn(valuesRef.current, key, value);
50
+ valuesRef.current = newValues;
51
+ setValuesInner(newValues);
52
+ if (!equal(getIn(initialValuesRef.current, key), value)) {
53
+ setDirty(true);
54
+ }
55
+ if (shouldValidate) {
56
+ validate();
57
+ }
58
+ }
59
+
60
+ const setFieldError = (key: string, error: string | undefined) => {
61
+ const newErrors = { ...errors };
62
+ if (error) {
63
+ newErrors[key] = error;
64
+ } else {
65
+ delete newErrors[key];
66
+ }
67
+ setErrors(newErrors);
68
+ }
69
+
70
+ const setFieldTouched = (key: string, touched: boolean, shouldValidate?: boolean | undefined) => {
71
+ const newTouched = { ...touchedState };
72
+ newTouched[key] = touched;
73
+ setTouchedState(newTouched);
74
+ if (shouldValidate) {
75
+ validate();
76
+ }
77
+ }
78
+
79
+ const handleChange = (event: React.SyntheticEvent) => {
80
+ const target = event.target as HTMLInputElement;
81
+ const value = target.type === "checkbox" ? target.checked : target.value;
82
+ const name = target.name;
83
+ setFieldValue(name, value, validateOnChange);
84
+ setFieldTouched(name, true);
85
+ }
86
+
87
+ const handleBlur = (event: React.FocusEvent) => {
88
+ const target = event.target as HTMLInputElement;
89
+ const name = target.name;
90
+ setFieldTouched(name, true);
91
+ }
92
+
93
+ const submit = async (e?: FormEvent<HTMLFormElement>) => {
94
+ e?.preventDefault();
95
+ e?.stopPropagation();
96
+ setIsSubmitting(true);
97
+ setSubmitCount(submitCount + 1);
98
+ const validationErrors = await validation?.(valuesRef.current);
99
+ if (validationErrors && Object.keys(validationErrors).length > 0) {
100
+ setErrors(validationErrors);
101
+ } else {
102
+ setErrors({});
103
+ await onSubmit?.(valuesRef.current, controllerRef.current);
104
+ }
105
+ setIsSubmitting(false);
106
+ }
107
+
108
+ const resetForm = (props?: FormexResetProps<T>) => {
109
+ const {
110
+ submitCount: submitCountProp,
111
+ values: valuesProp,
112
+ errors: errorsProp,
113
+ touched: touchedProp
114
+ } = props ?? {};
115
+ initialValuesRef.current = valuesProp ?? initialValues;
116
+ valuesRef.current = valuesProp ?? initialValues;
117
+ setValuesInner(valuesProp ?? initialValues);
118
+ setErrors(errorsProp ?? {});
119
+ setTouchedState(touchedProp ?? {});
120
+ setDirty(false);
121
+ setSubmitCount(submitCountProp ?? 0);
122
+ }
123
+
124
+ const controller: FormexController<T> = {
125
+ values,
126
+ initialValues: initialValuesRef.current,
127
+ handleChange,
128
+ isSubmitting,
129
+ setSubmitting: setIsSubmitting,
130
+ setValues,
131
+ setFieldValue,
132
+ errors,
133
+ setFieldError,
134
+ touched: touchedState,
135
+ setFieldTouched,
136
+ dirty,
137
+ setDirty,
138
+ handleSubmit: submit,
139
+ submitCount,
140
+ setSubmitCount,
141
+ handleBlur,
142
+ validate,
143
+ isValidating,
144
+ resetForm
145
+ };
146
+
147
+ const controllerRef = React.useRef<FormexController<T>>(controller);
148
+ controllerRef.current = controller;
149
+ return controller
150
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,169 @@
1
+ import * as React from "react";
2
+
3
+ /** @private is the value an empty array? */
4
+ export const isEmptyArray = (value?: any) =>
5
+ Array.isArray(value) && value.length === 0;
6
+
7
+ /** @private is the given object a Function? */
8
+ export const isFunction = (obj: any): obj is Function =>
9
+ typeof obj === "function";
10
+
11
+ /** @private is the given object an Object? */
12
+ export const isObject = (obj: any): obj is Object =>
13
+ obj !== null && typeof obj === "object";
14
+
15
+ /** @private is the given object an integer? */
16
+ export const isInteger = (obj: any): boolean =>
17
+ String(Math.floor(Number(obj))) === obj;
18
+
19
+ /** @private is the given object a string? */
20
+ export const isString = (obj: any): obj is string =>
21
+ Object.prototype.toString.call(obj) === "[object String]";
22
+
23
+ /** @private is the given object a NaN? */
24
+ // eslint-disable-next-line no-self-compare
25
+ export const isNaN = (obj: any): boolean => obj !== obj;
26
+
27
+ /** @private Does a React component have exactly 0 children? */
28
+ export const isEmptyChildren = (children: any): boolean =>
29
+ React.Children.count(children) === 0;
30
+
31
+ /** @private is the given object/value a promise? */
32
+ export const isPromise = (value: any): value is PromiseLike<any> =>
33
+ isObject(value) && isFunction(value.then);
34
+
35
+ /** @private is the given object/value a type of synthetic event? */
36
+ export const isInputEvent = (value: any): value is React.SyntheticEvent<any> =>
37
+ value && isObject(value) && isObject(value.target);
38
+
39
+ /**
40
+ * Same as document.activeElement but wraps in a try-catch block. In IE it is
41
+ * not safe to call document.activeElement if there is nothing focused.
42
+ *
43
+ * The activeElement will be null only if the document or document body is not
44
+ * yet defined.
45
+ *
46
+ * @param {?Document} doc Defaults to current document.
47
+ * @return {Element | null}
48
+ * @see https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/dom/getActiveElement.js
49
+ */
50
+ export function getActiveElement(doc?: Document): Element | null {
51
+ doc = doc || (typeof document !== "undefined" ? document : undefined);
52
+ if (typeof doc === "undefined") {
53
+ return null;
54
+ }
55
+ try {
56
+ return doc.activeElement || doc.body;
57
+ } catch (e) {
58
+ return doc.body;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Deeply get a value from an object via its path.
64
+ */
65
+ export function getIn(
66
+ obj: any,
67
+ key: string | string[],
68
+ def?: any,
69
+ p = 0
70
+ ) {
71
+ const path = toPath(key);
72
+ while (obj && p < path.length) {
73
+ obj = obj[path[p++]];
74
+ }
75
+
76
+ // check if path is not in the end
77
+ if (p !== path.length && !obj) {
78
+ return def;
79
+ }
80
+
81
+ return obj === undefined ? def : obj;
82
+ }
83
+
84
+ export function setIn(obj: any, path: string, value: any): any {
85
+ const res: any = clone(obj); // this keeps inheritance when obj is a class
86
+ let resVal: any = res;
87
+ let i = 0;
88
+ const pathArray = toPath(path);
89
+
90
+ for (; i < pathArray.length - 1; i++) {
91
+ const currentPath: string = pathArray[i];
92
+ const currentObj: any = getIn(obj, pathArray.slice(0, i + 1));
93
+
94
+ if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {
95
+ resVal = resVal[currentPath] = clone(currentObj);
96
+ } else {
97
+ const nextPath: string = pathArray[i + 1];
98
+ resVal = resVal[currentPath] =
99
+ isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};
100
+ }
101
+ }
102
+
103
+ // Return original object if new value is the same as current
104
+ if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {
105
+ return obj;
106
+ }
107
+
108
+ if (value === undefined) {
109
+ delete resVal[pathArray[i]];
110
+ } else {
111
+ resVal[pathArray[i]] = value;
112
+ }
113
+
114
+ // If the path array has a single element, the loop did not run.
115
+ // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.
116
+ if (i === 0 && value === undefined) {
117
+ delete res[pathArray[i]];
118
+ }
119
+
120
+ return res;
121
+ }
122
+
123
+ /**
124
+ * Recursively a set the same value for all keys and arrays nested object, cloning
125
+ * @param object
126
+ * @param value
127
+ * @param visited
128
+ * @param response
129
+ */
130
+ export function setNestedObjectValues<T>(
131
+ object: any,
132
+ value: any,
133
+ visited: any = new WeakMap(),
134
+ response: any = {}
135
+ ): T {
136
+ for (const k of Object.keys(object)) {
137
+ const val = object[k];
138
+ if (isObject(val)) {
139
+ if (!visited.get(val)) {
140
+ visited.set(val, true);
141
+ // In order to keep array values consistent for both dot path and
142
+ // bracket syntax, we need to check if this is an array so that
143
+ // this will output { friends: [true] } and not { friends: { "0": true } }
144
+ response[k] = Array.isArray(val) ? [] : {};
145
+ setNestedObjectValues(val, value, visited, response[k]);
146
+ }
147
+ } else {
148
+ response[k] = value;
149
+ }
150
+ }
151
+
152
+ return response;
153
+ }
154
+
155
+ function clone(value: any) {
156
+ if (Array.isArray(value)) {
157
+ return [...value];
158
+ } else if (typeof value === "object" && value !== null) {
159
+ return { ...value };
160
+ } else {
161
+ return value; // This is for primitive types which do not need cloning.
162
+ }
163
+ }
164
+
165
+ function toPath(value: string | string[]) {
166
+ if (Array.isArray(value)) return value; // Already in path array form.
167
+ // Replace brackets with dots, remove leading/trailing dots, then split by dot.
168
+ return value.replace(/\[(\d+)]/g, ".$1").replace(/^\./, "").replace(/\.$/, "").split(".");
169
+ }