@usels/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +83 -0
  11. package/dist/browser/useMediaQuery/demo.js.map +1 -0
  12. package/dist/browser/useMediaQuery/demo.mjs +63 -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 +87 -0
  41. package/dist/elements/useElementBounding/demo.js.map +1 -0
  42. package/dist/elements/useElementBounding/demo.mjs +67 -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 +83 -0
  53. package/dist/elements/useElementSize/demo.js.map +1 -0
  54. package/dist/elements/useElementSize/demo.mjs +63 -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 +104 -0
  89. package/dist/elements/useMouseInElement/demo.js.map +1 -0
  90. package/dist/elements/useMouseInElement/demo.mjs +84 -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 +104 -0
  143. package/dist/elements/useWindowFocus/demo.js.map +1 -0
  144. package/dist/elements/useWindowFocus/demo.mjs +84 -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 +79 -0
  155. package/dist/elements/useWindowSize/demo.js.map +1 -0
  156. package/dist/elements/useWindowSize/demo.mjs +59 -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 +122 -0
  203. package/dist/sensors/useScroll/demo.js.map +1 -0
  204. package/dist/sensors/useScroll/demo.mjs +102 -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 +85 -0
  215. package/dist/sensors/useWindowScroll/demo.js.map +1 -0
  216. package/dist/sensors/useWindowScroll/demo.mjs +65 -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 +63 -0
  259. package/src/browser/useMediaQuery/index.md +43 -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 +54 -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 +68 -0
  268. package/src/elements/useElementBounding/index.md +64 -0
  269. package/src/elements/useElementBounding/index.ts +159 -0
  270. package/src/elements/useElementSize/demo.tsx +53 -0
  271. package/src/elements/useElementSize/index.md +65 -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 +88 -0
  284. package/src/elements/useMouseInElement/index.md +76 -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 +62 -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 +79 -0
  304. package/src/elements/useWindowFocus/index.md +38 -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 +51 -0
  308. package/src/elements/useWindowSize/index.md +55 -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 +43 -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 +103 -0
  326. package/src/sensors/useScroll/index.md +117 -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 +78 -0
  330. package/src/sensors/useWindowScroll/index.md +98 -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,201 @@
1
+ "use client";
2
+ import type { Observable } from "@legendapp/state";
3
+ import { useMount, useObservable } from "@legendapp/state/react";
4
+ import { useMemo, useRef } from "react";
5
+ import { throttle } from "es-toolkit";
6
+ import { type MaybeElement, peekElement } from "../../elements/useRef$";
7
+ import { isWindow } from "../../shared";
8
+ import type { MaybeObservable } from "../../types";
9
+ import { useEventListener } from "../../browser/useEventListener";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Types
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export interface UseScrollOptions {
16
+ throttle?: number;
17
+ idle?: number;
18
+ onStop?: () => void;
19
+ onError?: (error: unknown) => void;
20
+ offset?: {
21
+ left?: number;
22
+ right?: number;
23
+ top?: number;
24
+ bottom?: number;
25
+ };
26
+ behavior?: ScrollBehavior;
27
+ eventListenerOptions?: MaybeObservable<AddEventListenerOptions>;
28
+ }
29
+
30
+ export interface ArrivedState {
31
+ left: boolean;
32
+ right: boolean;
33
+ top: boolean;
34
+ bottom: boolean;
35
+ }
36
+
37
+ export interface ScrollDirections {
38
+ left: boolean;
39
+ right: boolean;
40
+ top: boolean;
41
+ bottom: boolean;
42
+ }
43
+
44
+ export interface UseScrollReturn {
45
+ x: Observable<number>;
46
+ y: Observable<number>;
47
+ isScrolling: Observable<boolean>;
48
+ arrivedState: Observable<ArrivedState>;
49
+ directions: Observable<ScrollDirections>;
50
+ measure: () => void;
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Helpers
55
+ // ---------------------------------------------------------------------------
56
+
57
+ function getScrollValues(el: HTMLElement | Document | Window | null): {
58
+ x: number;
59
+ y: number;
60
+ } {
61
+ if (!el) return { x: 0, y: 0 };
62
+ if (isWindow(el)) return { x: el.scrollX, y: el.scrollY };
63
+ if (el instanceof Document)
64
+ return {
65
+ x: el.documentElement.scrollLeft,
66
+ y: el.documentElement.scrollTop,
67
+ };
68
+ return { x: el.scrollLeft, y: el.scrollTop };
69
+ }
70
+
71
+ function getScrollDimensions(el: HTMLElement | Document | Window | null): {
72
+ scrollW: number;
73
+ scrollH: number;
74
+ clientW: number;
75
+ clientH: number;
76
+ } {
77
+ if (!el || isWindow(el)) {
78
+ return {
79
+ scrollW: document.documentElement.scrollWidth,
80
+ scrollH: document.documentElement.scrollHeight,
81
+ clientW: window.innerWidth,
82
+ clientH: window.innerHeight,
83
+ };
84
+ }
85
+ if (el instanceof Document) {
86
+ const root = el.documentElement;
87
+ return {
88
+ scrollW: root.scrollWidth,
89
+ scrollH: root.scrollHeight,
90
+ clientW: root.clientWidth,
91
+ clientH: root.clientHeight,
92
+ };
93
+ }
94
+ return {
95
+ scrollW: el.scrollWidth,
96
+ scrollH: el.scrollHeight,
97
+ clientW: el.clientWidth,
98
+ clientH: el.clientHeight,
99
+ };
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Hook
104
+ // ---------------------------------------------------------------------------
105
+
106
+ export function useScroll(
107
+ element: MaybeElement,
108
+ options?: UseScrollOptions,
109
+ ): UseScrollReturn {
110
+ const initial = getScrollValues(peekElement(element));
111
+
112
+ const x$ = useObservable<number>(initial.x);
113
+ const y$ = useObservable<number>(initial.y);
114
+ const isScrolling$ = useObservable<boolean>(false);
115
+ const arrivedState$ = useObservable<ArrivedState>({
116
+ left: true,
117
+ right: false,
118
+ top: true,
119
+ bottom: false,
120
+ });
121
+ const directions$ = useObservable<ScrollDirections>({
122
+ left: false,
123
+ right: false,
124
+ top: false,
125
+ bottom: false,
126
+ });
127
+
128
+ const idleTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
129
+
130
+ const resetIdle = () => {
131
+ if (idleTimer.current) clearTimeout(idleTimer.current);
132
+ isScrolling$.set(true);
133
+ idleTimer.current = setTimeout(() => {
134
+ isScrolling$.set(false);
135
+ options?.onStop?.();
136
+ }, options?.idle ?? 200);
137
+ };
138
+
139
+ const measure = () => {
140
+ const el = peekElement(element);
141
+ if (!el) return;
142
+
143
+ const prevX = x$.peek();
144
+ const prevY = y$.peek();
145
+ const { x: newX, y: newY } = getScrollValues(el);
146
+
147
+ directions$.assign({
148
+ left: newX < prevX,
149
+ right: newX > prevX,
150
+ top: newY < prevY,
151
+ bottom: newY > prevY,
152
+ });
153
+
154
+ x$.set(newX);
155
+ y$.set(newY);
156
+
157
+ const { scrollW, scrollH, clientW, clientH } = getScrollDimensions(el);
158
+ const maxX = scrollW - clientW;
159
+ const maxY = scrollH - clientH;
160
+ const offset = options?.offset ?? {};
161
+
162
+ arrivedState$.assign({
163
+ left: newX <= (offset.left ?? 0),
164
+ right: newX >= maxX - (offset.right ?? 0),
165
+ top: newY <= (offset.top ?? 0),
166
+ bottom: newY >= maxY - (offset.bottom ?? 0),
167
+ });
168
+
169
+ resetIdle();
170
+ };
171
+
172
+ const measureRef = useRef(measure);
173
+ measureRef.current = measure;
174
+
175
+ const handler = useMemo(() => {
176
+ const ms = options?.throttle ?? 0;
177
+ return ms > 0
178
+ ? throttle(() => measureRef.current(), ms)
179
+ : () => measureRef.current();
180
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
181
+
182
+ useEventListener(element as any, "scroll", handler,
183
+ options?.eventListenerOptions ?? { capture: false, passive: true },
184
+ );
185
+
186
+ useMount(() => {
187
+ measure();
188
+ return () => {
189
+ if (idleTimer.current) clearTimeout(idleTimer.current);
190
+ };
191
+ });
192
+
193
+ return {
194
+ x: x$,
195
+ y: y$,
196
+ isScrolling: isScrolling$,
197
+ arrivedState: arrivedState$,
198
+ directions: directions$,
199
+ measure,
200
+ };
201
+ }
@@ -0,0 +1,78 @@
1
+ import { Computed } from "@legendapp/state/react";
2
+ import { useWindowScroll } from ".";
3
+
4
+ const card: React.CSSProperties = {
5
+ display: "flex",
6
+ flexWrap: "wrap",
7
+ gap: "16px",
8
+ padding: "10px 14px",
9
+ borderRadius: "6px",
10
+ border: "1px solid var(--sl-color-gray-5, #e2e8f0)",
11
+ background: "var(--sl-color-gray-6, #f8fafc)",
12
+ fontFamily: "monospace",
13
+ fontSize: "13px",
14
+ };
15
+
16
+ const chip = (active: boolean): React.CSSProperties => ({
17
+ padding: "2px 8px",
18
+ borderRadius: "4px",
19
+ background: active
20
+ ? "var(--sl-color-green-low, #f0fdf4)"
21
+ : "var(--sl-color-gray-5, #f1f5f9)",
22
+ color: active
23
+ ? "var(--sl-color-green, #16a34a)"
24
+ : "var(--sl-color-gray-3, #64748b)",
25
+ border: `1px solid ${active ? "var(--sl-color-green, #22c55e)" : "var(--sl-color-gray-5, #e2e8f0)"}`,
26
+ transition: "all 0.15s",
27
+ });
28
+
29
+ export default function UseWindowScrollDemo() {
30
+ const { x, y, isScrolling, arrivedState, directions } = useWindowScroll();
31
+
32
+ return (
33
+ <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
34
+ <Computed>
35
+ {() => (
36
+ <div style={card}>
37
+ <span>
38
+ x: <strong>{x.get()}</strong>
39
+ </span>
40
+ <span>
41
+ y: <strong>{y.get()}</strong>
42
+ </span>
43
+ <span style={chip(isScrolling.get())}>
44
+ {isScrolling.get() ? "scrolling" : "idle"}
45
+ </span>
46
+ </div>
47
+ )}
48
+ </Computed>
49
+
50
+ <Computed>
51
+ {() => (
52
+ <div style={card}>
53
+ <span style={chip(arrivedState.top.get())}>top</span>
54
+ <span style={chip(arrivedState.bottom.get())}>bottom</span>
55
+ <span style={chip(arrivedState.left.get())}>left</span>
56
+ <span style={chip(arrivedState.right.get())}>right</span>
57
+ <span style={{ color: "var(--sl-color-gray-3, #94a3b8)" }}>
58
+ {directions.top.get() && "↑ "}
59
+ {directions.bottom.get() && "↓ "}
60
+ {directions.left.get() && "← "}
61
+ {directions.right.get() && "→ "}
62
+ </span>
63
+ </div>
64
+ )}
65
+ </Computed>
66
+
67
+ <p
68
+ style={{
69
+ margin: 0,
70
+ fontSize: "11px",
71
+ color: "var(--sl-color-gray-3, #94a3b8)",
72
+ }}
73
+ >
74
+ Scroll the page to see the values update in real time.
75
+ </p>
76
+ </div>
77
+ );
78
+ }
@@ -0,0 +1,98 @@
1
+ ---
2
+ title: useWindowScroll
3
+ category: sensors
4
+ ---
5
+
6
+ Tracks the window scroll position, direction, arrived state, and scrolling status as reactive `Observable` values. A convenience wrapper around `useScroll(window)`.
7
+
8
+ ## Demo
9
+
10
+ ## Usage
11
+
12
+ ### Basic
13
+
14
+ ```tsx twoslash
15
+ // @noErrors
16
+ import { useWindowScroll } from '@usels/core'
17
+ import { Computed } from '@legendapp/state/react'
18
+
19
+ function Component() {
20
+ const { x, y, arrivedState } = useWindowScroll()
21
+
22
+ return (
23
+ <Computed>
24
+ {() => (
25
+ <p>
26
+ scrollX: {x.get()}, scrollY: {y.get()}
27
+ {arrivedState.bottom.get() && ' — reached bottom'}
28
+ </p>
29
+ )}
30
+ </Computed>
31
+ )
32
+ }
33
+ ```
34
+
35
+ ### Back-to-top button
36
+
37
+ ```tsx twoslash
38
+ // @noErrors
39
+ import { useWindowScroll } from '@usels/core'
40
+ import { Computed } from '@legendapp/state/react'
41
+
42
+ function BackToTop() {
43
+ const { arrivedState } = useWindowScroll()
44
+
45
+ return (
46
+ <Computed>
47
+ {() =>
48
+ !arrivedState.top.get() ? (
49
+ <button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
50
+ ↑ Back to top
51
+ </button>
52
+ ) : null
53
+ }
54
+ </Computed>
55
+ )
56
+ }
57
+ ```
58
+
59
+ ### Scroll direction indicator
60
+
61
+ ```tsx
62
+ const { directions } = useWindowScroll()
63
+ // directions.bottom.get() → true while scrolling down
64
+ // directions.top.get() → true while scrolling up
65
+ ```
66
+
67
+ ### Infinite scroll trigger
68
+
69
+ ```typescript
70
+ import { useObserveEffect } from '@legendapp/state/react'
71
+
72
+ const { arrivedState } = useWindowScroll({ offset: { bottom: 200 } })
73
+
74
+ useObserveEffect(arrivedState.bottom, (e) => {
75
+ if (e.value) fetchNextPage()
76
+ })
77
+ ```
78
+
79
+ ### isScrolling + onStop
80
+
81
+ ```typescript
82
+ const { isScrolling } = useWindowScroll({
83
+ idle: 300,
84
+ onStop: () => saveScrollPosition(),
85
+ })
86
+ ```
87
+
88
+ ### Throttled updates
89
+
90
+ ```typescript
91
+ const { y } = useWindowScroll({ throttle: 100 })
92
+ ```
93
+
94
+ ## Notes
95
+
96
+ **SSR-safe.** When `window` is not available (SSR), `useWindowScroll` passes `null` to `useScroll`, so all observables hold initial values (`x: 0`, `y: 0`, `isScrolling: false`) and no event listener is registered.
97
+
98
+ See [`useScroll`](/sensors/use-scroll) for the full API reference including all options.
@@ -0,0 +1,69 @@
1
+ // @vitest-environment jsdom
2
+ import { renderHook } from "@testing-library/react";
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
4
+ import { useWindowScroll } from ".";
5
+ import * as useScrollModule from "../useScroll";
6
+
7
+ describe("useWindowScroll()", () => {
8
+ beforeEach(() => {
9
+ vi.useFakeTimers();
10
+ Object.defineProperty(window, "scrollX", {
11
+ writable: true,
12
+ configurable: true,
13
+ value: 0,
14
+ });
15
+ Object.defineProperty(window, "scrollY", {
16
+ writable: true,
17
+ configurable: true,
18
+ value: 0,
19
+ });
20
+ Object.defineProperty(window, "innerWidth", {
21
+ writable: true,
22
+ configurable: true,
23
+ value: 1024,
24
+ });
25
+ Object.defineProperty(window, "innerHeight", {
26
+ writable: true,
27
+ configurable: true,
28
+ value: 768,
29
+ });
30
+ Object.defineProperty(document.documentElement, "scrollWidth", {
31
+ writable: true,
32
+ configurable: true,
33
+ value: 1024,
34
+ });
35
+ Object.defineProperty(document.documentElement, "scrollHeight", {
36
+ writable: true,
37
+ configurable: true,
38
+ value: 2000,
39
+ });
40
+ });
41
+
42
+ afterEach(() => {
43
+ vi.useRealTimers();
44
+ vi.restoreAllMocks();
45
+ });
46
+
47
+ it("calls useScroll internally with window as target", () => {
48
+ const spy = vi.spyOn(useScrollModule, "useScroll");
49
+ renderHook(() => useWindowScroll());
50
+ expect(spy).toHaveBeenCalledWith(window, undefined);
51
+ });
52
+
53
+ it("options are forwarded to useScroll as-is", () => {
54
+ const spy = vi.spyOn(useScrollModule, "useScroll");
55
+ const options = { idle: 500, throttle: 100 };
56
+ renderHook(() => useWindowScroll(options));
57
+ expect(spy).toHaveBeenCalledWith(window, options);
58
+ });
59
+
60
+ it("returns an object matching UseScrollReturn shape", () => {
61
+ const { result } = renderHook(() => useWindowScroll());
62
+ expect(typeof result.current.x.get).toBe("function");
63
+ expect(typeof result.current.y.get).toBe("function");
64
+ expect(typeof result.current.isScrolling.get).toBe("function");
65
+ expect(typeof result.current.arrivedState.get).toBe("function");
66
+ expect(typeof result.current.directions.get).toBe("function");
67
+ expect(typeof result.current.measure).toBe("function");
68
+ });
69
+ });
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { type UseScrollOptions, type UseScrollReturn, useScroll } from "../useScroll";
3
+
4
+ export type { UseScrollOptions, UseScrollReturn };
5
+
6
+ export function useWindowScroll(options?: UseScrollOptions): UseScrollReturn {
7
+ return useScroll(
8
+ typeof window !== "undefined" ? window : null,
9
+ options,
10
+ );
11
+ }
@@ -0,0 +1,35 @@
1
+ import { isClient } from "./utils";
2
+
3
+ export interface ConfigurableWindow {
4
+ window?: Window;
5
+ }
6
+
7
+ export interface ConfigurableDocument {
8
+ document?: Document;
9
+ }
10
+
11
+ export interface ConfigurableDocumentOrShadowRoot {
12
+ document?: DocumentOrShadowRoot;
13
+ }
14
+
15
+ export interface ConfigurableNavigator {
16
+ navigator?: Navigator;
17
+ }
18
+
19
+ export interface ConfigurableLocation {
20
+ location?: Location;
21
+ }
22
+
23
+ export const defaultWindow = /* #__PURE__ */ isClient ? window : undefined;
24
+
25
+ export const defaultDocument = /* #__PURE__ */ isClient
26
+ ? window.document
27
+ : undefined;
28
+
29
+ export const defaultNavigator = /* #__PURE__ */ isClient
30
+ ? window.navigator
31
+ : undefined;
32
+
33
+ export const defaultLocation = /* #__PURE__ */ isClient
34
+ ? window.location
35
+ : undefined;
@@ -0,0 +1,4 @@
1
+ /** typeof window 우선 검사 — instanceof Window는 jsdom 환경에서 실패할 수 있음 */
2
+ export function isWindow(el: unknown): el is Window {
3
+ return typeof window !== "undefined" && el === window;
4
+ }
@@ -0,0 +1,76 @@
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 } from "vitest";
6
+ import { useRef$ } from "../../elements/useRef$";
7
+ import type { Ref$ } from "../../elements/useRef$";
8
+ import { normalizeTargets } from ".";
9
+
10
+ const wrapEl = (el: Element) => observable<OpaqueObject<Element> | null>(ObservableHint.opaque(el));
11
+
12
+ describe("normalizeTargets()", () => {
13
+ it("returns empty array for null plain value", () => {
14
+ expect(normalizeTargets(null)).toEqual([]);
15
+ });
16
+
17
+ it("returns empty array for undefined", () => {
18
+ expect(normalizeTargets(undefined)).toEqual([]);
19
+ });
20
+
21
+ it("returns empty array for empty array input", () => {
22
+ expect(normalizeTargets([])).toEqual([]);
23
+ });
24
+
25
+ it("returns element for Observable<OpaqueObject<Element>> input", () => {
26
+ const div = document.createElement("div");
27
+ expect(normalizeTargets(wrapEl(div))).toEqual([div]);
28
+ });
29
+
30
+ it("returns elements for array of Observable<OpaqueObject<Element>>", () => {
31
+ const a = document.createElement("div");
32
+ const b = document.createElement("span");
33
+ expect(normalizeTargets([wrapEl(a), wrapEl(b)])).toEqual([a, b]);
34
+ });
35
+
36
+ it("unwraps Observable<OpaqueObject<Element>>", () => {
37
+ const div = document.createElement("div");
38
+ expect(normalizeTargets(wrapEl(div))).toEqual([div]);
39
+ });
40
+
41
+ it("filters out Observable<null>", () => {
42
+ const obs = observable<ReturnType<typeof ObservableHint.opaque<Element>> | null>(null);
43
+ expect(normalizeTargets(obs)).toEqual([]);
44
+ });
45
+
46
+ it("unwraps Ref$ target and returns the raw DOM element", () => {
47
+ const div = document.createElement("div");
48
+ const { result } = renderHook(() => useRef$<HTMLDivElement>());
49
+
50
+ act(() => result.current(div));
51
+
52
+ const elements = normalizeTargets(result.current as Ref$<Element>);
53
+ expect(elements).toEqual([div]);
54
+ });
55
+
56
+ it("returns empty array when Ref$ has no element assigned", () => {
57
+ const { result } = renderHook(() => useRef$<HTMLDivElement>());
58
+
59
+ const elements = normalizeTargets(result.current as Ref$<Element>);
60
+ expect(elements).toEqual([]);
61
+ });
62
+
63
+ it("handles mixed array of Ref$, Observable, and wrapped Element", () => {
64
+ const div = document.createElement("div");
65
+ const span = document.createElement("span");
66
+ const p = document.createElement("p");
67
+
68
+ const { result } = renderHook(() => useRef$<HTMLDivElement>());
69
+ act(() => result.current(div));
70
+
71
+ const obs = wrapEl(span);
72
+
73
+ const elements = normalizeTargets([result.current as Ref$<Element>, obs, wrapEl(p)]);
74
+ expect(elements).toEqual([div, span, p]);
75
+ });
76
+ });
@@ -0,0 +1,27 @@
1
+ import { getElement, type MaybeElement } from "../../elements/useRef$";
2
+
3
+ /**
4
+ * Normalizes one or more observable-element targets into a plain Element[].
5
+ *
6
+ * - `Ref$<T>` — calls `.get()` and unwraps the OpaqueObject via `.valueOf()`
7
+ * - `Observable<OpaqueObject<Element> | null>` — calls `.get()` and unwraps via `.valueOf()`
8
+ * - `Document` / `Window` — filtered out (not an Element, handled by callers)
9
+ * - `null` — filtered out
10
+ *
11
+ * When called inside `useObserve`, reading `.get()` registers observable
12
+ * dependencies so the observer re-fires when a tracked target changes.
13
+ *
14
+ * Currently shared by useResizeObserver.
15
+ * useIntersectionObserver, useMutationObserver will use this once implemented.
16
+ */
17
+ export function normalizeTargets(
18
+ target?: MaybeElement | MaybeElement[],
19
+ ): Element[] {
20
+ if (target == null) return [];
21
+ const arr = Array.isArray(target) ? target : [target];
22
+ return arr
23
+ .map((t) => {
24
+ return getElement(t) as any;
25
+ })
26
+ .filter((el): el is Element => el != null);
27
+ }
@@ -0,0 +1,67 @@
1
+ // ---------------------------------------------------------------------------
2
+ // TIER 0-B: Shared pure utility functions (VueUse equivalents, no dependencies)
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export const isClient =
6
+ typeof window !== "undefined" && typeof document !== "undefined";
7
+
8
+ export const noop = () => {};
9
+
10
+ export const clamp = (n: number, min: number, max: number) =>
11
+ Math.min(max, Math.max(min, n));
12
+
13
+ export const isDef = <T>(val?: T): val is T => typeof val !== "undefined";
14
+
15
+ export const notNullish = <T>(val?: T | null): val is T => val != null;
16
+
17
+ export const isObject = (val: unknown): val is object =>
18
+ Object.prototype.toString.call(val) === "[object Object]";
19
+
20
+ export const isIOS =
21
+ /* #__PURE__ */ isClient &&
22
+ /iPad|iPhone|iPod/.test(navigator.userAgent) &&
23
+ !(window as any).MSStream;
24
+
25
+ export function toArray<T>(v: T | T[]): T[] {
26
+ return Array.isArray(v) ? v : [v];
27
+ }
28
+
29
+ export function objectPick<O extends object, T extends keyof O>(
30
+ obj: O,
31
+ keys: T[],
32
+ ): Pick<O, T> {
33
+ return keys.reduce(
34
+ (n, k) => {
35
+ if (k in obj) n[k] = obj[k];
36
+ return n;
37
+ },
38
+ {} as Pick<O, T>,
39
+ );
40
+ }
41
+
42
+ export function objectOmit<O extends object, T extends keyof O>(
43
+ obj: O,
44
+ keys: T[],
45
+ ): Omit<O, T> {
46
+ return Object.fromEntries(
47
+ Object.entries(obj).filter(([k]) => !keys.includes(k as T)),
48
+ ) as Omit<O, T>;
49
+ }
50
+
51
+ export function promiseTimeout(ms: number, throwOnTimeout = false): Promise<void> {
52
+ return new Promise((resolve, reject) =>
53
+ throwOnTimeout
54
+ ? setTimeout(() => reject(new Error(`Timeout ${ms}ms`)), ms)
55
+ : setTimeout(resolve, ms),
56
+ );
57
+ }
58
+
59
+ export function increaseWithUnit(target: number, delta: number): number;
60
+ export function increaseWithUnit(target: string, delta: number): string;
61
+ export function increaseWithUnit(target: number | string, delta: number): number | string {
62
+ if (typeof target === "number") return target + delta;
63
+ const match = target.match(/^(-?\d*\.?\d+)([a-z%]*)$/i);
64
+ if (!match) return target;
65
+ const [, num, unit] = match;
66
+ return `${Number(num) + delta}${unit}`;
67
+ }