@axenstudio/axen-form 0.1.17 → 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.
Files changed (128) hide show
  1. package/README.md +34 -3
  2. package/dist/adapters/yupAdapter.cjs +29 -10
  3. package/dist/adapters/yupAdapter.d.ts +1 -2
  4. package/dist/adapters/yupAdapter.js +29 -10
  5. package/dist/adapters/zodAdapter.cjs +29 -10
  6. package/dist/adapters/zodAdapter.d.ts +1 -2
  7. package/dist/adapters/zodAdapter.js +29 -10
  8. package/dist/core/dateUtils.d.ts +0 -1
  9. package/dist/core/formStore.cjs +110 -39
  10. package/dist/core/formStore.d.ts +1 -1
  11. package/dist/core/formStore.js +110 -39
  12. package/dist/core/numberUtils.d.ts +0 -1
  13. package/dist/core/pathUtils.d.ts +0 -1
  14. package/dist/core/payloadUtils.d.ts +0 -1
  15. package/dist/core/semanticValidation.cjs +69 -0
  16. package/dist/core/semanticValidation.d.ts +5 -0
  17. package/dist/core/semanticValidation.js +69 -0
  18. package/dist/core/validation.cjs +4 -3
  19. package/dist/core/validation.d.ts +0 -1
  20. package/dist/core/validation.js +4 -3
  21. package/dist/fields/AutocompleteField/AutocompleteField.d.ts +1 -2
  22. package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts +1 -2
  23. package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts +1 -2
  24. package/dist/fields/CheckboxField/CheckboxField.d.ts +1 -2
  25. package/dist/fields/ColorField/ColorField.d.ts +1 -2
  26. package/dist/fields/CurrencyField/CurrencyField.d.ts +1 -2
  27. package/dist/fields/DateField/DateField.d.ts +1 -2
  28. package/dist/fields/DateField/DatePicker.d.ts +1 -2
  29. package/dist/fields/DateTimeField/DateTimeField.d.ts +1 -2
  30. package/dist/fields/EmailField/EmailField.d.ts +1 -2
  31. package/dist/fields/NumberField/NumberField.d.ts +1 -2
  32. package/dist/fields/PasswordField/PasswordField.d.ts +1 -2
  33. package/dist/fields/PhoneField/PhoneField.d.ts +1 -2
  34. package/dist/fields/RadioField/RadioField.d.ts +1 -2
  35. package/dist/fields/SelectField/SelectField.d.ts +1 -2
  36. package/dist/fields/SliderField/SliderField.d.ts +1 -2
  37. package/dist/fields/SwitchField/SwitchField.d.ts +1 -2
  38. package/dist/fields/TextField/TextField.d.ts +1 -2
  39. package/dist/fields/TextareaField/TextareaField.d.ts +1 -2
  40. package/dist/fields/TimeField/TimeField.d.ts +1 -2
  41. package/dist/fields/TimeField/TimePicker.d.ts +1 -2
  42. package/dist/fields/defaultComponentMap.cjs +3 -0
  43. package/dist/fields/defaultComponentMap.d.ts +0 -1
  44. package/dist/fields/defaultComponentMap.js +3 -0
  45. package/dist/fields/defaultPlaceholders.d.ts +0 -1
  46. package/dist/fields/fieldBase.module.css.cjs +92 -70
  47. package/dist/fields/fieldBase.module.css.js +92 -70
  48. package/dist/fields/fieldUtils.d.ts +0 -1
  49. package/dist/fields/iconUtils.d.ts +0 -1
  50. package/dist/fields/icons.d.ts +3 -4
  51. package/dist/index.cjs +31 -22
  52. package/dist/index.d.ts +3 -2
  53. package/dist/index.js +32 -23
  54. package/dist/layout/Box/Box.d.ts +1 -2
  55. package/dist/layout/Divider/Divider.d.ts +1 -2
  56. package/dist/layout/Grid/Grid.d.ts +1 -2
  57. package/dist/layout/Spacer/Spacer.d.ts +1 -2
  58. package/dist/layout/Stack/Stack.d.ts +1 -2
  59. package/dist/layout/index.d.ts +0 -1
  60. package/dist/react/AxenArrayField.d.ts +1 -2
  61. package/dist/react/AxenField.d.ts +1 -2
  62. package/dist/react/AxenForm.cjs +29 -23
  63. package/dist/react/AxenForm.d.ts +0 -1
  64. package/dist/react/AxenForm.js +30 -24
  65. package/dist/react/FormContext.d.ts +0 -1
  66. package/dist/react/useAxenForm.cjs +11 -4
  67. package/dist/react/useAxenForm.d.ts +1 -2
  68. package/dist/react/useAxenForm.js +11 -4
  69. package/dist/react/useField.cjs +3 -0
  70. package/dist/react/useField.d.ts +0 -1
  71. package/dist/react/useField.js +3 -0
  72. package/dist/react/useFieldArray.cjs +19 -13
  73. package/dist/react/useFieldArray.d.ts +0 -1
  74. package/dist/react/useFieldArray.js +19 -13
  75. package/dist/react/useFormState.cjs +18 -0
  76. package/dist/react/useFormState.d.ts +5 -0
  77. package/dist/react/useFormState.js +18 -0
  78. package/dist/types.d.ts +18 -2
  79. package/package.json +5 -2
  80. package/dist/adapters/yupAdapter.d.ts.map +0 -1
  81. package/dist/adapters/zodAdapter.d.ts.map +0 -1
  82. package/dist/core/dateUtils.d.ts.map +0 -1
  83. package/dist/core/formStore.d.ts.map +0 -1
  84. package/dist/core/numberUtils.d.ts.map +0 -1
  85. package/dist/core/pathUtils.d.ts.map +0 -1
  86. package/dist/core/payloadUtils.d.ts.map +0 -1
  87. package/dist/core/validation.d.ts.map +0 -1
  88. package/dist/fields/AutocompleteField/AutocompleteField.d.ts.map +0 -1
  89. package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts.map +0 -1
  90. package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts.map +0 -1
  91. package/dist/fields/CheckboxField/CheckboxField.d.ts.map +0 -1
  92. package/dist/fields/ColorField/ColorField.d.ts.map +0 -1
  93. package/dist/fields/CurrencyField/CurrencyField.d.ts.map +0 -1
  94. package/dist/fields/DateField/DateField.d.ts.map +0 -1
  95. package/dist/fields/DateField/DatePicker.d.ts.map +0 -1
  96. package/dist/fields/DateTimeField/DateTimeField.d.ts.map +0 -1
  97. package/dist/fields/EmailField/EmailField.d.ts.map +0 -1
  98. package/dist/fields/NumberField/NumberField.d.ts.map +0 -1
  99. package/dist/fields/PasswordField/PasswordField.d.ts.map +0 -1
  100. package/dist/fields/PhoneField/PhoneField.d.ts.map +0 -1
  101. package/dist/fields/RadioField/RadioField.d.ts.map +0 -1
  102. package/dist/fields/SelectField/SelectField.d.ts.map +0 -1
  103. package/dist/fields/SliderField/SliderField.d.ts.map +0 -1
  104. package/dist/fields/SwitchField/SwitchField.d.ts.map +0 -1
  105. package/dist/fields/TextField/TextField.d.ts.map +0 -1
  106. package/dist/fields/TextareaField/TextareaField.d.ts.map +0 -1
  107. package/dist/fields/TimeField/TimeField.d.ts.map +0 -1
  108. package/dist/fields/TimeField/TimePicker.d.ts.map +0 -1
  109. package/dist/fields/defaultComponentMap.d.ts.map +0 -1
  110. package/dist/fields/defaultPlaceholders.d.ts.map +0 -1
  111. package/dist/fields/fieldUtils.d.ts.map +0 -1
  112. package/dist/fields/iconUtils.d.ts.map +0 -1
  113. package/dist/fields/icons.d.ts.map +0 -1
  114. package/dist/index.d.ts.map +0 -1
  115. package/dist/layout/Box/Box.d.ts.map +0 -1
  116. package/dist/layout/Divider/Divider.d.ts.map +0 -1
  117. package/dist/layout/Grid/Grid.d.ts.map +0 -1
  118. package/dist/layout/Spacer/Spacer.d.ts.map +0 -1
  119. package/dist/layout/Stack/Stack.d.ts.map +0 -1
  120. package/dist/layout/index.d.ts.map +0 -1
  121. package/dist/react/AxenArrayField.d.ts.map +0 -1
  122. package/dist/react/AxenField.d.ts.map +0 -1
  123. package/dist/react/AxenForm.d.ts.map +0 -1
  124. package/dist/react/FormContext.d.ts.map +0 -1
  125. package/dist/react/useAxenForm.d.ts.map +0 -1
  126. package/dist/react/useField.d.ts.map +0 -1
  127. package/dist/react/useFieldArray.d.ts.map +0 -1
  128. 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 auto-generate schemas from `FieldConfig` (required, min, max, type-aware schema). You can also provide custom `validation` on individual fields.
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
- shape[field.name] = field.validation;
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
- shape[field.name] = yup.boolean().oneOf([true], msg).required(msg);
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
- shape[field.name] = yup.number().required(msg).typeError(msg);
22
+ rule = yup.number().required(msg).typeError(msg);
21
23
  break;
22
24
  case "autocomplete-multi":
23
- shape[field.name] = yup.array().min(1, msg).required(msg);
25
+ rule = yup.array().min(1, msg).required(msg);
24
26
  break;
25
27
  default:
26
- shape[field.name] = yup.string().required(msg);
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
- return yup.object(shape);
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, { [name]: value });
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/yupAdapter';
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
- shape[field.name] = field.validation;
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
- shape[field.name] = yup.boolean().oneOf([true], msg).required(msg);
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
- shape[field.name] = yup.number().required(msg).typeError(msg);
20
+ rule = yup.number().required(msg).typeError(msg);
19
21
  break;
20
22
  case "autocomplete-multi":
21
- shape[field.name] = yup.array().min(1, msg).required(msg);
23
+ rule = yup.array().min(1, msg).required(msg);
22
24
  break;
23
25
  default:
24
- shape[field.name] = yup.string().required(msg);
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
- return yup.object(shape);
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, { [name]: value });
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
- shape[field.name] = field.validation;
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
- shape[field.name] = z.literal(true, { errorMap: () => ({ message: msg }) });
16
+ rule = z.literal(true, { errorMap: () => ({ message: msg }) });
16
17
  break;
17
18
  case "number":
18
19
  case "currency":
19
20
  case "slider":
20
- shape[field.name] = z.number({ required_error: msg, invalid_type_error: msg });
21
+ rule = z.number({ required_error: msg, invalid_type_error: msg });
21
22
  break;
22
23
  case "autocomplete-multi":
23
- shape[field.name] = z.array(z.unknown()).min(1, msg);
24
+ rule = z.array(z.unknown()).min(1, msg);
24
25
  break;
25
26
  default:
26
- shape[field.name] = z.string({ required_error: msg }).min(1, msg);
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
- return z.object(shape);
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
- const fieldSchema = zodSchema.shape[name];
52
- if (!fieldSchema) return void 0;
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 (_b = (_a = result.error) == null ? void 0 : _a.issues[0]) == null ? void 0 : _b.message;
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/zodAdapter';
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
- shape[field.name] = field.validation;
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
- shape[field.name] = z.literal(true, { errorMap: () => ({ message: msg }) });
14
+ rule = z.literal(true, { errorMap: () => ({ message: msg }) });
14
15
  break;
15
16
  case "number":
16
17
  case "currency":
17
18
  case "slider":
18
- shape[field.name] = z.number({ required_error: msg, invalid_type_error: msg });
19
+ rule = z.number({ required_error: msg, invalid_type_error: msg });
19
20
  break;
20
21
  case "autocomplete-multi":
21
- shape[field.name] = z.array(z.unknown()).min(1, msg);
22
+ rule = z.array(z.unknown()).min(1, msg);
22
23
  break;
23
24
  default:
24
- shape[field.name] = z.string({ required_error: msg }).min(1, msg);
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
- return z.object(shape);
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
- const fieldSchema = zodSchema.shape[name];
50
- if (!fieldSchema) return void 0;
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 (_b = (_a = result.error) == null ? void 0 : _a.issues[0]) == null ? void 0 : _b.message;
72
+ return (_c = (_b = result.error) == null ? void 0 : _b.issues[0]) == null ? void 0 : _c.message;
54
73
  }
55
74
  };
56
75
  }
@@ -25,4 +25,3 @@ export declare function toISOTimeString(date: Date): string;
25
25
  * Falls back to 'en-US' in non-browser environments (SSR/tests).
26
26
  */
27
27
  export declare function getDefaultLocale(): string;
28
- //# sourceMappingURL=dateUtils.d.ts.map
@@ -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 setFieldValue(name, value) {
52
- state = {
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);
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);
58
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
- runFieldValidation(name);
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
- runFieldValidation(name);
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
- state = { ...state, values, dirty: true };
87
- notify();
88
- subscribers.forEach((fn) => fn());
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
- notify();
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,26 +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
- notify();
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();
112
- for (const name of fieldSubscribers.keys()) {
113
- notifyField(name);
114
- }
181
+ for (const name of fieldSubscribers.keys()) notifyField(name);
115
182
  return errors;
116
183
  }
117
184
  async function validateField(name) {
185
+ var _a;
118
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
+ }
119
195
  const fieldValue = pathUtils.get(state.values, name);
120
196
  const error = await validationAdapter.validateField(validationSchema, name, fieldValue);
121
- setFieldError(name, error);
122
- return error;
197
+ if (revision === valuesRevision && request === fieldRequests.get(name)) {
198
+ setFieldError(name, error);
199
+ return error;
200
+ }
201
+ return state.errors[name];
123
202
  }
124
203
  async function runFieldValidation(name) {
125
204
  if (validationAdapter == null ? void 0 : validationAdapter.validateField) {
@@ -129,27 +208,18 @@ function createFormStore(options) {
129
208
  }
130
209
  }
131
210
  async function submitForm(onSubmit) {
211
+ if (state.isSubmitting) return;
132
212
  state = { ...state, isSubmitting: true, submitCount: state.submitCount + 1 };
133
213
  notify();
134
- touchAllFields();
135
- const errors = await validateForm();
136
- for (const name of Object.keys(state.touched)) {
137
- notifyField(name);
138
- }
139
- if (Object.keys(errors).length > 0) {
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
  }
@@ -188,6 +258,7 @@ function createFormStore(options) {
188
258
  setFieldError,
189
259
  setValues,
190
260
  setErrors,
261
+ setFieldArrayValue,
191
262
  resetForm,
192
263
  validateForm,
193
264
  validateField,
@@ -27,6 +27,7 @@ export declare function createFormStore<TValues extends Record<string, unknown>>
27
27
  setFieldError: (name: string, error: string | undefined) => void;
28
28
  setValues: (values: TValues) => void;
29
29
  setErrors: (errors: Record<string, string>) => void;
30
+ setFieldArrayValue: (name: string, items: unknown[], origins: Array<number | null>) => void;
30
31
  resetForm: (nextValues?: Partial<TValues>) => void;
31
32
  validateForm: () => Promise<Record<string, string>>;
32
33
  validateField: (name: string) => Promise<string | undefined>;
@@ -37,4 +38,3 @@ export declare function createFormStore<TValues extends Record<string, unknown>>
37
38
  getFieldTouched: (name: string) => boolean;
38
39
  };
39
40
  export type FormStore<TValues extends Record<string, unknown> = Record<string, unknown>> = ReturnType<typeof createFormStore<TValues>>;
40
- //# sourceMappingURL=formStore.d.ts.map