@explita/formly 0.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.
Files changed (61) hide show
  1. package/README.md +261 -0
  2. package/README.old.md +141 -0
  3. package/dist/components/field-error.d.ts +2 -0
  4. package/dist/components/field-error.js +14 -0
  5. package/dist/components/field.d.ts +4 -0
  6. package/dist/components/field.js +120 -0
  7. package/dist/components/form-spy.d.ts +16 -0
  8. package/dist/components/form-spy.js +66 -0
  9. package/dist/components/index.d.ts +4 -0
  10. package/dist/components/index.js +20 -0
  11. package/dist/components/label.d.ts +2 -0
  12. package/dist/components/label.js +46 -0
  13. package/dist/hooks/use-field.d.ts +21 -0
  14. package/dist/hooks/use-field.js +66 -0
  15. package/dist/hooks/use-form-by-id.d.ts +6 -0
  16. package/dist/hooks/use-form-by-id.js +25 -0
  17. package/dist/hooks/use-form-context.d.ts +5 -0
  18. package/dist/hooks/use-form-context.js +17 -0
  19. package/dist/hooks/use-form.d.ts +43 -0
  20. package/dist/hooks/use-form.js +961 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.js +25 -0
  23. package/dist/lib/array-helpers.d.ts +6 -0
  24. package/dist/lib/array-helpers.js +281 -0
  25. package/dist/lib/css.d.ts +1 -0
  26. package/dist/lib/css.js +45 -0
  27. package/dist/lib/debounce.d.ts +13 -0
  28. package/dist/lib/debounce.js +28 -0
  29. package/dist/lib/deep-path.d.ts +4 -0
  30. package/dist/lib/deep-path.js +60 -0
  31. package/dist/lib/drafts-helpter.d.ts +31 -0
  32. package/dist/lib/drafts-helpter.js +67 -0
  33. package/dist/lib/form-registry.d.ts +9 -0
  34. package/dist/lib/form-registry.js +24 -0
  35. package/dist/lib/group-helpers.d.ts +9 -0
  36. package/dist/lib/group-helpers.js +29 -0
  37. package/dist/lib/pub-sub.d.ts +13 -0
  38. package/dist/lib/pub-sub.js +38 -0
  39. package/dist/lib/utils.d.ts +17 -0
  40. package/dist/lib/utils.js +190 -0
  41. package/dist/lib/validation.d.ts +22 -0
  42. package/dist/lib/validation.js +46 -0
  43. package/dist/lib/zod-helpers.d.ts +5 -0
  44. package/dist/lib/zod-helpers.js +63 -0
  45. package/dist/providers/form.d.ts +51 -0
  46. package/dist/providers/form.js +63 -0
  47. package/dist/types/array.d.ts +197 -0
  48. package/dist/types/array.js +2 -0
  49. package/dist/types/field.d.ts +61 -0
  50. package/dist/types/field.js +2 -0
  51. package/dist/types/group.d.ts +16 -0
  52. package/dist/types/group.js +2 -0
  53. package/dist/types/path.d.ts +8 -0
  54. package/dist/types/path.js +2 -0
  55. package/dist/types/pub-sub.d.ts +2 -0
  56. package/dist/types/pub-sub.js +2 -0
  57. package/dist/types/utils.d.ts +310 -0
  58. package/dist/types/utils.js +2 -0
  59. package/dist/utils/index.d.ts +4 -0
  60. package/dist/utils/index.js +14 -0
  61. package/package.json +53 -0
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createChannel = createChannel;
4
+ exports.createFormBus = createFormBus;
5
+ function createChannel() {
6
+ const listeners = new Set();
7
+ return {
8
+ emit(payload) {
9
+ listeners.forEach((cb) => cb(payload));
10
+ },
11
+ subscribe(cb) {
12
+ listeners.add(cb);
13
+ return () => {
14
+ listeners.delete(cb);
15
+ };
16
+ },
17
+ clear() {
18
+ listeners.clear();
19
+ },
20
+ };
21
+ }
22
+ function createFormBus() {
23
+ const channels = new Map();
24
+ // The hybrid callable-object
25
+ const channel = (name) => {
26
+ if (!name)
27
+ throw new Error("Channel name required");
28
+ if (!channels.has(name))
29
+ channels.set(name, createChannel());
30
+ return channels.get(name);
31
+ };
32
+ // Attach helper methods directly to the function
33
+ channel.clearAll = () => {
34
+ channels.forEach((c) => c.clear());
35
+ channels.clear();
36
+ };
37
+ return { channel };
38
+ }
@@ -0,0 +1,17 @@
1
+ import { Path } from "../types/path";
2
+ /**
3
+ * Combines multiple class names into a single string.
4
+ * This function takes an array of class names and returns a single string
5
+ * with all the class names combined, separated by spaces.
6
+ * It filters out any falsey values (null, undefined, etc.) before joining.
7
+ *
8
+ * @param {string[]} classes - An array of class names to combine.
9
+ * @returns {string} A single string with all the class names combined.
10
+ */
11
+ export declare function cn(...classes: (string | false | null | undefined)[]): string;
12
+ export declare function multiPathError<T>(paths: Path<T>[], message: string): Partial<Record<Path<T>, string>>;
13
+ export declare function mapErrors(obj: Record<string, any>, path?: string): Record<string, string>;
14
+ export declare function flattenFormValues<T extends any>(obj: T, prefix?: string): Record<string, any>;
15
+ export declare function nestFormValues<T extends any>(flat: T): T;
16
+ export declare function mergeValues<T extends any>(defaultValues: T, saved: T): T;
17
+ export declare function determineDirtyFields<T extends any>(defaultValues: T, saved: T): T;
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cn = cn;
4
+ exports.multiPathError = multiPathError;
5
+ exports.mapErrors = mapErrors;
6
+ exports.flattenFormValues = flattenFormValues;
7
+ exports.nestFormValues = nestFormValues;
8
+ exports.mergeValues = mergeValues;
9
+ exports.determineDirtyFields = determineDirtyFields;
10
+ /**
11
+ * Combines multiple class names into a single string.
12
+ * This function takes an array of class names and returns a single string
13
+ * with all the class names combined, separated by spaces.
14
+ * It filters out any falsey values (null, undefined, etc.) before joining.
15
+ *
16
+ * @param {string[]} classes - An array of class names to combine.
17
+ * @returns {string} A single string with all the class names combined.
18
+ */
19
+ function cn(...classes) {
20
+ return classes.filter(Boolean).join(" ");
21
+ }
22
+ function multiPathError(paths, message) {
23
+ return paths.reduce((acc, path) => {
24
+ acc[path] = message;
25
+ return acc;
26
+ }, {});
27
+ }
28
+ function mapErrors(obj, path = "") {
29
+ const result = {};
30
+ for (const key in obj) {
31
+ const value = obj[key];
32
+ const currentPath = path ? `${path}.${key}` : key;
33
+ if (Array.isArray(value)) {
34
+ value.forEach((item, i) => {
35
+ // graceful fallback for _index or broken items
36
+ const index = item && typeof item === "object" && "_index" in item
37
+ ? item._index
38
+ : i;
39
+ if (item && typeof item === "object" && !Array.isArray(item)) {
40
+ const keys = Object.keys(item);
41
+ const hasOtherKeys = keys.some((k) => k !== "_index" && k !== "error");
42
+ if (hasOtherKeys) {
43
+ // nested object or keyed structure
44
+ for (const subKey in item) {
45
+ if (subKey === "_index")
46
+ continue;
47
+ result[`${currentPath}.${index}.${subKey}`] = String(item[subKey]);
48
+ }
49
+ }
50
+ else if ("error" in item) {
51
+ // simple indexed error: { _index, error }
52
+ result[`${currentPath}.${index}`] = String(item.error);
53
+ }
54
+ else {
55
+ // empty or malformed object
56
+ result[`${currentPath}.${index}`] = "Invalid value";
57
+ }
58
+ }
59
+ else if (item != null && item !== "") {
60
+ // primitive value (string, number, etc.)
61
+ result[`${currentPath}.${index}`] = String(item);
62
+ }
63
+ else {
64
+ // null, undefined, or empty
65
+ result[`${currentPath}.${index}`] = "Invalid value";
66
+ }
67
+ });
68
+ }
69
+ else if (value && typeof value === "object") {
70
+ // nested object
71
+ Object.assign(result, mapErrors(value, currentPath));
72
+ }
73
+ else {
74
+ // primitive (string, number, etc.)
75
+ result[currentPath] = String(value);
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+ function flattenFormValues(obj, prefix = "") {
81
+ const result = {};
82
+ for (const key in obj) {
83
+ const value = obj[key];
84
+ const path = prefix ? `${prefix}.${key}` : key;
85
+ if (Array.isArray(value)) {
86
+ if (value.length === 0) {
87
+ // always include the array itself
88
+ result[path] = [];
89
+ }
90
+ else {
91
+ value.forEach((item, index) => {
92
+ if (typeof item === "object" && item !== null) {
93
+ Object.assign(result, flattenFormValues(item, `${path}.${index}`));
94
+ }
95
+ else {
96
+ result[`${path}.${index}`] = item;
97
+ }
98
+ });
99
+ }
100
+ }
101
+ else if (typeof value === "object" &&
102
+ !(value instanceof Date) &&
103
+ value !== null) {
104
+ Object.assign(result, flattenFormValues(value, path));
105
+ }
106
+ else {
107
+ result[path] = value;
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ function nestFormValues(flat) {
113
+ const result = {};
114
+ for (const key in flat) {
115
+ const parts = key.split("."); // split by dot
116
+ let current = result;
117
+ for (let i = 0; i < parts.length; i++) {
118
+ const part = parts[i];
119
+ // If part is numeric (array index)
120
+ const arrayIndex = Number(part);
121
+ const isLast = i === parts.length - 1;
122
+ if (!isNaN(arrayIndex) && Number.isInteger(arrayIndex)) {
123
+ // ensure current is an array
124
+ if (!Array.isArray(current))
125
+ current = [];
126
+ if (current[arrayIndex] === undefined)
127
+ current[arrayIndex] = {};
128
+ if (isLast) {
129
+ current[arrayIndex] = flat[key];
130
+ }
131
+ else {
132
+ current = current[arrayIndex];
133
+ }
134
+ }
135
+ else {
136
+ // normal object key
137
+ if (isLast) {
138
+ current[part] = flat[key];
139
+ }
140
+ else {
141
+ if (current[part] === undefined ||
142
+ typeof current[part] !== "object") {
143
+ // check if next part is numeric to pre-create array
144
+ const nextPart = parts[i + 1];
145
+ current[part] = !isNaN(Number(nextPart)) ? [] : {};
146
+ }
147
+ current = current[part];
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+ function mergeValues(defaultValues, saved) {
155
+ const flattenDefaultValues = flattenFormValues(defaultValues);
156
+ const flattenSaved = flattenFormValues(saved);
157
+ const result = {};
158
+ // Get all unique keys from both objects
159
+ const allKeys = new Set([
160
+ ...Object.keys(flattenDefaultValues),
161
+ ...Object.keys(flattenSaved),
162
+ ]);
163
+ for (const key of allKeys) {
164
+ const savedValue = flattenSaved[key];
165
+ const defaultValue = flattenDefaultValues[key];
166
+ result[key] =
167
+ defaultValue !== undefined && defaultValue !== null && defaultValue !== ""
168
+ ? defaultValue
169
+ : savedValue !== null && savedValue !== void 0 ? savedValue : defaultValue;
170
+ }
171
+ return result;
172
+ }
173
+ function determineDirtyFields(defaultValues, saved) {
174
+ const flattenDefaultValues = flattenFormValues(defaultValues);
175
+ const flattenSaved = flattenFormValues(saved);
176
+ const result = {};
177
+ // Get all unique keys from both objects
178
+ const allKeys = new Set([
179
+ ...Object.keys(flattenDefaultValues),
180
+ ...Object.keys(flattenSaved),
181
+ ]);
182
+ for (const key of allKeys) {
183
+ const savedValue = flattenSaved[key];
184
+ const defaultValue = flattenDefaultValues[key];
185
+ if (savedValue === defaultValue)
186
+ continue;
187
+ result[key] = true;
188
+ }
189
+ return result;
190
+ }
@@ -0,0 +1,22 @@
1
+ import type { z } from "zod";
2
+ type ValidationResponse<T> = {
3
+ success: true;
4
+ data: T;
5
+ } | {
6
+ success: false;
7
+ errors: Partial<Record<keyof T, string>>;
8
+ message: string;
9
+ data: T;
10
+ };
11
+ /**
12
+ * Validates form data against a Zod schema.
13
+ *
14
+ * @param {Schema} validationSchema - The Zod schema to validate the form data against.
15
+ * @param {FormData | Record<string, unknown> | undefined} formData - The form data to validate.
16
+ * @returns {Promise<ValidationResponse<z.infer<Schema>>>} - A promise that resolves to a validation response.
17
+ *
18
+ * @throws {Error} Throws an error if a valid Zod schema or valid FormData is not provided,
19
+ * or if an unexpected error occurs during validation.
20
+ */
21
+ export declare function validateForm<Schema extends z.ZodObject>(validationSchema: Schema, formData: FormData | Record<string, unknown> | undefined): Promise<ValidationResponse<z.infer<Schema>>>;
22
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateForm = validateForm;
4
+ const utils_1 = require("./utils");
5
+ const zod_helpers_1 = require("./zod-helpers");
6
+ /**
7
+ * Validates form data against a Zod schema.
8
+ *
9
+ * @param {Schema} validationSchema - The Zod schema to validate the form data against.
10
+ * @param {FormData | Record<string, unknown> | undefined} formData - The form data to validate.
11
+ * @returns {Promise<ValidationResponse<z.infer<Schema>>>} - A promise that resolves to a validation response.
12
+ *
13
+ * @throws {Error} Throws an error if a valid Zod schema or valid FormData is not provided,
14
+ * or if an unexpected error occurs during validation.
15
+ */
16
+ async function validateForm(validationSchema, formData) {
17
+ if (typeof (validationSchema === null || validationSchema === void 0 ? void 0 : validationSchema.safeParseAsync) !== "function") {
18
+ throw new Error("A valid Zod schema is required for form validation. Please provide a valid Zod schema and try again.");
19
+ }
20
+ if (!formData) {
21
+ throw new Error("A valid FormData object is required for form validation. Please provide a valid FormData object and try again.");
22
+ }
23
+ try {
24
+ let form = formData;
25
+ if (formData instanceof FormData) {
26
+ form = Object.fromEntries(formData.entries());
27
+ }
28
+ const result = await validationSchema.safeParseAsync((0, utils_1.nestFormValues)(form));
29
+ if (result.success) {
30
+ return {
31
+ success: true,
32
+ data: result.data,
33
+ };
34
+ }
35
+ const errors = (0, zod_helpers_1.mapZodErrors)(result.error.issues);
36
+ return {
37
+ success: false,
38
+ errors,
39
+ message: "Validation failed",
40
+ data: form,
41
+ };
42
+ }
43
+ catch (error) {
44
+ throw new Error(`Validation failed due to an unexpected error: ${error.message || "Please make sure Zod is installed and try again."}`);
45
+ }
46
+ }
@@ -0,0 +1,5 @@
1
+ import type { z } from "zod";
2
+ export declare function isZodSchema(schema: unknown): schema is z.ZodObject;
3
+ export declare function createEmptyValues<T extends z.ZodObject<any>>(schema?: T): z.infer<T>;
4
+ export declare function mapZodErrors(error?: z.ZodError["issues"]): Record<string, string>;
5
+ export declare function isZodError(issues: any): issues is z.ZodError["issues"];
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isZodSchema = isZodSchema;
4
+ exports.createEmptyValues = createEmptyValues;
5
+ exports.mapZodErrors = mapZodErrors;
6
+ exports.isZodError = isZodError;
7
+ function isZodSchema(schema) {
8
+ return typeof schema === "object" && schema !== null && "def" in schema;
9
+ }
10
+ function createEmptyValues(schema) {
11
+ if (!schema)
12
+ return {};
13
+ const shape = schema.shape;
14
+ const result = {};
15
+ for (const key in shape) {
16
+ const field = shape[key];
17
+ result[key] = getEmptyValue(field);
18
+ }
19
+ return result;
20
+ }
21
+ function getEmptyValue(field) {
22
+ const { type } = field.def;
23
+ switch (type) {
24
+ case "object":
25
+ return createEmptyValues(field);
26
+ case "array":
27
+ // safe recursion into array element type
28
+ //@ts-ignore
29
+ return [getEmptyValue(field.def.element)];
30
+ case "boolean":
31
+ return false;
32
+ case "string":
33
+ case "number":
34
+ case "bigint":
35
+ case "date":
36
+ return "";
37
+ case "optional":
38
+ case "nullable":
39
+ // unwrap and recurse
40
+ //@ts-ignore
41
+ return getEmptyValue(field.def.innerType);
42
+ default:
43
+ return "";
44
+ }
45
+ }
46
+ function mapZodErrors(error) {
47
+ if (!error)
48
+ return {};
49
+ const result = {};
50
+ for (const issue of error) {
51
+ const path = issue.path
52
+ .map((p) => (typeof p === "number" ? p : p.toString()))
53
+ .join(".");
54
+ result[path || "_root"] = issue.message;
55
+ }
56
+ return result;
57
+ }
58
+ function isZodError(issues) {
59
+ return (issues &&
60
+ typeof issues === "object" &&
61
+ Array.isArray(issues) &&
62
+ issues.every((i) => "path" in i && "message" in i));
63
+ }
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { useForm } from "../hooks/use-form";
3
+ import type { z } from "zod";
4
+ export type FormContextValue<TSchema extends z.ZodObject | undefined = undefined, DefaultValues = TSchema extends undefined ? Record<string, any> : Partial<z.infer<TSchema>>> = ReturnType<typeof useForm<TSchema, DefaultValues>>;
5
+ type FormProps<TSchema extends z.ZodObject | undefined = undefined, DefaultValues = TSchema extends undefined ? Record<string, any> : Partial<z.infer<TSchema>>> = {
6
+ use?: FormContextValue<TSchema, DefaultValues>;
7
+ children: React.ReactNode;
8
+ as?: "form" | "div" | "section";
9
+ className?: string;
10
+ } & React.HTMLAttributes<HTMLFormElement | HTMLDivElement>;
11
+ export declare const FormContext: React.Context<import("..").FormInstance<any> | null>;
12
+ /**
13
+ * A component that wraps the useForm hook and provides the form context
14
+ * to its children. It also provides a way to render the form as a
15
+ * different HTML element.
16
+ *
17
+ * @param {FormProps<TSchema, DefaultValues>} - The props for the Form component.
18
+ * @returns {ReactNode} - The rendered Form component.
19
+ *
20
+ * @example
21
+ *
22
+ * const form = useForm({
23
+ * schema,
24
+ * defaultValues,
25
+ * errors,
26
+ * mode,
27
+ * errorParser,
28
+ * persistKey,
29
+ * check,
30
+ * })
31
+ *
32
+ * <Form use={form} onSubmit={form.handleSubmit((data, ctx) => {
33
+ console.log("before", data);
34
+
35
+ const after = ctx
36
+ .array("contacts")
37
+ .filter((item) => item.phone > 3 && item.phone !== null)
38
+ .map((item) => item)
39
+ .removeIf((item) => item.phone === 4);
40
+
41
+ const snapshot = after.snapshot();
42
+ const values = after.get();
43
+
44
+ console.log("after", snapshot, values);
45
+ })}>
46
+ * <Field name="name" label="Name" />
47
+ * <Field name="age" label="Age" type="number" />
48
+ * </Form>
49
+ */
50
+ export declare function Form<TSchema extends z.ZodObject | undefined = undefined, DefaultValues = TSchema extends undefined ? Record<string, any> : Partial<z.infer<TSchema>>>({ children, use, as, className, onSubmit, ...rest }: FormProps<TSchema, DefaultValues>): React.JSX.Element;
51
+ export {};
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.FormContext = void 0;
8
+ exports.Form = Form;
9
+ const react_1 = __importDefault(require("react"));
10
+ const use_form_1 = require("../hooks/use-form");
11
+ const css_1 = require("../lib/css");
12
+ // export const FormContext = createContext<FormContextValue | null>(null);
13
+ exports.FormContext = react_1.default.createContext(null);
14
+ /**
15
+ * A component that wraps the useForm hook and provides the form context
16
+ * to its children. It also provides a way to render the form as a
17
+ * different HTML element.
18
+ *
19
+ * @param {FormProps<TSchema, DefaultValues>} - The props for the Form component.
20
+ * @returns {ReactNode} - The rendered Form component.
21
+ *
22
+ * @example
23
+ *
24
+ * const form = useForm({
25
+ * schema,
26
+ * defaultValues,
27
+ * errors,
28
+ * mode,
29
+ * errorParser,
30
+ * persistKey,
31
+ * check,
32
+ * })
33
+ *
34
+ * <Form use={form} onSubmit={form.handleSubmit((data, ctx) => {
35
+ console.log("before", data);
36
+
37
+ const after = ctx
38
+ .array("contacts")
39
+ .filter((item) => item.phone > 3 && item.phone !== null)
40
+ .map((item) => item)
41
+ .removeIf((item) => item.phone === 4);
42
+
43
+ const snapshot = after.snapshot();
44
+ const values = after.get();
45
+
46
+ console.log("after", snapshot, values);
47
+ })}>
48
+ * <Field name="name" label="Name" />
49
+ * <Field name="age" label="Age" type="number" />
50
+ * </Form>
51
+ */
52
+ function Form({ children, use, as = "form", className, onSubmit, ...rest }) {
53
+ const formInstance = use !== null && use !== void 0 ? use : (0, use_form_1.useForm)();
54
+ const Element = as;
55
+ //@ts-ignore
56
+ const submitFn = onSubmit || formInstance.onSubmit;
57
+ return (
58
+ // @ts-ignore
59
+ react_1.default.createElement(exports.FormContext.Provider, { value: formInstance },
60
+ react_1.default.createElement(Element, { onSubmit: submitFn, className: `explita-form ${className !== null && className !== void 0 ? className : ""}`, ...rest },
61
+ react_1.default.createElement("style", null, css_1.css),
62
+ children)));
63
+ }