@juantroconisf/lib 9.1.0 → 9.3.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/README.md +236 -156
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +75 -44
- package/dist/index.mjs +75 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @juantroconisf/lib
|
|
2
2
|
|
|
3
|
-
A powerful, type-safe form management and validation library
|
|
3
|
+
A powerful, type-safe form management and validation library tailored for **HeroUI** and **React**.
|
|
4
4
|
|
|
5
|
-
Designed for complex applications, it provides **O(1) value updates**, stable **ID-based array management**, and deep nesting support, all
|
|
5
|
+
Designed for complex applications, it provides **O(1) value updates**, stable **ID-based array management**, and deep nesting support, all seamlessly integrated with **Yup** schemas.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,30 +10,31 @@ Designed for complex applications, it provides **O(1) value updates**, stable **
|
|
|
10
10
|
|
|
11
11
|
- [Features](#features)
|
|
12
12
|
- [Installation](#installation)
|
|
13
|
+
- [Localization](#localization)
|
|
13
14
|
- [Quick Start](#quick-start)
|
|
14
15
|
- [Usage Guide](#usage-guide)
|
|
15
16
|
- [Scalar Fields](#scalar-fields)
|
|
16
17
|
- [Nested Objects](#nested-objects)
|
|
17
18
|
- [Managing Arrays](#managing-arrays)
|
|
18
|
-
- [
|
|
19
|
-
- [Submitting Forms](#submitting-forms)
|
|
19
|
+
- [Form Submission](#form-submission)
|
|
20
20
|
- [API Reference](#api-reference)
|
|
21
21
|
- [useForm](#useform)
|
|
22
|
-
- [The `on`
|
|
22
|
+
- [The `on` API](#the-on-api)
|
|
23
|
+
- [ControlledForm](#controlledform)
|
|
23
24
|
- [Array Helpers](#array-helpers)
|
|
24
|
-
- [
|
|
25
|
+
- [Form Controls](#form-controls)
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
28
29
|
## Features
|
|
29
30
|
|
|
30
|
-
- 🎯 **Polymorphic `on` API**:
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
31
|
+
- 🎯 **Polymorphic `on` API**: A unified interface (`on.input`, `on.select`, `on.autocomplete`) saving lines of boilerplate.
|
|
32
|
+
- 🧱 **ControlledForm Component**: Zero-boilerplate validated submissions leveraging the `@heroui/react` Form component directly.
|
|
33
|
+
- 🧩 **Deep Nesting**: Dot-notation support (e.g., `settings.theme`) out of the box.
|
|
34
|
+
- 🔢 **ID-Based Arrays**: List items are tracked by unique identifiers (default: `id`), ensuring states survive mapping, reordering, and deletions.
|
|
35
|
+
- ⚡ **O(1) Performance**: Internal mapping for array updates offers incredible speed regardless of data density.
|
|
36
|
+
- 🛡️ **Complete Type Safety**: TypeScript infers all acceptable paths, data structures, and outputs natively.
|
|
37
|
+
- 🌍 **Built-in Localization**: Ships with English and Spanish validation translations via runtime cookies.
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
@@ -45,69 +46,107 @@ pnpm add @juantroconisf/lib yup
|
|
|
45
46
|
|
|
46
47
|
---
|
|
47
48
|
|
|
49
|
+
## Localization
|
|
50
|
+
|
|
51
|
+
Validation strings (e.g., minimum length, required fields) are localized automatically. The library checks for a browser cookie named `LOCALE` on initialization.
|
|
52
|
+
|
|
53
|
+
- **Supported Locales**: `en` (English - default) and `es` (Spanish).
|
|
54
|
+
- **How to Set**:
|
|
55
|
+
Set the cookie in your client application:
|
|
56
|
+
```ts
|
|
57
|
+
document.cookie = "LOCALE=es; path=/; max-age=31536000"; // Sets language to Spanish
|
|
58
|
+
```
|
|
59
|
+
If no cookie is detected, or the value is unrecognized, the library gracefully falls back to English (`en`).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
48
63
|
## Quick Start
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
Throughout this documentation, we will refer to a common, diverse schema. It includes simple scalars, deep objects, and complex arrays.
|
|
51
66
|
|
|
52
67
|
```tsx
|
|
53
68
|
import { useForm } from "@juantroconisf/lib";
|
|
54
|
-
import { string, number, array, object } from "yup";
|
|
55
|
-
import { Input, Button } from "@heroui/react";
|
|
69
|
+
import { string, number, array, object, boolean } from "yup";
|
|
70
|
+
import { Input, Switch, Select, SelectItem, Button } from "@heroui/react";
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
|
|
72
|
+
// 1. Define your diverse schema structure
|
|
73
|
+
const mySchema = {
|
|
74
|
+
fullName: string().required("Name is required").default(""),
|
|
75
|
+
age: number().min(18).default(18),
|
|
76
|
+
settings: object({
|
|
77
|
+
theme: string().oneOf(["light", "dark"]).default("dark"),
|
|
78
|
+
notifications: boolean().default(true),
|
|
79
|
+
}),
|
|
80
|
+
users: array()
|
|
81
|
+
.of(
|
|
82
|
+
object().shape({
|
|
83
|
+
id: number().required(),
|
|
84
|
+
role: string().required(),
|
|
85
|
+
}),
|
|
86
|
+
)
|
|
87
|
+
.default([{ id: Date.now(), role: "admin" }]),
|
|
88
|
+
};
|
|
69
89
|
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
const MyForm = () => {
|
|
91
|
+
// 2. Initialize the hook with fully inferred types
|
|
92
|
+
const { on, state, helpers, ControlledForm } = useForm(mySchema);
|
|
93
|
+
|
|
94
|
+
// 3. Types are perfectly inferred from your Yup schema
|
|
95
|
+
const handleSubmit = (data, event) => {
|
|
96
|
+
console.log("Submitted payload:", data);
|
|
97
|
+
// data.fullName -> string
|
|
98
|
+
// data.settings.theme -> "light" | "dark"
|
|
99
|
+
// data.users[0].role -> string
|
|
72
100
|
};
|
|
73
101
|
|
|
74
102
|
return (
|
|
75
|
-
<
|
|
76
|
-
{/*
|
|
77
|
-
<Input {...on.input("
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
<ControlledForm onSubmit={handleSubmit} className='flex flex-col gap-4'>
|
|
104
|
+
{/* Scalar Fields */}
|
|
105
|
+
<Input {...on.input("fullName")} label='Full Name' />
|
|
106
|
+
<Input {...on.input("age")} type='number' label='Age' />
|
|
107
|
+
|
|
108
|
+
{/* Nested Object Fields */}
|
|
109
|
+
<Switch {...on.input("settings.notifications")}>
|
|
110
|
+
Enable Notifications
|
|
111
|
+
</Switch>
|
|
112
|
+
<Select {...on.select("settings.theme")} label='Theme'>
|
|
113
|
+
<SelectItem key='light'>Light</SelectItem>
|
|
114
|
+
<SelectItem key='dark'>Dark</SelectItem>
|
|
115
|
+
</Select>
|
|
116
|
+
|
|
117
|
+
{/* Array Fields */}
|
|
118
|
+
<div className='flex flex-col gap-2'>
|
|
119
|
+
<h3>Users</h3>
|
|
120
|
+
{state.users.map((user) => (
|
|
121
|
+
<div key={user.id} className='flex gap-2 items-center'>
|
|
122
|
+
{/* Bind by Array Path + Item ID */}
|
|
123
|
+
<Select {...on.select("users.role", user.id)} label='Role'>
|
|
124
|
+
<SelectItem key='admin'>Admin</SelectItem>
|
|
125
|
+
<SelectItem key='guest'>Guest</SelectItem>
|
|
126
|
+
</Select>
|
|
127
|
+
<Button
|
|
128
|
+
color='danger'
|
|
129
|
+
onPress={() => helpers.removeById("users", user.id)}
|
|
130
|
+
>
|
|
131
|
+
Remove
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div className='flex gap-2'>
|
|
103
138
|
<Button
|
|
104
|
-
|
|
139
|
+
onPress={() =>
|
|
140
|
+
helpers.addItem("users", { id: Date.now(), role: "guest" })
|
|
141
|
+
}
|
|
105
142
|
>
|
|
106
|
-
Add
|
|
143
|
+
Add User
|
|
144
|
+
</Button>
|
|
145
|
+
<Button type='submit' color='primary'>
|
|
146
|
+
Submit
|
|
107
147
|
</Button>
|
|
108
|
-
<Button type='submit' color="primary">Submit</Button>
|
|
109
148
|
</div>
|
|
110
|
-
</
|
|
149
|
+
</ControlledForm>
|
|
111
150
|
);
|
|
112
151
|
};
|
|
113
152
|
```
|
|
@@ -116,86 +155,122 @@ const MyForm = () => {
|
|
|
116
155
|
|
|
117
156
|
## Usage Guide
|
|
118
157
|
|
|
158
|
+
All examples below continue referencing the `mySchema` definition from the Quick Start.
|
|
159
|
+
|
|
119
160
|
### Scalar Fields
|
|
120
161
|
|
|
121
|
-
Scalar fields
|
|
162
|
+
Scalar fields map directly to your schema definitions. Use `on.input`, `on.select`, or `on.autocomplete` to yield binding props instantly compatible with HeroUI.
|
|
122
163
|
|
|
123
164
|
```tsx
|
|
124
165
|
<Input {...on.input("fullName")} label="Full Name" />
|
|
125
|
-
<Input {...on.input("
|
|
166
|
+
<Input {...on.input("age")} type="number" label="Age" />
|
|
126
167
|
```
|
|
127
168
|
|
|
128
169
|
### Nested Objects
|
|
129
170
|
|
|
130
|
-
|
|
171
|
+
Nested state is managed intuitively via dot-notation directly on your identifiers. Your TypeScript types will validate that the path points to real endpoints on your data shape.
|
|
131
172
|
|
|
132
173
|
```tsx
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<Input {...on.input("settings.theme.mode")} />
|
|
174
|
+
// Binds seamlessly to state.settings.theme
|
|
175
|
+
<Select {...on.select("settings.theme")} label="Theme">
|
|
176
|
+
<SelectItem key="light">Light</SelectItem>
|
|
177
|
+
<SelectItem key="dark">Dark</SelectItem>
|
|
178
|
+
</Select>
|
|
179
|
+
|
|
180
|
+
// Booleans map smoothly to Switches/Checkboxes
|
|
181
|
+
<Switch {...on.input("settings.notifications")}>Enable Alerts</Switch>
|
|
142
182
|
```
|
|
143
183
|
|
|
144
184
|
### Managing Arrays
|
|
145
185
|
|
|
146
|
-
|
|
186
|
+
Array management is uniquely built around tracking entries by ID (`'id'` field by default). This stops issues where UI inputs lose state references if the array gets shifted.
|
|
147
187
|
|
|
148
|
-
**
|
|
188
|
+
**Binding UI to Items:**
|
|
189
|
+
Combine the general array path and the item ID.
|
|
149
190
|
|
|
150
|
-
|
|
191
|
+
```tsx
|
|
192
|
+
// ✅ Correct: Binds perfectly regardless of list index changes
|
|
193
|
+
on.select("users.role", userId);
|
|
194
|
+
|
|
195
|
+
// ❌ Warning (Unless primitives array): Prevents index-shifting stability
|
|
196
|
+
on.select("users", 0);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Using the `helpers` Library:**
|
|
200
|
+
Extract `helpers` out of `useForm(...)` to manipulate array state securely.
|
|
151
201
|
|
|
152
202
|
```tsx
|
|
153
|
-
|
|
154
|
-
|
|
203
|
+
const { helpers } = useForm(mySchema);
|
|
204
|
+
|
|
205
|
+
// Add an item (Optionally provide index as a 3rd argument to insert)
|
|
206
|
+
helpers.addItem("users", { id: Date.now(), role: "guest" });
|
|
207
|
+
|
|
208
|
+
// Remove item by ID
|
|
209
|
+
helpers.removeById("users", 12345);
|
|
155
210
|
|
|
156
|
-
//
|
|
157
|
-
|
|
211
|
+
// Reorder items by ID
|
|
212
|
+
helpers.moveById("users", fromId, toId);
|
|
158
213
|
```
|
|
159
214
|
|
|
160
|
-
|
|
215
|
+
### Form Submission
|
|
161
216
|
|
|
162
|
-
|
|
217
|
+
There are two primary ways to submit, validate, and handle your logic, both fully type-safe.
|
|
163
218
|
|
|
164
|
-
|
|
165
|
-
// Add
|
|
166
|
-
helpers.addItem("users", { id: "new-id", name: "" });
|
|
219
|
+
**1. The `ControlledForm` Wrapper (Recommended)**
|
|
167
220
|
|
|
168
|
-
|
|
169
|
-
helpers.removeById("users", "user-id-123");
|
|
221
|
+
`ControlledForm` behaves exactly like a HeroUI `Form` component. It automatically intercepts submit events, validates all bindings through your schema, and invokes `onSubmit` with the validated dataset.
|
|
170
222
|
|
|
171
|
-
|
|
172
|
-
|
|
223
|
+
```tsx
|
|
224
|
+
const { ControlledForm } = useForm(mySchema);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<ControlledForm onSubmit={(data, event) => console.log(data.fullName)}>
|
|
228
|
+
<Input {...on.input("fullName")} />
|
|
229
|
+
<Button type='submit'>Submit</Button>
|
|
230
|
+
</ControlledForm>
|
|
231
|
+
);
|
|
173
232
|
```
|
|
174
233
|
|
|
175
|
-
|
|
234
|
+
**2. The `onFormSubmit` Handler Wrapper**
|
|
176
235
|
|
|
177
|
-
|
|
236
|
+
If you prefer using native HTML `<form>` tags, you can pass your callback into `onFormSubmit`. The params exposed to your callback are tightly inferred from the schema.
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
const { onFormSubmit, state } = useForm(mySchema);
|
|
178
240
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
241
|
+
// Automatically infers your final structured data (data) and the React form event (e)
|
|
242
|
+
const submitHandler = onFormSubmit((data, e) => {
|
|
243
|
+
// Validation passed successfully!
|
|
244
|
+
// 'data.settings.theme' is correctly typed as string.
|
|
245
|
+
api.post("/endpoint", data);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Used manually on form tags or button invocations
|
|
249
|
+
<form onSubmit={submitHandler}>{/* inputs */}</form>;
|
|
250
|
+
```
|
|
184
251
|
|
|
185
|
-
|
|
252
|
+
**3. External Handler Definitions**
|
|
186
253
|
|
|
187
|
-
|
|
254
|
+
If you define your schemas inline inside `useForm( { ... } )` but want to extract your `submit` handler natively without destructing variables you aren't using:
|
|
188
255
|
|
|
189
256
|
```tsx
|
|
190
|
-
const
|
|
257
|
+
export const MyForm = () => {
|
|
258
|
+
// 1. Schema is defined organically inline inside the component execution
|
|
259
|
+
const { ControlledForm } = useForm({
|
|
260
|
+
fullName: string().required(),
|
|
261
|
+
});
|
|
191
262
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
263
|
+
// 2. Type your extract handler capturing React.ComponentProps directly on the wrapper
|
|
264
|
+
const handleSubmit: React.ComponentProps<
|
|
265
|
+
typeof ControlledForm
|
|
266
|
+
>["onSubmit"] = (data, event) => {
|
|
267
|
+
// Both data.fullName and React Events are perfectly inferred natively!
|
|
268
|
+
console.log(data.fullName);
|
|
269
|
+
};
|
|
197
270
|
|
|
198
|
-
|
|
271
|
+
// 3. Destructure whatever you need right on your JSX render
|
|
272
|
+
return <ControlledForm onSubmit={handleSubmit}>...</ControlledForm>;
|
|
273
|
+
};
|
|
199
274
|
```
|
|
200
275
|
|
|
201
276
|
---
|
|
@@ -204,76 +279,81 @@ return <form onSubmit={onSubmit(handleSubmit)}>...</form>;
|
|
|
204
279
|
|
|
205
280
|
### `useForm`
|
|
206
281
|
|
|
282
|
+
Instantiates state and schema configurations.
|
|
283
|
+
|
|
207
284
|
```ts
|
|
208
|
-
const
|
|
209
|
-
state,
|
|
210
|
-
on,
|
|
211
|
-
helpers,
|
|
212
|
-
metadata,
|
|
213
|
-
reset,
|
|
214
|
-
onSubmit,
|
|
215
|
-
reset,
|
|
216
|
-
onSubmit,
|
|
217
|
-
isDirty,
|
|
218
|
-
onFieldChange,
|
|
219
|
-
onFieldBlur
|
|
220
|
-
} = useForm(schema, options?);
|
|
285
|
+
const formApi = useForm(schema, options?);
|
|
221
286
|
```
|
|
222
287
|
|
|
223
288
|
#### Arguments
|
|
224
289
|
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
| **`schema`**
|
|
228
|
-
| **`options`** | `FormOptions`
|
|
290
|
+
| Parameter | Type | Description |
|
|
291
|
+
| :------------ | :--------------------------- | :------------------------------------------------------------------------------- |
|
|
292
|
+
| **`schema`** | `Record<string, ConfigType>` | Your form shape configuration containing `Yup` schemas or primitive constraints. |
|
|
293
|
+
| **`options`** | `FormOptions` | Configuration behaviors. |
|
|
229
294
|
|
|
230
295
|
#### Options
|
|
231
296
|
|
|
232
|
-
|
|
297
|
+
| Option | Type | Description |
|
|
298
|
+
| :--------------------- | :--------- | :------------------------------------------------------------------------------ |
|
|
299
|
+
| **`arrayIdentifiers`** | `Object` | Identifier maps for objects missing `id` (e.g. `{ users: "uuid" }`). |
|
|
300
|
+
| **`resetOnSubmit`** | `boolean` | Instantly resets all values tracking arrays to initialization configurations. |
|
|
301
|
+
| **`onFormSubmit`** | `Function` | Fallback default submission pipeline. |
|
|
302
|
+
| **`keepValues`** | `string[]` | Keys in the root state to preserve during a reset (e.g. `["settings", "age"]`). |
|
|
233
303
|
|
|
234
|
-
|
|
304
|
+
---
|
|
235
305
|
|
|
236
|
-
The `on`
|
|
306
|
+
### The `on` API
|
|
237
307
|
|
|
238
|
-
|
|
239
|
-
| :--- | :--- | :--- |
|
|
240
|
-
| **`on.input`** | `(path, [id])` | Generic input handler. Returns standard input props. |
|
|
241
|
-
| **`on.select`** | `(path, [id])` | Returns props compatible with `Select` (handles `selectedKeys`). |
|
|
242
|
-
| **`on.autocomplete`** | `(path, [id])` | Returns props compatible with `Autocomplete` (handles `selectedKey`). |
|
|
308
|
+
`on` hooks dynamically assign value tracks, validity checks, error text extractions, and update invocations back to the controller.
|
|
243
309
|
|
|
244
|
-
|
|
310
|
+
| Method | Description |
|
|
311
|
+
| :-------------------------------- | :--------------------------------------------------------------------------- |
|
|
312
|
+
| **`on.input(path, [itemId])`** | Exports `{ id, isInvalid, errorMessage, value, onValueChange, onBlur }`. |
|
|
313
|
+
| **`on.select(path, [itemId])`** | Exports properties mapped for `<Select>` components matching `selectedKeys`. |
|
|
314
|
+
| **`on.autocomplete(path, [id])`** | Exports properties mapped for `<Autocomplete>` matching `selectedKey`. |
|
|
245
315
|
|
|
246
|
-
|
|
316
|
+
---
|
|
247
317
|
|
|
248
|
-
|
|
249
|
-
| :--- | :--- |
|
|
250
|
-
| **`addItem(key, item, index?)`** | Adds an item to the array. |
|
|
251
|
-
| **`removeItem(key, index)`** | Removes an item at a specific index. |
|
|
252
|
-
| **`removeById(key, id)`** | **Recommended**. Removes an item by its unique ID. |
|
|
253
|
-
| **`updateItem(key, index, val)`** | Replaces an item at a specific index. |
|
|
254
|
-
| **`moveItem(key, from, to)`** | Moves an item from one index to another. |
|
|
255
|
-
| **`moveById(key, fromId, toId)`** | Moves an item by ID. |
|
|
256
|
-
| **`getItem(key, id)`** | Retrieval helper. |
|
|
318
|
+
### ControlledForm
|
|
257
319
|
|
|
258
|
-
|
|
320
|
+
A Drop-in `<Form />` equivalent handling submit event delegation natively linked to validation triggers internally.
|
|
259
321
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
| **`
|
|
263
|
-
| **`reset`** | `(options?) => void` | Resets form state and metadata to initial values. |
|
|
322
|
+
| Prop | Type | Description |
|
|
323
|
+
| :------------- | :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
324
|
+
| **`onSubmit`** | `(data, e) => void` | Invoked strictly post-validation of `schema` variables passing criteria. Both arguments `data` (your state payload) and `e` (the React event) are correctly typed. |
|
|
264
325
|
|
|
265
|
-
|
|
326
|
+
_Notes: Spreads any traditional `<form>` properties to standard HeroUI mappings out of the box._
|
|
266
327
|
|
|
267
|
-
|
|
268
|
-
// Reset everything
|
|
269
|
-
reset();
|
|
328
|
+
---
|
|
270
329
|
|
|
271
|
-
|
|
272
|
-
reset({ keys: ["organizationId"] });
|
|
330
|
+
### Array Helpers
|
|
273
331
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
332
|
+
Array adjustments exported off the `helpers` map:
|
|
333
|
+
|
|
334
|
+
| Utility | Description |
|
|
335
|
+
| :--------------------------------- | :---------------------------------------------------------------------------- |
|
|
336
|
+
| **`addItem(path, item, index?)`** | Submits entries safely. |
|
|
337
|
+
| **`removeItem(path, index)`** | Excludes entry mapped at index map offset. |
|
|
338
|
+
| **`removeById(path, id)`** | Standard and secure ID withdrawal. |
|
|
339
|
+
| **`updateItem(path, index, val)`** | Hot swap content without re-render destruction. |
|
|
340
|
+
| **`moveItem(path, current, to)`** | Re-order index slots. |
|
|
341
|
+
| **`moveById(path, fromId, toId)`** | Shift lists seamlessly referencing fixed ID targets. |
|
|
342
|
+
| **`getItem(path, id)`** | Read items immediately via O(1) indexed lookups without array mapping cycles. |
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### Form Controls
|
|
347
|
+
|
|
348
|
+
Additional utilities available directly out of `useForm`:
|
|
349
|
+
|
|
350
|
+
| Method | Signatures / Types | Info |
|
|
351
|
+
| :---------------- | :---------------------------------- | :------------------------------------------------------------------ |
|
|
352
|
+
| **`state`** | `InferState<S>` | Evaluated state values matching object signatures defined. |
|
|
353
|
+
| **`setState`** | `Dispatch<SetStateAction>` | The fundamental state mutation. |
|
|
354
|
+
| **`onFormReset`** | `(opts?: { keepValues: string[] })` | Discards invalidity layers and remounts variables back to defaults. |
|
|
355
|
+
| **`isDirty`** | `boolean` | Truthy flag reflecting if state parameters altered. |
|
|
356
|
+
| **`metadata`** | `Map<string, FieldMetadata>` | Touched and Error metadata instances tracked. |
|
|
277
357
|
|
|
278
358
|
---
|
|
279
359
|
|
package/dist/index.d.mts
CHANGED
|
@@ -61,6 +61,11 @@ type FieldMetadata = {
|
|
|
61
61
|
};
|
|
62
62
|
type MetadataType = Map<string, FieldMetadata>;
|
|
63
63
|
type FormSubmitHandler<O extends StateType> = (data: O, e: React.FormEvent) => void;
|
|
64
|
+
/**
|
|
65
|
+
* A utility type for inferring the submit handler's signature directly from a Yup schema object.
|
|
66
|
+
* This saves developers from manually mapping `InferType` back to `FormSubmitHandler` when extracting sumbits.
|
|
67
|
+
*/
|
|
68
|
+
type InferSubmitHandler<S extends Record<string, ConfigType>> = (data: InferState<S>, e: React.FormEvent) => void;
|
|
64
69
|
interface FormResetOptions<O> {
|
|
65
70
|
keepValues?: (keyof O)[];
|
|
66
71
|
}
|
|
@@ -205,4 +210,4 @@ interface UseFormResponse<O extends StateType> {
|
|
|
205
210
|
|
|
206
211
|
declare function useForm<S extends Record<string, ConfigType>>(schema: S, { arrayIdentifiers, onFormSubmit: onFormSubmitProp, resetOnSubmit, keepValues: keepValuesProp, }?: FormOptions<InferState<S>>): UseFormResponse<InferState<S>>;
|
|
207
212
|
|
|
208
|
-
export { type BlurFunc, type ConfigType, type FieldMetadata, type FormOptions, type FormSubmitHandler, type HelpersFunc, type InferState, type MetadataType, type NestedChangeProps, NextUIError, type OnMethods, type StateType, type UseFormResponse, type ValueChangeFunc, useForm };
|
|
213
|
+
export { type BlurFunc, type ConfigType, type FieldMetadata, type FormOptions, type FormSubmitHandler, type HelpersFunc, type InferState, type InferSubmitHandler, type MetadataType, type NestedChangeProps, NextUIError, type OnMethods, type StateType, type UseFormResponse, type ValueChangeFunc, useForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,11 @@ type FieldMetadata = {
|
|
|
61
61
|
};
|
|
62
62
|
type MetadataType = Map<string, FieldMetadata>;
|
|
63
63
|
type FormSubmitHandler<O extends StateType> = (data: O, e: React.FormEvent) => void;
|
|
64
|
+
/**
|
|
65
|
+
* A utility type for inferring the submit handler's signature directly from a Yup schema object.
|
|
66
|
+
* This saves developers from manually mapping `InferType` back to `FormSubmitHandler` when extracting sumbits.
|
|
67
|
+
*/
|
|
68
|
+
type InferSubmitHandler<S extends Record<string, ConfigType>> = (data: InferState<S>, e: React.FormEvent) => void;
|
|
64
69
|
interface FormResetOptions<O> {
|
|
65
70
|
keepValues?: (keyof O)[];
|
|
66
71
|
}
|
|
@@ -205,4 +210,4 @@ interface UseFormResponse<O extends StateType> {
|
|
|
205
210
|
|
|
206
211
|
declare function useForm<S extends Record<string, ConfigType>>(schema: S, { arrayIdentifiers, onFormSubmit: onFormSubmitProp, resetOnSubmit, keepValues: keepValuesProp, }?: FormOptions<InferState<S>>): UseFormResponse<InferState<S>>;
|
|
207
212
|
|
|
208
|
-
export { type BlurFunc, type ConfigType, type FieldMetadata, type FormOptions, type FormSubmitHandler, type HelpersFunc, type InferState, type MetadataType, type NestedChangeProps, NextUIError, type OnMethods, type StateType, type UseFormResponse, type ValueChangeFunc, useForm };
|
|
213
|
+
export { type BlurFunc, type ConfigType, type FieldMetadata, type FormOptions, type FormSubmitHandler, type HelpersFunc, type InferState, type InferSubmitHandler, type MetadataType, type NestedChangeProps, NextUIError, type OnMethods, type StateType, type UseFormResponse, type ValueChangeFunc, useForm };
|
package/dist/index.js
CHANGED
|
@@ -227,6 +227,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
227
227
|
type: "scalar" /* Scalar */,
|
|
228
228
|
compositeKey: id,
|
|
229
229
|
fieldPath: id,
|
|
230
|
+
realPath: id,
|
|
230
231
|
value: getNestedValue2(state, id),
|
|
231
232
|
id
|
|
232
233
|
};
|
|
@@ -249,6 +250,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
249
250
|
compositeKey: `${arrayKey2}.${itemId}.${field}`,
|
|
250
251
|
fieldPath: arg0,
|
|
251
252
|
// "array.field"
|
|
253
|
+
realPath: `${arrayKey2}.${index2}.${field}`,
|
|
252
254
|
value,
|
|
253
255
|
arrayKey: arrayKey2,
|
|
254
256
|
itemId,
|
|
@@ -264,6 +266,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
264
266
|
type: "primitiveArray" /* PrimitiveArray */,
|
|
265
267
|
compositeKey: `${arrayKey}.@${index}`,
|
|
266
268
|
fieldPath: `${arrayKey}`,
|
|
269
|
+
realPath: `${arrayKey}.${index}`,
|
|
267
270
|
value: arr?.[index],
|
|
268
271
|
arrayKey,
|
|
269
272
|
index
|
|
@@ -281,6 +284,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
281
284
|
compositeKey: `${arrayKey}.${itemId}.${field}`,
|
|
282
285
|
fieldPath: `${arrayKey}.${field}`,
|
|
283
286
|
// Normalized path for rules
|
|
287
|
+
realPath: `${arrayKey}.${index}.${field}`,
|
|
284
288
|
value,
|
|
285
289
|
arrayKey,
|
|
286
290
|
itemId,
|
|
@@ -301,6 +305,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
301
305
|
compositeKey: `${parentKey}.${parentId}.${field}.@${index}`,
|
|
302
306
|
fieldPath: `${parentKey}.${field}`,
|
|
303
307
|
// Roughly?
|
|
308
|
+
realPath: `${parentKey}.${parentIndex}.${field}.${index}`,
|
|
304
309
|
value,
|
|
305
310
|
parentKey,
|
|
306
311
|
parentId,
|
|
@@ -403,10 +408,14 @@ function useForm(schema, {
|
|
|
403
408
|
return rule;
|
|
404
409
|
}, []);
|
|
405
410
|
const runValidation = (0, import_react2.useCallback)(
|
|
406
|
-
(ruleDef, value, compositeKey) => {
|
|
411
|
+
(ruleDef, value, compositeKey, realPath, fullState) => {
|
|
407
412
|
if ((0, import_yup2.isSchema)(ruleDef)) {
|
|
408
413
|
try {
|
|
409
|
-
|
|
414
|
+
if (validationSchema && realPath && fullState !== void 0) {
|
|
415
|
+
validationSchema.validateSyncAt(realPath, fullState);
|
|
416
|
+
} else {
|
|
417
|
+
ruleDef.validateSync(value);
|
|
418
|
+
}
|
|
410
419
|
return false;
|
|
411
420
|
} catch (err) {
|
|
412
421
|
const error = {
|
|
@@ -428,13 +437,14 @@ function useForm(schema, {
|
|
|
428
437
|
}
|
|
429
438
|
return false;
|
|
430
439
|
},
|
|
431
|
-
[]
|
|
440
|
+
[validationSchema]
|
|
432
441
|
);
|
|
433
442
|
const validateField = (0, import_react2.useCallback)(
|
|
434
|
-
(compositeKey, fieldPath, value) => {
|
|
443
|
+
(compositeKey, fieldPath, realPath, value, fullState) => {
|
|
435
444
|
let schemaRule = getRule(fieldPath, validationSchema);
|
|
436
445
|
if (schemaRule) {
|
|
437
|
-
if (runValidation(schemaRule, value, compositeKey))
|
|
446
|
+
if (runValidation(schemaRule, value, compositeKey, realPath, fullState))
|
|
447
|
+
return true;
|
|
438
448
|
}
|
|
439
449
|
setMetadata((prev) => {
|
|
440
450
|
const newMap = new Map(prev);
|
|
@@ -530,53 +540,56 @@ function useForm(schema, {
|
|
|
530
540
|
} catch {
|
|
531
541
|
}
|
|
532
542
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const pIndex = getIndex(parentKey, parentId);
|
|
561
|
-
if (pIndex === void 0) return prev;
|
|
562
|
-
const parentArr = [...prev[parentKey]];
|
|
543
|
+
let nextState = stateRef.current;
|
|
544
|
+
if (type === "scalar" /* Scalar */) {
|
|
545
|
+
nextState = handleNestedChange({
|
|
546
|
+
state: nextState,
|
|
547
|
+
id: compositeKey,
|
|
548
|
+
value: finalValue,
|
|
549
|
+
hasNestedValues: compositeKey.includes(".")
|
|
550
|
+
});
|
|
551
|
+
} else if (type === "primitiveArray" /* PrimitiveArray */) {
|
|
552
|
+
nextState = handleNestedChange({
|
|
553
|
+
state: nextState,
|
|
554
|
+
id: `${arrayKey}.${index}`,
|
|
555
|
+
value: finalValue,
|
|
556
|
+
hasNestedValues: true
|
|
557
|
+
});
|
|
558
|
+
} else if (type === "objectArray" /* ObjectArray */) {
|
|
559
|
+
nextState = handleArrayItemChange({
|
|
560
|
+
state: nextState,
|
|
561
|
+
arrayKey,
|
|
562
|
+
index,
|
|
563
|
+
field: resolution.field,
|
|
564
|
+
value: finalValue
|
|
565
|
+
});
|
|
566
|
+
} else if (type === "nestedPrimitiveArray" /* NestedPrimitiveArray */) {
|
|
567
|
+
const pIndex = getIndex(parentKey, parentId);
|
|
568
|
+
if (pIndex !== void 0) {
|
|
569
|
+
const parentArr = [...nextState[parentKey]];
|
|
563
570
|
const pItem = { ...parentArr[pIndex] };
|
|
564
571
|
const nestedArr = [...getNestedValue(pItem, nestedField) || []];
|
|
565
572
|
nestedArr[index] = finalValue;
|
|
566
573
|
pItem[nestedField] = nestedArr;
|
|
567
574
|
parentArr[pIndex] = pItem;
|
|
568
|
-
|
|
575
|
+
nextState = { ...nextState, [parentKey]: parentArr };
|
|
569
576
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
validateField(
|
|
577
|
+
}
|
|
578
|
+
setState(nextState);
|
|
579
|
+
validateField(
|
|
580
|
+
compositeKey,
|
|
581
|
+
fieldPath,
|
|
582
|
+
resolution.realPath,
|
|
583
|
+
finalValue,
|
|
584
|
+
nextState
|
|
585
|
+
);
|
|
573
586
|
},
|
|
574
587
|
[getIndex, validateField, getRule, validationSchema]
|
|
575
588
|
);
|
|
576
589
|
const createHandlers = (0, import_react2.useCallback)(
|
|
577
590
|
(resolution) => {
|
|
578
591
|
if (!resolution) return {};
|
|
579
|
-
const { compositeKey, fieldPath, value } = resolution;
|
|
592
|
+
const { compositeKey, fieldPath, realPath, value } = resolution;
|
|
580
593
|
const meta = metadataRef.current.get(compositeKey);
|
|
581
594
|
const isTouched = meta?.isTouched;
|
|
582
595
|
return {
|
|
@@ -585,7 +598,13 @@ function useForm(schema, {
|
|
|
585
598
|
errorMessage: isTouched ? meta?.errorMessage || "" : "",
|
|
586
599
|
onBlur: () => {
|
|
587
600
|
if (metadataRef.current.get(compositeKey)?.isTouched) return;
|
|
588
|
-
validateField(
|
|
601
|
+
validateField(
|
|
602
|
+
compositeKey,
|
|
603
|
+
fieldPath,
|
|
604
|
+
realPath,
|
|
605
|
+
value,
|
|
606
|
+
stateRef.current
|
|
607
|
+
);
|
|
589
608
|
setMetadata((prev) => {
|
|
590
609
|
const newMap = new Map(prev);
|
|
591
610
|
const current = newMap.get(compositeKey) || {
|
|
@@ -759,7 +778,13 @@ function useForm(schema, {
|
|
|
759
778
|
);
|
|
760
779
|
const onBlur = (0, import_react2.useCallback)(
|
|
761
780
|
(id) => {
|
|
762
|
-
validateField(
|
|
781
|
+
validateField(
|
|
782
|
+
String(id),
|
|
783
|
+
String(id),
|
|
784
|
+
String(id),
|
|
785
|
+
stateRef.current[id],
|
|
786
|
+
stateRef.current
|
|
787
|
+
);
|
|
763
788
|
setMetadata((prev) => {
|
|
764
789
|
const newMap = new Map(prev);
|
|
765
790
|
const current = newMap.get(String(id)) || {
|
|
@@ -790,8 +815,14 @@ function useForm(schema, {
|
|
|
790
815
|
const polymorphicOnSelectionChange = (0, import_react2.useCallback)(
|
|
791
816
|
(id, val) => {
|
|
792
817
|
const fixed = typeof val === "string" || val === null ? val : Array.from(val);
|
|
793
|
-
|
|
794
|
-
|
|
818
|
+
let nextState = handleNestedChange({
|
|
819
|
+
state: stateRef.current,
|
|
820
|
+
id,
|
|
821
|
+
value: fixed,
|
|
822
|
+
hasNestedValues: String(id).includes(".")
|
|
823
|
+
});
|
|
824
|
+
setState(nextState);
|
|
825
|
+
validateField(String(id), String(id), String(id), fixed, nextState);
|
|
795
826
|
},
|
|
796
827
|
[validateField]
|
|
797
828
|
);
|
package/dist/index.mjs
CHANGED
|
@@ -201,6 +201,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
201
201
|
type: "scalar" /* Scalar */,
|
|
202
202
|
compositeKey: id,
|
|
203
203
|
fieldPath: id,
|
|
204
|
+
realPath: id,
|
|
204
205
|
value: getNestedValue2(state, id),
|
|
205
206
|
id
|
|
206
207
|
};
|
|
@@ -223,6 +224,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
223
224
|
compositeKey: `${arrayKey2}.${itemId}.${field}`,
|
|
224
225
|
fieldPath: arg0,
|
|
225
226
|
// "array.field"
|
|
227
|
+
realPath: `${arrayKey2}.${index2}.${field}`,
|
|
226
228
|
value,
|
|
227
229
|
arrayKey: arrayKey2,
|
|
228
230
|
itemId,
|
|
@@ -238,6 +240,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
238
240
|
type: "primitiveArray" /* PrimitiveArray */,
|
|
239
241
|
compositeKey: `${arrayKey}.@${index}`,
|
|
240
242
|
fieldPath: `${arrayKey}`,
|
|
243
|
+
realPath: `${arrayKey}.${index}`,
|
|
241
244
|
value: arr?.[index],
|
|
242
245
|
arrayKey,
|
|
243
246
|
index
|
|
@@ -255,6 +258,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
255
258
|
compositeKey: `${arrayKey}.${itemId}.${field}`,
|
|
256
259
|
fieldPath: `${arrayKey}.${field}`,
|
|
257
260
|
// Normalized path for rules
|
|
261
|
+
realPath: `${arrayKey}.${index}.${field}`,
|
|
258
262
|
value,
|
|
259
263
|
arrayKey,
|
|
260
264
|
itemId,
|
|
@@ -275,6 +279,7 @@ function resolveFieldData(args, state, getIndex, getNestedValue2) {
|
|
|
275
279
|
compositeKey: `${parentKey}.${parentId}.${field}.@${index}`,
|
|
276
280
|
fieldPath: `${parentKey}.${field}`,
|
|
277
281
|
// Roughly?
|
|
282
|
+
realPath: `${parentKey}.${parentIndex}.${field}.${index}`,
|
|
278
283
|
value,
|
|
279
284
|
parentKey,
|
|
280
285
|
parentId,
|
|
@@ -377,10 +382,14 @@ function useForm(schema, {
|
|
|
377
382
|
return rule;
|
|
378
383
|
}, []);
|
|
379
384
|
const runValidation = useCallback(
|
|
380
|
-
(ruleDef, value, compositeKey) => {
|
|
385
|
+
(ruleDef, value, compositeKey, realPath, fullState) => {
|
|
381
386
|
if (isSchema(ruleDef)) {
|
|
382
387
|
try {
|
|
383
|
-
|
|
388
|
+
if (validationSchema && realPath && fullState !== void 0) {
|
|
389
|
+
validationSchema.validateSyncAt(realPath, fullState);
|
|
390
|
+
} else {
|
|
391
|
+
ruleDef.validateSync(value);
|
|
392
|
+
}
|
|
384
393
|
return false;
|
|
385
394
|
} catch (err) {
|
|
386
395
|
const error = {
|
|
@@ -402,13 +411,14 @@ function useForm(schema, {
|
|
|
402
411
|
}
|
|
403
412
|
return false;
|
|
404
413
|
},
|
|
405
|
-
[]
|
|
414
|
+
[validationSchema]
|
|
406
415
|
);
|
|
407
416
|
const validateField = useCallback(
|
|
408
|
-
(compositeKey, fieldPath, value) => {
|
|
417
|
+
(compositeKey, fieldPath, realPath, value, fullState) => {
|
|
409
418
|
let schemaRule = getRule(fieldPath, validationSchema);
|
|
410
419
|
if (schemaRule) {
|
|
411
|
-
if (runValidation(schemaRule, value, compositeKey))
|
|
420
|
+
if (runValidation(schemaRule, value, compositeKey, realPath, fullState))
|
|
421
|
+
return true;
|
|
412
422
|
}
|
|
413
423
|
setMetadata((prev) => {
|
|
414
424
|
const newMap = new Map(prev);
|
|
@@ -504,53 +514,56 @@ function useForm(schema, {
|
|
|
504
514
|
} catch {
|
|
505
515
|
}
|
|
506
516
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const pIndex = getIndex(parentKey, parentId);
|
|
535
|
-
if (pIndex === void 0) return prev;
|
|
536
|
-
const parentArr = [...prev[parentKey]];
|
|
517
|
+
let nextState = stateRef.current;
|
|
518
|
+
if (type === "scalar" /* Scalar */) {
|
|
519
|
+
nextState = handleNestedChange({
|
|
520
|
+
state: nextState,
|
|
521
|
+
id: compositeKey,
|
|
522
|
+
value: finalValue,
|
|
523
|
+
hasNestedValues: compositeKey.includes(".")
|
|
524
|
+
});
|
|
525
|
+
} else if (type === "primitiveArray" /* PrimitiveArray */) {
|
|
526
|
+
nextState = handleNestedChange({
|
|
527
|
+
state: nextState,
|
|
528
|
+
id: `${arrayKey}.${index}`,
|
|
529
|
+
value: finalValue,
|
|
530
|
+
hasNestedValues: true
|
|
531
|
+
});
|
|
532
|
+
} else if (type === "objectArray" /* ObjectArray */) {
|
|
533
|
+
nextState = handleArrayItemChange({
|
|
534
|
+
state: nextState,
|
|
535
|
+
arrayKey,
|
|
536
|
+
index,
|
|
537
|
+
field: resolution.field,
|
|
538
|
+
value: finalValue
|
|
539
|
+
});
|
|
540
|
+
} else if (type === "nestedPrimitiveArray" /* NestedPrimitiveArray */) {
|
|
541
|
+
const pIndex = getIndex(parentKey, parentId);
|
|
542
|
+
if (pIndex !== void 0) {
|
|
543
|
+
const parentArr = [...nextState[parentKey]];
|
|
537
544
|
const pItem = { ...parentArr[pIndex] };
|
|
538
545
|
const nestedArr = [...getNestedValue(pItem, nestedField) || []];
|
|
539
546
|
nestedArr[index] = finalValue;
|
|
540
547
|
pItem[nestedField] = nestedArr;
|
|
541
548
|
parentArr[pIndex] = pItem;
|
|
542
|
-
|
|
549
|
+
nextState = { ...nextState, [parentKey]: parentArr };
|
|
543
550
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
validateField(
|
|
551
|
+
}
|
|
552
|
+
setState(nextState);
|
|
553
|
+
validateField(
|
|
554
|
+
compositeKey,
|
|
555
|
+
fieldPath,
|
|
556
|
+
resolution.realPath,
|
|
557
|
+
finalValue,
|
|
558
|
+
nextState
|
|
559
|
+
);
|
|
547
560
|
},
|
|
548
561
|
[getIndex, validateField, getRule, validationSchema]
|
|
549
562
|
);
|
|
550
563
|
const createHandlers = useCallback(
|
|
551
564
|
(resolution) => {
|
|
552
565
|
if (!resolution) return {};
|
|
553
|
-
const { compositeKey, fieldPath, value } = resolution;
|
|
566
|
+
const { compositeKey, fieldPath, realPath, value } = resolution;
|
|
554
567
|
const meta = metadataRef.current.get(compositeKey);
|
|
555
568
|
const isTouched = meta?.isTouched;
|
|
556
569
|
return {
|
|
@@ -559,7 +572,13 @@ function useForm(schema, {
|
|
|
559
572
|
errorMessage: isTouched ? meta?.errorMessage || "" : "",
|
|
560
573
|
onBlur: () => {
|
|
561
574
|
if (metadataRef.current.get(compositeKey)?.isTouched) return;
|
|
562
|
-
validateField(
|
|
575
|
+
validateField(
|
|
576
|
+
compositeKey,
|
|
577
|
+
fieldPath,
|
|
578
|
+
realPath,
|
|
579
|
+
value,
|
|
580
|
+
stateRef.current
|
|
581
|
+
);
|
|
563
582
|
setMetadata((prev) => {
|
|
564
583
|
const newMap = new Map(prev);
|
|
565
584
|
const current = newMap.get(compositeKey) || {
|
|
@@ -733,7 +752,13 @@ function useForm(schema, {
|
|
|
733
752
|
);
|
|
734
753
|
const onBlur = useCallback(
|
|
735
754
|
(id) => {
|
|
736
|
-
validateField(
|
|
755
|
+
validateField(
|
|
756
|
+
String(id),
|
|
757
|
+
String(id),
|
|
758
|
+
String(id),
|
|
759
|
+
stateRef.current[id],
|
|
760
|
+
stateRef.current
|
|
761
|
+
);
|
|
737
762
|
setMetadata((prev) => {
|
|
738
763
|
const newMap = new Map(prev);
|
|
739
764
|
const current = newMap.get(String(id)) || {
|
|
@@ -764,8 +789,14 @@ function useForm(schema, {
|
|
|
764
789
|
const polymorphicOnSelectionChange = useCallback(
|
|
765
790
|
(id, val) => {
|
|
766
791
|
const fixed = typeof val === "string" || val === null ? val : Array.from(val);
|
|
767
|
-
|
|
768
|
-
|
|
792
|
+
let nextState = handleNestedChange({
|
|
793
|
+
state: stateRef.current,
|
|
794
|
+
id,
|
|
795
|
+
value: fixed,
|
|
796
|
+
hasNestedValues: String(id).includes(".")
|
|
797
|
+
});
|
|
798
|
+
setState(nextState);
|
|
799
|
+
validateField(String(id), String(id), String(id), fixed, nextState);
|
|
769
800
|
},
|
|
770
801
|
[validateField]
|
|
771
802
|
);
|