@bquery/bquery 1.6.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/README.md +192 -18
  2. package/dist/a11y/announce.d.ts +43 -0
  3. package/dist/a11y/announce.d.ts.map +1 -0
  4. package/dist/a11y/audit.d.ts +42 -0
  5. package/dist/a11y/audit.d.ts.map +1 -0
  6. package/dist/a11y/index.d.ts +53 -0
  7. package/dist/a11y/index.d.ts.map +1 -0
  8. package/dist/a11y/media-preferences.d.ts +77 -0
  9. package/dist/a11y/media-preferences.d.ts.map +1 -0
  10. package/dist/a11y/roving-tab-index.d.ts +38 -0
  11. package/dist/a11y/roving-tab-index.d.ts.map +1 -0
  12. package/dist/a11y/skip-link.d.ts +37 -0
  13. package/dist/a11y/skip-link.d.ts.map +1 -0
  14. package/dist/a11y/trap-focus.d.ts +49 -0
  15. package/dist/a11y/trap-focus.d.ts.map +1 -0
  16. package/dist/a11y/types.d.ts +152 -0
  17. package/dist/a11y/types.d.ts.map +1 -0
  18. package/dist/a11y-DVBCy09c.js +421 -0
  19. package/dist/a11y-DVBCy09c.js.map +1 -0
  20. package/dist/a11y.es.mjs +14 -0
  21. package/dist/component/component.d.ts.map +1 -1
  22. package/dist/component/html.d.ts.map +1 -1
  23. package/dist/component/index.d.ts +2 -1
  24. package/dist/component/index.d.ts.map +1 -1
  25. package/dist/component/library.d.ts.map +1 -1
  26. package/dist/component/scope.d.ts +138 -0
  27. package/dist/component/scope.d.ts.map +1 -0
  28. package/dist/component/types.d.ts +53 -1
  29. package/dist/component/types.d.ts.map +1 -1
  30. package/dist/component-L3-JfOFz.js +684 -0
  31. package/dist/component-L3-JfOFz.js.map +1 -0
  32. package/dist/component.es.mjs +9 -6
  33. package/dist/{config-DRmZZno3.js → config-DhT9auRm.js} +4 -4
  34. package/dist/{config-DRmZZno3.js.map → config-DhT9auRm.js.map} +1 -1
  35. package/dist/constraints-D5RHQLmP.js +100 -0
  36. package/dist/constraints-D5RHQLmP.js.map +1 -0
  37. package/dist/core/collection.d.ts +134 -0
  38. package/dist/core/collection.d.ts.map +1 -1
  39. package/dist/core/element.d.ts +120 -0
  40. package/dist/core/element.d.ts.map +1 -1
  41. package/dist/core/env.d.ts +18 -0
  42. package/dist/core/env.d.ts.map +1 -0
  43. package/dist/core/index.d.ts +1 -0
  44. package/dist/core/index.d.ts.map +1 -1
  45. package/dist/core/shared.d.ts +14 -0
  46. package/dist/core/shared.d.ts.map +1 -1
  47. package/dist/core/utils/index.d.ts +52 -41
  48. package/dist/core/utils/index.d.ts.map +1 -1
  49. package/dist/core-DdtZHzsS.js +168 -0
  50. package/dist/core-DdtZHzsS.js.map +1 -0
  51. package/dist/{core-CCEabVHl.js → core-EMYSLzaT.js} +293 -194
  52. package/dist/core-EMYSLzaT.js.map +1 -0
  53. package/dist/core.es.mjs +48 -46
  54. package/dist/custom-directives-Dr4C5lVV.js +9 -0
  55. package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
  56. package/dist/devtools/devtools.d.ts +212 -0
  57. package/dist/devtools/devtools.d.ts.map +1 -0
  58. package/dist/devtools/index.d.ts +20 -0
  59. package/dist/devtools/index.d.ts.map +1 -0
  60. package/dist/devtools/types.d.ts +69 -0
  61. package/dist/devtools/types.d.ts.map +1 -0
  62. package/dist/devtools-BhB2iDPT.js +122 -0
  63. package/dist/devtools-BhB2iDPT.js.map +1 -0
  64. package/dist/devtools.es.mjs +19 -0
  65. package/dist/dnd/draggable.d.ts +51 -0
  66. package/dist/dnd/draggable.d.ts.map +1 -0
  67. package/dist/dnd/droppable.d.ts +38 -0
  68. package/dist/dnd/droppable.d.ts.map +1 -0
  69. package/dist/dnd/index.d.ts +47 -0
  70. package/dist/dnd/index.d.ts.map +1 -0
  71. package/dist/dnd/sortable.d.ts +43 -0
  72. package/dist/dnd/sortable.d.ts.map +1 -0
  73. package/dist/dnd/types.d.ts +250 -0
  74. package/dist/dnd/types.d.ts.map +1 -0
  75. package/dist/dnd-NwZBYh4l.js +244 -0
  76. package/dist/dnd-NwZBYh4l.js.map +1 -0
  77. package/dist/dnd.es.mjs +6 -0
  78. package/dist/env-CTdvLaH2.js +19 -0
  79. package/dist/env-CTdvLaH2.js.map +1 -0
  80. package/dist/forms/create-form.d.ts +49 -0
  81. package/dist/forms/create-form.d.ts.map +1 -0
  82. package/dist/forms/index.d.ts +40 -0
  83. package/dist/forms/index.d.ts.map +1 -0
  84. package/dist/forms/types.d.ts +185 -0
  85. package/dist/forms/types.d.ts.map +1 -0
  86. package/dist/forms/use-field.d.ts +34 -0
  87. package/dist/forms/use-field.d.ts.map +1 -0
  88. package/dist/forms/validators.d.ts +204 -0
  89. package/dist/forms/validators.d.ts.map +1 -0
  90. package/dist/forms-UcRHsYxC.js +227 -0
  91. package/dist/forms-UcRHsYxC.js.map +1 -0
  92. package/dist/forms.es.mjs +16 -0
  93. package/dist/full.d.ts +30 -11
  94. package/dist/full.d.ts.map +1 -1
  95. package/dist/full.es.mjs +209 -93
  96. package/dist/full.iife.js +47 -31
  97. package/dist/full.iife.js.map +1 -1
  98. package/dist/full.umd.js +47 -31
  99. package/dist/full.umd.js.map +1 -1
  100. package/dist/function-Cybd57JV.js +33 -0
  101. package/dist/function-Cybd57JV.js.map +1 -0
  102. package/dist/i18n/formatting.d.ts +40 -0
  103. package/dist/i18n/formatting.d.ts.map +1 -0
  104. package/dist/i18n/i18n.d.ts +48 -0
  105. package/dist/i18n/i18n.d.ts.map +1 -0
  106. package/dist/i18n/index.d.ts +57 -0
  107. package/dist/i18n/index.d.ts.map +1 -0
  108. package/dist/i18n/translate.d.ts +83 -0
  109. package/dist/i18n/translate.d.ts.map +1 -0
  110. package/dist/i18n/types.d.ts +156 -0
  111. package/dist/i18n/types.d.ts.map +1 -0
  112. package/dist/i18n-kuF6Ekj6.js +89 -0
  113. package/dist/i18n-kuF6Ekj6.js.map +1 -0
  114. package/dist/i18n.es.mjs +6 -0
  115. package/dist/index.d.ts +11 -0
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.es.mjs +257 -143
  118. package/dist/media/battery.d.ts +35 -0
  119. package/dist/media/battery.d.ts.map +1 -0
  120. package/dist/media/breakpoints.d.ts +51 -0
  121. package/dist/media/breakpoints.d.ts.map +1 -0
  122. package/dist/media/clipboard.d.ts +30 -0
  123. package/dist/media/clipboard.d.ts.map +1 -0
  124. package/dist/media/device-sensors.d.ts +54 -0
  125. package/dist/media/device-sensors.d.ts.map +1 -0
  126. package/dist/media/geolocation.d.ts +38 -0
  127. package/dist/media/geolocation.d.ts.map +1 -0
  128. package/dist/media/index.d.ts +42 -0
  129. package/dist/media/index.d.ts.map +1 -0
  130. package/dist/media/media-query.d.ts +36 -0
  131. package/dist/media/media-query.d.ts.map +1 -0
  132. package/dist/media/network.d.ts +35 -0
  133. package/dist/media/network.d.ts.map +1 -0
  134. package/dist/media/types.d.ts +173 -0
  135. package/dist/media/types.d.ts.map +1 -0
  136. package/dist/media/viewport.d.ts +32 -0
  137. package/dist/media/viewport.d.ts.map +1 -0
  138. package/dist/media-i-fB5WxI.js +340 -0
  139. package/dist/media-i-fB5WxI.js.map +1 -0
  140. package/dist/media.es.mjs +12 -0
  141. package/dist/motion/index.d.ts +7 -3
  142. package/dist/motion/index.d.ts.map +1 -1
  143. package/dist/motion/morph.d.ts +27 -0
  144. package/dist/motion/morph.d.ts.map +1 -0
  145. package/dist/motion/parallax.d.ts +30 -0
  146. package/dist/motion/parallax.d.ts.map +1 -0
  147. package/dist/motion/reduced-motion.d.ts +36 -3
  148. package/dist/motion/reduced-motion.d.ts.map +1 -1
  149. package/dist/motion/types.d.ts +58 -0
  150. package/dist/motion/types.d.ts.map +1 -1
  151. package/dist/motion/typewriter.d.ts +31 -0
  152. package/dist/motion/typewriter.d.ts.map +1 -0
  153. package/dist/motion-BJsAuULb.js +530 -0
  154. package/dist/motion-BJsAuULb.js.map +1 -0
  155. package/dist/motion.es.mjs +27 -23
  156. package/dist/{view-C70lA3vf.js → mount-B4Y8bk8Z.js} +166 -160
  157. package/dist/mount-B4Y8bk8Z.js.map +1 -0
  158. package/dist/{object-qGpWr6-J.js → object-BCk-1c8T.js} +5 -4
  159. package/dist/{object-qGpWr6-J.js.map → object-BCk-1c8T.js.map} +1 -1
  160. package/dist/{platform-Dr9b6fsq.js → platform-Dw2gE3zI.js} +21 -22
  161. package/dist/{platform-Dr9b6fsq.js.map → platform-Dw2gE3zI.js.map} +1 -1
  162. package/dist/platform.es.mjs +2 -2
  163. package/dist/plugin/index.d.ts +22 -0
  164. package/dist/plugin/index.d.ts.map +1 -0
  165. package/dist/plugin/registry.d.ts +108 -0
  166. package/dist/plugin/registry.d.ts.map +1 -0
  167. package/dist/plugin/types.d.ts +110 -0
  168. package/dist/plugin/types.d.ts.map +1 -0
  169. package/dist/plugin-C2WuC8SF.js +66 -0
  170. package/dist/plugin-C2WuC8SF.js.map +1 -0
  171. package/dist/plugin.es.mjs +9 -0
  172. package/dist/reactive/async-data.d.ts +28 -3
  173. package/dist/reactive/async-data.d.ts.map +1 -1
  174. package/dist/reactive/computed.d.ts +10 -0
  175. package/dist/reactive/computed.d.ts.map +1 -1
  176. package/dist/reactive/effect.d.ts +3 -0
  177. package/dist/reactive/effect.d.ts.map +1 -1
  178. package/dist/reactive/http.d.ts +194 -0
  179. package/dist/reactive/http.d.ts.map +1 -0
  180. package/dist/reactive/index.d.ts +2 -2
  181. package/dist/reactive/index.d.ts.map +1 -1
  182. package/dist/reactive/pagination.d.ts +126 -0
  183. package/dist/reactive/pagination.d.ts.map +1 -0
  184. package/dist/reactive/polling.d.ts +55 -0
  185. package/dist/reactive/polling.d.ts.map +1 -0
  186. package/dist/reactive/readonly.d.ts +20 -1
  187. package/dist/reactive/readonly.d.ts.map +1 -1
  188. package/dist/reactive/rest.d.ts +293 -0
  189. package/dist/reactive/rest.d.ts.map +1 -0
  190. package/dist/reactive/scope.d.ts +140 -0
  191. package/dist/reactive/scope.d.ts.map +1 -0
  192. package/dist/reactive/signal.d.ts +16 -2
  193. package/dist/reactive/signal.d.ts.map +1 -1
  194. package/dist/reactive/to-value.d.ts +57 -0
  195. package/dist/reactive/to-value.d.ts.map +1 -0
  196. package/dist/reactive/websocket.d.ts +285 -0
  197. package/dist/reactive/websocket.d.ts.map +1 -0
  198. package/dist/reactive-DwkhUJfP.js +1148 -0
  199. package/dist/reactive-DwkhUJfP.js.map +1 -0
  200. package/dist/reactive.es.mjs +38 -20
  201. package/dist/registry-B08iilIh.js +26 -0
  202. package/dist/registry-B08iilIh.js.map +1 -0
  203. package/dist/router/bq-link.d.ts +112 -0
  204. package/dist/router/bq-link.d.ts.map +1 -0
  205. package/dist/router/constraints.d.ts +9 -0
  206. package/dist/router/constraints.d.ts.map +1 -0
  207. package/dist/router/index.d.ts +15 -7
  208. package/dist/router/index.d.ts.map +1 -1
  209. package/dist/router/match.d.ts +0 -1
  210. package/dist/router/match.d.ts.map +1 -1
  211. package/dist/router/path-pattern.d.ts +14 -0
  212. package/dist/router/path-pattern.d.ts.map +1 -0
  213. package/dist/router/query.d.ts.map +1 -1
  214. package/dist/router/router.d.ts +3 -1
  215. package/dist/router/router.d.ts.map +1 -1
  216. package/dist/router/state.d.ts +25 -2
  217. package/dist/router/state.d.ts.map +1 -1
  218. package/dist/router/types.d.ts +48 -4
  219. package/dist/router/types.d.ts.map +1 -1
  220. package/dist/router/use-route.d.ts +50 -0
  221. package/dist/router/use-route.d.ts.map +1 -0
  222. package/dist/router/utils.d.ts +3 -0
  223. package/dist/router/utils.d.ts.map +1 -1
  224. package/dist/router-CQikC9Ed.js +492 -0
  225. package/dist/router-CQikC9Ed.js.map +1 -0
  226. package/dist/router.es.mjs +14 -10
  227. package/dist/{sanitize-Bs2dkMby.js → sanitize-B1V4JswB.js} +2 -1
  228. package/dist/{sanitize-Bs2dkMby.js.map → sanitize-B1V4JswB.js.map} +1 -1
  229. package/dist/security/index.d.ts +2 -2
  230. package/dist/security/index.d.ts.map +1 -1
  231. package/dist/security.es.mjs +1 -1
  232. package/dist/ssr/hydrate.d.ts +65 -0
  233. package/dist/ssr/hydrate.d.ts.map +1 -0
  234. package/dist/ssr/index.d.ts +59 -0
  235. package/dist/ssr/index.d.ts.map +1 -0
  236. package/dist/ssr/render.d.ts +62 -0
  237. package/dist/ssr/render.d.ts.map +1 -0
  238. package/dist/ssr/serialize.d.ts +118 -0
  239. package/dist/ssr/serialize.d.ts.map +1 -0
  240. package/dist/ssr/types.d.ts +70 -0
  241. package/dist/ssr/types.d.ts.map +1 -0
  242. package/dist/ssr-_dAcGdzu.js +248 -0
  243. package/dist/ssr-_dAcGdzu.js.map +1 -0
  244. package/dist/ssr.es.mjs +9 -0
  245. package/dist/store/create-store.d.ts.map +1 -1
  246. package/dist/store/index.d.ts +1 -1
  247. package/dist/store/index.d.ts.map +1 -1
  248. package/dist/store/persisted.d.ts +38 -4
  249. package/dist/store/persisted.d.ts.map +1 -1
  250. package/dist/store/types.d.ts +138 -1
  251. package/dist/store/types.d.ts.map +1 -1
  252. package/dist/store/utils.d.ts +2 -2
  253. package/dist/store/utils.d.ts.map +1 -1
  254. package/dist/store-Cb3gPRve.js +338 -0
  255. package/dist/store-Cb3gPRve.js.map +1 -0
  256. package/dist/store.es.mjs +11 -10
  257. package/dist/storybook/index.d.ts.map +1 -1
  258. package/dist/storybook.es.mjs +1 -1
  259. package/dist/storybook.es.mjs.map +1 -1
  260. package/dist/testing/index.d.ts +23 -0
  261. package/dist/testing/index.d.ts.map +1 -0
  262. package/dist/testing/testing.d.ts +156 -0
  263. package/dist/testing/testing.d.ts.map +1 -0
  264. package/dist/testing/types.d.ts +134 -0
  265. package/dist/testing/types.d.ts.map +1 -0
  266. package/dist/testing-C5Sjfsna.js +224 -0
  267. package/dist/testing-C5Sjfsna.js.map +1 -0
  268. package/dist/testing.es.mjs +9 -0
  269. package/dist/type-guards-BMX2c0LP.js +44 -0
  270. package/dist/type-guards-BMX2c0LP.js.map +1 -0
  271. package/dist/untrack-D0fnO5k2.js +36 -0
  272. package/dist/untrack-D0fnO5k2.js.map +1 -0
  273. package/dist/view/custom-directives.d.ts +20 -0
  274. package/dist/view/custom-directives.d.ts.map +1 -0
  275. package/dist/view/evaluate.d.ts.map +1 -1
  276. package/dist/view/process.d.ts.map +1 -1
  277. package/dist/view.es.mjs +9 -9
  278. package/package.json +47 -11
  279. package/src/a11y/announce.ts +131 -0
  280. package/src/a11y/audit.ts +314 -0
  281. package/src/a11y/index.ts +68 -0
  282. package/src/a11y/media-preferences.ts +255 -0
  283. package/src/a11y/roving-tab-index.ts +164 -0
  284. package/src/a11y/skip-link.ts +255 -0
  285. package/src/a11y/trap-focus.ts +184 -0
  286. package/src/a11y/types.ts +183 -0
  287. package/src/component/component.ts +599 -524
  288. package/src/component/html.ts +153 -153
  289. package/src/component/index.ts +52 -50
  290. package/src/component/library.ts +540 -518
  291. package/src/component/scope.ts +212 -0
  292. package/src/component/types.ts +310 -256
  293. package/src/core/collection.ts +249 -1
  294. package/src/core/element.ts +252 -11
  295. package/src/core/env.ts +60 -0
  296. package/src/core/index.ts +1 -0
  297. package/src/core/shared.ts +64 -0
  298. package/src/core/utils/index.ts +66 -1
  299. package/src/devtools/devtools.ts +410 -0
  300. package/src/devtools/index.ts +48 -0
  301. package/src/devtools/types.ts +104 -0
  302. package/src/dnd/draggable.ts +296 -0
  303. package/src/dnd/droppable.ts +228 -0
  304. package/src/dnd/index.ts +62 -0
  305. package/src/dnd/sortable.ts +307 -0
  306. package/src/dnd/types.ts +293 -0
  307. package/src/forms/create-form.ts +320 -0
  308. package/src/forms/index.ts +70 -0
  309. package/src/forms/types.ts +203 -0
  310. package/src/forms/use-field.ts +231 -0
  311. package/src/forms/validators.ts +294 -0
  312. package/src/full.ts +554 -229
  313. package/src/i18n/formatting.ts +67 -0
  314. package/src/i18n/i18n.ts +200 -0
  315. package/src/i18n/index.ts +67 -0
  316. package/src/i18n/translate.ts +182 -0
  317. package/src/i18n/types.ts +171 -0
  318. package/src/index.ts +72 -0
  319. package/src/media/battery.ts +116 -0
  320. package/src/media/breakpoints.ts +129 -0
  321. package/src/media/clipboard.ts +80 -0
  322. package/src/media/device-sensors.ts +158 -0
  323. package/src/media/geolocation.ts +119 -0
  324. package/src/media/index.ts +76 -0
  325. package/src/media/media-query.ts +92 -0
  326. package/src/media/network.ts +115 -0
  327. package/src/media/types.ts +177 -0
  328. package/src/media/viewport.ts +84 -0
  329. package/src/motion/index.ts +11 -2
  330. package/src/motion/morph.ts +151 -0
  331. package/src/motion/parallax.ts +120 -0
  332. package/src/motion/reduced-motion.ts +52 -3
  333. package/src/motion/types.ts +63 -0
  334. package/src/motion/typewriter.ts +164 -0
  335. package/src/plugin/index.ts +37 -0
  336. package/src/plugin/registry.ts +284 -0
  337. package/src/plugin/types.ts +137 -0
  338. package/src/reactive/async-data.ts +250 -29
  339. package/src/reactive/computed.ts +53 -1
  340. package/src/reactive/effect.ts +29 -6
  341. package/src/reactive/http.ts +790 -0
  342. package/src/reactive/index.ts +60 -0
  343. package/src/reactive/pagination.ts +317 -0
  344. package/src/reactive/polling.ts +179 -0
  345. package/src/reactive/readonly.ts +52 -8
  346. package/src/reactive/rest.ts +859 -0
  347. package/src/reactive/scope.ts +276 -0
  348. package/src/reactive/signal.ts +61 -1
  349. package/src/reactive/to-value.ts +71 -0
  350. package/src/reactive/websocket.ts +849 -0
  351. package/src/router/bq-link.ts +279 -0
  352. package/src/router/constraints.ts +204 -0
  353. package/src/router/index.ts +15 -7
  354. package/src/router/match.ts +255 -49
  355. package/src/router/path-pattern.ts +52 -0
  356. package/src/router/query.ts +3 -0
  357. package/src/router/router.ts +258 -48
  358. package/src/router/state.ts +51 -3
  359. package/src/router/types.ts +50 -4
  360. package/src/router/use-route.ts +68 -0
  361. package/src/router/utils.ts +44 -3
  362. package/src/security/index.ts +12 -17
  363. package/src/security/sanitize.ts +70 -70
  364. package/src/security/trusted-html.ts +71 -71
  365. package/src/ssr/hydrate.ts +84 -0
  366. package/src/ssr/index.ts +70 -0
  367. package/src/ssr/render.ts +508 -0
  368. package/src/ssr/serialize.ts +296 -0
  369. package/src/ssr/types.ts +81 -0
  370. package/src/store/create-store.ts +146 -8
  371. package/src/store/define-store.ts +49 -49
  372. package/src/store/index.ts +5 -0
  373. package/src/store/mapping.ts +74 -74
  374. package/src/store/persisted.ts +245 -62
  375. package/src/store/types.ts +247 -92
  376. package/src/store/utils.ts +4 -10
  377. package/src/store/watch.ts +53 -53
  378. package/src/storybook/index.ts +480 -479
  379. package/src/testing/index.ts +42 -0
  380. package/src/testing/testing.ts +593 -0
  381. package/src/testing/types.ts +170 -0
  382. package/src/view/custom-directives.ts +28 -0
  383. package/src/view/evaluate.ts +2 -0
  384. package/src/view/process.ts +19 -3
  385. package/dist/component-BEQgt5hl.js +0 -600
  386. package/dist/component-BEQgt5hl.js.map +0 -1
  387. package/dist/core-BGQJVw0-.js +0 -35
  388. package/dist/core-BGQJVw0-.js.map +0 -1
  389. package/dist/core-CCEabVHl.js.map +0 -1
  390. package/dist/effect-AFRW_Plg.js +0 -84
  391. package/dist/effect-AFRW_Plg.js.map +0 -1
  392. package/dist/motion-D9TcHxOF.js +0 -415
  393. package/dist/motion-D9TcHxOF.js.map +0 -1
  394. package/dist/reactive-DSkct0dO.js +0 -254
  395. package/dist/reactive-DSkct0dO.js.map +0 -1
  396. package/dist/router-CbDhl8rS.js +0 -188
  397. package/dist/router-CbDhl8rS.js.map +0 -1
  398. package/dist/store-BwDvI45q.js +0 -263
  399. package/dist/store-BwDvI45q.js.map +0 -1
  400. package/dist/untrack-B0rVscTc.js +0 -7
  401. package/dist/untrack-B0rVscTc.js.map +0 -1
  402. package/dist/view-C70lA3vf.js.map +0 -1
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Reactive form creation and management.
3
+ *
4
+ * @module bquery/forms
5
+ */
6
+
7
+ import { computed, signal } from '../reactive/index';
8
+ import { isPrototypePollutionKey } from '../core/utils/object';
9
+ import { isPromise } from '../core/utils/type-guards';
10
+ import type {
11
+ CrossFieldValidator,
12
+ FieldConfig,
13
+ Form,
14
+ FormConfig,
15
+ FormErrors,
16
+ FormField,
17
+ FormFields,
18
+ ValidationResult,
19
+ Validator,
20
+ } from './types';
21
+
22
+ /**
23
+ * Determines whether a validator returned a valid result.
24
+ * @internal
25
+ */
26
+ const isValid = (result: ValidationResult): boolean => result === true || result === undefined;
27
+
28
+ /**
29
+ * Runs a single validator, normalising sync and async results.
30
+ * @internal
31
+ */
32
+ const runValidator = async <T>(validator: Validator<T>, value: T): Promise<string | undefined> => {
33
+ const result = validator(value);
34
+ const resolved = isPromise(result) ? await result : result;
35
+ return isValid(resolved) ? undefined : (resolved as string);
36
+ };
37
+
38
+ /**
39
+ * Creates a reactive form field from its configuration.
40
+ * @internal
41
+ */
42
+ const createField = <T>(config: FieldConfig<T>): FormField<T> => {
43
+ const initial = config.initialValue;
44
+ const value = signal<T>(initial);
45
+ const error = signal('');
46
+ const isTouched = signal(false);
47
+
48
+ const isDirty = computed(() => !Object.is(value.value, initial));
49
+ const isPristine = computed(() => !isDirty.value);
50
+
51
+ return {
52
+ value,
53
+ error,
54
+ isDirty,
55
+ isTouched,
56
+ isPristine,
57
+ touch: () => {
58
+ isTouched.value = true;
59
+ },
60
+ reset: () => {
61
+ value.value = initial;
62
+ error.value = '';
63
+ isTouched.value = false;
64
+ },
65
+ };
66
+ };
67
+
68
+ /**
69
+ * Validates a single field against its validators.
70
+ * Sets the field's error signal.
71
+ *
72
+ * @returns The first error message, or an empty string if valid.
73
+ * @internal
74
+ */
75
+ const validateSingleField = async <T>(
76
+ field: FormField<T>,
77
+ validators: Validator<T>[] | undefined
78
+ ): Promise<string> => {
79
+ if (!validators || validators.length === 0) {
80
+ field.error.value = '';
81
+ return '';
82
+ }
83
+
84
+ for (const validator of validators) {
85
+ const errorMsg = await runValidator(validator, field.value.value);
86
+ if (errorMsg) {
87
+ field.error.value = errorMsg;
88
+ return errorMsg;
89
+ }
90
+ }
91
+
92
+ field.error.value = '';
93
+ return '';
94
+ };
95
+
96
+ /**
97
+ * Creates a fully reactive form with field-level validation,
98
+ * dirty/touched tracking, cross-field validation, and submission handling.
99
+ *
100
+ * Each field's `value`, `error`, `isDirty`, `isTouched`, and `isPristine`
101
+ * are reactive signals/computed values that can be used in effects, computed
102
+ * values, or directly read/written.
103
+ *
104
+ * @template T - Shape of the form values (e.g. `{ name: string; age: number }`)
105
+ * @param config - Form configuration with field definitions, validators, and submit handler
106
+ * @returns A reactive {@link Form} instance
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * import { createForm, required, email, min } from '@bquery/bquery/forms';
111
+ *
112
+ * const form = createForm({
113
+ * fields: {
114
+ * name: { initialValue: '', validators: [required()] },
115
+ * email: { initialValue: '', validators: [required(), email()] },
116
+ * age: { initialValue: 0, validators: [min(18, 'Must be 18+')] },
117
+ * },
118
+ * onSubmit: async (values) => {
119
+ * await fetch('/api/register', {
120
+ * method: 'POST',
121
+ * body: JSON.stringify(values),
122
+ * });
123
+ * },
124
+ * });
125
+ *
126
+ * // Read reactive state
127
+ * console.log(form.isValid.value); // true (initially, before validation runs)
128
+ * console.log(form.fields.name.value.value); // ''
129
+ *
130
+ * // Update a field
131
+ * form.fields.name.value.value = 'Ada';
132
+ *
133
+ * // Validate and submit
134
+ * await form.handleSubmit();
135
+ * ```
136
+ */
137
+ export const createForm = <T extends Record<string, unknown>>(config: FormConfig<T>): Form<T> => {
138
+ // Build reactive field objects
139
+ const fieldEntries = Object.entries(config.fields) as [
140
+ keyof T & string,
141
+ FieldConfig<T[keyof T]>,
142
+ ][];
143
+
144
+ const fields = {} as FormFields<T>;
145
+ const errors = {} as FormErrors<T>;
146
+
147
+ for (const [name, fieldConfig] of fieldEntries) {
148
+ const field = createField(fieldConfig as FieldConfig<T[typeof name]>);
149
+ (fields as Record<string, FormField>)[name] = field;
150
+ (errors as Record<string, typeof field.error>)[name] = field.error;
151
+ }
152
+
153
+ const isSubmitting = signal(false);
154
+
155
+ // Computed: form is valid when all error signals are empty
156
+ const isFormValid = computed(() => {
157
+ for (const name of Object.keys(fields)) {
158
+ if ((fields as Record<string, FormField>)[name].error.value !== '') {
159
+ return false;
160
+ }
161
+ }
162
+ return true;
163
+ });
164
+
165
+ // Computed: form is dirty when any field is dirty
166
+ const isFormDirty = computed(() => {
167
+ for (const name of Object.keys(fields)) {
168
+ if ((fields as Record<string, FormField>)[name].isDirty.value) {
169
+ return true;
170
+ }
171
+ }
172
+ return false;
173
+ });
174
+
175
+ /**
176
+ * Validate a single field by name.
177
+ */
178
+ const validateField = async (name: keyof T & string): Promise<void> => {
179
+ const field = (fields as Record<string, FormField>)[name];
180
+ const fieldConfig = (config.fields as Record<string, FieldConfig>)[name];
181
+ if (!field || !fieldConfig) return;
182
+ await validateSingleField(field, fieldConfig.validators);
183
+ };
184
+
185
+ /**
186
+ * Validate all fields (per-field + cross-field).
187
+ * Returns `true` if the entire form is valid.
188
+ */
189
+ const validate = async (): Promise<boolean> => {
190
+ let hasError = false;
191
+
192
+ // Per-field validation
193
+ for (const [name, fieldConfig] of fieldEntries) {
194
+ const field = (fields as Record<string, FormField>)[name];
195
+ const error = await validateSingleField(field, (fieldConfig as FieldConfig).validators);
196
+ if (error) hasError = true;
197
+ }
198
+
199
+ // Cross-field validation
200
+ if (config.crossValidators && config.crossValidators.length > 0) {
201
+ const values = getValues();
202
+ for (const crossValidator of config.crossValidators as CrossFieldValidator<T>[]) {
203
+ const crossErrors = await crossValidator(values);
204
+ if (crossErrors) {
205
+ for (const [fieldName, errorMsg] of Object.entries(crossErrors) as [
206
+ string,
207
+ string | undefined,
208
+ ][]) {
209
+ if (errorMsg) {
210
+ const field = (fields as Record<string, FormField>)[fieldName];
211
+ if (field) {
212
+ // Only set cross-field error if no per-field error exists
213
+ if (field.error.value === '') {
214
+ field.error.value = errorMsg;
215
+ }
216
+ hasError = true;
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ return !hasError;
225
+ };
226
+
227
+ /**
228
+ * Validate all fields and, if valid, invoke the onSubmit handler.
229
+ * Prevents concurrent submissions by setting isSubmitting before validation.
230
+ */
231
+ const handleSubmit = async (): Promise<void> => {
232
+ if (isSubmitting.value) return;
233
+ isSubmitting.value = true;
234
+
235
+ try {
236
+ const valid = await validate();
237
+ if (!valid) return;
238
+
239
+ if (config.onSubmit) {
240
+ await config.onSubmit(getValues());
241
+ }
242
+ } finally {
243
+ isSubmitting.value = false;
244
+ }
245
+ };
246
+
247
+ /**
248
+ * Reset every field to its initial value and clear all errors.
249
+ */
250
+ const reset = (): void => {
251
+ for (const name of Object.keys(fields)) {
252
+ (fields as Record<string, FormField>)[name].reset();
253
+ }
254
+ };
255
+
256
+ /**
257
+ * Return a plain object snapshot of all current field values.
258
+ */
259
+ const getValues = (): T => {
260
+ const values = {} as Record<string, unknown>;
261
+ for (const name of Object.keys(fields)) {
262
+ values[name] = (fields as Record<string, FormField>)[name].value.value;
263
+ }
264
+ return values as T;
265
+ };
266
+
267
+ /**
268
+ * Bulk-set field values from a partial object.
269
+ * Only fields present in the object are updated; missing keys are left unchanged.
270
+ */
271
+ const setValues = (values: Partial<T>): void => {
272
+ for (const [name, val] of Object.entries(values)) {
273
+ // Ignore inherited keys and prototype-pollution vectors before mutating field state.
274
+ if (isPrototypePollutionKey(name) || !Object.prototype.hasOwnProperty.call(fields, name)) {
275
+ continue;
276
+ }
277
+
278
+ const field = (fields as Record<string, FormField>)[name];
279
+ if (!field) {
280
+ continue;
281
+ }
282
+ field.value.value = val;
283
+ }
284
+ };
285
+
286
+ /**
287
+ * Bulk-set field error messages from a partial object.
288
+ * Useful for applying server-side validation errors.
289
+ * Only fields present in the object are updated; missing keys are left unchanged.
290
+ */
291
+ const setErrors = (errorMap: Partial<Record<keyof T & string, string>>): void => {
292
+ for (const [name, msg] of Object.entries(errorMap)) {
293
+ // Ignore inherited keys and prototype-pollution vectors before mutating field state.
294
+ if (isPrototypePollutionKey(name) || !Object.prototype.hasOwnProperty.call(fields, name)) {
295
+ continue;
296
+ }
297
+
298
+ const field = (fields as Record<string, FormField>)[name];
299
+ if (!field) {
300
+ continue;
301
+ }
302
+ field.error.value = (msg as string) ?? '';
303
+ }
304
+ };
305
+
306
+ return {
307
+ fields,
308
+ errors,
309
+ isValid: isFormValid,
310
+ isDirty: isFormDirty,
311
+ isSubmitting,
312
+ handleSubmit,
313
+ validateField,
314
+ validate,
315
+ reset,
316
+ getValues,
317
+ setValues,
318
+ setErrors,
319
+ };
320
+ };
@@ -0,0 +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
+ 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';
@@ -0,0 +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
+ * 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
+ };