@boxcustodia/library 2.0.0-alpha.19 → 2.0.0-alpha.20

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 (265) hide show
  1. package/dist/components/button/button.cjs.js +1 -1
  2. package/dist/components/button/button.es.js +19 -18
  3. package/dist/components/button/components/base-button.cjs.js +1 -1
  4. package/dist/components/button/components/base-button.es.js +20 -20
  5. package/dist/components/calendar/calendar.cjs.js +1 -1
  6. package/dist/components/calendar/calendar.es.js +1 -0
  7. package/dist/components/date-picker/date-input.cjs.js +1 -1
  8. package/dist/components/date-picker/date-input.es.js +92 -75
  9. package/dist/components/date-picker/date-picker.cjs.js +1 -1
  10. package/dist/components/date-picker/date-picker.es.js +104 -95
  11. package/dist/components/date-picker/date-picker.utils.cjs.js +1 -1
  12. package/dist/components/date-picker/date-picker.utils.es.js +51 -43
  13. package/dist/components/date-picker/use-hidden-field-value.cjs.js +1 -0
  14. package/dist/components/date-picker/use-hidden-field-value.es.js +11 -0
  15. package/dist/components/menu/menu.es.js +1 -9
  16. package/dist/components/otp/otp.cjs.js +2 -0
  17. package/dist/components/otp/otp.es.js +93 -0
  18. package/dist/components/password/password.cjs.js +1 -1
  19. package/dist/components/password/password.es.js +2 -2
  20. package/dist/components/select/select.cjs.js +1 -1
  21. package/dist/components/select/select.es.js +68 -60
  22. package/dist/hooks/internal/is-apple-device.cjs.js +1 -0
  23. package/dist/hooks/internal/is-apple-device.es.js +9 -0
  24. package/dist/hooks/internal/use-latest-ref.cjs.js +1 -0
  25. package/dist/hooks/internal/use-latest-ref.es.js +11 -0
  26. package/dist/hooks/use-array/use-array.cjs.js +1 -1
  27. package/dist/hooks/use-array/use-array.es.js +54 -42
  28. package/dist/hooks/use-async/use-async.cjs.js +1 -1
  29. package/dist/hooks/use-async/use-async.es.js +53 -20
  30. package/dist/hooks/use-boolean/use-boolean.cjs.js +1 -0
  31. package/dist/hooks/use-boolean/use-boolean.es.js +25 -0
  32. package/dist/hooks/use-click-outside/use-click-outside.cjs.js +1 -1
  33. package/dist/hooks/use-click-outside/use-click-outside.es.js +26 -12
  34. package/dist/hooks/use-debounce-callback/use-debounced-callback.cjs.js +1 -1
  35. package/dist/hooks/use-debounce-callback/use-debounced-callback.es.js +27 -10
  36. package/dist/hooks/use-debounce-value/use-debounced-value.cjs.js +1 -1
  37. package/dist/hooks/use-debounce-value/use-debounced-value.es.js +7 -9
  38. package/dist/hooks/use-disclosure/use-disclosure.cjs.js +1 -1
  39. package/dist/hooks/use-disclosure/use-disclosure.es.js +21 -11
  40. package/dist/hooks/use-document-title/use-document-title.cjs.js +1 -1
  41. package/dist/hooks/use-document-title/use-document-title.es.js +14 -12
  42. package/dist/hooks/use-event-listener/use-event-listener.cjs.js +1 -1
  43. package/dist/hooks/use-event-listener/use-event-listener.es.js +17 -9
  44. package/dist/hooks/use-hotkey/use-hotkey.cjs.js +1 -1
  45. package/dist/hooks/use-hotkey/use-hotkey.es.js +30 -14
  46. package/dist/hooks/use-hotkey/utils/is-input-field.cjs.js +1 -1
  47. package/dist/hooks/use-hotkey/utils/is-input-field.es.js +4 -2
  48. package/dist/hooks/use-hotkey/utils/match-and-run.cjs.js +1 -0
  49. package/dist/hooks/use-hotkey/utils/match-and-run.es.js +12 -0
  50. package/dist/hooks/use-hotkey/utils/match-key-modifiers.cjs.js +1 -1
  51. package/dist/hooks/use-hotkey/utils/match-key-modifiers.es.js +13 -12
  52. package/dist/hooks/use-hover/use-hover.cjs.js +1 -1
  53. package/dist/hooks/use-hover/use-hover.es.js +32 -17
  54. package/dist/hooks/use-is-visible/use-is-visible.cjs.js +1 -1
  55. package/dist/hooks/use-is-visible/use-is-visible.es.js +31 -27
  56. package/dist/hooks/use-local-storage/use-local-storage.cjs.js +1 -1
  57. package/dist/hooks/use-local-storage/use-local-storage.es.js +52 -20
  58. package/dist/hooks/use-media-query/use-media-query.cjs.js +1 -1
  59. package/dist/hooks/use-media-query/use-media-query.es.js +21 -11
  60. package/dist/hooks/use-mutation/use-mutation.cjs.js +1 -1
  61. package/dist/hooks/use-mutation/use-mutation.es.js +36 -22
  62. package/dist/hooks/use-object/use-object.cjs.js +1 -1
  63. package/dist/hooks/use-object/use-object.es.js +26 -22
  64. package/dist/hooks/use-prevent-page-close/use-prevent-page-close.cjs.js +1 -0
  65. package/dist/hooks/use-prevent-page-close/use-prevent-page-close.es.js +14 -0
  66. package/dist/hooks/use-step/use-step.cjs.js +1 -1
  67. package/dist/hooks/use-step/use-step.es.js +25 -24
  68. package/dist/index.cjs.js +1 -1
  69. package/dist/index.es.js +308 -300
  70. package/dist/src/components/date-picker/date-picker.utils.d.ts +17 -0
  71. package/dist/src/components/date-picker/use-hidden-field-value.d.ts +12 -0
  72. package/dist/src/components/index.d.ts +1 -0
  73. package/dist/src/hooks/index.d.ts +2 -2
  74. package/dist/src/hooks/internal/index.d.ts +2 -0
  75. package/dist/src/hooks/internal/is-apple-device.d.ts +12 -0
  76. package/dist/src/hooks/internal/use-latest-ref.d.ts +12 -0
  77. package/dist/src/hooks/use-array/use-array.d.ts +24 -11
  78. package/dist/src/hooks/use-async/use-async.d.ts +16 -13
  79. package/dist/src/hooks/use-boolean/index.d.ts +1 -0
  80. package/dist/src/hooks/use-boolean/use-boolean.d.ts +15 -0
  81. package/dist/src/hooks/use-boolean/use-boolean.test.d.ts +1 -0
  82. package/dist/src/hooks/use-click-outside/use-click-outside.d.ts +23 -1
  83. package/dist/src/hooks/use-debounce-callback/use-debounced-callback.d.ts +19 -1
  84. package/dist/src/hooks/use-debounce-value/use-debounced-value.d.ts +10 -1
  85. package/dist/src/hooks/use-disclosure/use-disclosure.d.ts +17 -8
  86. package/dist/src/hooks/use-document-title/use-document-title.d.ts +11 -0
  87. package/dist/src/hooks/use-event-listener/use-event-listener.d.ts +18 -1
  88. package/dist/src/hooks/use-hotkey/index.d.ts +2 -1
  89. package/dist/src/hooks/use-hotkey/use-hotkey.d.ts +62 -5
  90. package/dist/src/hooks/use-hotkey/utils/index.d.ts +4 -3
  91. package/dist/src/hooks/use-hotkey/utils/is-input-field.d.ts +12 -2
  92. package/dist/src/hooks/use-hotkey/utils/is-input-field.test.d.ts +1 -0
  93. package/dist/src/hooks/use-hotkey/utils/match-and-run.d.ts +36 -0
  94. package/dist/src/hooks/use-hotkey/utils/match-and-run.test.d.ts +1 -0
  95. package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.d.ts +20 -6
  96. package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.test.d.ts +1 -0
  97. package/dist/src/hooks/use-hover/use-hover.d.ts +8 -4
  98. package/dist/src/hooks/use-is-visible/use-is-visible.d.ts +28 -4
  99. package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +13 -2
  100. package/dist/src/hooks/use-media-query/use-media-query.d.ts +10 -1
  101. package/dist/src/hooks/use-media-query/use-media-query.test.d.ts +1 -0
  102. package/dist/src/hooks/use-mutation/use-mutation.d.ts +18 -11
  103. package/dist/src/hooks/use-object/use-object.d.ts +15 -6
  104. package/dist/src/hooks/use-prevent-page-close/index.d.ts +1 -0
  105. package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.d.ts +10 -0
  106. package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.test.d.ts +1 -0
  107. package/dist/src/hooks/use-step/use-step.d.ts +18 -11
  108. package/dist/src/utils/form.d.ts +10 -0
  109. package/package.json +1 -1
  110. package/src/components/alert-dialog/alert-dialog.test.tsx +13 -9
  111. package/src/components/auto-complete/auto-complete.test.tsx +4 -14
  112. package/src/components/avatar/avatar.test.tsx +7 -12
  113. package/src/components/button/button.test.tsx +10 -15
  114. package/src/components/button/button.tsx +14 -9
  115. package/src/components/button/components/base-button.tsx +2 -4
  116. package/src/components/calendar/calendar.test.tsx +12 -19
  117. package/src/components/calendar/calendar.tsx +4 -0
  118. package/src/components/card/card.test.tsx +4 -6
  119. package/src/components/checkbox/checkbox.test.tsx +12 -8
  120. package/src/components/checkbox-group/checkbox-group.test.tsx +7 -8
  121. package/src/components/combobox/combobox.test.tsx +24 -21
  122. package/src/components/date-picker/date-input-form.test.tsx +77 -0
  123. package/src/components/date-picker/date-input.stories.tsx +30 -18
  124. package/src/components/date-picker/date-input.tsx +77 -44
  125. package/src/components/date-picker/date-picker.stories.tsx +31 -1
  126. package/src/components/date-picker/date-picker.test.tsx +3 -13
  127. package/src/components/date-picker/date-picker.tsx +35 -16
  128. package/src/components/date-picker/date-picker.utils.test.ts +32 -14
  129. package/src/components/date-picker/date-picker.utils.ts +33 -0
  130. package/src/components/date-picker/use-date-input-popover.test.ts +3 -1
  131. package/src/components/date-picker/use-hidden-field-value.ts +23 -0
  132. package/src/components/dialog/dialog.test.tsx +10 -8
  133. package/src/components/dropzone/dropzone.test.tsx +11 -13
  134. package/src/components/empty/empty.test.tsx +4 -3
  135. package/src/components/field/field.test.tsx +12 -13
  136. package/src/components/form/form.stories.tsx +16 -1
  137. package/src/components/index.ts +1 -0
  138. package/src/components/label/label.test.tsx +3 -3
  139. package/src/components/menu/menu.tsx +1 -5
  140. package/src/components/number-input/number-input.test.tsx +6 -2
  141. package/src/components/password/password.test.tsx +20 -6
  142. package/src/components/password/password.tsx +2 -2
  143. package/src/components/popover/popover.test.tsx +4 -4
  144. package/src/components/progress/progress.test.tsx +7 -8
  145. package/src/components/radio-group/radio-group.test.tsx +17 -11
  146. package/src/components/select/select.test.tsx +10 -10
  147. package/src/components/select/select.tsx +9 -1
  148. package/src/components/stepper/stepper.stories.tsx +11 -15
  149. package/src/components/stepper/stepper.test.tsx +6 -4
  150. package/src/components/switch/switch.test.tsx +3 -3
  151. package/src/components/table/table.test.tsx +9 -3
  152. package/src/components/tabs/tabs.test.tsx +6 -2
  153. package/src/components/tag/tag.test.tsx +1 -3
  154. package/src/components/textarea/textarea.test.tsx +4 -1
  155. package/src/components/timeline/timeline.test.tsx +10 -5
  156. package/src/components/toast/toast.test.tsx +11 -14
  157. package/src/components/tooltip/tooltip.test.tsx +1 -5
  158. package/src/components/tree/tree.test.tsx +3 -1
  159. package/src/hooks/index.ts +2 -2
  160. package/src/hooks/internal/index.ts +2 -0
  161. package/src/hooks/internal/is-apple-device.test.ts +41 -0
  162. package/src/hooks/internal/is-apple-device.ts +33 -0
  163. package/src/hooks/internal/use-isomorphic-layout-effect.ts +3 -1
  164. package/src/hooks/internal/use-latest-ref.ts +21 -0
  165. package/src/hooks/use-array/use-array.stories.tsx +435 -64
  166. package/src/hooks/use-array/use-array.test.tsx +398 -15
  167. package/src/hooks/use-array/use-array.ts +105 -66
  168. package/src/hooks/use-async/use-async.stories.tsx +255 -131
  169. package/src/hooks/use-async/use-async.test.ts +397 -0
  170. package/src/hooks/use-async/use-async.ts +117 -39
  171. package/src/hooks/use-boolean/index.ts +1 -0
  172. package/src/hooks/use-boolean/use-boolean.stories.tsx +377 -0
  173. package/src/hooks/use-boolean/use-boolean.test.tsx +177 -0
  174. package/src/hooks/use-boolean/use-boolean.ts +50 -0
  175. package/src/hooks/use-click-outside/use-click-outside.stories.tsx +188 -18
  176. package/src/hooks/use-click-outside/use-click-outside.test.tsx +89 -10
  177. package/src/hooks/use-click-outside/use-click-outside.ts +62 -16
  178. package/src/hooks/use-debounce-callback/use-debounced-callback.stories.tsx +141 -41
  179. package/src/hooks/use-debounce-callback/use-debounced-callback.test.ts +217 -9
  180. package/src/hooks/use-debounce-callback/use-debounced-callback.ts +71 -11
  181. package/src/hooks/use-debounce-value/use-debounced-value.stories.tsx +247 -47
  182. package/src/hooks/use-debounce-value/use-debounced-value.test.ts +105 -10
  183. package/src/hooks/use-debounce-value/use-debounced-value.ts +19 -10
  184. package/src/hooks/use-disclosure/use-disclosure.stories.tsx +305 -14
  185. package/src/hooks/use-disclosure/use-disclosure.test.ts +198 -50
  186. package/src/hooks/use-disclosure/use-disclosure.ts +49 -29
  187. package/src/hooks/use-document-title/use-document-title.stories.tsx +54 -0
  188. package/src/hooks/use-document-title/use-document-title.test.tsx +26 -0
  189. package/src/hooks/use-document-title/{use-document-title.tsx → use-document-title.ts} +17 -3
  190. package/src/hooks/use-event-listener/use-event-listener.stories.tsx +105 -9
  191. package/src/hooks/use-event-listener/use-event-listener.test.tsx +77 -10
  192. package/src/hooks/use-event-listener/use-event-listener.ts +71 -11
  193. package/src/hooks/use-focus-trap/use-focus-trap.test.ts +31 -6
  194. package/src/hooks/use-focus-trap/use-focus-trap.ts +3 -2
  195. package/src/hooks/use-hotkey/index.ts +9 -1
  196. package/src/hooks/use-hotkey/use-hotkey.stories.tsx +279 -74
  197. package/src/hooks/use-hotkey/use-hotkey.test.tsx +286 -34
  198. package/src/hooks/use-hotkey/use-hotkey.ts +141 -17
  199. package/src/hooks/use-hotkey/utils/index.ts +8 -3
  200. package/src/hooks/use-hotkey/utils/is-input-field.test.ts +78 -0
  201. package/src/hooks/use-hotkey/utils/is-input-field.ts +31 -10
  202. package/src/hooks/use-hotkey/utils/match-and-run.test.ts +203 -0
  203. package/src/hooks/use-hotkey/utils/match-and-run.ts +62 -0
  204. package/src/hooks/use-hotkey/utils/match-key-modifiers.test.ts +65 -0
  205. package/src/hooks/use-hotkey/utils/match-key-modifiers.ts +39 -12
  206. package/src/hooks/use-hover/use-hover.stories.tsx +258 -80
  207. package/src/hooks/use-hover/use-hover.test.tsx +266 -26
  208. package/src/hooks/use-hover/use-hover.tsx +93 -28
  209. package/src/hooks/use-is-visible/use-is-visible.stories.tsx +193 -46
  210. package/src/hooks/use-is-visible/use-is-visible.test.tsx +235 -7
  211. package/src/hooks/use-is-visible/use-is-visible.ts +114 -0
  212. package/src/hooks/use-local-storage/use-local-storage.stories.tsx +129 -29
  213. package/src/hooks/use-local-storage/use-local-storage.test.ts +106 -41
  214. package/src/hooks/use-local-storage/use-local-storage.ts +100 -31
  215. package/src/hooks/use-media-query/use-media-query.stories.tsx +86 -26
  216. package/src/hooks/use-media-query/use-media-query.test.ts +132 -0
  217. package/src/hooks/use-media-query/use-media-query.ts +39 -14
  218. package/src/hooks/use-memoized-fn/use-memoized-fn.ts +0 -1
  219. package/src/hooks/use-mutation/use-mutation.stories.tsx +260 -94
  220. package/src/hooks/use-mutation/use-mutation.test.ts +359 -0
  221. package/src/hooks/use-mutation/use-mutation.ts +97 -0
  222. package/src/hooks/use-object/use-object.stories.tsx +310 -79
  223. package/src/hooks/use-object/use-object.test.tsx +235 -56
  224. package/src/hooks/use-object/use-object.ts +59 -0
  225. package/src/hooks/use-pagination/use-pagination.tsx +0 -1
  226. package/src/hooks/use-prevent-page-close/index.ts +1 -0
  227. package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +39 -0
  228. package/src/hooks/use-prevent-page-close/use-prevent-page-close.test.ts +89 -0
  229. package/src/hooks/use-prevent-page-close/use-prevent-page-close.ts +27 -0
  230. package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +1 -1
  231. package/src/hooks/use-range-pagination/use-range-pagination.tsx +1 -1
  232. package/src/hooks/use-selection/use-selection.ts +0 -1
  233. package/src/hooks/use-step/use-step.stories.tsx +178 -65
  234. package/src/hooks/use-step/use-step.test.ts +178 -53
  235. package/src/hooks/use-step/use-step.ts +57 -49
  236. package/src/utils/form.test.tsx +13 -8
  237. package/src/utils/form.tsx +10 -0
  238. package/src/utils/functions/getFormData.test.ts +1 -1
  239. package/dist/hooks/use-hotkey/utils/create-hotkey-listener.cjs.js +0 -1
  240. package/dist/hooks/use-hotkey/utils/create-hotkey-listener.es.js +0 -10
  241. package/dist/hooks/use-prevent-close-window/use-prevent-close-window.cjs.js +0 -1
  242. package/dist/hooks/use-prevent-close-window/use-prevent-close-window.es.js +0 -15
  243. package/dist/hooks/use-toggle/use-toggle.cjs.js +0 -1
  244. package/dist/hooks/use-toggle/use-toggle.es.js +0 -10
  245. package/dist/src/hooks/use-hotkey/utils/create-hotkey-listener.d.ts +0 -1
  246. package/dist/src/hooks/use-prevent-close-window/index.d.ts +0 -1
  247. package/dist/src/hooks/use-prevent-close-window/use-prevent-close-window.d.ts +0 -13
  248. package/dist/src/hooks/use-toggle/index.d.ts +0 -1
  249. package/dist/src/hooks/use-toggle/use-toggle.d.ts +0 -3
  250. package/src/hooks/use-async/use-async.test.tsx +0 -68
  251. package/src/hooks/use-hotkey/utils/create-hotkey-listener.ts +0 -25
  252. package/src/hooks/use-is-visible/use-is-visible.tsx +0 -49
  253. package/src/hooks/use-mutation/use-mutation.test.tsx +0 -83
  254. package/src/hooks/use-mutation/use-mutation.tsx +0 -59
  255. package/src/hooks/use-object/use-object.tsx +0 -46
  256. package/src/hooks/use-prevent-close-window/index.ts +0 -1
  257. package/src/hooks/use-prevent-close-window/use-prevent-close-window.stories.tsx +0 -32
  258. package/src/hooks/use-prevent-close-window/use-prevent-close-window.test.ts +0 -79
  259. package/src/hooks/use-prevent-close-window/use-prevent-close-window.ts +0 -33
  260. package/src/hooks/use-toggle/index.ts +0 -1
  261. package/src/hooks/use-toggle/use-toggle.stories.tsx +0 -25
  262. package/src/hooks/use-toggle/use-toggle.test.tsx +0 -64
  263. package/src/hooks/use-toggle/use-toggle.ts +0 -14
  264. /package/dist/src/{hooks/use-prevent-close-window/use-prevent-close-window.test.d.ts → components/date-picker/date-input-form.test.d.ts} +0 -0
  265. /package/dist/src/hooks/{use-toggle/use-toggle.test.d.ts → internal/is-apple-device.test.d.ts} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { screen } from "@testing-library/dom";
2
- import { render, waitFor } from "@testing-library/react";
2
+ import { fireEvent, render, renderHook } from "@testing-library/react";
3
+ import { useRef } from "react";
3
4
  import { describe, expect, it, vi } from "vitest";
4
5
  import { click } from "../../utils/tests";
5
6
  import { useEventListener } from "../use-event-listener";
@@ -9,21 +10,87 @@ describe("useEventListener hook", () => {
9
10
  expect(useEventListener).toBeDefined();
10
11
  });
11
12
 
12
- it("should handle event", () => {
13
- const handleEvent = vi.fn();
13
+ it("listens on window by default when no target is passed", () => {
14
+ const handler = vi.fn();
15
+ renderHook(() => useEventListener("resize", handler));
16
+
17
+ fireEvent(window, new Event("resize"));
18
+
19
+ expect(handler).toHaveBeenCalledTimes(1);
20
+ });
21
+
22
+ it("listens on an element passed by ref", () => {
23
+ const handler = vi.fn();
14
24
  const Component = () => {
15
- const ref = useEventListener<"click", HTMLDivElement>(
16
- "click",
17
- handleEvent,
18
- );
25
+ const ref = useRef<HTMLDivElement>(null);
26
+ useEventListener("click", handler, ref);
19
27
  return <div ref={ref} data-testid="target" />;
20
28
  };
21
29
  render(<Component />);
22
30
 
23
31
  click(screen.getByTestId("target"));
24
32
 
25
- waitFor(() => {
26
- expect(handleEvent).toHaveBeenCalled();
27
- });
33
+ expect(handler).toHaveBeenCalledTimes(1);
34
+ });
35
+
36
+ it("listens on document when passed directly", () => {
37
+ const handler = vi.fn();
38
+ renderHook(() => useEventListener("keydown", handler, document));
39
+
40
+ fireEvent.keyDown(document.body);
41
+
42
+ expect(handler).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it("always calls the latest listener without re-attaching", () => {
46
+ const addSpy = vi.spyOn(window, "addEventListener");
47
+ const first = vi.fn();
48
+ const second = vi.fn();
49
+
50
+ const { rerender } = renderHook(
51
+ ({ cb }) => useEventListener("resize", cb),
52
+ {
53
+ initialProps: { cb: first },
54
+ },
55
+ );
56
+ const attachCountAfterMount = addSpy.mock.calls.filter(
57
+ ([type]) => type === "resize",
58
+ ).length;
59
+
60
+ rerender({ cb: second });
61
+ fireEvent(window, new Event("resize"));
62
+
63
+ expect(first).not.toHaveBeenCalled();
64
+ expect(second).toHaveBeenCalledTimes(1);
65
+ // listener identity change must not re-attach
66
+ expect(addSpy.mock.calls.filter(([type]) => type === "resize").length).toBe(
67
+ attachCountAfterMount,
68
+ );
69
+
70
+ addSpy.mockRestore();
71
+ });
72
+
73
+ it("removes the listener on unmount", () => {
74
+ const removeSpy = vi.spyOn(window, "removeEventListener");
75
+ const { unmount } = renderHook(() => useEventListener("resize", () => {}));
76
+
77
+ unmount();
78
+
79
+ expect(removeSpy).toHaveBeenCalledWith(
80
+ "resize",
81
+ expect.any(Function),
82
+ undefined,
83
+ );
84
+ removeSpy.mockRestore();
85
+ });
86
+
87
+ it("does not call the listener after unmount", () => {
88
+ const handler = vi.fn();
89
+ const { unmount } = renderHook(() => useEventListener("resize", handler));
90
+
91
+ unmount();
92
+ fireEvent(window, new Event("resize"));
93
+
94
+ expect(handler).not.toHaveBeenCalled();
28
95
  });
29
96
  });
@@ -1,24 +1,84 @@
1
- import { useEffect, useRef } from "react";
1
+ import { type RefObject, useEffect } from "react";
2
+ import { isBrowser, useLatestRef } from "../internal";
2
3
 
4
+ type ListenableTarget = Window | Document | HTMLElement | MediaQueryList;
5
+
6
+ type TargetArg<T extends ListenableTarget> = T | RefObject<T | null> | null;
7
+
8
+ const resolveTarget = (
9
+ target: TargetArg<ListenableTarget> | undefined,
10
+ ): EventTarget | null => {
11
+ if (target == null) return isBrowser ? window : null;
12
+ if ("current" in target) return target.current;
13
+ return target;
14
+ };
15
+
16
+ /**
17
+ * Subscribe to a DOM event with automatic cleanup. The `listener` is read
18
+ * through an internal ref (handler-stable pattern), so passing an inline
19
+ * function never re-attaches the underlying listener on every render.
20
+ *
21
+ * The target is explicit and selected by overload:
22
+ * - omit it to listen on `window` (the default);
23
+ * - pass a `RefObject` or the element itself for an `HTMLElement`;
24
+ * - pass `document` for document-level events;
25
+ * - pass a `MediaQueryList` for media query changes.
26
+ *
27
+ * SSR-safe: the effect no-ops when there is no DOM.
28
+ */
29
+ export function useEventListener<K extends keyof WindowEventMap>(
30
+ type: K,
31
+ listener: (event: WindowEventMap[K]) => void,
32
+ target?: Window | RefObject<Window | null> | null,
33
+ options?: boolean | AddEventListenerOptions,
34
+ ): void;
35
+ export function useEventListener<K extends keyof DocumentEventMap>(
36
+ type: K,
37
+ listener: (event: DocumentEventMap[K]) => void,
38
+ target: Document | RefObject<Document | null>,
39
+ options?: boolean | AddEventListenerOptions,
40
+ ): void;
3
41
  export function useEventListener<
4
42
  K extends keyof HTMLElementEventMap,
5
43
  T extends HTMLElement = HTMLElement,
6
44
  >(
7
45
  type: K,
8
- listener: (this: T, ev: HTMLElementEventMap[K]) => void,
46
+ listener: (event: HTMLElementEventMap[K]) => void,
47
+ target: T | RefObject<T | null>,
48
+ options?: boolean | AddEventListenerOptions,
49
+ ): void;
50
+ export function useEventListener<K extends keyof MediaQueryListEventMap>(
51
+ type: K,
52
+ listener: (event: MediaQueryListEventMap[K]) => void,
53
+ target: MediaQueryList | RefObject<MediaQueryList | null>,
54
+ options?: boolean | AddEventListenerOptions,
55
+ ): void;
56
+ export function useEventListener(
57
+ type: string,
58
+ listener: (event: Event) => void,
59
+ target?: TargetArg<ListenableTarget>,
9
60
  options?: boolean | AddEventListenerOptions,
10
- ) {
11
- const ref = useRef<T | null>(null);
61
+ ): void {
62
+ const listenerRef = useLatestRef(listener);
63
+ const optionsRef = useLatestRef(options);
64
+
65
+ const capture = typeof options === "boolean" ? options : options?.capture;
66
+ const once = typeof options === "object" ? options.once : undefined;
67
+ const passive = typeof options === "object" ? options.passive : undefined;
12
68
 
13
69
  useEffect(() => {
14
- const target = ref.current ?? document;
70
+ if (!isBrowser) return;
15
71
 
16
- target.addEventListener(type, listener as EventListener, options);
17
- return () => {
18
- target.removeEventListener(type, listener as EventListener, options);
72
+ const node = resolveTarget(target);
73
+ if (!node) return;
74
+
75
+ const handler = (event: Event) => {
76
+ listenerRef.current(event);
19
77
  };
20
- }, [type, listener, options]);
21
78
 
22
- return ref;
79
+ node.addEventListener(type, handler, optionsRef.current);
80
+ return () => {
81
+ node.removeEventListener(type, handler, optionsRef.current);
82
+ };
83
+ }, [type, target, capture, once, passive]);
23
84
  }
24
-
@@ -1,7 +1,12 @@
1
1
  import { renderHook } from "@testing-library/react";
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
- import { FOCUS_SELECTOR, findTabbableDescendants, focusable, tabbable } from "./tabbable";
4
3
  import { scopeTab } from "./scope-tab";
4
+ import {
5
+ FOCUS_SELECTOR,
6
+ findTabbableDescendants,
7
+ focusable,
8
+ tabbable,
9
+ } from "./tabbable";
5
10
  import { useFocusTrap } from "./use-focus-trap";
6
11
 
7
12
  // ─── tabbable.ts ─────────────────────────────────────────────────────────────
@@ -151,7 +156,11 @@ describe("scopeTab", () => {
151
156
  });
152
157
 
153
158
  it("calls preventDefault when no tabbable descendants", () => {
154
- const event = { key: "Tab", shiftKey: false, preventDefault: vi.fn() } as any;
159
+ const event = {
160
+ key: "Tab",
161
+ shiftKey: false,
162
+ preventDefault: vi.fn(),
163
+ } as any;
155
164
  scopeTab(container, event);
156
165
  expect(event.preventDefault).toHaveBeenCalledOnce();
157
166
  });
@@ -162,7 +171,11 @@ describe("scopeTab", () => {
162
171
  container.append(btn1, btn2);
163
172
  btn2.focus();
164
173
 
165
- const event = { key: "Tab", shiftKey: false, preventDefault: vi.fn() } as any;
174
+ const event = {
175
+ key: "Tab",
176
+ shiftKey: false,
177
+ preventDefault: vi.fn(),
178
+ } as any;
166
179
  scopeTab(container, event);
167
180
 
168
181
  expect(event.preventDefault).toHaveBeenCalledOnce();
@@ -175,7 +188,11 @@ describe("scopeTab", () => {
175
188
  container.append(btn1, btn2);
176
189
  btn1.focus();
177
190
 
178
- const event = { key: "Tab", shiftKey: true, preventDefault: vi.fn() } as any;
191
+ const event = {
192
+ key: "Tab",
193
+ shiftKey: true,
194
+ preventDefault: vi.fn(),
195
+ } as any;
179
196
  scopeTab(container, event);
180
197
 
181
198
  expect(event.preventDefault).toHaveBeenCalledOnce();
@@ -189,7 +206,11 @@ describe("scopeTab", () => {
189
206
  container.append(btn1, btn2, btn3);
190
207
  btn1.focus();
191
208
 
192
- const event = { key: "Tab", shiftKey: false, preventDefault: vi.fn() } as any;
209
+ const event = {
210
+ key: "Tab",
211
+ shiftKey: false,
212
+ preventDefault: vi.fn(),
213
+ } as any;
193
214
  scopeTab(container, event);
194
215
 
195
216
  expect(event.preventDefault).not.toHaveBeenCalled();
@@ -206,7 +227,11 @@ describe("scopeTab", () => {
206
227
  container.append(btn, radio1, radio2);
207
228
  radio1.focus();
208
229
 
209
- const event = { key: "Tab", shiftKey: false, preventDefault: vi.fn() } as any;
230
+ const event = {
231
+ key: "Tab",
232
+ shiftKey: false,
233
+ preventDefault: vi.fn(),
234
+ } as any;
210
235
  scopeTab(container, event);
211
236
 
212
237
  expect(event.preventDefault).toHaveBeenCalledOnce();
@@ -2,7 +2,9 @@ import { useCallback, useEffect, useRef } from "react";
2
2
  import { scopeTab } from "./scope-tab";
3
3
  import { FOCUS_SELECTOR, focusable, tabbable } from "./tabbable";
4
4
 
5
- export function useFocusTrap(active = true): (instance: HTMLElement | null) => void {
5
+ export function useFocusTrap(
6
+ active = true,
7
+ ): (instance: HTMLElement | null) => void {
6
8
  const ref = useRef<HTMLElement | null>(null);
7
9
 
8
10
  const focusNode = (node: HTMLElement) => {
@@ -74,4 +76,3 @@ export function useFocusTrap(active = true): (instance: HTMLElement | null) => v
74
76
 
75
77
  return setRef;
76
78
  }
77
-
@@ -1 +1,9 @@
1
- export * from "./use-hotkey";
1
+ export type {
2
+ HotkeyEventName,
3
+ HotkeyItem,
4
+ HotkeyItemOptions,
5
+ HotkeyOptions,
6
+ KeyboardEventLike,
7
+ Keys,
8
+ } from "./use-hotkey";
9
+ export { getHotkeyHandler, useHotkey } from "./use-hotkey";