@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,296 @@
1
+ /**
2
+ * Make an element draggable using pointer events.
3
+ *
4
+ * Uses Pointer Events (not HTML5 Drag & Drop) for reliable
5
+ * cross-platform behavior including touch support.
6
+ *
7
+ * @module bquery/dnd
8
+ */
9
+
10
+ import type {
11
+ BoundsRect,
12
+ DragBounds,
13
+ DragEventData,
14
+ DragPosition,
15
+ DraggableHandle,
16
+ DraggableOptions,
17
+ } from './types';
18
+
19
+ /** Global registry of active draggable elements for drop zone detection. */
20
+ const activeDrags = new Map<HTMLElement, { element: HTMLElement; position: DragPosition }>();
21
+
22
+ /**
23
+ * Returns the currently active drag state, if any.
24
+ * Used internally by `droppable()` to detect drag interactions.
25
+ * @internal
26
+ */
27
+ export const getActiveDrag = (): { element: HTMLElement; position: DragPosition } | undefined => {
28
+ const entries = Array.from(activeDrags.values());
29
+ return entries[entries.length - 1];
30
+ };
31
+
32
+ /**
33
+ * Resolves a `DragBounds` value to an absolute `BoundsRect`.
34
+ * @internal
35
+ */
36
+ const resolveBounds = (el: HTMLElement, bounds: DragBounds): BoundsRect | null => {
37
+ if (typeof bounds === 'object') {
38
+ return bounds;
39
+ }
40
+
41
+ let target: HTMLElement | null = null;
42
+
43
+ if (bounds === 'parent') {
44
+ target = el.parentElement;
45
+ } else {
46
+ target = document.querySelector(bounds) as HTMLElement | null;
47
+ }
48
+
49
+ if (!target) return null;
50
+
51
+ const rect = target.getBoundingClientRect();
52
+ const elRect = el.getBoundingClientRect();
53
+ const rawLeft = parseFloat(el.style.left || '0');
54
+ const rawTop = parseFloat(el.style.top || '0');
55
+ const leftOffset = Number.isNaN(rawLeft) ? 0 : rawLeft;
56
+ const topOffset = Number.isNaN(rawTop) ? 0 : rawTop;
57
+
58
+ return {
59
+ left: rect.left - elRect.left + leftOffset,
60
+ top: rect.top - elRect.top + topOffset,
61
+ right: rect.right - elRect.right + leftOffset + (rect.width - elRect.width),
62
+ bottom: rect.bottom - elRect.bottom + topOffset + (rect.height - elRect.height),
63
+ };
64
+ };
65
+
66
+ /**
67
+ * Clamp a position within bounds.
68
+ * @internal
69
+ */
70
+ const clampPosition = (pos: DragPosition, bounds: BoundsRect | null): DragPosition => {
71
+ if (!bounds) return pos;
72
+ return {
73
+ x: Math.max(bounds.left, Math.min(bounds.right, pos.x)),
74
+ y: Math.max(bounds.top, Math.min(bounds.bottom, pos.y)),
75
+ };
76
+ };
77
+
78
+ /**
79
+ * Makes an element draggable using pointer events.
80
+ *
81
+ * Features:
82
+ * - Touch and mouse support via Pointer Events
83
+ * - Axis locking (`x`, `y`, or `both`)
84
+ * - Bounds constraint (parent, selector, or explicit rect)
85
+ * - Optional drag handle
86
+ * - Ghost/clone preview during drag
87
+ * - Callbacks: `onDragStart`, `onDrag`, `onDragEnd`
88
+ *
89
+ * @param el - The element to make draggable
90
+ * @param options - Configuration options
91
+ * @returns A handle with `destroy()`, `disable()`, and `enable()` methods
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { draggable } from '@bquery/bquery/dnd';
96
+ *
97
+ * const handle = draggable(document.querySelector('#box'), {
98
+ * axis: 'both',
99
+ * bounds: 'parent',
100
+ * onDragEnd: ({ position }) => {
101
+ * console.log('Dropped at', position.x, position.y);
102
+ * },
103
+ * });
104
+ *
105
+ * // Later:
106
+ * handle.destroy();
107
+ * ```
108
+ */
109
+ export const draggable = (el: HTMLElement, options: DraggableOptions = {}): DraggableHandle => {
110
+ const {
111
+ axis = 'both',
112
+ bounds,
113
+ handle,
114
+ ghost = false,
115
+ ghostClass = 'bq-drag-ghost',
116
+ draggingClass = 'bq-dragging',
117
+ onDragStart,
118
+ onDrag,
119
+ onDragEnd,
120
+ } = options;
121
+
122
+ let enabled = !options.disabled;
123
+ let isDragging = false;
124
+ let startPointer: DragPosition = { x: 0, y: 0 };
125
+ let currentPosition: DragPosition = { x: 0, y: 0 };
126
+ let previousPosition: DragPosition = { x: 0, y: 0 };
127
+ let ghostEl: HTMLElement | null = null;
128
+ let ghostStartPosition: DragPosition | null = null;
129
+ const previousTouchAction = el.style.touchAction;
130
+ const previousUserSelect = el.style.userSelect;
131
+
132
+ const createEventData = (event: PointerEvent): DragEventData => ({
133
+ element: el,
134
+ position: { ...currentPosition },
135
+ delta: {
136
+ x: currentPosition.x - previousPosition.x,
137
+ y: currentPosition.y - previousPosition.y,
138
+ },
139
+ event,
140
+ });
141
+
142
+ const createGhost = (): HTMLElement => {
143
+ const clone = el.cloneNode(true) as HTMLElement;
144
+ const rect = el.getBoundingClientRect();
145
+ clone.classList.add(ghostClass);
146
+ clone.style.position = 'fixed';
147
+ clone.style.left = `${rect.left}px`;
148
+ clone.style.top = `${rect.top}px`;
149
+ clone.style.width = `${rect.width}px`;
150
+ clone.style.height = `${rect.height}px`;
151
+ clone.style.pointerEvents = 'none';
152
+ clone.style.zIndex = '999999';
153
+ clone.style.opacity = '0.7';
154
+ clone.style.margin = '0';
155
+ document.body.appendChild(clone);
156
+ return clone;
157
+ };
158
+
159
+ const removeGhost = (): void => {
160
+ if (ghostEl) {
161
+ ghostEl.remove();
162
+ ghostEl = null;
163
+ }
164
+ ghostStartPosition = null;
165
+ };
166
+
167
+ const onPointerDown = (e: PointerEvent): void => {
168
+ if (!enabled) return;
169
+
170
+ // Check handle constraint
171
+ if (handle) {
172
+ const target = e.target as Element;
173
+ if (!target.closest(handle)) return;
174
+ }
175
+
176
+ e.preventDefault();
177
+ isDragging = true;
178
+ startPointer = { x: e.clientX, y: e.clientY };
179
+ previousPosition = { ...currentPosition };
180
+
181
+ el.classList.add(draggingClass);
182
+ el.setPointerCapture(e.pointerId);
183
+
184
+ if (ghost) {
185
+ const rect = el.getBoundingClientRect();
186
+ ghostStartPosition = { x: rect.left, y: rect.top };
187
+ ghostEl = createGhost();
188
+ }
189
+
190
+ // Register in global active drags
191
+ activeDrags.set(el, { element: el, position: currentPosition });
192
+
193
+ onDragStart?.(createEventData(e));
194
+ };
195
+
196
+ const onPointerMove = (e: PointerEvent): void => {
197
+ if (!isDragging) return;
198
+
199
+ e.preventDefault();
200
+ previousPosition = { ...currentPosition };
201
+
202
+ let newX = currentPosition.x + (e.clientX - startPointer.x);
203
+ let newY = currentPosition.y + (e.clientY - startPointer.y);
204
+
205
+ // Reset start pointer to current for delta calculation
206
+ startPointer = { x: e.clientX, y: e.clientY };
207
+
208
+ // Apply axis constraint
209
+ if (axis === 'x') newY = currentPosition.y;
210
+ if (axis === 'y') newX = currentPosition.x;
211
+
212
+ let newPos: DragPosition = { x: newX, y: newY };
213
+
214
+ // Apply bounds constraint
215
+ if (bounds) {
216
+ const resolvedBounds = resolveBounds(el, bounds);
217
+ newPos = clampPosition(newPos, resolvedBounds);
218
+ }
219
+
220
+ currentPosition = newPos;
221
+
222
+ // Update active drag position
223
+ activeDrags.set(el, { element: el, position: currentPosition });
224
+
225
+ // Apply the position
226
+ if (ghost && ghostEl) {
227
+ const start = ghostStartPosition ?? {
228
+ x: el.getBoundingClientRect().left,
229
+ y: el.getBoundingClientRect().top,
230
+ };
231
+ ghostEl.style.left = `${start.x + currentPosition.x}px`;
232
+ ghostEl.style.top = `${start.y + currentPosition.y}px`;
233
+ } else {
234
+ el.style.transform = `translate(${currentPosition.x}px, ${currentPosition.y}px)`;
235
+ }
236
+
237
+ onDrag?.(createEventData(e));
238
+ };
239
+
240
+ const onPointerUp = (e: PointerEvent): void => {
241
+ if (!isDragging) return;
242
+
243
+ isDragging = false;
244
+ el.classList.remove(draggingClass);
245
+ try {
246
+ if (
247
+ typeof el.releasePointerCapture === 'function' &&
248
+ (typeof el.hasPointerCapture !== 'function' || el.hasPointerCapture(e.pointerId))
249
+ ) {
250
+ el.releasePointerCapture(e.pointerId);
251
+ }
252
+ } catch {
253
+ // Pointer capture may already be released in some interrupted drag flows.
254
+ } finally {
255
+ removeGhost();
256
+
257
+ // Remove from active drags
258
+ activeDrags.delete(el);
259
+
260
+ onDragEnd?.(createEventData(e));
261
+ }
262
+ };
263
+
264
+ // Attach listeners
265
+ el.addEventListener('pointerdown', onPointerDown);
266
+ el.addEventListener('pointermove', onPointerMove);
267
+ el.addEventListener('pointerup', onPointerUp);
268
+ el.addEventListener('pointercancel', onPointerUp);
269
+
270
+ // Prevent default drag behavior
271
+ el.style.touchAction = 'none';
272
+ el.style.userSelect = 'none';
273
+
274
+ return {
275
+ destroy: () => {
276
+ el.removeEventListener('pointerdown', onPointerDown);
277
+ el.removeEventListener('pointermove', onPointerMove);
278
+ el.removeEventListener('pointerup', onPointerUp);
279
+ el.removeEventListener('pointercancel', onPointerUp);
280
+ removeGhost();
281
+ activeDrags.delete(el);
282
+ el.style.touchAction = previousTouchAction;
283
+ el.style.userSelect = previousUserSelect;
284
+ el.classList.remove(draggingClass);
285
+ },
286
+ disable: () => {
287
+ enabled = false;
288
+ },
289
+ enable: () => {
290
+ enabled = true;
291
+ },
292
+ get enabled() {
293
+ return enabled;
294
+ },
295
+ };
296
+ };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Define drop zones for draggable elements.
3
+ *
4
+ * Drop zones detect when draggable elements enter, move over,
5
+ * leave, or are dropped onto them using pointer event hit-testing.
6
+ *
7
+ * @module bquery/dnd
8
+ */
9
+
10
+ import { getActiveDrag } from './draggable';
11
+ import type { DropEventData, DroppableHandle, DroppableOptions } from './types';
12
+
13
+ type DroppableListener = {
14
+ handlePointerMove: (event: PointerEvent) => void;
15
+ handlePointerUp: (event: PointerEvent) => void;
16
+ };
17
+
18
+ const passivePointerMoveListenerOptions = { passive: true } as const;
19
+
20
+ const droppableListeners = new Set<DroppableListener>();
21
+ let queuedPointerMove: PointerEvent | null = null;
22
+ let pointerMoveFrame: number | null = null;
23
+
24
+ const getDroppableListenersSnapshot = (): DroppableListener[] => Array.from(droppableListeners);
25
+
26
+ const hasDroppableEnvironment = (): boolean => {
27
+ return (
28
+ typeof document !== 'undefined' &&
29
+ typeof document.addEventListener === 'function' &&
30
+ typeof document.removeEventListener === 'function' &&
31
+ typeof requestAnimationFrame === 'function' &&
32
+ typeof cancelAnimationFrame === 'function'
33
+ );
34
+ };
35
+
36
+ const dispatchPointerMove = (event: PointerEvent): void => {
37
+ for (const listener of getDroppableListenersSnapshot()) {
38
+ listener.handlePointerMove(event);
39
+ }
40
+ };
41
+
42
+ const flushPointerMove = (): void => {
43
+ pointerMoveFrame = null;
44
+ const event = queuedPointerMove;
45
+ queuedPointerMove = null;
46
+ if (!event) return;
47
+ dispatchPointerMove(event);
48
+ };
49
+
50
+ const handleDocumentPointerMove = (event: PointerEvent): void => {
51
+ queuedPointerMove = event;
52
+ if (pointerMoveFrame === null) {
53
+ pointerMoveFrame = requestAnimationFrame(flushPointerMove);
54
+ }
55
+ };
56
+
57
+ const handleDocumentPointerUp = (event: PointerEvent): void => {
58
+ if (pointerMoveFrame !== null) {
59
+ cancelAnimationFrame(pointerMoveFrame);
60
+ pointerMoveFrame = null;
61
+ queuedPointerMove = null;
62
+ }
63
+
64
+ for (const listener of getDroppableListenersSnapshot()) {
65
+ listener.handlePointerUp(event);
66
+ }
67
+ };
68
+
69
+ const registerDroppableListener = (listener: DroppableListener): void => {
70
+ if (droppableListeners.size === 0) {
71
+ document.addEventListener(
72
+ 'pointermove',
73
+ handleDocumentPointerMove,
74
+ passivePointerMoveListenerOptions
75
+ );
76
+ document.addEventListener('pointerup', handleDocumentPointerUp);
77
+ }
78
+
79
+ droppableListeners.add(listener);
80
+ };
81
+
82
+ const unregisterDroppableListener = (listener: DroppableListener): void => {
83
+ droppableListeners.delete(listener);
84
+
85
+ if (droppableListeners.size !== 0) return;
86
+
87
+ document.removeEventListener('pointermove', handleDocumentPointerMove);
88
+ document.removeEventListener('pointerup', handleDocumentPointerUp);
89
+ if (pointerMoveFrame !== null) {
90
+ cancelAnimationFrame(pointerMoveFrame);
91
+ pointerMoveFrame = null;
92
+ }
93
+ queuedPointerMove = null;
94
+ };
95
+
96
+ /**
97
+ * Checks whether a dragged element is accepted by the drop zone.
98
+ * @internal
99
+ */
100
+ const isAccepted = (dragged: HTMLElement, accept: DroppableOptions['accept']): boolean => {
101
+ if (!accept) return true;
102
+ if (typeof accept === 'string') return dragged.matches(accept);
103
+ return accept(dragged);
104
+ };
105
+
106
+ /**
107
+ * Defines an element as a drop zone.
108
+ *
109
+ * Drop zones respond to draggable elements being moved over them
110
+ * by firing callbacks and applying CSS classes. They work with
111
+ * the `draggable()` function from this module.
112
+ *
113
+ * @param el - The drop zone element
114
+ * @param options - Configuration options
115
+ * @returns A handle with a `destroy()` method
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * import { droppable } from '@bquery/bquery/dnd';
120
+ *
121
+ * const handle = droppable(document.querySelector('#dropzone'), {
122
+ * accept: '.draggable-item',
123
+ * overClass: 'drop-active',
124
+ * onDrop: ({ dragged }) => {
125
+ * console.log('Dropped:', dragged);
126
+ * },
127
+ * });
128
+ *
129
+ * // Later:
130
+ * handle.destroy();
131
+ * ```
132
+ */
133
+ export const droppable = (el: HTMLElement, options: DroppableOptions = {}): DroppableHandle => {
134
+ const {
135
+ overClass = 'bq-drop-over',
136
+ accept,
137
+ onDragEnter,
138
+ onDragOver,
139
+ onDragLeave,
140
+ onDrop,
141
+ } = options;
142
+
143
+ if (!hasDroppableEnvironment()) {
144
+ return {
145
+ destroy: () => {},
146
+ };
147
+ }
148
+
149
+ let isOver = false;
150
+ let currentDragged: HTMLElement | null = null;
151
+
152
+ const createEventData = (dragged: HTMLElement, event: PointerEvent): DropEventData => ({
153
+ zone: el,
154
+ dragged,
155
+ event,
156
+ });
157
+
158
+ const isPointerInside = (event: PointerEvent): boolean => {
159
+ const rect = el.getBoundingClientRect();
160
+ return (
161
+ event.clientX >= rect.left &&
162
+ event.clientX <= rect.right &&
163
+ event.clientY >= rect.top &&
164
+ event.clientY <= rect.bottom
165
+ );
166
+ };
167
+
168
+ const resolveDraggedElement = (): HTMLElement | null => {
169
+ return getActiveDrag()?.element ?? currentDragged;
170
+ };
171
+
172
+ const clearOverState = (event: PointerEvent, dragged = currentDragged): void => {
173
+ if (!isOver) return;
174
+ isOver = false;
175
+ el.classList.remove(overClass);
176
+ if (dragged) {
177
+ onDragLeave?.(createEventData(dragged, event));
178
+ }
179
+ currentDragged = null;
180
+ };
181
+
182
+ const handlePointerMove = (e: PointerEvent): void => {
183
+ const dragged = getActiveDrag()?.element ?? null;
184
+ const isInside = isPointerInside(e);
185
+ const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);
186
+
187
+ if (!acceptsDragged || !isInside) {
188
+ clearOverState(e, dragged ?? currentDragged);
189
+ return;
190
+ }
191
+
192
+ if (!isOver) {
193
+ isOver = true;
194
+ currentDragged = dragged;
195
+ el.classList.add(overClass);
196
+ onDragEnter?.(createEventData(dragged, e));
197
+ } else {
198
+ onDragOver?.(createEventData(dragged, e));
199
+ }
200
+ };
201
+
202
+ const handlePointerUp = (e: PointerEvent): void => {
203
+ const dragged = resolveDraggedElement();
204
+ const isInside = isPointerInside(e);
205
+ const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);
206
+
207
+ if (isInside && acceptsDragged && dragged) {
208
+ onDrop?.(createEventData(dragged, e));
209
+ }
210
+
211
+ if (isOver) {
212
+ isOver = false;
213
+ el.classList.remove(overClass);
214
+ }
215
+ currentDragged = null;
216
+ };
217
+
218
+ const listener: DroppableListener = { handlePointerMove, handlePointerUp };
219
+ registerDroppableListener(listener);
220
+
221
+ return {
222
+ destroy: () => {
223
+ unregisterDroppableListener(listener);
224
+ el.classList.remove(overClass);
225
+ currentDragged = null;
226
+ },
227
+ };
228
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * bQuery Drag & Drop module.
3
+ *
4
+ * Provides pointer-event-based drag-and-drop, drop zones, and sortable
5
+ * lists with built-in touch support, axis locking, bounds constraints,
6
+ * and animated reordering.
7
+ *
8
+ * @module bquery/dnd
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { draggable, droppable, sortable } from '@bquery/bquery/dnd';
13
+ *
14
+ * // Make an element draggable
15
+ * const drag = draggable(document.querySelector('#box'), {
16
+ * axis: 'both',
17
+ * bounds: 'parent',
18
+ * ghost: true,
19
+ * onDragEnd: ({ position }) => console.log(position),
20
+ * });
21
+ *
22
+ * // Define a drop zone
23
+ * const drop = droppable(document.querySelector('#zone'), {
24
+ * accept: '.draggable',
25
+ * onDrop: ({ dragged }) => console.log('Dropped!', dragged),
26
+ * });
27
+ *
28
+ * // Make a list sortable
29
+ * const sort = sortable(document.querySelector('#list'), {
30
+ * items: 'li',
31
+ * axis: 'y',
32
+ * onSortEnd: ({ oldIndex, newIndex }) => {
33
+ * console.log(`Moved from ${oldIndex} to ${newIndex}`);
34
+ * },
35
+ * });
36
+ *
37
+ * // Cleanup when done
38
+ * drag.destroy();
39
+ * drop.destroy();
40
+ * sort.destroy();
41
+ * ```
42
+ */
43
+
44
+ export { draggable } from './draggable';
45
+ export { droppable } from './droppable';
46
+ export { sortable } from './sortable';
47
+
48
+ export type {
49
+ BoundsRect,
50
+ DragAxis,
51
+ DragBounds,
52
+ DragEventData,
53
+ DragPosition,
54
+ DraggableHandle,
55
+ DraggableOptions,
56
+ DropEventData,
57
+ DroppableHandle,
58
+ DroppableOptions,
59
+ SortEventData,
60
+ SortableHandle,
61
+ SortableOptions,
62
+ } from './types';