@mapples/form 1.0.1
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/README.md +411 -0
- package/dist/components/Form.d.ts +37 -0
- package/dist/components/Form.d.ts.map +1 -0
- package/dist/components/Form.js +111 -0
- package/dist/components/Form.js.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -0
- package/dist/context/FormContext.d.ts +19 -0
- package/dist/context/FormContext.d.ts.map +1 -0
- package/dist/context/FormContext.js +28 -0
- package/dist/context/FormContext.js.map +1 -0
- package/dist/context/index.d.ts +4 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +4 -0
- package/dist/context/index.js.map +1 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +11 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useBindFormField.d.ts +43 -0
- package/dist/hooks/useBindFormField.d.ts.map +1 -0
- package/dist/hooks/useBindFormField.js +93 -0
- package/dist/hooks/useBindFormField.js.map +1 -0
- package/dist/hooks/useFormArrayField.d.ts +43 -0
- package/dist/hooks/useFormArrayField.d.ts.map +1 -0
- package/dist/hooks/useFormArrayField.js +88 -0
- package/dist/hooks/useFormArrayField.js.map +1 -0
- package/dist/hooks/useFormContext.d.ts +28 -0
- package/dist/hooks/useFormContext.d.ts.map +1 -0
- package/dist/hooks/useFormContext.js +32 -0
- package/dist/hooks/useFormContext.js.map +1 -0
- package/dist/hooks/useFormField.d.ts +42 -0
- package/dist/hooks/useFormField.d.ts.map +1 -0
- package/dist/hooks/useFormField.js +79 -0
- package/dist/hooks/useFormField.js.map +1 -0
- package/dist/hooks/useFormFieldToggle.d.ts +33 -0
- package/dist/hooks/useFormFieldToggle.d.ts.map +1 -0
- package/dist/hooks/useFormFieldToggle.js +49 -0
- package/dist/hooks/useFormFieldToggle.js.map +1 -0
- package/dist/hooks/useFormHistory.d.ts +30 -0
- package/dist/hooks/useFormHistory.d.ts.map +1 -0
- package/dist/hooks/useFormHistory.js +57 -0
- package/dist/hooks/useFormHistory.js.map +1 -0
- package/dist/hooks/useFormState.d.ts +34 -0
- package/dist/hooks/useFormState.d.ts.map +1 -0
- package/dist/hooks/useFormState.js +54 -0
- package/dist/hooks/useFormState.js.map +1 -0
- package/dist/hooks/useFormSubmit.d.ts +27 -0
- package/dist/hooks/useFormSubmit.d.ts.map +1 -0
- package/dist/hooks/useFormSubmit.js +31 -0
- package/dist/hooks/useFormSubmit.js.map +1 -0
- package/dist/hooks/useFormValue.d.ts +28 -0
- package/dist/hooks/useFormValue.d.ts.map +1 -0
- package/dist/hooks/useFormValue.js +43 -0
- package/dist/hooks/useFormValue.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/state/index.d.ts +128 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +249 -0
- package/dist/state/index.js.map +1 -0
- package/dist/types/index.d.ts +71 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +40 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +95 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# @mapples/form
|
|
2
|
+
|
|
3
|
+
A comprehensive form management library for React and React Native with TypeScript support, validation, and advanced features like form history management.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Type-safe** - Full TypeScript support with generic types
|
|
8
|
+
- ✅ **Validation** - Yup schema validation integration
|
|
9
|
+
- 📝 **Form History** - Built-in undo/redo functionality
|
|
10
|
+
- 🔄 **Reactive State** - Subscription-based reactive updates
|
|
11
|
+
- 🎣 **Hook-based API** - Comprehensive hooks for form management
|
|
12
|
+
- 🏗️ **Nested Objects** - Support for complex nested data structures
|
|
13
|
+
- 📱 **Cross-platform** - Works with React and React Native
|
|
14
|
+
- 👆 **Touch Tracking** - Built-in field touch state management
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @mapples/form yup
|
|
20
|
+
# or
|
|
21
|
+
yarn add @mapples/form yup
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { Form, useFormField, useFormSubmit } from '@mapples/form';
|
|
28
|
+
import * as yup from 'yup';
|
|
29
|
+
|
|
30
|
+
// Define your form schema
|
|
31
|
+
const userSchema = yup.object({
|
|
32
|
+
name: yup.string().required('Name is required'),
|
|
33
|
+
email: yup.string().email('Invalid email').required('Email is required'),
|
|
34
|
+
age: yup.number().min(18, 'Must be at least 18').required('Age is required'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
interface UserForm {
|
|
38
|
+
name: string;
|
|
39
|
+
email: string;
|
|
40
|
+
age: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Form component
|
|
44
|
+
const UserForm = () => (
|
|
45
|
+
<Form<UserForm>
|
|
46
|
+
initialValues={{ name: '', email: '', age: 0 }}
|
|
47
|
+
validationSchema={userSchema}
|
|
48
|
+
onSubmit={async (values) => {
|
|
49
|
+
console.log('Submitting:', values);
|
|
50
|
+
// Handle form submission
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<NameField />
|
|
54
|
+
<EmailField />
|
|
55
|
+
<AgeField />
|
|
56
|
+
<SubmitButton />
|
|
57
|
+
</Form>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Individual field components
|
|
61
|
+
const NameField = () => {
|
|
62
|
+
const { value, error, isTouched, setValue, touch } = useFormField<string>(
|
|
63
|
+
'name',
|
|
64
|
+
'',
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<input
|
|
70
|
+
value={value}
|
|
71
|
+
onChange={(e) => setValue(e.target.value)}
|
|
72
|
+
onBlur={() => touch('name')}
|
|
73
|
+
placeholder="Enter your name"
|
|
74
|
+
/>
|
|
75
|
+
{isTouched && error && <span className="error">{error}</span>}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const SubmitButton = () => {
|
|
81
|
+
const { submitting, submitForm } = useFormSubmit();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<button onClick={submitForm} disabled={submitting}>
|
|
85
|
+
{submitting ? 'Submitting...' : 'Submit'}
|
|
86
|
+
</button>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API Reference
|
|
92
|
+
|
|
93
|
+
### Components
|
|
94
|
+
|
|
95
|
+
#### `<Form<T>>`
|
|
96
|
+
|
|
97
|
+
The main form component that provides form context to its children.
|
|
98
|
+
|
|
99
|
+
**Props:**
|
|
100
|
+
|
|
101
|
+
- `initialValues: T` - Initial values for the form
|
|
102
|
+
- `validationSchema?: ObjectSchema<T>` - Optional Yup validation schema
|
|
103
|
+
- `onSubmit?: (values: T) => void | Promise<void>` - Form submission handler
|
|
104
|
+
- `ref?: RefObject<FormState<T> | null>` - Optional ref to access FormState directly
|
|
105
|
+
- `historyItems?: number` - Number of history items for undo/redo (0 to disable)
|
|
106
|
+
- `historyItemThrottle?: number` - Throttle for saving history items (default: 5)
|
|
107
|
+
- `filterHistoryKeys?: string[]` - Field paths to exclude from history tracking
|
|
108
|
+
- `onChange?: (values: T) => void` - Callback for form value changes
|
|
109
|
+
|
|
110
|
+
### Hooks
|
|
111
|
+
|
|
112
|
+
#### `useFormContext()`
|
|
113
|
+
|
|
114
|
+
Access the form context containing form state and methods.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
const {
|
|
118
|
+
form,
|
|
119
|
+
submitting,
|
|
120
|
+
submitForm,
|
|
121
|
+
validationErrors,
|
|
122
|
+
validate,
|
|
123
|
+
touch,
|
|
124
|
+
touched,
|
|
125
|
+
} = useFormContext();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `useFormField<T>(fieldPath, fallbackValue?, subscribe?)`
|
|
129
|
+
|
|
130
|
+
Manage a single form field with value, validation, and touch state.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
const { value, error, errors, isTouched, setValue, validate, touch } =
|
|
134
|
+
useFormField<string>('name', '');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
|
|
139
|
+
- `fieldPath: string` - Dot notation path to the field (e.g., 'user.name')
|
|
140
|
+
- `fallbackValue?: T` - Default value if field is undefined
|
|
141
|
+
- `subscribe?: boolean` - Whether to subscribe to real-time changes (default: true)
|
|
142
|
+
|
|
143
|
+
#### `useFormValue<T>(fieldPath?)`
|
|
144
|
+
|
|
145
|
+
Get a reactive value from the form state.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const userName = useFormValue<string>('user.name');
|
|
149
|
+
const allValues = useFormValue<UserForm>(); // Gets all form values
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `useFormState<T>()`
|
|
153
|
+
|
|
154
|
+
Manage the entire form state with reactive values and control methods.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
const { values, setValues, getValues } = useFormState<UserForm>();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `useFormSubmit()`
|
|
161
|
+
|
|
162
|
+
Access form submission state and submit function.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
const { submitting, submitForm } = useFormSubmit();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### `useFormHistory()`
|
|
169
|
+
|
|
170
|
+
Manage form history for undo/redo functionality.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
const { prev, next, canPrev, canNext } = useFormHistory();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `useFormArrayField<T>(fieldPath, fallbackValue?)`
|
|
177
|
+
|
|
178
|
+
Manage array fields with specialized array manipulation methods.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
const { value, api } = useFormArrayField<string>('tags', []);
|
|
182
|
+
|
|
183
|
+
// Available methods:
|
|
184
|
+
// api.push(item) - Add item to end
|
|
185
|
+
// api.remove(index) - Remove item at index
|
|
186
|
+
// api.paste(item, index) - Insert item at index
|
|
187
|
+
// api.dropLeft(count?) - Remove items from start
|
|
188
|
+
// api.dropRight(count?) - Remove items from end
|
|
189
|
+
// api.toggle(item) - Toggle item presence
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `useFormFieldToggle(fieldPath)`
|
|
193
|
+
|
|
194
|
+
Manage boolean toggle fields.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
const { value, toggleValue } = useFormFieldToggle('isActive');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `useBindFormField<T>(fieldPath, fallBackPath?, fallbackValue?, subscribe?)`
|
|
201
|
+
|
|
202
|
+
Bind a form field with optional fallback path synchronization.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
const { value, error, setValue } = useBindFormField<string>(
|
|
206
|
+
'user.name',
|
|
207
|
+
'displayName', // Also updates displayName when user.name changes
|
|
208
|
+
'',
|
|
209
|
+
);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### FormState Class
|
|
213
|
+
|
|
214
|
+
For advanced usage, you can access the FormState instance directly:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
const formRef = useRef<FormState<UserForm>>(null);
|
|
218
|
+
|
|
219
|
+
<Form ref={formRef} initialValues={initialValues}>
|
|
220
|
+
{/* form content */}
|
|
221
|
+
</Form>;
|
|
222
|
+
|
|
223
|
+
// Access FormState methods
|
|
224
|
+
formRef.current?.getValues();
|
|
225
|
+
formRef.current?.setValue('name', 'John');
|
|
226
|
+
formRef.current?.validate();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**FormState Methods:**
|
|
230
|
+
|
|
231
|
+
- `getValues()` - Get all form values
|
|
232
|
+
- `setValues(values)` - Set all form values
|
|
233
|
+
- `getValue<V>(path?)` - Get value at specific path
|
|
234
|
+
- `setValue<V>(path, value)` - Set value at specific path
|
|
235
|
+
- `validate()` - Validate form using schema
|
|
236
|
+
- `subscribeOnChange<V>(callback, path?)` - Subscribe to value changes
|
|
237
|
+
- `subscribeOnHistoryChange(callback)` - Subscribe to history changes
|
|
238
|
+
- `prev()` - Move to previous history state (undo)
|
|
239
|
+
- `next()` - Move to next history state (redo)
|
|
240
|
+
- `canPrev()` - Check if undo is possible
|
|
241
|
+
- `canNext()` - Check if redo is possible
|
|
242
|
+
|
|
243
|
+
### Utility Functions
|
|
244
|
+
|
|
245
|
+
#### `objectDiff<T>(obj1, obj2, excludeKeys?)`
|
|
246
|
+
|
|
247
|
+
Compare two objects and return differences.
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
const diff = objectDiff(oldValues, newValues, ['timestamp']);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### `isEmpty(obj)`
|
|
254
|
+
|
|
255
|
+
Check if an object is empty.
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
const isFormEmpty = isEmpty(formValues);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Advanced Examples
|
|
262
|
+
|
|
263
|
+
### Nested Objects
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
interface UserForm {
|
|
267
|
+
personal: {
|
|
268
|
+
name: string;
|
|
269
|
+
email: string;
|
|
270
|
+
};
|
|
271
|
+
address: {
|
|
272
|
+
street: string;
|
|
273
|
+
city: string;
|
|
274
|
+
country: string;
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const PersonalInfo = () => {
|
|
279
|
+
const { value, setValue } = useFormField<string>('personal.name', '');
|
|
280
|
+
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const AddressField = () => {
|
|
284
|
+
const { value, setValue } = useFormField<string>('address.street', '');
|
|
285
|
+
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
|
286
|
+
};
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Form History Management
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
const FormWithHistory = () => (
|
|
293
|
+
<Form
|
|
294
|
+
initialValues={initialValues}
|
|
295
|
+
historyItems={50} // Keep 50 history items
|
|
296
|
+
historyItemThrottle={3} // Save every 3rd change
|
|
297
|
+
filterHistoryKeys={['timestamp']} // Don't track timestamp changes
|
|
298
|
+
>
|
|
299
|
+
<FormFields />
|
|
300
|
+
<HistoryControls />
|
|
301
|
+
</Form>
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const HistoryControls = () => {
|
|
305
|
+
const { prev, next, canPrev, canNext } = useFormHistory();
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div>
|
|
309
|
+
<button onClick={prev} disabled={!canPrev}>
|
|
310
|
+
Undo
|
|
311
|
+
</button>
|
|
312
|
+
<button onClick={next} disabled={!canNext}>
|
|
313
|
+
Redo
|
|
314
|
+
</button>
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Array Field Management
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
const TagsField = () => {
|
|
324
|
+
const { value, api } = useFormArrayField<string>('tags', []);
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<div>
|
|
328
|
+
{value.map((tag, index) => (
|
|
329
|
+
<div key={index}>
|
|
330
|
+
<span>{tag}</span>
|
|
331
|
+
<button onClick={() => api.remove(index)}>Remove</button>
|
|
332
|
+
</div>
|
|
333
|
+
))}
|
|
334
|
+
<input
|
|
335
|
+
onKeyPress={(e) => {
|
|
336
|
+
if (e.key === 'Enter') {
|
|
337
|
+
api.push(e.target.value);
|
|
338
|
+
e.target.value = '';
|
|
339
|
+
}
|
|
340
|
+
}}
|
|
341
|
+
placeholder="Add tag"
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Custom Validation
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
const customSchema = yup.object({
|
|
352
|
+
password: yup.string().required('Password is required'),
|
|
353
|
+
confirmPassword: yup
|
|
354
|
+
.string()
|
|
355
|
+
.oneOf([yup.ref('password')], 'Passwords must match')
|
|
356
|
+
.required('Confirm password is required'),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const PasswordField = () => {
|
|
360
|
+
const { value, error, setValue, touch } = useFormField<string>(
|
|
361
|
+
'password',
|
|
362
|
+
'',
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<div>
|
|
367
|
+
<input
|
|
368
|
+
type="password"
|
|
369
|
+
value={value}
|
|
370
|
+
onChange={(e) => setValue(e.target.value)}
|
|
371
|
+
onBlur={() => touch('password')}
|
|
372
|
+
/>
|
|
373
|
+
{error && <span className="error">{error}</span>}
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
};
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## TypeScript Support
|
|
380
|
+
|
|
381
|
+
The library is fully typed with TypeScript. All hooks and components are generic and provide full type safety:
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
interface MyFormData {
|
|
385
|
+
name: string;
|
|
386
|
+
age: number;
|
|
387
|
+
preferences: {
|
|
388
|
+
theme: 'light' | 'dark';
|
|
389
|
+
notifications: boolean;
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Type-safe form
|
|
394
|
+
<Form<MyFormData> initialValues={initialValues}>
|
|
395
|
+
{/* Type-safe field access */}
|
|
396
|
+
<MyFormFields />
|
|
397
|
+
</Form>;
|
|
398
|
+
|
|
399
|
+
// Type-safe field hooks
|
|
400
|
+
const { value } = useFormField<string>('name'); // value is typed as string
|
|
401
|
+
const { value: age } = useFormField<number>('age'); // value is typed as number
|
|
402
|
+
const { value: theme } = useFormField<'light' | 'dark'>('preferences.theme');
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## License
|
|
406
|
+
|
|
407
|
+
MIT
|
|
408
|
+
|
|
409
|
+
## Contributing
|
|
410
|
+
|
|
411
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { FormProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Form component that provides form state management, validation, and submission functionality.
|
|
5
|
+
* It creates a FormState instance and provides it to child components through React Context.
|
|
6
|
+
*
|
|
7
|
+
* @template T - The type of the form data object
|
|
8
|
+
* @param props - Form component props
|
|
9
|
+
* @returns JSX element that provides form context to children
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* interface UserForm {
|
|
14
|
+
* name: string;
|
|
15
|
+
* email: string;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* const MyForm = () => (
|
|
19
|
+
* <Form<UserForm>
|
|
20
|
+
* initialValues={{ name: '', email: '' }}
|
|
21
|
+
* validationSchema={userSchema}
|
|
22
|
+
* onSubmit={async (values) => {
|
|
23
|
+
* await saveUser(values);
|
|
24
|
+
* }}
|
|
25
|
+
* >
|
|
26
|
+
* <FormField name="name" />
|
|
27
|
+
* <FormField name="email" />
|
|
28
|
+
* <SubmitButton />
|
|
29
|
+
* </Form>
|
|
30
|
+
* );
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
declare const Form: <T extends object>({ children, initialValues, validationSchema, onSubmit, ref, historyItems, filterHistoryKeys, historyItemThrottle, onChange, }: PropsWithChildren<FormProps<T>>) => import("react").JSX.Element;
|
|
36
|
+
export default Form;
|
|
37
|
+
//# sourceMappingURL=Form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../src/components/Form.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EAMlB,MAAM,OAAO,CAAC;AAOf,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,QAAA,MAAM,IAAI,GAAI,CAAC,SAAS,MAAM,EAAE,+HAU7B,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,gCAkGjC,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useCallback, useEffect, useImperativeHandle, useRef, useState, } from 'react';
|
|
2
|
+
import { mapValues } from 'lodash';
|
|
3
|
+
import { FormContextProvider } from '../context';
|
|
4
|
+
import FormState from '../state';
|
|
5
|
+
/**
|
|
6
|
+
* Form component that provides form state management, validation, and submission functionality.
|
|
7
|
+
* It creates a FormState instance and provides it to child components through React Context.
|
|
8
|
+
*
|
|
9
|
+
* @template T - The type of the form data object
|
|
10
|
+
* @param props - Form component props
|
|
11
|
+
* @returns JSX element that provides form context to children
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* interface UserForm {
|
|
16
|
+
* name: string;
|
|
17
|
+
* email: string;
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* const MyForm = () => (
|
|
21
|
+
* <Form<UserForm>
|
|
22
|
+
* initialValues={{ name: '', email: '' }}
|
|
23
|
+
* validationSchema={userSchema}
|
|
24
|
+
* onSubmit={async (values) => {
|
|
25
|
+
* await saveUser(values);
|
|
26
|
+
* }}
|
|
27
|
+
* >
|
|
28
|
+
* <FormField name="name" />
|
|
29
|
+
* <FormField name="email" />
|
|
30
|
+
* <SubmitButton />
|
|
31
|
+
* </Form>
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
const Form = ({ children, initialValues, validationSchema, onSubmit, ref, historyItems, filterHistoryKeys, historyItemThrottle, onChange, }) => {
|
|
38
|
+
const [touched, setTouched] = useState({});
|
|
39
|
+
const [submitting, setSubmitting] = useState(false);
|
|
40
|
+
const [validationErrors, setValidationErrors] = useState(null);
|
|
41
|
+
const form = useRef(new FormState(initialValues, validationSchema, historyItems, historyItemThrottle, filterHistoryKeys)).current;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!onChange)
|
|
44
|
+
return;
|
|
45
|
+
const unsubscribe = form.subscribeOnChange(() => {
|
|
46
|
+
onChange(form.getValues());
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
unsubscribe();
|
|
50
|
+
};
|
|
51
|
+
}, [form, onChange]);
|
|
52
|
+
useImperativeHandle(ref, () => {
|
|
53
|
+
return form;
|
|
54
|
+
});
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
form.setValues(initialValues);
|
|
57
|
+
}, [form, initialValues]);
|
|
58
|
+
const setFormTouched = useCallback(() => {
|
|
59
|
+
setTouched(mapValues(initialValues, () => true));
|
|
60
|
+
}, [initialValues]);
|
|
61
|
+
const handleSubmit = useCallback(async () => {
|
|
62
|
+
setSubmitting(true);
|
|
63
|
+
setFormTouched();
|
|
64
|
+
try {
|
|
65
|
+
await form.validate();
|
|
66
|
+
setValidationErrors(null);
|
|
67
|
+
}
|
|
68
|
+
catch (errors) {
|
|
69
|
+
setValidationErrors(errors);
|
|
70
|
+
setSubmitting(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await onSubmit?.(form.getValues());
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
console.error(e);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
setSubmitting(false);
|
|
81
|
+
}
|
|
82
|
+
}, [form, onSubmit, setFormTouched]);
|
|
83
|
+
const validate = useCallback(async () => {
|
|
84
|
+
try {
|
|
85
|
+
await form.validate();
|
|
86
|
+
setValidationErrors(null);
|
|
87
|
+
}
|
|
88
|
+
catch (errors) {
|
|
89
|
+
setValidationErrors(errors);
|
|
90
|
+
}
|
|
91
|
+
}, [form]);
|
|
92
|
+
const touch = useCallback((fieldName) => {
|
|
93
|
+
setTouched((prev) => ({
|
|
94
|
+
...prev,
|
|
95
|
+
[fieldName]: true,
|
|
96
|
+
}));
|
|
97
|
+
}, []);
|
|
98
|
+
return (<FormContextProvider value={{
|
|
99
|
+
touched,
|
|
100
|
+
form: form,
|
|
101
|
+
submitForm: handleSubmit,
|
|
102
|
+
submitting,
|
|
103
|
+
validationErrors,
|
|
104
|
+
validate,
|
|
105
|
+
touch,
|
|
106
|
+
}}>
|
|
107
|
+
{children}
|
|
108
|
+
</FormContextProvider>);
|
|
109
|
+
};
|
|
110
|
+
export default Form;
|
|
111
|
+
//# sourceMappingURL=Form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Form.js","sourceRoot":"","sources":["../../src/components/Form.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,QAAQ,CAAC;AAE1B,OAAO,EAAE,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,QAAQ,CAAC;AAE1B,OAAO,EAAE,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FormContextType } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* React Context for form state management.
|
|
4
|
+
* This context provides form state, validation errors, submission state,
|
|
5
|
+
* and form control methods to child components.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
declare const FormContext: import("react").Context<FormContextType<object>>;
|
|
10
|
+
/**
|
|
11
|
+
* Provider component for the FormContext.
|
|
12
|
+
* This component should be used by the Form component to provide
|
|
13
|
+
* form state to its children.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export declare const FormContextProvider: import("react").Provider<FormContextType<object>>;
|
|
18
|
+
export default FormContext;
|
|
19
|
+
//# sourceMappingURL=FormContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormContext.d.ts","sourceRoot":"","sources":["../../src/context/FormContext.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;;;;GAMG;AACH,QAAA,MAAM,WAAW,kDAQf,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,mDAAuB,CAAC;AAExD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import { noop } from 'lodash';
|
|
3
|
+
/**
|
|
4
|
+
* React Context for form state management.
|
|
5
|
+
* This context provides form state, validation errors, submission state,
|
|
6
|
+
* and form control methods to child components.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
const FormContext = createContext({
|
|
11
|
+
form: null,
|
|
12
|
+
validationErrors: null,
|
|
13
|
+
submitting: true,
|
|
14
|
+
submitForm: noop,
|
|
15
|
+
validate: noop,
|
|
16
|
+
touch: noop,
|
|
17
|
+
touched: {},
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Provider component for the FormContext.
|
|
21
|
+
* This component should be used by the Form component to provide
|
|
22
|
+
* form state to its children.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export const FormContextProvider = FormContext.Provider;
|
|
27
|
+
export default FormContext;
|
|
28
|
+
//# sourceMappingURL=FormContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormContext.js","sourceRoot":"","sources":["../../src/context/FormContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAI9B;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,aAAa,CAA0B;IACzD,IAAI,EAAE,IAAI;IACV,gBAAgB,EAAE,IAAI;IACtB,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;IAChB,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,EAAE;CACZ,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC,QAAQ,CAAC;AAExD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,eAAe,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,eAAe,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import useBindFormField from './useBindFormField';
|
|
2
|
+
import useFormArrayField from './useFormArrayField';
|
|
3
|
+
import useFormContext from './useFormContext';
|
|
4
|
+
import useFormField from './useFormField';
|
|
5
|
+
import useFormFieldToggle from './useFormFieldToggle';
|
|
6
|
+
import useFormHistory from './useFormHistory';
|
|
7
|
+
import useFormState from './useFormState';
|
|
8
|
+
import useFormSubmit from './useFormSubmit';
|
|
9
|
+
import useFormValue from './useFormValue';
|
|
10
|
+
export { useFormField, useFormState, useFormValue, useFormContext, useFormArrayField, useFormFieldToggle, useFormHistory, useFormSubmit, useBindFormField, };
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAClD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import useBindFormField from './useBindFormField';
|
|
2
|
+
import useFormArrayField from './useFormArrayField';
|
|
3
|
+
import useFormContext from './useFormContext';
|
|
4
|
+
import useFormField from './useFormField';
|
|
5
|
+
import useFormFieldToggle from './useFormFieldToggle';
|
|
6
|
+
import useFormHistory from './useFormHistory';
|
|
7
|
+
import useFormState from './useFormState';
|
|
8
|
+
import useFormSubmit from './useFormSubmit';
|
|
9
|
+
import useFormValue from './useFormValue';
|
|
10
|
+
export { useFormField, useFormState, useFormValue, useFormContext, useFormArrayField, useFormFieldToggle, useFormHistory, useFormSubmit, useBindFormField, };
|
|
11
|
+
//# sourceMappingURL=index.js.map
|