@axenstudio/axen-form 0.1.16 → 0.1.18
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 +34 -3
- package/dist/adapters/yupAdapter.cjs +29 -10
- package/dist/adapters/yupAdapter.d.ts +1 -2
- package/dist/adapters/yupAdapter.js +29 -10
- package/dist/adapters/zodAdapter.cjs +29 -10
- package/dist/adapters/zodAdapter.d.ts +1 -2
- package/dist/adapters/zodAdapter.js +29 -10
- package/dist/core/dateUtils.d.ts +0 -1
- package/dist/core/formStore.cjs +127 -39
- package/dist/core/formStore.d.ts +2 -1
- package/dist/core/formStore.js +127 -39
- package/dist/core/numberUtils.d.ts +0 -1
- package/dist/core/pathUtils.d.ts +0 -1
- package/dist/core/payloadUtils.d.ts +0 -1
- package/dist/core/semanticValidation.cjs +69 -0
- package/dist/core/semanticValidation.d.ts +5 -0
- package/dist/core/semanticValidation.js +69 -0
- package/dist/core/validation.cjs +4 -3
- package/dist/core/validation.d.ts +0 -1
- package/dist/core/validation.js +4 -3
- package/dist/fields/AutocompleteField/AutocompleteField.d.ts +1 -2
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts +1 -2
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts +1 -2
- package/dist/fields/CheckboxField/CheckboxField.d.ts +1 -2
- package/dist/fields/ColorField/ColorField.d.ts +1 -2
- package/dist/fields/CurrencyField/CurrencyField.d.ts +1 -2
- package/dist/fields/DateField/DateField.d.ts +1 -2
- package/dist/fields/DateField/DatePicker.d.ts +1 -2
- package/dist/fields/DateTimeField/DateTimeField.d.ts +1 -2
- package/dist/fields/EmailField/EmailField.d.ts +1 -2
- package/dist/fields/NumberField/NumberField.d.ts +1 -2
- package/dist/fields/PasswordField/PasswordField.d.ts +1 -2
- package/dist/fields/PhoneField/PhoneField.d.ts +1 -2
- package/dist/fields/RadioField/RadioField.d.ts +1 -2
- package/dist/fields/SelectField/SelectField.d.ts +1 -2
- package/dist/fields/SliderField/SliderField.d.ts +1 -2
- package/dist/fields/SwitchField/SwitchField.d.ts +1 -2
- package/dist/fields/TextField/TextField.d.ts +1 -2
- package/dist/fields/TextareaField/TextareaField.d.ts +1 -2
- package/dist/fields/TimeField/TimeField.d.ts +1 -2
- package/dist/fields/TimeField/TimePicker.d.ts +1 -2
- package/dist/fields/defaultComponentMap.cjs +3 -0
- package/dist/fields/defaultComponentMap.d.ts +0 -1
- package/dist/fields/defaultComponentMap.js +3 -0
- package/dist/fields/defaultPlaceholders.d.ts +0 -1
- package/dist/fields/fieldBase.module.css.cjs +92 -70
- package/dist/fields/fieldBase.module.css.js +92 -70
- package/dist/fields/fieldUtils.d.ts +0 -1
- package/dist/fields/iconUtils.d.ts +0 -1
- package/dist/fields/icons.d.ts +3 -4
- package/dist/index.cjs +31 -22
- package/dist/index.d.ts +3 -2
- package/dist/index.js +32 -23
- package/dist/layout/Box/Box.d.ts +1 -2
- package/dist/layout/Divider/Divider.d.ts +1 -2
- package/dist/layout/Grid/Grid.d.ts +1 -2
- package/dist/layout/Spacer/Spacer.d.ts +1 -2
- package/dist/layout/Stack/Stack.d.ts +1 -2
- package/dist/layout/index.d.ts +0 -1
- package/dist/react/AxenArrayField.d.ts +1 -2
- package/dist/react/AxenField.d.ts +1 -2
- package/dist/react/AxenForm.cjs +33 -23
- package/dist/react/AxenForm.d.ts +0 -1
- package/dist/react/AxenForm.js +34 -24
- package/dist/react/FormContext.d.ts +0 -1
- package/dist/react/useAxenForm.cjs +11 -4
- package/dist/react/useAxenForm.d.ts +1 -2
- package/dist/react/useAxenForm.js +11 -4
- package/dist/react/useField.cjs +3 -0
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +3 -0
- package/dist/react/useFieldArray.cjs +19 -13
- package/dist/react/useFieldArray.d.ts +0 -1
- package/dist/react/useFieldArray.js +19 -13
- package/dist/react/useFormState.cjs +18 -0
- package/dist/react/useFormState.d.ts +5 -0
- package/dist/react/useFormState.js +18 -0
- package/dist/types.d.ts +19 -2
- package/package.json +5 -2
- package/dist/adapters/yupAdapter.d.ts.map +0 -1
- package/dist/adapters/zodAdapter.d.ts.map +0 -1
- package/dist/core/dateUtils.d.ts.map +0 -1
- package/dist/core/formStore.d.ts.map +0 -1
- package/dist/core/numberUtils.d.ts.map +0 -1
- package/dist/core/pathUtils.d.ts.map +0 -1
- package/dist/core/payloadUtils.d.ts.map +0 -1
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/fields/AutocompleteField/AutocompleteField.d.ts.map +0 -1
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts.map +0 -1
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts.map +0 -1
- package/dist/fields/CheckboxField/CheckboxField.d.ts.map +0 -1
- package/dist/fields/ColorField/ColorField.d.ts.map +0 -1
- package/dist/fields/CurrencyField/CurrencyField.d.ts.map +0 -1
- package/dist/fields/DateField/DateField.d.ts.map +0 -1
- package/dist/fields/DateField/DatePicker.d.ts.map +0 -1
- package/dist/fields/DateTimeField/DateTimeField.d.ts.map +0 -1
- package/dist/fields/EmailField/EmailField.d.ts.map +0 -1
- package/dist/fields/NumberField/NumberField.d.ts.map +0 -1
- package/dist/fields/PasswordField/PasswordField.d.ts.map +0 -1
- package/dist/fields/PhoneField/PhoneField.d.ts.map +0 -1
- package/dist/fields/RadioField/RadioField.d.ts.map +0 -1
- package/dist/fields/SelectField/SelectField.d.ts.map +0 -1
- package/dist/fields/SliderField/SliderField.d.ts.map +0 -1
- package/dist/fields/SwitchField/SwitchField.d.ts.map +0 -1
- package/dist/fields/TextField/TextField.d.ts.map +0 -1
- package/dist/fields/TextareaField/TextareaField.d.ts.map +0 -1
- package/dist/fields/TimeField/TimeField.d.ts.map +0 -1
- package/dist/fields/TimeField/TimePicker.d.ts.map +0 -1
- package/dist/fields/defaultComponentMap.d.ts.map +0 -1
- package/dist/fields/defaultPlaceholders.d.ts.map +0 -1
- package/dist/fields/fieldUtils.d.ts.map +0 -1
- package/dist/fields/iconUtils.d.ts.map +0 -1
- package/dist/fields/icons.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/layout/Box/Box.d.ts.map +0 -1
- package/dist/layout/Divider/Divider.d.ts.map +0 -1
- package/dist/layout/Grid/Grid.d.ts.map +0 -1
- package/dist/layout/Spacer/Spacer.d.ts.map +0 -1
- package/dist/layout/Stack/Stack.d.ts.map +0 -1
- package/dist/layout/index.d.ts.map +0 -1
- package/dist/react/AxenArrayField.d.ts.map +0 -1
- package/dist/react/AxenField.d.ts.map +0 -1
- package/dist/react/AxenForm.d.ts.map +0 -1
- package/dist/react/FormContext.d.ts.map +0 -1
- package/dist/react/useAxenForm.d.ts.map +0 -1
- package/dist/react/useField.d.ts.map +0 -1
- package/dist/react/useFieldArray.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -324,7 +324,7 @@ const config = {
|
|
|
324
324
|
<AxenForm
|
|
325
325
|
config={config}
|
|
326
326
|
components={defaultComponentMap}
|
|
327
|
-
validationAdapter={yupAdapter}
|
|
327
|
+
validationAdapter={yupAdapter(yup)}
|
|
328
328
|
onSubmit={handleSubmit}
|
|
329
329
|
/>;
|
|
330
330
|
```
|
|
@@ -333,11 +333,12 @@ const config = {
|
|
|
333
333
|
|
|
334
334
|
```tsx
|
|
335
335
|
import { zodAdapter } from '@axenstudio/axen-form/adapters/zod';
|
|
336
|
+
import { z } from 'zod';
|
|
336
337
|
|
|
337
338
|
<AxenForm
|
|
338
339
|
config={config}
|
|
339
340
|
components={defaultComponentMap}
|
|
340
|
-
validationAdapter={zodAdapter}
|
|
341
|
+
validationAdapter={zodAdapter(z)}
|
|
341
342
|
onSubmit={handleSubmit}
|
|
342
343
|
/>;
|
|
343
344
|
```
|
|
@@ -964,7 +965,7 @@ Plug in any validation library:
|
|
|
964
965
|
| `zodAdapter` | `@axenstudio/axen-form/adapters/zod` | Zod |
|
|
965
966
|
| `simpleValidator` | Built-in (default) | None |
|
|
966
967
|
|
|
967
|
-
Both adapters
|
|
968
|
+
Both adapters are factories: call `yupAdapter(yup)` or `zodAdapter(z)` with your installed validation library. They generate type-aware schemas for required fields. Put additional rules such as min/max on the field's custom `validation` schema; the adapters do not infer those rules from display props. The packed-consumer suite verifies Yup 1 and Zod 3.
|
|
968
969
|
|
|
969
970
|
---
|
|
970
971
|
|
|
@@ -1138,6 +1139,13 @@ src/
|
|
|
1138
1139
|
|
|
1139
1140
|
axen-form uses CSS Custom Properties (design tokens) for fine-grained theming.
|
|
1140
1141
|
|
|
1142
|
+
Published JS injects the compiled styles automatically; no separate CSS import is needed.
|
|
1143
|
+
Form resets and the default light color scheme apply inside `AxenForm` (or an explicit
|
|
1144
|
+
`data-axen-theme` wrapper). Standalone fields keep their own scoped base styles.
|
|
1145
|
+
Importing the package does not reset unrelated application controls or change the
|
|
1146
|
+
document's color scheme. The namespaced `--axen-*` token defaults remain available
|
|
1147
|
+
on `:root` for standalone layout components.
|
|
1148
|
+
|
|
1141
1149
|
**Key token categories:**
|
|
1142
1150
|
|
|
1143
1151
|
| Category | Example Tokens |
|
|
@@ -1173,6 +1181,29 @@ Requires React 19+ and ES2022+ environment.
|
|
|
1173
1181
|
|
|
1174
1182
|
---
|
|
1175
1183
|
|
|
1184
|
+
## Development validation
|
|
1185
|
+
|
|
1186
|
+
From `libs/axen-form`, using the tested Linux/WSL Node 24 and pnpm 10 toolchain:
|
|
1187
|
+
|
|
1188
|
+
```bash
|
|
1189
|
+
pnpm install --frozen-lockfile
|
|
1190
|
+
pnpm typecheck
|
|
1191
|
+
pnpm test
|
|
1192
|
+
pnpm test:consumer
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
`test:consumer` builds and packs the library, installs the tarball into a fresh
|
|
1196
|
+
temporary React application, checks public ESM/CJS/types exports and server
|
|
1197
|
+
rendering, then builds that application and runs Chromium desktop/mobile tests.
|
|
1198
|
+
It covers required-field validation, submission and reset with the built-in,
|
|
1199
|
+
Yup 1 and Zod 3 adapters, CSS isolation, and standalone static named imports.
|
|
1200
|
+
It requires npm and network access to install the fixture dependencies and
|
|
1201
|
+
Chromium. The evidence directory is printed and retained under the system temp
|
|
1202
|
+
directory, including browser failure screenshots. The preview uses local port
|
|
1203
|
+
4179 and fails if that port is already occupied.
|
|
1204
|
+
|
|
1205
|
+
---
|
|
1206
|
+
|
|
1176
1207
|
## License
|
|
1177
1208
|
|
|
1178
1209
|
MIT © [Axen Studio](https://github.com/axenstudio)
|
|
@@ -1,33 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const pathUtils = require("../core/pathUtils.cjs");
|
|
3
4
|
function yupAdapter(yup) {
|
|
4
5
|
return {
|
|
5
6
|
buildSchema(fields) {
|
|
6
7
|
const shape = {};
|
|
7
8
|
for (const field of fields) {
|
|
9
|
+
let rule;
|
|
8
10
|
if (field.validation) {
|
|
9
|
-
|
|
11
|
+
rule = field.validation;
|
|
10
12
|
} else if (field.required) {
|
|
11
13
|
const msg = field.requiredMessage ?? `${field.label ?? field.name} is required`;
|
|
12
14
|
switch (field.type) {
|
|
13
15
|
case "checkbox":
|
|
14
16
|
case "switch":
|
|
15
|
-
|
|
17
|
+
rule = yup.boolean().oneOf([true], msg).required(msg);
|
|
16
18
|
break;
|
|
17
19
|
case "number":
|
|
18
20
|
case "currency":
|
|
19
21
|
case "slider":
|
|
20
|
-
|
|
22
|
+
rule = yup.number().required(msg).typeError(msg);
|
|
21
23
|
break;
|
|
22
24
|
case "autocomplete-multi":
|
|
23
|
-
|
|
25
|
+
rule = yup.array().min(1, msg).required(msg);
|
|
24
26
|
break;
|
|
25
27
|
default:
|
|
26
|
-
|
|
28
|
+
rule = yup.string().required(msg);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
31
|
+
if (!rule) continue;
|
|
32
|
+
const path = field.name.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
33
|
+
let node = shape;
|
|
34
|
+
for (const segment of path.slice(0, -1)) {
|
|
35
|
+
node[segment] ?? (node[segment] = {});
|
|
36
|
+
node = node[segment];
|
|
37
|
+
}
|
|
38
|
+
const leaf = path.at(-1);
|
|
39
|
+
if (leaf) node[leaf] = rule;
|
|
29
40
|
}
|
|
30
|
-
|
|
41
|
+
const nested = (node) => yup.object(Object.fromEntries(
|
|
42
|
+
Object.entries(node).map(([key, value]) => [
|
|
43
|
+
key,
|
|
44
|
+
value && Object.getPrototypeOf(value) === Object.prototype ? nested(value) : value
|
|
45
|
+
])
|
|
46
|
+
));
|
|
47
|
+
return nested(shape);
|
|
31
48
|
},
|
|
32
49
|
async validate(schema, values) {
|
|
33
50
|
const yupSchema = schema;
|
|
@@ -39,10 +56,12 @@ function yupAdapter(yup) {
|
|
|
39
56
|
const errors = {};
|
|
40
57
|
if (validationError.inner) {
|
|
41
58
|
for (const e of validationError.inner) {
|
|
42
|
-
if (e.path && !errors[e.path])
|
|
43
|
-
errors[e.path] = e.message;
|
|
44
|
-
}
|
|
59
|
+
if (e.path && !errors[e.path]) errors[e.path] = e.message;
|
|
45
60
|
}
|
|
61
|
+
} else if ("path" in validationError && typeof validationError.path === "string") {
|
|
62
|
+
errors[validationError.path] = validationError.message;
|
|
63
|
+
} else {
|
|
64
|
+
throw err;
|
|
46
65
|
}
|
|
47
66
|
return errors;
|
|
48
67
|
}
|
|
@@ -50,7 +69,7 @@ function yupAdapter(yup) {
|
|
|
50
69
|
async validateField(schema, name, value) {
|
|
51
70
|
const yupSchema = schema;
|
|
52
71
|
try {
|
|
53
|
-
await yupSchema.validateAt(name, {
|
|
72
|
+
await yupSchema.validateAt(name, pathUtils.set({}, name, value));
|
|
54
73
|
return void 0;
|
|
55
74
|
} catch (err) {
|
|
56
75
|
return err.message;
|
|
@@ -5,7 +5,7 @@ import { ValidationAdapter } from '../types';
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* import * as yup from 'yup';
|
|
8
|
-
* import { yupAdapter } from '@axenstudio/axen-form/
|
|
8
|
+
* import { yupAdapter } from '@axenstudio/axen-form/adapters/yup';
|
|
9
9
|
*
|
|
10
10
|
* const adapter = yupAdapter(yup);
|
|
11
11
|
*/
|
|
@@ -18,4 +18,3 @@ interface YupLike {
|
|
|
18
18
|
}
|
|
19
19
|
export declare function yupAdapter(yup: YupLike): ValidationAdapter;
|
|
20
20
|
export {};
|
|
21
|
-
//# sourceMappingURL=yupAdapter.d.ts.map
|
|
@@ -1,31 +1,48 @@
|
|
|
1
|
+
import { set } from "../core/pathUtils.js";
|
|
1
2
|
function yupAdapter(yup) {
|
|
2
3
|
return {
|
|
3
4
|
buildSchema(fields) {
|
|
4
5
|
const shape = {};
|
|
5
6
|
for (const field of fields) {
|
|
7
|
+
let rule;
|
|
6
8
|
if (field.validation) {
|
|
7
|
-
|
|
9
|
+
rule = field.validation;
|
|
8
10
|
} else if (field.required) {
|
|
9
11
|
const msg = field.requiredMessage ?? `${field.label ?? field.name} is required`;
|
|
10
12
|
switch (field.type) {
|
|
11
13
|
case "checkbox":
|
|
12
14
|
case "switch":
|
|
13
|
-
|
|
15
|
+
rule = yup.boolean().oneOf([true], msg).required(msg);
|
|
14
16
|
break;
|
|
15
17
|
case "number":
|
|
16
18
|
case "currency":
|
|
17
19
|
case "slider":
|
|
18
|
-
|
|
20
|
+
rule = yup.number().required(msg).typeError(msg);
|
|
19
21
|
break;
|
|
20
22
|
case "autocomplete-multi":
|
|
21
|
-
|
|
23
|
+
rule = yup.array().min(1, msg).required(msg);
|
|
22
24
|
break;
|
|
23
25
|
default:
|
|
24
|
-
|
|
26
|
+
rule = yup.string().required(msg);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
if (!rule) continue;
|
|
30
|
+
const path = field.name.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
31
|
+
let node = shape;
|
|
32
|
+
for (const segment of path.slice(0, -1)) {
|
|
33
|
+
node[segment] ?? (node[segment] = {});
|
|
34
|
+
node = node[segment];
|
|
35
|
+
}
|
|
36
|
+
const leaf = path.at(-1);
|
|
37
|
+
if (leaf) node[leaf] = rule;
|
|
27
38
|
}
|
|
28
|
-
|
|
39
|
+
const nested = (node) => yup.object(Object.fromEntries(
|
|
40
|
+
Object.entries(node).map(([key, value]) => [
|
|
41
|
+
key,
|
|
42
|
+
value && Object.getPrototypeOf(value) === Object.prototype ? nested(value) : value
|
|
43
|
+
])
|
|
44
|
+
));
|
|
45
|
+
return nested(shape);
|
|
29
46
|
},
|
|
30
47
|
async validate(schema, values) {
|
|
31
48
|
const yupSchema = schema;
|
|
@@ -37,10 +54,12 @@ function yupAdapter(yup) {
|
|
|
37
54
|
const errors = {};
|
|
38
55
|
if (validationError.inner) {
|
|
39
56
|
for (const e of validationError.inner) {
|
|
40
|
-
if (e.path && !errors[e.path])
|
|
41
|
-
errors[e.path] = e.message;
|
|
42
|
-
}
|
|
57
|
+
if (e.path && !errors[e.path]) errors[e.path] = e.message;
|
|
43
58
|
}
|
|
59
|
+
} else if ("path" in validationError && typeof validationError.path === "string") {
|
|
60
|
+
errors[validationError.path] = validationError.message;
|
|
61
|
+
} else {
|
|
62
|
+
throw err;
|
|
44
63
|
}
|
|
45
64
|
return errors;
|
|
46
65
|
}
|
|
@@ -48,7 +67,7 @@ function yupAdapter(yup) {
|
|
|
48
67
|
async validateField(schema, name, value) {
|
|
49
68
|
const yupSchema = schema;
|
|
50
69
|
try {
|
|
51
|
-
await yupSchema.validateAt(name, {
|
|
70
|
+
await yupSchema.validateAt(name, set({}, name, value));
|
|
52
71
|
return void 0;
|
|
53
72
|
} catch (err) {
|
|
54
73
|
return err.message;
|
|
@@ -5,29 +5,45 @@ function zodAdapter(z) {
|
|
|
5
5
|
buildSchema(fields) {
|
|
6
6
|
const shape = {};
|
|
7
7
|
for (const field of fields) {
|
|
8
|
+
let rule;
|
|
8
9
|
if (field.validation) {
|
|
9
|
-
|
|
10
|
+
rule = field.validation;
|
|
10
11
|
} else if (field.required) {
|
|
11
12
|
const msg = field.requiredMessage ?? `${field.label ?? field.name} is required`;
|
|
12
13
|
switch (field.type) {
|
|
13
14
|
case "checkbox":
|
|
14
15
|
case "switch":
|
|
15
|
-
|
|
16
|
+
rule = z.literal(true, { errorMap: () => ({ message: msg }) });
|
|
16
17
|
break;
|
|
17
18
|
case "number":
|
|
18
19
|
case "currency":
|
|
19
20
|
case "slider":
|
|
20
|
-
|
|
21
|
+
rule = z.number({ required_error: msg, invalid_type_error: msg });
|
|
21
22
|
break;
|
|
22
23
|
case "autocomplete-multi":
|
|
23
|
-
|
|
24
|
+
rule = z.array(z.unknown()).min(1, msg);
|
|
24
25
|
break;
|
|
25
26
|
default:
|
|
26
|
-
|
|
27
|
+
rule = z.string({ required_error: msg }).min(1, msg);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
if (!rule) continue;
|
|
31
|
+
const path = field.name.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
32
|
+
let node = shape;
|
|
33
|
+
for (const segment of path.slice(0, -1)) {
|
|
34
|
+
node[segment] ?? (node[segment] = {});
|
|
35
|
+
node = node[segment];
|
|
36
|
+
}
|
|
37
|
+
const leaf = path.at(-1);
|
|
38
|
+
if (leaf) node[leaf] = rule;
|
|
29
39
|
}
|
|
30
|
-
|
|
40
|
+
const nested = (node) => z.object(Object.fromEntries(
|
|
41
|
+
Object.entries(node).map(([key, value]) => [
|
|
42
|
+
key,
|
|
43
|
+
value && Object.getPrototypeOf(value) === Object.prototype ? nested(value) : value
|
|
44
|
+
])
|
|
45
|
+
));
|
|
46
|
+
return nested(shape);
|
|
31
47
|
},
|
|
32
48
|
async validate(schema, values) {
|
|
33
49
|
var _a;
|
|
@@ -46,13 +62,16 @@ function zodAdapter(z) {
|
|
|
46
62
|
return errors;
|
|
47
63
|
},
|
|
48
64
|
async validateField(schema, name, value) {
|
|
49
|
-
var _a, _b;
|
|
65
|
+
var _a, _b, _c;
|
|
50
66
|
const zodSchema = schema;
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
let fieldSchema = zodSchema;
|
|
68
|
+
for (const segment of name.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean)) {
|
|
69
|
+
fieldSchema = (_a = fieldSchema.shape) == null ? void 0 : _a[segment];
|
|
70
|
+
if (!fieldSchema) return void 0;
|
|
71
|
+
}
|
|
53
72
|
const result = fieldSchema.safeParse(value);
|
|
54
73
|
if (result.success) return void 0;
|
|
55
|
-
return (
|
|
74
|
+
return (_c = (_b = result.error) == null ? void 0 : _b.issues[0]) == null ? void 0 : _c.message;
|
|
56
75
|
}
|
|
57
76
|
};
|
|
58
77
|
}
|
|
@@ -5,7 +5,7 @@ import { ValidationAdapter } from '../types';
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* import { z } from 'zod';
|
|
8
|
-
* import { zodAdapter } from '@axenstudio/axen-form/
|
|
8
|
+
* import { zodAdapter } from '@axenstudio/axen-form/adapters/zod';
|
|
9
9
|
*
|
|
10
10
|
* const adapter = zodAdapter(z);
|
|
11
11
|
*/
|
|
@@ -19,4 +19,3 @@ interface ZodLike {
|
|
|
19
19
|
}
|
|
20
20
|
export declare function zodAdapter(z: ZodLike): ValidationAdapter;
|
|
21
21
|
export {};
|
|
22
|
-
//# sourceMappingURL=zodAdapter.d.ts.map
|
|
@@ -3,29 +3,45 @@ function zodAdapter(z) {
|
|
|
3
3
|
buildSchema(fields) {
|
|
4
4
|
const shape = {};
|
|
5
5
|
for (const field of fields) {
|
|
6
|
+
let rule;
|
|
6
7
|
if (field.validation) {
|
|
7
|
-
|
|
8
|
+
rule = field.validation;
|
|
8
9
|
} else if (field.required) {
|
|
9
10
|
const msg = field.requiredMessage ?? `${field.label ?? field.name} is required`;
|
|
10
11
|
switch (field.type) {
|
|
11
12
|
case "checkbox":
|
|
12
13
|
case "switch":
|
|
13
|
-
|
|
14
|
+
rule = z.literal(true, { errorMap: () => ({ message: msg }) });
|
|
14
15
|
break;
|
|
15
16
|
case "number":
|
|
16
17
|
case "currency":
|
|
17
18
|
case "slider":
|
|
18
|
-
|
|
19
|
+
rule = z.number({ required_error: msg, invalid_type_error: msg });
|
|
19
20
|
break;
|
|
20
21
|
case "autocomplete-multi":
|
|
21
|
-
|
|
22
|
+
rule = z.array(z.unknown()).min(1, msg);
|
|
22
23
|
break;
|
|
23
24
|
default:
|
|
24
|
-
|
|
25
|
+
rule = z.string({ required_error: msg }).min(1, msg);
|
|
25
26
|
}
|
|
26
27
|
}
|
|
28
|
+
if (!rule) continue;
|
|
29
|
+
const path = field.name.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
30
|
+
let node = shape;
|
|
31
|
+
for (const segment of path.slice(0, -1)) {
|
|
32
|
+
node[segment] ?? (node[segment] = {});
|
|
33
|
+
node = node[segment];
|
|
34
|
+
}
|
|
35
|
+
const leaf = path.at(-1);
|
|
36
|
+
if (leaf) node[leaf] = rule;
|
|
27
37
|
}
|
|
28
|
-
|
|
38
|
+
const nested = (node) => z.object(Object.fromEntries(
|
|
39
|
+
Object.entries(node).map(([key, value]) => [
|
|
40
|
+
key,
|
|
41
|
+
value && Object.getPrototypeOf(value) === Object.prototype ? nested(value) : value
|
|
42
|
+
])
|
|
43
|
+
));
|
|
44
|
+
return nested(shape);
|
|
29
45
|
},
|
|
30
46
|
async validate(schema, values) {
|
|
31
47
|
var _a;
|
|
@@ -44,13 +60,16 @@ function zodAdapter(z) {
|
|
|
44
60
|
return errors;
|
|
45
61
|
},
|
|
46
62
|
async validateField(schema, name, value) {
|
|
47
|
-
var _a, _b;
|
|
63
|
+
var _a, _b, _c;
|
|
48
64
|
const zodSchema = schema;
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
let fieldSchema = zodSchema;
|
|
66
|
+
for (const segment of name.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean)) {
|
|
67
|
+
fieldSchema = (_a = fieldSchema.shape) == null ? void 0 : _a[segment];
|
|
68
|
+
if (!fieldSchema) return void 0;
|
|
69
|
+
}
|
|
51
70
|
const result = fieldSchema.safeParse(value);
|
|
52
71
|
if (result.success) return void 0;
|
|
53
|
-
return (
|
|
72
|
+
return (_c = (_b = result.error) == null ? void 0 : _b.issues[0]) == null ? void 0 : _c.message;
|
|
54
73
|
}
|
|
55
74
|
};
|
|
56
75
|
}
|
package/dist/core/dateUtils.d.ts
CHANGED
package/dist/core/formStore.cjs
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const pathUtils = require("./pathUtils.cjs");
|
|
4
|
+
function valuesEqual(a, b) {
|
|
5
|
+
if (Object.is(a, b)) return true;
|
|
6
|
+
if (a instanceof Date || b instanceof Date) return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
7
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
8
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
9
|
+
const left = a;
|
|
10
|
+
const right = b;
|
|
11
|
+
const keys = Object.keys(left);
|
|
12
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
13
|
+
return keys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && valuesEqual(left[key], right[key]));
|
|
14
|
+
}
|
|
4
15
|
function createFormStore(options) {
|
|
5
16
|
const {
|
|
6
17
|
initialValues,
|
|
@@ -20,6 +31,9 @@ function createFormStore(options) {
|
|
|
20
31
|
const subscribers = /* @__PURE__ */ new Set();
|
|
21
32
|
const fieldSubscribers = /* @__PURE__ */ new Map();
|
|
22
33
|
let validationSchema = options.validationSchema;
|
|
34
|
+
let valuesRevision = 0;
|
|
35
|
+
let formRequest = 0;
|
|
36
|
+
const fieldRequests = /* @__PURE__ */ new Map();
|
|
23
37
|
if (validationAdapter && options.fields && !validationSchema) {
|
|
24
38
|
validationSchema = validationAdapter.buildSchema(options.fields);
|
|
25
39
|
}
|
|
@@ -48,16 +62,30 @@ function createFormStore(options) {
|
|
|
48
62
|
var _a;
|
|
49
63
|
(_a = fieldSubscribers.get(name)) == null ? void 0 : _a.forEach((fn) => fn());
|
|
50
64
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
...state,
|
|
54
|
-
values: pathUtils.set(state.values, name, value),
|
|
55
|
-
dirty: true
|
|
56
|
-
};
|
|
57
|
-
notifyField(name);
|
|
65
|
+
function notifyAllFields() {
|
|
66
|
+
for (const name of fieldSubscribers.keys()) notifyField(name);
|
|
58
67
|
notify();
|
|
68
|
+
}
|
|
69
|
+
function notifyValuePath(path) {
|
|
70
|
+
const related = (key) => key === path || key.startsWith(`${path}.`) || key.startsWith(`${path}[`) || path.startsWith(`${key}.`) || path.startsWith(`${key}[`);
|
|
71
|
+
for (const key of fieldSubscribers.keys()) if (related(key)) notifyField(key);
|
|
72
|
+
notify();
|
|
73
|
+
}
|
|
74
|
+
function setFieldValue(name, value) {
|
|
75
|
+
const nextValues = pathUtils.set(state.values, name, value);
|
|
76
|
+
valuesRevision += 1;
|
|
77
|
+
state = { ...state, values: nextValues, dirty: !valuesEqual(nextValues, state.initialValues) };
|
|
78
|
+
for (const field of options.fields ?? []) {
|
|
79
|
+
if (field.hidden && state.errors[field.name] && (typeof field.hidden === "function" ? field.hidden(nextValues) : field.hidden)) {
|
|
80
|
+
setFieldError(field.name, void 0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
notifyValuePath(name);
|
|
59
84
|
if (validateOnChange && validationAdapter && validationSchema) {
|
|
60
|
-
|
|
85
|
+
const revision = valuesRevision;
|
|
86
|
+
void runFieldValidation(name).catch((error) => {
|
|
87
|
+
if (revision === valuesRevision) setFieldError(name, error instanceof Error ? error.message : "Validation failed");
|
|
88
|
+
});
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
function setFieldTouched(name, touched = true) {
|
|
@@ -68,7 +96,10 @@ function createFormStore(options) {
|
|
|
68
96
|
notifyField(name);
|
|
69
97
|
notify();
|
|
70
98
|
if (validateOnBlur && touched && validationAdapter && validationSchema) {
|
|
71
|
-
|
|
99
|
+
const revision = valuesRevision;
|
|
100
|
+
void runFieldValidation(name).catch((error) => {
|
|
101
|
+
if (revision === valuesRevision) setFieldError(name, error instanceof Error ? error.message : "Validation failed");
|
|
102
|
+
});
|
|
72
103
|
}
|
|
73
104
|
}
|
|
74
105
|
function setFieldError(name, error) {
|
|
@@ -83,13 +114,47 @@ function createFormStore(options) {
|
|
|
83
114
|
notify();
|
|
84
115
|
}
|
|
85
116
|
function setValues(values) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
valuesRevision += 1;
|
|
118
|
+
state = { ...state, values, dirty: !valuesEqual(values, state.initialValues) };
|
|
119
|
+
notifyAllFields();
|
|
89
120
|
}
|
|
90
121
|
function setErrors(errors) {
|
|
91
122
|
state = { ...state, errors };
|
|
92
|
-
|
|
123
|
+
notifyAllFields();
|
|
124
|
+
}
|
|
125
|
+
function setFieldArrayValue(name, items, origins) {
|
|
126
|
+
const remap = (entries) => {
|
|
127
|
+
const result = {};
|
|
128
|
+
for (const [path, value] of Object.entries(entries)) {
|
|
129
|
+
if (!path.startsWith(name)) {
|
|
130
|
+
result[path] = value;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const suffix = path.slice(name.length);
|
|
134
|
+
const match = /^(?:\.(\d+)|\[(\d+)\])(?=\.|\[|$)/.exec(suffix);
|
|
135
|
+
if (!match) {
|
|
136
|
+
result[path] = value;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const oldIndex = Number(match[1] ?? match[2]);
|
|
140
|
+
const tail = suffix.slice(match[0].length);
|
|
141
|
+
for (let i = 0; i < origins.length; i++) {
|
|
142
|
+
if (origins[i] !== oldIndex) continue;
|
|
143
|
+
result[`${name}${match[1] !== void 0 ? `.${i}` : `[${i}]`}${tail}`] = value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
};
|
|
148
|
+
const values = pathUtils.set(state.values, name, items);
|
|
149
|
+
valuesRevision += 1;
|
|
150
|
+
state = {
|
|
151
|
+
...state,
|
|
152
|
+
values,
|
|
153
|
+
dirty: !valuesEqual(values, state.initialValues),
|
|
154
|
+
errors: remap(state.errors),
|
|
155
|
+
touched: remap(state.touched)
|
|
156
|
+
};
|
|
157
|
+
notifyValuePath(name);
|
|
93
158
|
}
|
|
94
159
|
function resetForm(nextValues) {
|
|
95
160
|
const newValues = nextValues ? { ...state.initialValues, ...nextValues } : structuredClone(state.initialValues);
|
|
@@ -100,23 +165,40 @@ function createFormStore(options) {
|
|
|
100
165
|
dirty: false,
|
|
101
166
|
isSubmitting: false,
|
|
102
167
|
submitCount: state.submitCount,
|
|
103
|
-
initialValues: state.initialValues
|
|
168
|
+
initialValues: nextValues ? structuredClone(newValues) : state.initialValues
|
|
104
169
|
};
|
|
105
|
-
|
|
170
|
+
valuesRevision += 1;
|
|
171
|
+
notifyAllFields();
|
|
106
172
|
}
|
|
107
173
|
async function validateForm() {
|
|
108
174
|
if (!validationAdapter || !validationSchema) return {};
|
|
175
|
+
const revision = valuesRevision;
|
|
176
|
+
const request = ++formRequest;
|
|
109
177
|
const errors = await validationAdapter.validate(validationSchema, state.values);
|
|
178
|
+
if (revision !== valuesRevision || request !== formRequest) return state.errors;
|
|
110
179
|
state = { ...state, errors };
|
|
111
180
|
notify();
|
|
181
|
+
for (const name of fieldSubscribers.keys()) notifyField(name);
|
|
112
182
|
return errors;
|
|
113
183
|
}
|
|
114
184
|
async function validateField(name) {
|
|
185
|
+
var _a;
|
|
115
186
|
if (!(validationAdapter == null ? void 0 : validationAdapter.validateField) || !validationSchema) return void 0;
|
|
187
|
+
const revision = valuesRevision;
|
|
188
|
+
const request = (fieldRequests.get(name) ?? 0) + 1;
|
|
189
|
+
fieldRequests.set(name, request);
|
|
190
|
+
const config = (_a = options.fields) == null ? void 0 : _a.find((field) => field.name === name);
|
|
191
|
+
if ((config == null ? void 0 : config.hidden) && (typeof config.hidden === "function" ? config.hidden(state.values) : config.hidden)) {
|
|
192
|
+
setFieldError(name, void 0);
|
|
193
|
+
return void 0;
|
|
194
|
+
}
|
|
116
195
|
const fieldValue = pathUtils.get(state.values, name);
|
|
117
196
|
const error = await validationAdapter.validateField(validationSchema, name, fieldValue);
|
|
118
|
-
|
|
119
|
-
|
|
197
|
+
if (revision === valuesRevision && request === fieldRequests.get(name)) {
|
|
198
|
+
setFieldError(name, error);
|
|
199
|
+
return error;
|
|
200
|
+
}
|
|
201
|
+
return state.errors[name];
|
|
120
202
|
}
|
|
121
203
|
async function runFieldValidation(name) {
|
|
122
204
|
if (validationAdapter == null ? void 0 : validationAdapter.validateField) {
|
|
@@ -126,30 +208,18 @@ function createFormStore(options) {
|
|
|
126
208
|
}
|
|
127
209
|
}
|
|
128
210
|
async function submitForm(onSubmit) {
|
|
211
|
+
if (state.isSubmitting) return;
|
|
129
212
|
state = { ...state, isSubmitting: true, submitCount: state.submitCount + 1 };
|
|
130
213
|
notify();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
state = { ...state, isSubmitting: false };
|
|
141
|
-
notify();
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const handler = onSubmit ?? options.onSubmit;
|
|
145
|
-
if (handler) {
|
|
146
|
-
try {
|
|
147
|
-
await handler(state.values);
|
|
148
|
-
} finally {
|
|
149
|
-
state = { ...state, isSubmitting: false };
|
|
150
|
-
notify();
|
|
151
|
-
}
|
|
152
|
-
} else {
|
|
214
|
+
try {
|
|
215
|
+
touchAllFields();
|
|
216
|
+
const revision = valuesRevision;
|
|
217
|
+
const errors = await validateForm();
|
|
218
|
+
for (const name of Object.keys(state.touched)) notifyField(name);
|
|
219
|
+
if (revision !== valuesRevision || Object.keys(errors).length > 0) return;
|
|
220
|
+
const handler = onSubmit ?? options.onSubmit;
|
|
221
|
+
if (handler) await handler(state.values);
|
|
222
|
+
} finally {
|
|
153
223
|
state = { ...state, isSubmitting: false };
|
|
154
224
|
notify();
|
|
155
225
|
}
|
|
@@ -163,6 +233,22 @@ function createFormStore(options) {
|
|
|
163
233
|
function getFieldTouched(name) {
|
|
164
234
|
return state.touched[name] ?? false;
|
|
165
235
|
}
|
|
236
|
+
function touchAllFields() {
|
|
237
|
+
const allTouched = {};
|
|
238
|
+
if (options.fields) {
|
|
239
|
+
for (const field of options.fields) {
|
|
240
|
+
allTouched[field.name] = true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const name of fieldSubscribers.keys()) {
|
|
244
|
+
allTouched[name] = true;
|
|
245
|
+
}
|
|
246
|
+
state = { ...state, touched: { ...state.touched, ...allTouched } };
|
|
247
|
+
for (const name of Object.keys(allTouched)) {
|
|
248
|
+
notifyField(name);
|
|
249
|
+
}
|
|
250
|
+
notify();
|
|
251
|
+
}
|
|
166
252
|
return {
|
|
167
253
|
getState,
|
|
168
254
|
subscribe,
|
|
@@ -172,10 +258,12 @@ function createFormStore(options) {
|
|
|
172
258
|
setFieldError,
|
|
173
259
|
setValues,
|
|
174
260
|
setErrors,
|
|
261
|
+
setFieldArrayValue,
|
|
175
262
|
resetForm,
|
|
176
263
|
validateForm,
|
|
177
264
|
validateField,
|
|
178
265
|
submitForm,
|
|
266
|
+
touchAllFields,
|
|
179
267
|
getFieldValue,
|
|
180
268
|
getFieldError,
|
|
181
269
|
getFieldTouched
|