@flightdev/forms 0.0.2

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.
@@ -0,0 +1,110 @@
1
+ // src/index.ts
2
+ var formCounter = 0;
3
+ function createForm(options) {
4
+ const { validator, action, id, csrf = true, progressive = true } = options;
5
+ const formId = id ?? `form_${++formCounter}`;
6
+ return {
7
+ id: formId,
8
+ validate(data) {
9
+ return validator.validate(data);
10
+ },
11
+ validateAsync(data) {
12
+ return validator.validateAsync(data);
13
+ },
14
+ async execute(data, context) {
15
+ const validation = await validator.validateAsync(data);
16
+ if (!validation.success) {
17
+ return {
18
+ success: false,
19
+ error: "Validation failed",
20
+ fieldErrors: validation.errors
21
+ };
22
+ }
23
+ try {
24
+ return await action(validation.data, context);
25
+ } catch (error) {
26
+ return {
27
+ success: false,
28
+ error: error instanceof Error ? error.message : "Unknown error"
29
+ };
30
+ }
31
+ },
32
+ parseFormData(formData) {
33
+ const result = {};
34
+ for (const [key, value] of formData.entries()) {
35
+ const keys = key.split(/[\.\[\]]/).filter(Boolean);
36
+ if (keys.length === 1) {
37
+ if (result[key] !== void 0) {
38
+ if (Array.isArray(result[key])) {
39
+ result[key].push(value);
40
+ } else {
41
+ result[key] = [result[key], value];
42
+ }
43
+ } else {
44
+ result[key] = value === "" ? void 0 : value;
45
+ }
46
+ } else {
47
+ setNestedValue(result, keys, value);
48
+ }
49
+ }
50
+ return result;
51
+ },
52
+ async handleSubmit(request) {
53
+ const contentType = request.headers.get("content-type") ?? "";
54
+ let data;
55
+ if (contentType.includes("application/json")) {
56
+ data = await request.json();
57
+ } else if (contentType.includes("multipart/form-data") || contentType.includes("application/x-www-form-urlencoded")) {
58
+ const formData = await request.formData();
59
+ data = this.parseFormData(formData);
60
+ } else {
61
+ return {
62
+ success: false,
63
+ error: "Unsupported content type"
64
+ };
65
+ }
66
+ return this.execute(data, { request });
67
+ }
68
+ };
69
+ }
70
+ function setNestedValue(obj, keys, value) {
71
+ let current = obj;
72
+ for (let i = 0; i < keys.length - 1; i++) {
73
+ const key = keys[i] ?? "";
74
+ const nextKey = keys[i + 1] ?? "";
75
+ const isNextArray = /^\d+$/.test(nextKey);
76
+ if (current[key] === void 0) {
77
+ current[key] = isNextArray ? [] : {};
78
+ }
79
+ current = current[key];
80
+ }
81
+ const lastKey = keys[keys.length - 1] ?? "";
82
+ current[lastKey] = value === "" ? void 0 : value;
83
+ }
84
+ function createFormState() {
85
+ return {
86
+ pending: false,
87
+ errors: {},
88
+ touched: /* @__PURE__ */ new Set(),
89
+ dirty: false
90
+ };
91
+ }
92
+ function errorsToObject(errors) {
93
+ const result = {};
94
+ for (const error of errors) {
95
+ if (!result[error.path]) {
96
+ result[error.path] = error.message;
97
+ }
98
+ }
99
+ return result;
100
+ }
101
+ function getFieldError(errors, path) {
102
+ return errors.find((e) => e.path === path)?.message;
103
+ }
104
+ function hasErrors(state) {
105
+ return Object.keys(state.errors).length > 0;
106
+ }
107
+
108
+ export { createForm, createFormState, errorsToObject, getFieldError, hasErrors };
109
+ //# sourceMappingURL=chunk-5LLYBEXV.js.map
110
+ //# sourceMappingURL=chunk-5LLYBEXV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"chunk-5LLYBEXV.js","sourcesContent":["/**\r\n * @flightdev/forms - Agnostic Full-Stack Form Handling\r\n * \r\n * Flight provides form primitives, you choose the validator.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createForm } from '@flightdev/forms';\r\n * import { zod } from '@flightdev/forms/zod';\r\n * import { z } from 'zod';\r\n * \r\n * const contactForm = createForm({\r\n * validator: zod(z.object({\r\n * email: z.string().email(),\r\n * message: z.string().min(10),\r\n * })),\r\n * action: async (data) => {\r\n * await sendEmail(data.email, data.message);\r\n * return { success: true };\r\n * },\r\n * });\r\n * ```\r\n */\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/** Validation error for a single field */\r\nexport interface FieldError {\r\n /** Field path (e.g., 'email', 'address.street') */\r\n path: string;\r\n /** Error message */\r\n message: string;\r\n /** Error code (e.g., 'invalid_type', 'too_small') */\r\n code?: string;\r\n}\r\n\r\n/** Result of validation */\r\nexport interface ValidationResult<T> {\r\n /** Whether validation passed */\r\n success: boolean;\r\n /** Validated and transformed data (if success) */\r\n data?: T;\r\n /** Validation errors (if not success) */\r\n errors?: FieldError[];\r\n}\r\n\r\n/** Form submission result */\r\nexport interface FormResult<T = unknown> {\r\n /** Whether the action succeeded */\r\n success: boolean;\r\n /** Returned data from the action */\r\n data?: T;\r\n /** Error message if failed */\r\n error?: string;\r\n /** Field-specific errors */\r\n fieldErrors?: FieldError[];\r\n}\r\n\r\n/** Form state during submission */\r\nexport interface FormState<T = unknown> {\r\n /** Whether form is currently submitting */\r\n pending: boolean;\r\n /** Last submission result */\r\n result?: FormResult<T>;\r\n /** Current form data */\r\n data?: Partial<T>;\r\n /** Current validation errors */\r\n errors: Record<string, string>;\r\n /** Fields that have been touched */\r\n touched: Set<string>;\r\n /** Whether form has been modified */\r\n dirty: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Validation Adapter Interface\r\n// ============================================================================\r\n\r\n/**\r\n * Validation Adapter Interface\r\n * \r\n * Implement this to create a custom validator adapter.\r\n * Flight provides Zod, Yup, and Valibot adapters.\r\n */\r\nexport interface ValidationAdapter<TInput = unknown, TOutput = TInput> {\r\n /** Adapter name for debugging */\r\n readonly name: string;\r\n\r\n /**\r\n * Validate input data\r\n * \r\n * @param data - Raw input data to validate\r\n * @returns Validation result with either typed data or errors\r\n */\r\n validate(data: unknown): ValidationResult<TOutput>;\r\n\r\n /**\r\n * Async validate (for schemas with async refinements)\r\n */\r\n validateAsync(data: unknown): Promise<ValidationResult<TOutput>>;\r\n\r\n /**\r\n * Get the inferred output type (for TypeScript)\r\n * This is used for type inference only.\r\n */\r\n readonly _output: TOutput;\r\n}\r\n\r\n/** Validation adapter factory type */\r\nexport type ValidationAdapterFactory<TSchema, TOutput> = (\r\n schema: TSchema\r\n) => ValidationAdapter<unknown, TOutput>;\r\n\r\n// ============================================================================\r\n// Form Definition\r\n// ============================================================================\r\n\r\n/** Form action function type */\r\nexport type FormAction<TInput, TResult = unknown> = (\r\n data: TInput,\r\n context?: FormActionContext\r\n) => Promise<FormResult<TResult>>;\r\n\r\n/** Context passed to form actions */\r\nexport interface FormActionContext {\r\n /** Original request (server-side) */\r\n request?: Request;\r\n /** Form data (for file uploads) */\r\n formData?: FormData;\r\n}\r\n\r\n/** Form definition options */\r\nexport interface FormOptions<TValidator extends ValidationAdapter<unknown, unknown>, TResult = unknown> {\r\n /** Validation adapter with schema */\r\n validator: TValidator;\r\n /** Server action to execute on submit */\r\n action: FormAction<TValidator['_output'], TResult>;\r\n /** Enable CSRF protection */\r\n csrf?: boolean;\r\n /** Form ID (for multiple forms on same page) */\r\n id?: string;\r\n /** Redirect URL on success */\r\n redirectOnSuccess?: string;\r\n /** Enable progressive enhancement */\r\n progressive?: boolean;\r\n}\r\n\r\n/** Form instance */\r\nexport interface FormDefinition<TInput, TResult = unknown> {\r\n /** Form ID */\r\n readonly id: string;\r\n /** Validate data */\r\n validate(data: unknown): ValidationResult<TInput>;\r\n /** Validate data async */\r\n validateAsync(data: unknown): Promise<ValidationResult<TInput>>;\r\n /** Execute the form action (server-side) */\r\n execute(data: unknown, context?: FormActionContext): Promise<FormResult<TResult>>;\r\n /** Parse FormData to object */\r\n parseFormData(formData: FormData): Record<string, unknown>;\r\n /** Handle form submission (server-side) */\r\n handleSubmit(request: Request): Promise<FormResult<TResult>>;\r\n}\r\n\r\n// ============================================================================\r\n// Form Factory\r\n// ============================================================================\r\n\r\nlet formCounter = 0;\r\n\r\n/**\r\n * Create a form definition\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createForm } from '@flightdev/forms';\r\n * import { zod } from '@flightdev/forms/zod';\r\n * import { z } from 'zod';\r\n * \r\n * const loginForm = createForm({\r\n * validator: zod(z.object({\r\n * email: z.string().email(),\r\n * password: z.string().min(8),\r\n * })),\r\n * action: async (data) => {\r\n * const user = await authenticate(data.email, data.password);\r\n * if (user) {\r\n * return { success: true, data: { userId: user.id } };\r\n * }\r\n * return {\r\n * success: false,\r\n * error: 'Invalid credentials',\r\n * fieldErrors: [{ path: 'email', message: 'Invalid email or password' }],\r\n * };\r\n * },\r\n * });\r\n * ```\r\n */\r\nexport function createForm<\r\n TValidator extends ValidationAdapter<unknown, unknown>,\r\n TResult = unknown\r\n>(\r\n options: FormOptions<TValidator, TResult>\r\n): FormDefinition<TValidator['_output'], TResult> {\r\n const { validator, action, id, csrf = true, progressive = true } = options;\r\n const formId = id ?? `form_${++formCounter}`;\r\n\r\n return {\r\n id: formId,\r\n\r\n validate(data: unknown) {\r\n return validator.validate(data);\r\n },\r\n\r\n validateAsync(data: unknown) {\r\n return validator.validateAsync(data);\r\n },\r\n\r\n async execute(data: unknown, context?: FormActionContext) {\r\n // Validate first\r\n const validation = await validator.validateAsync(data);\r\n\r\n if (!validation.success) {\r\n return {\r\n success: false,\r\n error: 'Validation failed',\r\n fieldErrors: validation.errors,\r\n };\r\n }\r\n\r\n // Execute action\r\n try {\r\n return await action(validation.data as TValidator['_output'], context);\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error.message : 'Unknown error',\r\n };\r\n }\r\n },\r\n\r\n parseFormData(formData: FormData): Record<string, unknown> {\r\n const result: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of formData.entries()) {\r\n // Handle nested keys (e.g., 'address.street' or 'items[0].name')\r\n const keys = key.split(/[\\.\\[\\]]/).filter(Boolean);\r\n\r\n if (keys.length === 1) {\r\n // Simple key\r\n if (result[key] !== undefined) {\r\n // Multiple values with same key -> array\r\n if (Array.isArray(result[key])) {\r\n (result[key] as unknown[]).push(value);\r\n } else {\r\n result[key] = [result[key], value];\r\n }\r\n } else {\r\n result[key] = value === '' ? undefined : value;\r\n }\r\n } else {\r\n // Nested key - set using path\r\n setNestedValue(result, keys, value);\r\n }\r\n }\r\n\r\n return result;\r\n },\r\n\r\n async handleSubmit(request: Request) {\r\n const contentType = request.headers.get('content-type') ?? '';\r\n let data: unknown;\r\n\r\n if (contentType.includes('application/json')) {\r\n data = await request.json();\r\n } else if (contentType.includes('multipart/form-data') || contentType.includes('application/x-www-form-urlencoded')) {\r\n const formData = await request.formData();\r\n data = this.parseFormData(formData);\r\n } else {\r\n return {\r\n success: false,\r\n error: 'Unsupported content type',\r\n } as FormResult<TResult>;\r\n }\r\n\r\n return this.execute(data, { request });\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Set a nested value in an object using a path array\r\n */\r\nfunction setNestedValue(obj: Record<string, unknown>, keys: string[], value: unknown): void {\r\n let current = obj;\r\n\r\n for (let i = 0; i < keys.length - 1; i++) {\r\n const key = keys[i] ?? '';\r\n const nextKey = keys[i + 1] ?? '';\r\n const isNextArray = /^\\d+$/.test(nextKey);\r\n\r\n if (current[key] === undefined) {\r\n current[key] = isNextArray ? [] : {};\r\n }\r\n current = current[key] as Record<string, unknown>;\r\n }\r\n\r\n const lastKey = keys[keys.length - 1] ?? '';\r\n current[lastKey] = value === '' ? undefined : value;\r\n}\r\n\r\n/**\r\n * Create initial form state\r\n */\r\nexport function createFormState<T>(): FormState<T> {\r\n return {\r\n pending: false,\r\n errors: {},\r\n touched: new Set(),\r\n dirty: false,\r\n };\r\n}\r\n\r\n/**\r\n * Convert field errors array to errors object\r\n */\r\nexport function errorsToObject(errors: FieldError[]): Record<string, string> {\r\n const result: Record<string, string> = {};\r\n for (const error of errors) {\r\n if (!result[error.path]) {\r\n result[error.path] = error.message;\r\n }\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Get error for a specific field\r\n */\r\nexport function getFieldError(errors: FieldError[], path: string): string | undefined {\r\n return errors.find(e => e.path === path)?.message;\r\n}\r\n\r\n/**\r\n * Check if form has any errors\r\n */\r\nexport function hasErrors(state: FormState<unknown>): boolean {\r\n return Object.keys(state.errors).length > 0;\r\n}\r\n\r\n// Note: FormData and Request are global types available in modern runtimes\r\n"]}
@@ -0,0 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export { __require };
9
+ //# sourceMappingURL=chunk-DGUM43GV.js.map
10
+ //# sourceMappingURL=chunk-DGUM43GV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-DGUM43GV.js"}
@@ -0,0 +1,85 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { FormDefinition, FormResult } from '../index.js';
4
+
5
+ interface UseFormOptions<TResult = unknown> {
6
+ /** Callback on successful submission */
7
+ onSuccess?: (result: FormResult<TResult>) => void;
8
+ /** Callback on failed submission */
9
+ onError?: (result: FormResult<TResult>) => void;
10
+ /** Reset form after successful submission */
11
+ resetOnSuccess?: boolean;
12
+ /** Validate on blur */
13
+ validateOnBlur?: boolean;
14
+ /** Validate on change */
15
+ validateOnChange?: boolean;
16
+ }
17
+ interface UseFormReturn<TInput, TResult = unknown> {
18
+ /** Register a form field */
19
+ register: (name: keyof TInput & string) => FieldRegistration;
20
+ /** Handle form submission */
21
+ handleSubmit: (e?: React.FormEvent) => Promise<void>;
22
+ /** Current form values */
23
+ values: Partial<TInput>;
24
+ /** Set form values */
25
+ setValue: (name: keyof TInput & string, value: unknown) => void;
26
+ /** Set multiple values */
27
+ setValues: (values: Partial<TInput>) => void;
28
+ /** Reset form to initial values */
29
+ reset: () => void;
30
+ /** Field errors */
31
+ errors: Record<string, string>;
32
+ /** Touched fields */
33
+ touched: Set<string>;
34
+ /** Whether form is submitting */
35
+ pending: boolean;
36
+ /** Whether form has been modified */
37
+ dirty: boolean;
38
+ /** Last submission result */
39
+ result?: FormResult<TResult>;
40
+ /** Validate single field */
41
+ validateField: (name: string) => void;
42
+ /** Validate entire form */
43
+ validate: () => Promise<boolean>;
44
+ }
45
+ interface FieldRegistration {
46
+ name: string;
47
+ value: string | number | readonly string[];
48
+ onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;
49
+ onBlur: (e: React.FocusEvent) => void;
50
+ 'aria-invalid'?: boolean;
51
+ 'aria-describedby'?: string;
52
+ }
53
+ /**
54
+ * React hook for form handling
55
+ */
56
+ declare function useForm<TInput, TResult = unknown>(form: FormDefinition<TInput, TResult>, options?: UseFormOptions<TResult>): UseFormReturn<TInput, TResult>;
57
+ interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'onSubmit'> {
58
+ onSubmit: (e?: React.FormEvent) => Promise<void>;
59
+ children: React.ReactNode;
60
+ }
61
+ /**
62
+ * Form component with progressive enhancement
63
+ */
64
+ declare const Form: React.ForwardRefExoticComponent<FormProps & React.RefAttributes<HTMLFormElement>>;
65
+ interface FieldProps {
66
+ name: string;
67
+ register: (name: string) => FieldRegistration;
68
+ error?: string;
69
+ label?: string;
70
+ type?: string;
71
+ placeholder?: string;
72
+ className?: string;
73
+ required?: boolean;
74
+ }
75
+ /**
76
+ * Form field component with error display
77
+ */
78
+ declare function Field({ name, register, error, label, type, placeholder, className, required, }: FieldProps): react_jsx_runtime.JSX.Element;
79
+ declare const _default: {
80
+ useForm: typeof useForm;
81
+ Form: React.ForwardRefExoticComponent<FormProps & React.RefAttributes<HTMLFormElement>>;
82
+ Field: typeof Field;
83
+ };
84
+
85
+ export { Field, type FieldProps, type FieldRegistration, Form, type FormProps, type UseFormOptions, type UseFormReturn, _default as default, useForm };
@@ -0,0 +1,195 @@
1
+ import { errorsToObject } from '../chunk-5LLYBEXV.js';
2
+ import '../chunk-DGUM43GV.js';
3
+ import * as React from 'react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ function useForm(form, options = {}) {
7
+ const {
8
+ onSuccess,
9
+ onError,
10
+ resetOnSuccess = false,
11
+ validateOnBlur = true,
12
+ validateOnChange = false
13
+ } = options;
14
+ const [values, setValuesState] = React.useState({});
15
+ const [errors, setErrors] = React.useState({});
16
+ const [touched, setTouched] = React.useState(/* @__PURE__ */ new Set());
17
+ const [pending, setPending] = React.useState(false);
18
+ const [dirty, setDirty] = React.useState(false);
19
+ const [result, setResult] = React.useState();
20
+ const setValue = React.useCallback((name, value) => {
21
+ setValuesState((prev) => ({ ...prev, [name]: value }));
22
+ setDirty(true);
23
+ if (validateOnChange) {
24
+ const validation = form.validate({ ...values, [name]: value });
25
+ if (!validation.success && validation.errors) {
26
+ const fieldError = validation.errors.find((e) => e.path === name);
27
+ if (fieldError) {
28
+ setErrors((prev) => ({ ...prev, [name]: fieldError.message }));
29
+ } else {
30
+ setErrors((prev) => {
31
+ const next = { ...prev };
32
+ delete next[name];
33
+ return next;
34
+ });
35
+ }
36
+ } else {
37
+ setErrors((prev) => {
38
+ const next = { ...prev };
39
+ delete next[name];
40
+ return next;
41
+ });
42
+ }
43
+ }
44
+ }, [form, values, validateOnChange]);
45
+ const setValues = React.useCallback((newValues) => {
46
+ setValuesState((prev) => ({ ...prev, ...newValues }));
47
+ setDirty(true);
48
+ }, []);
49
+ const reset = React.useCallback(() => {
50
+ setValuesState({});
51
+ setErrors({});
52
+ setTouched(/* @__PURE__ */ new Set());
53
+ setDirty(false);
54
+ setResult(void 0);
55
+ }, []);
56
+ const validateField = React.useCallback((name) => {
57
+ const validation = form.validate(values);
58
+ if (!validation.success && validation.errors) {
59
+ const fieldError = validation.errors.find((e) => e.path === name);
60
+ if (fieldError) {
61
+ setErrors((prev) => ({ ...prev, [name]: fieldError.message }));
62
+ } else {
63
+ setErrors((prev) => {
64
+ const next = { ...prev };
65
+ delete next[name];
66
+ return next;
67
+ });
68
+ }
69
+ } else {
70
+ setErrors((prev) => {
71
+ const next = { ...prev };
72
+ delete next[name];
73
+ return next;
74
+ });
75
+ }
76
+ }, [form, values]);
77
+ const validate = React.useCallback(async () => {
78
+ const validation = await form.validateAsync(values);
79
+ if (!validation.success && validation.errors) {
80
+ setErrors(errorsToObject(validation.errors));
81
+ return false;
82
+ }
83
+ setErrors({});
84
+ return true;
85
+ }, [form, values]);
86
+ const register = React.useCallback((name) => {
87
+ const rawValue = values[name];
88
+ const value = typeof rawValue === "string" || typeof rawValue === "number" ? rawValue : Array.isArray(rawValue) ? rawValue : String(rawValue ?? "");
89
+ return {
90
+ name,
91
+ value,
92
+ onChange: (e) => {
93
+ const newValue = e.target.type === "checkbox" ? e.target.checked : e.target.value;
94
+ setValue(name, newValue);
95
+ },
96
+ onBlur: () => {
97
+ setTouched((prev) => new Set(prev).add(name));
98
+ if (validateOnBlur) {
99
+ validateField(name);
100
+ }
101
+ },
102
+ "aria-invalid": errors[name] ? true : void 0,
103
+ "aria-describedby": errors[name] ? `${name}-error` : void 0
104
+ };
105
+ }, [values, errors, setValue, validateField, validateOnBlur]);
106
+ const handleSubmit = React.useCallback(async (e) => {
107
+ e?.preventDefault();
108
+ setPending(true);
109
+ try {
110
+ const formResult = await form.execute(values);
111
+ setResult(formResult);
112
+ if (formResult.success) {
113
+ onSuccess?.(formResult);
114
+ if (resetOnSuccess) {
115
+ reset();
116
+ }
117
+ } else {
118
+ if (formResult.fieldErrors) {
119
+ setErrors(errorsToObject(formResult.fieldErrors));
120
+ }
121
+ onError?.(formResult);
122
+ }
123
+ } catch (error) {
124
+ const errorResult = {
125
+ success: false,
126
+ error: error instanceof Error ? error.message : "Unknown error"
127
+ };
128
+ setResult(errorResult);
129
+ onError?.(errorResult);
130
+ } finally {
131
+ setPending(false);
132
+ }
133
+ }, [form, values, onSuccess, onError, reset, resetOnSuccess]);
134
+ return {
135
+ register,
136
+ handleSubmit,
137
+ values,
138
+ setValue,
139
+ setValues,
140
+ reset,
141
+ errors,
142
+ touched,
143
+ pending,
144
+ dirty,
145
+ result,
146
+ validateField,
147
+ validate
148
+ };
149
+ }
150
+ var Form = React.forwardRef(function Form2({ onSubmit, children, method = "POST", ...props }, ref) {
151
+ return /* @__PURE__ */ jsx(
152
+ "form",
153
+ {
154
+ ref,
155
+ method,
156
+ onSubmit,
157
+ ...props,
158
+ children
159
+ }
160
+ );
161
+ });
162
+ function Field({
163
+ name,
164
+ register,
165
+ error,
166
+ label,
167
+ type = "text",
168
+ placeholder,
169
+ className,
170
+ required
171
+ }) {
172
+ const registration = register(name);
173
+ return /* @__PURE__ */ jsxs("div", { className, children: [
174
+ label && /* @__PURE__ */ jsxs("label", { htmlFor: name, children: [
175
+ label,
176
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " *" })
177
+ ] }),
178
+ /* @__PURE__ */ jsx(
179
+ "input",
180
+ {
181
+ id: name,
182
+ type,
183
+ placeholder,
184
+ required,
185
+ ...registration
186
+ }
187
+ ),
188
+ error && /* @__PURE__ */ jsx("span", { id: `${name}-error`, role: "alert", children: error })
189
+ ] });
190
+ }
191
+ var react_default = { useForm, Form, Field };
192
+
193
+ export { Field, Form, react_default as default, useForm };
194
+ //# sourceMappingURL=react.js.map
195
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/frameworks/react.tsx"],"names":["Form"],"mappings":"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","file":"react.js","sourcesContent":["/**\r\n * React Hooks for @flightdev/forms\r\n * \r\n * @example\r\n * ```tsx\r\n * import { createForm } from '@flightdev/forms';\r\n * import { zod } from '@flightdev/forms/zod';\r\n * import { useForm, Form, Field } from '@flightdev/forms/react';\r\n * \r\n * const loginForm = createForm({ ... });\r\n * \r\n * function LoginPage() {\r\n * const { register, handleSubmit, errors, pending } = useForm(loginForm);\r\n * \r\n * return (\r\n * <Form onSubmit={handleSubmit}>\r\n * <Field name=\"email\" register={register} error={errors.email} />\r\n * <button disabled={pending}>Submit</button>\r\n * </Form>\r\n * );\r\n * }\r\n * ```\r\n */\r\n\r\nimport * as React from 'react';\r\nimport type { FormDefinition, FormState, FormResult, FieldError } from '../index.js';\r\nimport { createFormState, errorsToObject } from '../index.js';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface UseFormOptions<TResult = unknown> {\r\n /** Callback on successful submission */\r\n onSuccess?: (result: FormResult<TResult>) => void;\r\n /** Callback on failed submission */\r\n onError?: (result: FormResult<TResult>) => void;\r\n /** Reset form after successful submission */\r\n resetOnSuccess?: boolean;\r\n /** Validate on blur */\r\n validateOnBlur?: boolean;\r\n /** Validate on change */\r\n validateOnChange?: boolean;\r\n}\r\n\r\nexport interface UseFormReturn<TInput, TResult = unknown> {\r\n /** Register a form field */\r\n register: (name: keyof TInput & string) => FieldRegistration;\r\n /** Handle form submission */\r\n handleSubmit: (e?: React.FormEvent) => Promise<void>;\r\n /** Current form values */\r\n values: Partial<TInput>;\r\n /** Set form values */\r\n setValue: (name: keyof TInput & string, value: unknown) => void;\r\n /** Set multiple values */\r\n setValues: (values: Partial<TInput>) => void;\r\n /** Reset form to initial values */\r\n reset: () => void;\r\n /** Field errors */\r\n errors: Record<string, string>;\r\n /** Touched fields */\r\n touched: Set<string>;\r\n /** Whether form is submitting */\r\n pending: boolean;\r\n /** Whether form has been modified */\r\n dirty: boolean;\r\n /** Last submission result */\r\n result?: FormResult<TResult>;\r\n /** Validate single field */\r\n validateField: (name: string) => void;\r\n /** Validate entire form */\r\n validate: () => Promise<boolean>;\r\n}\r\n\r\nexport interface FieldRegistration {\r\n name: string;\r\n value: string | number | readonly string[];\r\n onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;\r\n onBlur: (e: React.FocusEvent) => void;\r\n 'aria-invalid'?: boolean;\r\n 'aria-describedby'?: string;\r\n}\r\n\r\n// ============================================================================\r\n// useForm Hook\r\n// ============================================================================\r\n\r\n/**\r\n * React hook for form handling\r\n */\r\nexport function useForm<TInput, TResult = unknown>(\r\n form: FormDefinition<TInput, TResult>,\r\n options: UseFormOptions<TResult> = {}\r\n): UseFormReturn<TInput, TResult> {\r\n const {\r\n onSuccess,\r\n onError,\r\n resetOnSuccess = false,\r\n validateOnBlur = true,\r\n validateOnChange = false,\r\n } = options;\r\n\r\n // State\r\n const [values, setValuesState] = React.useState<Partial<TInput>>({});\r\n const [errors, setErrors] = React.useState<Record<string, string>>({});\r\n const [touched, setTouched] = React.useState<Set<string>>(new Set());\r\n const [pending, setPending] = React.useState(false);\r\n const [dirty, setDirty] = React.useState(false);\r\n const [result, setResult] = React.useState<FormResult<TResult> | undefined>();\r\n\r\n // Set a single value\r\n const setValue = React.useCallback((name: keyof TInput & string, value: unknown) => {\r\n setValuesState(prev => ({ ...prev, [name]: value }));\r\n setDirty(true);\r\n\r\n if (validateOnChange) {\r\n // Validate the field\r\n const validation = form.validate({ ...values, [name]: value });\r\n if (!validation.success && validation.errors) {\r\n const fieldError = validation.errors.find(e => e.path === name);\r\n if (fieldError) {\r\n setErrors(prev => ({ ...prev, [name]: fieldError.message }));\r\n } else {\r\n setErrors(prev => {\r\n const next = { ...prev };\r\n delete next[name];\r\n return next;\r\n });\r\n }\r\n } else {\r\n setErrors(prev => {\r\n const next = { ...prev };\r\n delete next[name];\r\n return next;\r\n });\r\n }\r\n }\r\n }, [form, values, validateOnChange]);\r\n\r\n // Set multiple values\r\n const setValues = React.useCallback((newValues: Partial<TInput>) => {\r\n setValuesState(prev => ({ ...prev, ...newValues }));\r\n setDirty(true);\r\n }, []);\r\n\r\n // Reset form\r\n const reset = React.useCallback(() => {\r\n setValuesState({});\r\n setErrors({});\r\n setTouched(new Set());\r\n setDirty(false);\r\n setResult(undefined);\r\n }, []);\r\n\r\n // Validate single field\r\n const validateField = React.useCallback((name: string) => {\r\n const validation = form.validate(values);\r\n if (!validation.success && validation.errors) {\r\n const fieldError = validation.errors.find(e => e.path === name);\r\n if (fieldError) {\r\n setErrors(prev => ({ ...prev, [name]: fieldError.message }));\r\n } else {\r\n setErrors(prev => {\r\n const next = { ...prev };\r\n delete next[name];\r\n return next;\r\n });\r\n }\r\n } else {\r\n setErrors(prev => {\r\n const next = { ...prev };\r\n delete next[name];\r\n return next;\r\n });\r\n }\r\n }, [form, values]);\r\n\r\n // Validate entire form\r\n const validate = React.useCallback(async (): Promise<boolean> => {\r\n const validation = await form.validateAsync(values);\r\n if (!validation.success && validation.errors) {\r\n setErrors(errorsToObject(validation.errors));\r\n return false;\r\n }\r\n setErrors({});\r\n return true;\r\n }, [form, values]);\r\n\r\n // Register a field\r\n const register = React.useCallback((name: keyof TInput & string): FieldRegistration => {\r\n const rawValue = values[name];\r\n const value = typeof rawValue === 'string' || typeof rawValue === 'number'\r\n ? rawValue\r\n : Array.isArray(rawValue)\r\n ? rawValue as readonly string[]\r\n : String(rawValue ?? '');\r\n\r\n return {\r\n name,\r\n value,\r\n onChange: (e) => {\r\n const newValue = e.target.type === 'checkbox'\r\n ? (e.target as HTMLInputElement).checked\r\n : e.target.value;\r\n setValue(name, newValue);\r\n },\r\n onBlur: () => {\r\n setTouched(prev => new Set(prev).add(name));\r\n if (validateOnBlur) {\r\n validateField(name);\r\n }\r\n },\r\n 'aria-invalid': errors[name] ? true : undefined,\r\n 'aria-describedby': errors[name] ? `${name}-error` : undefined,\r\n };\r\n }, [values, errors, setValue, validateField, validateOnBlur]);\r\n\r\n // Handle form submission\r\n const handleSubmit = React.useCallback(async (e?: React.FormEvent) => {\r\n e?.preventDefault();\r\n setPending(true);\r\n\r\n try {\r\n const formResult = await form.execute(values);\r\n setResult(formResult);\r\n\r\n if (formResult.success) {\r\n onSuccess?.(formResult);\r\n if (resetOnSuccess) {\r\n reset();\r\n }\r\n } else {\r\n if (formResult.fieldErrors) {\r\n setErrors(errorsToObject(formResult.fieldErrors));\r\n }\r\n onError?.(formResult);\r\n }\r\n } catch (error) {\r\n const errorResult: FormResult<TResult> = {\r\n success: false,\r\n error: error instanceof Error ? error.message : 'Unknown error',\r\n };\r\n setResult(errorResult);\r\n onError?.(errorResult);\r\n } finally {\r\n setPending(false);\r\n }\r\n }, [form, values, onSuccess, onError, reset, resetOnSuccess]);\r\n\r\n return {\r\n register,\r\n handleSubmit,\r\n values,\r\n setValue,\r\n setValues,\r\n reset,\r\n errors,\r\n touched,\r\n pending,\r\n dirty,\r\n result,\r\n validateField,\r\n validate,\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Form Component\r\n// ============================================================================\r\n\r\nexport interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'onSubmit'> {\r\n onSubmit: (e?: React.FormEvent) => Promise<void>;\r\n children: React.ReactNode;\r\n}\r\n\r\n/**\r\n * Form component with progressive enhancement\r\n */\r\nexport const Form = React.forwardRef<HTMLFormElement, FormProps>(function Form(\r\n { onSubmit, children, method = 'POST', ...props },\r\n ref\r\n) {\r\n return (\r\n <form\r\n ref={ref}\r\n method={method}\r\n onSubmit={onSubmit}\r\n {...props}\r\n >\r\n {children}\r\n </form>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// Field Component\r\n// ============================================================================\r\n\r\nexport interface FieldProps {\r\n name: string;\r\n register: (name: string) => FieldRegistration;\r\n error?: string;\r\n label?: string;\r\n type?: string;\r\n placeholder?: string;\r\n className?: string;\r\n required?: boolean;\r\n}\r\n\r\n/**\r\n * Form field component with error display\r\n */\r\nexport function Field({\r\n name,\r\n register,\r\n error,\r\n label,\r\n type = 'text',\r\n placeholder,\r\n className,\r\n required,\r\n}: FieldProps) {\r\n const registration = register(name);\r\n\r\n return (\r\n <div className={className}>\r\n {label && (\r\n <label htmlFor={name}>\r\n {label}\r\n {required && <span aria-hidden=\"true\"> *</span>}\r\n </label>\r\n )}\r\n <input\r\n id={name}\r\n type={type}\r\n placeholder={placeholder}\r\n required={required}\r\n {...registration}\r\n />\r\n {error && (\r\n <span id={`${name}-error`} role=\"alert\">\r\n {error}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default { useForm, Form, Field };\r\n"]}
@@ -0,0 +1,63 @@
1
+ import { Accessor, Setter } from 'solid-js';
2
+ import { FormDefinition, FormResult } from '../index.js';
3
+
4
+ /**
5
+ * Solid.js Primitives for @flightdev/forms
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { createForm } from '@flightdev/forms';
10
+ * import { useForm } from '@flightdev/forms/solid';
11
+ *
12
+ * function LoginPage() {
13
+ * const { register, handleSubmit, errors, pending } = useForm(loginForm);
14
+ *
15
+ * return (
16
+ * <form onSubmit={handleSubmit}>
17
+ * <input {...register('email')} />
18
+ * <Show when={errors().email}>
19
+ * <span>{errors().email}</span>
20
+ * </Show>
21
+ * <button disabled={pending()}>Submit</button>
22
+ * </form>
23
+ * );
24
+ * }
25
+ * ```
26
+ */
27
+
28
+ interface UseFormOptions<TResult = unknown> {
29
+ onSuccess?: (result: FormResult<TResult>) => void;
30
+ onError?: (result: FormResult<TResult>) => void;
31
+ resetOnSuccess?: boolean;
32
+ validateOnBlur?: boolean;
33
+ validateOnChange?: boolean;
34
+ }
35
+ interface FieldBinding {
36
+ name: string;
37
+ value: unknown;
38
+ onInput: (e: InputEvent) => void;
39
+ onBlur: (e: FocusEvent) => void;
40
+ }
41
+ interface UseFormReturn<TInput, TResult = unknown> {
42
+ register: (name: keyof TInput & string) => FieldBinding;
43
+ handleSubmit: (e?: Event) => Promise<void>;
44
+ values: Accessor<Partial<TInput>>;
45
+ setValues: Setter<Partial<TInput>>;
46
+ setValue: (name: keyof TInput & string, value: unknown) => void;
47
+ reset: () => void;
48
+ errors: Accessor<Record<string, string>>;
49
+ touched: Accessor<Set<string>>;
50
+ pending: Accessor<boolean>;
51
+ dirty: Accessor<boolean>;
52
+ result: Accessor<FormResult<TResult> | undefined>;
53
+ validate: () => Promise<boolean>;
54
+ }
55
+ /**
56
+ * Solid.js primitive for form handling
57
+ */
58
+ declare function useForm<TInput, TResult = unknown>(form: FormDefinition<TInput, TResult>, options?: UseFormOptions<TResult>): UseFormReturn<TInput, TResult>;
59
+ declare const _default: {
60
+ useForm: typeof useForm;
61
+ };
62
+
63
+ export { type FieldBinding, type UseFormOptions, type UseFormReturn, _default as default, useForm };