@formos/react 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.
package/dist/index.js ADDED
@@ -0,0 +1,396 @@
1
+ // src/FormProvider.tsx
2
+ import { useState, useMemo, useCallback, useEffect } from "react";
3
+ import { createFormEngine } from "@formos/kernel";
4
+
5
+ // src/FormContext.tsx
6
+ import { createContext } from "react";
7
+ var FormContext = createContext(null);
8
+ FormContext.displayName = "FormContext";
9
+
10
+ // src/FormProvider.tsx
11
+ import { jsx } from "react/jsx-runtime";
12
+ function FormProvider({
13
+ schema,
14
+ initialValues,
15
+ children,
16
+ ...engineOptions
17
+ }) {
18
+ const [version, setVersion] = useState(0);
19
+ const [isSubmitting, setIsSubmitting] = useState(false);
20
+ const engine = useMemo(() => {
21
+ const { onSubmit, ...otherOptions } = engineOptions;
22
+ const wrappedOnSubmit = onSubmit ? async (values) => {
23
+ setIsSubmitting(true);
24
+ try {
25
+ await onSubmit(values);
26
+ } finally {
27
+ setIsSubmitting(false);
28
+ }
29
+ } : void 0;
30
+ return createFormEngine(schema, {
31
+ ...otherOptions,
32
+ onSubmit: wrappedOnSubmit
33
+ });
34
+ }, [schema, engineOptions]);
35
+ useEffect(() => {
36
+ if (initialValues) {
37
+ Object.entries(initialValues).forEach(([fieldName, value]) => {
38
+ engine.setValue(fieldName, value, { silent: true });
39
+ });
40
+ }
41
+ }, []);
42
+ const notifyUpdate = useCallback(() => {
43
+ setVersion((v) => v + 1);
44
+ }, []);
45
+ const contextValue = useMemo(
46
+ () => ({
47
+ engine,
48
+ schema,
49
+ version,
50
+ notifyUpdate,
51
+ isSubmitting
52
+ }),
53
+ [engine, schema, version, notifyUpdate, isSubmitting]
54
+ );
55
+ return /* @__PURE__ */ jsx(FormContext.Provider, { value: contextValue, children });
56
+ }
57
+
58
+ // src/useForm.ts
59
+ import { useCallback as useCallback2, useContext } from "react";
60
+ function useForm() {
61
+ const context = useContext(FormContext);
62
+ if (!context) {
63
+ throw new Error("useForm must be used within a FormProvider");
64
+ }
65
+ const { engine, schema, notifyUpdate } = context;
66
+ const submit = useCallback2(async () => {
67
+ const success = await engine.submit();
68
+ notifyUpdate();
69
+ return success;
70
+ }, [engine, notifyUpdate]);
71
+ const reset = useCallback2(() => {
72
+ engine.reset();
73
+ notifyUpdate();
74
+ }, [engine, notifyUpdate]);
75
+ const validate = useCallback2(
76
+ async (trigger) => {
77
+ const isValid2 = await engine.validate(void 0, trigger);
78
+ notifyUpdate();
79
+ return isValid2;
80
+ },
81
+ [engine, notifyUpdate]
82
+ );
83
+ const getValues = useCallback2(() => {
84
+ const values = {};
85
+ schema.fields.forEach((field) => {
86
+ if (field?.name) {
87
+ values[field.name] = engine.getValue(field.name);
88
+ }
89
+ });
90
+ return values;
91
+ }, [engine, schema]);
92
+ const getValue = useCallback2(
93
+ (fieldName) => {
94
+ return engine.getValue(fieldName);
95
+ },
96
+ [engine]
97
+ );
98
+ const setValue = useCallback2(
99
+ async (fieldName, value) => {
100
+ await engine.setValue(fieldName, value);
101
+ notifyUpdate();
102
+ },
103
+ [engine, notifyUpdate]
104
+ );
105
+ const getErrors = useCallback2(() => {
106
+ return engine.getErrors();
107
+ }, [engine]);
108
+ const getError = useCallback2(
109
+ (fieldName) => {
110
+ return engine.getError(fieldName);
111
+ },
112
+ [engine]
113
+ );
114
+ const setError = useCallback2(
115
+ (fieldName, error) => {
116
+ engine.setError(fieldName, error);
117
+ notifyUpdate();
118
+ },
119
+ [engine, notifyUpdate]
120
+ );
121
+ const clearError = useCallback2(
122
+ (fieldName) => {
123
+ engine.clearError(fieldName);
124
+ notifyUpdate();
125
+ },
126
+ [engine, notifyUpdate]
127
+ );
128
+ const isValid = useCallback2(() => {
129
+ return engine.isValid();
130
+ }, [engine]);
131
+ const isDirty = useCallback2(
132
+ (fieldName) => {
133
+ return engine.isDirty(fieldName);
134
+ },
135
+ [engine]
136
+ );
137
+ const isTouched = useCallback2(
138
+ (fieldName) => {
139
+ return engine.isTouched(fieldName);
140
+ },
141
+ [engine]
142
+ );
143
+ const markTouched = useCallback2(
144
+ (fieldName) => {
145
+ engine.markTouched(fieldName);
146
+ notifyUpdate();
147
+ },
148
+ [engine, notifyUpdate]
149
+ );
150
+ const getCurrentStep = useCallback2(() => {
151
+ return engine.getCurrentStep?.();
152
+ }, [engine]);
153
+ const getTotalSteps = useCallback2(() => {
154
+ return engine.getTotalSteps?.();
155
+ }, [engine]);
156
+ const nextStep = engine.nextStep ? useCallback2(async () => {
157
+ const moved = await engine.nextStep();
158
+ notifyUpdate();
159
+ return moved;
160
+ }, [engine, notifyUpdate]) : void 0;
161
+ const prevStep = engine.prevStep ? useCallback2(() => {
162
+ engine.prevStep();
163
+ notifyUpdate();
164
+ }, [engine, notifyUpdate]) : void 0;
165
+ const goToStep = engine.goToStep ? useCallback2(
166
+ async (stepIndex) => {
167
+ const moved = await engine.goToStep(stepIndex);
168
+ notifyUpdate();
169
+ return moved;
170
+ },
171
+ [engine, notifyUpdate]
172
+ ) : void 0;
173
+ const canGoToStep = engine.canGoToStep ? useCallback2(
174
+ (stepIndex) => {
175
+ return engine.canGoToStep(stepIndex);
176
+ },
177
+ [engine]
178
+ ) : void 0;
179
+ return {
180
+ // Form actions
181
+ submit,
182
+ reset,
183
+ // Validation
184
+ validate,
185
+ // Value access
186
+ getValues,
187
+ getValue,
188
+ setValue,
189
+ // Error access
190
+ getErrors,
191
+ getError,
192
+ setError,
193
+ clearError,
194
+ // State checks
195
+ isValid,
196
+ isDirty,
197
+ isTouched,
198
+ // Field actions
199
+ markTouched,
200
+ // Multi-step navigation
201
+ getCurrentStep,
202
+ getTotalSteps,
203
+ nextStep,
204
+ prevStep,
205
+ goToStep,
206
+ canGoToStep,
207
+ // Advanced (escape hatches)
208
+ engine,
209
+ schema
210
+ };
211
+ }
212
+
213
+ // src/useField.ts
214
+ import { useContext as useContext2, useState as useState2, useCallback as useCallback3, useEffect as useEffect2 } from "react";
215
+ function useField(fieldName, options = {}) {
216
+ const context = useContext2(FormContext);
217
+ if (!context) {
218
+ throw new Error("useField must be used within a FormProvider");
219
+ }
220
+ const { engine, version, notifyUpdate } = context;
221
+ const { validateOnChange = true, validateOnBlur = true } = options;
222
+ const [fieldState, setFieldState] = useState2(() => ({
223
+ value: engine.getValue(fieldName),
224
+ error: engine.getError(fieldName),
225
+ touched: engine.isTouched(fieldName),
226
+ dirty: engine.isDirty(fieldName)
227
+ }));
228
+ useEffect2(() => {
229
+ setFieldState({
230
+ value: engine.getValue(fieldName),
231
+ error: engine.getError(fieldName),
232
+ touched: engine.isTouched(fieldName),
233
+ dirty: engine.isDirty(fieldName)
234
+ });
235
+ }, [engine, fieldName, version]);
236
+ const setValue = useCallback3(
237
+ async (value) => {
238
+ await engine.setValue(fieldName, value, {
239
+ trigger: validateOnChange ? "onChange" : "manual"
240
+ });
241
+ notifyUpdate();
242
+ },
243
+ [engine, fieldName, validateOnChange, notifyUpdate]
244
+ );
245
+ const setError = useCallback3(
246
+ (error) => {
247
+ engine.setError(fieldName, error);
248
+ notifyUpdate();
249
+ },
250
+ [engine, fieldName, notifyUpdate]
251
+ );
252
+ const clearError = useCallback3(() => {
253
+ engine.clearError(fieldName);
254
+ notifyUpdate();
255
+ }, [engine, fieldName, notifyUpdate]);
256
+ const markTouched = useCallback3(async () => {
257
+ engine.markTouched(fieldName);
258
+ if (validateOnBlur) {
259
+ await engine.validate(fieldName, "onBlur");
260
+ }
261
+ notifyUpdate();
262
+ }, [engine, fieldName, validateOnBlur, notifyUpdate]);
263
+ const validate = useCallback3(async () => {
264
+ const isValid = await engine.validate(fieldName, "manual");
265
+ notifyUpdate();
266
+ return isValid;
267
+ }, [engine, fieldName, notifyUpdate]);
268
+ return {
269
+ value: fieldState.value,
270
+ error: fieldState.error,
271
+ touched: fieldState.touched,
272
+ dirty: fieldState.dirty,
273
+ setValue,
274
+ setError,
275
+ clearError,
276
+ markTouched,
277
+ validate
278
+ };
279
+ }
280
+
281
+ // src/useFormState.ts
282
+ import { useContext as useContext3, useState as useState3, useEffect as useEffect3 } from "react";
283
+ function useFormState() {
284
+ const context = useContext3(FormContext);
285
+ if (!context) {
286
+ throw new Error("useFormState must be used within a FormProvider");
287
+ }
288
+ const { engine, schema, version, isSubmitting } = context;
289
+ const [formState, setFormState] = useState3(() => {
290
+ const values = {};
291
+ schema.fields.forEach((field) => {
292
+ if (field?.name) {
293
+ values[field.name] = engine.getValue(field.name);
294
+ }
295
+ });
296
+ return {
297
+ values,
298
+ errors: engine.getErrors(),
299
+ isValid: engine.isValid(),
300
+ isDirty: engine.isDirty(),
301
+ isSubmitting
302
+ };
303
+ });
304
+ useEffect3(() => {
305
+ const values = {};
306
+ schema.fields.forEach((field) => {
307
+ if (field?.name) {
308
+ values[field.name] = engine.getValue(field.name);
309
+ }
310
+ });
311
+ setFormState({
312
+ values,
313
+ errors: engine.getErrors(),
314
+ isValid: engine.isValid(),
315
+ isDirty: engine.isDirty(),
316
+ isSubmitting
317
+ });
318
+ }, [engine, schema, version, isSubmitting]);
319
+ return formState;
320
+ }
321
+
322
+ // src/useStep.ts
323
+ import { useContext as useContext4, useState as useState4, useCallback as useCallback4, useEffect as useEffect4 } from "react";
324
+ function useStep() {
325
+ const context = useContext4(FormContext);
326
+ if (!context) {
327
+ throw new Error("useStep must be used within a FormProvider");
328
+ }
329
+ const { engine, version, notifyUpdate, schema } = context;
330
+ if (!schema.steps || schema.steps.length === 0) {
331
+ throw new Error(
332
+ "useStep can only be used with multi-step forms. Schema must have steps defined."
333
+ );
334
+ }
335
+ const [stepState, setStepState] = useState4(() => ({
336
+ currentStep: engine.getCurrentStep?.() ?? 0,
337
+ totalSteps: engine.getTotalSteps?.() ?? 1
338
+ }));
339
+ useEffect4(() => {
340
+ setStepState({
341
+ currentStep: engine.getCurrentStep?.() ?? 0,
342
+ totalSteps: engine.getTotalSteps?.() ?? 1
343
+ });
344
+ }, [engine, version]);
345
+ const nextStep = useCallback4(async () => {
346
+ if (!engine.nextStep) {
347
+ throw new Error("nextStep is not available on this engine");
348
+ }
349
+ const moved = await engine.nextStep();
350
+ notifyUpdate();
351
+ return moved;
352
+ }, [engine, notifyUpdate]);
353
+ const prevStep = useCallback4(() => {
354
+ if (!engine.prevStep) {
355
+ throw new Error("prevStep is not available on this engine");
356
+ }
357
+ engine.prevStep();
358
+ notifyUpdate();
359
+ }, [engine, notifyUpdate]);
360
+ const goToStep = useCallback4(
361
+ async (stepIndex) => {
362
+ if (!engine.goToStep) {
363
+ throw new Error("goToStep is not available on this engine");
364
+ }
365
+ const moved = await engine.goToStep(stepIndex);
366
+ notifyUpdate();
367
+ return moved;
368
+ },
369
+ [engine, notifyUpdate]
370
+ );
371
+ const canGoToStep = useCallback4(
372
+ (stepIndex) => {
373
+ if (!engine.canGoToStep) {
374
+ return false;
375
+ }
376
+ return engine.canGoToStep(stepIndex);
377
+ },
378
+ [engine]
379
+ );
380
+ return {
381
+ currentStep: stepState.currentStep,
382
+ totalSteps: stepState.totalSteps,
383
+ nextStep,
384
+ prevStep,
385
+ goToStep,
386
+ canGoToStep
387
+ };
388
+ }
389
+ export {
390
+ FormProvider,
391
+ useField,
392
+ useForm,
393
+ useFormState,
394
+ useStep
395
+ };
396
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/FormProvider.tsx","../src/FormContext.tsx","../src/useForm.ts","../src/useField.ts","../src/useFormState.ts","../src/useStep.ts"],"sourcesContent":["/**\r\n * FormProvider component\r\n * Wraps the application and provides form engine to child components\r\n */\r\n\r\nimport { useState, useMemo, useCallback, useEffect } from \"react\";\r\nimport { createFormEngine } from \"@formos/kernel\";\r\nimport { FormContext } from \"./FormContext.js\";\r\nimport type { FormProviderProps } from \"./types.js\";\r\n\r\n/**\r\n * Form provider component that creates and manages a form engine instance.\r\n *\r\n * Wrap your form components with this provider to enable form hooks.\r\n * The provider creates a single form engine instance and makes it available\r\n * via React context.\r\n *\r\n * @public\r\n * @stable\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormProvider } from '@formos/react';\r\n * import { normalizeSchema } from '@formos/schema';\r\n *\r\n * const schema = { version: 'v1', fields: [...] };\r\n * const normalized = normalizeSchema(schema);\r\n *\r\n * function App() {\r\n * return (\r\n * <FormProvider\r\n * schema={normalized}\r\n * validators={{\r\n * required: (value) => value ? null : 'Required'\r\n * }}\r\n * onSubmit={async (values) => {\r\n * await api.post('/submit', values);\r\n * }}\r\n * >\r\n * <MyForm />\r\n * </FormProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormProvider({\r\n schema,\r\n initialValues,\r\n children,\r\n ...engineOptions\r\n}: FormProviderProps) {\r\n // Version counter for forcing re-renders on subscriptions\r\n const [version, setVersion] = useState(0);\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n\r\n // Create form engine instance (memoized)\r\n const engine = useMemo(() => {\r\n const { onSubmit, ...otherOptions } = engineOptions;\r\n\r\n // Wrap onSubmit to track submitting state\r\n const wrappedOnSubmit = onSubmit\r\n ? async (values: Record<string, unknown>) => {\r\n setIsSubmitting(true);\r\n try {\r\n await onSubmit(values);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n }\r\n : undefined;\r\n\r\n return createFormEngine(schema, {\r\n ...otherOptions,\r\n onSubmit: wrappedOnSubmit,\r\n });\r\n }, [schema, engineOptions]);\r\n\r\n // Set initial values on mount\r\n useEffect(() => {\r\n if (initialValues) {\r\n Object.entries(initialValues).forEach(([fieldName, value]) => {\r\n // Use silent to avoid triggering validation/effects on initial load\r\n engine.setValue(fieldName, value, { silent: true });\r\n });\r\n }\r\n }, []); // Only run on mount\r\n\r\n // Callback to notify subscribers of updates\r\n const notifyUpdate = useCallback(() => {\r\n setVersion((v) => v + 1);\r\n }, []);\r\n\r\n // Context value (memoized)\r\n const contextValue = useMemo(\r\n () => ({\r\n engine,\r\n schema,\r\n version,\r\n notifyUpdate,\r\n isSubmitting,\r\n }),\r\n [engine, schema, version, notifyUpdate, isSubmitting],\r\n );\r\n\r\n return (\r\n <FormContext.Provider value={contextValue}>{children}</FormContext.Provider>\r\n );\r\n}\r\n","/**\r\n * Form context for React integration\r\n * Provides FormEngine instance to child components\r\n */\r\n\r\nimport { createContext } from \"react\";\r\nimport type { FormContextValue } from \"./types.js\";\r\n\r\n/**\r\n * React context for form engine instance.\r\n *\r\n * @internal\r\n */\r\nexport const FormContext = createContext<FormContextValue | null>(null);\r\n\r\nFormContext.displayName = \"FormContext\";\r\n","/**\r\n * useForm hook - primary hook for form-level operations\r\n */\r\n\r\nimport { useCallback, useContext } from \"react\";\r\nimport type { UseFormResult } from \"./types.js\";\r\nimport { FormContext } from \"./FormContext.js\";\r\n\r\n/**\r\n * Primary hook for form-level operations.\r\n *\r\n * Provides access to form engine and form-level actions like submit,\r\n * reset, validation, and multi-step navigation. Wraps @formos/kernel\r\n * with React-idiomatic API.\r\n *\r\n * @returns Form operations and state access methods\r\n * @throws {Error} If used outside FormProvider\r\n *\r\n * @public\r\n * @stable\r\n *\r\n * @example\r\n * Single-step form:\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { submit, reset, isValid, isDirty } = useForm();\r\n *\r\n * const handleSubmit = async (e: React.FormEvent) => {\r\n * e.preventDefault();\r\n * const success = await submit();\r\n * if (success) {\r\n * console.log('Login successful');\r\n * }\r\n * };\r\n *\r\n * return (\r\n * <form onSubmit={handleSubmit}>\r\n * <EmailField />\r\n * <PasswordField />\r\n * <button type=\"submit\" disabled={!isValid()}>\r\n * Submit\r\n * </button>\r\n * <button type=\"button\" onClick={reset} disabled={!isDirty()}>\r\n * Reset\r\n * </button>\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * Multi-step form:\r\n * ```tsx\r\n * function SignupWizard() {\r\n * const {\r\n * submit,\r\n * getCurrentStep,\r\n * getTotalSteps,\r\n * nextStep,\r\n * prevStep,\r\n * isValid\r\n * } = useForm();\r\n *\r\n * const currentStep = getCurrentStep() ?? 0;\r\n * const totalSteps = getTotalSteps() ?? 1;\r\n *\r\n * const handleNext = async () => {\r\n * const moved = await nextStep?.();\r\n * if (!moved) {\r\n * alert('Fix errors before proceeding');\r\n * }\r\n * };\r\n *\r\n * return (\r\n * <div>\r\n * <div>Step {currentStep + 1} of {totalSteps}</div>\r\n * <button onClick={prevStep} disabled={currentStep === 0}>\r\n * Previous\r\n * </button>\r\n * <button onClick={handleNext}>\r\n * {currentStep === totalSteps - 1 ? 'Submit' : 'Next'}\r\n * </button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useForm(): UseFormResult {\r\n const context = useContext(FormContext);\r\n\r\n if (!context) {\r\n throw new Error(\"useForm must be used within a FormProvider\");\r\n }\r\n\r\n const { engine, schema, notifyUpdate } = context;\r\n\r\n // =========================================================================\r\n // FORM ACTIONS\r\n // =========================================================================\r\n\r\n /**\r\n * Submit form: validates all fields and calls onSubmit handler if valid.\r\n * Returns true if submission succeeded, false if validation failed.\r\n */\r\n const submit = useCallback(async (): Promise<boolean> => {\r\n const success = await engine.submit();\r\n notifyUpdate(); // Notify subscribers of potential state changes\r\n return success;\r\n }, [engine, notifyUpdate]);\r\n\r\n /**\r\n * Reset form to initial state (values, errors, touched, dirty).\r\n */\r\n const reset = useCallback((): void => {\r\n engine.reset();\r\n notifyUpdate(); // Notify subscribers of state reset\r\n }, [engine, notifyUpdate]);\r\n\r\n // =========================================================================\r\n // VALIDATION\r\n // =========================================================================\r\n\r\n /**\r\n * Validate entire form with optional trigger.\r\n * Useful for manual validation before navigation or submission.\r\n */\r\n const validate = useCallback(\r\n async (\r\n trigger?: \"onChange\" | \"onBlur\" | \"onSubmit\" | \"manual\",\r\n ): Promise<boolean> => {\r\n const isValid = await engine.validate(undefined, trigger);\r\n notifyUpdate(); // Notify subscribers of validation changes\r\n return isValid;\r\n },\r\n [engine, notifyUpdate],\r\n );\r\n\r\n // =========================================================================\r\n // VALUE ACCESS\r\n // =========================================================================\r\n\r\n /**\r\n * Get all form values as object.\r\n * Useful for debugging or passing to external functions.\r\n */\r\n const getValues = useCallback((): Record<string, unknown> => {\r\n const values: Record<string, unknown> = {};\r\n schema.fields.forEach((field) => {\r\n if (field?.name) {\r\n values[field.name] = engine.getValue(field.name);\r\n }\r\n });\r\n return values;\r\n }, [engine, schema]);\r\n\r\n /**\r\n * Get single field value.\r\n * Useful for conditional logic based on other field values.\r\n */\r\n const getValue = useCallback(\r\n (fieldName: string): unknown => {\r\n return engine.getValue(fieldName);\r\n },\r\n [engine],\r\n );\r\n\r\n /**\r\n * Set field value programmatically.\r\n * Triggers validation and effects according to field configuration.\r\n */\r\n const setValue = useCallback(\r\n async (fieldName: string, value: unknown): Promise<void> => {\r\n await engine.setValue(fieldName, value);\r\n notifyUpdate();\r\n },\r\n [engine, notifyUpdate],\r\n );\r\n\r\n // =========================================================================\r\n // ERROR ACCESS\r\n // =========================================================================\r\n\r\n /**\r\n * Get all field errors.\r\n * Useful for error summaries or checking total error count.\r\n */\r\n const getErrors = useCallback((): Record<string, string | undefined> => {\r\n return engine.getErrors();\r\n }, [engine]);\r\n\r\n /**\r\n * Get single field error.\r\n * Useful for displaying errors outside field components.\r\n */\r\n const getError = useCallback(\r\n (fieldName: string): string | undefined => {\r\n return engine.getError(fieldName);\r\n },\r\n [engine],\r\n );\r\n\r\n /**\r\n * Set field error manually (e.g., from server validation).\r\n */\r\n const setError = useCallback(\r\n (fieldName: string, error: string): void => {\r\n engine.setError(fieldName, error);\r\n notifyUpdate();\r\n },\r\n [engine, notifyUpdate],\r\n );\r\n\r\n /**\r\n * Clear field error.\r\n */\r\n const clearError = useCallback(\r\n (fieldName: string): void => {\r\n engine.clearError(fieldName);\r\n notifyUpdate();\r\n },\r\n [engine, notifyUpdate],\r\n );\r\n\r\n // =========================================================================\r\n // STATE CHECKS\r\n // =========================================================================\r\n\r\n /**\r\n * Check if form is valid (has no errors).\r\n * Use for enabling/disabling submit buttons.\r\n */\r\n const isValid = useCallback((): boolean => {\r\n return engine.isValid();\r\n }, [engine]);\r\n\r\n /**\r\n * Check if form or specific field is dirty (changed from initial value).\r\n * Without parameter: checks if ANY field is dirty.\r\n * With parameter: checks if SPECIFIC field is dirty.\r\n */\r\n const isDirty = useCallback(\r\n (fieldName?: string): boolean => {\r\n return engine.isDirty(fieldName);\r\n },\r\n [engine],\r\n );\r\n\r\n /**\r\n * Check if field has been touched (user interacted with it).\r\n * Useful for showing errors only after user interaction.\r\n */\r\n const isTouched = useCallback(\r\n (fieldName: string): boolean => {\r\n return engine.isTouched(fieldName);\r\n },\r\n [engine],\r\n );\r\n\r\n // =========================================================================\r\n // FIELD ACTIONS\r\n // =========================================================================\r\n\r\n /**\r\n * Mark field as touched programmatically.\r\n */\r\n const markTouched = useCallback(\r\n (fieldName: string): void => {\r\n engine.markTouched(fieldName);\r\n notifyUpdate();\r\n },\r\n [engine, notifyUpdate],\r\n );\r\n\r\n // =========================================================================\r\n // MULTI-STEP NAVIGATION\r\n // =========================================================================\r\n\r\n /**\r\n * Get current step index (0-based).\r\n * Returns undefined for single-step forms.\r\n */\r\n const getCurrentStep = useCallback((): number | undefined => {\r\n return engine.getCurrentStep?.();\r\n }, [engine]);\r\n\r\n /**\r\n * Get total number of steps.\r\n * Returns undefined for single-step forms.\r\n */\r\n const getTotalSteps = useCallback((): number | undefined => {\r\n return engine.getTotalSteps?.();\r\n }, [engine]);\r\n\r\n /**\r\n * Go to next step (validates current step first).\r\n * Returns true if moved, false if validation failed.\r\n * Undefined for single-step forms.\r\n */\r\n const nextStep = engine.nextStep\r\n ? useCallback(async (): Promise<boolean> => {\r\n const moved = await engine.nextStep!();\r\n notifyUpdate();\r\n return moved;\r\n }, [engine, notifyUpdate])\r\n : undefined;\r\n\r\n /**\r\n * Go to previous step (no validation).\r\n * Undefined for single-step forms.\r\n */\r\n const prevStep = engine.prevStep\r\n ? useCallback((): void => {\r\n engine.prevStep!();\r\n notifyUpdate();\r\n }, [engine, notifyUpdate])\r\n : undefined;\r\n\r\n /**\r\n * Jump to specific step (validates if moving forward).\r\n * Returns true if moved, false if validation failed.\r\n * Undefined for single-step forms.\r\n */\r\n const goToStep = engine.goToStep\r\n ? useCallback(\r\n async (stepIndex: number): Promise<boolean> => {\r\n const moved = await engine.goToStep!(stepIndex);\r\n notifyUpdate();\r\n return moved;\r\n },\r\n [engine, notifyUpdate],\r\n )\r\n : undefined;\r\n\r\n /**\r\n * Check if can navigate to step.\r\n * Undefined for single-step forms.\r\n */\r\n const canGoToStep = engine.canGoToStep\r\n ? useCallback(\r\n (stepIndex: number): boolean => {\r\n return engine.canGoToStep!(stepIndex);\r\n },\r\n [engine],\r\n )\r\n : undefined;\r\n\r\n // =========================================================================\r\n // RETURN OBJECT\r\n // =========================================================================\r\n\r\n return {\r\n // Form actions\r\n submit,\r\n reset,\r\n\r\n // Validation\r\n validate,\r\n\r\n // Value access\r\n getValues,\r\n getValue,\r\n setValue,\r\n\r\n // Error access\r\n getErrors,\r\n getError,\r\n setError,\r\n clearError,\r\n\r\n // State checks\r\n isValid,\r\n isDirty,\r\n isTouched,\r\n\r\n // Field actions\r\n markTouched,\r\n\r\n // Multi-step navigation\r\n getCurrentStep,\r\n getTotalSteps,\r\n nextStep,\r\n prevStep,\r\n goToStep,\r\n canGoToStep,\r\n\r\n // Advanced (escape hatches)\r\n engine,\r\n schema,\r\n };\r\n}\r\n","/**\r\n * useField hook - field-level operations and state\r\n */\r\n\r\nimport { useContext, useState, useCallback, useEffect } from \"react\";\r\nimport { FormContext } from \"./FormContext.js\";\r\nimport type { UseFieldOptions, UseFieldResult } from \"./types.js\";\r\n\r\n/**\r\n * Hook for field-level operations and state management.\r\n *\r\n * Provides field value, error, touched/dirty state, and actions.\r\n * Automatically subscribes to field changes for reactive updates.\r\n *\r\n * @param fieldName - Name of the field to manage\r\n * @param options - Optional configuration for validation triggers\r\n * @returns Field state and actions\r\n * @throws {Error} If used outside FormProvider\r\n *\r\n * @public\r\n * @stable\r\n *\r\n * @example\r\n * ```tsx\r\n * function EmailField() {\r\n * const {\r\n * value,\r\n * error,\r\n * touched,\r\n * setValue,\r\n * markTouched\r\n * } = useField('email', {\r\n * validateOnChange: true,\r\n * validateOnBlur: true\r\n * });\r\n *\r\n * return (\r\n * <div>\r\n * <input\r\n * type=\"email\"\r\n * value={String(value || '')}\r\n * onChange={(e) => setValue(e.target.value)}\r\n * onBlur={() => markTouched()}\r\n * />\r\n * {touched && error && <span>{error}</span>}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useField(\r\n fieldName: string,\r\n options: UseFieldOptions = {},\r\n): UseFieldResult {\r\n const context = useContext(FormContext);\r\n\r\n if (!context) {\r\n throw new Error(\"useField must be used within a FormProvider\");\r\n }\r\n\r\n const { engine, version, notifyUpdate } = context;\r\n const { validateOnChange = true, validateOnBlur = true } = options;\r\n\r\n // Local state to track field state (subscribed to context version)\r\n const [fieldState, setFieldState] = useState(() => ({\r\n value: engine.getValue(fieldName),\r\n error: engine.getError(fieldName),\r\n touched: engine.isTouched(fieldName),\r\n dirty: engine.isDirty(fieldName),\r\n }));\r\n\r\n // Update local state when context version changes\r\n useEffect(() => {\r\n setFieldState({\r\n value: engine.getValue(fieldName),\r\n error: engine.getError(fieldName),\r\n touched: engine.isTouched(fieldName),\r\n dirty: engine.isDirty(fieldName),\r\n });\r\n }, [engine, fieldName, version]);\r\n\r\n // Set field value\r\n const setValue = useCallback(\r\n async (value: unknown) => {\r\n await engine.setValue(fieldName, value, {\r\n trigger: validateOnChange ? \"onChange\" : \"manual\",\r\n });\r\n notifyUpdate(); // Notify context of change\r\n },\r\n [engine, fieldName, validateOnChange, notifyUpdate],\r\n );\r\n\r\n // Set field error\r\n const setError = useCallback(\r\n (error: string) => {\r\n engine.setError(fieldName, error);\r\n notifyUpdate();\r\n },\r\n [engine, fieldName, notifyUpdate],\r\n );\r\n\r\n // Clear field error\r\n const clearError = useCallback(() => {\r\n engine.clearError(fieldName);\r\n notifyUpdate();\r\n }, [engine, fieldName, notifyUpdate]);\r\n\r\n // Mark field as touched\r\n const markTouched = useCallback(async () => {\r\n engine.markTouched(fieldName);\r\n if (validateOnBlur) {\r\n await engine.validate(fieldName, \"onBlur\");\r\n }\r\n notifyUpdate();\r\n }, [engine, fieldName, validateOnBlur, notifyUpdate]);\r\n\r\n // Validate field\r\n const validate = useCallback(async () => {\r\n const isValid = await engine.validate(fieldName, \"manual\");\r\n notifyUpdate();\r\n return isValid;\r\n }, [engine, fieldName, notifyUpdate]);\r\n\r\n return {\r\n value: fieldState.value,\r\n error: fieldState.error,\r\n touched: fieldState.touched,\r\n dirty: fieldState.dirty,\r\n setValue,\r\n setError,\r\n clearError,\r\n markTouched,\r\n validate,\r\n };\r\n}\r\n","/**\r\n * useFormState hook - observe form-level state\r\n */\r\n\r\nimport { useContext, useState, useEffect } from \"react\";\r\nimport { FormContext } from \"./FormContext.js\";\r\nimport type { UseFormStateResult } from \"./types.js\";\r\n\r\n/**\r\n * Hook for observing form-level state.\r\n *\r\n * Returns reactive state for all form values, errors, and validation status.\r\n * Use this for displaying form-level information like submit button state\r\n * or global error summaries.\r\n *\r\n * @returns Form state object\r\n * @throws {Error} If used outside FormProvider\r\n *\r\n * @public\r\n * @stable\r\n *\r\n * @example\r\n * ```tsx\r\n * function SubmitButton() {\r\n * const { isValid, isDirty, isSubmitting } = useFormState();\r\n *\r\n * return (\r\n * <button\r\n * type=\"submit\"\r\n * disabled={!isValid || !isDirty || isSubmitting}\r\n * >\r\n * {isSubmitting ? 'Submitting...' : 'Submit'}\r\n * </button>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useFormState(): UseFormStateResult {\r\n const context = useContext(FormContext);\r\n\r\n if (!context) {\r\n throw new Error(\"useFormState must be used within a FormProvider\");\r\n }\r\n\r\n const { engine, schema, version, isSubmitting } = context;\r\n\r\n // Compute form state (re-computes when version changes)\r\n const [formState, setFormState] = useState(() => {\r\n const values: Record<string, unknown> = {};\r\n schema.fields.forEach((field) => {\r\n if (field?.name) {\r\n values[field.name] = engine.getValue(field.name);\r\n }\r\n });\r\n\r\n return {\r\n values,\r\n errors: engine.getErrors(),\r\n isValid: engine.isValid(),\r\n isDirty: engine.isDirty(),\r\n isSubmitting,\r\n };\r\n });\r\n\r\n // Update state when version changes\r\n useEffect(() => {\r\n const values: Record<string, unknown> = {};\r\n schema.fields.forEach((field) => {\r\n if (field?.name) {\r\n values[field.name] = engine.getValue(field.name);\r\n }\r\n });\r\n\r\n setFormState({\r\n values,\r\n errors: engine.getErrors(),\r\n isValid: engine.isValid(),\r\n isDirty: engine.isDirty(),\r\n isSubmitting,\r\n });\r\n }, [engine, schema, version, isSubmitting]);\r\n\r\n return formState;\r\n}\r\n","/**\r\n * useStep hook - multi-step form navigation\r\n */\r\n\r\nimport { useContext, useState, useCallback, useEffect } from \"react\";\r\nimport { FormContext } from \"./FormContext.js\";\r\nimport type { UseStepResult } from \"./types.js\";\r\n\r\n/**\r\n * Hook for multi-step form navigation.\r\n *\r\n * Only works with multi-step forms (schemas with `steps` defined).\r\n * Provides current step information and navigation methods.\r\n *\r\n * @returns Step state and navigation methods\r\n * @throws {Error} If used outside FormProvider or in single-step form\r\n *\r\n * @public\r\n * @stable\r\n *\r\n * @example\r\n * ```tsx\r\n * function StepNavigation() {\r\n * const {\r\n * currentStep,\r\n * totalSteps,\r\n * nextStep,\r\n * prevStep,\r\n * canGoToStep\r\n * } = useStep();\r\n *\r\n * const handleNext = async () => {\r\n * const moved = await nextStep();\r\n * if (!moved) {\r\n * alert('Please fix errors before proceeding');\r\n * }\r\n * };\r\n *\r\n * return (\r\n * <div>\r\n * <p>Step {currentStep + 1} of {totalSteps}</p>\r\n * <button onClick={prevStep} disabled={currentStep === 0}>\r\n * Previous\r\n * </button>\r\n * <button onClick={handleNext}>\r\n * {currentStep === totalSteps - 1 ? 'Submit' : 'Next'}\r\n * </button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useStep(): UseStepResult {\r\n const context = useContext(FormContext);\r\n\r\n if (!context) {\r\n throw new Error(\"useStep must be used within a FormProvider\");\r\n }\r\n\r\n const { engine, version, notifyUpdate, schema } = context;\r\n\r\n // Check if this is a multi-step form\r\n if (!schema.steps || schema.steps.length === 0) {\r\n throw new Error(\r\n \"useStep can only be used with multi-step forms. Schema must have steps defined.\",\r\n );\r\n }\r\n\r\n // Track step state\r\n const [stepState, setStepState] = useState(() => ({\r\n currentStep: engine.getCurrentStep?.() ?? 0,\r\n totalSteps: engine.getTotalSteps?.() ?? 1,\r\n }));\r\n\r\n // Update step state when version changes\r\n useEffect(() => {\r\n setStepState({\r\n currentStep: engine.getCurrentStep?.() ?? 0,\r\n totalSteps: engine.getTotalSteps?.() ?? 1,\r\n });\r\n }, [engine, version]);\r\n\r\n // Go to next step\r\n const nextStep = useCallback(async () => {\r\n if (!engine.nextStep) {\r\n throw new Error(\"nextStep is not available on this engine\");\r\n }\r\n\r\n const moved = await engine.nextStep();\r\n notifyUpdate(); // Notify context of step change\r\n return moved;\r\n }, [engine, notifyUpdate]);\r\n\r\n // Go to previous step\r\n const prevStep = useCallback(() => {\r\n if (!engine.prevStep) {\r\n throw new Error(\"prevStep is not available on this engine\");\r\n }\r\n\r\n engine.prevStep();\r\n notifyUpdate();\r\n }, [engine, notifyUpdate]);\r\n\r\n // Jump to specific step\r\n const goToStep = useCallback(\r\n async (stepIndex: number) => {\r\n if (!engine.goToStep) {\r\n throw new Error(\"goToStep is not available on this engine\");\r\n }\r\n\r\n const moved = await engine.goToStep(stepIndex);\r\n notifyUpdate();\r\n return moved;\r\n },\r\n [engine, notifyUpdate],\r\n );\r\n\r\n // Check if can navigate to step\r\n const canGoToStep = useCallback(\r\n (stepIndex: number) => {\r\n if (!engine.canGoToStep) {\r\n return false;\r\n }\r\n\r\n return engine.canGoToStep(stepIndex);\r\n },\r\n [engine],\r\n );\r\n\r\n return {\r\n currentStep: stepState.currentStep,\r\n totalSteps: stepState.totalSteps,\r\n nextStep,\r\n prevStep,\r\n goToStep,\r\n canGoToStep,\r\n };\r\n}\r\n"],"mappings":"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","names":["useCallback","useCallback","isValid","useContext","useState","useCallback","useEffect","useContext","useState","useEffect","useCallback","useContext","useState","useEffect","useContext","useState","useEffect","useContext","useState","useCallback","useEffect","useContext","useState","useEffect","useCallback"]}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@formos/react",
3
+ "version": "0.1.0",
4
+ "description": "React bindings for Formos - Headless form engine hooks",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "sideEffects": false,
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "keywords": [
24
+ "form",
25
+ "react",
26
+ "hooks",
27
+ "form-builder",
28
+ "form-engine",
29
+ "headless",
30
+ "typescript"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/yourusername/formos.git",
35
+ "directory": "packages/react"
36
+ },
37
+ "license": "MIT",
38
+ "peerDependencies": {
39
+ "react": "^18.0.0"
40
+ },
41
+ "dependencies": {
42
+ "@formos/schema": "0.1.0",
43
+ "@formos/kernel": "0.1.0"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/react": "^14.1.2",
47
+ "@types/jest": "^29.5.14",
48
+ "@types/react": "^18.2.47",
49
+ "@jest/globals": "^29.7.0",
50
+ "eslint": "^9.17.0",
51
+ "jest": "^29.7.0",
52
+ "jest-environment-jsdom": "^29.7.0",
53
+ "react": "^18.2.0",
54
+ "react-dom": "^18.2.0",
55
+ "ts-jest": "^29.1.2",
56
+ "tsup": "^8.0.1",
57
+ "typescript": "^5.9.2",
58
+ "@repo/typescript-config": "0.0.0",
59
+ "@repo/eslint-config": "0.0.0"
60
+ },
61
+ "scripts": {
62
+ "build": "tsup",
63
+ "dev": "tsup --watch",
64
+ "lint": "eslint . --max-warnings 0",
65
+ "check-types": "tsc --noEmit",
66
+ "test": "jest",
67
+ "test:watch": "jest --watch",
68
+ "test:coverage": "jest --coverage",
69
+ "publish:npm": "pnpm publish --access public"
70
+ }
71
+ }