@jbpark/use-hooks 4.0.0 → 4.0.2

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 (97) hide show
  1. package/README.ko.md +22 -10
  2. package/README.md +24 -12
  3. package/dist/hooks/index.d.mts +31 -30
  4. package/dist/hooks/index.mjs +1 -2
  5. package/dist/hooks/use-body-scroll-lock.d.mts +1 -1
  6. package/dist/hooks/use-body-scroll-lock.mjs +1 -5
  7. package/dist/hooks/use-body-scroll-lock.mjs.map +1 -1
  8. package/dist/hooks/use-click-outside.d.mts +1 -2
  9. package/dist/hooks/use-click-outside.mjs +3 -3
  10. package/dist/hooks/use-click-outside.mjs.map +1 -1
  11. package/dist/hooks/use-controllable-state.d.mts +1 -1
  12. package/dist/hooks/use-controllable-state.mjs +1 -2
  13. package/dist/hooks/use-controllable-state.mjs.map +1 -1
  14. package/dist/hooks/use-debounced-callback.d.mts +2 -6
  15. package/dist/hooks/use-debounced-callback.mjs +9 -9
  16. package/dist/hooks/use-debounced-callback.mjs.map +1 -1
  17. package/dist/hooks/use-debounced-value.d.mts +1 -1
  18. package/dist/hooks/use-debounced-value.mjs +1 -2
  19. package/dist/hooks/use-debounced-value.mjs.map +1 -1
  20. package/dist/hooks/use-element-position.d.mts +1 -2
  21. package/dist/hooks/use-element-position.mjs +1 -2
  22. package/dist/hooks/use-element-position.mjs.map +1 -1
  23. package/dist/hooks/use-element-scroll.d.mts +2 -4
  24. package/dist/hooks/use-element-scroll.mjs +3 -3
  25. package/dist/hooks/use-element-scroll.mjs.map +1 -1
  26. package/dist/hooks/use-event-listener.d.mts +1 -2
  27. package/dist/hooks/use-event-listener.mjs +1 -2
  28. package/dist/hooks/use-event-listener.mjs.map +1 -1
  29. package/dist/hooks/use-file-drop.d.mts +1 -1
  30. package/dist/hooks/use-file-drop.mjs +7 -6
  31. package/dist/hooks/use-file-drop.mjs.map +1 -1
  32. package/dist/hooks/use-file-to-data-url.d.mts +1 -1
  33. package/dist/hooks/use-file-to-data-url.mjs +1 -2
  34. package/dist/hooks/use-file-to-data-url.mjs.map +1 -1
  35. package/dist/hooks/use-history-state.d.mts +1 -1
  36. package/dist/hooks/use-history-state.mjs +3 -3
  37. package/dist/hooks/use-history-state.mjs.map +1 -1
  38. package/dist/hooks/use-image.d.mts +1 -1
  39. package/dist/hooks/use-image.mjs +24 -8
  40. package/dist/hooks/use-image.mjs.map +1 -1
  41. package/dist/hooks/use-intersection-observer.d.mts +1 -1
  42. package/dist/hooks/use-intersection-observer.mjs +1 -2
  43. package/dist/hooks/use-intersection-observer.mjs.map +1 -1
  44. package/dist/hooks/use-interval.d.mts +1 -1
  45. package/dist/hooks/use-interval.mjs +1 -2
  46. package/dist/hooks/use-interval.mjs.map +1 -1
  47. package/dist/hooks/use-key-press.d.mts +1 -2
  48. package/dist/hooks/use-key-press.mjs +7 -5
  49. package/dist/hooks/use-key-press.mjs.map +1 -1
  50. package/dist/hooks/use-local-storage.d.mts +1 -1
  51. package/dist/hooks/use-local-storage.mjs +5 -3
  52. package/dist/hooks/use-local-storage.mjs.map +1 -1
  53. package/dist/hooks/use-merged-ref.d.mts +1 -2
  54. package/dist/hooks/use-merged-ref.mjs +1 -2
  55. package/dist/hooks/use-merged-ref.mjs.map +1 -1
  56. package/dist/hooks/use-multi-select.d.mts +1 -1
  57. package/dist/hooks/use-multi-select.mjs +10 -9
  58. package/dist/hooks/use-multi-select.mjs.map +1 -1
  59. package/dist/hooks/use-mutation-observer.d.mts +1 -2
  60. package/dist/hooks/use-mutation-observer.mjs +3 -3
  61. package/dist/hooks/use-mutation-observer.mjs.map +1 -1
  62. package/dist/hooks/use-previous.d.mts +1 -1
  63. package/dist/hooks/use-previous.mjs +1 -2
  64. package/dist/hooks/use-previous.mjs.map +1 -1
  65. package/dist/hooks/use-recursive-timeout.d.mts +1 -1
  66. package/dist/hooks/use-recursive-timeout.mjs +1 -2
  67. package/dist/hooks/use-recursive-timeout.mjs.map +1 -1
  68. package/dist/hooks/use-resize-observer.d.mts +1 -1
  69. package/dist/hooks/use-resize-observer.mjs +1 -2
  70. package/dist/hooks/use-resize-observer.mjs.map +1 -1
  71. package/dist/hooks/use-responsive-size.d.mts +1 -1
  72. package/dist/hooks/use-responsive-size.mjs +1 -2
  73. package/dist/hooks/use-responsive-size.mjs.map +1 -1
  74. package/dist/hooks/use-scroll-to-elements.d.mts +1 -2
  75. package/dist/hooks/use-scroll-to-elements.mjs +14 -3
  76. package/dist/hooks/use-scroll-to-elements.mjs.map +1 -1
  77. package/dist/hooks/use-throttled-callback.d.mts +2 -5
  78. package/dist/hooks/use-throttled-callback.mjs +6 -8
  79. package/dist/hooks/use-throttled-callback.mjs.map +1 -1
  80. package/dist/hooks/use-throttled-value.d.mts +1 -1
  81. package/dist/hooks/use-throttled-value.mjs +1 -2
  82. package/dist/hooks/use-throttled-value.mjs.map +1 -1
  83. package/dist/hooks/use-timeout.d.mts +1 -1
  84. package/dist/hooks/use-timeout.mjs +1 -2
  85. package/dist/hooks/use-timeout.mjs.map +1 -1
  86. package/dist/hooks/use-toggle.d.mts +1 -1
  87. package/dist/hooks/use-toggle.mjs +1 -2
  88. package/dist/hooks/use-toggle.mjs.map +1 -1
  89. package/dist/hooks/use-viewport.d.mts +1 -1
  90. package/dist/hooks/use-viewport.mjs +1 -2
  91. package/dist/hooks/use-viewport.mjs.map +1 -1
  92. package/dist/hooks/use-window-scroll.d.mts +1 -2
  93. package/dist/hooks/use-window-scroll.mjs +1 -2
  94. package/dist/hooks/use-window-scroll.mjs.map +1 -1
  95. package/dist/index.d.mts +30 -30
  96. package/dist/index.mjs +1 -2
  97. package/package.json +21 -30
package/README.ko.md CHANGED
@@ -7,7 +7,9 @@
7
7
  [![GitHub issues](https://img.shields.io/github/issues/pjb0811/use-hooks)](https://github.com/pjb0811/use-hooks/issues)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
- 일반적인 UI 및 상호작용 패턴을 위한 재사용 가능한 React 19 훅 모음입니다. TypeScript와 Vite로 빌드되었으며, 서버 사이드 렌더링과 클라이언트 사이드 애플리케이션 모두에 최적화되어 있습니다.
10
+ 📖 **문서 및 라이브 데모:** https://use-hooks-lab.vercel.app
11
+
12
+ 일반적인 UI 및 상호작용 패턴을 위한 재사용 가능한 React 19 훅 모음입니다. TypeScript로 빌드되었으며, 서버 사이드 렌더링과 클라이언트 사이드 애플리케이션 모두에 최적화되어 있습니다.
11
13
 
12
14
  ## 기능
13
15
 
@@ -80,7 +82,7 @@ function MyComponent() {
80
82
  | `useElementPosition` | 스크롤/리사이즈 시 요소의 바운딩 렉트 모니터링 (요소 참조 지원) |
81
83
  | `useResponsiveSize` | Tailwind 유사 브레이크포인트를 포함한 요소 크기 추적 (debounce) |
82
84
  | `useViewport` | visualViewport 지원, 인앱 모드 옵션, debounce 포함 |
83
- | `useScrollToElements` | 키로 요소를 등록하고 키로 스크롤 (오프셋 조절, 컨테이너 지정 가능) |
85
+ | `useScrollToElements` | 키로 요소를 등록하고 키로 스크롤 (오프셋 조절, 가장 가까운 스크롤 조상을 스크롤) |
84
86
  | `useBodyScrollLock` | 스타일 보존을 포함한 바디 스크롤 잠금/해제 (iOS 특별 처리) |
85
87
  | `useIntersectionObserver` | 뷰포트 교차 추적, `[ref, { entry, isIntersecting }]` 반환, `freezeOnceVisible` 옵션 |
86
88
  | `useResizeObserver` | 콜백 ref로 요소 자체의 너비/높이 추적 (content-box/border-box) |
@@ -108,22 +110,26 @@ v3.0.0은 breaking change가 여럿 포함된 메이저 릴리스입니다. 전
108
110
  ## 개발
109
111
 
110
112
  ```bash
111
- # HMR이 포함된 개발 서버 시작
112
- pnpm dev
113
-
114
- # 라이브러리 빌드 (tsc + vite)
113
+ # 라이브러리 빌드 (tsc + tsdown)
115
114
  pnpm build
116
115
 
117
- # 빌드된 라이브러리 미리보기
118
- pnpm preview
119
-
120
116
  # 린트 및 타입 체크
121
117
  pnpm lint
122
118
 
123
119
  # prettier로 포맷팅
124
120
  pnpm exec prettier --write .
121
+
122
+ # 문서/데모 사이트 시작 (각 훅의 데모는 해당 훅의 문서 페이지에 있습니다)
123
+ pnpm --dir website start
124
+
125
+ # 문서 사이트 의존성 설치 (별도의 pnpm 프로젝트입니다)
126
+ pnpm --dir website install
125
127
  ```
126
128
 
129
+ 두 `package.json` 모두 `packageManager`에 같은 pnpm 버전을 핀하고 있어,
130
+ `website/` 안에서 pnpm을 실행해도 `PATH`에 깔린 버전이 아니라 핀된 버전이
131
+ 쓰입니다. 버전을 올릴 때는 두 곳을 함께 올립니다.
132
+
127
133
  ## 프로젝트 구조
128
134
 
129
135
  ```
@@ -149,6 +155,9 @@ src/
149
155
  └── index.ts # 패키지 진입점
150
156
 
151
157
  dist/ # 빌드된 라이브러리 (ESM + types)
158
+ website/ # Docusaurus 문서 및 데모 사이트
159
+ └── src/demo/ # 훅별 인터랙티브 데모 컴포넌트 1개씩
160
+ └── use-x-demo.tsx # 해당 훅의 문서 페이지에 그대로 렌더링됨
152
161
  ```
153
162
 
154
163
  ## 빌드 및 배포
@@ -164,10 +173,13 @@ dist/ # 빌드된 라이브러리 (ESM + types)
164
173
  - **ES Module**: `dist/index.mjs`
165
174
  - **타입 정의**: `dist/index.d.ts`
166
175
 
176
+ Docusaurus 문서 사이트는 `website/`에서 빌드되어 Vercel로 배포됩니다
177
+ (`vercel.json` 참고).
178
+
167
179
  ## 주요 패턴
168
180
 
169
181
  - **훅 1개 = 파일 1개**: 각 훅은 `src/hooks/use-x.ts` 형태의 단일 파일로 존재하며 (폴더/`index.ts` 없음), `src/hooks/index.ts`에서 재수출됩니다
170
- - **데모 파일 네이밍**: 각 훅의 데모 페이지 파일명은 훅 파일명에 `-demo`를 붙인 `src/demo/use-x-demo.tsx` 형태를 따릅니다 (컴포넌트명 자체는 기존처럼 PascalCase 유지, 예: `ClickOutsideDemo`)
182
+ - **데모 파일 네이밍**: 각 훅의 데모 페이지 파일명은 훅 파일명에 `-demo`를 붙인 `website/src/demo/use-x-demo.tsx` 형태를 따릅니다 (컴포넌트명 자체는 기존처럼 PascalCase 유지, 예: `ClickOutsideDemo`)
171
183
  - **Window 보호**: `window`/`document`에 접근하는 훅은 SSR 안전성을 위해 `typeof window` 체크 (예: `useLocalStorage`)
172
184
  - **이벤트 리스너**: 모든 스크롤/리사이즈 리스너는 가능한 한 passive 플래그 사용
173
185
  - **ResizeObserver**: `useResponsiveSize`와 `useElementPosition`에서 사용하여 성능 최적화
package/README.md CHANGED
@@ -7,7 +7,9 @@
7
7
  [![GitHub issues](https://img.shields.io/github/issues/pjb0811/use-hooks)](https://github.com/pjb0811/use-hooks/issues)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
- A collection of reusable React 19 hooks for common UI and interaction patterns. Built with TypeScript and Vite, optimized for both server-side rendering and client-side applications.
10
+ 📖 **Documentation & live demos:** https://use-hooks-lab.vercel.app
11
+
12
+ A collection of reusable React 19 hooks for common UI and interaction patterns. Built with TypeScript, optimized for both server-side rendering and client-side applications.
11
13
 
12
14
  ## Features
13
15
 
@@ -80,7 +82,7 @@ function MyComponent() {
80
82
  | `useElementPosition` | Monitor element bounding rect on scroll/resize (element ref support) |
81
83
  | `useResponsiveSize` | Track element size with Tailwind-like breakpoints (debounced) |
82
84
  | `useViewport` | visualViewport support with in-app mode option and debounce |
83
- | `useScrollToElements` | Register elements by key and scroll to them by key (adjustable offset, optional container) |
85
+ | `useScrollToElements` | Register elements by key and scroll to them by key (adjustable offset, scrolls the nearest scrollable ancestor) |
84
86
  | `useBodyScrollLock` | Lock/unlock body scroll with style preservation (iOS-specific handling) |
85
87
  | `useIntersectionObserver` | Track viewport intersection; returns `[ref, { entry, isIntersecting }]` with optional `freezeOnceVisible` |
86
88
  | `useResizeObserver` | Track an element's own width/height via a callback ref (content-box or border-box) |
@@ -108,22 +110,26 @@ v3.0.0 is a major release with several breaking changes. See the
108
110
  ## Development
109
111
 
110
112
  ```bash
111
- # Start development server with HMR
112
- pnpm dev
113
-
114
- # Build library (tsc + vite)
113
+ # Build library (tsc + tsdown)
115
114
  pnpm build
116
115
 
117
- # Preview built library
118
- pnpm preview
119
-
120
116
  # Run lint and type check
121
117
  pnpm lint
122
118
 
123
119
  # Format code with prettier
124
120
  pnpm exec prettier --write .
121
+
122
+ # Start the docs/demo site (each hook's demo lives on its docs page)
123
+ pnpm --dir website start
124
+
125
+ # Install the docs site's own dependencies (separate pnpm project)
126
+ pnpm --dir website install
125
127
  ```
126
128
 
129
+ Both `package.json` files pin `packageManager` to the same pnpm version, so
130
+ running pnpm inside `website/` picks up that version rather than whatever is
131
+ on `PATH`. Bump the two together.
132
+
127
133
  ## Project Structure
128
134
 
129
135
  ```
@@ -131,7 +137,7 @@ src/
131
137
  ├── hooks/ # Individual hook implementations (one file per hook)
132
138
  │ ├── use-body-scroll-lock.ts
133
139
  │ ├── use-click-outside.ts
134
- │ ├── use-debounce.ts
140
+ │ ├── use-debounced-value.ts
135
141
  │ ├── use-element-position.ts
136
142
  │ ├── use-element-scroll.ts
137
143
  │ ├── use-history-state.ts
@@ -142,13 +148,16 @@ src/
142
148
  │ ├── use-recursive-timeout.ts
143
149
  │ ├── use-responsive-size.ts
144
150
  │ ├── use-scroll-to-elements.ts
145
- │ ├── use-throttle.ts
151
+ │ ├── use-throttled-value.ts
146
152
  │ ├── use-viewport.ts
147
153
  │ ├── use-window-scroll.ts
148
154
  │ └── index.ts # Barrel export
149
155
  └── index.ts # Package entry point
150
156
 
151
157
  dist/ # Built library (ESM + types)
158
+ website/ # Docusaurus documentation & demo site
159
+ └── src/demo/ # One interactive demo component per hook
160
+ └── use-x-demo.tsx # Rendered directly on that hook's docs page
152
161
  ```
153
162
 
154
163
  ## Build & Deployment
@@ -164,10 +173,13 @@ The library is built as:
164
173
  - **ES Module**: `dist/index.mjs`
165
174
  - **Type Definitions**: `dist/index.d.ts`
166
175
 
176
+ The Docusaurus documentation site is built from `website/` and deployed via
177
+ Vercel (see `vercel.json`).
178
+
167
179
  ## Key Patterns
168
180
 
169
181
  - **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`
170
- - **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`)
182
+ - **Demo File Naming**: Each hook's demo page file is named `website/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`)
171
183
  - **Window Protection**: Hooks accessing `window`/`document` check `typeof window` for SSR safety (e.g., `useLocalStorage`)
172
184
  - **Event Listeners**: All scroll/resize listeners use passive flag when possible
173
185
  - **ResizeObserver**: Used in `useResponsiveSize` and `useElementPosition` for performance
@@ -1,30 +1,31 @@
1
- import { useDebouncedCallback } from "./use-debounced-callback.mjs";
2
- import { useDebouncedValue } from "./use-debounced-value.mjs";
3
- import { useBodyScrollLock } from "./use-body-scroll-lock.mjs";
4
- import { useClickOutside } from "./use-click-outside.mjs";
5
- import { useControllableState } from "./use-controllable-state.mjs";
6
- import { useElementPosition } from "./use-element-position.mjs";
7
- import { useElementScroll } from "./use-element-scroll.mjs";
8
- import { useEventListener } from "./use-event-listener.mjs";
9
- import { useFileDrop } from "./use-file-drop.mjs";
10
- import { useFileToDataUrl } from "./use-file-to-data-url.mjs";
11
- import { useHistoryState } from "./use-history-state.mjs";
12
- import { useIntersectionObserver } from "./use-intersection-observer.mjs";
13
- import { useInterval } from "./use-interval.mjs";
14
- import { useResponsiveSize } from "./use-responsive-size.mjs";
15
- import { useImage } from "./use-image.mjs";
16
- import { useKeyPress } from "./use-key-press.mjs";
17
- import { useLocalStorage } from "./use-local-storage.mjs";
18
- import { useMergedRef } from "./use-merged-ref.mjs";
19
- import { useMultiSelect } from "./use-multi-select.mjs";
20
- import { useMutationObserver } from "./use-mutation-observer.mjs";
21
- import { usePrevious } from "./use-previous.mjs";
22
- import { useRecursiveTimeout } from "./use-recursive-timeout.mjs";
23
- import { useResizeObserver } from "./use-resize-observer.mjs";
24
- import { useScrollToElements } from "./use-scroll-to-elements.mjs";
25
- import { useThrottledValue } from "./use-throttled-value.mjs";
26
- import { useThrottledCallback } from "./use-throttled-callback.mjs";
27
- import { useTimeout } from "./use-timeout.mjs";
28
- import { useToggle } from "./use-toggle.mjs";
29
- import { useWindowScroll } from "./use-window-scroll.mjs";
30
- import { useViewport } from "./use-viewport.mjs";
1
+ import useDebouncedCallback from "./use-debounced-callback.mjs";
2
+ import useDebouncedValue from "./use-debounced-value.mjs";
3
+ import useBodyScrollLock from "./use-body-scroll-lock.mjs";
4
+ import useClickOutside from "./use-click-outside.mjs";
5
+ import useControllableState from "./use-controllable-state.mjs";
6
+ import useElementPosition from "./use-element-position.mjs";
7
+ import useElementScroll from "./use-element-scroll.mjs";
8
+ import useEventListener from "./use-event-listener.mjs";
9
+ import useFileDrop from "./use-file-drop.mjs";
10
+ import useFileToDataUrl from "./use-file-to-data-url.mjs";
11
+ import useHistoryState from "./use-history-state.mjs";
12
+ import useIntersectionObserver from "./use-intersection-observer.mjs";
13
+ import useInterval from "./use-interval.mjs";
14
+ import useResponsiveSize from "./use-responsive-size.mjs";
15
+ import useImage from "./use-image.mjs";
16
+ import useKeyPress from "./use-key-press.mjs";
17
+ import useLocalStorage from "./use-local-storage.mjs";
18
+ import useMergedRef from "./use-merged-ref.mjs";
19
+ import useMultiSelect from "./use-multi-select.mjs";
20
+ import useMutationObserver from "./use-mutation-observer.mjs";
21
+ import usePrevious from "./use-previous.mjs";
22
+ import useRecursiveTimeout from "./use-recursive-timeout.mjs";
23
+ import useResizeObserver from "./use-resize-observer.mjs";
24
+ import useScrollToElements from "./use-scroll-to-elements.mjs";
25
+ import useThrottledValue from "./use-throttled-value.mjs";
26
+ import useThrottledCallback from "./use-throttled-callback.mjs";
27
+ import useTimeout from "./use-timeout.mjs";
28
+ import useToggle from "./use-toggle.mjs";
29
+ import useWindowScroll from "./use-window-scroll.mjs";
30
+ import useViewport from "./use-viewport.mjs";
31
+ export { useBodyScrollLock, useClickOutside, useControllableState, useDebouncedCallback as useDebounce, useDebouncedCallback, useDebouncedValue, useElementPosition, useElementScroll, useEventListener, useFileDrop, useFileToDataUrl, useHistoryState, useImage, useIntersectionObserver, useInterval, useKeyPress, useLocalStorage, useMergedRef, useMultiSelect, useMutationObserver, usePrevious, useRecursiveTimeout, useResizeObserver, useResponsiveSize, useScrollToElements, useThrottledValue as useThrottle, useThrottledCallback, useThrottledValue, useTimeout, useToggle, useViewport, useWindowScroll };
@@ -28,5 +28,4 @@ import useTimeout from "./use-timeout.mjs";
28
28
  import useToggle from "./use-toggle.mjs";
29
29
  import useWindowScroll from "./use-window-scroll.mjs";
30
30
  import useViewport from "./use-viewport.mjs";
31
-
32
- export { };
31
+ export { useBodyScrollLock, useClickOutside, useControllableState, useDebouncedCallback as useDebounce, useDebouncedCallback, useDebouncedValue, useElementPosition, useElementScroll, useEventListener, useFileDrop, useFileToDataUrl, useHistoryState, useImage, useIntersectionObserver, useInterval, useKeyPress, useLocalStorage, useMergedRef, useMultiSelect, useMutationObserver, usePrevious, useRecursiveTimeout, useResizeObserver, useResponsiveSize, useScrollToElements, useThrottledValue as useThrottle, useThrottledCallback, useThrottledValue, useTimeout, useToggle, useViewport, useWindowScroll };
@@ -1,5 +1,5 @@
1
1
  //#region src/hooks/use-body-scroll-lock.d.ts
2
2
  declare const useBodyScrollLock: (enabled?: boolean) => void;
3
3
  //#endregion
4
- export { useBodyScrollLock };
4
+ export { useBodyScrollLock as default };
5
5
  //# sourceMappingURL=use-body-scroll-lock.d.mts.map
@@ -1,5 +1,4 @@
1
1
  import { useEffect } from "react";
2
-
3
2
  //#region src/hooks/use-body-scroll-lock.ts
4
3
  const activeLocks = /* @__PURE__ */ new Set();
5
4
  let originalStyles = null;
@@ -18,7 +17,6 @@ const lockScroll = (token) => {
18
17
  width: document.documentElement.style.width
19
18
  },
20
19
  body: {
21
- overflow: document.body.style.overflow,
22
20
  height: document.body.style.height,
23
21
  position: document.body.style.position,
24
22
  top: document.body.style.top,
@@ -33,7 +31,6 @@ const lockScroll = (token) => {
33
31
  document.documentElement.style.height = "100%";
34
32
  document.documentElement.style.position = "fixed";
35
33
  document.documentElement.style.width = "100%";
36
- document.body.style.overflow = "hidden";
37
34
  document.body.style.height = "100%";
38
35
  document.body.style.position = "fixed";
39
36
  document.body.style.top = `-${originalScrollY}px`;
@@ -52,7 +49,6 @@ const unlockScroll = (token) => {
52
49
  document.documentElement.style.height = originalStyles.documentElement.height;
53
50
  document.documentElement.style.position = originalStyles.documentElement.position;
54
51
  document.documentElement.style.width = originalStyles.documentElement.width;
55
- document.body.style.overflow = originalStyles.body.overflow;
56
52
  document.body.style.height = originalStyles.body.height;
57
53
  document.body.style.position = originalStyles.body.position;
58
54
  document.body.style.top = originalStyles.body.top;
@@ -86,7 +82,7 @@ const useBodyScrollLock = (enabled = true) => {
86
82
  };
87
83
  }, [enabled]);
88
84
  };
89
-
90
85
  //#endregion
91
86
  export { useBodyScrollLock as default };
87
+
92
88
  //# sourceMappingURL=use-body-scroll-lock.mjs.map
@@ -1 +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\n// A Set of per-instance tokens instead of a raw counter — deleting the same\n// token twice (or locking with a token that's already in the set) can't\n// drift the count the way increment/decrement pairs could.\nconst activeLocks = new Set<symbol>();\nlet originalStyles: LockedStyles | null = null;\nlet originalScrollY = 0;\n\nconst isIOS = () =>\n typeof navigator !== 'undefined' &&\n /iPad|iPhone|iPod/.test(navigator.userAgent);\n\nconst lockScroll = (token: symbol) => {\n if (activeLocks.size === 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 activeLocks.add(token);\n};\n\nconst unlockScroll = (token: symbol) => {\n activeLocks.delete(token);\n\n if (activeLocks.size > 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 (typeof window === 'undefined' || !enabled) {\n return;\n }\n\n const token = Symbol('body-scroll-lock');\n lockScroll(token);\n\n // The position:fixed styles above are the real lock — this is only a\n // narrow safety net for iOS Safari, which can still rubber-band the\n // page on a touchmove that starts directly on the body/html background.\n // Only that one event is handled (no capture, no stopPropagation), so\n // a scrollable element inside the locked content (e.g. a modal body)\n // keeps scrolling normally — its touchmove target isn't body/html.\n let removeTouchListener: (() => void) | undefined;\n\n if (isIOS()) {\n const preventTouchMove = (event: TouchEvent) => {\n if (\n event.target === document.body ||\n event.target === document.documentElement\n ) {\n event.preventDefault();\n }\n };\n\n document.addEventListener('touchmove', preventTouchMove, {\n passive: false,\n });\n\n removeTouchListener = () => {\n document.removeEventListener('touchmove', preventTouchMove);\n };\n }\n\n return () => {\n removeTouchListener?.();\n unlockScroll(token);\n };\n }, [enabled]);\n};\n\nexport default useBodyScrollLock;\n"],"mappings":"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"}
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 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\n// A Set of per-instance tokens instead of a raw counter — deleting the same\n// token twice (or locking with a token that's already in the set) can't\n// drift the count the way increment/decrement pairs could.\nconst activeLocks = new Set<symbol>();\nlet originalStyles: LockedStyles | null = null;\nlet originalScrollY = 0;\n\nconst isIOS = () =>\n typeof navigator !== 'undefined' &&\n /iPad|iPhone|iPod/.test(navigator.userAgent);\n\nconst lockScroll = (token: symbol) => {\n if (activeLocks.size === 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 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 // `overflow: hidden` belongs on the root element only. Setting it on the\n // body as well clips the body's own box — and that box is shifted up by\n // `top: -scrollY`, so everything the user was looking at gets clipped out\n // of view and the page goes blank behind the modal. The overlay survives\n // because a `position: fixed` element isn't clipped by an ancestor's\n // overflow, which makes the failure look like an opaque backdrop rather\n // than a rendering bug.\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 activeLocks.add(token);\n};\n\nconst unlockScroll = (token: symbol) => {\n activeLocks.delete(token);\n\n if (activeLocks.size > 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.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 (typeof window === 'undefined' || !enabled) {\n return;\n }\n\n const token = Symbol('body-scroll-lock');\n lockScroll(token);\n\n // The position:fixed styles above are the real lock — this is only a\n // narrow safety net for iOS Safari, which can still rubber-band the\n // page on a touchmove that starts directly on the body/html background.\n // Only that one event is handled (no capture, no stopPropagation), so\n // a scrollable element inside the locked content (e.g. a modal body)\n // keeps scrolling normally — its touchmove target isn't body/html.\n let removeTouchListener: (() => void) | undefined;\n\n if (isIOS()) {\n const preventTouchMove = (event: TouchEvent) => {\n if (\n event.target === document.body ||\n event.target === document.documentElement\n ) {\n event.preventDefault();\n }\n };\n\n document.addEventListener('touchmove', preventTouchMove, {\n passive: false,\n });\n\n removeTouchListener = () => {\n document.removeEventListener('touchmove', preventTouchMove);\n };\n }\n\n return () => {\n removeTouchListener?.();\n unlockScroll(token);\n };\n }, [enabled]);\n};\n\nexport default useBodyScrollLock;\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { RefObject } from "react";
2
-
3
2
  //#region src/hooks/use-click-outside.d.ts
4
3
  type ClickOutsideRef = RefObject<HTMLElement | null> | null | undefined;
5
4
  type ClickOutsideEvent = PointerEvent | MouseEvent | TouchEvent | KeyboardEvent;
@@ -10,5 +9,5 @@ interface Options {
10
9
  }
11
10
  declare const useClickOutside: (refs: ClickOutsideRef | ClickOutsideRef[], handler: (event: ClickOutsideEvent) => void, options?: Options) => void;
12
11
  //#endregion
13
- export { useClickOutside };
12
+ export { useClickOutside as default };
14
13
  //# sourceMappingURL=use-click-outside.d.mts.map
@@ -1,5 +1,4 @@
1
1
  import { useEffect, useRef } from "react";
2
-
3
2
  //#region src/hooks/use-click-outside.ts
4
3
  const useClickOutside = (refs, handler, options = {}) => {
5
4
  const { enabled = true, events = ["pointerdown"], escape = false } = options;
@@ -12,6 +11,7 @@ const useClickOutside = (refs, handler, options = {}) => {
12
11
  useEffect(() => {
13
12
  refListRef.current = refList;
14
13
  });
14
+ const eventsKey = events.join(",");
15
15
  useEffect(() => {
16
16
  if (!enabled || typeof document === "undefined") return;
17
17
  const isInside = (target) => refListRef.current.some((ref) => ref?.current?.contains(target));
@@ -36,10 +36,10 @@ const useClickOutside = (refs, handler, options = {}) => {
36
36
  }, [
37
37
  enabled,
38
38
  escape,
39
- events.join(",")
39
+ eventsKey
40
40
  ]);
41
41
  };
42
-
43
42
  //#endregion
44
43
  export { useClickOutside as default };
44
+
45
45
  //# sourceMappingURL=use-click-outside.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-click-outside.mjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { type RefObject, useEffect, useRef } from 'react';\n\ntype ClickOutsideRef = RefObject<HTMLElement | null> | null | undefined;\n\ntype ClickOutsideEvent = PointerEvent | MouseEvent | TouchEvent | KeyboardEvent;\n\ninterface Options {\n enabled?: boolean;\n events?: ('pointerdown' | 'mousedown' | 'touchstart')[];\n escape?: boolean;\n}\n\n// Takes the \"inside\" ref(s) as an argument instead of creating and\n// returning one — lets a caller pass multiple exclusion targets (e.g. a\n// dropdown's trigger button *and* its portaled panel, which live in\n// different DOM subtrees) instead of only ever watching one element.\nconst useClickOutside = (\n refs: ClickOutsideRef | ClickOutsideRef[],\n handler: (event: ClickOutsideEvent) => void,\n options: Options = {},\n) => {\n // `escape` is opt-in (default `false`): the hook is named for clicks, and\n // an always-on Escape handler both surprises callers who already handle\n // Escape themselves (double-fire) and closes every layer at once in\n // nested UI (a dropdown inside a modal). Turn it on where you want it.\n const { enabled = true, events = ['pointerdown'], escape = false } = options;\n\n const handlerRef = useRef(handler);\n\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const refList = Array.isArray(refs) ? refs : [refs];\n // `refs` is almost always an inline array literal at the call site, so\n // depending on it directly would tear down/rebuild the listeners on\n // every render — read the latest value from a ref instead.\n const refListRef = useRef(refList);\n\n useEffect(() => {\n refListRef.current = refList;\n });\n\n const eventsKey = events.join(',');\n\n useEffect(() => {\n if (!enabled || typeof document === 'undefined') {\n return;\n }\n\n const isInside = (target: Node) =>\n refListRef.current.some(ref => ref?.current?.contains(target));\n\n const onPointerEvent = (event: Event) => {\n const target = event.target as Node | null;\n\n if (!target || isInside(target)) {\n return;\n }\n\n handlerRef.current(event as ClickOutsideEvent);\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n handlerRef.current(event);\n }\n };\n\n events.forEach(eventName => {\n document.addEventListener(eventName, onPointerEvent);\n });\n\n if (escape) {\n document.addEventListener('keydown', onKeyDown);\n }\n\n return () => {\n events.forEach(eventName => {\n document.removeEventListener(eventName, onPointerEvent);\n });\n\n if (escape) {\n document.removeEventListener('keydown', onKeyDown);\n }\n };\n // `events` is covered by `eventsKey` below instead of the array\n // itself, which (like `refs`) is usually a fresh literal every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, escape, eventsKey]);\n};\n\nexport default useClickOutside;\n"],"mappings":";;;AAgBA,MAAM,mBACJ,MACA,SACA,UAAmB,EAAE,KAClB;CAKH,MAAM,EAAE,UAAU,MAAM,SAAS,CAAC,cAAc,EAAE,SAAS,UAAU;CAErE,MAAM,aAAa,OAAO,QAAQ;AAElC,iBAAgB;AACd,aAAW,UAAU;GACrB;CAEF,MAAM,UAAU,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC,KAAK;CAInD,MAAM,aAAa,OAAO,QAAQ;AAElC,iBAAgB;AACd,aAAW,UAAU;GACrB;AAIF,iBAAgB;AACd,MAAI,CAAC,WAAW,OAAO,aAAa,YAClC;EAGF,MAAM,YAAY,WAChB,WAAW,QAAQ,MAAK,QAAO,KAAK,SAAS,SAAS,OAAO,CAAC;EAEhE,MAAM,kBAAkB,UAAiB;GACvC,MAAM,SAAS,MAAM;AAErB,OAAI,CAAC,UAAU,SAAS,OAAO,CAC7B;AAGF,cAAW,QAAQ,MAA2B;;EAGhD,MAAM,aAAa,UAAyB;AAC1C,OAAI,MAAM,QAAQ,SAChB,YAAW,QAAQ,MAAM;;AAI7B,SAAO,SAAQ,cAAa;AAC1B,YAAS,iBAAiB,WAAW,eAAe;IACpD;AAEF,MAAI,OACF,UAAS,iBAAiB,WAAW,UAAU;AAGjD,eAAa;AACX,UAAO,SAAQ,cAAa;AAC1B,aAAS,oBAAoB,WAAW,eAAe;KACvD;AAEF,OAAI,OACF,UAAS,oBAAoB,WAAW,UAAU;;IAMrD;EAAC;EAAS;EA9CK,OAAO,KAAK,IAAI;EA8CH,CAAC"}
1
+ {"version":3,"file":"use-click-outside.mjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { type RefObject, useEffect, useRef } from 'react';\n\ntype ClickOutsideRef = RefObject<HTMLElement | null> | null | undefined;\n\ntype ClickOutsideEvent = PointerEvent | MouseEvent | TouchEvent | KeyboardEvent;\n\ninterface Options {\n enabled?: boolean;\n events?: ('pointerdown' | 'mousedown' | 'touchstart')[];\n escape?: boolean;\n}\n\n// Takes the \"inside\" ref(s) as an argument instead of creating and\n// returning one — lets a caller pass multiple exclusion targets (e.g. a\n// dropdown's trigger button *and* its portaled panel, which live in\n// different DOM subtrees) instead of only ever watching one element.\nconst useClickOutside = (\n refs: ClickOutsideRef | ClickOutsideRef[],\n handler: (event: ClickOutsideEvent) => void,\n options: Options = {},\n) => {\n // `escape` is opt-in (default `false`): the hook is named for clicks, and\n // an always-on Escape handler both surprises callers who already handle\n // Escape themselves (double-fire) and closes every layer at once in\n // nested UI (a dropdown inside a modal). Turn it on where you want it.\n const { enabled = true, events = ['pointerdown'], escape = false } = options;\n\n const handlerRef = useRef(handler);\n\n useEffect(() => {\n handlerRef.current = handler;\n });\n\n const refList = Array.isArray(refs) ? refs : [refs];\n // `refs` is almost always an inline array literal at the call site, so\n // depending on it directly would tear down/rebuild the listeners on\n // every render — read the latest value from a ref instead.\n const refListRef = useRef(refList);\n\n useEffect(() => {\n refListRef.current = refList;\n });\n\n const eventsKey = events.join(',');\n\n useEffect(() => {\n if (!enabled || typeof document === 'undefined') {\n return;\n }\n\n const isInside = (target: Node) =>\n refListRef.current.some(ref => ref?.current?.contains(target));\n\n const onPointerEvent = (event: Event) => {\n const target = event.target as Node | null;\n\n if (!target || isInside(target)) {\n return;\n }\n\n handlerRef.current(event as ClickOutsideEvent);\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n handlerRef.current(event);\n }\n };\n\n events.forEach(eventName => {\n document.addEventListener(eventName, onPointerEvent);\n });\n\n if (escape) {\n document.addEventListener('keydown', onKeyDown);\n }\n\n return () => {\n events.forEach(eventName => {\n document.removeEventListener(eventName, onPointerEvent);\n });\n\n if (escape) {\n document.removeEventListener('keydown', onKeyDown);\n }\n };\n // `events` is covered by `eventsKey` below instead of the array\n // itself, which (like `refs`) is usually a fresh literal every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, escape, eventsKey]);\n};\n\nexport default useClickOutside;\n"],"mappings":";;AAgBA,MAAM,mBACJ,MACA,SACA,UAAmB,CAAC,MACjB;CAKH,MAAM,EAAE,UAAU,MAAM,SAAS,CAAC,aAAa,GAAG,SAAS,UAAU;CAErE,MAAM,aAAa,OAAO,OAAO;CAEjC,gBAAgB;EACd,WAAW,UAAU;CACvB,CAAC;CAED,MAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;CAIlD,MAAM,aAAa,OAAO,OAAO;CAEjC,gBAAgB;EACd,WAAW,UAAU;CACvB,CAAC;CAED,MAAM,YAAY,OAAO,KAAK,GAAG;CAEjC,gBAAgB;EACd,IAAI,CAAC,WAAW,OAAO,aAAa,aAClC;EAGF,MAAM,YAAY,WAChB,WAAW,QAAQ,MAAK,QAAO,KAAK,SAAS,SAAS,MAAM,CAAC;EAE/D,MAAM,kBAAkB,UAAiB;GACvC,MAAM,SAAS,MAAM;GAErB,IAAI,CAAC,UAAU,SAAS,MAAM,GAC5B;GAGF,WAAW,QAAQ,KAA0B;EAC/C;EAEA,MAAM,aAAa,UAAyB;GAC1C,IAAI,MAAM,QAAQ,UAChB,WAAW,QAAQ,KAAK;EAE5B;EAEA,OAAO,SAAQ,cAAa;GAC1B,SAAS,iBAAiB,WAAW,cAAc;EACrD,CAAC;EAED,IAAI,QACF,SAAS,iBAAiB,WAAW,SAAS;EAGhD,aAAa;GACX,OAAO,SAAQ,cAAa;IAC1B,SAAS,oBAAoB,WAAW,cAAc;GACxD,CAAC;GAED,IAAI,QACF,SAAS,oBAAoB,WAAW,SAAS;EAErD;CAIF,GAAG;EAAC;EAAS;EAAQ;CAAS,CAAC;AACjC"}
@@ -9,5 +9,5 @@ declare function useControllableState<T>(props: UseControllableStateProps<T> & {
9
9
  }): [T, (next: T) => void];
10
10
  declare function useControllableState<T>(props: UseControllableStateProps<T>): [T | undefined, (next: T) => void];
11
11
  //#endregion
12
- export { useControllableState };
12
+ export { useControllableState as default };
13
13
  //# sourceMappingURL=use-controllable-state.d.mts.map
@@ -1,5 +1,4 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
-
3
2
  //#region src/hooks/use-controllable-state.ts
4
3
  function useControllableState({ value, defaultValue, onChange }) {
5
4
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
@@ -15,7 +14,7 @@ function useControllableState({ value, defaultValue, onChange }) {
15
14
  onChange?.(next);
16
15
  }, [isControlled, onChange])];
17
16
  }
18
-
19
17
  //#endregion
20
18
  export { useControllableState as default };
19
+
21
20
  //# sourceMappingURL=use-controllable-state.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-controllable-state.mjs","names":[],"sources":["../../src/hooks/use-controllable-state.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\ninterface UseControllableStateProps<T> {\n value?: T;\n defaultValue?: T;\n onChange?: (value: T) => void;\n}\n\n// Backs a controlled/uncontrolled prop pair (`value`/`defaultValue`/\n// `onChange`) with one hook: falls back to internal state whenever `value`\n// is `undefined`, and always calls `onChange` regardless of which mode is\n// active, so callers don't have to branch on it themselves.\n//\n// `value === undefined` is what marks a render as uncontrolled, so it can't\n// also be used as a real, controlled value of `undefined` — that's an\n// inherent limitation of this \"sentinel\" style of controlled-state\n// detection (shared by most hooks of this shape, including Radix's own),\n// not something fixable without a different API (e.g. an explicit\n// `controlled` boolean prop instead of inferring it from `value`).\nfunction useControllableState<T>(\n props: UseControllableStateProps<T> & { defaultValue: T },\n): [T, (next: T) => void];\nfunction useControllableState<T>(\n props: UseControllableStateProps<T>,\n): [T | undefined, (next: T) => void];\nfunction useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateProps<T>) {\n const [uncontrolledValue, setUncontrolledValue] = useState<T | undefined>(\n defaultValue,\n );\n\n const isControlled = value !== undefined;\n const currentValue = isControlled ? value : uncontrolledValue;\n\n const wasControlledRef = useRef(isControlled);\n\n useEffect(() => {\n // Read `process.env.NODE_ENV` through `globalThis` rather than the\n // bare `process` global — this hook ships as plain ESM and can end up\n // somewhere with no bundler define for it (or no `process` at all, and\n // no @types/node in scope to even name it), where referencing it\n // directly would fail instead of just missing a warning.\n const nodeEnv = (\n globalThis as { process?: { env?: { NODE_ENV?: string } } }\n ).process?.env?.NODE_ENV;\n const isProduction = nodeEnv === 'production';\n\n if (!isProduction && wasControlledRef.current !== isControlled) {\n console.warn(\n `useControllableState is changing from ${\n wasControlledRef.current ? 'controlled' : 'uncontrolled'\n } to ${\n isControlled ? 'controlled' : 'uncontrolled'\n }. A component should not switch between controlled and uncontrolled — decide on one and stick with it (e.g. by always passing a non-undefined \\`value\\`, or never passing one).`,\n );\n }\n wasControlledRef.current = isControlled;\n }, [isControlled]);\n\n const setValue = useCallback(\n (next: T) => {\n if (!isControlled) {\n setUncontrolledValue(next);\n }\n onChange?.(next);\n },\n [isControlled, onChange],\n );\n\n return [currentValue, setValue];\n}\n\nexport default useControllableState;\n"],"mappings":";;;AAyBA,SAAS,qBAAwB,EAC/B,OACA,cACA,YAC+B;CAC/B,MAAM,CAAC,mBAAmB,wBAAwB,SAChD,aACD;CAED,MAAM,eAAe,UAAU;CAC/B,MAAM,eAAe,eAAe,QAAQ;CAE5C,MAAM,mBAAmB,OAAO,aAAa;AAE7C,iBAAgB;AAWd,MAAI,EAJF,WACA,SAAS,KAAK,aACiB,iBAEZ,iBAAiB,YAAY,aAChD,SAAQ,KACN,yCACE,iBAAiB,UAAU,eAAe,eAC3C,MACC,eAAe,eAAe,eAC/B,iLACF;AAEH,mBAAiB,UAAU;IAC1B,CAAC,aAAa,CAAC;AAYlB,QAAO,CAAC,cAVS,aACd,SAAY;AACX,MAAI,CAAC,aACH,sBAAqB,KAAK;AAE5B,aAAW,KAAK;IAElB,CAAC,cAAc,SAAS,CACzB,CAE8B"}
1
+ {"version":3,"file":"use-controllable-state.mjs","names":[],"sources":["../../src/hooks/use-controllable-state.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\ninterface UseControllableStateProps<T> {\n value?: T;\n defaultValue?: T;\n onChange?: (value: T) => void;\n}\n\n// Backs a controlled/uncontrolled prop pair (`value`/`defaultValue`/\n// `onChange`) with one hook: falls back to internal state whenever `value`\n// is `undefined`, and always calls `onChange` regardless of which mode is\n// active, so callers don't have to branch on it themselves.\n//\n// `value === undefined` is what marks a render as uncontrolled, so it can't\n// also be used as a real, controlled value of `undefined` — that's an\n// inherent limitation of this \"sentinel\" style of controlled-state\n// detection (shared by most hooks of this shape, including Radix's own),\n// not something fixable without a different API (e.g. an explicit\n// `controlled` boolean prop instead of inferring it from `value`).\nfunction useControllableState<T>(\n props: UseControllableStateProps<T> & { defaultValue: T },\n): [T, (next: T) => void];\nfunction useControllableState<T>(\n props: UseControllableStateProps<T>,\n): [T | undefined, (next: T) => void];\nfunction useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateProps<T>) {\n const [uncontrolledValue, setUncontrolledValue] = useState<T | undefined>(\n defaultValue,\n );\n\n const isControlled = value !== undefined;\n const currentValue = isControlled ? value : uncontrolledValue;\n\n const wasControlledRef = useRef(isControlled);\n\n useEffect(() => {\n // Read `process.env.NODE_ENV` through `globalThis` rather than the\n // bare `process` global — this hook ships as plain ESM and can end up\n // somewhere with no bundler define for it (or no `process` at all, and\n // no @types/node in scope to even name it), where referencing it\n // directly would fail instead of just missing a warning.\n const nodeEnv = (\n globalThis as { process?: { env?: { NODE_ENV?: string } } }\n ).process?.env?.NODE_ENV;\n const isProduction = nodeEnv === 'production';\n\n if (!isProduction && wasControlledRef.current !== isControlled) {\n console.warn(\n `useControllableState is changing from ${\n wasControlledRef.current ? 'controlled' : 'uncontrolled'\n } to ${\n isControlled ? 'controlled' : 'uncontrolled'\n }. A component should not switch between controlled and uncontrolled — decide on one and stick with it (e.g. by always passing a non-undefined \\`value\\`, or never passing one).`,\n );\n }\n wasControlledRef.current = isControlled;\n }, [isControlled]);\n\n const setValue = useCallback(\n (next: T) => {\n if (!isControlled) {\n setUncontrolledValue(next);\n }\n onChange?.(next);\n },\n [isControlled, onChange],\n );\n\n return [currentValue, setValue];\n}\n\nexport default useControllableState;\n"],"mappings":";;AAyBA,SAAS,qBAAwB,EAC/B,OACA,cACA,YAC+B;CAC/B,MAAM,CAAC,mBAAmB,wBAAwB,SAChD,YACF;CAEA,MAAM,eAAe,UAAU,KAAA;CAC/B,MAAM,eAAe,eAAe,QAAQ;CAE5C,MAAM,mBAAmB,OAAO,YAAY;CAE5C,gBAAgB;EAWd,IAAI,EAJF,WACA,SAAS,KAAK,aACiB,iBAEZ,iBAAiB,YAAY,cAChD,QAAQ,KACN,yCACE,iBAAiB,UAAU,eAAe,eAC3C,MACC,eAAe,eAAe,eAC/B,gLACH;EAEF,iBAAiB,UAAU;CAC7B,GAAG,CAAC,YAAY,CAAC;CAYjB,OAAO,CAAC,cAVS,aACd,SAAY;EACX,IAAI,CAAC,cACH,qBAAqB,IAAI;EAE3B,WAAW,IAAI;CACjB,GACA,CAAC,cAAc,QAAQ,CAGI,CAAC;AAChC"}
@@ -4,11 +4,7 @@ interface Options {
4
4
  autoInvoke?: boolean;
5
5
  leading?: boolean;
6
6
  }
7
- declare const useDebouncedCallback: (callback: () => unknown, {
8
- delay,
9
- autoInvoke,
10
- leading
11
- }: Options, deps?: React.DependencyList) => () => void;
7
+ declare const useDebouncedCallback: (callback: () => unknown, { delay, autoInvoke, leading }: Options, deps?: React.DependencyList) => () => void;
12
8
  //#endregion
13
- export { useDebouncedCallback };
9
+ export { useDebouncedCallback as default };
14
10
  //# sourceMappingURL=use-debounced-callback.d.mts.map
@@ -1,5 +1,4 @@
1
- import { useEffect, useRef } from "react";
2
-
1
+ import { useEffect, useRef, useState } from "react";
3
2
  //#region src/hooks/use-debounced-callback.ts
4
3
  const useDebouncedCallback = (callback, { delay = 100, autoInvoke = true, leading = true }, deps = []) => {
5
4
  const timeoutRef = useRef(null);
@@ -10,18 +9,19 @@ const useDebouncedCallback = (callback, { delay = 100, autoInvoke = true, leadin
10
9
  callbackRef.current = callback;
11
10
  delayRef.current = delay;
12
11
  });
13
- const stableDebouncedCallback = useRef(null);
14
- if (!stableDebouncedCallback.current) stableDebouncedCallback.current = () => {
12
+ const [stableDebouncedCallback] = useState(() => () => {
15
13
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
16
14
  timeoutRef.current = setTimeout(() => {
17
15
  callbackRef.current();
18
16
  }, delayRef.current);
19
- };
17
+ });
20
18
  useEffect(() => {
21
19
  const depsChanged = prevDeps.current === void 0 || prevDeps.current.length !== deps.length || prevDeps.current.some((dep, i) => dep !== deps[i]);
22
20
  if (depsChanged && timeoutRef.current) clearTimeout(timeoutRef.current);
23
- if (autoInvoke && depsChanged) if (prevDeps.current === void 0 && leading) callbackRef.current();
24
- else stableDebouncedCallback.current?.();
21
+ if (autoInvoke && depsChanged) {
22
+ if (prevDeps.current === void 0 && leading) callbackRef.current();
23
+ else stableDebouncedCallback();
24
+ }
25
25
  prevDeps.current = deps;
26
26
  });
27
27
  useEffect(() => {
@@ -29,9 +29,9 @@ const useDebouncedCallback = (callback, { delay = 100, autoInvoke = true, leadin
29
29
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
30
30
  };
31
31
  }, []);
32
- return stableDebouncedCallback.current;
32
+ return stableDebouncedCallback;
33
33
  };
34
-
35
34
  //#endregion
36
35
  export { useDebouncedCallback as default };
36
+
37
37
  //# sourceMappingURL=use-debounced-callback.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-debounced-callback.mjs","names":[],"sources":["../../src/hooks/use-debounced-callback.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\ninterface Options {\n delay?: number;\n autoInvoke?: boolean;\n leading?: boolean;\n}\n\n// Auto-invokes `callback` (debounced by `delay`ms) whenever `deps` change,\n// and also returns a stable debounced version of `callback` for manual use.\n// `callback` intentionally takes no arguments — the deps-triggered\n// auto-invoke has no natural argument to supply, so an arg-taking callback\n// here was a latent type hole (it was always invoked with zero args\n// regardless of what the callback's own signature claimed).\nconst useDebouncedCallback = (\n callback: () => unknown,\n { delay = 100, autoInvoke = true, leading = true }: Options,\n deps: React.DependencyList = [],\n) => {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n // Read the latest `delay` when the timeout actually fires, instead of\n // baking in whatever `delay` was on the render that created the stable\n // debounced function (which otherwise never updates again).\n const delayRef = useRef(delay);\n const prevDeps = useRef<React.DependencyList | undefined>(undefined);\n\n useEffect(() => {\n callbackRef.current = callback;\n delayRef.current = delay;\n });\n\n const stableDebouncedCallback = useRef<(() => void) | null>(null);\n\n if (!stableDebouncedCallback.current) {\n stableDebouncedCallback.current = () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n callbackRef.current();\n }, delayRef.current);\n };\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\n // `leading` makes the \"first invocation fires immediately, without\n // waiting `delay`ms\" behavior explicit and opt-out-able, instead of\n // an unconditional special case baked into the first render.\n if (isFirstRender && leading) {\n callbackRef.current();\n } else {\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 useDebouncedCallback;\n"],"mappings":";;;AAcA,MAAM,wBACJ,UACA,EAAE,QAAQ,KAAK,aAAa,MAAM,UAAU,QAC5C,OAA6B,EAAE,KAC5B;CACH,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CAIpC,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,WAAW,OAAyC,OAAU;AAEpE,iBAAgB;AACd,cAAY,UAAU;AACtB,WAAS,UAAU;GACnB;CAEF,MAAM,0BAA0B,OAA4B,KAAK;AAEjE,KAAI,CAAC,wBAAwB,QAC3B,yBAAwB,gBAAgB;AACtC,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAGlC,aAAW,UAAU,iBAAiB;AACpC,eAAY,SAAS;KACpB,SAAS,QAAQ;;AAIxB,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,YAMhB,KALsB,SAAS,YAAY,UAKtB,QACnB,aAAY,SAAS;MAErB,yBAAwB,WAAW;AAIvC,WAAS,UAAU;GACnB;AAEF,iBAAgB;AACd,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;AAEN,QAAO,wBAAwB"}
1
+ {"version":3,"file":"use-debounced-callback.mjs","names":[],"sources":["../../src/hooks/use-debounced-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\ninterface Options {\n delay?: number;\n autoInvoke?: boolean;\n leading?: boolean;\n}\n\n// Auto-invokes `callback` (debounced by `delay`ms) whenever `deps` change,\n// and also returns a stable debounced version of `callback` for manual use.\n// `callback` intentionally takes no arguments — the deps-triggered\n// auto-invoke has no natural argument to supply, so an arg-taking callback\n// here was a latent type hole (it was always invoked with zero args\n// regardless of what the callback's own signature claimed).\nconst useDebouncedCallback = (\n callback: () => unknown,\n { delay = 100, autoInvoke = true, leading = true }: Options,\n deps: React.DependencyList = [],\n) => {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n // Read the latest `delay` when the timeout actually fires, instead of\n // baking in whatever `delay` was on the render that created the stable\n // debounced function (which otherwise never updates again).\n const delayRef = useRef(delay);\n const prevDeps = useRef<React.DependencyList | undefined>(undefined);\n\n useEffect(() => {\n callbackRef.current = callback;\n delayRef.current = delay;\n });\n\n // Built once via useState's lazy initializer (runs only on mount) rather\n // than the ref-guarded-by-if pattern this used to use — same \"create\n // once, keep a stable identity\" effect, but without ever reading/writing\n // a ref during render, which react-hooks/refs now disallows even for the\n // otherwise-idempotent lazy-ref-init form (see facebook/react#36896).\n const [stableDebouncedCallback] = useState(() => () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n callbackRef.current();\n }, delayRef.current);\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\n // `leading` makes the \"first invocation fires immediately, without\n // waiting `delay`ms\" behavior explicit and opt-out-able, instead of\n // an unconditional special case baked into the first render.\n if (isFirstRender && leading) {\n callbackRef.current();\n } else {\n stableDebouncedCallback();\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;\n};\n\nexport default useDebouncedCallback;\n"],"mappings":";;AAcA,MAAM,wBACJ,UACA,EAAE,QAAQ,KAAK,aAAa,MAAM,UAAU,QAC5C,OAA6B,CAAC,MAC3B;CACH,MAAM,aAAa,OAA6C,IAAI;CACpE,MAAM,cAAc,OAAO,QAAQ;CAInC,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,WAAW,OAAyC,KAAA,CAAS;CAEnE,gBAAgB;EACd,YAAY,UAAU;EACtB,SAAS,UAAU;CACrB,CAAC;CAOD,MAAM,CAAC,2BAA2B,qBAAqB;EACrD,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAGjC,WAAW,UAAU,iBAAiB;GACpC,YAAY,QAAQ;EACtB,GAAG,SAAS,OAAO;CACrB,CAAC;CAED,gBAAgB;EACd,MAAM,cACJ,SAAS,YAAY,KAAA,KACrB,SAAS,QAAQ,WAAW,KAAK,UACjC,SAAS,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,EAAE;EAEnD,IAAI,eAAe,WAAW,SAC5B,aAAa,WAAW,OAAO;EAGjC,IAAI,cAAc,aAAa;GAM7B,IALsB,SAAS,YAAY,KAAA,KAKtB,SACnB,YAAY,QAAQ;QAEpB,wBAAwB;EAE5B;EAEA,SAAS,UAAU;CACrB,CAAC;CAED,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAEnC;CACF,GAAG,CAAC,CAAC;CAEL,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  //#region src/hooks/use-debounced-value.d.ts
2
2
  declare const useDebouncedValue: <T>(value: T, delay?: number) => T;
3
3
  //#endregion
4
- export { useDebouncedValue };
4
+ export { useDebouncedValue as default };
5
5
  //# sourceMappingURL=use-debounced-value.d.mts.map
@@ -1,5 +1,4 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
-
3
2
  //#region src/hooks/use-debounced-value.ts
4
3
  const useDebouncedValue = (value, delay = 100) => {
5
4
  const [debouncedValue, setDebouncedValue] = useState(value);
@@ -14,7 +13,7 @@ const useDebouncedValue = (value, delay = 100) => {
14
13
  }, [value, delay]);
15
14
  return debouncedValue;
16
15
  };
17
-
18
16
  //#endregion
19
17
  export { useDebouncedValue as default };
18
+
20
19
  //# sourceMappingURL=use-debounced-value.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-debounced-value.mjs","names":[],"sources":["../../src/hooks/use-debounced-value.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\n// The value-shaped counterpart to useDebounce (which is callback-shaped),\n// symmetric with useThrottle's `(value, delay) => value` signature. The\n// common \"just give me the debounced value\" case otherwise needs two\n// pieces of state and an effect built on useDebounce by hand.\nconst useDebouncedValue = <T>(value: T, delay = 100): T => {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n timeoutRef.current = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, [value, delay]);\n\n return debouncedValue;\n};\n\nexport default useDebouncedValue;\n"],"mappings":";;;AAMA,MAAM,qBAAwB,OAAU,QAAQ,QAAW;CACzD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,aAAa,OAA6C,KAAK;AAErE,iBAAgB;AACd,aAAW,UAAU,iBAAiB;AACpC,qBAAkB,MAAM;KACvB,MAAM;AAET,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,CAAC,OAAO,MAAM,CAAC;AAElB,QAAO"}
1
+ {"version":3,"file":"use-debounced-value.mjs","names":[],"sources":["../../src/hooks/use-debounced-value.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\n// The value-shaped counterpart to useDebounce (which is callback-shaped),\n// symmetric with useThrottle's `(value, delay) => value` signature. The\n// common \"just give me the debounced value\" case otherwise needs two\n// pieces of state and an effect built on useDebounce by hand.\nconst useDebouncedValue = <T>(value: T, delay = 100): T => {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n timeoutRef.current = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, [value, delay]);\n\n return debouncedValue;\n};\n\nexport default useDebouncedValue;\n"],"mappings":";;AAMA,MAAM,qBAAwB,OAAU,QAAQ,QAAW;CACzD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK;CAC1D,MAAM,aAAa,OAA6C,IAAI;CAEpE,gBAAgB;EACd,WAAW,UAAU,iBAAiB;GACpC,kBAAkB,KAAK;EACzB,GAAG,KAAK;EAER,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAEnC;CACF,GAAG,CAAC,OAAO,KAAK,CAAC;CAEjB,OAAO;AACT"}
@@ -1,8 +1,7 @@
1
1
  import { RefObject } from "react";
2
-
3
2
  //#region src/hooks/use-element-position.d.ts
4
3
  type ElementReference<T> = string | RefObject<T>;
5
4
  declare const useElementPosition: <T>(elementRef: ElementReference<T>) => DOMRect | null;
6
5
  //#endregion
7
- export { useElementPosition };
6
+ export { useElementPosition as default };
8
7
  //# sourceMappingURL=use-element-position.d.mts.map
@@ -1,5 +1,4 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
-
3
2
  //#region src/hooks/use-element-position.ts
4
3
  const rectsEqual = (a, b) => {
5
4
  if (a === b) return true;
@@ -55,7 +54,7 @@ const useElementPosition = (elementRef) => {
55
54
  }, [elementRef]);
56
55
  return rect;
57
56
  };
58
-
59
57
  //#endregion
60
58
  export { useElementPosition as default };
59
+
61
60
  //# sourceMappingURL=use-element-position.mjs.map