@usels/core 0.0.1-beta.3

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 (341) hide show
  1. package/README.md +21 -0
  2. package/dist/browser/useEventListener/index.d.mts +56 -0
  3. package/dist/browser/useEventListener/index.d.ts +56 -0
  4. package/dist/browser/useEventListener/index.js +112 -0
  5. package/dist/browser/useEventListener/index.js.map +1 -0
  6. package/dist/browser/useEventListener/index.mjs +88 -0
  7. package/dist/browser/useEventListener/index.mjs.map +1 -0
  8. package/dist/browser/useMediaQuery/demo.d.mts +5 -0
  9. package/dist/browser/useMediaQuery/demo.d.ts +5 -0
  10. package/dist/browser/useMediaQuery/demo.js +82 -0
  11. package/dist/browser/useMediaQuery/demo.js.map +1 -0
  12. package/dist/browser/useMediaQuery/demo.mjs +62 -0
  13. package/dist/browser/useMediaQuery/demo.mjs.map +1 -0
  14. package/dist/browser/useMediaQuery/index.d.mts +11 -0
  15. package/dist/browser/useMediaQuery/index.d.ts +11 -0
  16. package/dist/browser/useMediaQuery/index.js +89 -0
  17. package/dist/browser/useMediaQuery/index.js.map +1 -0
  18. package/dist/browser/useMediaQuery/index.mjs +64 -0
  19. package/dist/browser/useMediaQuery/index.mjs.map +1 -0
  20. package/dist/components/Auto/index.d.mts +33 -0
  21. package/dist/components/Auto/index.d.ts +33 -0
  22. package/dist/components/Auto/index.js +66 -0
  23. package/dist/components/Auto/index.js.map +1 -0
  24. package/dist/components/Auto/index.mjs +34 -0
  25. package/dist/components/Auto/index.mjs.map +1 -0
  26. package/dist/elements/useDocumentVisibility/demo.d.mts +5 -0
  27. package/dist/elements/useDocumentVisibility/demo.d.ts +5 -0
  28. package/dist/elements/useDocumentVisibility/demo.js +130 -0
  29. package/dist/elements/useDocumentVisibility/demo.js.map +1 -0
  30. package/dist/elements/useDocumentVisibility/demo.mjs +114 -0
  31. package/dist/elements/useDocumentVisibility/demo.mjs.map +1 -0
  32. package/dist/elements/useDocumentVisibility/index.d.mts +5 -0
  33. package/dist/elements/useDocumentVisibility/index.d.ts +5 -0
  34. package/dist/elements/useDocumentVisibility/index.js +45 -0
  35. package/dist/elements/useDocumentVisibility/index.js.map +1 -0
  36. package/dist/elements/useDocumentVisibility/index.mjs +21 -0
  37. package/dist/elements/useDocumentVisibility/index.mjs.map +1 -0
  38. package/dist/elements/useElementBounding/demo.d.mts +5 -0
  39. package/dist/elements/useElementBounding/demo.d.ts +5 -0
  40. package/dist/elements/useElementBounding/demo.js +86 -0
  41. package/dist/elements/useElementBounding/demo.js.map +1 -0
  42. package/dist/elements/useElementBounding/demo.mjs +66 -0
  43. package/dist/elements/useElementBounding/demo.mjs.map +1 -0
  44. package/dist/elements/useElementBounding/index.d.mts +46 -0
  45. package/dist/elements/useElementBounding/index.d.ts +46 -0
  46. package/dist/elements/useElementBounding/index.js +122 -0
  47. package/dist/elements/useElementBounding/index.js.map +1 -0
  48. package/dist/elements/useElementBounding/index.mjs +98 -0
  49. package/dist/elements/useElementBounding/index.mjs.map +1 -0
  50. package/dist/elements/useElementSize/demo.d.mts +5 -0
  51. package/dist/elements/useElementSize/demo.d.ts +5 -0
  52. package/dist/elements/useElementSize/demo.js +82 -0
  53. package/dist/elements/useElementSize/demo.js.map +1 -0
  54. package/dist/elements/useElementSize/demo.mjs +62 -0
  55. package/dist/elements/useElementSize/demo.mjs.map +1 -0
  56. package/dist/elements/useElementSize/index.d.mts +34 -0
  57. package/dist/elements/useElementSize/index.d.ts +34 -0
  58. package/dist/elements/useElementSize/index.js +85 -0
  59. package/dist/elements/useElementSize/index.js.map +1 -0
  60. package/dist/elements/useElementSize/index.mjs +61 -0
  61. package/dist/elements/useElementSize/index.mjs.map +1 -0
  62. package/dist/elements/useElementVisibility/demo.d.mts +5 -0
  63. package/dist/elements/useElementVisibility/demo.d.ts +5 -0
  64. package/dist/elements/useElementVisibility/demo.js +110 -0
  65. package/dist/elements/useElementVisibility/demo.js.map +1 -0
  66. package/dist/elements/useElementVisibility/demo.mjs +90 -0
  67. package/dist/elements/useElementVisibility/demo.mjs.map +1 -0
  68. package/dist/elements/useElementVisibility/index.d.mts +43 -0
  69. package/dist/elements/useElementVisibility/index.d.ts +43 -0
  70. package/dist/elements/useElementVisibility/index.js +58 -0
  71. package/dist/elements/useElementVisibility/index.js.map +1 -0
  72. package/dist/elements/useElementVisibility/index.mjs +34 -0
  73. package/dist/elements/useElementVisibility/index.mjs.map +1 -0
  74. package/dist/elements/useIntersectionObserver/demo.d.mts +5 -0
  75. package/dist/elements/useIntersectionObserver/demo.d.ts +5 -0
  76. package/dist/elements/useIntersectionObserver/demo.js +173 -0
  77. package/dist/elements/useIntersectionObserver/demo.js.map +1 -0
  78. package/dist/elements/useIntersectionObserver/demo.mjs +153 -0
  79. package/dist/elements/useIntersectionObserver/demo.mjs.map +1 -0
  80. package/dist/elements/useIntersectionObserver/index.d.mts +47 -0
  81. package/dist/elements/useIntersectionObserver/index.d.ts +47 -0
  82. package/dist/elements/useIntersectionObserver/index.js +111 -0
  83. package/dist/elements/useIntersectionObserver/index.js.map +1 -0
  84. package/dist/elements/useIntersectionObserver/index.mjs +87 -0
  85. package/dist/elements/useIntersectionObserver/index.mjs.map +1 -0
  86. package/dist/elements/useMouseInElement/demo.d.mts +5 -0
  87. package/dist/elements/useMouseInElement/demo.d.ts +5 -0
  88. package/dist/elements/useMouseInElement/demo.js +103 -0
  89. package/dist/elements/useMouseInElement/demo.js.map +1 -0
  90. package/dist/elements/useMouseInElement/demo.mjs +83 -0
  91. package/dist/elements/useMouseInElement/demo.mjs.map +1 -0
  92. package/dist/elements/useMouseInElement/index.d.mts +56 -0
  93. package/dist/elements/useMouseInElement/index.d.ts +56 -0
  94. package/dist/elements/useMouseInElement/index.js +148 -0
  95. package/dist/elements/useMouseInElement/index.js.map +1 -0
  96. package/dist/elements/useMouseInElement/index.mjs +124 -0
  97. package/dist/elements/useMouseInElement/index.mjs.map +1 -0
  98. package/dist/elements/useMutationObserver/demo.d.mts +5 -0
  99. package/dist/elements/useMutationObserver/demo.d.ts +5 -0
  100. package/dist/elements/useMutationObserver/demo.js +240 -0
  101. package/dist/elements/useMutationObserver/demo.js.map +1 -0
  102. package/dist/elements/useMutationObserver/demo.mjs +220 -0
  103. package/dist/elements/useMutationObserver/demo.mjs.map +1 -0
  104. package/dist/elements/useMutationObserver/index.d.mts +15 -0
  105. package/dist/elements/useMutationObserver/index.d.ts +15 -0
  106. package/dist/elements/useMutationObserver/index.js +69 -0
  107. package/dist/elements/useMutationObserver/index.js.map +1 -0
  108. package/dist/elements/useMutationObserver/index.mjs +45 -0
  109. package/dist/elements/useMutationObserver/index.mjs.map +1 -0
  110. package/dist/elements/useParentElement/demo.d.mts +5 -0
  111. package/dist/elements/useParentElement/demo.d.ts +5 -0
  112. package/dist/elements/useParentElement/demo.js +132 -0
  113. package/dist/elements/useParentElement/demo.js.map +1 -0
  114. package/dist/elements/useParentElement/demo.mjs +112 -0
  115. package/dist/elements/useParentElement/demo.mjs.map +1 -0
  116. package/dist/elements/useParentElement/index.d.mts +7 -0
  117. package/dist/elements/useParentElement/index.d.ts +7 -0
  118. package/dist/elements/useParentElement/index.js +47 -0
  119. package/dist/elements/useParentElement/index.js.map +1 -0
  120. package/dist/elements/useParentElement/index.mjs +23 -0
  121. package/dist/elements/useParentElement/index.mjs.map +1 -0
  122. package/dist/elements/useRef$/index.js +89 -0
  123. package/dist/elements/useRef$/index.js.map +1 -0
  124. package/dist/elements/useRef$/index.mjs +62 -0
  125. package/dist/elements/useRef$/index.mjs.map +1 -0
  126. package/dist/elements/useRef_/index.d.mts +60 -0
  127. package/dist/elements/useRef_/index.d.ts +60 -0
  128. package/dist/elements/useResizeObserver/demo.d.mts +5 -0
  129. package/dist/elements/useResizeObserver/demo.d.ts +5 -0
  130. package/dist/elements/useResizeObserver/demo.js +90 -0
  131. package/dist/elements/useResizeObserver/demo.js.map +1 -0
  132. package/dist/elements/useResizeObserver/demo.mjs +70 -0
  133. package/dist/elements/useResizeObserver/demo.mjs.map +1 -0
  134. package/dist/elements/useResizeObserver/index.d.mts +36 -0
  135. package/dist/elements/useResizeObserver/index.d.ts +36 -0
  136. package/dist/elements/useResizeObserver/index.js +74 -0
  137. package/dist/elements/useResizeObserver/index.js.map +1 -0
  138. package/dist/elements/useResizeObserver/index.mjs +49 -0
  139. package/dist/elements/useResizeObserver/index.mjs.map +1 -0
  140. package/dist/elements/useWindowFocus/demo.d.mts +5 -0
  141. package/dist/elements/useWindowFocus/demo.d.ts +5 -0
  142. package/dist/elements/useWindowFocus/demo.js +100 -0
  143. package/dist/elements/useWindowFocus/demo.js.map +1 -0
  144. package/dist/elements/useWindowFocus/demo.mjs +80 -0
  145. package/dist/elements/useWindowFocus/demo.mjs.map +1 -0
  146. package/dist/elements/useWindowFocus/index.d.mts +5 -0
  147. package/dist/elements/useWindowFocus/index.d.ts +5 -0
  148. package/dist/elements/useWindowFocus/index.js +42 -0
  149. package/dist/elements/useWindowFocus/index.js.map +1 -0
  150. package/dist/elements/useWindowFocus/index.mjs +18 -0
  151. package/dist/elements/useWindowFocus/index.mjs.map +1 -0
  152. package/dist/elements/useWindowSize/demo.d.mts +5 -0
  153. package/dist/elements/useWindowSize/demo.d.ts +5 -0
  154. package/dist/elements/useWindowSize/demo.js +78 -0
  155. package/dist/elements/useWindowSize/demo.js.map +1 -0
  156. package/dist/elements/useWindowSize/demo.mjs +58 -0
  157. package/dist/elements/useWindowSize/demo.mjs.map +1 -0
  158. package/dist/elements/useWindowSize/index.d.mts +17 -0
  159. package/dist/elements/useWindowSize/index.d.ts +17 -0
  160. package/dist/elements/useWindowSize/index.js +96 -0
  161. package/dist/elements/useWindowSize/index.js.map +1 -0
  162. package/dist/elements/useWindowSize/index.mjs +76 -0
  163. package/dist/elements/useWindowSize/index.mjs.map +1 -0
  164. package/dist/function/get/index.d.mts +45 -0
  165. package/dist/function/get/index.d.ts +45 -0
  166. package/dist/function/get/index.js +39 -0
  167. package/dist/function/get/index.js.map +1 -0
  168. package/dist/function/get/index.mjs +15 -0
  169. package/dist/function/get/index.mjs.map +1 -0
  170. package/dist/function/peek/index.d.mts +46 -0
  171. package/dist/function/peek/index.d.ts +46 -0
  172. package/dist/function/peek/index.js +39 -0
  173. package/dist/function/peek/index.js.map +1 -0
  174. package/dist/function/peek/index.mjs +15 -0
  175. package/dist/function/peek/index.mjs.map +1 -0
  176. package/dist/function/useMayObservableOptions/index.d.mts +59 -0
  177. package/dist/function/useMayObservableOptions/index.d.ts +59 -0
  178. package/dist/function/useMayObservableOptions/index.js +109 -0
  179. package/dist/function/useMayObservableOptions/index.js.map +1 -0
  180. package/dist/function/useMayObservableOptions/index.mjs +88 -0
  181. package/dist/function/useMayObservableOptions/index.mjs.map +1 -0
  182. package/dist/function/useSupported/index.d.mts +6 -0
  183. package/dist/function/useSupported/index.d.ts +6 -0
  184. package/dist/function/useSupported/index.js +37 -0
  185. package/dist/function/useSupported/index.js.map +1 -0
  186. package/dist/function/useSupported/index.mjs +13 -0
  187. package/dist/function/useSupported/index.mjs.map +1 -0
  188. package/dist/function/useWhenMounted/index.d.mts +6 -0
  189. package/dist/function/useWhenMounted/index.d.ts +6 -0
  190. package/dist/function/useWhenMounted/index.js +37 -0
  191. package/dist/function/useWhenMounted/index.js.map +1 -0
  192. package/dist/function/useWhenMounted/index.mjs +13 -0
  193. package/dist/function/useWhenMounted/index.mjs.map +1 -0
  194. package/dist/index.d.mts +24 -0
  195. package/dist/index.d.ts +24 -0
  196. package/dist/index.js +63 -0
  197. package/dist/index.js.map +1 -0
  198. package/dist/index.mjs +22 -0
  199. package/dist/index.mjs.map +1 -0
  200. package/dist/sensors/useScroll/demo.d.mts +5 -0
  201. package/dist/sensors/useScroll/demo.d.ts +5 -0
  202. package/dist/sensors/useScroll/demo.js +121 -0
  203. package/dist/sensors/useScroll/demo.js.map +1 -0
  204. package/dist/sensors/useScroll/demo.mjs +101 -0
  205. package/dist/sensors/useScroll/demo.mjs.map +1 -0
  206. package/dist/sensors/useScroll/index.d.mts +42 -0
  207. package/dist/sensors/useScroll/index.d.ts +42 -0
  208. package/dist/sensors/useScroll/index.js +149 -0
  209. package/dist/sensors/useScroll/index.js.map +1 -0
  210. package/dist/sensors/useScroll/index.mjs +125 -0
  211. package/dist/sensors/useScroll/index.mjs.map +1 -0
  212. package/dist/sensors/useWindowScroll/demo.d.mts +5 -0
  213. package/dist/sensors/useWindowScroll/demo.d.ts +5 -0
  214. package/dist/sensors/useWindowScroll/demo.js +84 -0
  215. package/dist/sensors/useWindowScroll/demo.js.map +1 -0
  216. package/dist/sensors/useWindowScroll/demo.mjs +64 -0
  217. package/dist/sensors/useWindowScroll/demo.mjs.map +1 -0
  218. package/dist/sensors/useWindowScroll/index.d.mts +9 -0
  219. package/dist/sensors/useWindowScroll/index.d.ts +9 -0
  220. package/dist/sensors/useWindowScroll/index.js +36 -0
  221. package/dist/sensors/useWindowScroll/index.js.map +1 -0
  222. package/dist/sensors/useWindowScroll/index.mjs +12 -0
  223. package/dist/sensors/useWindowScroll/index.mjs.map +1 -0
  224. package/dist/shared/configurable.d.mts +21 -0
  225. package/dist/shared/configurable.d.ts +21 -0
  226. package/dist/shared/configurable.js +39 -0
  227. package/dist/shared/configurable.js.map +1 -0
  228. package/dist/shared/configurable.mjs +12 -0
  229. package/dist/shared/configurable.mjs.map +1 -0
  230. package/dist/shared/index.d.mts +4 -0
  231. package/dist/shared/index.d.ts +4 -0
  232. package/dist/shared/index.js +31 -0
  233. package/dist/shared/index.js.map +1 -0
  234. package/dist/shared/index.mjs +7 -0
  235. package/dist/shared/index.mjs.map +1 -0
  236. package/dist/shared/normalizeTargets/index.d.mts +21 -0
  237. package/dist/shared/normalizeTargets/index.d.ts +21 -0
  238. package/dist/shared/normalizeTargets/index.js +36 -0
  239. package/dist/shared/normalizeTargets/index.js.map +1 -0
  240. package/dist/shared/normalizeTargets/index.mjs +12 -0
  241. package/dist/shared/normalizeTargets/index.mjs.map +1 -0
  242. package/dist/shared/utils.d.mts +15 -0
  243. package/dist/shared/utils.d.ts +15 -0
  244. package/dist/shared/utils.js +87 -0
  245. package/dist/shared/utils.js.map +1 -0
  246. package/dist/shared/utils.mjs +52 -0
  247. package/dist/shared/utils.mjs.map +1 -0
  248. package/dist/types.d.mts +52 -0
  249. package/dist/types.d.ts +52 -0
  250. package/dist/types.js +17 -0
  251. package/dist/types.js.map +1 -0
  252. package/dist/types.mjs +1 -0
  253. package/dist/types.mjs.map +1 -0
  254. package/package.json +54 -0
  255. package/src/browser/useEventListener/index.md +109 -0
  256. package/src/browser/useEventListener/index.spec.ts +611 -0
  257. package/src/browser/useEventListener/index.ts +242 -0
  258. package/src/browser/useMediaQuery/demo.tsx +58 -0
  259. package/src/browser/useMediaQuery/index.md +40 -0
  260. package/src/browser/useMediaQuery/index.spec.ts +267 -0
  261. package/src/browser/useMediaQuery/index.ts +96 -0
  262. package/src/components/Auto/index.tsx +65 -0
  263. package/src/elements/useDocumentVisibility/demo.tsx +111 -0
  264. package/src/elements/useDocumentVisibility/index.md +51 -0
  265. package/src/elements/useDocumentVisibility/index.spec.ts +114 -0
  266. package/src/elements/useDocumentVisibility/index.ts +26 -0
  267. package/src/elements/useElementBounding/demo.tsx +63 -0
  268. package/src/elements/useElementBounding/index.md +59 -0
  269. package/src/elements/useElementBounding/index.ts +159 -0
  270. package/src/elements/useElementSize/demo.tsx +48 -0
  271. package/src/elements/useElementSize/index.md +60 -0
  272. package/src/elements/useElementSize/index.spec.ts +295 -0
  273. package/src/elements/useElementSize/index.ts +100 -0
  274. package/src/elements/useElementVisibility/deep-observable-pattern.spec.ts +453 -0
  275. package/src/elements/useElementVisibility/demo.tsx +97 -0
  276. package/src/elements/useElementVisibility/index.md +98 -0
  277. package/src/elements/useElementVisibility/index.spec.ts +227 -0
  278. package/src/elements/useElementVisibility/index.ts +78 -0
  279. package/src/elements/useIntersectionObserver/demo.tsx +180 -0
  280. package/src/elements/useIntersectionObserver/index.md +99 -0
  281. package/src/elements/useIntersectionObserver/index.spec.ts +482 -0
  282. package/src/elements/useIntersectionObserver/index.ts +149 -0
  283. package/src/elements/useMouseInElement/demo.tsx +79 -0
  284. package/src/elements/useMouseInElement/index.md +71 -0
  285. package/src/elements/useMouseInElement/index.spec.ts +398 -0
  286. package/src/elements/useMouseInElement/index.ts +209 -0
  287. package/src/elements/useMutationObserver/demo.tsx +270 -0
  288. package/src/elements/useMutationObserver/index.md +99 -0
  289. package/src/elements/useMutationObserver/index.spec.ts +421 -0
  290. package/src/elements/useMutationObserver/index.ts +66 -0
  291. package/src/elements/useParentElement/demo.tsx +120 -0
  292. package/src/elements/useParentElement/index.md +67 -0
  293. package/src/elements/useParentElement/index.spec.ts +208 -0
  294. package/src/elements/useParentElement/index.ts +35 -0
  295. package/src/elements/useRef$/index.md +62 -0
  296. package/src/elements/useRef$/index.spec.ts +205 -0
  297. package/src/elements/useRef$/index.ts +137 -0
  298. package/src/elements/useRef$/useImperativeHandle.spec.ts +339 -0
  299. package/src/elements/useResizeObserver/demo.tsx +56 -0
  300. package/src/elements/useResizeObserver/index.md +51 -0
  301. package/src/elements/useResizeObserver/index.spec.ts +312 -0
  302. package/src/elements/useResizeObserver/index.ts +106 -0
  303. package/src/elements/useWindowFocus/demo.tsx +71 -0
  304. package/src/elements/useWindowFocus/index.md +35 -0
  305. package/src/elements/useWindowFocus/index.spec.ts +103 -0
  306. package/src/elements/useWindowFocus/index.ts +21 -0
  307. package/src/elements/useWindowSize/demo.tsx +46 -0
  308. package/src/elements/useWindowSize/index.md +50 -0
  309. package/src/elements/useWindowSize/index.spec.ts +310 -0
  310. package/src/elements/useWindowSize/index.ts +107 -0
  311. package/src/function/get/index.md +25 -0
  312. package/src/function/get/index.spec.ts +87 -0
  313. package/src/function/get/index.ts +70 -0
  314. package/src/function/peek/index.spec.ts +97 -0
  315. package/src/function/peek/index.ts +69 -0
  316. package/src/function/useMayObservableOptions/index.spec.ts +521 -0
  317. package/src/function/useMayObservableOptions/index.ts +173 -0
  318. package/src/function/useSupported/index.md +38 -0
  319. package/src/function/useSupported/index.spec.ts +116 -0
  320. package/src/function/useSupported/index.ts +14 -0
  321. package/src/function/useWhenMounted/index.md +25 -0
  322. package/src/function/useWhenMounted/index.spec.ts +120 -0
  323. package/src/function/useWhenMounted/index.ts +16 -0
  324. package/src/index.ts +25 -0
  325. package/src/sensors/useScroll/demo.tsx +98 -0
  326. package/src/sensors/useScroll/index.md +112 -0
  327. package/src/sensors/useScroll/index.spec.ts +678 -0
  328. package/src/sensors/useScroll/index.ts +201 -0
  329. package/src/sensors/useWindowScroll/demo.tsx +69 -0
  330. package/src/sensors/useWindowScroll/index.md +88 -0
  331. package/src/sensors/useWindowScroll/index.spec.ts +69 -0
  332. package/src/sensors/useWindowScroll/index.ts +11 -0
  333. package/src/shared/configurable.ts +35 -0
  334. package/src/shared/index.ts +4 -0
  335. package/src/shared/normalizeTargets/index.spec.ts +76 -0
  336. package/src/shared/normalizeTargets/index.ts +27 -0
  337. package/src/shared/utils.ts +67 -0
  338. package/src/types.ts +56 -0
  339. package/tsconfig.json +9 -0
  340. package/tsup.config.ts +10 -0
  341. package/vitest.config.ts +22 -0
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: useMouseInElement
3
+ category: elements
4
+ ---
5
+
6
+ Tracks the mouse cursor position relative to a DOM element and reports whether the cursor is inside or outside it.
7
+ Observes `mousemove`, `document` `mouseleave`, [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) (`style`/`class` attribute changes), and `window` `scroll`/`resize` events to keep values accurate as the element moves or resizes.
8
+ All return values are reactive `Observable<number | boolean>`.
9
+
10
+ ## Demo
11
+
12
+ ## Usage
13
+
14
+ ```tsx twoslash
15
+ // @noErrors
16
+ import { useRef$, useMouseInElement } from '@usels/core'
17
+
18
+ function Component() {
19
+ const el$ = useRef$<HTMLDivElement>()
20
+ const { elementX$, elementY$, isOutside$ } = useMouseInElement(el$)
21
+
22
+ return (
23
+ <div ref={el$}>
24
+ {isOutside$.get() ? 'outside' : `${elementX$.get()}, ${elementY$.get()}`}
25
+ </div>
26
+ )
27
+ }
28
+ ```
29
+
30
+ ### Disable outside tracking
31
+
32
+ By default `elementX$`/`elementY$` continue to update even when the cursor leaves the element.
33
+ Pass `handleOutside: false` to freeze the last in-element position once the cursor exits.
34
+
35
+ ```typescript
36
+ const { elementX$, elementY$ } = useMouseInElement(el$, { handleOutside: false })
37
+ ```
38
+
39
+ ### Disable scroll / resize recalculation
40
+
41
+ ```typescript
42
+ const { elementX$, elementY$ } = useMouseInElement(el$, {
43
+ windowScroll: false,
44
+ windowResize: false,
45
+ })
46
+ ```
47
+
48
+ ### Stop all observers manually
49
+
50
+ ```tsx twoslash
51
+ // @noErrors
52
+ import { useRef$, Ref$, useMouseInElement } from '@usels/core'
53
+ declare const el$: Ref$<HTMLDivElement>
54
+ // ---cut---
55
+ const { elementX$, elementY$, stop } = useMouseInElement(el$)
56
+
57
+ // Tear down all event listeners and observers
58
+ stop()
59
+ ```
60
+
61
+ ### Global mouse coordinates
62
+
63
+ The raw `clientX` / `clientY` values are also exposed as `x$` and `y$`.
64
+
65
+ ```tsx twoslash
66
+ // @noErrors
67
+ import { useRef$, Ref$, useMouseInElement } from '@usels/core'
68
+ declare const el$: Ref$<HTMLDivElement>
69
+ // ---cut---
70
+ const { x$, y$, elementX$, elementY$ } = useMouseInElement(el$)
71
+ ```
@@ -0,0 +1,398 @@
1
+ // @vitest-environment jsdom
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { observable, ObservableHint } from "@legendapp/state";
4
+ import type { OpaqueObject } from "@legendapp/state";
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
6
+ import { useMouseInElement } from ".";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+
12
+ const wrapEl = (el: Element) =>
13
+ observable<OpaqueObject<Element> | null>(ObservableHint.opaque(el));
14
+
15
+ /**
16
+ * Create a div whose getClientRects() returns a single rect at the given position.
17
+ * Defaults: left=0, top=0, right=200, bottom=100, width=200, height=100
18
+ */
19
+ function createDiv(rect: Partial<DOMRect> = {}) {
20
+ const div = document.createElement("div");
21
+ const full: DOMRect = {
22
+ left: 0,
23
+ top: 0,
24
+ right: 200,
25
+ bottom: 100,
26
+ width: 200,
27
+ height: 100,
28
+ x: 0,
29
+ y: 0,
30
+ toJSON: () => ({}),
31
+ ...rect,
32
+ };
33
+ vi.spyOn(div, "getClientRects").mockReturnValue(
34
+ [full] as unknown as DOMRectList,
35
+ );
36
+ return div;
37
+ }
38
+
39
+ function fireMouseMove(x: number, y: number) {
40
+ act(() => {
41
+ window.dispatchEvent(
42
+ new MouseEvent("mousemove", { clientX: x, clientY: y, bubbles: true }),
43
+ );
44
+ });
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Mocks: ResizeObserver, MutationObserver
49
+ // ---------------------------------------------------------------------------
50
+
51
+ class ResizeObserverMock {
52
+ static instances: ResizeObserverMock[] = [];
53
+ callback: ResizeObserverCallback;
54
+ observed: Element[] = [];
55
+ disconnected = false;
56
+
57
+ constructor(cb: ResizeObserverCallback) {
58
+ this.callback = cb;
59
+ ResizeObserverMock.instances.push(this);
60
+ }
61
+ observe(el: Element) {
62
+ this.observed.push(el);
63
+ }
64
+ unobserve(el: Element) {
65
+ this.observed = this.observed.filter((e) => e !== el);
66
+ }
67
+ disconnect() {
68
+ this.disconnected = true;
69
+ this.observed = [];
70
+ }
71
+ }
72
+
73
+ class MutationObserverMock {
74
+ static instances: MutationObserverMock[] = [];
75
+ callback: MutationCallback;
76
+ disconnected = false;
77
+
78
+ constructor(cb: MutationCallback) {
79
+ this.callback = cb;
80
+ MutationObserverMock.instances.push(this);
81
+ }
82
+ observe(_el: Element, _opts?: MutationObserverInit) {}
83
+ disconnect() {
84
+ this.disconnected = true;
85
+ }
86
+ takeRecords(): MutationRecord[] {
87
+ return [];
88
+ }
89
+ }
90
+
91
+ beforeEach(() => {
92
+ ResizeObserverMock.instances = [];
93
+ MutationObserverMock.instances = [];
94
+ vi.stubGlobal("ResizeObserver", ResizeObserverMock);
95
+ vi.stubGlobal("MutationObserver", MutationObserverMock);
96
+ Object.defineProperty(window, "scrollX", { value: 0, configurable: true });
97
+ Object.defineProperty(window, "scrollY", { value: 0, configurable: true });
98
+ });
99
+
100
+ afterEach(() => {
101
+ vi.unstubAllGlobals();
102
+ });
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // useMouseInElement tests
106
+ // ---------------------------------------------------------------------------
107
+
108
+ describe("useMouseInElement()", () => {
109
+ it("initial state: all zeros and isOutside = true", () => {
110
+ const div = createDiv();
111
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
112
+
113
+ expect(result.current.elementX$.get()).toBe(0);
114
+ expect(result.current.elementY$.get()).toBe(0);
115
+ expect(result.current.elementPositionX$.get()).toBe(0);
116
+ expect(result.current.elementPositionY$.get()).toBe(0);
117
+ expect(result.current.elementWidth$.get()).toBe(0);
118
+ expect(result.current.elementHeight$.get()).toBe(0);
119
+ expect(result.current.isOutside$.get()).toBe(true);
120
+ expect(result.current.x$.get()).toBe(0);
121
+ expect(result.current.y$.get()).toBe(0);
122
+ });
123
+
124
+ it("mousemove inside rect → updates elementX/Y and sets isOutside = false", () => {
125
+ // Rect: left=10, top=20, right=210, bottom=120
126
+ const div = createDiv({
127
+ left: 10,
128
+ top: 20,
129
+ right: 210,
130
+ bottom: 120,
131
+ width: 200,
132
+ height: 100,
133
+ });
134
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
135
+
136
+ fireMouseMove(60, 70); // inside: 60-10=50, 70-20=50
137
+
138
+ expect(result.current.isOutside$.get()).toBe(false);
139
+ expect(result.current.elementX$.get()).toBe(50);
140
+ expect(result.current.elementY$.get()).toBe(50);
141
+ });
142
+
143
+ it("exposes raw clientX/clientY as x and y", () => {
144
+ const div = createDiv();
145
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
146
+
147
+ fireMouseMove(350, 420);
148
+
149
+ expect(result.current.x$.get()).toBe(350);
150
+ expect(result.current.y$.get()).toBe(420);
151
+ });
152
+
153
+ it("elementPositionX/Y includes window.scrollX/scrollY", () => {
154
+ Object.defineProperty(window, "scrollX", { value: 100, configurable: true });
155
+ Object.defineProperty(window, "scrollY", { value: 200, configurable: true });
156
+
157
+ const div = createDiv({
158
+ left: 10,
159
+ top: 20,
160
+ right: 210,
161
+ bottom: 120,
162
+ width: 200,
163
+ height: 100,
164
+ });
165
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
166
+
167
+ fireMouseMove(60, 70); // inside
168
+
169
+ // elementPositionX = rect.left + scrollX = 10 + 100 = 110
170
+ expect(result.current.elementPositionX$.get()).toBe(110);
171
+ // elementPositionY = rect.top + scrollY = 20 + 200 = 220
172
+ expect(result.current.elementPositionY$.get()).toBe(220);
173
+ });
174
+
175
+ it("exposes elementWidth and elementHeight from matched rect", () => {
176
+ const div = createDiv({
177
+ left: 0,
178
+ top: 0,
179
+ right: 300,
180
+ bottom: 150,
181
+ width: 300,
182
+ height: 150,
183
+ });
184
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
185
+
186
+ fireMouseMove(50, 50); // inside
187
+
188
+ expect(result.current.elementWidth$.get()).toBe(300);
189
+ expect(result.current.elementHeight$.get()).toBe(150);
190
+ });
191
+
192
+ it("mousemove outside rect → isOutside = true", () => {
193
+ const div = createDiv({
194
+ left: 0,
195
+ top: 0,
196
+ right: 200,
197
+ bottom: 100,
198
+ width: 200,
199
+ height: 100,
200
+ });
201
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
202
+
203
+ fireMouseMove(50, 50); // inside first
204
+ expect(result.current.isOutside$.get()).toBe(false);
205
+
206
+ fireMouseMove(500, 500); // outside
207
+ expect(result.current.isOutside$.get()).toBe(true);
208
+ });
209
+
210
+ it("handleOutside: true (default) — still updates elementX/Y when outside", () => {
211
+ const div = createDiv({
212
+ left: 0,
213
+ top: 0,
214
+ right: 200,
215
+ bottom: 100,
216
+ width: 200,
217
+ height: 100,
218
+ });
219
+ const { result } = renderHook(() =>
220
+ useMouseInElement(wrapEl(div) as any, { handleOutside: true }),
221
+ );
222
+
223
+ fireMouseMove(300, 400); // outside: elementX = 300-0, elementY = 400-0
224
+
225
+ expect(result.current.isOutside$.get()).toBe(true);
226
+ expect(result.current.elementX$.get()).toBe(300);
227
+ expect(result.current.elementY$.get()).toBe(400);
228
+ });
229
+
230
+ it("handleOutside: false — elementX/Y frozen at last inside position after leaving", () => {
231
+ const div = createDiv({
232
+ left: 0,
233
+ top: 0,
234
+ right: 200,
235
+ bottom: 100,
236
+ width: 200,
237
+ height: 100,
238
+ });
239
+ const { result } = renderHook(() =>
240
+ useMouseInElement(wrapEl(div) as any, { handleOutside: false }),
241
+ );
242
+
243
+ fireMouseMove(80, 60); // inside
244
+ expect(result.current.elementX$.get()).toBe(80);
245
+ expect(result.current.elementY$.get()).toBe(60);
246
+
247
+ fireMouseMove(500, 500); // outside — should NOT update elementX/Y
248
+ expect(result.current.isOutside$.get()).toBe(true);
249
+ expect(result.current.elementX$.get()).toBe(80);
250
+ expect(result.current.elementY$.get()).toBe(60);
251
+ });
252
+
253
+ it("document mouseleave → sets isOutside = true regardless of cursor position", () => {
254
+ const div = createDiv({
255
+ left: 0,
256
+ top: 0,
257
+ right: 200,
258
+ bottom: 100,
259
+ width: 200,
260
+ height: 100,
261
+ });
262
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
263
+
264
+ fireMouseMove(50, 50); // inside
265
+ expect(result.current.isOutside$.get()).toBe(false);
266
+
267
+ act(() => {
268
+ document.dispatchEvent(new MouseEvent("mouseleave", { bubbles: false }));
269
+ });
270
+
271
+ expect(result.current.isOutside$.get()).toBe(true);
272
+ });
273
+
274
+ it("scroll event triggers recalculation (windowScroll: true by default)", () => {
275
+ const div = createDiv();
276
+ const spy = vi.spyOn(div, "getClientRects");
277
+ renderHook(() => useMouseInElement(wrapEl(div) as any));
278
+
279
+ const before = spy.mock.calls.length;
280
+
281
+ act(() => {
282
+ window.dispatchEvent(new Event("scroll"));
283
+ });
284
+
285
+ expect(spy.mock.calls.length).toBeGreaterThan(before);
286
+ });
287
+
288
+ it("windowScroll: false — scroll event does not trigger recalculation", () => {
289
+ const div = createDiv();
290
+ const spy = vi.spyOn(div, "getClientRects");
291
+ renderHook(() =>
292
+ useMouseInElement(wrapEl(div) as any, { windowScroll: false }),
293
+ );
294
+
295
+ const before = spy.mock.calls.length;
296
+
297
+ act(() => {
298
+ window.dispatchEvent(new Event("scroll"));
299
+ });
300
+
301
+ expect(spy.mock.calls.length).toBe(before);
302
+ });
303
+
304
+ it("resize event triggers recalculation (windowResize: true by default)", () => {
305
+ const div = createDiv();
306
+ const spy = vi.spyOn(div, "getClientRects");
307
+ renderHook(() => useMouseInElement(wrapEl(div) as any));
308
+
309
+ const before = spy.mock.calls.length;
310
+
311
+ act(() => {
312
+ window.dispatchEvent(new Event("resize"));
313
+ });
314
+
315
+ expect(spy.mock.calls.length).toBeGreaterThan(before);
316
+ });
317
+
318
+ it("windowResize: false — resize event does not trigger recalculation", () => {
319
+ const div = createDiv();
320
+ const spy = vi.spyOn(div, "getClientRects");
321
+ renderHook(() =>
322
+ useMouseInElement(wrapEl(div) as any, { windowResize: false }),
323
+ );
324
+
325
+ const before = spy.mock.calls.length;
326
+
327
+ act(() => {
328
+ window.dispatchEvent(new Event("resize"));
329
+ });
330
+
331
+ expect(spy.mock.calls.length).toBe(before);
332
+ });
333
+
334
+ it("stop() disconnects ResizeObserver and MutationObserver", () => {
335
+ const div = createDiv();
336
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
337
+
338
+ act(() => result.current.stop());
339
+
340
+ expect(ResizeObserverMock.instances.some((i) => i.disconnected)).toBe(true);
341
+ expect(MutationObserverMock.instances.some((i) => i.disconnected)).toBe(
342
+ true,
343
+ );
344
+ });
345
+
346
+ it("stop() removes mousemove listener — state does not update afterwards", () => {
347
+ const div = createDiv({
348
+ left: 0,
349
+ top: 0,
350
+ right: 200,
351
+ bottom: 100,
352
+ width: 200,
353
+ height: 100,
354
+ });
355
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
356
+
357
+ fireMouseMove(80, 60); // inside
358
+ expect(result.current.elementX$.get()).toBe(80);
359
+
360
+ act(() => result.current.stop());
361
+
362
+ // Dispatch mousemove after stop — listener should be removed
363
+ act(() => {
364
+ window.dispatchEvent(
365
+ new MouseEvent("mousemove", {
366
+ clientX: 10,
367
+ clientY: 10,
368
+ bubbles: true,
369
+ }),
370
+ );
371
+ });
372
+
373
+ // x/y raw values should not have updated
374
+ expect(result.current.x$.get()).toBe(80);
375
+ expect(result.current.y$.get()).toBe(60);
376
+ });
377
+
378
+ it("does not throw when target is null", () => {
379
+ expect(() => {
380
+ renderHook(() => useMouseInElement(null as any));
381
+ }).not.toThrow();
382
+ });
383
+
384
+ it("skips update when getClientRects returns empty (no layout)", () => {
385
+ const div = document.createElement("div");
386
+ vi.spyOn(div, "getClientRects").mockReturnValue(
387
+ [] as unknown as DOMRectList,
388
+ );
389
+ const { result } = renderHook(() => useMouseInElement(wrapEl(div) as any));
390
+
391
+ fireMouseMove(50, 50);
392
+
393
+ // No rect → state stays at initial values
394
+ expect(result.current.isOutside$.get()).toBe(true);
395
+ expect(result.current.elementX$.get()).toBe(0);
396
+ expect(result.current.elementY$.get()).toBe(0);
397
+ });
398
+ });
@@ -0,0 +1,209 @@
1
+ import type { Observable } from "@legendapp/state";
2
+ import { useObservable } from "@legendapp/state/react";
3
+ import { useCallback } from "react";
4
+ import { isWindow } from "../../shared";
5
+ import { useMayObservableOptions } from "../../function/useMayObservableOptions";
6
+ import type { DeepMaybeObservable } from "../../types";
7
+ import { type MaybeElement, peekElement } from "../useRef$";
8
+ import { useResizeObserver } from "../useResizeObserver";
9
+ import { useMutationObserver } from "../useMutationObserver";
10
+ import { useEventListener } from "../../browser/useEventListener";
11
+
12
+ export interface UseMouseInElementOptions {
13
+ /** Also update elementX/Y when mouse is outside the element. Default: true */
14
+ handleOutside?: boolean;
15
+ /** Re-calculate on window scroll. Default: true */
16
+ windowScroll?: boolean;
17
+ /** Re-calculate on window resize. Default: true */
18
+ windowResize?: boolean;
19
+ }
20
+
21
+ export interface UseMouseInElementReturn {
22
+ /** Mouse X position relative to the element */
23
+ elementX$: Observable<number>;
24
+ /** Mouse Y position relative to the element */
25
+ elementY$: Observable<number>;
26
+ /** Element's absolute X position on the page */
27
+ elementPositionX$: Observable<number>;
28
+ /** Element's absolute Y position on the page */
29
+ elementPositionY$: Observable<number>;
30
+ /** Element width */
31
+ elementWidth$: Observable<number>;
32
+ /** Element height */
33
+ elementHeight$: Observable<number>;
34
+ /** Whether the mouse is outside the element */
35
+ isOutside$: Observable<boolean>;
36
+ /** Global mouse X (clientX) */
37
+ x$: Observable<number>;
38
+ /** Global mouse Y (clientY) */
39
+ y$: Observable<number>;
40
+ /** Stop all observers and event listeners */
41
+ stop: () => void;
42
+ }
43
+
44
+ // isWindow(window) returns false in SSR (typeof window === "undefined"), true in browser.
45
+ const win = typeof window !== "undefined" ? window : null;
46
+
47
+ /**
48
+ * Tracks whether the mouse cursor is inside a DOM element and calculates
49
+ * the cursor position relative to that element.
50
+ *
51
+ * Observes mousemove, document mouseleave, ResizeObserver, MutationObserver
52
+ * (style/class changes), window scroll, and resize.
53
+ *
54
+ * @param target - Element to observe: Ref$, Observable<OpaqueObject<Element>|null>, Document, Window, or null
55
+ * @param options - Configuration options
56
+ * @returns Reactive mouse position values relative to the element, plus a manual `stop()` function
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * const el$ = useRef$<HTMLDivElement>();
61
+ * const { elementX$, elementY$, isOutside$ } = useMouseInElement(el$);
62
+ * return <div ref={el$} />;
63
+ * ```
64
+ */
65
+ export function useMouseInElement(
66
+ target: MaybeElement,
67
+ options?: DeepMaybeObservable<UseMouseInElementOptions>,
68
+ ): UseMouseInElementReturn {
69
+ // Rule 2: normalize with useMayObservableOptions
70
+ // Rule 3: handleOutside/windowScroll/windowResize are mount-time-only → 'peek'
71
+ const opts$ = useMayObservableOptions<UseMouseInElementOptions>(options, {
72
+ handleOutside: "peek",
73
+ windowScroll: "peek",
74
+ windowResize: "peek",
75
+ });
76
+
77
+ // Global mouse coordinates (exposed in return)
78
+ const mouse$ = useObservable({ x: 0, y: 0 });
79
+
80
+ // Element-relative state
81
+ const state$ = useObservable({
82
+ elementX: 0,
83
+ elementY: 0,
84
+ elementPositionX: 0,
85
+ elementPositionY: 0,
86
+ elementWidth: 0,
87
+ elementHeight: 0,
88
+ isOutside: true,
89
+ });
90
+
91
+ // Recalculate element-relative position from current mouse coords
92
+ const update = useCallback(() => {
93
+ const el = peekElement(target) as HTMLElement | null;
94
+ if (!el || !(el instanceof Element)) return;
95
+
96
+ const rects = Array.from(el.getClientRects());
97
+ if (!rects.length) return;
98
+
99
+ const mx = mouse$.x.peek();
100
+ const my = mouse$.y.peek();
101
+ let found = false;
102
+
103
+ for (const rect of rects) {
104
+ if (
105
+ mx >= rect.left &&
106
+ mx <= rect.right &&
107
+ my >= rect.top &&
108
+ my <= rect.bottom
109
+ ) {
110
+ state$.assign({
111
+ elementX: mx - rect.left,
112
+ elementY: my - rect.top,
113
+ elementPositionX: rect.left + window.scrollX,
114
+ elementPositionY: rect.top + window.scrollY,
115
+ elementWidth: rect.width,
116
+ elementHeight: rect.height,
117
+ isOutside: false,
118
+ });
119
+ found = true;
120
+ break;
121
+ }
122
+ }
123
+
124
+ if (!found) {
125
+ state$.isOutside.set(true);
126
+ if (opts$.handleOutside.peek() !== false) {
127
+ const rect = rects[0];
128
+ state$.assign({
129
+ elementX: mx - rect.left,
130
+ elementY: my - rect.top,
131
+ elementPositionX: rect.left + window.scrollX,
132
+ elementPositionY: rect.top + window.scrollY,
133
+ elementWidth: rect.width,
134
+ elementHeight: rect.height,
135
+ });
136
+ }
137
+ }
138
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
139
+
140
+ // Update global mouse coords then recalculate
141
+ const onMouseMove = useCallback(
142
+ (e: MouseEvent) => {
143
+ mouse$.assign({ x: e.clientX, y: e.clientY });
144
+ update();
145
+ },
146
+ [update], // eslint-disable-line react-hooks/exhaustive-deps
147
+ );
148
+
149
+ // Always call hooks unconditionally — Rules of Hooks.
150
+ // null target → useEventListener registers no listener (no-op).
151
+ // peek() — evaluated once at render time, no reactive subscription needed.
152
+ const stopMouse = useEventListener(
153
+ isWindow(win) ? win : null,
154
+ "mousemove",
155
+ onMouseMove,
156
+ { passive: true },
157
+ );
158
+
159
+ // document mouseleave → force isOutside = true
160
+ const stopLeave = useEventListener(
161
+ typeof document !== "undefined" ? document : null,
162
+ "mouseleave",
163
+ () => state$.isOutside.set(true),
164
+ );
165
+
166
+ const stopScroll = useEventListener(
167
+ isWindow(win) && opts$.windowScroll.peek() !== false ? win : null,
168
+ "scroll",
169
+ update,
170
+ { passive: true },
171
+ );
172
+ const stopResize = useEventListener(
173
+ isWindow(win) && opts$.windowResize.peek() !== false ? win : null,
174
+ "resize",
175
+ update,
176
+ { passive: true },
177
+ );
178
+
179
+ // Observe element size changes
180
+ const { stop: stopRO } = useResizeObserver(target, update);
181
+
182
+ // Observe style/class attribute changes (e.g. CSS transitions, class toggles)
183
+ const { stop: stopMO } = useMutationObserver(target, update, {
184
+ attributes: true,
185
+ attributeFilter: ["style", "class"],
186
+ });
187
+
188
+ const stop = useCallback(() => {
189
+ stopMouse();
190
+ stopLeave();
191
+ stopScroll();
192
+ stopResize();
193
+ stopRO();
194
+ stopMO();
195
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
196
+
197
+ return {
198
+ elementX$: state$.elementX,
199
+ elementY$: state$.elementY,
200
+ elementPositionX$: state$.elementPositionX,
201
+ elementPositionY$: state$.elementPositionY,
202
+ elementWidth$: state$.elementWidth,
203
+ elementHeight$: state$.elementHeight,
204
+ isOutside$: state$.isOutside,
205
+ x$: mouse$.x,
206
+ y$: mouse$.y,
207
+ stop,
208
+ };
209
+ }