@bquery/bquery 1.6.0 → 1.7.0

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 (359) hide show
  1. package/README.md +716 -586
  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-C5QOVvRn.js +421 -0
  19. package/dist/a11y-C5QOVvRn.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-CuuTijA6.js +684 -0
  31. package/dist/component-CuuTijA6.js.map +1 -0
  32. package/dist/component.es.mjs +9 -6
  33. package/dist/{config-DRmZZno3.js → config-BW35FKuA.js} +4 -4
  34. package/dist/{config-DRmZZno3.js.map → config-BW35FKuA.js.map} +1 -1
  35. package/dist/constraints-3lV9yyBw.js +100 -0
  36. package/dist/constraints-3lV9yyBw.js.map +1 -0
  37. package/dist/core/collection.d.ts +48 -0
  38. package/dist/core/collection.d.ts.map +1 -1
  39. package/dist/core/element.d.ts +92 -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 +8 -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-Cjl7GUu8.js +717 -0
  50. package/dist/core-Cjl7GUu8.js.map +1 -0
  51. package/dist/core-DnlyjbF2.js +112 -0
  52. package/dist/core-DnlyjbF2.js.map +1 -0
  53. package/dist/core.es.mjs +45 -44
  54. package/dist/custom-directives-7wAShnnd.js +9 -0
  55. package/dist/custom-directives-7wAShnnd.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-D2fQLhDN.js +122 -0
  63. package/dist/devtools-D2fQLhDN.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-B8EgyzaI.js +244 -0
  76. package/dist/dnd-B8EgyzaI.js.map +1 -0
  77. package/dist/dnd.es.mjs +6 -0
  78. package/dist/env-NeVmr4Gf.js +19 -0
  79. package/dist/env-NeVmr4Gf.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 +39 -0
  83. package/dist/forms/index.d.ts.map +1 -0
  84. package/dist/forms/types.d.ts +139 -0
  85. package/dist/forms/types.d.ts.map +1 -0
  86. package/dist/forms/validators.d.ts +179 -0
  87. package/dist/forms/validators.d.ts.map +1 -0
  88. package/dist/forms-C3yovgH9.js +141 -0
  89. package/dist/forms-C3yovgH9.js.map +1 -0
  90. package/dist/forms.es.mjs +14 -0
  91. package/dist/full.d.ts +35 -7
  92. package/dist/full.d.ts.map +1 -1
  93. package/dist/full.es.mjs +182 -91
  94. package/dist/full.iife.js +47 -31
  95. package/dist/full.iife.js.map +1 -1
  96. package/dist/full.umd.js +47 -31
  97. package/dist/full.umd.js.map +1 -1
  98. package/dist/i18n/formatting.d.ts +40 -0
  99. package/dist/i18n/formatting.d.ts.map +1 -0
  100. package/dist/i18n/i18n.d.ts +48 -0
  101. package/dist/i18n/i18n.d.ts.map +1 -0
  102. package/dist/i18n/index.d.ts +57 -0
  103. package/dist/i18n/index.d.ts.map +1 -0
  104. package/dist/i18n/translate.d.ts +83 -0
  105. package/dist/i18n/translate.d.ts.map +1 -0
  106. package/dist/i18n/types.d.ts +156 -0
  107. package/dist/i18n/types.d.ts.map +1 -0
  108. package/dist/i18n-BnnhTFOS.js +89 -0
  109. package/dist/i18n-BnnhTFOS.js.map +1 -0
  110. package/dist/i18n.es.mjs +6 -0
  111. package/dist/index.d.ts +11 -0
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.es.mjs +227 -136
  114. package/dist/media/battery.d.ts +35 -0
  115. package/dist/media/battery.d.ts.map +1 -0
  116. package/dist/media/breakpoints.d.ts +51 -0
  117. package/dist/media/breakpoints.d.ts.map +1 -0
  118. package/dist/media/clipboard.d.ts +30 -0
  119. package/dist/media/clipboard.d.ts.map +1 -0
  120. package/dist/media/device-sensors.d.ts +54 -0
  121. package/dist/media/device-sensors.d.ts.map +1 -0
  122. package/dist/media/geolocation.d.ts +38 -0
  123. package/dist/media/geolocation.d.ts.map +1 -0
  124. package/dist/media/index.d.ts +42 -0
  125. package/dist/media/index.d.ts.map +1 -0
  126. package/dist/media/media-query.d.ts +36 -0
  127. package/dist/media/media-query.d.ts.map +1 -0
  128. package/dist/media/network.d.ts +35 -0
  129. package/dist/media/network.d.ts.map +1 -0
  130. package/dist/media/types.d.ts +173 -0
  131. package/dist/media/types.d.ts.map +1 -0
  132. package/dist/media/viewport.d.ts +32 -0
  133. package/dist/media/viewport.d.ts.map +1 -0
  134. package/dist/media-Di2Ta22s.js +340 -0
  135. package/dist/media-Di2Ta22s.js.map +1 -0
  136. package/dist/media.es.mjs +12 -0
  137. package/dist/motion/index.d.ts +7 -3
  138. package/dist/motion/index.d.ts.map +1 -1
  139. package/dist/motion/morph.d.ts +27 -0
  140. package/dist/motion/morph.d.ts.map +1 -0
  141. package/dist/motion/parallax.d.ts +30 -0
  142. package/dist/motion/parallax.d.ts.map +1 -0
  143. package/dist/motion/reduced-motion.d.ts +36 -3
  144. package/dist/motion/reduced-motion.d.ts.map +1 -1
  145. package/dist/motion/types.d.ts +58 -0
  146. package/dist/motion/types.d.ts.map +1 -1
  147. package/dist/motion/typewriter.d.ts +31 -0
  148. package/dist/motion/typewriter.d.ts.map +1 -0
  149. package/dist/motion-qPj_TYGv.js +530 -0
  150. package/dist/motion-qPj_TYGv.js.map +1 -0
  151. package/dist/motion.es.mjs +27 -23
  152. package/dist/{view-C70lA3vf.js → mount-SM07RUa6.js} +166 -160
  153. package/dist/mount-SM07RUa6.js.map +1 -0
  154. package/dist/{object-qGpWr6-J.js → object-BCk-1c8T.js} +5 -4
  155. package/dist/{object-qGpWr6-J.js.map → object-BCk-1c8T.js.map} +1 -1
  156. package/dist/{platform-Dr9b6fsq.js → platform-CPbCprb6.js} +21 -22
  157. package/dist/{platform-Dr9b6fsq.js.map → platform-CPbCprb6.js.map} +1 -1
  158. package/dist/platform.es.mjs +2 -2
  159. package/dist/plugin/index.d.ts +22 -0
  160. package/dist/plugin/index.d.ts.map +1 -0
  161. package/dist/plugin/registry.d.ts +108 -0
  162. package/dist/plugin/registry.d.ts.map +1 -0
  163. package/dist/plugin/types.d.ts +110 -0
  164. package/dist/plugin/types.d.ts.map +1 -0
  165. package/dist/plugin-cPoOHFLY.js +64 -0
  166. package/dist/plugin-cPoOHFLY.js.map +1 -0
  167. package/dist/plugin.es.mjs +9 -0
  168. package/dist/reactive/computed.d.ts +7 -0
  169. package/dist/reactive/computed.d.ts.map +1 -1
  170. package/dist/reactive-Cfv0RK6x.js +233 -0
  171. package/dist/reactive-Cfv0RK6x.js.map +1 -0
  172. package/dist/reactive.es.mjs +19 -20
  173. package/dist/registry-CWf368tT.js +26 -0
  174. package/dist/registry-CWf368tT.js.map +1 -0
  175. package/dist/router/bq-link.d.ts +112 -0
  176. package/dist/router/bq-link.d.ts.map +1 -0
  177. package/dist/router/constraints.d.ts +9 -0
  178. package/dist/router/constraints.d.ts.map +1 -0
  179. package/dist/router/index.d.ts +14 -6
  180. package/dist/router/index.d.ts.map +1 -1
  181. package/dist/router/match.d.ts +0 -1
  182. package/dist/router/match.d.ts.map +1 -1
  183. package/dist/router/path-pattern.d.ts +14 -0
  184. package/dist/router/path-pattern.d.ts.map +1 -0
  185. package/dist/router/query.d.ts.map +1 -1
  186. package/dist/router/router.d.ts +3 -1
  187. package/dist/router/router.d.ts.map +1 -1
  188. package/dist/router/types.d.ts +48 -4
  189. package/dist/router/types.d.ts.map +1 -1
  190. package/dist/router/use-route.d.ts +50 -0
  191. package/dist/router/use-route.d.ts.map +1 -0
  192. package/dist/router/utils.d.ts +3 -0
  193. package/dist/router/utils.d.ts.map +1 -1
  194. package/dist/router-BrthaP_z.js +473 -0
  195. package/dist/router-BrthaP_z.js.map +1 -0
  196. package/dist/router.es.mjs +13 -10
  197. package/dist/{sanitize-Bs2dkMby.js → sanitize-B1V4JswB.js} +2 -1
  198. package/dist/{sanitize-Bs2dkMby.js.map → sanitize-B1V4JswB.js.map} +1 -1
  199. package/dist/security/index.d.ts +2 -2
  200. package/dist/security/index.d.ts.map +1 -1
  201. package/dist/security.es.mjs +1 -1
  202. package/dist/ssr/hydrate.d.ts +65 -0
  203. package/dist/ssr/hydrate.d.ts.map +1 -0
  204. package/dist/ssr/index.d.ts +59 -0
  205. package/dist/ssr/index.d.ts.map +1 -0
  206. package/dist/ssr/render.d.ts +62 -0
  207. package/dist/ssr/render.d.ts.map +1 -0
  208. package/dist/ssr/serialize.d.ts +118 -0
  209. package/dist/ssr/serialize.d.ts.map +1 -0
  210. package/dist/ssr/types.d.ts +70 -0
  211. package/dist/ssr/types.d.ts.map +1 -0
  212. package/dist/ssr-B2qd_WBB.js +248 -0
  213. package/dist/ssr-B2qd_WBB.js.map +1 -0
  214. package/dist/ssr.es.mjs +9 -0
  215. package/dist/store/create-store.d.ts.map +1 -1
  216. package/dist/store/index.d.ts +1 -1
  217. package/dist/store/index.d.ts.map +1 -1
  218. package/dist/store/persisted.d.ts +38 -4
  219. package/dist/store/persisted.d.ts.map +1 -1
  220. package/dist/store/types.d.ts +138 -1
  221. package/dist/store/types.d.ts.map +1 -1
  222. package/dist/store/utils.d.ts +2 -2
  223. package/dist/store/utils.d.ts.map +1 -1
  224. package/dist/store-DWpyH6p5.js +338 -0
  225. package/dist/store-DWpyH6p5.js.map +1 -0
  226. package/dist/store.es.mjs +11 -10
  227. package/dist/storybook/index.d.ts.map +1 -1
  228. package/dist/storybook.es.mjs +1 -1
  229. package/dist/storybook.es.mjs.map +1 -1
  230. package/dist/testing/index.d.ts +23 -0
  231. package/dist/testing/index.d.ts.map +1 -0
  232. package/dist/testing/testing.d.ts +156 -0
  233. package/dist/testing/testing.d.ts.map +1 -0
  234. package/dist/testing/types.d.ts +134 -0
  235. package/dist/testing/types.d.ts.map +1 -0
  236. package/dist/testing-CsqjNUyy.js +224 -0
  237. package/dist/testing-CsqjNUyy.js.map +1 -0
  238. package/dist/testing.es.mjs +9 -0
  239. package/dist/type-guards-Do9DWgNp.js +44 -0
  240. package/dist/type-guards-Do9DWgNp.js.map +1 -0
  241. package/dist/untrack-DJVQQ2WM.js +33 -0
  242. package/dist/untrack-DJVQQ2WM.js.map +1 -0
  243. package/dist/view/custom-directives.d.ts +20 -0
  244. package/dist/view/custom-directives.d.ts.map +1 -0
  245. package/dist/view/evaluate.d.ts.map +1 -1
  246. package/dist/view/process.d.ts.map +1 -1
  247. package/dist/view.es.mjs +9 -9
  248. package/package.json +177 -141
  249. package/src/a11y/announce.ts +131 -0
  250. package/src/a11y/audit.ts +314 -0
  251. package/src/a11y/index.ts +68 -0
  252. package/src/a11y/media-preferences.ts +255 -0
  253. package/src/a11y/roving-tab-index.ts +164 -0
  254. package/src/a11y/skip-link.ts +255 -0
  255. package/src/a11y/trap-focus.ts +184 -0
  256. package/src/a11y/types.ts +183 -0
  257. package/src/component/component.ts +104 -29
  258. package/src/component/html.ts +5 -5
  259. package/src/component/index.ts +2 -0
  260. package/src/component/library.ts +26 -2
  261. package/src/component/scope.ts +212 -0
  262. package/src/component/types.ts +94 -40
  263. package/src/core/collection.ts +707 -628
  264. package/src/core/element.ts +981 -774
  265. package/src/core/env.ts +60 -0
  266. package/src/core/index.ts +49 -48
  267. package/src/core/shared.ts +62 -13
  268. package/src/core/utils/index.ts +148 -83
  269. package/src/devtools/devtools.ts +410 -0
  270. package/src/devtools/index.ts +48 -0
  271. package/src/devtools/types.ts +104 -0
  272. package/src/dnd/draggable.ts +296 -0
  273. package/src/dnd/droppable.ts +228 -0
  274. package/src/dnd/index.ts +62 -0
  275. package/src/dnd/sortable.ts +307 -0
  276. package/src/dnd/types.ts +293 -0
  277. package/src/forms/create-form.ts +278 -0
  278. package/src/forms/index.ts +65 -0
  279. package/src/forms/types.ts +154 -0
  280. package/src/forms/validators.ts +265 -0
  281. package/src/full.ts +253 -2
  282. package/src/i18n/formatting.ts +67 -0
  283. package/src/i18n/i18n.ts +200 -0
  284. package/src/i18n/index.ts +67 -0
  285. package/src/i18n/translate.ts +182 -0
  286. package/src/i18n/types.ts +171 -0
  287. package/src/index.ts +108 -36
  288. package/src/media/battery.ts +116 -0
  289. package/src/media/breakpoints.ts +131 -0
  290. package/src/media/clipboard.ts +80 -0
  291. package/src/media/device-sensors.ts +158 -0
  292. package/src/media/geolocation.ts +119 -0
  293. package/src/media/index.ts +76 -0
  294. package/src/media/media-query.ts +92 -0
  295. package/src/media/network.ts +115 -0
  296. package/src/media/types.ts +177 -0
  297. package/src/media/viewport.ts +84 -0
  298. package/src/motion/index.ts +57 -48
  299. package/src/motion/morph.ts +151 -0
  300. package/src/motion/parallax.ts +120 -0
  301. package/src/motion/reduced-motion.ts +66 -17
  302. package/src/motion/types.ts +271 -208
  303. package/src/motion/typewriter.ts +164 -0
  304. package/src/plugin/index.ts +37 -0
  305. package/src/plugin/registry.ts +269 -0
  306. package/src/plugin/types.ts +137 -0
  307. package/src/reactive/computed.ts +130 -92
  308. package/src/router/bq-link.ts +279 -0
  309. package/src/router/constraints.ts +201 -0
  310. package/src/router/index.ts +49 -41
  311. package/src/router/match.ts +312 -106
  312. package/src/router/path-pattern.ts +52 -0
  313. package/src/router/query.ts +38 -35
  314. package/src/router/router.ts +402 -211
  315. package/src/router/types.ts +139 -93
  316. package/src/router/use-route.ts +68 -0
  317. package/src/router/utils.ts +157 -116
  318. package/src/security/index.ts +2 -7
  319. package/src/security/sanitize.ts +70 -70
  320. package/src/security/trusted-html.ts +71 -71
  321. package/src/ssr/hydrate.ts +82 -0
  322. package/src/ssr/index.ts +70 -0
  323. package/src/ssr/render.ts +508 -0
  324. package/src/ssr/serialize.ts +296 -0
  325. package/src/ssr/types.ts +81 -0
  326. package/src/store/create-store.ts +467 -329
  327. package/src/store/define-store.ts +49 -49
  328. package/src/store/index.ts +27 -22
  329. package/src/store/mapping.ts +74 -74
  330. package/src/store/persisted.ts +206 -19
  331. package/src/store/types.ts +157 -2
  332. package/src/store/utils.ts +135 -141
  333. package/src/store/watch.ts +53 -53
  334. package/src/storybook/index.ts +2 -1
  335. package/src/testing/index.ts +42 -0
  336. package/src/testing/testing.ts +593 -0
  337. package/src/testing/types.ts +170 -0
  338. package/src/view/custom-directives.ts +30 -0
  339. package/src/view/evaluate.ts +292 -290
  340. package/src/view/process.ts +108 -92
  341. package/dist/component-BEQgt5hl.js +0 -600
  342. package/dist/component-BEQgt5hl.js.map +0 -1
  343. package/dist/core-BGQJVw0-.js +0 -35
  344. package/dist/core-BGQJVw0-.js.map +0 -1
  345. package/dist/core-CCEabVHl.js +0 -648
  346. package/dist/core-CCEabVHl.js.map +0 -1
  347. package/dist/effect-AFRW_Plg.js +0 -84
  348. package/dist/effect-AFRW_Plg.js.map +0 -1
  349. package/dist/motion-D9TcHxOF.js +0 -415
  350. package/dist/motion-D9TcHxOF.js.map +0 -1
  351. package/dist/reactive-DSkct0dO.js +0 -254
  352. package/dist/reactive-DSkct0dO.js.map +0 -1
  353. package/dist/router-CbDhl8rS.js +0 -188
  354. package/dist/router-CbDhl8rS.js.map +0 -1
  355. package/dist/store-BwDvI45q.js +0 -263
  356. package/dist/store-BwDvI45q.js.map +0 -1
  357. package/dist/untrack-B0rVscTc.js +0 -7
  358. package/dist/untrack-B0rVscTc.js.map +0 -1
  359. package/dist/view-C70lA3vf.js.map +0 -1
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Type definitions for the bQuery accessibility (a11y) module.
3
+ *
4
+ * @module bquery/a11y
5
+ */
6
+
7
+ import type { ReadonlySignal } from '../reactive/index';
8
+
9
+ // ─── Focus Trap ──────────────────────────────────────────────────────────────
10
+
11
+ /**
12
+ * Options for configuring focus trapping behavior.
13
+ */
14
+ export interface TrapFocusOptions {
15
+ /**
16
+ * Element to receive initial focus when the trap activates.
17
+ * If not provided, the first focusable element is focused.
18
+ */
19
+ initialFocus?: HTMLElement | string;
20
+
21
+ /**
22
+ * Element to receive focus when the trap is released.
23
+ * If not provided, focus returns to the element that was focused
24
+ * before the trap was activated.
25
+ */
26
+ returnFocus?: HTMLElement | string;
27
+
28
+ /**
29
+ * Whether pressing Escape releases the focus trap.
30
+ * @default true
31
+ */
32
+ escapeDeactivates?: boolean;
33
+
34
+ /**
35
+ * Callback invoked when the trap is deactivated via Escape.
36
+ */
37
+ onEscape?: () => void;
38
+ }
39
+
40
+ /**
41
+ * Handle returned by `trapFocus()` for managing the focus trap lifecycle.
42
+ */
43
+ export interface FocusTrapHandle {
44
+ /** Release the focus trap, restoring focus to the previous element. */
45
+ release: () => void;
46
+ /** Whether the trap is currently active. */
47
+ active: boolean;
48
+ }
49
+
50
+ // ─── Screen Reader Announcements ─────────────────────────────────────────────
51
+
52
+ /**
53
+ * Priority level for screen reader announcements.
54
+ * - `'polite'` — announced when the user is idle (default)
55
+ * - `'assertive'` — announced immediately, interrupting current speech
56
+ */
57
+ export type AnnouncePriority = 'polite' | 'assertive';
58
+
59
+ // ─── Roving Tab Index ────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Options for configuring roving tab index behavior.
63
+ */
64
+ export interface RovingTabIndexOptions {
65
+ /**
66
+ * Whether navigation wraps around from last to first (and vice versa).
67
+ * @default true
68
+ */
69
+ wrap?: boolean;
70
+
71
+ /**
72
+ * Orientation of the group — determines which arrow keys are used.
73
+ * - `'horizontal'` — Left/Right arrows
74
+ * - `'vertical'` — Up/Down arrows
75
+ * - `'both'` — All arrow keys
76
+ * @default 'vertical'
77
+ */
78
+ orientation?: 'horizontal' | 'vertical' | 'both';
79
+
80
+ /**
81
+ * Callback fired when the active item changes.
82
+ */
83
+ onActivate?: (element: Element, index: number) => void;
84
+ }
85
+
86
+ /**
87
+ * Handle returned by `rovingTabIndex()` for cleanup.
88
+ */
89
+ export interface RovingTabIndexHandle {
90
+ /** Remove event listeners and restore original tabindex values. */
91
+ destroy: () => void;
92
+ /** Programmatically focus a specific item by index. */
93
+ focusItem: (index: number) => void;
94
+ /** Get the currently active index. */
95
+ activeIndex: () => number;
96
+ }
97
+
98
+ // ─── Skip Link ───────────────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Options for configuring auto-generated skip navigation.
102
+ */
103
+ export interface SkipLinkOptions {
104
+ /**
105
+ * Text content of the skip link.
106
+ * @default 'Skip to main content'
107
+ */
108
+ text?: string;
109
+
110
+ /**
111
+ * CSS class applied to the skip link element.
112
+ * @default 'bq-skip-link'
113
+ */
114
+ className?: string;
115
+ }
116
+
117
+ /**
118
+ * Handle returned by `skipLink()` for cleanup.
119
+ */
120
+ export interface SkipLinkHandle {
121
+ /** Remove the skip link from the DOM. */
122
+ destroy: () => void;
123
+ /**
124
+ * The created skip link element, or `null` when `skipLink()` is called in a
125
+ * non-DOM environment and returns a no-op handle.
126
+ */
127
+ element: HTMLAnchorElement | null;
128
+ }
129
+
130
+ // ─── Media Preferences ───────────────────────────────────────────────────────
131
+
132
+ /**
133
+ * Color scheme preference value.
134
+ */
135
+ export type ColorScheme = 'light' | 'dark';
136
+
137
+ /**
138
+ * Contrast preference value.
139
+ */
140
+ export type ContrastPreference = 'no-preference' | 'more' | 'less' | 'custom';
141
+
142
+ /**
143
+ * Readonly media preference signal with an explicit cleanup hook.
144
+ */
145
+ export interface MediaPreferenceSignal<T> extends ReadonlySignal<T> {
146
+ /** Releases underlying media-query listeners. Safe to call multiple times. */
147
+ destroy(): void;
148
+ }
149
+
150
+ // ─── Accessibility Audit ─────────────────────────────────────────────────────
151
+
152
+ /**
153
+ * Severity level for audit findings.
154
+ */
155
+ export type AuditSeverity = 'error' | 'warning' | 'info';
156
+
157
+ /**
158
+ * A single accessibility audit finding.
159
+ */
160
+ export interface AuditFinding {
161
+ /** Severity level of the finding. */
162
+ severity: AuditSeverity;
163
+ /** Human-readable description of the issue. */
164
+ message: string;
165
+ /** The DOM element with the issue. */
166
+ element: Element;
167
+ /** The audit rule that triggered this finding. */
168
+ rule: string;
169
+ }
170
+
171
+ /**
172
+ * Result of an accessibility audit.
173
+ */
174
+ export interface AuditResult {
175
+ /** All findings from the audit. */
176
+ findings: AuditFinding[];
177
+ /** Number of errors found. */
178
+ errors: number;
179
+ /** Number of warnings found. */
180
+ warnings: number;
181
+ /** Whether the audit passed (no errors). */
182
+ passed: boolean;
183
+ }
@@ -4,10 +4,11 @@
4
4
  * @module bquery/component
5
5
  */
6
6
 
7
- import { sanitizeHtml } from '../security/sanitize';
8
- import { effect, untrack } from '../reactive/signal';
9
7
  import type { CleanupFn } from '../reactive/signal';
8
+ import { effect, untrack } from '../reactive/signal';
9
+ import { sanitizeHtml } from '../security/sanitize';
10
10
  import { coercePropValue } from './props';
11
+ import { createComponentScope, setCurrentScope, type ComponentScope } from './scope';
11
12
  import type {
12
13
  AttributeChange,
13
14
  ComponentClass,
@@ -16,6 +17,7 @@ import type {
16
17
  ComponentSignals,
17
18
  ComponentStateShape,
18
19
  PropDefinition,
20
+ ShadowMode,
19
21
  } from './types';
20
22
 
21
23
  /**
@@ -73,13 +75,33 @@ const createComponentClass = <
73
75
  tagName: string,
74
76
  definition: ComponentDefinition<TProps, TState, TSignals>
75
77
  ): ComponentClass<TState> => {
76
- const componentAllowedTags = [...COMPONENT_ALLOWED_TAGS, ...(definition.sanitize?.allowTags ?? [])];
78
+ const componentAllowedTags = [
79
+ ...COMPONENT_ALLOWED_TAGS,
80
+ ...(definition.sanitize?.allowTags ?? []),
81
+ ];
77
82
  const componentAllowedAttributes = [
78
83
  ...COMPONENT_ALLOWED_ATTRIBUTES,
79
84
  ...(definition.sanitize?.allowAttributes ?? []),
80
85
  ];
81
86
  const signalSources = Object.values(definition.signals ?? {}) as ComponentSignalLike<unknown>[];
82
87
 
88
+ /** Resolve the Shadow DOM mode from the `shadow` option. */
89
+ const resolveShadowMode = (option: ShadowMode | undefined): 'open' | 'closed' | false => {
90
+ if (option === false) return false;
91
+ if (option === 'closed') return 'closed';
92
+ // true, 'open', or undefined all resolve to 'open'
93
+ return 'open';
94
+ };
95
+ const shadowMode = resolveShadowMode(definition.shadow);
96
+
97
+ /**
98
+ * Merges prop-derived observed attributes with any extra attributes from
99
+ * `observeAttributes`, deduplicating to avoid redundant callbacks.
100
+ */
101
+ const observedAttrs = Array.from(
102
+ new Set([...Object.keys(definition.props ?? {}), ...(definition.observeAttributes ?? [])])
103
+ );
104
+
83
105
  class BQueryComponent extends HTMLElement {
84
106
  /** Internal state object for the component */
85
107
  private readonly state: ComponentStateShape<TState> = {
@@ -93,10 +115,18 @@ const createComponentClass = <
93
115
  private hasMounted = false;
94
116
  /** Cleanup for external signal subscriptions */
95
117
  private signalEffectCleanup?: CleanupFn;
118
+ /** Component-scoped reactive resource tracker */
119
+ private scope?: ComponentScope;
120
+ /** Render target for open/closed shadow roots or the host element when shadow DOM is disabled */
121
+ private readonly renderRootNode: HTMLElement | ShadowRoot;
96
122
 
97
123
  constructor() {
98
124
  super();
99
- this.attachShadow({ mode: 'open' });
125
+ if (shadowMode !== false) {
126
+ this.renderRootNode = this.attachShadow({ mode: shadowMode });
127
+ } else {
128
+ this.renderRootNode = this;
129
+ }
100
130
  this.syncProps();
101
131
  }
102
132
 
@@ -104,7 +134,7 @@ const createComponentClass = <
104
134
  * Returns the list of attributes to observe for changes.
105
135
  */
106
136
  static get observedAttributes(): string[] {
107
- return Object.keys(definition.props ?? {});
137
+ return observedAttrs;
108
138
  }
109
139
 
110
140
  /**
@@ -121,10 +151,15 @@ const createComponentClass = <
121
151
  return;
122
152
  }
123
153
  if (this.hasMounted) {
154
+ // Recreate scope for reconnected component
155
+ this.scope = createComponentScope();
156
+ const previousScope = setCurrentScope(this.scope);
124
157
  try {
125
158
  definition.connected?.call(this);
126
159
  } catch (error) {
127
160
  this.handleError(error as Error);
161
+ } finally {
162
+ setCurrentScope(previousScope);
128
163
  }
129
164
  this.setupSignalSubscriptions(true);
130
165
  return;
@@ -142,8 +177,22 @@ const createComponentClass = <
142
177
  */
143
178
  private mount(): void {
144
179
  if (this.hasMounted) return;
145
- definition.beforeMount?.call(this);
146
- definition.connected?.call(this);
180
+ const previousScope = setCurrentScope(this.ensureScope());
181
+ let hookError = false;
182
+ try {
183
+ definition.beforeMount?.call(this);
184
+ definition.connected?.call(this);
185
+ } catch (error) {
186
+ hookError = true;
187
+ this.handleError(error as Error);
188
+ } finally {
189
+ setCurrentScope(previousScope);
190
+ }
191
+ if (hookError) {
192
+ this.scope?.dispose();
193
+ this.scope = undefined;
194
+ return;
195
+ }
147
196
  this.render();
148
197
  this.setupSignalSubscriptions();
149
198
  this.hasMounted = true;
@@ -156,6 +205,9 @@ const createComponentClass = <
156
205
  try {
157
206
  this.signalEffectCleanup?.();
158
207
  this.signalEffectCleanup = undefined;
208
+ // Dispose all scoped reactive resources (useSignal, useComputed, useEffect)
209
+ this.scope?.dispose();
210
+ this.scope = undefined;
159
211
  definition.disconnected?.call(this);
160
212
  } catch (error) {
161
213
  this.handleError(error as Error);
@@ -165,15 +217,21 @@ const createComponentClass = <
165
217
  /**
166
218
  * Called when an observed attribute changes.
167
219
  */
168
- attributeChangedCallback(
169
- name: string,
170
- oldValue: string | null,
171
- newValue: string | null
172
- ): void {
220
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
173
221
  try {
174
222
  const previousProps = this.cloneProps();
175
223
  this.syncProps();
176
224
 
225
+ // Fire the user-facing onAttributeChanged hook for every observed attribute change
226
+ if (definition.onAttributeChanged) {
227
+ const previousScope = setCurrentScope(this.ensureScope());
228
+ try {
229
+ definition.onAttributeChanged.call(this, name, oldValue, newValue);
230
+ } finally {
231
+ setCurrentScope(previousScope);
232
+ }
233
+ }
234
+
177
235
  if (this.hasMounted) {
178
236
  // Component already mounted - trigger update render
179
237
  this.render(true, previousProps, { name, oldValue, newValue });
@@ -187,6 +245,24 @@ const createComponentClass = <
187
245
  }
188
246
  }
189
247
 
248
+ /**
249
+ * Called when the element is moved to a new document (e.g. via `document.adoptNode`).
250
+ */
251
+ adoptedCallback(): void {
252
+ if (!definition.onAdopted) {
253
+ return;
254
+ }
255
+
256
+ const previousScope = setCurrentScope(this.ensureScope());
257
+ try {
258
+ definition.onAdopted.call(this);
259
+ } catch (error) {
260
+ this.handleError(error as Error);
261
+ } finally {
262
+ setCurrentScope(previousScope);
263
+ }
264
+ }
265
+
190
266
  /**
191
267
  * Handles errors during component lifecycle.
192
268
  * @internal
@@ -199,6 +275,14 @@ const createComponentClass = <
199
275
  }
200
276
  }
201
277
 
278
+ /**
279
+ * Ensures the component has an active scope for scoped reactive primitives.
280
+ * @internal
281
+ */
282
+ private ensureScope(): ComponentScope {
283
+ return (this.scope ??= createComponentScope());
284
+ }
285
+
202
286
  /**
203
287
  * Updates a state property and triggers a re-render.
204
288
  *
@@ -324,7 +408,7 @@ const createComponentClass = <
324
408
  }
325
409
 
326
410
  /**
327
- * Renders the component to its shadow root.
411
+ * Renders the component to its shadow root or host element.
328
412
  * @internal
329
413
  */
330
414
  private render(): void;
@@ -354,7 +438,7 @@ const createComponentClass = <
354
438
  this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, composed: true }));
355
439
  };
356
440
 
357
- if (!this.shadowRoot) return;
441
+ const renderRoot = this.renderRootNode;
358
442
 
359
443
  const markup = definition.render({
360
444
  props: this.props,
@@ -373,12 +457,12 @@ const createComponentClass = <
373
457
  });
374
458
  let existingStyleElement: HTMLStyleElement | null = null;
375
459
  if (definition.styles) {
376
- existingStyleElement = this.shadowRoot.querySelector<HTMLStyleElement>(
460
+ existingStyleElement = renderRoot.querySelector<HTMLStyleElement>(
377
461
  'style[data-bquery-component-style]'
378
462
  );
379
463
  }
380
464
 
381
- this.shadowRoot.innerHTML = sanitizedMarkup;
465
+ renderRoot.innerHTML = sanitizedMarkup;
382
466
 
383
467
  if (definition.styles) {
384
468
  const styleElement = existingStyleElement ?? document.createElement('style');
@@ -386,7 +470,7 @@ const createComponentClass = <
386
470
  styleElement.setAttribute('data-bquery-component-style', '');
387
471
  }
388
472
  styleElement.textContent = definition.styles;
389
- this.shadowRoot.prepend(styleElement);
473
+ renderRoot.prepend(styleElement);
390
474
  }
391
475
 
392
476
  if (triggerUpdated) {
@@ -496,26 +580,17 @@ export function defineComponent<
496
580
  export function component<
497
581
  TProps extends Record<string, unknown>,
498
582
  TSignals extends ComponentSignals = Record<string, never>,
499
- >(
500
- tagName: string,
501
- definition: ComponentDefinition<TProps, undefined, TSignals>
502
- ): void;
583
+ >(tagName: string, definition: ComponentDefinition<TProps, undefined, TSignals>): void;
503
584
  export function component<
504
585
  TProps extends Record<string, unknown>,
505
586
  TState extends Record<string, unknown>,
506
587
  TSignals extends ComponentSignals = Record<string, never>,
507
- >(
508
- tagName: string,
509
- definition: ComponentDefinition<TProps, TState, TSignals>
510
- ): void;
588
+ >(tagName: string, definition: ComponentDefinition<TProps, TState, TSignals>): void;
511
589
  export function component<
512
590
  TProps extends Record<string, unknown>,
513
591
  TState extends Record<string, unknown> | undefined = undefined,
514
592
  TSignals extends ComponentSignals = Record<string, never>,
515
- >(
516
- tagName: string,
517
- definition: ComponentDefinition<TProps, TState, TSignals>
518
- ): void {
593
+ >(tagName: string, definition: ComponentDefinition<TProps, TState, TSignals>): void {
519
594
  const elementClass = createComponentClass(tagName, definition);
520
595
 
521
596
  if (!customElements.get(tagName)) {
@@ -117,7 +117,10 @@ export const bool = (name: string, enabled: unknown): BooleanAttribute => {
117
117
  * ```
118
118
  */
119
119
  export const html = (strings: TemplateStringsArray, ...values: unknown[]): string => {
120
- return strings.reduce((acc, part, index) => `${acc}${part}${stringifyTemplateValue(values[index])}`, '');
120
+ return strings.reduce(
121
+ (acc, part, index) => `${acc}${part}${stringifyTemplateValue(values[index])}`,
122
+ ''
123
+ );
121
124
  };
122
125
 
123
126
  /**
@@ -135,10 +138,7 @@ export const html = (strings: TemplateStringsArray, ...values: unknown[]): strin
135
138
  * // Result: '<div>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</div>'
136
139
  * ```
137
140
  */
138
- export const safeHtml = (
139
- strings: TemplateStringsArray,
140
- ...values: unknown[]
141
- ): SanitizedHtml => {
141
+ export const safeHtml = (strings: TemplateStringsArray, ...values: unknown[]): SanitizedHtml => {
142
142
  const escape = (value: unknown): string => {
143
143
  if (isTrustedHtml(value)) return unwrapTrustedHtml(value);
144
144
  return escapeTemplateValue(value);
@@ -38,6 +38,7 @@
38
38
  export { component, defineComponent } from './component';
39
39
  export { bool, html, safeHtml } from './html';
40
40
  export { registerDefaultComponents } from './library';
41
+ export { useComputed, useEffect, useSignal } from './scope';
41
42
  export type { DefaultComponentLibraryOptions, RegisteredDefaultComponents } from './library';
42
43
  export type {
43
44
  AttributeChange,
@@ -47,4 +48,5 @@ export type {
47
48
  ComponentSignalLike,
48
49
  ComponentSignals,
49
50
  PropDefinition,
51
+ ShadowMode,
50
52
  } from './types';
@@ -120,9 +120,21 @@ const canSkipInputRender = (
120
120
  if (oldProps.name !== newProps.name) return false;
121
121
  if (oldProps.disabled !== newProps.disabled) return false;
122
122
 
123
- const control = element.shadowRoot?.querySelector('input.control') as HTMLInputElement | null;
123
+ // Verify shadow DOM still matches expected non-value props before skipping re-render
124
+ const shadowRoot = element.shadowRoot;
125
+ if (!shadowRoot) return false;
126
+
127
+ const labelEl = shadowRoot.querySelector('.label');
128
+ if ((labelEl?.textContent ?? '') !== newProps.label) return false;
129
+
130
+ const control = shadowRoot.querySelector('input.control') as HTMLInputElement | null;
124
131
  if (!control) return false;
125
132
 
133
+ if (control.type !== newProps.type) return false;
134
+ if (control.placeholder !== newProps.placeholder) return false;
135
+ if (control.name !== newProps.name) return false;
136
+ if (control.disabled !== newProps.disabled) return false;
137
+
126
138
  if (control.value !== newProps.value) {
127
139
  control.value = newProps.value;
128
140
  }
@@ -163,11 +175,23 @@ const canSkipTextareaRender = (
163
175
  if (oldProps.rows !== newProps.rows) return false;
164
176
  if (oldProps.disabled !== newProps.disabled) return false;
165
177
 
166
- const control = element.shadowRoot?.querySelector(
178
+ // Verify shadow DOM still matches expected non-value props before skipping re-render
179
+ const shadowRoot = element.shadowRoot;
180
+ if (!shadowRoot) return false;
181
+
182
+ const labelEl = shadowRoot.querySelector('.label');
183
+ if ((labelEl?.textContent ?? '') !== newProps.label) return false;
184
+
185
+ const control = shadowRoot.querySelector(
167
186
  'textarea.control'
168
187
  ) as HTMLTextAreaElement | null;
169
188
  if (!control) return false;
170
189
 
190
+ if (control.placeholder !== newProps.placeholder) return false;
191
+ if (control.name !== newProps.name) return false;
192
+ if (Number(control.rows) !== newProps.rows) return false;
193
+ if (control.disabled !== newProps.disabled) return false;
194
+
171
195
  if (control.value !== newProps.value) {
172
196
  control.value = newProps.value;
173
197
  }