@jbpark/use-hooks 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.ko.md +23 -29
  2. package/README.md +23 -29
  3. package/dist/hooks/index.d.mts +14 -13
  4. package/dist/hooks/index.mjs +14 -13
  5. package/dist/hooks/{useBodyScrollLock/index.d.mts → use-body-scroll-lock.d.mts} +2 -2
  6. package/dist/hooks/{useBodyScrollLock/index.mjs → use-body-scroll-lock.mjs} +46 -37
  7. package/dist/hooks/use-body-scroll-lock.mjs.map +1 -0
  8. package/dist/hooks/use-click-outside.d.mts +7 -0
  9. package/dist/hooks/use-click-outside.mjs +29 -0
  10. package/dist/hooks/use-click-outside.mjs.map +1 -0
  11. package/dist/hooks/{useDebounce/index.d.mts → use-debounce.d.mts} +2 -2
  12. package/dist/hooks/{useDebounce/index.mjs → use-debounce.mjs} +2 -6
  13. package/dist/hooks/use-debounce.mjs.map +1 -0
  14. package/dist/hooks/{useElementPosition/index.d.mts → use-element-position.d.mts} +2 -2
  15. package/dist/hooks/{useElementPosition/index.mjs → use-element-position.mjs} +2 -2
  16. package/dist/hooks/use-element-position.mjs.map +1 -0
  17. package/dist/hooks/{useElementScroll/index.d.mts → use-element-scroll.d.mts} +2 -2
  18. package/dist/hooks/{useElementScroll/index.mjs → use-element-scroll.mjs} +2 -2
  19. package/dist/hooks/use-element-scroll.mjs.map +1 -0
  20. package/dist/hooks/{useImage/index.d.mts → use-image.d.mts} +2 -2
  21. package/dist/hooks/{useImage/index.mjs → use-image.mjs} +20 -12
  22. package/dist/hooks/use-image.mjs.map +1 -0
  23. package/dist/hooks/{useLocalStorage/index.d.mts → use-local-storage.d.mts} +2 -2
  24. package/dist/hooks/{useLocalStorage/index.mjs → use-local-storage.mjs} +20 -4
  25. package/dist/hooks/use-local-storage.mjs.map +1 -0
  26. package/dist/hooks/{useRecursiveTimeout/index.d.mts → use-recursive-timeout.d.mts} +2 -2
  27. package/dist/hooks/{useRecursiveTimeout/index.mjs → use-recursive-timeout.mjs} +5 -3
  28. package/dist/hooks/use-recursive-timeout.mjs.map +1 -0
  29. package/dist/hooks/{useResponsiveSize/index.d.mts → use-responsive-size.d.mts} +2 -2
  30. package/dist/hooks/{useResponsiveSize/index.mjs → use-responsive-size.mjs} +3 -3
  31. package/dist/hooks/use-responsive-size.mjs.map +1 -0
  32. package/dist/hooks/{useScrollToElements/index.d.mts → use-scroll-to-elements.d.mts} +4 -4
  33. package/dist/hooks/use-scroll-to-elements.mjs +34 -0
  34. package/dist/hooks/use-scroll-to-elements.mjs.map +1 -0
  35. package/dist/hooks/{useThrottle/index.d.mts → use-throttle.d.mts} +2 -2
  36. package/dist/hooks/{useThrottle/index.mjs → use-throttle.mjs} +2 -2
  37. package/dist/hooks/use-throttle.mjs.map +1 -0
  38. package/dist/hooks/{useTimeline/index.d.mts → use-timeline.d.mts} +2 -2
  39. package/dist/hooks/{useTimeline/index.mjs → use-timeline.mjs} +4 -3
  40. package/dist/hooks/use-timeline.mjs.map +1 -0
  41. package/dist/hooks/{useViewport/index.d.mts → use-viewport.d.mts} +2 -2
  42. package/dist/hooks/{useViewport/index.mjs → use-viewport.mjs} +2 -2
  43. package/dist/hooks/use-viewport.mjs.map +1 -0
  44. package/dist/hooks/{useWindowScroll/index.d.mts → use-window-scroll.d.mts} +2 -2
  45. package/dist/hooks/{useWindowScroll/index.mjs → use-window-scroll.mjs} +2 -2
  46. package/dist/hooks/use-window-scroll.mjs.map +1 -0
  47. package/dist/index.d.mts +15 -14
  48. package/dist/index.mjs +15 -14
  49. package/package.json +16 -10
  50. package/dist/hooks/useBodyScrollLock/index.mjs.map +0 -1
  51. package/dist/hooks/useDebounce/index.mjs.map +0 -1
  52. package/dist/hooks/useElementPosition/index.mjs.map +0 -1
  53. package/dist/hooks/useElementScroll/index.mjs.map +0 -1
  54. package/dist/hooks/useImage/index.mjs.map +0 -1
  55. package/dist/hooks/useLocalStorage/index.mjs.map +0 -1
  56. package/dist/hooks/useRecursiveTimeout/index.mjs.map +0 -1
  57. package/dist/hooks/useResponsiveSize/index.mjs.map +0 -1
  58. package/dist/hooks/useScrollToElements/index.mjs +0 -34
  59. package/dist/hooks/useScrollToElements/index.mjs.map +0 -1
  60. package/dist/hooks/useThrottle/index.mjs.map +0 -1
  61. package/dist/hooks/useTimeline/index.mjs.map +0 -1
  62. package/dist/hooks/useViewport/index.mjs.map +0 -1
  63. package/dist/hooks/useWindowScroll/index.mjs.map +0 -1
package/README.ko.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## 기능
13
13
 
14
- - 📦 **12개 프로덕션 레디 훅** - 스크롤, 뷰포트, 스토리지 등 다양한 유틸리티
14
+ - 📦 **13개 프로덕션 레디 훅** - 스크롤, 뷰포트, 스토리지 등 다양한 유틸리티
15
15
  - 🎯 **TypeScript 지원** - 완전한 타입 지원으로 더 나은 개발 경험
16
16
  - ⚡ **트리 셰이킹 지원** - 필요한 것만 임포트하세요
17
17
  - 🔒 **SSR 안전** - window/document 전역 변수에 대한 보호
@@ -75,6 +75,7 @@ function MyComponent() {
75
75
  | `useElementPosition` | 스크롤/리사이즈 시 요소의 바운딩 렉트 모니터링 (요소 참조 지원) |
76
76
  | `useResponsiveSize` | Tailwind 유사 브레이크포인트를 포함한 요소 크기 추적 (debounce) |
77
77
  | `useBodyScrollLock` | 스타일 보존을 포함한 바디 스크롤 잠금/해제 (iOS 특별 처리) |
78
+ | `useClickOutside` | 참조한 요소 바깥을 클릭/터치하면 콜백 실행 |
78
79
  | `useScrollToElements` | 인덱스별로 특정 요소로 스크롤 (오프셋 조절 가능) |
79
80
  | `useImage` | 이미지 사전로드 및 로딩/에러 상태 노출 |
80
81
  | `useRecursiveTimeout` | 비동기/동기 콜백을 재귀적으로 스케줄링 |
@@ -105,43 +106,34 @@ pnpm exec prettier --write .
105
106
 
106
107
  ```
107
108
  src/
108
- ├── hooks/ # 개별 훅 구현
109
- │ ├── useBodyScrollLock/
110
- │ ├── useDebounce/
111
- │ ├── useElementPosition/
112
- │ ├── useElementScroll/
113
- │ ├── useImage/
114
- │ ├── useLocalStorage/
115
- │ ├── useRecursiveTimeout/
116
- │ ├── useResponsiveSize/
117
- │ ├── useScrollToElements/
118
- │ ├── useThrottle/
119
- │ ├── useViewport/
120
- │ ├── useWindowScroll/
109
+ ├── hooks/ # 개별 훅 구현 (훅 1개 = 파일 1개)
110
+ │ ├── use-body-scroll-lock.ts
111
+ │ ├── use-click-outside.ts
112
+ │ ├── use-debounce.ts
113
+ │ ├── use-element-position.ts
114
+ │ ├── use-element-scroll.ts
115
+ │ ├── use-image.ts
116
+ │ ├── use-local-storage.ts
117
+ │ ├── use-recursive-timeout.ts
118
+ │ ├── use-responsive-size.ts
119
+ │ ├── use-scroll-to-elements.ts
120
+ │ ├── use-throttle.ts
121
+ │ ├── use-viewport.ts
122
+ │ ├── use-window-scroll.ts
121
123
  │ └── index.ts # 배럴 익스포트
122
124
  └── index.ts # 패키지 진입점
123
125
 
124
126
  dist/ # 빌드된 라이브러리 (ESM + types)
125
- .changeset/ # 버저닝을 위한 Changesets
126
127
  ```
127
128
 
128
129
  ## 빌드 및 배포
129
130
 
130
- 이 프로젝트는 버전 관리를 위해 Changesets를 사용합니다:
131
+ 이 프로젝트는 `develop` → `main` 브랜치 흐름과 AI 기반 자동 릴리스 노트를 사용합니다:
131
132
 
132
- ```bash
133
- # 변경사항 기록
134
- pnpm changeset add
135
-
136
- # 버전 업데이트 및 CHANGELOG 생성
137
- pnpm changeset version
138
-
139
- # npm에 배포
140
- pnpm changeset publish
141
-
142
- # 태그 푸시
143
- git push --follow-tags
144
- ```
133
+ - **기능 브랜치**는 `develop`으로 머지됩니다.
134
+ - `develop`에 push될 때마다 워크플로가 diff를 분석해 `CHANGELOG.md`의 `## [Unreleased]` 섹션에 항목을 누적합니다 (버전은 아직 올리지 않음).
135
+ - `develop`이 `main`으로 머지되면 `Unreleased` 섹션에 버전+날짜가 확정되고 `package.json` 버전이 그에 맞춰 올라간 릴리스 PR이 생성됩니다.
136
+ - 이 릴리스 PR을 머지하면 빌드, npm 배포, 태그 생성까지 자동으로 진행됩니다.
145
137
 
146
138
  라이브러리는 다음과 같이 빌드됩니다:
147
139
 
@@ -150,6 +142,8 @@ git push --follow-tags
150
142
 
151
143
  ## 주요 패턴
152
144
 
145
+ - **훅 1개 = 파일 1개**: 각 훅은 `src/hooks/use-x.ts` 형태의 단일 파일로 존재하며 (폴더/`index.ts` 없음), `src/hooks/index.ts`에서 재수출됩니다
146
+ - **데모 파일 네이밍**: 각 훅의 데모 페이지 파일명은 훅 파일명에 `-demo`를 붙인 `src/demo/use-x-demo.tsx` 형태를 따릅니다 (컴포넌트명 자체는 기존처럼 PascalCase 유지, 예: `ClickOutsideDemo`)
153
147
  - **Window 보호**: `window`/`document`에 접근하는 훅은 SSR 안전성을 위해 `typeof window` 체크 (예: `useLocalStorage`)
154
148
  - **이벤트 리스너**: 모든 스크롤/리사이즈 리스너는 가능한 한 passive 플래그 사용
155
149
  - **ResizeObserver**: `useResponsiveSize`와 `useElementPosition`에서 사용하여 성능 최적화
package/README.md CHANGED
@@ -11,7 +11,7 @@ A collection of reusable React 19 hooks for common UI and interaction patterns.
11
11
 
12
12
  ## Features
13
13
 
14
- - 📦 **12 Production-Ready Hooks** - Utilities for scrolling, viewport, storage, and more
14
+ - 📦 **13 Production-Ready Hooks** - Utilities for scrolling, viewport, storage, and more
15
15
  - 🎯 **Full TypeScript Support** - Complete type definitions for better development experience
16
16
  - ⚡ **Tree-Shakeable** - Import only what you need
17
17
  - 🔒 **SSR-Safe** - Built-in protection for window/document globals
@@ -75,6 +75,7 @@ function MyComponent() {
75
75
  | `useElementPosition` | Monitor element bounding rect on scroll/resize (element ref support) |
76
76
  | `useResponsiveSize` | Track element size with Tailwind-like breakpoints (debounced) |
77
77
  | `useBodyScrollLock` | Lock/unlock body scroll with style preservation (iOS-specific handling) |
78
+ | `useClickOutside` | Run a callback when a click/touch happens outside a referenced element |
78
79
  | `useScrollToElements` | Scroll to specific elements by index (adjustable offset) |
79
80
  | `useImage` | Preload images and expose loading/error states |
80
81
  | `useRecursiveTimeout` | Recursively schedule async/sync callbacks |
@@ -105,43 +106,34 @@ pnpm exec prettier --write .
105
106
 
106
107
  ```
107
108
  src/
108
- ├── hooks/ # Individual hook implementations
109
- │ ├── useBodyScrollLock/
110
- │ ├── useDebounce/
111
- │ ├── useElementPosition/
112
- │ ├── useElementScroll/
113
- │ ├── useImage/
114
- │ ├── useLocalStorage/
115
- │ ├── useRecursiveTimeout/
116
- │ ├── useResponsiveSize/
117
- │ ├── useScrollToElements/
118
- │ ├── useThrottle/
119
- │ ├── useViewport/
120
- │ ├── useWindowScroll/
109
+ ├── hooks/ # Individual hook implementations (one file per hook)
110
+ │ ├── use-body-scroll-lock.ts
111
+ │ ├── use-click-outside.ts
112
+ │ ├── use-debounce.ts
113
+ │ ├── use-element-position.ts
114
+ │ ├── use-element-scroll.ts
115
+ │ ├── use-image.ts
116
+ │ ├── use-local-storage.ts
117
+ │ ├── use-recursive-timeout.ts
118
+ │ ├── use-responsive-size.ts
119
+ │ ├── use-scroll-to-elements.ts
120
+ │ ├── use-throttle.ts
121
+ │ ├── use-viewport.ts
122
+ │ ├── use-window-scroll.ts
121
123
  │ └── index.ts # Barrel export
122
124
  └── index.ts # Package entry point
123
125
 
124
126
  dist/ # Built library (ESM + types)
125
- .changeset/ # Changesets for versioning
126
127
  ```
127
128
 
128
129
  ## Build & Deployment
129
130
 
130
- This project uses Changesets for version management:
131
+ This project uses a `develop` → `main` branch flow with automated, AI-assisted release notes:
131
132
 
132
- ```bash
133
- # Record changes
134
- pnpm changeset add
135
-
136
- # Update versions and generate CHANGELOG
137
- pnpm changeset version
138
-
139
- # Publish to npm
140
- pnpm changeset publish
141
-
142
- # Push tags
143
- git push --follow-tags
144
- ```
133
+ - **Feature branches** merge into `develop`.
134
+ - On every push to `develop`, a workflow analyzes the diff and appends bullet points to the `## [Unreleased]` section of `CHANGELOG.md` (no version bump yet).
135
+ - When `develop` is merged into `main`, that `Unreleased` section is stamped with a version + date and `package.json` is bumped to match, opened as a release PR.
136
+ - Merging the release PR builds, publishes to npm, and tags the release.
145
137
 
146
138
  The library is built as:
147
139
 
@@ -150,6 +142,8 @@ The library is built as:
150
142
 
151
143
  ## Key Patterns
152
144
 
145
+ - **One File Per Hook**: Each hook lives in a single flat file at `src/hooks/use-x.ts` (no per-hook folder/`index.ts`) and is re-exported from `src/hooks/index.ts`
146
+ - **Demo File Naming**: Each hook's demo page file is named `src/demo/use-x-demo.tsx`, matching the hook's own filename with a `-demo` suffix (the component name itself stays PascalCase, e.g. `ClickOutsideDemo`)
153
147
  - **Window Protection**: Hooks accessing `window`/`document` check `typeof window` for SSR safety (e.g., `useLocalStorage`)
154
148
  - **Event Listeners**: All scroll/resize listeners use passive flag when possible
155
149
  - **ResizeObserver**: Used in `useResponsiveSize` and `useElementPosition` for performance
@@ -1,13 +1,14 @@
1
- import { useDebounce } from "./useDebounce/index.mjs";
2
- import { useBodyScrollLock } from "./useBodyScrollLock/index.mjs";
3
- import { useElementPosition } from "./useElementPosition/index.mjs";
4
- import { useElementScroll } from "./useElementScroll/index.mjs";
5
- import { useResponsiveSize } from "./useResponsiveSize/index.mjs";
6
- import { useImage } from "./useImage/index.mjs";
7
- import { useLocalStorage } from "./useLocalStorage/index.mjs";
8
- import { useRecursiveTimeout } from "./useRecursiveTimeout/index.mjs";
9
- import { useScrollToElements } from "./useScrollToElements/index.mjs";
10
- import { useThrottle } from "./useThrottle/index.mjs";
11
- import { useTimeline } from "./useTimeline/index.mjs";
12
- import { useWindowScroll } from "./useWindowScroll/index.mjs";
13
- import { useViewport } from "./useViewport/index.mjs";
1
+ import { useDebounce } from "./use-debounce.mjs";
2
+ import { useBodyScrollLock } from "./use-body-scroll-lock.mjs";
3
+ import { useClickOutside } from "./use-click-outside.mjs";
4
+ import { useElementPosition } from "./use-element-position.mjs";
5
+ import { useElementScroll } from "./use-element-scroll.mjs";
6
+ import { useResponsiveSize } from "./use-responsive-size.mjs";
7
+ import { useImage } from "./use-image.mjs";
8
+ import { useLocalStorage } from "./use-local-storage.mjs";
9
+ import { useRecursiveTimeout } from "./use-recursive-timeout.mjs";
10
+ import { useScrollToElements } from "./use-scroll-to-elements.mjs";
11
+ import { useThrottle } from "./use-throttle.mjs";
12
+ import { useTimeline } from "./use-timeline.mjs";
13
+ import { useWindowScroll } from "./use-window-scroll.mjs";
14
+ import { useViewport } from "./use-viewport.mjs";
@@ -1,15 +1,16 @@
1
- import useDebounce from "./useDebounce/index.mjs";
2
- import useBodyScrollLock from "./useBodyScrollLock/index.mjs";
3
- import useElementPosition from "./useElementPosition/index.mjs";
4
- import useElementScroll from "./useElementScroll/index.mjs";
5
- import useResponsiveSize from "./useResponsiveSize/index.mjs";
6
- import useImage from "./useImage/index.mjs";
7
- import useLocalStorage from "./useLocalStorage/index.mjs";
8
- import useRecursiveTimeout from "./useRecursiveTimeout/index.mjs";
9
- import useScrollToElements from "./useScrollToElements/index.mjs";
10
- import useThrottle from "./useThrottle/index.mjs";
11
- import useTimeline from "./useTimeline/index.mjs";
12
- import useWindowScroll from "./useWindowScroll/index.mjs";
13
- import useViewport from "./useViewport/index.mjs";
1
+ import useDebounce from "./use-debounce.mjs";
2
+ import useBodyScrollLock from "./use-body-scroll-lock.mjs";
3
+ import useClickOutside from "./use-click-outside.mjs";
4
+ import useElementPosition from "./use-element-position.mjs";
5
+ import useElementScroll from "./use-element-scroll.mjs";
6
+ import useResponsiveSize from "./use-responsive-size.mjs";
7
+ import useImage from "./use-image.mjs";
8
+ import useLocalStorage from "./use-local-storage.mjs";
9
+ import useRecursiveTimeout from "./use-recursive-timeout.mjs";
10
+ import useScrollToElements from "./use-scroll-to-elements.mjs";
11
+ import useThrottle from "./use-throttle.mjs";
12
+ import useTimeline from "./use-timeline.mjs";
13
+ import useWindowScroll from "./use-window-scroll.mjs";
14
+ import useViewport from "./use-viewport.mjs";
14
15
 
15
16
  export { };
@@ -1,5 +1,5 @@
1
- //#region src/hooks/useBodyScrollLock/index.d.ts
1
+ //#region src/hooks/use-body-scroll-lock.d.ts
2
2
  declare const useBodyScrollLock: (enabled?: boolean) => void;
3
3
  //#endregion
4
4
  export { useBodyScrollLock };
5
- //# sourceMappingURL=index.d.mts.map
5
+ //# sourceMappingURL=use-body-scroll-lock.d.mts.map
@@ -1,12 +1,16 @@
1
1
  import { useEffect } from "react";
2
2
 
3
- //#region src/hooks/useBodyScrollLock/index.ts
4
- const useBodyScrollLock = (enabled = true) => {
5
- useEffect(() => {
6
- if (!enabled) return;
7
- const scrollY = window.scrollY;
8
- const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
9
- const originalStyles = {
3
+ //#region src/hooks/use-body-scroll-lock.ts
4
+ let lockCount = 0;
5
+ let originalStyles = null;
6
+ let originalScrollY = 0;
7
+ const isIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent);
8
+ const lockScroll = () => {
9
+ if (lockCount === 0) {
10
+ originalScrollY = window.scrollY;
11
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
12
+ const currentPaddingRight = parseFloat(getComputedStyle(document.body).paddingRight) || 0;
13
+ originalStyles = {
10
14
  documentElement: {
11
15
  overflow: document.documentElement.style.overflow,
12
16
  height: document.documentElement.style.height,
@@ -21,6 +25,7 @@ const useBodyScrollLock = (enabled = true) => {
21
25
  left: document.body.style.left,
22
26
  right: document.body.style.right,
23
27
  width: document.body.style.width,
28
+ paddingRight: document.body.style.paddingRight,
24
29
  webkitOverflowScrolling: document.body.style.getPropertyValue("-webkit-overflow-scrolling")
25
30
  }
26
31
  };
@@ -31,12 +36,40 @@ const useBodyScrollLock = (enabled = true) => {
31
36
  document.body.style.overflow = "hidden";
32
37
  document.body.style.height = "100%";
33
38
  document.body.style.position = "fixed";
34
- document.body.style.top = `-${scrollY}px`;
39
+ document.body.style.top = `-${originalScrollY}px`;
35
40
  document.body.style.left = "0";
36
41
  document.body.style.right = "0";
37
42
  document.body.style.width = "100%";
38
- if (isIOS) {
39
- document.body.style.setProperty("-webkit-overflow-scrolling", "touch");
43
+ if (scrollbarWidth > 0) document.body.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;
44
+ if (isIOS()) document.body.style.setProperty("-webkit-overflow-scrolling", "touch");
45
+ }
46
+ lockCount += 1;
47
+ };
48
+ const unlockScroll = () => {
49
+ lockCount = Math.max(0, lockCount - 1);
50
+ if (lockCount > 0 || !originalStyles) return;
51
+ document.documentElement.style.overflow = originalStyles.documentElement.overflow;
52
+ document.documentElement.style.height = originalStyles.documentElement.height;
53
+ document.documentElement.style.position = originalStyles.documentElement.position;
54
+ document.documentElement.style.width = originalStyles.documentElement.width;
55
+ document.body.style.overflow = originalStyles.body.overflow;
56
+ document.body.style.height = originalStyles.body.height;
57
+ document.body.style.position = originalStyles.body.position;
58
+ document.body.style.top = originalStyles.body.top;
59
+ document.body.style.left = originalStyles.body.left;
60
+ document.body.style.right = originalStyles.body.right;
61
+ document.body.style.width = originalStyles.body.width;
62
+ document.body.style.paddingRight = originalStyles.body.paddingRight;
63
+ if (originalStyles.body.webkitOverflowScrolling) document.body.style.setProperty("-webkit-overflow-scrolling", originalStyles.body.webkitOverflowScrolling);
64
+ else document.body.style.removeProperty("-webkit-overflow-scrolling");
65
+ window.scrollTo(0, originalScrollY);
66
+ originalStyles = null;
67
+ };
68
+ const useBodyScrollLock = (enabled = true) => {
69
+ useEffect(() => {
70
+ if (!enabled) return;
71
+ lockScroll();
72
+ if (isIOS()) {
40
73
  const preventAll = (e) => {
41
74
  if (e.target === document.body || e.target === document.documentElement || e.target === window) {
42
75
  e.preventDefault();
@@ -77,39 +110,15 @@ const useBodyScrollLock = (enabled = true) => {
77
110
  document.removeEventListener(event, preventAll, { capture: true });
78
111
  document.body.removeEventListener(event, preventAll, { capture: true });
79
112
  });
80
- document.documentElement.style.overflow = originalStyles.documentElement.overflow;
81
- document.documentElement.style.height = originalStyles.documentElement.height;
82
- document.documentElement.style.position = originalStyles.documentElement.position;
83
- document.documentElement.style.width = originalStyles.documentElement.width;
84
- document.body.style.overflow = originalStyles.body.overflow;
85
- document.body.style.height = originalStyles.body.height;
86
- document.body.style.position = originalStyles.body.position;
87
- document.body.style.top = originalStyles.body.top;
88
- document.body.style.left = originalStyles.body.left;
89
- document.body.style.right = originalStyles.body.right;
90
- document.body.style.width = originalStyles.body.width;
91
- if (originalStyles.body.webkitOverflowScrolling) document.body.style.setProperty("-webkit-overflow-scrolling", originalStyles.body.webkitOverflowScrolling);
92
- else document.body.style.removeProperty("-webkit-overflow-scrolling");
93
- window.scrollTo(0, scrollY);
113
+ unlockScroll();
94
114
  };
95
115
  }
96
116
  return () => {
97
- document.documentElement.style.overflow = originalStyles.documentElement.overflow;
98
- document.documentElement.style.height = originalStyles.documentElement.height;
99
- document.documentElement.style.position = originalStyles.documentElement.position;
100
- document.documentElement.style.width = originalStyles.documentElement.width;
101
- document.body.style.overflow = originalStyles.body.overflow;
102
- document.body.style.height = originalStyles.body.height;
103
- document.body.style.position = originalStyles.body.position;
104
- document.body.style.top = originalStyles.body.top;
105
- document.body.style.left = originalStyles.body.left;
106
- document.body.style.right = originalStyles.body.right;
107
- document.body.style.width = originalStyles.body.width;
108
- window.scrollTo(0, scrollY);
117
+ unlockScroll();
109
118
  };
110
119
  }, [enabled]);
111
120
  };
112
121
 
113
122
  //#endregion
114
123
  export { useBodyScrollLock as default };
115
- //# sourceMappingURL=index.mjs.map
124
+ //# sourceMappingURL=use-body-scroll-lock.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-body-scroll-lock.mjs","names":[],"sources":["../../src/hooks/use-body-scroll-lock.ts"],"sourcesContent":["import { useEffect } from 'react';\n\ninterface LockedStyles {\n documentElement: {\n overflow: string;\n height: string;\n position: string;\n width: string;\n };\n body: {\n overflow: string;\n height: string;\n position: string;\n top: string;\n left: string;\n right: string;\n width: string;\n paddingRight: string;\n webkitOverflowScrolling: string;\n };\n}\n\nlet lockCount = 0;\nlet originalStyles: LockedStyles | null = null;\nlet originalScrollY = 0;\n\nconst isIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent);\n\nconst lockScroll = () => {\n if (lockCount === 0) {\n originalScrollY = window.scrollY;\n const scrollbarWidth =\n window.innerWidth - document.documentElement.clientWidth;\n const currentPaddingRight =\n parseFloat(getComputedStyle(document.body).paddingRight) || 0;\n\n originalStyles = {\n documentElement: {\n overflow: document.documentElement.style.overflow,\n height: document.documentElement.style.height,\n position: document.documentElement.style.position,\n width: document.documentElement.style.width,\n },\n body: {\n overflow: document.body.style.overflow,\n height: document.body.style.height,\n position: document.body.style.position,\n top: document.body.style.top,\n left: document.body.style.left,\n right: document.body.style.right,\n width: document.body.style.width,\n paddingRight: document.body.style.paddingRight,\n webkitOverflowScrolling: document.body.style.getPropertyValue(\n '-webkit-overflow-scrolling',\n ),\n },\n };\n\n document.documentElement.style.overflow = 'hidden';\n document.documentElement.style.height = '100%';\n document.documentElement.style.position = 'fixed';\n document.documentElement.style.width = '100%';\n\n document.body.style.overflow = 'hidden';\n document.body.style.height = '100%';\n document.body.style.position = 'fixed';\n document.body.style.top = `-${originalScrollY}px`;\n document.body.style.left = '0';\n document.body.style.right = '0';\n document.body.style.width = '100%';\n\n if (scrollbarWidth > 0) {\n document.body.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;\n }\n\n if (isIOS()) {\n document.body.style.setProperty('-webkit-overflow-scrolling', 'touch');\n }\n }\n\n lockCount += 1;\n};\n\nconst unlockScroll = () => {\n lockCount = Math.max(0, lockCount - 1);\n\n if (lockCount > 0 || !originalStyles) {\n return;\n }\n\n document.documentElement.style.overflow =\n originalStyles.documentElement.overflow;\n document.documentElement.style.height = originalStyles.documentElement.height;\n document.documentElement.style.position =\n originalStyles.documentElement.position;\n document.documentElement.style.width = originalStyles.documentElement.width;\n\n document.body.style.overflow = originalStyles.body.overflow;\n document.body.style.height = originalStyles.body.height;\n document.body.style.position = originalStyles.body.position;\n document.body.style.top = originalStyles.body.top;\n document.body.style.left = originalStyles.body.left;\n document.body.style.right = originalStyles.body.right;\n document.body.style.width = originalStyles.body.width;\n document.body.style.paddingRight = originalStyles.body.paddingRight;\n\n if (originalStyles.body.webkitOverflowScrolling) {\n document.body.style.setProperty(\n '-webkit-overflow-scrolling',\n originalStyles.body.webkitOverflowScrolling,\n );\n } else {\n document.body.style.removeProperty('-webkit-overflow-scrolling');\n }\n\n window.scrollTo(0, originalScrollY);\n originalStyles = null;\n};\n\nconst useBodyScrollLock = (enabled: boolean = true) => {\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n lockScroll();\n\n if (isIOS()) {\n const preventAll = (e: Event) => {\n if (\n e.target === document.body ||\n e.target === document.documentElement ||\n e.target === window\n ) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n }\n };\n\n const preventScroll = () => {\n window.scrollTo(0, 0);\n document.body.scrollTop = 0;\n document.documentElement.scrollTop = 0;\n };\n\n const events = ['scroll', 'touchmove', 'touchstart', 'touchend'];\n events.forEach(event => {\n window.addEventListener(event, preventAll, {\n passive: false,\n capture: true,\n });\n document.addEventListener(event, preventAll, {\n passive: false,\n capture: true,\n });\n document.body.addEventListener(event, preventAll, {\n passive: false,\n capture: true,\n });\n });\n\n const scrollInterval = setInterval(preventScroll, 16);\n\n return () => {\n clearInterval(scrollInterval);\n events.forEach(event => {\n window.removeEventListener(event, preventAll, { capture: true });\n document.removeEventListener(event, preventAll, { capture: true });\n document.body.removeEventListener(event, preventAll, {\n capture: true,\n });\n });\n unlockScroll();\n };\n }\n\n return () => {\n unlockScroll();\n };\n }, [enabled]);\n};\n\nexport default useBodyScrollLock;\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import * as react from "react";
2
+
3
+ //#region src/hooks/use-click-outside.d.ts
4
+ declare const useClickOutside: <T extends HTMLElement = HTMLElement>(handler: (event: MouseEvent | TouchEvent) => void, enabled?: boolean) => react.RefObject<T | null>;
5
+ //#endregion
6
+ export { useClickOutside };
7
+ //# sourceMappingURL=use-click-outside.d.mts.map
@@ -0,0 +1,29 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/use-click-outside.ts
4
+ const useClickOutside = (handler, enabled = true) => {
5
+ const ref = useRef(null);
6
+ const handlerRef = useRef(handler);
7
+ useEffect(() => {
8
+ handlerRef.current = handler;
9
+ });
10
+ useEffect(() => {
11
+ if (!enabled || typeof document === "undefined") return;
12
+ const listener = (event) => {
13
+ const el = ref.current;
14
+ if (!el || el.contains(event.target)) return;
15
+ handlerRef.current(event);
16
+ };
17
+ document.addEventListener("mousedown", listener);
18
+ document.addEventListener("touchstart", listener);
19
+ return () => {
20
+ document.removeEventListener("mousedown", listener);
21
+ document.removeEventListener("touchstart", listener);
22
+ };
23
+ }, [enabled]);
24
+ return ref;
25
+ };
26
+
27
+ //#endregion
28
+ export { useClickOutside as default };
29
+ //# sourceMappingURL=use-click-outside.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-click-outside.mjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nconst useClickOutside = <T extends HTMLElement = HTMLElement>(\n handler: (event: MouseEvent | TouchEvent) => void,\n enabled = true,\n) => {\n const ref = useRef<T>(null);\n const handlerRef = useRef(handler);\n\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n useEffect(() => {\n if (!enabled || typeof document === 'undefined') {\n return;\n }\n\n const listener = (event: MouseEvent | TouchEvent) => {\n const el = ref.current;\n if (!el || el.contains(event.target as Node)) {\n return;\n }\n handlerRef.current(event);\n };\n\n document.addEventListener('mousedown', listener);\n document.addEventListener('touchstart', listener);\n\n return () => {\n document.removeEventListener('mousedown', listener);\n document.removeEventListener('touchstart', listener);\n };\n }, [enabled]);\n\n return ref;\n};\n\nexport default useClickOutside;\n"],"mappings":";;;AAEA,MAAM,mBACJ,SACA,UAAU,SACP;CACH,MAAM,MAAM,OAAU,KAAK;CAC3B,MAAM,aAAa,OAAO,QAAQ;AAElC,iBAAgB;AACd,aAAW,UAAU;GACrB;AAEF,iBAAgB;AACd,MAAI,CAAC,WAAW,OAAO,aAAa,YAClC;EAGF,MAAM,YAAY,UAAmC;GACnD,MAAM,KAAK,IAAI;AACf,OAAI,CAAC,MAAM,GAAG,SAAS,MAAM,OAAe,CAC1C;AAEF,cAAW,QAAQ,MAAM;;AAG3B,WAAS,iBAAiB,aAAa,SAAS;AAChD,WAAS,iBAAiB,cAAc,SAAS;AAEjD,eAAa;AACX,YAAS,oBAAoB,aAAa,SAAS;AACnD,YAAS,oBAAoB,cAAc,SAAS;;IAErD,CAAC,QAAQ,CAAC;AAEb,QAAO"}
@@ -1,4 +1,4 @@
1
- //#region src/hooks/useDebounce/index.d.ts
1
+ //#region src/hooks/use-debounce.d.ts
2
2
  interface Options {
3
3
  delay?: number;
4
4
  autoInvoke?: boolean;
@@ -9,4 +9,4 @@ declare const useDebounce: <T extends (...args: unknown[]) => unknown>(callback:
9
9
  }: Options, deps?: React.DependencyList) => T;
10
10
  //#endregion
11
11
  export { useDebounce };
12
- //# sourceMappingURL=index.d.mts.map
12
+ //# sourceMappingURL=use-debounce.d.mts.map
@@ -1,17 +1,13 @@
1
1
  import { useEffect, useRef } from "react";
2
2
 
3
- //#region src/hooks/useDebounce/index.ts
3
+ //#region src/hooks/use-debounce.ts
4
4
  const useDebounce = (callback, { delay = 100, autoInvoke = true }, deps = []) => {
5
5
  const timeoutRef = useRef(null);
6
6
  const callbackRef = useRef(callback);
7
- const depsRef = useRef(deps);
8
7
  const prevDeps = useRef(void 0);
9
8
  useEffect(() => {
10
9
  callbackRef.current = callback;
11
10
  });
12
- useEffect(() => {
13
- depsRef.current = deps;
14
- });
15
11
  const stableDebouncedCallback = useRef(null);
16
12
  if (!stableDebouncedCallback.current) stableDebouncedCallback.current = ((...args) => {
17
13
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -38,4 +34,4 @@ const useDebounce = (callback, { delay = 100, autoInvoke = true }, deps = []) =>
38
34
 
39
35
  //#endregion
40
36
  export { useDebounce as default };
41
- //# sourceMappingURL=index.mjs.map
37
+ //# sourceMappingURL=use-debounce.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-debounce.mjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface Options {\n delay?: number;\n autoInvoke?: boolean;\n}\n\nconst useDebounce = <T extends (...args: unknown[]) => unknown>(\n callback: T,\n { delay = 100, autoInvoke = true }: Options,\n deps: React.DependencyList = [],\n): T => {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n const prevDeps = useRef<React.DependencyList | undefined>(undefined);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n const stableDebouncedCallback = useRef<T | null>(null);\n\n if (!stableDebouncedCallback.current) {\n stableDebouncedCallback.current = ((...args: Parameters<T>) => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n }, delay);\n }) as T;\n }\n\n useEffect(() => {\n const depsChanged =\n prevDeps.current === undefined ||\n prevDeps.current.length !== deps.length ||\n prevDeps.current.some((dep, i) => dep !== deps[i]);\n\n if (depsChanged && timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n if (autoInvoke && depsChanged) {\n const isFirstRender = prevDeps.current === undefined;\n if (isFirstRender) {\n callbackRef.current();\n } else if (stableDebouncedCallback.current) {\n stableDebouncedCallback.current();\n }\n }\n\n prevDeps.current = deps;\n });\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return stableDebouncedCallback.current;\n};\n\nexport default useDebounce;\n"],"mappings":";;;AAOA,MAAM,eACJ,UACA,EAAE,QAAQ,KAAK,aAAa,QAC5B,OAA6B,EAAE,KACzB;CACN,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CACpC,MAAM,WAAW,OAAyC,OAAU;AAEpE,iBAAgB;AACd,cAAY,UAAU;GACtB;CAEF,MAAM,0BAA0B,OAAiB,KAAK;AAEtD,KAAI,CAAC,wBAAwB,QAC3B,yBAAwB,YAAY,GAAG,SAAwB;AAC7D,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAGlC,aAAW,UAAU,iBAAiB;AACpC,eAAY,QAAQ,GAAG,KAAK;KAC3B,MAAM;;AAIb,iBAAgB;EACd,MAAM,cACJ,SAAS,YAAY,UACrB,SAAS,QAAQ,WAAW,KAAK,UACjC,SAAS,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG;AAEpD,MAAI,eAAe,WAAW,QAC5B,cAAa,WAAW,QAAQ;AAGlC,MAAI,cAAc,aAEhB;OADsB,SAAS,YAAY,OAEzC,aAAY,SAAS;YACZ,wBAAwB,QACjC,yBAAwB,SAAS;;AAIrC,WAAS,UAAU;GACnB;AAEF,iBAAgB;AACd,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;AAEN,QAAO,wBAAwB"}
@@ -1,8 +1,8 @@
1
1
  import { RefObject } from "react";
2
2
 
3
- //#region src/hooks/useElementPosition/index.d.ts
3
+ //#region src/hooks/use-element-position.d.ts
4
4
  type ElementReference<T> = string | RefObject<T>;
5
5
  declare const useElementPosition: <T>(elementRef: ElementReference<T>) => DOMRect | null;
6
6
  //#endregion
7
7
  export { useElementPosition };
8
- //# sourceMappingURL=index.d.mts.map
8
+ //# sourceMappingURL=use-element-position.d.mts.map
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
2
 
3
- //#region src/hooks/useElementPosition/index.ts
3
+ //#region src/hooks/use-element-position.ts
4
4
  const useElementPosition = (elementRef) => {
5
5
  const [rect, setRect] = useState(null);
6
6
  useEffect(() => {
@@ -29,4 +29,4 @@ const useElementPosition = (elementRef) => {
29
29
 
30
30
  //#endregion
31
31
  export { useElementPosition as default };
32
- //# sourceMappingURL=index.mjs.map
32
+ //# sourceMappingURL=use-element-position.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-element-position.mjs","names":[],"sources":["../../src/hooks/use-element-position.ts"],"sourcesContent":["import { type RefObject, useEffect, useState } from 'react';\n\ntype ElementReference<T> = string | RefObject<T>;\n\nconst useElementPosition = <T>(elementRef: ElementReference<T>) => {\n const [rect, setRect] = useState<DOMRect | null>(null);\n\n useEffect(() => {\n const getElement = (ref: ElementReference<T>): T | null => {\n if (typeof ref === 'string') {\n return document.querySelector(ref) as T;\n }\n return ref.current;\n };\n\n const updateRect = () => {\n const element = getElement(elementRef) as HTMLElement | null;\n if (element) {\n setRect(element.getBoundingClientRect());\n } else {\n setRect(null);\n }\n };\n\n const onUpdate = () => {\n requestAnimationFrame(updateRect);\n };\n\n updateRect();\n\n window.addEventListener('scroll', onUpdate, { passive: true });\n window.addEventListener('resize', onUpdate, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', onUpdate);\n window.removeEventListener('resize', onUpdate);\n };\n }, [elementRef]);\n\n return rect;\n};\n\nexport default useElementPosition;\n"],"mappings":";;;AAIA,MAAM,sBAAyB,eAAoC;CACjE,MAAM,CAAC,MAAM,WAAW,SAAyB,KAAK;AAEtD,iBAAgB;EACd,MAAM,cAAc,QAAuC;AACzD,OAAI,OAAO,QAAQ,SACjB,QAAO,SAAS,cAAc,IAAI;AAEpC,UAAO,IAAI;;EAGb,MAAM,mBAAmB;GACvB,MAAM,UAAU,WAAW,WAAW;AACtC,OAAI,QACF,SAAQ,QAAQ,uBAAuB,CAAC;OAExC,SAAQ,KAAK;;EAIjB,MAAM,iBAAiB;AACrB,yBAAsB,WAAW;;AAGnC,cAAY;AAEZ,SAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,MAAM,CAAC;AAC9D,SAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,MAAM,CAAC;AAE9D,eAAa;AACX,UAAO,oBAAoB,UAAU,SAAS;AAC9C,UAAO,oBAAoB,UAAU,SAAS;;IAE/C,CAAC,WAAW,CAAC;AAEhB,QAAO"}
@@ -1,4 +1,4 @@
1
- //#region src/hooks/useElementScroll/index.d.ts
1
+ //#region src/hooks/use-element-scroll.d.ts
2
2
  declare const useElementScroll: () => {
3
3
  element: HTMLElement | null;
4
4
  setRef: (el: HTMLElement | null) => void;
@@ -12,4 +12,4 @@ declare const useElementScroll: () => {
12
12
  };
13
13
  //#endregion
14
14
  export { useElementScroll };
15
- //# sourceMappingURL=index.d.mts.map
15
+ //# sourceMappingURL=use-element-scroll.d.mts.map
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
 
3
- //#region src/hooks/useElementScroll/index.ts
3
+ //#region src/hooks/use-element-scroll.ts
4
4
  const useElementScroll = () => {
5
5
  const [element, setElement] = useState(null);
6
6
  const [scrollPosition, setScrollPosition] = useState({
@@ -65,4 +65,4 @@ const useElementScroll = () => {
65
65
 
66
66
  //#endregion
67
67
  export { useElementScroll as default };
68
- //# sourceMappingURL=index.mjs.map
68
+ //# sourceMappingURL=use-element-scroll.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-element-scroll.mjs","names":[],"sources":["../../src/hooks/use-element-scroll.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\n\ninterface ScrollPosition {\n scrollY: number;\n scrollPercentage: number;\n isAtTop: boolean;\n isAtBottom: boolean;\n scrollableHeight: number;\n clientHeight: number;\n scrollHeight: number;\n}\n\nconst useElementScroll = () => {\n const [element, setElement] = useState<HTMLElement | null>(null);\n const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({\n scrollY: 0,\n scrollPercentage: 0,\n isAtTop: true,\n isAtBottom: false,\n scrollableHeight: 0,\n clientHeight: 0,\n scrollHeight: 0,\n });\n\n const setRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n useEffect(() => {\n if (!element) {\n return;\n }\n\n const updateScrollPosition = () => {\n const { scrollTop, scrollHeight, clientHeight } = element;\n const scrollableHeight = scrollHeight - clientHeight;\n\n if (scrollableHeight <= 0) {\n setScrollPosition({\n scrollY: 0,\n scrollPercentage: 0,\n isAtTop: true,\n isAtBottom: true,\n scrollableHeight: 0,\n clientHeight,\n scrollHeight,\n });\n return;\n }\n\n const percentage = Math.min(\n 100,\n Math.max(0, (scrollTop / scrollableHeight) * 100),\n );\n\n setScrollPosition({\n scrollY: scrollTop,\n scrollPercentage: percentage,\n isAtTop: scrollTop <= 0,\n isAtBottom: scrollTop >= scrollableHeight - 1,\n scrollableHeight,\n clientHeight,\n scrollHeight,\n });\n };\n\n updateScrollPosition();\n\n const onScroll = () => {\n updateScrollPosition();\n };\n\n element.addEventListener('scroll', onScroll, { passive: true });\n\n const resizeObserver = new ResizeObserver(() => {\n updateScrollPosition();\n });\n\n resizeObserver.observe(element);\n\n return () => {\n element.removeEventListener('scroll', onScroll);\n resizeObserver.unobserve(element);\n };\n }, [element]);\n\n return { ...scrollPosition, element, setRef };\n};\n\nexport default useElementScroll;\n"],"mappings":";;;AAYA,MAAM,yBAAyB;CAC7B,MAAM,CAAC,SAAS,cAAc,SAA6B,KAAK;CAChE,MAAM,CAAC,gBAAgB,qBAAqB,SAAyB;EACnE,SAAS;EACT,kBAAkB;EAClB,SAAS;EACT,YAAY;EACZ,kBAAkB;EAClB,cAAc;EACd,cAAc;EACf,CAAC;CAEF,MAAM,SAAS,aAAa,OAA2B;AACrD,aAAW,GAAG;IACb,EAAE,CAAC;AAEN,iBAAgB;AACd,MAAI,CAAC,QACH;EAGF,MAAM,6BAA6B;GACjC,MAAM,EAAE,WAAW,cAAc,iBAAiB;GAClD,MAAM,mBAAmB,eAAe;AAExC,OAAI,oBAAoB,GAAG;AACzB,sBAAkB;KAChB,SAAS;KACT,kBAAkB;KAClB,SAAS;KACT,YAAY;KACZ,kBAAkB;KAClB;KACA;KACD,CAAC;AACF;;AAQF,qBAAkB;IAChB,SAAS;IACT,kBAPiB,KAAK,IACtB,KACA,KAAK,IAAI,GAAI,YAAY,mBAAoB,IAAI,CAClD;IAKC,SAAS,aAAa;IACtB,YAAY,aAAa,mBAAmB;IAC5C;IACA;IACA;IACD,CAAC;;AAGJ,wBAAsB;EAEtB,MAAM,iBAAiB;AACrB,yBAAsB;;AAGxB,UAAQ,iBAAiB,UAAU,UAAU,EAAE,SAAS,MAAM,CAAC;EAE/D,MAAM,iBAAiB,IAAI,qBAAqB;AAC9C,yBAAsB;IACtB;AAEF,iBAAe,QAAQ,QAAQ;AAE/B,eAAa;AACX,WAAQ,oBAAoB,UAAU,SAAS;AAC/C,kBAAe,UAAU,QAAQ;;IAElC,CAAC,QAAQ,CAAC;AAEb,QAAO;EAAE,GAAG;EAAgB;EAAS;EAAQ"}
@@ -1,4 +1,4 @@
1
- //#region src/hooks/useImage/index.d.ts
1
+ //#region src/hooks/use-image.d.ts
2
2
  interface Options {
3
3
  retryCount?: number;
4
4
  retryDelay?: number;
@@ -11,4 +11,4 @@ declare const useImage: (src: string, options?: Options) => {
11
11
  };
12
12
  //#endregion
13
13
  export { useImage };
14
- //# sourceMappingURL=index.d.mts.map
14
+ //# sourceMappingURL=use-image.d.mts.map
@@ -1,56 +1,64 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
 
3
- //#region src/hooks/useImage/index.ts
3
+ //#region src/hooks/use-image.ts
4
4
  const useImage = (src, options = {}) => {
5
5
  const { retryCount = 0, retryDelay = 1e3 } = options;
6
6
  const [loading, setLoading] = useState(true);
7
7
  const [error, setError] = useState(null);
8
8
  const [loaded, setLoaded] = useState(false);
9
9
  const [attemptCount, setAttemptCount] = useState(0);
10
- const loadImage = useCallback(() => {
10
+ const [reloadToken, setReloadToken] = useState(0);
11
+ useEffect(() => {
11
12
  if (!src) {
12
13
  setLoading(false);
13
14
  setLoaded(false);
14
15
  return;
15
16
  }
17
+ let cancelled = false;
18
+ let retryTimeoutId;
16
19
  setLoading(true);
17
20
  setError(null);
18
21
  const img = new Image();
19
22
  img.src = src;
20
23
  img.onload = () => {
24
+ if (cancelled) return;
21
25
  setLoading(false);
22
26
  setLoaded(true);
23
27
  setError(null);
24
- setAttemptCount(0);
25
28
  };
26
29
  img.onerror = (err) => {
30
+ if (cancelled) return;
27
31
  setLoading(false);
28
32
  setLoaded(false);
29
33
  setError(err);
30
- if (attemptCount < retryCount) setTimeout(() => {
31
- setAttemptCount((prev) => prev + 1);
34
+ if (attemptCount < retryCount) retryTimeoutId = setTimeout(() => {
35
+ if (!cancelled) setAttemptCount((prev) => prev + 1);
32
36
  }, retryDelay);
33
37
  };
38
+ return () => {
39
+ cancelled = true;
40
+ img.onload = null;
41
+ img.onerror = null;
42
+ if (retryTimeoutId) clearTimeout(retryTimeoutId);
43
+ };
34
44
  }, [
35
45
  src,
36
46
  attemptCount,
37
47
  retryCount,
38
- retryDelay
48
+ retryDelay,
49
+ reloadToken
39
50
  ]);
40
- useEffect(() => {
41
- loadImage();
42
- }, [loadImage]);
43
51
  return {
44
52
  loading,
45
53
  error,
46
54
  loaded,
47
55
  retry: useCallback(() => {
48
56
  setAttemptCount(0);
49
- loadImage();
50
- }, [loadImage])
57
+ setReloadToken((token) => token + 1);
58
+ }, [])
51
59
  };
52
60
  };
53
61
 
54
62
  //#endregion
55
63
  export { useImage as default };
56
- //# sourceMappingURL=index.mjs.map
64
+ //# sourceMappingURL=use-image.mjs.map