@bquery/bquery 1.5.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 (380) hide show
  1. package/README.md +193 -23
  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 +13 -5
  22. package/dist/component/component.d.ts.map +1 -1
  23. package/dist/component/html.d.ts +40 -3
  24. package/dist/component/html.d.ts.map +1 -1
  25. package/dist/component/index.d.ts +3 -2
  26. package/dist/component/index.d.ts.map +1 -1
  27. package/dist/component/library.d.ts.map +1 -1
  28. package/dist/component/scope.d.ts +138 -0
  29. package/dist/component/scope.d.ts.map +1 -0
  30. package/dist/component/types.d.ts +184 -17
  31. package/dist/component/types.d.ts.map +1 -1
  32. package/dist/component-CuuTijA6.js +684 -0
  33. package/dist/component-CuuTijA6.js.map +1 -0
  34. package/dist/component.es.mjs +10 -6
  35. package/dist/{config-DRmZZno3.js → config-BW35FKuA.js} +4 -4
  36. package/dist/config-BW35FKuA.js.map +1 -0
  37. package/dist/constraints-3lV9yyBw.js +100 -0
  38. package/dist/constraints-3lV9yyBw.js.map +1 -0
  39. package/dist/core/collection.d.ts +48 -0
  40. package/dist/core/collection.d.ts.map +1 -1
  41. package/dist/core/element.d.ts +92 -0
  42. package/dist/core/element.d.ts.map +1 -1
  43. package/dist/core/env.d.ts +18 -0
  44. package/dist/core/env.d.ts.map +1 -0
  45. package/dist/core/index.d.ts +1 -0
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/shared.d.ts +8 -0
  48. package/dist/core/shared.d.ts.map +1 -1
  49. package/dist/core/utils/index.d.ts +52 -41
  50. package/dist/core/utils/index.d.ts.map +1 -1
  51. package/dist/core-Cjl7GUu8.js +717 -0
  52. package/dist/core-Cjl7GUu8.js.map +1 -0
  53. package/dist/{core-DPdbItcq.js → core-DnlyjbF2.js} +1 -1
  54. package/dist/{core-DPdbItcq.js.map → core-DnlyjbF2.js.map} +1 -1
  55. package/dist/core.es.mjs +45 -44
  56. package/dist/custom-directives-7wAShnnd.js +9 -0
  57. package/dist/custom-directives-7wAShnnd.js.map +1 -0
  58. package/dist/devtools/devtools.d.ts +212 -0
  59. package/dist/devtools/devtools.d.ts.map +1 -0
  60. package/dist/devtools/index.d.ts +20 -0
  61. package/dist/devtools/index.d.ts.map +1 -0
  62. package/dist/devtools/types.d.ts +69 -0
  63. package/dist/devtools/types.d.ts.map +1 -0
  64. package/dist/devtools-D2fQLhDN.js +122 -0
  65. package/dist/devtools-D2fQLhDN.js.map +1 -0
  66. package/dist/devtools.es.mjs +19 -0
  67. package/dist/dnd/draggable.d.ts +51 -0
  68. package/dist/dnd/draggable.d.ts.map +1 -0
  69. package/dist/dnd/droppable.d.ts +38 -0
  70. package/dist/dnd/droppable.d.ts.map +1 -0
  71. package/dist/dnd/index.d.ts +47 -0
  72. package/dist/dnd/index.d.ts.map +1 -0
  73. package/dist/dnd/sortable.d.ts +43 -0
  74. package/dist/dnd/sortable.d.ts.map +1 -0
  75. package/dist/dnd/types.d.ts +250 -0
  76. package/dist/dnd/types.d.ts.map +1 -0
  77. package/dist/dnd-B8EgyzaI.js +244 -0
  78. package/dist/dnd-B8EgyzaI.js.map +1 -0
  79. package/dist/dnd.es.mjs +6 -0
  80. package/dist/env-NeVmr4Gf.js +19 -0
  81. package/dist/env-NeVmr4Gf.js.map +1 -0
  82. package/dist/forms/create-form.d.ts +49 -0
  83. package/dist/forms/create-form.d.ts.map +1 -0
  84. package/dist/forms/index.d.ts +39 -0
  85. package/dist/forms/index.d.ts.map +1 -0
  86. package/dist/forms/types.d.ts +139 -0
  87. package/dist/forms/types.d.ts.map +1 -0
  88. package/dist/forms/validators.d.ts +179 -0
  89. package/dist/forms/validators.d.ts.map +1 -0
  90. package/dist/forms-C3yovgH9.js +141 -0
  91. package/dist/forms-C3yovgH9.js.map +1 -0
  92. package/dist/forms.es.mjs +14 -0
  93. package/dist/full.d.ts +37 -9
  94. package/dist/full.d.ts.map +1 -1
  95. package/dist/full.es.mjs +186 -91
  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/i18n/formatting.d.ts +40 -0
  101. package/dist/i18n/formatting.d.ts.map +1 -0
  102. package/dist/i18n/i18n.d.ts +48 -0
  103. package/dist/i18n/i18n.d.ts.map +1 -0
  104. package/dist/i18n/index.d.ts +57 -0
  105. package/dist/i18n/index.d.ts.map +1 -0
  106. package/dist/i18n/translate.d.ts +83 -0
  107. package/dist/i18n/translate.d.ts.map +1 -0
  108. package/dist/i18n/types.d.ts +156 -0
  109. package/dist/i18n/types.d.ts.map +1 -0
  110. package/dist/i18n-BnnhTFOS.js +89 -0
  111. package/dist/i18n-BnnhTFOS.js.map +1 -0
  112. package/dist/i18n.es.mjs +6 -0
  113. package/dist/index.d.ts +11 -0
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.es.mjs +233 -138
  116. package/dist/media/battery.d.ts +35 -0
  117. package/dist/media/battery.d.ts.map +1 -0
  118. package/dist/media/breakpoints.d.ts +51 -0
  119. package/dist/media/breakpoints.d.ts.map +1 -0
  120. package/dist/media/clipboard.d.ts +30 -0
  121. package/dist/media/clipboard.d.ts.map +1 -0
  122. package/dist/media/device-sensors.d.ts +54 -0
  123. package/dist/media/device-sensors.d.ts.map +1 -0
  124. package/dist/media/geolocation.d.ts +38 -0
  125. package/dist/media/geolocation.d.ts.map +1 -0
  126. package/dist/media/index.d.ts +42 -0
  127. package/dist/media/index.d.ts.map +1 -0
  128. package/dist/media/media-query.d.ts +36 -0
  129. package/dist/media/media-query.d.ts.map +1 -0
  130. package/dist/media/network.d.ts +35 -0
  131. package/dist/media/network.d.ts.map +1 -0
  132. package/dist/media/types.d.ts +173 -0
  133. package/dist/media/types.d.ts.map +1 -0
  134. package/dist/media/viewport.d.ts +32 -0
  135. package/dist/media/viewport.d.ts.map +1 -0
  136. package/dist/media-Di2Ta22s.js +340 -0
  137. package/dist/media-Di2Ta22s.js.map +1 -0
  138. package/dist/media.es.mjs +12 -0
  139. package/dist/motion/index.d.ts +7 -3
  140. package/dist/motion/index.d.ts.map +1 -1
  141. package/dist/motion/morph.d.ts +27 -0
  142. package/dist/motion/morph.d.ts.map +1 -0
  143. package/dist/motion/parallax.d.ts +30 -0
  144. package/dist/motion/parallax.d.ts.map +1 -0
  145. package/dist/motion/reduced-motion.d.ts +36 -3
  146. package/dist/motion/reduced-motion.d.ts.map +1 -1
  147. package/dist/motion/types.d.ts +58 -0
  148. package/dist/motion/types.d.ts.map +1 -1
  149. package/dist/motion/typewriter.d.ts +31 -0
  150. package/dist/motion/typewriter.d.ts.map +1 -0
  151. package/dist/motion-qPj_TYGv.js +530 -0
  152. package/dist/motion-qPj_TYGv.js.map +1 -0
  153. package/dist/motion.es.mjs +27 -23
  154. package/dist/mount-SM07RUa6.js +403 -0
  155. package/dist/mount-SM07RUa6.js.map +1 -0
  156. package/dist/{object-qGpWr6-J.js → object-BCk-1c8T.js} +5 -4
  157. package/dist/{object-qGpWr6-J.js.map → object-BCk-1c8T.js.map} +1 -1
  158. package/dist/{platform-B7JhGBc7.js → platform-CPbCprb6.js} +3 -3
  159. package/dist/platform-CPbCprb6.js.map +1 -0
  160. package/dist/platform.es.mjs +2 -2
  161. package/dist/plugin/index.d.ts +22 -0
  162. package/dist/plugin/index.d.ts.map +1 -0
  163. package/dist/plugin/registry.d.ts +108 -0
  164. package/dist/plugin/registry.d.ts.map +1 -0
  165. package/dist/plugin/types.d.ts +110 -0
  166. package/dist/plugin/types.d.ts.map +1 -0
  167. package/dist/plugin-cPoOHFLY.js +64 -0
  168. package/dist/plugin-cPoOHFLY.js.map +1 -0
  169. package/dist/plugin.es.mjs +9 -0
  170. package/dist/reactive/computed.d.ts +7 -0
  171. package/dist/reactive/computed.d.ts.map +1 -1
  172. package/dist/reactive-Cfv0RK6x.js +233 -0
  173. package/dist/reactive-Cfv0RK6x.js.map +1 -0
  174. package/dist/reactive.es.mjs +18 -17
  175. package/dist/registry-CWf368tT.js +26 -0
  176. package/dist/registry-CWf368tT.js.map +1 -0
  177. package/dist/router/bq-link.d.ts +112 -0
  178. package/dist/router/bq-link.d.ts.map +1 -0
  179. package/dist/router/constraints.d.ts +9 -0
  180. package/dist/router/constraints.d.ts.map +1 -0
  181. package/dist/router/index.d.ts +14 -6
  182. package/dist/router/index.d.ts.map +1 -1
  183. package/dist/router/match.d.ts +0 -1
  184. package/dist/router/match.d.ts.map +1 -1
  185. package/dist/router/path-pattern.d.ts +14 -0
  186. package/dist/router/path-pattern.d.ts.map +1 -0
  187. package/dist/router/query.d.ts.map +1 -1
  188. package/dist/router/router.d.ts +3 -1
  189. package/dist/router/router.d.ts.map +1 -1
  190. package/dist/router/types.d.ts +48 -4
  191. package/dist/router/types.d.ts.map +1 -1
  192. package/dist/router/use-route.d.ts +50 -0
  193. package/dist/router/use-route.d.ts.map +1 -0
  194. package/dist/router/utils.d.ts +3 -0
  195. package/dist/router/utils.d.ts.map +1 -1
  196. package/dist/router-BrthaP_z.js +473 -0
  197. package/dist/router-BrthaP_z.js.map +1 -0
  198. package/dist/router.es.mjs +13 -10
  199. package/dist/{sanitize-jyJ2ryE2.js → sanitize-B1V4JswB.js} +95 -83
  200. package/dist/sanitize-B1V4JswB.js.map +1 -0
  201. package/dist/security/index.d.ts +2 -0
  202. package/dist/security/index.d.ts.map +1 -1
  203. package/dist/security/sanitize.d.ts +4 -1
  204. package/dist/security/sanitize.d.ts.map +1 -1
  205. package/dist/security/trusted-html.d.ts +53 -0
  206. package/dist/security/trusted-html.d.ts.map +1 -0
  207. package/dist/security.es.mjs +10 -9
  208. package/dist/ssr/hydrate.d.ts +65 -0
  209. package/dist/ssr/hydrate.d.ts.map +1 -0
  210. package/dist/ssr/index.d.ts +59 -0
  211. package/dist/ssr/index.d.ts.map +1 -0
  212. package/dist/ssr/render.d.ts +62 -0
  213. package/dist/ssr/render.d.ts.map +1 -0
  214. package/dist/ssr/serialize.d.ts +118 -0
  215. package/dist/ssr/serialize.d.ts.map +1 -0
  216. package/dist/ssr/types.d.ts +70 -0
  217. package/dist/ssr/types.d.ts.map +1 -0
  218. package/dist/ssr-B2qd_WBB.js +248 -0
  219. package/dist/ssr-B2qd_WBB.js.map +1 -0
  220. package/dist/ssr.es.mjs +9 -0
  221. package/dist/store/create-store.d.ts.map +1 -1
  222. package/dist/store/define-store.d.ts +1 -1
  223. package/dist/store/define-store.d.ts.map +1 -1
  224. package/dist/store/index.d.ts +1 -1
  225. package/dist/store/index.d.ts.map +1 -1
  226. package/dist/store/mapping.d.ts +1 -1
  227. package/dist/store/mapping.d.ts.map +1 -1
  228. package/dist/store/persisted.d.ts +38 -4
  229. package/dist/store/persisted.d.ts.map +1 -1
  230. package/dist/store/types.d.ts +140 -3
  231. package/dist/store/types.d.ts.map +1 -1
  232. package/dist/store/utils.d.ts +2 -2
  233. package/dist/store/utils.d.ts.map +1 -1
  234. package/dist/store/watch.d.ts +1 -1
  235. package/dist/store/watch.d.ts.map +1 -1
  236. package/dist/store-DWpyH6p5.js +338 -0
  237. package/dist/store-DWpyH6p5.js.map +1 -0
  238. package/dist/store.es.mjs +11 -10
  239. package/dist/storybook/index.d.ts +37 -0
  240. package/dist/storybook/index.d.ts.map +1 -0
  241. package/dist/storybook.es.mjs +151 -0
  242. package/dist/storybook.es.mjs.map +1 -0
  243. package/dist/testing/index.d.ts +23 -0
  244. package/dist/testing/index.d.ts.map +1 -0
  245. package/dist/testing/testing.d.ts +156 -0
  246. package/dist/testing/testing.d.ts.map +1 -0
  247. package/dist/testing/types.d.ts +134 -0
  248. package/dist/testing/types.d.ts.map +1 -0
  249. package/dist/testing-CsqjNUyy.js +224 -0
  250. package/dist/testing-CsqjNUyy.js.map +1 -0
  251. package/dist/testing.es.mjs +9 -0
  252. package/dist/type-guards-Do9DWgNp.js +44 -0
  253. package/dist/type-guards-Do9DWgNp.js.map +1 -0
  254. package/dist/untrack-DJVQQ2WM.js +33 -0
  255. package/dist/untrack-DJVQQ2WM.js.map +1 -0
  256. package/dist/view/custom-directives.d.ts +20 -0
  257. package/dist/view/custom-directives.d.ts.map +1 -0
  258. package/dist/view/evaluate.d.ts.map +1 -1
  259. package/dist/view/process.d.ts.map +1 -1
  260. package/dist/view.es.mjs +11 -10
  261. package/package.json +52 -11
  262. package/src/a11y/announce.ts +131 -0
  263. package/src/a11y/audit.ts +314 -0
  264. package/src/a11y/index.ts +68 -0
  265. package/src/a11y/media-preferences.ts +255 -0
  266. package/src/a11y/roving-tab-index.ts +164 -0
  267. package/src/a11y/skip-link.ts +255 -0
  268. package/src/a11y/trap-focus.ts +184 -0
  269. package/src/a11y/types.ts +183 -0
  270. package/src/component/component.ts +345 -65
  271. package/src/component/html.ts +153 -53
  272. package/src/component/index.ts +12 -2
  273. package/src/component/library.ts +66 -28
  274. package/src/component/scope.ts +212 -0
  275. package/src/component/types.ts +238 -19
  276. package/src/core/collection.ts +707 -628
  277. package/src/core/element.ts +981 -774
  278. package/src/core/env.ts +60 -0
  279. package/src/core/index.ts +49 -48
  280. package/src/core/shared.ts +62 -13
  281. package/src/core/utils/index.ts +148 -83
  282. package/src/devtools/devtools.ts +410 -0
  283. package/src/devtools/index.ts +48 -0
  284. package/src/devtools/types.ts +104 -0
  285. package/src/dnd/draggable.ts +296 -0
  286. package/src/dnd/droppable.ts +228 -0
  287. package/src/dnd/index.ts +62 -0
  288. package/src/dnd/sortable.ts +307 -0
  289. package/src/dnd/types.ts +293 -0
  290. package/src/forms/create-form.ts +278 -0
  291. package/src/forms/index.ts +65 -0
  292. package/src/forms/types.ts +154 -0
  293. package/src/forms/validators.ts +265 -0
  294. package/src/full.ts +260 -3
  295. package/src/i18n/formatting.ts +67 -0
  296. package/src/i18n/i18n.ts +200 -0
  297. package/src/i18n/index.ts +67 -0
  298. package/src/i18n/translate.ts +182 -0
  299. package/src/i18n/types.ts +171 -0
  300. package/src/index.ts +108 -36
  301. package/src/media/battery.ts +116 -0
  302. package/src/media/breakpoints.ts +131 -0
  303. package/src/media/clipboard.ts +80 -0
  304. package/src/media/device-sensors.ts +158 -0
  305. package/src/media/geolocation.ts +119 -0
  306. package/src/media/index.ts +76 -0
  307. package/src/media/media-query.ts +92 -0
  308. package/src/media/network.ts +115 -0
  309. package/src/media/types.ts +177 -0
  310. package/src/media/viewport.ts +84 -0
  311. package/src/motion/index.ts +57 -48
  312. package/src/motion/morph.ts +151 -0
  313. package/src/motion/parallax.ts +120 -0
  314. package/src/motion/reduced-motion.ts +66 -17
  315. package/src/motion/transition.ts +97 -97
  316. package/src/motion/types.ts +63 -0
  317. package/src/motion/typewriter.ts +164 -0
  318. package/src/platform/announcer.ts +208 -208
  319. package/src/platform/config.ts +163 -163
  320. package/src/platform/cookies.ts +165 -165
  321. package/src/platform/index.ts +39 -39
  322. package/src/platform/meta.ts +168 -168
  323. package/src/plugin/index.ts +37 -0
  324. package/src/plugin/registry.ts +269 -0
  325. package/src/plugin/types.ts +137 -0
  326. package/src/reactive/async-data.ts +486 -486
  327. package/src/reactive/computed.ts +130 -92
  328. package/src/reactive/index.ts +37 -37
  329. package/src/reactive/signal.ts +29 -29
  330. package/src/router/bq-link.ts +279 -0
  331. package/src/router/constraints.ts +201 -0
  332. package/src/router/index.ts +49 -41
  333. package/src/router/match.ts +312 -106
  334. package/src/router/path-pattern.ts +52 -0
  335. package/src/router/query.ts +38 -35
  336. package/src/router/router.ts +402 -211
  337. package/src/router/types.ts +139 -93
  338. package/src/router/use-route.ts +68 -0
  339. package/src/router/utils.ts +157 -116
  340. package/src/security/constants.ts +211 -211
  341. package/src/security/index.ts +12 -10
  342. package/src/security/sanitize.ts +6 -2
  343. package/src/security/trusted-html.ts +71 -0
  344. package/src/ssr/hydrate.ts +82 -0
  345. package/src/ssr/index.ts +70 -0
  346. package/src/ssr/render.ts +508 -0
  347. package/src/ssr/serialize.ts +296 -0
  348. package/src/ssr/types.ts +81 -0
  349. package/src/store/create-store.ts +467 -329
  350. package/src/store/define-store.ts +2 -1
  351. package/src/store/index.ts +27 -22
  352. package/src/store/mapping.ts +2 -1
  353. package/src/store/persisted.ts +249 -61
  354. package/src/store/types.ts +247 -94
  355. package/src/store/utils.ts +135 -141
  356. package/src/store/watch.ts +2 -1
  357. package/src/storybook/index.ts +480 -0
  358. package/src/testing/index.ts +42 -0
  359. package/src/testing/testing.ts +593 -0
  360. package/src/testing/types.ts +170 -0
  361. package/src/view/custom-directives.ts +30 -0
  362. package/src/view/evaluate.ts +292 -290
  363. package/src/view/process.ts +108 -92
  364. package/dist/component-CY5MVoYN.js +0 -531
  365. package/dist/component-CY5MVoYN.js.map +0 -1
  366. package/dist/config-DRmZZno3.js.map +0 -1
  367. package/dist/core-CK2Mfpf4.js +0 -648
  368. package/dist/core-CK2Mfpf4.js.map +0 -1
  369. package/dist/motion-C5DRdPnO.js +0 -415
  370. package/dist/motion-C5DRdPnO.js.map +0 -1
  371. package/dist/platform-B7JhGBc7.js.map +0 -1
  372. package/dist/reactive-BDya-ia8.js +0 -253
  373. package/dist/reactive-BDya-ia8.js.map +0 -1
  374. package/dist/router-CijiICxt.js +0 -188
  375. package/dist/router-CijiICxt.js.map +0 -1
  376. package/dist/sanitize-jyJ2ryE2.js.map +0 -1
  377. package/dist/store-CPK9E62U.js +0 -262
  378. package/dist/store-CPK9E62U.js.map +0 -1
  379. package/dist/view-Cdi0g-qo.js +0 -396
  380. package/dist/view-Cdi0g-qo.js.map +0 -1
@@ -4,9 +4,58 @@
4
4
  * @module bquery/component
5
5
  */
6
6
 
7
+ import type { CleanupFn } from '../reactive/signal';
8
+ import { effect, untrack } from '../reactive/signal';
7
9
  import { sanitizeHtml } from '../security/sanitize';
8
10
  import { coercePropValue } from './props';
9
- import type { ComponentDefinition, PropDefinition } from './types';
11
+ import { createComponentScope, setCurrentScope, type ComponentScope } from './scope';
12
+ import type {
13
+ AttributeChange,
14
+ ComponentClass,
15
+ ComponentDefinition,
16
+ ComponentSignalLike,
17
+ ComponentSignals,
18
+ ComponentStateShape,
19
+ PropDefinition,
20
+ ShadowMode,
21
+ } from './types';
22
+
23
+ /**
24
+ * Base extra tags preserved for component shadow DOM renders in addition to the
25
+ * global sanitizer defaults. `slot` must remain allowed here because shadow DOM
26
+ * content projection depends on authored `<slot>` elements in component render
27
+ * output.
28
+ */
29
+ const COMPONENT_ALLOWED_TAGS = ['slot'];
30
+
31
+ /**
32
+ * Base extra attributes preserved for component shadow DOM renders in addition
33
+ * to the global sanitizer defaults.
34
+ */
35
+ const COMPONENT_ALLOWED_ATTRIBUTES = [
36
+ 'part',
37
+ // Standard form attributes required by interactive shadow DOM content
38
+ 'disabled',
39
+ 'checked',
40
+ 'placeholder',
41
+ 'value',
42
+ 'rows',
43
+ 'cols',
44
+ 'readonly',
45
+ 'required',
46
+ 'maxlength',
47
+ 'minlength',
48
+ 'max',
49
+ 'min',
50
+ 'step',
51
+ 'pattern',
52
+ 'autocomplete',
53
+ 'autofocus',
54
+ 'for',
55
+ 'multiple',
56
+ 'selected',
57
+ 'wrap',
58
+ ];
10
59
 
11
60
  /**
12
61
  * Creates a custom element class for a component definition.
@@ -18,23 +67,66 @@ import type { ComponentDefinition, PropDefinition } from './types';
18
67
  * @param tagName - The custom element tag name (used for diagnostics)
19
68
  * @param definition - The component configuration
20
69
  */
21
- export const defineComponent = <TProps extends Record<string, unknown>>(
70
+ const createComponentClass = <
71
+ TProps extends Record<string, unknown>,
72
+ TState extends Record<string, unknown> | undefined = undefined,
73
+ TSignals extends ComponentSignals = Record<string, never>,
74
+ >(
22
75
  tagName: string,
23
- definition: ComponentDefinition<TProps>
24
- ): typeof HTMLElement => {
76
+ definition: ComponentDefinition<TProps, TState, TSignals>
77
+ ): ComponentClass<TState> => {
78
+ const componentAllowedTags = [
79
+ ...COMPONENT_ALLOWED_TAGS,
80
+ ...(definition.sanitize?.allowTags ?? []),
81
+ ];
82
+ const componentAllowedAttributes = [
83
+ ...COMPONENT_ALLOWED_ATTRIBUTES,
84
+ ...(definition.sanitize?.allowAttributes ?? []),
85
+ ];
86
+ const signalSources = Object.values(definition.signals ?? {}) as ComponentSignalLike<unknown>[];
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
+
25
105
  class BQueryComponent extends HTMLElement {
26
106
  /** Internal state object for the component */
27
- private readonly state = { ...(definition.state ?? {}) };
107
+ private readonly state: ComponentStateShape<TState> = {
108
+ ...(definition.state ?? {}),
109
+ } as ComponentStateShape<TState>;
28
110
  /** Typed props object populated from attributes */
29
111
  private props = {} as TProps;
30
112
  /** Tracks missing required props for validation during connectedCallback */
31
113
  private missingRequiredProps = new Set<string>();
32
114
  /** Tracks whether the component has completed its initial mount */
33
115
  private hasMounted = false;
116
+ /** Cleanup for external signal subscriptions */
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;
34
122
 
35
123
  constructor() {
36
124
  super();
37
- this.attachShadow({ mode: 'open' });
125
+ if (shadowMode !== false) {
126
+ this.renderRootNode = this.attachShadow({ mode: shadowMode });
127
+ } else {
128
+ this.renderRootNode = this;
129
+ }
38
130
  this.syncProps();
39
131
  }
40
132
 
@@ -42,7 +134,7 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
42
134
  * Returns the list of attributes to observe for changes.
43
135
  */
44
136
  static get observedAttributes(): string[] {
45
- return Object.keys(definition.props ?? {});
137
+ return observedAttrs;
46
138
  }
47
139
 
48
140
  /**
@@ -50,13 +142,28 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
50
142
  */
51
143
  connectedCallback(): void {
52
144
  try {
53
- // Defer initial render until all required props are present
54
- // This allows attributes to be set after element creation
55
- if (this.missingRequiredProps.size > 0) {
145
+ // Defer only the initial mount until all required props are present.
146
+ // Already-mounted components must still reconnect their signal
147
+ // subscriptions so reactive updates can resume after reattachment.
148
+ if (!this.hasMounted && this.missingRequiredProps.size > 0) {
56
149
  // Component will mount once all required props are satisfied
57
150
  // via attributeChangedCallback
58
151
  return;
59
152
  }
153
+ if (this.hasMounted) {
154
+ // Recreate scope for reconnected component
155
+ this.scope = createComponentScope();
156
+ const previousScope = setCurrentScope(this.scope);
157
+ try {
158
+ definition.connected?.call(this);
159
+ } catch (error) {
160
+ this.handleError(error as Error);
161
+ } finally {
162
+ setCurrentScope(previousScope);
163
+ }
164
+ this.setupSignalSubscriptions(true);
165
+ return;
166
+ }
60
167
  this.mount();
61
168
  } catch (error) {
62
169
  this.handleError(error as Error);
@@ -70,9 +177,24 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
70
177
  */
71
178
  private mount(): void {
72
179
  if (this.hasMounted) return;
73
- definition.beforeMount?.call(this);
74
- 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
+ }
75
196
  this.render();
197
+ this.setupSignalSubscriptions();
76
198
  this.hasMounted = true;
77
199
  }
78
200
 
@@ -81,6 +203,11 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
81
203
  */
82
204
  disconnectedCallback(): void {
83
205
  try {
206
+ this.signalEffectCleanup?.();
207
+ this.signalEffectCleanup = undefined;
208
+ // Dispose all scoped reactive resources (useSignal, useComputed, useEffect)
209
+ this.scope?.dispose();
210
+ this.scope = undefined;
84
211
  definition.disconnected?.call(this);
85
212
  } catch (error) {
86
213
  this.handleError(error as Error);
@@ -90,17 +217,24 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
90
217
  /**
91
218
  * Called when an observed attribute changes.
92
219
  */
93
- attributeChangedCallback(
94
- _name: string,
95
- _oldValue: string | null,
96
- _newValue: string | null
97
- ): void {
220
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
98
221
  try {
222
+ const previousProps = this.cloneProps();
99
223
  this.syncProps();
100
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
+
101
235
  if (this.hasMounted) {
102
236
  // Component already mounted - trigger update render
103
- this.render(true);
237
+ this.render(true, previousProps, { name, oldValue, newValue });
104
238
  } else if (this.isConnected && this.missingRequiredProps.size === 0) {
105
239
  // All required props are now satisfied and element is connected
106
240
  // Trigger the deferred initial mount
@@ -111,6 +245,24 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
111
245
  }
112
246
  }
113
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
+
114
266
  /**
115
267
  * Handles errors during component lifecycle.
116
268
  * @internal
@@ -123,15 +275,26 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
123
275
  }
124
276
  }
125
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
+
126
286
  /**
127
287
  * Updates a state property and triggers a re-render.
128
288
  *
129
289
  * @param key - The state property key
130
290
  * @param value - The new value
131
291
  */
132
- setState(key: string, value: unknown): void {
292
+ setState<TKey extends keyof ComponentStateShape<TState>>(
293
+ key: TKey,
294
+ value: ComponentStateShape<TState>[TKey]
295
+ ): void {
133
296
  this.state[key] = value;
134
- this.render(true);
297
+ this.render(true, this.cloneProps(), undefined, false);
135
298
  }
136
299
 
137
300
  /**
@@ -140,8 +303,58 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
140
303
  * @param key - The state property key
141
304
  * @returns The current value
142
305
  */
143
- getState<T = unknown>(key: string): T {
144
- return this.state[key] as T;
306
+ getState<TKey extends keyof ComponentStateShape<TState>>(
307
+ key: TKey
308
+ ): ComponentStateShape<TState>[TKey];
309
+ getState<TResult = unknown>(key: string): TResult;
310
+ getState(key: string): unknown {
311
+ return (this.state as Record<string, unknown>)[key];
312
+ }
313
+
314
+ /**
315
+ * Subscribes to declared reactive sources and re-renders on change.
316
+ *
317
+ * @param renderOnInitialRun - When true, immediately re-renders after
318
+ * re-subscribing so detached components resync with any signal changes
319
+ * that happened while they were disconnected.
320
+ * @internal
321
+ */
322
+ private setupSignalSubscriptions(renderOnInitialRun = false): void {
323
+ if (this.signalEffectCleanup || signalSources.length === 0) return;
324
+
325
+ let isInitialRun = true;
326
+ this.signalEffectCleanup = effect(() => {
327
+ try {
328
+ for (const source of signalSources) {
329
+ // Intentionally read each source to register this effect as a subscriber.
330
+ void source.value;
331
+ }
332
+
333
+ if (isInitialRun) {
334
+ isInitialRun = false;
335
+ if (renderOnInitialRun && this.hasMounted && this.isConnected) {
336
+ // Signal-driven reconnect renders do not change props, so the
337
+ // previous-props snapshot is the current prop set at reconnect time.
338
+ const previousProps = this.cloneProps();
339
+ untrack(() => {
340
+ this.render(true, previousProps, undefined, false);
341
+ });
342
+ }
343
+ return;
344
+ }
345
+
346
+ if (!this.hasMounted || !this.isConnected) return;
347
+
348
+ // Signal updates leave props unchanged, so cloning the current props
349
+ // provides the previous-props snapshot expected by beforeUpdate().
350
+ const previousProps = this.cloneProps();
351
+ untrack(() => {
352
+ this.render(true, previousProps, undefined, false);
353
+ });
354
+ } catch (error) {
355
+ this.handleError(error as Error);
356
+ }
357
+ });
145
358
  }
146
359
 
147
360
  /**
@@ -184,13 +397,40 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
184
397
  }
185
398
 
186
399
  /**
187
- * Renders the component to its shadow root.
400
+ * Creates a shallow snapshot of the current props for lifecycle diffing.
401
+ * A shallow copy is sufficient because component props are re-derived from
402
+ * reflected attributes on each update, so nested object mutation is not
403
+ * tracked as part of this lifecycle diff.
404
+ * @internal
405
+ */
406
+ private cloneProps(): TProps {
407
+ return { ...(this.props as Record<string, unknown>) } as TProps;
408
+ }
409
+
410
+ /**
411
+ * Renders the component to its shadow root or host element.
188
412
  * @internal
189
413
  */
190
- private render(triggerUpdated = false): void {
414
+ private render(): void;
415
+ private render(triggerUpdated: true, oldProps: TProps, change?: AttributeChange): void;
416
+ private render(
417
+ triggerUpdated: true,
418
+ oldProps: TProps,
419
+ change: AttributeChange | undefined,
420
+ runBeforeUpdate: boolean
421
+ ): void;
422
+ private render(
423
+ triggerUpdated = false,
424
+ oldProps?: TProps,
425
+ change?: AttributeChange,
426
+ runBeforeUpdate = true
427
+ ): void {
191
428
  try {
192
- if (triggerUpdated && definition.beforeUpdate) {
193
- const shouldUpdate = definition.beforeUpdate.call(this, this.props);
429
+ if (triggerUpdated && runBeforeUpdate && definition.beforeUpdate) {
430
+ if (!oldProps) {
431
+ throw new Error('bQuery component: previous props are required for update renders');
432
+ }
433
+ const shouldUpdate = definition.beforeUpdate.call(this, this.props, oldProps);
194
434
  if (shouldUpdate === false) return;
195
435
  }
196
436
 
@@ -198,11 +438,12 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
198
438
  this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, composed: true }));
199
439
  };
200
440
 
201
- if (!this.shadowRoot) return;
441
+ const renderRoot = this.renderRootNode;
202
442
 
203
443
  const markup = definition.render({
204
444
  props: this.props,
205
445
  state: this.state,
446
+ signals: (definition.signals ?? {}) as TSignals,
206
447
  emit,
207
448
  });
208
449
 
@@ -211,42 +452,29 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
211
452
  // the stylistic `part` attribute, and standard form/input attributes without
212
453
  // relaxing the global DOM sanitization rules.
213
454
  const sanitizedMarkup = sanitizeHtml(markup, {
214
- allowTags: ['slot'],
215
- allowAttributes: [
216
- 'part',
217
- // Standard form attributes required by interactive shadow DOM content
218
- 'disabled',
219
- 'checked',
220
- 'placeholder',
221
- 'value',
222
- 'rows',
223
- 'cols',
224
- 'readonly',
225
- 'required',
226
- 'maxlength',
227
- 'minlength',
228
- 'max',
229
- 'min',
230
- 'step',
231
- 'pattern',
232
- 'autocomplete',
233
- 'autofocus',
234
- 'for',
235
- 'multiple',
236
- 'selected',
237
- 'wrap',
238
- ],
455
+ allowTags: componentAllowedTags,
456
+ allowAttributes: componentAllowedAttributes,
239
457
  });
240
- this.shadowRoot.innerHTML = sanitizedMarkup;
458
+ let existingStyleElement: HTMLStyleElement | null = null;
459
+ if (definition.styles) {
460
+ existingStyleElement = renderRoot.querySelector<HTMLStyleElement>(
461
+ 'style[data-bquery-component-style]'
462
+ );
463
+ }
464
+
465
+ renderRoot.innerHTML = sanitizedMarkup;
241
466
 
242
467
  if (definition.styles) {
243
- const styleElement = document.createElement('style');
468
+ const styleElement = existingStyleElement ?? document.createElement('style');
469
+ if (!existingStyleElement) {
470
+ styleElement.setAttribute('data-bquery-component-style', '');
471
+ }
244
472
  styleElement.textContent = definition.styles;
245
- this.shadowRoot.prepend(styleElement);
473
+ renderRoot.prepend(styleElement);
246
474
  }
247
475
 
248
476
  if (triggerUpdated) {
249
- definition.updated?.call(this);
477
+ definition.updated?.call(this, change);
250
478
  }
251
479
  } catch (error) {
252
480
  this.handleError(error as Error);
@@ -254,9 +482,48 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
254
482
  }
255
483
  }
256
484
 
257
- return BQueryComponent;
485
+ return BQueryComponent as ComponentClass<TState>;
258
486
  };
259
487
 
488
+ /**
489
+ * Creates a custom element class for a component definition.
490
+ *
491
+ * This is useful when you want to extend or register the class manually
492
+ * (e.g. with different tag names in tests or custom registries).
493
+ *
494
+ * @template TProps - Type of the component's props
495
+ * @template TState - Type of the component's internal state. When provided,
496
+ * `definition.state` is required, `render({ state })` is strongly typed, and
497
+ * returned instances expose typed `getState()` / `setState()` helpers.
498
+ * @param tagName - The custom element tag name (used for diagnostics)
499
+ * @param definition - The component configuration
500
+ */
501
+ export function defineComponent<
502
+ TProps extends Record<string, unknown>,
503
+ TSignals extends ComponentSignals = Record<string, never>,
504
+ >(
505
+ tagName: string,
506
+ definition: ComponentDefinition<TProps, undefined, TSignals>
507
+ ): ComponentClass<undefined>;
508
+ export function defineComponent<
509
+ TProps extends Record<string, unknown>,
510
+ TState extends Record<string, unknown>,
511
+ TSignals extends ComponentSignals = Record<string, never>,
512
+ >(
513
+ tagName: string,
514
+ definition: ComponentDefinition<TProps, TState, TSignals>
515
+ ): ComponentClass<TState>;
516
+ export function defineComponent<
517
+ TProps extends Record<string, unknown>,
518
+ TState extends Record<string, unknown> | undefined = undefined,
519
+ TSignals extends ComponentSignals = Record<string, never>,
520
+ >(
521
+ tagName: string,
522
+ definition: ComponentDefinition<TProps, TState, TSignals>
523
+ ): ComponentClass<TState> {
524
+ return createComponentClass(tagName, definition);
525
+ }
526
+
260
527
  /**
261
528
  * Defines and registers a custom Web Component.
262
529
  *
@@ -265,12 +532,15 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
265
532
  * and automatically re-renders when observed attributes change.
266
533
  *
267
534
  * @template TProps - Type of the component's props
535
+ * @template TState - Type of the component's internal state. When provided,
536
+ * `definition.state` is required and lifecycle hooks receive typed state
537
+ * helpers via `this.getState()` / `this.setState()`.
268
538
  * @param tagName - The custom element tag name (must contain a hyphen)
269
539
  * @param definition - The component configuration
270
540
  *
271
541
  * @example
272
542
  * ```ts
273
- * component('counter-button', {
543
+ * component<{ start: number }, { count: number }>('counter-button', {
274
544
  * props: {
275
545
  * start: { type: Number, default: 0 },
276
546
  * },
@@ -283,7 +553,7 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
283
553
  * const handleClick = (event: Event) => {
284
554
  * const target = event.target as HTMLElement | null;
285
555
  * if (target?.matches('button')) {
286
- * this.setState('count', (this.getState('count') as number) + 1);
556
+ * this.setState('count', this.getState('count') + 1);
287
557
  * }
288
558
  * };
289
559
  * this.shadowRoot?.addEventListener('click', handleClick);
@@ -307,13 +577,23 @@ export const defineComponent = <TProps extends Record<string, unknown>>(
307
577
  * });
308
578
  * ```
309
579
  */
310
- export const component = <TProps extends Record<string, unknown>>(
311
- tagName: string,
312
- definition: ComponentDefinition<TProps>
313
- ): void => {
314
- const elementClass = defineComponent(tagName, definition);
580
+ export function component<
581
+ TProps extends Record<string, unknown>,
582
+ TSignals extends ComponentSignals = Record<string, never>,
583
+ >(tagName: string, definition: ComponentDefinition<TProps, undefined, TSignals>): void;
584
+ export function component<
585
+ TProps extends Record<string, unknown>,
586
+ TState extends Record<string, unknown>,
587
+ TSignals extends ComponentSignals = Record<string, never>,
588
+ >(tagName: string, definition: ComponentDefinition<TProps, TState, TSignals>): void;
589
+ export function component<
590
+ TProps extends Record<string, unknown>,
591
+ TState extends Record<string, unknown> | undefined = undefined,
592
+ TSignals extends ComponentSignals = Record<string, never>,
593
+ >(tagName: string, definition: ComponentDefinition<TProps, TState, TSignals>): void {
594
+ const elementClass = createComponentClass(tagName, definition);
315
595
 
316
596
  if (!customElements.get(tagName)) {
317
597
  customElements.define(tagName, elementClass);
318
598
  }
319
- };
599
+ }