@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,231 @@
1
+ /**
2
+ * Standalone reactive field composable.
3
+ *
4
+ * @module bquery/forms
5
+ */
6
+
7
+ import { debounce } from '../core/utils/function';
8
+ import { isPromise } from '../core/utils/type-guards';
9
+ import { Computed } from '../reactive/computed';
10
+ import { Signal } from '../reactive/core';
11
+ import { computed, effect, signal } from '../reactive/index';
12
+ import type { MaybeSignal } from '../reactive/index';
13
+ import { isReadonlySignal } from '../reactive/readonly';
14
+ import type { UseFormFieldOptions, UseFormFieldReturn, ValidationResult, Validator } from './types';
15
+
16
+ /**
17
+ * Determines whether a validator returned a valid result.
18
+ * @internal
19
+ */
20
+ const isValidationSuccess = (result: ValidationResult): boolean =>
21
+ result === true || result === undefined;
22
+
23
+ /**
24
+ * Runs a single validator, normalising sync and async results.
25
+ * @internal
26
+ */
27
+ const runValidator = async <T>(validator: Validator<T>, value: T): Promise<string | undefined> => {
28
+ const result = validator(value);
29
+ const resolved = isPromise(result) ? await result : result;
30
+ return isValidationSuccess(resolved) ? undefined : (resolved as string);
31
+ };
32
+
33
+ /**
34
+ * Creates a standalone reactive form field with optional automatic validation.
35
+ *
36
+ * This helper is useful when you want field-level state without creating a full form,
37
+ * or when you want to bind an existing signal to the forms validation model.
38
+ *
39
+ * @template T - The type of the field value
40
+ * @param initialValue - Plain initial value, an existing writable signal to reuse, or a
41
+ * computed / readonly reactive source to snapshot
42
+ * @param options - Validation mode, validators, debounce, and initial error configuration
43
+ * @returns A reactive field handle with validation helpers
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { useFormField, required } from '@bquery/bquery/forms';
48
+ *
49
+ * const email = useFormField('', {
50
+ * validators: [required()],
51
+ * validateOn: 'blur',
52
+ * });
53
+ *
54
+ * email.value.value = 'ada@example.com';
55
+ * email.touch();
56
+ * ```
57
+ */
58
+ export const useFormField = <T>(
59
+ initialValue: MaybeSignal<T>,
60
+ options: UseFormFieldOptions<T> = {}
61
+ ): UseFormFieldReturn<T> => {
62
+ let value: Signal<T>;
63
+
64
+ if (isSignal(initialValue)) {
65
+ value = initialValue as Signal<T>;
66
+ } else {
67
+ const startingValue: T =
68
+ isReadonlySignal<T>(initialValue) || isComputedValue<T>(initialValue)
69
+ ? initialValue.peek()
70
+ : (initialValue as T);
71
+ value = signal(startingValue);
72
+ }
73
+
74
+ const initial = value.peek();
75
+ const error = signal(options.initialError ?? '');
76
+ const isTouched = signal(false);
77
+ const isValidating = signal(false);
78
+ const isDirty = computed(() => !Object.is(value.value, initial));
79
+ const isPristine = computed(() => !isDirty.value);
80
+ const isValid = computed(() => error.value === '');
81
+ const validateOn = options.validateOn ?? 'manual';
82
+ const debounceMs = Math.max(0, options.debounceMs ?? 0);
83
+
84
+ let validationId = 0;
85
+ let changeInitialized = false;
86
+ let suppressNextChangeValidation = false;
87
+ let isDestroyed = false;
88
+ let stopChangeValidationEffect: (() => void) | undefined;
89
+
90
+ const logValidationError = (validationError: unknown): void => {
91
+ console.error('bQuery forms: Error in scheduled field validation', validationError);
92
+ };
93
+
94
+ const runValidation = async (): Promise<boolean> => {
95
+ const currentValidationId = ++validationId;
96
+ const validators = options.validators;
97
+
98
+ if (!validators || validators.length === 0) {
99
+ error.value = '';
100
+ isValidating.value = false;
101
+ return true;
102
+ }
103
+
104
+ isValidating.value = true;
105
+
106
+ try {
107
+ const currentValue = value.peek();
108
+
109
+ for (const validator of validators) {
110
+ const nextError = await runValidator(validator, currentValue);
111
+
112
+ if (currentValidationId !== validationId) {
113
+ return error.peek() === '';
114
+ }
115
+
116
+ if (nextError) {
117
+ error.value = nextError;
118
+ return false;
119
+ }
120
+ }
121
+
122
+ if (currentValidationId === validationId) {
123
+ error.value = '';
124
+ }
125
+ return true;
126
+ } finally {
127
+ if (currentValidationId === validationId) {
128
+ isValidating.value = false;
129
+ }
130
+ }
131
+ };
132
+
133
+ const debouncedValidate = debounce(() => {
134
+ void runValidation().catch(logValidationError);
135
+ }, debounceMs);
136
+
137
+ const scheduleValidation = (): void => {
138
+ if (isDestroyed) {
139
+ return;
140
+ }
141
+
142
+ if (debounceMs > 0) {
143
+ debouncedValidate();
144
+ return;
145
+ }
146
+
147
+ void runValidation().catch(logValidationError);
148
+ };
149
+
150
+ if (validateOn === 'change' || validateOn === 'both') {
151
+ stopChangeValidationEffect = effect(() => {
152
+ void value.value;
153
+
154
+ if (!changeInitialized) {
155
+ changeInitialized = true;
156
+ return;
157
+ }
158
+
159
+ if (suppressNextChangeValidation) {
160
+ suppressNextChangeValidation = false;
161
+ return;
162
+ }
163
+
164
+ scheduleValidation();
165
+ });
166
+ }
167
+
168
+ const destroy = (): void => {
169
+ if (isDestroyed) {
170
+ return;
171
+ }
172
+
173
+ isDestroyed = true;
174
+ validationId += 1;
175
+ debouncedValidate.cancel();
176
+ stopChangeValidationEffect?.();
177
+ stopChangeValidationEffect = undefined;
178
+ isDirty.dispose();
179
+ isPristine.dispose();
180
+ isValid.dispose();
181
+ isValidating.value = false;
182
+ };
183
+
184
+ return {
185
+ value,
186
+ error,
187
+ isDirty,
188
+ isTouched,
189
+ isPristine,
190
+ isValid,
191
+ isValidating,
192
+ touch: () => {
193
+ isTouched.value = true;
194
+ if (validateOn === 'blur' || validateOn === 'both') {
195
+ scheduleValidation();
196
+ }
197
+ },
198
+ reset: () => {
199
+ validationId += 1;
200
+ debouncedValidate.cancel();
201
+ if (!Object.is(value.peek(), initial)) {
202
+ suppressNextChangeValidation = true;
203
+ }
204
+ value.value = initial;
205
+ error.value = options.initialError ?? '';
206
+ isTouched.value = false;
207
+ isValidating.value = false;
208
+ },
209
+ validate: async () => {
210
+ debouncedValidate.cancel();
211
+ return runValidation();
212
+ },
213
+ destroy,
214
+ };
215
+ };
216
+
217
+ /**
218
+ * Determines whether a value looks like a writable signal.
219
+ * @internal
220
+ */
221
+ const isSignal = (value: unknown): value is Signal<unknown> => {
222
+ return value instanceof Signal;
223
+ };
224
+
225
+ /**
226
+ * Determines whether a value is a computed reactive source.
227
+ * @internal
228
+ */
229
+ const isComputedValue = <T>(value: unknown): value is Computed<T> => {
230
+ return value instanceof Computed;
231
+ };
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Built-in validation functions for form fields.
3
+ *
4
+ * Each factory returns a {@link SyncValidator} that can be passed
5
+ * to a field's `validators` array in {@link FormConfig}.
6
+ *
7
+ * @module bquery/forms
8
+ */
9
+
10
+ import type { AsyncValidator, SyncValidator } from './types';
11
+
12
+ /**
13
+ * Requires a non-empty value.
14
+ *
15
+ * Fails for `undefined`, `null`, empty strings (after trim), and empty arrays.
16
+ *
17
+ * @param message - Custom error message (default: `'This field is required'`)
18
+ * @returns A sync validator function
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { required } from '@bquery/bquery/forms';
23
+ * const validate = required('Name is required');
24
+ * validate(''); // 'Name is required'
25
+ * validate('Ada'); // true
26
+ * ```
27
+ */
28
+ export const required = (message = 'This field is required'): SyncValidator => {
29
+ return (value: unknown) => {
30
+ if (value == null) return message;
31
+ if (typeof value === 'string' && value.trim() === '') return message;
32
+ if (Array.isArray(value) && value.length === 0) return message;
33
+ return true;
34
+ };
35
+ };
36
+
37
+ /**
38
+ * Requires a string to have at least `len` characters.
39
+ *
40
+ * Non-string values are coerced via `String()` before checking length.
41
+ *
42
+ * @param len - Minimum length
43
+ * @param message - Custom error message
44
+ * @returns A sync validator function
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { minLength } from '@bquery/bquery/forms';
49
+ * const validate = minLength(3);
50
+ * validate('ab'); // 'Must be at least 3 characters'
51
+ * validate('abc'); // true
52
+ * ```
53
+ */
54
+ export const minLength = (len: number, message?: string): SyncValidator<unknown> => {
55
+ const msg = message ?? `Must be at least ${len} characters`;
56
+ return (value: unknown) => {
57
+ const str = typeof value === 'string' ? value : String(value ?? '');
58
+ return str.length >= len ? true : msg;
59
+ };
60
+ };
61
+
62
+ /**
63
+ * Requires a string to have at most `len` characters.
64
+ *
65
+ * Non-string values are coerced via `String()` before checking length.
66
+ *
67
+ * @param len - Maximum length
68
+ * @param message - Custom error message
69
+ * @returns A sync validator function
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { maxLength } from '@bquery/bquery/forms';
74
+ * const validate = maxLength(10);
75
+ * validate('hello world!!'); // 'Must be at most 10 characters'
76
+ * validate('hello'); // true
77
+ * ```
78
+ */
79
+ export const maxLength = (len: number, message?: string): SyncValidator<unknown> => {
80
+ const msg = message ?? `Must be at most ${len} characters`;
81
+ return (value: unknown) => {
82
+ const str = typeof value === 'string' ? value : String(value ?? '');
83
+ return str.length <= len ? true : msg;
84
+ };
85
+ };
86
+
87
+ /**
88
+ * Requires a string to match a regular expression pattern.
89
+ *
90
+ * Non-string values are coerced via `String()` before testing.
91
+ *
92
+ * @param regex - Pattern to test against
93
+ * @param message - Custom error message (default: `'Invalid format'`)
94
+ * @returns A sync validator function
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * import { pattern } from '@bquery/bquery/forms';
99
+ * const validate = pattern(/^\d+$/, 'Numbers only');
100
+ * validate('abc'); // 'Numbers only'
101
+ * validate('123'); // true
102
+ * ```
103
+ */
104
+ export const pattern = (regex: RegExp, message = 'Invalid format'): SyncValidator<unknown> => {
105
+ const safeRegex =
106
+ regex.global || regex.sticky
107
+ ? new RegExp(regex.source, regex.flags.replace(/[gy]/g, ''))
108
+ : regex;
109
+
110
+ return (value: unknown) => {
111
+ const str = typeof value === 'string' ? value : String(value ?? '');
112
+ safeRegex.lastIndex = 0;
113
+ return safeRegex.test(str) ? true : message;
114
+ };
115
+ };
116
+
117
+ /**
118
+ * RFC 5322–simplified email validation.
119
+ *
120
+ * @param message - Custom error message (default: `'Invalid email address'`)
121
+ * @returns A sync validator function
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * import { email } from '@bquery/bquery/forms';
126
+ * const validate = email();
127
+ * validate('nope'); // 'Invalid email address'
128
+ * validate('ada@lovelace'); // 'Invalid email address'
129
+ * validate('ada@love.co'); // true
130
+ * ```
131
+ */
132
+ export const email = (message = 'Invalid email address'): SyncValidator<unknown> => {
133
+ // Intentionally simple — covers the vast majority of valid addresses
134
+ // without re-implementing the full RFC 5322 grammar.
135
+ const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
136
+ return (value: unknown) => {
137
+ const str = typeof value === 'string' ? value : String(value ?? '');
138
+ if (str === '') return true; // empty is handled by `required`
139
+ return re.test(str) ? true : message;
140
+ };
141
+ };
142
+
143
+ /**
144
+ * Requires a string to be a valid URL.
145
+ *
146
+ * Uses the native `URL` constructor for validation.
147
+ *
148
+ * @param message - Custom error message (default: `'Invalid URL'`)
149
+ * @returns A sync validator function
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * import { url } from '@bquery/bquery/forms';
154
+ * const validate = url();
155
+ * validate('not-a-url'); // 'Invalid URL'
156
+ * validate('https://example.com'); // true
157
+ * ```
158
+ */
159
+ export const url = (message = 'Invalid URL'): SyncValidator<unknown> => {
160
+ return (value: unknown) => {
161
+ const str = typeof value === 'string' ? value : String(value ?? '');
162
+ if (str === '') return true; // empty is handled by `required`
163
+ try {
164
+ new URL(str);
165
+ return true;
166
+ } catch {
167
+ return message;
168
+ }
169
+ };
170
+ };
171
+
172
+ /**
173
+ * Requires a numeric value to be at least `limit`.
174
+ *
175
+ * @param limit - Minimum allowed value (inclusive)
176
+ * @param message - Custom error message
177
+ * @returns A sync validator function
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * import { min } from '@bquery/bquery/forms';
182
+ * const validate = min(1, 'Must be positive');
183
+ * validate(0); // 'Must be positive'
184
+ * validate(1); // true
185
+ * ```
186
+ */
187
+ export const min = (limit: number, message?: string): SyncValidator<unknown> => {
188
+ const msg = message ?? `Must be at least ${limit}`;
189
+ return (value: unknown) => {
190
+ if (value == null) return true;
191
+ if (typeof value === 'string' && value.trim() === '') return true;
192
+ const num = typeof value === 'number' ? value : Number(value);
193
+ return num >= limit ? true : msg;
194
+ };
195
+ };
196
+
197
+ /**
198
+ * Requires a numeric value to be at most `limit`.
199
+ *
200
+ * @param limit - Maximum allowed value (inclusive)
201
+ * @param message - Custom error message
202
+ * @returns A sync validator function
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { max } from '@bquery/bquery/forms';
207
+ * const validate = max(100, 'Too high');
208
+ * validate(101); // 'Too high'
209
+ * validate(100); // true
210
+ * ```
211
+ */
212
+ export const max = (limit: number, message?: string): SyncValidator<unknown> => {
213
+ const msg = message ?? `Must be at most ${limit}`;
214
+ return (value: unknown) => {
215
+ if (value == null) return true;
216
+ if (typeof value === 'string' && value.trim() === '') return true;
217
+ const num = typeof value === 'number' ? value : Number(value);
218
+ return num <= limit ? true : msg;
219
+ };
220
+ };
221
+
222
+ /**
223
+ * Creates a custom synchronous validator from any predicate function.
224
+ *
225
+ * @param fn - Predicate that returns `true` when the value is valid
226
+ * @param message - Error message when the predicate returns `false`
227
+ * @returns A sync validator function
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * import { custom } from '@bquery/bquery/forms';
232
+ * const isEven = custom((v: number) => v % 2 === 0, 'Must be even');
233
+ * isEven(3); // 'Must be even'
234
+ * isEven(4); // true
235
+ * ```
236
+ */
237
+ export const custom = <T = unknown>(
238
+ fn: (value: T) => boolean,
239
+ message: string
240
+ ): SyncValidator<T> => {
241
+ return (value: T) => (fn(value) ? true : message);
242
+ };
243
+
244
+ /**
245
+ * Creates a custom asynchronous validator.
246
+ *
247
+ * @param fn - Async predicate that resolves to `true` when valid
248
+ * @param message - Error message when the predicate resolves to `false`
249
+ * @returns An async validator function
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import { customAsync } from '@bquery/bquery/forms';
254
+ * const isUnique = customAsync(
255
+ * async (name: string) => !(await checkExists(name)),
256
+ * 'Already taken',
257
+ * );
258
+ * ```
259
+ */
260
+ export const customAsync = <T = unknown>(
261
+ fn: (value: T) => Promise<boolean>,
262
+ message: string
263
+ ): AsyncValidator<T> => {
264
+ return async (value: T) => ((await fn(value)) ? true : message);
265
+ };
266
+
267
+ /**
268
+ * Requires a field's value to match the current value of a reference signal.
269
+ *
270
+ * Typically used for "confirm password" or "confirm email" patterns where
271
+ * one field must have the same value as another.
272
+ *
273
+ * @param ref - A reactive signal whose current value is the comparison target
274
+ * @param message - Custom error message (default: `'Fields do not match'`)
275
+ * @returns A sync validator function
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * import { signal } from '@bquery/bquery/reactive';
280
+ * import { matchField } from '@bquery/bquery/forms';
281
+ *
282
+ * const password = signal('secret');
283
+ * const confirmPassword = signal('');
284
+ * const validateConfirmPassword = matchField(password, 'Passwords must match');
285
+ *
286
+ * validateConfirmPassword(confirmPassword.value);
287
+ * ```
288
+ */
289
+ export const matchField = <T>(
290
+ ref: { readonly value: T },
291
+ message = 'Fields do not match'
292
+ ): SyncValidator<T> => {
293
+ return (value: T) => (Object.is(value, ref.value) ? true : message);
294
+ };