@bquery/bquery 1.7.0 → 1.8.2

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 (262) hide show
  1. package/README.md +760 -716
  2. package/dist/{a11y-C5QOVvRn.js → a11y-DVBCy09c.js} +3 -3
  3. package/dist/a11y-DVBCy09c.js.map +1 -0
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/component/library.d.ts.map +1 -1
  6. package/dist/{component-CuuTijA6.js → component-L3-JfOFz.js} +5 -5
  7. package/dist/component-L3-JfOFz.js.map +1 -0
  8. package/dist/component.es.mjs +1 -1
  9. package/dist/{config-BW35FKuA.js → config-DhT9auRm.js} +1 -1
  10. package/dist/{config-BW35FKuA.js.map → config-DhT9auRm.js.map} +1 -1
  11. package/dist/{constraints-3lV9yyBw.js → constraints-D5RHQLmP.js} +1 -1
  12. package/dist/constraints-D5RHQLmP.js.map +1 -0
  13. package/dist/core/collection.d.ts +86 -0
  14. package/dist/core/collection.d.ts.map +1 -1
  15. package/dist/core/element.d.ts +28 -0
  16. package/dist/core/element.d.ts.map +1 -1
  17. package/dist/core/shared.d.ts +6 -0
  18. package/dist/core/shared.d.ts.map +1 -1
  19. package/dist/core-DdtZHzsS.js +168 -0
  20. package/dist/core-DdtZHzsS.js.map +1 -0
  21. package/dist/{core-Cjl7GUu8.js → core-EMYSLzaT.js} +289 -259
  22. package/dist/core-EMYSLzaT.js.map +1 -0
  23. package/dist/core.es.mjs +48 -47
  24. package/dist/{custom-directives-7wAShnnd.js → custom-directives-Dr4C5lVV.js} +1 -1
  25. package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
  26. package/dist/{devtools-D2fQLhDN.js → devtools-BhB2iDPT.js} +2 -2
  27. package/dist/devtools-BhB2iDPT.js.map +1 -0
  28. package/dist/devtools.es.mjs +1 -1
  29. package/dist/{dnd-B8EgyzaI.js → dnd-NwZBYh4l.js} +1 -1
  30. package/dist/dnd-NwZBYh4l.js.map +1 -0
  31. package/dist/dnd.es.mjs +1 -1
  32. package/dist/{env-NeVmr4Gf.js → env-CTdvLaH2.js} +1 -1
  33. package/dist/env-CTdvLaH2.js.map +1 -0
  34. package/dist/forms/create-form.d.ts.map +1 -1
  35. package/dist/forms/index.d.ts +3 -2
  36. package/dist/forms/index.d.ts.map +1 -1
  37. package/dist/forms/types.d.ts +46 -0
  38. package/dist/forms/types.d.ts.map +1 -1
  39. package/dist/forms/use-field.d.ts +34 -0
  40. package/dist/forms/use-field.d.ts.map +1 -0
  41. package/dist/forms/validators.d.ts +25 -0
  42. package/dist/forms/validators.d.ts.map +1 -1
  43. package/dist/forms-UcRHsYxC.js +227 -0
  44. package/dist/forms-UcRHsYxC.js.map +1 -0
  45. package/dist/forms.es.mjs +14 -12
  46. package/dist/full.d.ts +17 -26
  47. package/dist/full.d.ts.map +1 -1
  48. package/dist/full.es.mjs +206 -181
  49. package/dist/full.iife.js +33 -33
  50. package/dist/full.iife.js.map +1 -1
  51. package/dist/full.umd.js +33 -33
  52. package/dist/full.umd.js.map +1 -1
  53. package/dist/function-Cybd57JV.js +33 -0
  54. package/dist/function-Cybd57JV.js.map +1 -0
  55. package/dist/{i18n-BnnhTFOS.js → i18n-kuF6Ekj6.js} +3 -3
  56. package/dist/i18n-kuF6Ekj6.js.map +1 -0
  57. package/dist/i18n.es.mjs +1 -1
  58. package/dist/index.es.mjs +251 -228
  59. package/dist/media/breakpoints.d.ts.map +1 -1
  60. package/dist/media/types.d.ts +2 -2
  61. package/dist/media/types.d.ts.map +1 -1
  62. package/dist/{media-Di2Ta22s.js → media-i-fB5WxI.js} +3 -3
  63. package/dist/media-i-fB5WxI.js.map +1 -0
  64. package/dist/media.es.mjs +1 -1
  65. package/dist/{motion-qPj_TYGv.js → motion-BJsAuULb.js} +2 -2
  66. package/dist/motion-BJsAuULb.js.map +1 -0
  67. package/dist/motion.es.mjs +1 -1
  68. package/dist/{mount-SM07RUa6.js → mount-B4Y8bk8Z.js} +5 -5
  69. package/dist/mount-B4Y8bk8Z.js.map +1 -0
  70. package/dist/{platform-CPbCprb6.js → platform-Dw2gE3zI.js} +3 -3
  71. package/dist/{platform-CPbCprb6.js.map → platform-Dw2gE3zI.js.map} +1 -1
  72. package/dist/platform.es.mjs +2 -2
  73. package/dist/plugin/registry.d.ts.map +1 -1
  74. package/dist/{plugin-cPoOHFLY.js → plugin-C2WuC8SF.js} +20 -18
  75. package/dist/plugin-C2WuC8SF.js.map +1 -0
  76. package/dist/plugin.es.mjs +1 -1
  77. package/dist/reactive/async-data.d.ts +28 -3
  78. package/dist/reactive/async-data.d.ts.map +1 -1
  79. package/dist/reactive/computed.d.ts +3 -0
  80. package/dist/reactive/computed.d.ts.map +1 -1
  81. package/dist/reactive/effect.d.ts +3 -0
  82. package/dist/reactive/effect.d.ts.map +1 -1
  83. package/dist/reactive/http.d.ts +194 -0
  84. package/dist/reactive/http.d.ts.map +1 -0
  85. package/dist/reactive/index.d.ts +2 -2
  86. package/dist/reactive/index.d.ts.map +1 -1
  87. package/dist/reactive/pagination.d.ts +126 -0
  88. package/dist/reactive/pagination.d.ts.map +1 -0
  89. package/dist/reactive/polling.d.ts +55 -0
  90. package/dist/reactive/polling.d.ts.map +1 -0
  91. package/dist/reactive/readonly.d.ts +20 -1
  92. package/dist/reactive/readonly.d.ts.map +1 -1
  93. package/dist/reactive/rest.d.ts +293 -0
  94. package/dist/reactive/rest.d.ts.map +1 -0
  95. package/dist/reactive/scope.d.ts +140 -0
  96. package/dist/reactive/scope.d.ts.map +1 -0
  97. package/dist/reactive/signal.d.ts +16 -2
  98. package/dist/reactive/signal.d.ts.map +1 -1
  99. package/dist/reactive/to-value.d.ts +57 -0
  100. package/dist/reactive/to-value.d.ts.map +1 -0
  101. package/dist/reactive/websocket.d.ts +285 -0
  102. package/dist/reactive/websocket.d.ts.map +1 -0
  103. package/dist/reactive-DwkhUJfP.js +1148 -0
  104. package/dist/reactive-DwkhUJfP.js.map +1 -0
  105. package/dist/reactive.es.mjs +38 -19
  106. package/dist/{registry-CWf368tT.js → registry-B08iilIh.js} +1 -1
  107. package/dist/{registry-CWf368tT.js.map → registry-B08iilIh.js.map} +1 -1
  108. package/dist/router/constraints.d.ts.map +1 -1
  109. package/dist/router/index.d.ts +1 -1
  110. package/dist/router/index.d.ts.map +1 -1
  111. package/dist/router/router.d.ts.map +1 -1
  112. package/dist/router/state.d.ts +25 -2
  113. package/dist/router/state.d.ts.map +1 -1
  114. package/dist/router-CQikC9Ed.js +492 -0
  115. package/dist/router-CQikC9Ed.js.map +1 -0
  116. package/dist/router.es.mjs +9 -8
  117. package/dist/ssr/hydrate.d.ts.map +1 -1
  118. package/dist/{ssr-B2qd_WBB.js → ssr-_dAcGdzu.js} +4 -4
  119. package/dist/ssr-_dAcGdzu.js.map +1 -0
  120. package/dist/ssr.es.mjs +1 -1
  121. package/dist/store/persisted.d.ts.map +1 -1
  122. package/dist/{store-DWpyH6p5.js → store-Cb3gPRve.js} +7 -7
  123. package/dist/store-Cb3gPRve.js.map +1 -0
  124. package/dist/store.es.mjs +2 -2
  125. package/dist/storybook.es.mjs.map +1 -1
  126. package/dist/{testing-CsqjNUyy.js → testing-C5Sjfsna.js} +8 -8
  127. package/dist/testing-C5Sjfsna.js.map +1 -0
  128. package/dist/testing.es.mjs +1 -1
  129. package/dist/{type-guards-Do9DWgNp.js → type-guards-BMX2c0LP.js} +1 -1
  130. package/dist/{type-guards-Do9DWgNp.js.map → type-guards-BMX2c0LP.js.map} +1 -1
  131. package/dist/untrack-D0fnO5k2.js +36 -0
  132. package/dist/untrack-D0fnO5k2.js.map +1 -0
  133. package/dist/view/custom-directives.d.ts.map +1 -1
  134. package/dist/view.es.mjs +4 -4
  135. package/package.json +178 -177
  136. package/src/a11y/announce.ts +131 -131
  137. package/src/a11y/audit.ts +314 -314
  138. package/src/a11y/index.ts +68 -68
  139. package/src/a11y/media-preferences.ts +255 -255
  140. package/src/a11y/roving-tab-index.ts +164 -164
  141. package/src/a11y/skip-link.ts +255 -255
  142. package/src/a11y/trap-focus.ts +184 -184
  143. package/src/a11y/types.ts +183 -183
  144. package/src/component/component.ts +599 -599
  145. package/src/component/html.ts +153 -153
  146. package/src/component/index.ts +52 -52
  147. package/src/component/library.ts +540 -542
  148. package/src/component/scope.ts +212 -212
  149. package/src/component/types.ts +310 -310
  150. package/src/core/collection.ts +876 -707
  151. package/src/core/element.ts +1015 -981
  152. package/src/core/env.ts +60 -60
  153. package/src/core/index.ts +49 -49
  154. package/src/core/shared.ts +77 -62
  155. package/src/core/utils/index.ts +148 -148
  156. package/src/devtools/devtools.ts +410 -410
  157. package/src/devtools/index.ts +48 -48
  158. package/src/devtools/types.ts +104 -104
  159. package/src/dnd/draggable.ts +296 -296
  160. package/src/dnd/droppable.ts +228 -228
  161. package/src/dnd/index.ts +62 -62
  162. package/src/dnd/sortable.ts +307 -307
  163. package/src/dnd/types.ts +293 -293
  164. package/src/forms/create-form.ts +320 -278
  165. package/src/forms/index.ts +70 -65
  166. package/src/forms/types.ts +203 -154
  167. package/src/forms/use-field.ts +231 -0
  168. package/src/forms/validators.ts +294 -265
  169. package/src/full.ts +554 -480
  170. package/src/i18n/formatting.ts +67 -67
  171. package/src/i18n/i18n.ts +200 -200
  172. package/src/i18n/index.ts +67 -67
  173. package/src/i18n/translate.ts +182 -182
  174. package/src/i18n/types.ts +171 -171
  175. package/src/index.ts +108 -108
  176. package/src/media/battery.ts +116 -116
  177. package/src/media/breakpoints.ts +129 -131
  178. package/src/media/clipboard.ts +80 -80
  179. package/src/media/device-sensors.ts +158 -158
  180. package/src/media/geolocation.ts +119 -119
  181. package/src/media/index.ts +76 -76
  182. package/src/media/media-query.ts +92 -92
  183. package/src/media/network.ts +115 -115
  184. package/src/media/types.ts +177 -177
  185. package/src/media/viewport.ts +84 -84
  186. package/src/motion/index.ts +57 -57
  187. package/src/motion/morph.ts +151 -151
  188. package/src/motion/parallax.ts +120 -120
  189. package/src/motion/reduced-motion.ts +66 -66
  190. package/src/motion/types.ts +271 -271
  191. package/src/motion/typewriter.ts +164 -164
  192. package/src/plugin/index.ts +37 -37
  193. package/src/plugin/registry.ts +284 -269
  194. package/src/plugin/types.ts +137 -137
  195. package/src/reactive/async-data.ts +250 -29
  196. package/src/reactive/computed.ts +144 -130
  197. package/src/reactive/effect.ts +29 -6
  198. package/src/reactive/http.ts +790 -0
  199. package/src/reactive/index.ts +60 -0
  200. package/src/reactive/pagination.ts +317 -0
  201. package/src/reactive/polling.ts +179 -0
  202. package/src/reactive/readonly.ts +52 -8
  203. package/src/reactive/rest.ts +859 -0
  204. package/src/reactive/scope.ts +276 -0
  205. package/src/reactive/signal.ts +61 -1
  206. package/src/reactive/to-value.ts +71 -0
  207. package/src/reactive/websocket.ts +849 -0
  208. package/src/router/bq-link.ts +279 -279
  209. package/src/router/constraints.ts +204 -201
  210. package/src/router/index.ts +49 -49
  211. package/src/router/match.ts +312 -312
  212. package/src/router/path-pattern.ts +52 -52
  213. package/src/router/query.ts +38 -38
  214. package/src/router/router.ts +421 -402
  215. package/src/router/state.ts +51 -3
  216. package/src/router/types.ts +139 -139
  217. package/src/router/use-route.ts +68 -68
  218. package/src/router/utils.ts +157 -157
  219. package/src/security/index.ts +12 -12
  220. package/src/ssr/hydrate.ts +84 -82
  221. package/src/ssr/index.ts +70 -70
  222. package/src/ssr/render.ts +508 -508
  223. package/src/ssr/serialize.ts +296 -296
  224. package/src/ssr/types.ts +81 -81
  225. package/src/store/create-store.ts +467 -467
  226. package/src/store/index.ts +27 -27
  227. package/src/store/persisted.ts +245 -249
  228. package/src/store/types.ts +247 -247
  229. package/src/store/utils.ts +135 -135
  230. package/src/storybook/index.ts +480 -480
  231. package/src/testing/index.ts +42 -42
  232. package/src/testing/testing.ts +593 -593
  233. package/src/testing/types.ts +170 -170
  234. package/src/view/custom-directives.ts +28 -30
  235. package/src/view/evaluate.ts +292 -292
  236. package/src/view/process.ts +108 -108
  237. package/dist/a11y-C5QOVvRn.js.map +0 -1
  238. package/dist/component-CuuTijA6.js.map +0 -1
  239. package/dist/constraints-3lV9yyBw.js.map +0 -1
  240. package/dist/core-Cjl7GUu8.js.map +0 -1
  241. package/dist/core-DnlyjbF2.js +0 -112
  242. package/dist/core-DnlyjbF2.js.map +0 -1
  243. package/dist/custom-directives-7wAShnnd.js.map +0 -1
  244. package/dist/devtools-D2fQLhDN.js.map +0 -1
  245. package/dist/dnd-B8EgyzaI.js.map +0 -1
  246. package/dist/env-NeVmr4Gf.js.map +0 -1
  247. package/dist/forms-C3yovgH9.js +0 -141
  248. package/dist/forms-C3yovgH9.js.map +0 -1
  249. package/dist/i18n-BnnhTFOS.js.map +0 -1
  250. package/dist/media-Di2Ta22s.js.map +0 -1
  251. package/dist/motion-qPj_TYGv.js.map +0 -1
  252. package/dist/mount-SM07RUa6.js.map +0 -1
  253. package/dist/plugin-cPoOHFLY.js.map +0 -1
  254. package/dist/reactive-Cfv0RK6x.js +0 -233
  255. package/dist/reactive-Cfv0RK6x.js.map +0 -1
  256. package/dist/router-BrthaP_z.js +0 -473
  257. package/dist/router-BrthaP_z.js.map +0 -1
  258. package/dist/ssr-B2qd_WBB.js.map +0 -1
  259. package/dist/store-DWpyH6p5.js.map +0 -1
  260. package/dist/testing-CsqjNUyy.js.map +0 -1
  261. package/dist/untrack-DJVQQ2WM.js +0 -33
  262. package/dist/untrack-DJVQQ2WM.js.map +0 -1
@@ -1,65 +1,70 @@
1
- /**
2
- * Form handling module for bQuery.js.
3
- *
4
- * Provides a reactive, TypeScript-first form API with field-level
5
- * and cross-field validation, dirty/touched tracking, and submission
6
- * handling — all backed by bQuery's signal-based reactivity system.
7
- *
8
- * @module bquery/forms
9
- *
10
- * @example
11
- * ```ts
12
- * import { createForm, required, email, min } from '@bquery/bquery/forms';
13
- *
14
- * const form = createForm({
15
- * fields: {
16
- * name: { initialValue: '', validators: [required()] },
17
- * email: { initialValue: '', validators: [required(), email()] },
18
- * age: { initialValue: 0, validators: [min(18, 'Must be 18+')] },
19
- * },
20
- * onSubmit: async (values) => {
21
- * await fetch('/api/register', {
22
- * method: 'POST',
23
- * body: JSON.stringify(values),
24
- * });
25
- * },
26
- * });
27
- *
28
- * // Reactive access
29
- * console.log(form.isValid.value); // boolean
30
- * console.log(form.fields.name.error.value); // '' or error message
31
- *
32
- * // Submit
33
- * await form.handleSubmit();
34
- * ```
35
- */
36
-
37
- export { createForm } from './create-form';
38
-
39
- export {
40
- custom,
41
- customAsync,
42
- email,
43
- max,
44
- maxLength,
45
- min,
46
- minLength,
47
- pattern,
48
- required,
49
- url,
50
- } from './validators';
51
-
52
- export type {
53
- AsyncValidator,
54
- CrossFieldValidator,
55
- FieldConfig,
56
- Form,
57
- FormConfig,
58
- FormErrors,
59
- FormField,
60
- FormFields,
61
- SubmitHandler,
62
- SyncValidator,
63
- ValidationResult,
64
- Validator,
65
- } from './types';
1
+ /**
2
+ * Form handling module for bQuery.js.
3
+ *
4
+ * Provides a reactive, TypeScript-first form API with field-level
5
+ * and cross-field validation, dirty/touched tracking, and submission
6
+ * handling — all backed by bQuery's signal-based reactivity system.
7
+ *
8
+ * @module bquery/forms
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { createForm, required, email, min } from '@bquery/bquery/forms';
13
+ *
14
+ * const form = createForm({
15
+ * fields: {
16
+ * name: { initialValue: '', validators: [required()] },
17
+ * email: { initialValue: '', validators: [required(), email()] },
18
+ * age: { initialValue: 0, validators: [min(18, 'Must be 18+')] },
19
+ * },
20
+ * onSubmit: async (values) => {
21
+ * await fetch('/api/register', {
22
+ * method: 'POST',
23
+ * body: JSON.stringify(values),
24
+ * });
25
+ * },
26
+ * });
27
+ *
28
+ * // Reactive access
29
+ * console.log(form.isValid.value); // boolean
30
+ * console.log(form.fields.name.error.value); // '' or error message
31
+ *
32
+ * // Submit
33
+ * await form.handleSubmit();
34
+ * ```
35
+ */
36
+
37
+ export { createForm } from './create-form';
38
+ export { useFormField } from './use-field';
39
+
40
+ export {
41
+ custom,
42
+ customAsync,
43
+ email,
44
+ matchField,
45
+ max,
46
+ maxLength,
47
+ min,
48
+ minLength,
49
+ pattern,
50
+ required,
51
+ url,
52
+ } from './validators';
53
+
54
+ export type {
55
+ AsyncValidator,
56
+ CrossFieldValidator,
57
+ FieldConfig,
58
+ Form,
59
+ FormConfig,
60
+ FormErrors,
61
+ FormField,
62
+ FormFieldValidationMode,
63
+ FormFields,
64
+ SubmitHandler,
65
+ SyncValidator,
66
+ UseFormFieldOptions,
67
+ UseFormFieldReturn,
68
+ ValidationResult,
69
+ Validator,
70
+ } from './types';
@@ -1,154 +1,203 @@
1
- /**
2
- * Form module types and interfaces.
3
- *
4
- * @module bquery/forms
5
- */
6
-
7
- import type { Computed, Signal } from '../reactive/index';
8
-
9
- /**
10
- * Result of a single validation rule.
11
- * A string indicates an error message; `true` or `undefined` means valid.
12
- */
13
- export type ValidationResult = string | true | undefined;
14
-
15
- /**
16
- * Synchronous validator function.
17
- *
18
- * @param value - The current field value
19
- * @returns A validation result — `true` / `undefined` for valid, or an error string
20
- */
21
- export type SyncValidator<T = unknown> = (value: T) => ValidationResult;
22
-
23
- /**
24
- * Asynchronous validator function.
25
- *
26
- * @param value - The current field value
27
- * @returns A promise resolving to a validation result
28
- */
29
- export type AsyncValidator<T = unknown> = (value: T) => Promise<ValidationResult>;
30
-
31
- /**
32
- * Either a sync or async validator.
33
- */
34
- export type Validator<T = unknown> = SyncValidator<T> | AsyncValidator<T>;
35
-
36
- /**
37
- * Configuration for a single form field.
38
- *
39
- * @template T - The type of the field value
40
- */
41
- export type FieldConfig<T = unknown> = {
42
- /** Initial value for this field */
43
- initialValue: T;
44
- /** Validation rules applied in order; stops at first failure */
45
- validators?: Validator<T>[];
46
- };
47
-
48
- /**
49
- * Reactive state for a single form field.
50
- *
51
- * @template T - The type of the field value
52
- */
53
- export type FormField<T = unknown> = {
54
- /** Reactive signal holding the current value */
55
- value: Signal<T>;
56
- /** Reactive signal for the first validation error (empty string when valid) */
57
- error: Signal<string>;
58
- /** Whether the field value differs from its initial value */
59
- isDirty: Computed<boolean>;
60
- /** Whether the field has been interacted with (blur / explicit touch) */
61
- isTouched: Signal<boolean>;
62
- /** Whether the field has never been modified */
63
- isPristine: Computed<boolean>;
64
- /** Mark the field as touched */
65
- touch: () => void;
66
- /** Reset the field to its initial value and clear errors */
67
- reset: () => void;
68
- };
69
-
70
- /**
71
- * Map of field names to their reactive field state.
72
- */
73
- export type FormFields<T extends Record<string, unknown>> = {
74
- [K in keyof T]: FormField<T[K]>;
75
- };
76
-
77
- /**
78
- * Map of field names to their error strings (reactive signals).
79
- */
80
- export type FormErrors<T extends Record<string, unknown>> = {
81
- [K in keyof T]: Signal<string>;
82
- };
83
-
84
- /**
85
- * Cross-field validation function.
86
- * Receives all current field values and returns a map of field name → error message,
87
- * or an empty object / undefined if all fields are valid.
88
- */
89
- export type CrossFieldValidator<T extends Record<string, unknown>> = (
90
- values: T
91
- ) =>
92
- | Partial<Record<keyof T, string>>
93
- | undefined
94
- | Promise<Partial<Record<keyof T, string>> | undefined>;
95
-
96
- /**
97
- * Submit handler function.
98
- *
99
- * @template T - Shape of the form values
100
- */
101
- export type SubmitHandler<T extends Record<string, unknown>> = (values: T) => void | Promise<void>;
102
-
103
- /**
104
- * Configuration for `createForm()`.
105
- *
106
- * @template T - Shape of the form values
107
- *
108
- * @example
109
- * ```ts
110
- * const config: FormConfig<{ name: string; age: number }> = {
111
- * fields: {
112
- * name: { initialValue: '', validators: [required('Name is required')] },
113
- * age: { initialValue: 0, validators: [min(1, 'Must be positive')] },
114
- * },
115
- * onSubmit: (values) => console.log(values),
116
- * };
117
- * ```
118
- */
119
- export type FormConfig<T extends Record<string, unknown>> = {
120
- /** Per-field configuration with initial values and validators */
121
- fields: { [K in keyof T]: FieldConfig<T[K]> };
122
- /** Optional cross-field validators run after per-field validation */
123
- crossValidators?: CrossFieldValidator<T>[];
124
- /** Callback invoked on successful form submission */
125
- onSubmit?: SubmitHandler<T>;
126
- };
127
-
128
- /**
129
- * Return value of `createForm()`.
130
- *
131
- * @template T - Shape of the form values
132
- */
133
- export type Form<T extends Record<string, unknown>> = {
134
- /** Reactive field objects keyed by field name */
135
- fields: FormFields<T>;
136
- /** Shorthand error signals keyed by field name */
137
- errors: FormErrors<T>;
138
- /** Computed signal: `true` when all fields pass validation */
139
- isValid: Computed<boolean>;
140
- /** Computed signal: `true` when any field value differs from initial */
141
- isDirty: Computed<boolean>;
142
- /** Reactive signal: `true` while the submit handler is executing */
143
- isSubmitting: Signal<boolean>;
144
- /** Validate all fields and, if valid, call the `onSubmit` handler */
145
- handleSubmit: () => Promise<void>;
146
- /** Validate a single field by name */
147
- validateField: (name: keyof T & string) => Promise<void>;
148
- /** Validate all fields without submitting */
149
- validate: () => Promise<boolean>;
150
- /** Reset the entire form to initial values */
151
- reset: () => void;
152
- /** Get a snapshot of all current field values */
153
- getValues: () => T;
154
- };
1
+ /**
2
+ * Form module types and interfaces.
3
+ *
4
+ * @module bquery/forms
5
+ */
6
+
7
+ import type { Computed, Signal } from '../reactive/index';
8
+
9
+ /**
10
+ * Result of a single validation rule.
11
+ * A string indicates an error message; `true` or `undefined` means valid.
12
+ */
13
+ export type ValidationResult = string | true | undefined;
14
+
15
+ /**
16
+ * Synchronous validator function.
17
+ *
18
+ * @param value - The current field value
19
+ * @returns A validation result — `true` / `undefined` for valid, or an error string
20
+ */
21
+ export type SyncValidator<T = unknown> = (value: T) => ValidationResult;
22
+
23
+ /**
24
+ * Asynchronous validator function.
25
+ *
26
+ * @param value - The current field value
27
+ * @returns A promise resolving to a validation result
28
+ */
29
+ export type AsyncValidator<T = unknown> = (value: T) => Promise<ValidationResult>;
30
+
31
+ /**
32
+ * Either a sync or async validator.
33
+ */
34
+ export type Validator<T = unknown> = SyncValidator<T> | AsyncValidator<T>;
35
+
36
+ /**
37
+ * Configuration for a single form field.
38
+ *
39
+ * @template T - The type of the field value
40
+ */
41
+ export type FieldConfig<T = unknown> = {
42
+ /** Initial value for this field */
43
+ initialValue: T;
44
+ /** Validation rules applied in order; stops at first failure */
45
+ validators?: Validator<T>[];
46
+ };
47
+
48
+ /**
49
+ * Reactive state for a single form field.
50
+ *
51
+ * @template T - The type of the field value
52
+ */
53
+ export type FormField<T = unknown> = {
54
+ /** Reactive signal holding the current value */
55
+ value: Signal<T>;
56
+ /** Reactive signal for the first validation error (empty string when valid) */
57
+ error: Signal<string>;
58
+ /** Whether the field value differs from its initial value */
59
+ isDirty: Computed<boolean>;
60
+ /** Whether the field has been interacted with (blur / explicit touch) */
61
+ isTouched: Signal<boolean>;
62
+ /** Whether the field has never been modified */
63
+ isPristine: Computed<boolean>;
64
+ /** Mark the field as touched */
65
+ touch: () => void;
66
+ /** Reset the field to its initial value and clear errors */
67
+ reset: () => void;
68
+ };
69
+
70
+ /**
71
+ * Controls when {@link useFormField} runs validation automatically.
72
+ */
73
+ export type FormFieldValidationMode = 'manual' | 'change' | 'blur' | 'both';
74
+
75
+ /**
76
+ * Configuration for {@link useFormField}.
77
+ *
78
+ * @template T - The type of the field value
79
+ */
80
+ export type UseFormFieldOptions<T = unknown> = {
81
+ /** Validation rules applied in order; stops at first failure */
82
+ validators?: Validator<T>[];
83
+ /** When validation should run automatically. Defaults to `'manual'`. */
84
+ validateOn?: FormFieldValidationMode;
85
+ /** Delay automatic validation by the given milliseconds. Defaults to `0`. */
86
+ debounceMs?: number;
87
+ /** Initial error message for the field. Defaults to an empty string. */
88
+ initialError?: string;
89
+ };
90
+
91
+ /**
92
+ * Return value of {@link useFormField}.
93
+ *
94
+ * Extends the standard field state with validation helpers for standalone field usage.
95
+ *
96
+ * @template T - The type of the field value
97
+ */
98
+ export type UseFormFieldReturn<T = unknown> = FormField<T> & {
99
+ /** Whether the current field has no validation error */
100
+ isValid: Computed<boolean>;
101
+ /** Reactive signal: `true` while async validation is still running */
102
+ isValidating: Signal<boolean>;
103
+ /** Validate the current field value immediately */
104
+ validate: () => Promise<boolean>;
105
+ /** Cancel pending timers and automatic validation subscriptions */
106
+ destroy: () => void;
107
+ };
108
+
109
+ /**
110
+ * Map of field names to their reactive field state.
111
+ */
112
+ export type FormFields<T extends Record<string, unknown>> = {
113
+ [K in keyof T]: FormField<T[K]>;
114
+ };
115
+
116
+ /**
117
+ * Map of field names to their error strings (reactive signals).
118
+ */
119
+ export type FormErrors<T extends Record<string, unknown>> = {
120
+ [K in keyof T]: Signal<string>;
121
+ };
122
+
123
+ /**
124
+ * Cross-field validation function.
125
+ * Receives all current field values and returns a map of field name → error message,
126
+ * or an empty object / undefined if all fields are valid.
127
+ */
128
+ export type CrossFieldValidator<T extends Record<string, unknown>> = (
129
+ values: T
130
+ ) =>
131
+ | Partial<Record<keyof T, string>>
132
+ | undefined
133
+ | Promise<Partial<Record<keyof T, string>> | undefined>;
134
+
135
+ /**
136
+ * Submit handler function.
137
+ *
138
+ * @template T - Shape of the form values
139
+ */
140
+ export type SubmitHandler<T extends Record<string, unknown>> = (values: T) => void | Promise<void>;
141
+
142
+ /**
143
+ * Configuration for `createForm()`.
144
+ *
145
+ * @template T - Shape of the form values
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const config: FormConfig<{ name: string; age: number }> = {
150
+ * fields: {
151
+ * name: { initialValue: '', validators: [required('Name is required')] },
152
+ * age: { initialValue: 0, validators: [min(1, 'Must be positive')] },
153
+ * },
154
+ * onSubmit: (values) => console.log(values),
155
+ * };
156
+ * ```
157
+ */
158
+ export type FormConfig<T extends Record<string, unknown>> = {
159
+ /** Per-field configuration with initial values and validators */
160
+ fields: { [K in keyof T]: FieldConfig<T[K]> };
161
+ /** Optional cross-field validators run after per-field validation */
162
+ crossValidators?: CrossFieldValidator<T>[];
163
+ /** Callback invoked on successful form submission */
164
+ onSubmit?: SubmitHandler<T>;
165
+ };
166
+
167
+ /**
168
+ * Return value of `createForm()`.
169
+ *
170
+ * @template T - Shape of the form values
171
+ */
172
+ export type Form<T extends Record<string, unknown>> = {
173
+ /** Reactive field objects keyed by field name */
174
+ fields: FormFields<T>;
175
+ /** Shorthand error signals keyed by field name */
176
+ errors: FormErrors<T>;
177
+ /** Computed signal: `true` when all fields pass validation */
178
+ isValid: Computed<boolean>;
179
+ /** Computed signal: `true` when any field value differs from initial */
180
+ isDirty: Computed<boolean>;
181
+ /** Reactive signal: `true` while the submit handler is executing */
182
+ isSubmitting: Signal<boolean>;
183
+ /** Validate all fields and, if valid, call the `onSubmit` handler */
184
+ handleSubmit: () => Promise<void>;
185
+ /** Validate a single field by name */
186
+ validateField: (name: keyof T & string) => Promise<void>;
187
+ /** Validate all fields without submitting */
188
+ validate: () => Promise<boolean>;
189
+ /** Reset the entire form to initial values */
190
+ reset: () => void;
191
+ /** Get a snapshot of all current field values */
192
+ getValues: () => T;
193
+ /**
194
+ * Bulk-set field values from a partial object.
195
+ * Only fields present in the object are updated; missing keys are left unchanged.
196
+ */
197
+ setValues: (values: Partial<T>) => void;
198
+ /**
199
+ * Bulk-set field error messages from a partial object.
200
+ * Useful for applying server-side validation errors.
201
+ */
202
+ setErrors: (errors: Partial<Record<keyof T & string, string>>) => void;
203
+ };