@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,159 @@
1
+ import type { Observable } from "@legendapp/state";
2
+ import { useObservable } from "@legendapp/state/react";
3
+ import { useCallback, useEffect, useRef } from "react";
4
+ import { type MaybeElement, peekElement } from "../useRef$";
5
+ import { useResizeObserver } from "../useResizeObserver";
6
+ import { useMutationObserver } from "../useMutationObserver";
7
+ import { useEventListener } from "../../browser/useEventListener";
8
+ import { isWindow } from "../../shared";
9
+ import { useMayObservableOptions } from "../../function/useMayObservableOptions";
10
+ import type { DeepMaybeObservable } from "../../types";
11
+
12
+ export interface UseElementBoundingOptions {
13
+ /** Reset all values to 0 when element unmounts. Default: true */
14
+ reset?: boolean;
15
+ /** Re-calculate on window resize. Default: true */
16
+ windowResize?: boolean;
17
+ /** Re-calculate on window scroll. Default: true */
18
+ windowScroll?: boolean;
19
+ /** Calculate immediately on mount. Default: true */
20
+ immediate?: boolean;
21
+ /** Use requestAnimationFrame to read rect after CSS transforms settle. Default: true */
22
+ useCssTransforms?: boolean;
23
+ }
24
+
25
+ export interface UseElementBoundingReturn {
26
+ x$: Observable<number>;
27
+ y$: Observable<number>;
28
+ top$: Observable<number>;
29
+ right$: Observable<number>;
30
+ bottom$: Observable<number>;
31
+ left$: Observable<number>;
32
+ width$: Observable<number>;
33
+ height$: Observable<number>;
34
+ update: () => void;
35
+ }
36
+
37
+ const ZERO = {
38
+ x: 0,
39
+ y: 0,
40
+ top: 0,
41
+ right: 0,
42
+ bottom: 0,
43
+ left: 0,
44
+ width: 0,
45
+ height: 0,
46
+ };
47
+
48
+ // isWindow(window) returns false in SSR (typeof window === "undefined"), true in browser.
49
+ const win = typeof window !== "undefined" ? window : null;
50
+
51
+ /**
52
+ * Tracks the bounding rect of a DOM element (x, y, top, right, bottom, left, width, height).
53
+ * Observes ResizeObserver, MutationObserver (style/class changes), window scroll, and resize.
54
+ *
55
+ * @param target - Element to observe: Ref$, Observable<OpaqueObject<Element>|null>, Document, Window, or null
56
+ * @param options - Configuration options
57
+ * @returns Reactive bounding rect values plus a manual `update()` function
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * const el$ = useRef$<HTMLDivElement>();
62
+ * const { top$, left$, width$, height$ } = useElementBounding(el$);
63
+ * return <div ref={el$} />;
64
+ * ```
65
+ */
66
+ export function useElementBounding(
67
+ target: MaybeElement,
68
+ options?: DeepMaybeObservable<UseElementBoundingOptions>,
69
+ ): UseElementBoundingReturn {
70
+ const opts$ = useMayObservableOptions<UseElementBoundingOptions>(options, {
71
+ immediate: "peek",
72
+ });
73
+
74
+ const bounding$ = useObservable({ ...ZERO });
75
+
76
+ // Guards rAF callbacks from updating state after unmount.
77
+ const unmountedRef = useRef(false);
78
+ const rafRef = useRef<number | null>(null);
79
+
80
+ const recalculate = useCallback(() => {
81
+ const el = peekElement(target) as Element | null;
82
+ if (!el || !(el instanceof Element)) {
83
+ if (opts$.reset.peek() !== false) bounding$.assign({ ...ZERO });
84
+ return;
85
+ }
86
+ const rect = el.getBoundingClientRect();
87
+ bounding$.assign({
88
+ x: rect.x,
89
+ y: rect.y,
90
+ top: rect.top,
91
+ right: rect.right,
92
+ bottom: rect.bottom,
93
+ left: rect.left,
94
+ width: rect.width,
95
+ height: rect.height,
96
+ });
97
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
98
+
99
+ const update = useCallback(() => {
100
+ if (opts$.useCssTransforms.peek() !== false) {
101
+ rafRef.current = requestAnimationFrame(() => {
102
+ if (!unmountedRef.current) recalculate();
103
+ });
104
+ } else {
105
+ recalculate();
106
+ }
107
+ }, [recalculate]); // eslint-disable-line react-hooks/exhaustive-deps
108
+
109
+ // Observe size changes
110
+ useResizeObserver(target, update);
111
+
112
+ // Observe style/class attribute changes (e.g. CSS transitions, class toggles)
113
+ useMutationObserver(target, update, {
114
+ attributes: true,
115
+ attributeFilter: ["style", "class"],
116
+ });
117
+
118
+ // Observe window scroll / resize (always call hooks unconditionally — Rules of Hooks)
119
+ // isWindow(win) is false in SSR, so target becomes null outside the browser.
120
+ // peek() — evaluated once at render time, no reactive subscription needed.
121
+ useEventListener(
122
+ isWindow(win) && opts$.windowScroll.peek() !== false ? win : null,
123
+ "scroll",
124
+ update,
125
+ { passive: true },
126
+ );
127
+ useEventListener(
128
+ isWindow(win) && opts$.windowResize.peek() !== false ? win : null,
129
+ "resize",
130
+ update,
131
+ { passive: true },
132
+ );
133
+
134
+ // eslint-disable-next-line react-hooks/exhaustive-deps
135
+ useEffect(() => {
136
+ unmountedRef.current = false;
137
+ if (opts$.immediate.peek() !== false) update();
138
+ return () => {
139
+ unmountedRef.current = true;
140
+ if (rafRef.current !== null) {
141
+ cancelAnimationFrame(rafRef.current);
142
+ rafRef.current = null;
143
+ }
144
+ if (opts$.reset.peek() !== false) bounding$.assign({ ...ZERO });
145
+ };
146
+ }, []);
147
+
148
+ return {
149
+ x$: bounding$.x,
150
+ y$: bounding$.y,
151
+ top$: bounding$.top,
152
+ right$: bounding$.right,
153
+ bottom$: bounding$.bottom,
154
+ left$: bounding$.left,
155
+ width$: bounding$.width,
156
+ height$: bounding$.height,
157
+ update,
158
+ };
159
+ }
@@ -0,0 +1,48 @@
1
+ import { useRef$ } from "../useRef$";
2
+ import { useElementSize } from ".";
3
+
4
+ export default function UseElementSizeDemo() {
5
+ const el$ = useRef$<HTMLTextAreaElement>();
6
+ const { width$, height$ } = useElementSize(el$);
7
+
8
+ return (
9
+ <div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
10
+ <div
11
+ style={{
12
+ display: "flex",
13
+ gap: "24px",
14
+ fontFamily: "monospace",
15
+ fontSize: "14px",
16
+ padding: "8px 12px",
17
+ background: "var(--sl-color-gray-6, #f1f5f9)",
18
+ borderRadius: "6px",
19
+ }}
20
+ >
21
+ <>
22
+ <span>
23
+ width: <strong>{Math.round(width$.get())}px</strong>
24
+ </span>
25
+ <span>
26
+ height: <strong>{Math.round(height$.get())}px</strong>
27
+ </span>
28
+ </>
29
+ </div>
30
+ <textarea
31
+ ref={el$}
32
+ defaultValue="Resize this textarea to see width & height update"
33
+ style={{
34
+ resize: "both",
35
+ overflow: "auto",
36
+ width: "300px",
37
+ height: "120px",
38
+ padding: "10px",
39
+ border: "1px solid var(--sl-color-gray-5, #cbd5e1)",
40
+ borderRadius: "6px",
41
+ fontFamily: "inherit",
42
+ fontSize: "14px",
43
+ lineHeight: "1.5",
44
+ }}
45
+ />
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,60 @@
1
+ ---
2
+ title: useElementSize
3
+ category: elements
4
+ ---
5
+
6
+ Tracks the width and height of a DOM element using the [ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
7
+ Returns reactive `Observable<number>` values that update whenever the element resizes.
8
+ SVG elements use `getBoundingClientRect()` as a fallback. Supports all three box models.
9
+
10
+ ## Demo
11
+
12
+ ## Usage
13
+
14
+ ```tsx twoslash
15
+ // @noErrors
16
+ import { useRef$, useElementSize } from '@usels/core'
17
+
18
+ function Component() {
19
+ const el$ = useRef$<HTMLDivElement>()
20
+ const { width$, height$ } = useElementSize(el$)
21
+
22
+ return (
23
+ <div ref={el$}>
24
+ {width$.get()} × {height$.get()}
25
+ </div>
26
+ )
27
+ }
28
+ ```
29
+
30
+ ### Custom initial size
31
+
32
+ ```tsx twoslash
33
+ // @noErrors
34
+ import { useRef$, Ref$, useElementSize } from '@usels/core'
35
+ declare const el$: Ref$<HTMLDivElement>
36
+ // ---cut---
37
+ const { width$, height$ } = useElementSize(el$, { width: 320, height: 240 })
38
+ ```
39
+
40
+ ### With `border-box`
41
+
42
+ ```tsx twoslash
43
+ // @noErrors
44
+ import { useRef$, Ref$, useElementSize } from '@usels/core'
45
+ declare const el$: Ref$<HTMLDivElement>
46
+ // ---cut---
47
+ const { width$, height$ } = useElementSize(el$, undefined, { box: 'border-box' })
48
+ ```
49
+
50
+ ### Stopping observation manually
51
+
52
+ ```tsx twoslash
53
+ // @noErrors
54
+ import { useRef$, Ref$, useElementSize } from '@usels/core'
55
+ declare const el$: Ref$<HTMLDivElement>
56
+ // ---cut---
57
+ const { width$, height$, stop } = useElementSize(el$)
58
+
59
+ stop()
60
+ ```
@@ -0,0 +1,295 @@
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 { useRef$ } from "../useRef$";
7
+ import { useElementSize } from ".";
8
+
9
+ const wrapEl = (el: Element) =>
10
+ observable<OpaqueObject<Element> | null>(ObservableHint.opaque(el));
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // ResizeObserver mock (extended with box size support)
14
+ // ---------------------------------------------------------------------------
15
+
16
+ type BoxSize = { inlineSize: number; blockSize: number };
17
+
18
+ class ResizeObserverMock {
19
+ static instances: ResizeObserverMock[] = [];
20
+
21
+ callback: ResizeObserverCallback;
22
+ observed: Element[] = [];
23
+ disconnected = false;
24
+
25
+ constructor(cb: ResizeObserverCallback) {
26
+ this.callback = cb;
27
+ ResizeObserverMock.instances.push(this);
28
+ }
29
+
30
+ observe(el: Element) {
31
+ this.observed.push(el);
32
+ }
33
+
34
+ unobserve(el: Element) {
35
+ this.observed = this.observed.filter((e) => e !== el);
36
+ }
37
+
38
+ disconnect() {
39
+ this.disconnected = true;
40
+ this.observed = [];
41
+ }
42
+
43
+ /**
44
+ * Trigger the callback with a configurable fake entry.
45
+ * All box size arrays default to empty (falling back to contentRect).
46
+ */
47
+ trigger(
48
+ el: Element,
49
+ options: {
50
+ contentRect?: Partial<DOMRectReadOnly>;
51
+ contentBoxSize?: BoxSize[];
52
+ borderBoxSize?: BoxSize[];
53
+ devicePixelContentBoxSize?: BoxSize[];
54
+ } = {},
55
+ ) {
56
+ if (this.disconnected) return;
57
+ const entry = {
58
+ target: el,
59
+ contentRect: {
60
+ width: 100,
61
+ height: 200,
62
+ ...options.contentRect,
63
+ } as DOMRectReadOnly,
64
+ contentBoxSize: options.contentBoxSize ?? [],
65
+ borderBoxSize: options.borderBoxSize ?? [],
66
+ devicePixelContentBoxSize: options.devicePixelContentBoxSize ?? [],
67
+ } as unknown as ResizeObserverEntry;
68
+ this.callback([entry], this);
69
+ }
70
+ }
71
+
72
+ beforeEach(() => {
73
+ ResizeObserverMock.instances = [];
74
+ vi.stubGlobal("ResizeObserver", ResizeObserverMock);
75
+ });
76
+
77
+ afterEach(() => {
78
+ vi.unstubAllGlobals();
79
+ });
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // useElementSize
83
+ // ---------------------------------------------------------------------------
84
+
85
+ describe("useElementSize()", () => {
86
+ it("updates width and height on resize event (contentRect fallback)", () => {
87
+ const div = document.createElement("div");
88
+
89
+ const { result } = renderHook(() => useElementSize(wrapEl(div) as any));
90
+
91
+ const instance = ResizeObserverMock.instances.at(-1)!;
92
+ act(() =>
93
+ instance.trigger(div, { contentRect: { width: 320, height: 240 } }),
94
+ );
95
+
96
+ expect(result.current.width$.get()).toBe(320);
97
+ expect(result.current.height$.get()).toBe(240);
98
+ });
99
+
100
+ it("uses getBoundingClientRect() for SVG elements", () => {
101
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
102
+ vi.spyOn(svg, "getBoundingClientRect").mockReturnValue({
103
+ width: 150,
104
+ height: 75,
105
+ top: 0,
106
+ left: 0,
107
+ bottom: 75,
108
+ right: 150,
109
+ x: 0,
110
+ y: 0,
111
+ toJSON: () => ({}),
112
+ });
113
+
114
+ const { result } = renderHook(() => useElementSize(wrapEl(svg) as any));
115
+
116
+ const instance = ResizeObserverMock.instances.at(-1)!;
117
+ act(() => instance.trigger(svg));
118
+
119
+ expect(svg.getBoundingClientRect).toHaveBeenCalled();
120
+ expect(result.current.width$.get()).toBe(150);
121
+ expect(result.current.height$.get()).toBe(75);
122
+ });
123
+
124
+ it("reads borderBoxSize when box option is 'border-box'", () => {
125
+ const div = document.createElement("div");
126
+
127
+ const { result } = renderHook(() =>
128
+ useElementSize(wrapEl(div) as any, undefined, { box: "border-box" }),
129
+ );
130
+
131
+ const instance = ResizeObserverMock.instances.at(-1)!;
132
+ act(() =>
133
+ instance.trigger(div, {
134
+ borderBoxSize: [{ inlineSize: 400, blockSize: 300 }],
135
+ }),
136
+ );
137
+
138
+ expect(result.current.width$.get()).toBe(400);
139
+ expect(result.current.height$.get()).toBe(300);
140
+ });
141
+
142
+ it("reads devicePixelContentBoxSize when box option is 'device-pixel-content-box'", () => {
143
+ const div = document.createElement("div");
144
+
145
+ const { result } = renderHook(() =>
146
+ useElementSize(wrapEl(div) as any, undefined, {
147
+ box: "device-pixel-content-box",
148
+ }),
149
+ );
150
+
151
+ const instance = ResizeObserverMock.instances.at(-1)!;
152
+ act(() =>
153
+ instance.trigger(div, {
154
+ devicePixelContentBoxSize: [{ inlineSize: 800, blockSize: 600 }],
155
+ }),
156
+ );
157
+
158
+ expect(result.current.width$.get()).toBe(800);
159
+ expect(result.current.height$.get()).toBe(600);
160
+ });
161
+
162
+ it("falls back to contentBoxSize when present and no specific box matched", () => {
163
+ const div = document.createElement("div");
164
+
165
+ // default box is "content-box" — reads contentBoxSize
166
+ const { result } = renderHook(() => useElementSize(wrapEl(div) as any));
167
+
168
+ const instance = ResizeObserverMock.instances.at(-1)!;
169
+ act(() =>
170
+ instance.trigger(div, {
171
+ contentBoxSize: [{ inlineSize: 500, blockSize: 250 }],
172
+ }),
173
+ );
174
+
175
+ expect(result.current.width$.get()).toBe(500);
176
+ expect(result.current.height$.get()).toBe(250);
177
+ });
178
+
179
+ it("falls back to contentRect when no boxSize arrays are present", () => {
180
+ const div = document.createElement("div");
181
+
182
+ const { result } = renderHook(() => useElementSize(wrapEl(div) as any));
183
+
184
+ const instance = ResizeObserverMock.instances.at(-1)!;
185
+ act(() =>
186
+ instance.trigger(div, { contentRect: { width: 123, height: 456 } }),
187
+ );
188
+
189
+ expect(result.current.width$.get()).toBe(123);
190
+ expect(result.current.height$.get()).toBe(456);
191
+ });
192
+
193
+ it("returns initialSize when target is null", () => {
194
+ const { result } = renderHook(() =>
195
+ useElementSize(null as any, { width: 10, height: 20 }),
196
+ );
197
+
198
+ expect(result.current.width$.get()).toBe(10);
199
+ expect(result.current.height$.get()).toBe(20);
200
+ });
201
+
202
+ it("returns default initialSize { width: 0, height: 0 } when no initialSize provided and target is null", () => {
203
+ const { result } = renderHook(() => useElementSize(null as any));
204
+
205
+ expect(result.current.width$.get()).toBe(0);
206
+ expect(result.current.height$.get()).toBe(0);
207
+ });
208
+
209
+ it("stop() suppresses further size updates", () => {
210
+ const div = document.createElement("div");
211
+
212
+ const { result } = renderHook(() => useElementSize(wrapEl(div) as any));
213
+
214
+ const instance = ResizeObserverMock.instances.at(-1)!;
215
+
216
+ // Trigger first resize — should update
217
+ act(() =>
218
+ instance.trigger(div, { contentRect: { width: 100, height: 100 } }),
219
+ );
220
+ expect(result.current.width$.get()).toBe(100);
221
+
222
+ // Stop observing
223
+ act(() => result.current.stop());
224
+
225
+ // Trigger again — instance is disconnected, trigger() is a no-op
226
+ act(() =>
227
+ instance.trigger(div, { contentRect: { width: 999, height: 999 } }),
228
+ );
229
+ expect(result.current.width$.get()).toBe(100);
230
+ expect(result.current.height$.get()).toBe(100);
231
+ });
232
+
233
+ it("Ref$ target: sets offsetWidth/offsetHeight as initial size after element is assigned", () => {
234
+ const { result } = renderHook(() => {
235
+ const el$ = useRef$<HTMLDivElement>();
236
+ const size = useElementSize(el$);
237
+ return { el$, size };
238
+ });
239
+
240
+ // Before assignment — initial values
241
+ expect(result.current.size.width$.get()).toBe(0);
242
+ expect(result.current.size.height$.get()).toBe(0);
243
+
244
+ const div = document.createElement("div");
245
+ // jsdom returns 0 for offsetWidth/Height by default, but we can override
246
+ Object.defineProperty(div, "offsetWidth", { value: 640, configurable: true });
247
+ Object.defineProperty(div, "offsetHeight", { value: 480, configurable: true });
248
+
249
+ act(() => result.current.el$(div));
250
+
251
+ expect(result.current.size.width$.get()).toBe(640);
252
+ expect(result.current.size.height$.get()).toBe(480);
253
+ });
254
+
255
+ it("resets to initialSize when target Ref$ becomes null", () => {
256
+ const div = document.createElement("div");
257
+
258
+ const { result } = renderHook(() => {
259
+ const el$ = useRef$<HTMLDivElement>();
260
+ const size = useElementSize(el$, { width: 5, height: 10 });
261
+ return { el$, size };
262
+ });
263
+
264
+ // Assign element first
265
+ act(() => result.current.el$(div));
266
+
267
+ // Then set element to null (passing null simulates unmounting)
268
+ act(() => result.current.el$(null));
269
+
270
+ expect(result.current.size.width$.get()).toBe(5);
271
+ expect(result.current.size.height$.get()).toBe(10);
272
+ });
273
+
274
+ it("accumulates multiple borderBoxSize entries", () => {
275
+ const div = document.createElement("div");
276
+
277
+ const { result } = renderHook(() =>
278
+ useElementSize(wrapEl(div) as any, undefined, { box: "border-box" }),
279
+ );
280
+
281
+ const instance = ResizeObserverMock.instances.at(-1)!;
282
+ act(() =>
283
+ instance.trigger(div, {
284
+ borderBoxSize: [
285
+ { inlineSize: 100, blockSize: 50 },
286
+ { inlineSize: 200, blockSize: 100 },
287
+ ],
288
+ }),
289
+ );
290
+
291
+ // Accumulated: 100+200=300, 50+100=150
292
+ expect(result.current.width$.get()).toBe(300);
293
+ expect(result.current.height$.get()).toBe(150);
294
+ });
295
+ });
@@ -0,0 +1,100 @@
1
+ import type { Observable } from "@legendapp/state";
2
+ import { useObservable, useObserveEffect } from "@legendapp/state/react";
3
+ import { useCallback } from "react";
4
+ import { getElement, MaybeElement } from "../useRef$";
5
+ import { useResizeObserver } from "../useResizeObserver";
6
+
7
+ export interface UseElementSizeOptions {
8
+ box?: "content-box" | "border-box" | "device-pixel-content-box";
9
+ }
10
+
11
+ export interface UseElementSizeReturn {
12
+ width$: Observable<number>;
13
+ height$: Observable<number>;
14
+ stop: () => void;
15
+ }
16
+
17
+ /**
18
+ * Tracks the width and height of a DOM element using ResizeObserver.
19
+ * SVG elements use getBoundingClientRect() as fallback.
20
+ *
21
+ * @param target - Element to observe (Ref$, Observable<Element|null>, Document, Window, or null)
22
+ * @param initialSize - Initial size values (default: { width: 0, height: 0 })
23
+ * @param options - Optional box model option
24
+ * @returns `{ width$, height$, stop }` — reactive size observables and manual stop function
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * const el$ = useRef$<HTMLDivElement>();
29
+ * const { width$, height$ } = useElementSize(el$);
30
+ * return <div ref={el$}>{width$.get()} x {height$.get()}</div>;
31
+ * ```
32
+ */
33
+ export function useElementSize(
34
+ target: MaybeElement,
35
+ initialSize?: { width: number; height: number },
36
+ options?: UseElementSizeOptions,
37
+ ): UseElementSizeReturn {
38
+ const initial = initialSize ?? { width: 0, height: 0 };
39
+ const size$ = useObservable({ width: initial.width, height: initial.height });
40
+
41
+ const onResize = useCallback<ResizeObserverCallback>((entries) => {
42
+ for (const entry of entries) {
43
+ const el = entry.target;
44
+ const isSvg = (el as Element).namespaceURI?.includes("svg");
45
+
46
+ if (isSvg) {
47
+ const rect = el.getBoundingClientRect();
48
+ size$.assign({ width: rect.width, height: rect.height });
49
+ } else {
50
+ const box = options?.box ?? "content-box";
51
+ let w = 0,
52
+ h = 0;
53
+ if (box === "border-box" && entry.borderBoxSize?.length) {
54
+ for (const b of entry.borderBoxSize) {
55
+ w += b.inlineSize;
56
+ h += b.blockSize;
57
+ }
58
+ } else if (
59
+ box === "device-pixel-content-box" &&
60
+ entry.devicePixelContentBoxSize?.length
61
+ ) {
62
+ for (const b of entry.devicePixelContentBoxSize) {
63
+ w += b.inlineSize;
64
+ h += b.blockSize;
65
+ }
66
+ } else if (entry.contentBoxSize?.length) {
67
+ for (const b of entry.contentBoxSize) {
68
+ w += b.inlineSize;
69
+ h += b.blockSize;
70
+ }
71
+ } else {
72
+ w = entry.contentRect.width;
73
+ h = entry.contentRect.height;
74
+ }
75
+ size$.assign({ width: w, height: h });
76
+ }
77
+ }
78
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
79
+
80
+ const { stop } = useResizeObserver(target, onResize, { box: options?.box });
81
+
82
+ // Set initial size from offsetWidth/Height after element mounts.
83
+ // Uses getElement (tracked) so this re-runs when target Ref$ changes.
84
+ useObserveEffect(() => {
85
+ const el = getElement(target) as HTMLElement | null;
86
+ if (!el) {
87
+ size$.assign({ width: initial.width, height: initial.height });
88
+ return;
89
+ }
90
+ const isSvg = (el as Element).namespaceURI?.includes("svg");
91
+ if (!isSvg) {
92
+ size$.assign({
93
+ width: (el as HTMLElement).offsetWidth ?? initial.width,
94
+ height: (el as HTMLElement).offsetHeight ?? initial.height,
95
+ });
96
+ }
97
+ });
98
+
99
+ return { width$: size$.width, height$: size$.height, stop };
100
+ }