@jbpark/use-hooks 2.0.1 → 2.1.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 (47) hide show
  1. package/README.ko.md +18 -10
  2. package/README.md +16 -10
  3. package/dist/hooks/index.d.mts +12 -0
  4. package/dist/hooks/index.mjs +14 -0
  5. package/dist/hooks/useBodyScrollLock/index.d.mts +5 -0
  6. package/dist/hooks/useBodyScrollLock/index.mjs +115 -0
  7. package/dist/hooks/useBodyScrollLock/index.mjs.map +1 -0
  8. package/dist/hooks/useDebounce/index.d.mts +12 -0
  9. package/dist/hooks/useDebounce/index.mjs +41 -0
  10. package/dist/hooks/useDebounce/index.mjs.map +1 -0
  11. package/dist/hooks/useElementPosition/index.d.mts +8 -0
  12. package/dist/hooks/useElementPosition/index.mjs +32 -0
  13. package/dist/hooks/useElementPosition/index.mjs.map +1 -0
  14. package/dist/hooks/useElementScroll/index.d.mts +15 -0
  15. package/dist/hooks/useElementScroll/index.mjs +68 -0
  16. package/dist/hooks/useElementScroll/index.mjs.map +1 -0
  17. package/dist/hooks/useImage/index.d.mts +14 -0
  18. package/dist/hooks/useImage/index.mjs +56 -0
  19. package/dist/hooks/useImage/index.mjs.map +1 -0
  20. package/dist/hooks/useLocalStorage/index.d.mts +5 -0
  21. package/dist/hooks/useLocalStorage/index.mjs +40 -0
  22. package/dist/hooks/useLocalStorage/index.mjs.map +1 -0
  23. package/dist/hooks/useRecursiveTimeout/index.d.mts +5 -0
  24. package/dist/hooks/useRecursiveTimeout/index.mjs +27 -0
  25. package/dist/hooks/useRecursiveTimeout/index.mjs.map +1 -0
  26. package/dist/hooks/useResponsiveSize/index.d.mts +26 -0
  27. package/dist/hooks/useResponsiveSize/index.mjs +108 -0
  28. package/dist/hooks/useResponsiveSize/index.mjs.map +1 -0
  29. package/dist/hooks/useScrollToElements/index.d.mts +14 -0
  30. package/dist/hooks/useScrollToElements/index.mjs +34 -0
  31. package/dist/hooks/useScrollToElements/index.mjs.map +1 -0
  32. package/dist/hooks/useThrottle/index.d.mts +5 -0
  33. package/dist/hooks/useThrottle/index.mjs +42 -0
  34. package/dist/hooks/useThrottle/index.mjs.map +1 -0
  35. package/dist/hooks/useViewport/index.d.mts +18 -0
  36. package/dist/hooks/useViewport/index.mjs +87 -0
  37. package/dist/hooks/useViewport/index.mjs.map +1 -0
  38. package/dist/hooks/useWindowScroll/index.d.mts +12 -0
  39. package/dist/hooks/useWindowScroll/index.mjs +60 -0
  40. package/dist/hooks/useWindowScroll/index.mjs.map +1 -0
  41. package/dist/index.d.mts +14 -0
  42. package/dist/index.mjs +15 -0
  43. package/package.json +7 -8
  44. package/dist/index.cjs +0 -1
  45. package/dist/index.d.ts +0 -106
  46. package/dist/index.js +0 -422
  47. package/dist/vite.svg +0 -1
package/README.ko.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## 기능
13
13
 
14
- - 📦 **10개 프로덕션 레디 훅** - 스크롤, 뷰포트, 스토리지 등 다양한 유틸리티
14
+ - 📦 **12개 프로덕션 레디 훅** - 스크롤, 뷰포트, 스토리지 등 다양한 유틸리티
15
15
  - 🎯 **TypeScript 지원** - 완전한 타입 지원으로 더 나은 개발 경험
16
16
  - ⚡ **트리 셰이킹 지원** - 필요한 것만 임포트하세요
17
17
  - 🔒 **SSR 안전** - window/document 전역 변수에 대한 보호
@@ -34,8 +34,9 @@ pnpm add @jbpark/use-hooks
34
34
 
35
35
  ```tsx
36
36
  import {
37
- useElementSize,
38
37
  useLocalStorage,
38
+ useResponsiveSize,
39
+ useThrottle,
39
40
  useWindowScroll,
40
41
  } from '@jbpark/use-hooks';
41
42
 
@@ -47,13 +48,17 @@ function MyComponent() {
47
48
  const { y, percent } = useWindowScroll();
48
49
 
49
50
  // 브레이크포인트를 포함한 요소 크기 모니터링
50
- const { size, breakpoint, ref } = useElementSize();
51
+ const { size, breakpoint, ref } = useResponsiveSize();
52
+
53
+ // 너비 업데이트를 스로틀링
54
+ const throttledWidth = useThrottle(size.width, 200);
51
55
 
52
56
  return (
53
57
  <div ref={ref}>
54
58
  <p>Count: {count}</p>
55
59
  <p>Scroll: {percent.y}%</p>
56
60
  <p>Breakpoint: {breakpoint.current}</p>
61
+ <p>Throttled width: {throttledWidth}</p>
57
62
  <button onClick={() => setCount(count + 1)}>증가</button>
58
63
  </div>
59
64
  );
@@ -66,14 +71,16 @@ function MyComponent() {
66
71
  | --------------------- | --------------------------------------------------------------- |
67
72
  | `useLocalStorage` | 에러 핸들링이 포함된 JSON 기반 영속 상태 (SSR 안전) |
68
73
  | `useWindowScroll` | 윈도우 스크롤 위치 및 백분율 추적 (iOS visualViewport 대응) |
69
- | `useScrollPosition` | ResizeObserver를 사용한 특정 요소의 스크롤 상태 추적 |
74
+ | `useElementScroll` | ResizeObserver를 사용한 특정 요소의 스크롤 상태 추적 |
70
75
  | `useElementPosition` | 스크롤/리사이즈 시 요소의 바운딩 렉트 모니터링 (요소 참조 지원) |
71
- | `useElementSize` | Tailwind 유사 브레이크포인트를 포함한 요소 크기 추적 (debounce) |
76
+ | `useResponsiveSize` | Tailwind 유사 브레이크포인트를 포함한 요소 크기 추적 (debounce) |
72
77
  | `useBodyScrollLock` | 스타일 보존을 포함한 바디 스크롤 잠금/해제 (iOS 특별 처리) |
73
78
  | `useScrollToElements` | 인덱스별로 특정 요소로 스크롤 (오프셋 조절 가능) |
74
79
  | `useImage` | 이미지 사전로드 및 로딩/에러 상태 노출 |
75
80
  | `useRecursiveTimeout` | 비동기/동기 콜백을 재귀적으로 스케줄링 |
76
81
  | `useViewport` | visualViewport 지원, 인앱 모드 옵션, debounce 포함 |
82
+ | `useDebounce` | 함수 실행을 지연해 과도한 업데이트를 방지 (autoInvoke 지원) |
83
+ | `useThrottle` | 값 업데이트를 일정 간격으로 제한 |
77
84
 
78
85
  ## 개발
79
86
 
@@ -100,6 +107,7 @@ pnpm exec prettier --write .
100
107
  src/
101
108
  ├── hooks/ # 개별 훅 구현
102
109
  │ ├── useBodyScrollLock/
110
+ │ ├── useDebounce/
103
111
  │ ├── useElementPosition/
104
112
  │ ├── useElementScroll/
105
113
  │ ├── useImage/
@@ -107,12 +115,13 @@ src/
107
115
  │ ├── useRecursiveTimeout/
108
116
  │ ├── useResponsiveSize/
109
117
  │ ├── useScrollToElements/
118
+ │ ├── useThrottle/
110
119
  │ ├── useViewport/
111
120
  │ ├── useWindowScroll/
112
121
  │ └── index.ts # 배럴 익스포트
113
122
  └── index.ts # 패키지 진입점
114
123
 
115
- dist/ # 빌드된 라이브러리 (ES + CJS + types)
124
+ dist/ # 빌드된 라이브러리 (ESM + types)
116
125
  .changeset/ # 버저닝을 위한 Changesets
117
126
  ```
118
127
 
@@ -136,18 +145,17 @@ git push --follow-tags
136
145
 
137
146
  라이브러리는 다음과 같이 빌드됩니다:
138
147
 
139
- - **ES Module**: `dist/index.js`
140
- - **CommonJS**: `dist/index.cjs`
148
+ - **ES Module**: `dist/index.mjs`
141
149
  - **타입 정의**: `dist/index.d.ts`
142
150
 
143
151
  ## 주요 패턴
144
152
 
145
153
  - **Window 보호**: `window`/`document`에 접근하는 훅은 SSR 안전성을 위해 `typeof window` 체크 (예: `useLocalStorage`)
146
154
  - **이벤트 리스너**: 모든 스크롤/리사이즈 리스너는 가능한 한 passive 플래그 사용
147
- - **ResizeObserver**: `useElementSize`와 `useElementPosition`에서 사용하여 성능 최적화
155
+ - **ResizeObserver**: `useResponsiveSize`와 `useElementPosition`에서 사용하여 성능 최적화
148
156
  - **requestAnimationFrame**: 스크롤/리사이즈 콜백에서 레이아웃 스래싱 방지
149
157
  - **iOS 대응**: `useBodyScrollLock`, `useWindowScroll`, `useViewport`에서 iOS의 visualViewport 특성 처리
150
- - **Debounce**: `useElementSize`와 `useViewport`에서 리사이즈 이벤트 디바운싱 지원
158
+ - **Debounce**: `useResponsiveSize`와 `useViewport`에서 리사이즈 이벤트 디바운싱 지원
151
159
 
152
160
  ## 브라우저 지원
153
161
 
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
- - 📦 **11 Production-Ready Hooks** - Utilities for scrolling, viewport, storage, and more
14
+ - 📦 **12 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
@@ -34,8 +34,9 @@ pnpm add @jbpark/use-hooks
34
34
 
35
35
  ```tsx
36
36
  import {
37
- useElementSize,
38
37
  useLocalStorage,
38
+ useResponsiveSize,
39
+ useThrottle,
39
40
  useWindowScroll,
40
41
  } from '@jbpark/use-hooks';
41
42
 
@@ -47,13 +48,17 @@ function MyComponent() {
47
48
  const { y, percent } = useWindowScroll();
48
49
 
49
50
  // Monitor element size with breakpoints
50
- const { size, breakpoint, ref } = useElementSize();
51
+ const { size, breakpoint, ref } = useResponsiveSize();
52
+
53
+ // Throttled width update
54
+ const throttledWidth = useThrottle(size.width, 200);
51
55
 
52
56
  return (
53
57
  <div ref={ref}>
54
58
  <p>Count: {count}</p>
55
59
  <p>Scroll: {percent.y}%</p>
56
60
  <p>Breakpoint: {breakpoint.current}</p>
61
+ <p>Throttled width: {throttledWidth}</p>
57
62
  <button onClick={() => setCount(count + 1)}>Increment</button>
58
63
  </div>
59
64
  );
@@ -66,15 +71,16 @@ function MyComponent() {
66
71
  | --------------------- | --------------------------------------------------------------------------- |
67
72
  | `useLocalStorage` | JSON-based persistent state with error handling (SSR-safe) |
68
73
  | `useWindowScroll` | Track window scroll position and percentage (iOS visualViewport compatible) |
69
- | `useScrollPosition` | Monitor scroll state of specific elements using ResizeObserver |
74
+ | `useElementScroll` | Monitor scroll state of specific elements using ResizeObserver |
70
75
  | `useElementPosition` | Monitor element bounding rect on scroll/resize (element ref support) |
71
- | `useElementSize` | Track element size with Tailwind-like breakpoints (debounced) |
76
+ | `useResponsiveSize` | Track element size with Tailwind-like breakpoints (debounced) |
72
77
  | `useBodyScrollLock` | Lock/unlock body scroll with style preservation (iOS-specific handling) |
73
78
  | `useScrollToElements` | Scroll to specific elements by index (adjustable offset) |
74
79
  | `useImage` | Preload images and expose loading/error states |
75
80
  | `useRecursiveTimeout` | Recursively schedule async/sync callbacks |
76
81
  | `useViewport` | visualViewport support with in-app mode option and debounce |
77
82
  | `useDebounce` | Delay function execution to prevent excessive updates (autoInvoke support) |
83
+ | `useThrottle` | Throttle value updates to a fixed interval |
78
84
 
79
85
  ## Development
80
86
 
@@ -109,12 +115,13 @@ src/
109
115
  │ ├── useRecursiveTimeout/
110
116
  │ ├── useResponsiveSize/
111
117
  │ ├── useScrollToElements/
118
+ │ ├── useThrottle/
112
119
  │ ├── useViewport/
113
120
  │ ├── useWindowScroll/
114
121
  │ └── index.ts # Barrel export
115
122
  └── index.ts # Package entry point
116
123
 
117
- dist/ # Built library (ES + CJS + types)
124
+ dist/ # Built library (ESM + types)
118
125
  .changeset/ # Changesets for versioning
119
126
  ```
120
127
 
@@ -138,18 +145,17 @@ git push --follow-tags
138
145
 
139
146
  The library is built as:
140
147
 
141
- - **ES Module**: `dist/index.js`
142
- - **CommonJS**: `dist/index.cjs`
148
+ - **ES Module**: `dist/index.mjs`
143
149
  - **Type Definitions**: `dist/index.d.ts`
144
150
 
145
151
  ## Key Patterns
146
152
 
147
153
  - **Window Protection**: Hooks accessing `window`/`document` check `typeof window` for SSR safety (e.g., `useLocalStorage`)
148
154
  - **Event Listeners**: All scroll/resize listeners use passive flag when possible
149
- - **ResizeObserver**: Used in `useElementSize` and `useElementPosition` for performance
155
+ - **ResizeObserver**: Used in `useResponsiveSize` and `useElementPosition` for performance
150
156
  - **requestAnimationFrame**: Prevents layout thrashing in scroll/resize callbacks
151
157
  - **iOS Compatibility**: Special handling of iOS visualViewport in `useBodyScrollLock`, `useWindowScroll`, and `useViewport`
152
- - **Debounce**: Optional debouncing for resize events in `useElementSize` and `useViewport`
158
+ - **Debounce**: Optional debouncing for resize events in `useResponsiveSize` and `useViewport`
153
159
 
154
160
  ## Browser Support
155
161
 
@@ -0,0 +1,12 @@
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 { useWindowScroll } from "./useWindowScroll/index.mjs";
12
+ import { useViewport } from "./useViewport/index.mjs";
@@ -0,0 +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 useWindowScroll from "./useWindowScroll/index.mjs";
12
+ import useViewport from "./useViewport/index.mjs";
13
+
14
+ export { };
@@ -0,0 +1,5 @@
1
+ //#region src/hooks/useBodyScrollLock/index.d.ts
2
+ declare const useBodyScrollLock: (enabled?: boolean) => void;
3
+ //#endregion
4
+ export { useBodyScrollLock };
5
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,115 @@
1
+ import { useEffect } from "react";
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 = {
10
+ documentElement: {
11
+ overflow: document.documentElement.style.overflow,
12
+ height: document.documentElement.style.height,
13
+ position: document.documentElement.style.position,
14
+ width: document.documentElement.style.width
15
+ },
16
+ body: {
17
+ overflow: document.body.style.overflow,
18
+ height: document.body.style.height,
19
+ position: document.body.style.position,
20
+ top: document.body.style.top,
21
+ left: document.body.style.left,
22
+ right: document.body.style.right,
23
+ width: document.body.style.width,
24
+ webkitOverflowScrolling: document.body.style.getPropertyValue("-webkit-overflow-scrolling")
25
+ }
26
+ };
27
+ document.documentElement.style.overflow = "hidden";
28
+ document.documentElement.style.height = "100%";
29
+ document.documentElement.style.position = "fixed";
30
+ document.documentElement.style.width = "100%";
31
+ document.body.style.overflow = "hidden";
32
+ document.body.style.height = "100%";
33
+ document.body.style.position = "fixed";
34
+ document.body.style.top = `-${scrollY}px`;
35
+ document.body.style.left = "0";
36
+ document.body.style.right = "0";
37
+ document.body.style.width = "100%";
38
+ if (isIOS) {
39
+ document.body.style.setProperty("-webkit-overflow-scrolling", "touch");
40
+ const preventAll = (e) => {
41
+ if (e.target === document.body || e.target === document.documentElement || e.target === window) {
42
+ e.preventDefault();
43
+ e.stopPropagation();
44
+ e.stopImmediatePropagation();
45
+ }
46
+ };
47
+ const preventScroll = () => {
48
+ window.scrollTo(0, 0);
49
+ document.body.scrollTop = 0;
50
+ document.documentElement.scrollTop = 0;
51
+ };
52
+ const events = [
53
+ "scroll",
54
+ "touchmove",
55
+ "touchstart",
56
+ "touchend"
57
+ ];
58
+ events.forEach((event) => {
59
+ window.addEventListener(event, preventAll, {
60
+ passive: false,
61
+ capture: true
62
+ });
63
+ document.addEventListener(event, preventAll, {
64
+ passive: false,
65
+ capture: true
66
+ });
67
+ document.body.addEventListener(event, preventAll, {
68
+ passive: false,
69
+ capture: true
70
+ });
71
+ });
72
+ const scrollInterval = setInterval(preventScroll, 16);
73
+ return () => {
74
+ clearInterval(scrollInterval);
75
+ events.forEach((event) => {
76
+ window.removeEventListener(event, preventAll, { capture: true });
77
+ document.removeEventListener(event, preventAll, { capture: true });
78
+ document.body.removeEventListener(event, preventAll, { capture: true });
79
+ });
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);
94
+ };
95
+ }
96
+ 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);
109
+ };
110
+ }, [enabled]);
111
+ };
112
+
113
+ //#endregion
114
+ export { useBodyScrollLock as default };
115
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useBodyScrollLock/index.ts"],"sourcesContent":["import { useEffect } from 'react';\n\nconst useBodyScrollLock = (enabled: boolean = true) => {\n useEffect(() => {\n if (!enabled) {\n return;\n }\n const scrollY = window.scrollY;\n\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);\n\n const 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 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 = `-${scrollY}px`;\n document.body.style.left = '0';\n document.body.style.right = '0';\n document.body.style.width = '100%';\n\n if (isIOS) {\n document.body.style.setProperty('-webkit-overflow-scrolling', 'touch');\n\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\n document.documentElement.style.overflow =\n originalStyles.documentElement.overflow;\n document.documentElement.style.height =\n originalStyles.documentElement.height;\n document.documentElement.style.position =\n originalStyles.documentElement.position;\n document.documentElement.style.width =\n 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\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, scrollY);\n };\n }\n\n return () => {\n document.documentElement.style.overflow =\n originalStyles.documentElement.overflow;\n document.documentElement.style.height =\n originalStyles.documentElement.height;\n document.documentElement.style.position =\n originalStyles.documentElement.position;\n document.documentElement.style.width =\n 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\n window.scrollTo(0, scrollY);\n };\n }, [enabled]);\n};\n\nexport default useBodyScrollLock;\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ //#region src/hooks/useDebounce/index.d.ts
2
+ interface Options {
3
+ delay?: number;
4
+ autoInvoke?: boolean;
5
+ }
6
+ declare const useDebounce: <T extends (...args: unknown[]) => unknown>(callback: T, {
7
+ delay,
8
+ autoInvoke
9
+ }: Options, deps?: React.DependencyList) => T;
10
+ //#endregion
11
+ export { useDebounce };
12
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,41 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ //#region src/hooks/useDebounce/index.ts
4
+ const useDebounce = (callback, { delay = 100, autoInvoke = true }, deps = []) => {
5
+ const timeoutRef = useRef(null);
6
+ const callbackRef = useRef(callback);
7
+ const depsRef = useRef(deps);
8
+ const prevDeps = useRef(void 0);
9
+ useEffect(() => {
10
+ callbackRef.current = callback;
11
+ });
12
+ useEffect(() => {
13
+ depsRef.current = deps;
14
+ });
15
+ const stableDebouncedCallback = useRef(null);
16
+ if (!stableDebouncedCallback.current) stableDebouncedCallback.current = ((...args) => {
17
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
18
+ timeoutRef.current = setTimeout(() => {
19
+ callbackRef.current(...args);
20
+ }, delay);
21
+ });
22
+ useEffect(() => {
23
+ const depsChanged = prevDeps.current === void 0 || prevDeps.current.length !== deps.length || prevDeps.current.some((dep, i) => dep !== deps[i]);
24
+ if (depsChanged && timeoutRef.current) clearTimeout(timeoutRef.current);
25
+ if (autoInvoke && depsChanged) {
26
+ if (prevDeps.current === void 0) callbackRef.current();
27
+ else if (stableDebouncedCallback.current) stableDebouncedCallback.current();
28
+ }
29
+ prevDeps.current = deps;
30
+ });
31
+ useEffect(() => {
32
+ return () => {
33
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
34
+ };
35
+ }, []);
36
+ return stableDebouncedCallback.current;
37
+ };
38
+
39
+ //#endregion
40
+ export { useDebounce as default };
41
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useDebounce/index.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<NodeJS.Timeout | null>(null);\n const callbackRef = useRef(callback);\n const depsRef = useRef(deps);\n const prevDeps = useRef<React.DependencyList | undefined>(undefined);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n useEffect(() => {\n depsRef.current = deps;\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,OAA8B,KAAK;CACtD,MAAM,cAAc,OAAO,SAAS;CACpC,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,WAAW,OAAyC,OAAU;AAEpE,iBAAgB;AACd,cAAY,UAAU;GACtB;AAEF,iBAAgB;AACd,UAAQ,UAAU;GAClB;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"}
@@ -0,0 +1,8 @@
1
+ import { RefObject } from "react";
2
+
3
+ //#region src/hooks/useElementPosition/index.d.ts
4
+ type ElementReference<T> = string | RefObject<T>;
5
+ declare const useElementPosition: <T>(elementRef: ElementReference<T>) => DOMRect | null;
6
+ //#endregion
7
+ export { useElementPosition };
8
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,32 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ //#region src/hooks/useElementPosition/index.ts
4
+ const useElementPosition = (elementRef) => {
5
+ const [rect, setRect] = useState(null);
6
+ useEffect(() => {
7
+ const getElement = (ref) => {
8
+ if (typeof ref === "string") return document.querySelector(ref);
9
+ return ref.current;
10
+ };
11
+ const updateRect = () => {
12
+ const element = getElement(elementRef);
13
+ if (element) setRect(element.getBoundingClientRect());
14
+ else setRect(null);
15
+ };
16
+ const onUpdate = () => {
17
+ requestAnimationFrame(updateRect);
18
+ };
19
+ updateRect();
20
+ window.addEventListener("scroll", onUpdate, { passive: true });
21
+ window.addEventListener("resize", onUpdate, { passive: true });
22
+ return () => {
23
+ window.removeEventListener("scroll", onUpdate);
24
+ window.removeEventListener("resize", onUpdate);
25
+ };
26
+ }, [elementRef]);
27
+ return rect;
28
+ };
29
+
30
+ //#endregion
31
+ export { useElementPosition as default };
32
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useElementPosition/index.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"}
@@ -0,0 +1,15 @@
1
+ //#region src/hooks/useElementScroll/index.d.ts
2
+ declare const useElementScroll: () => {
3
+ element: HTMLElement | null;
4
+ setRef: (el: HTMLElement | null) => void;
5
+ scrollY: number;
6
+ scrollPercentage: number;
7
+ isAtTop: boolean;
8
+ isAtBottom: boolean;
9
+ scrollableHeight: number;
10
+ clientHeight: number;
11
+ scrollHeight: number;
12
+ };
13
+ //#endregion
14
+ export { useElementScroll };
15
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,68 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ //#region src/hooks/useElementScroll/index.ts
4
+ const useElementScroll = () => {
5
+ const [element, setElement] = useState(null);
6
+ const [scrollPosition, setScrollPosition] = useState({
7
+ scrollY: 0,
8
+ scrollPercentage: 0,
9
+ isAtTop: true,
10
+ isAtBottom: false,
11
+ scrollableHeight: 0,
12
+ clientHeight: 0,
13
+ scrollHeight: 0
14
+ });
15
+ const setRef = useCallback((el) => {
16
+ setElement(el);
17
+ }, []);
18
+ useEffect(() => {
19
+ if (!element) return;
20
+ const updateScrollPosition = () => {
21
+ const { scrollTop, scrollHeight, clientHeight } = element;
22
+ const scrollableHeight = scrollHeight - clientHeight;
23
+ if (scrollableHeight <= 0) {
24
+ setScrollPosition({
25
+ scrollY: 0,
26
+ scrollPercentage: 0,
27
+ isAtTop: true,
28
+ isAtBottom: true,
29
+ scrollableHeight: 0,
30
+ clientHeight,
31
+ scrollHeight
32
+ });
33
+ return;
34
+ }
35
+ setScrollPosition({
36
+ scrollY: scrollTop,
37
+ scrollPercentage: Math.min(100, Math.max(0, scrollTop / scrollableHeight * 100)),
38
+ isAtTop: scrollTop <= 0,
39
+ isAtBottom: scrollTop >= scrollableHeight - 1,
40
+ scrollableHeight,
41
+ clientHeight,
42
+ scrollHeight
43
+ });
44
+ };
45
+ updateScrollPosition();
46
+ const onScroll = () => {
47
+ updateScrollPosition();
48
+ };
49
+ element.addEventListener("scroll", onScroll, { passive: true });
50
+ const resizeObserver = new ResizeObserver(() => {
51
+ updateScrollPosition();
52
+ });
53
+ resizeObserver.observe(element);
54
+ return () => {
55
+ element.removeEventListener("scroll", onScroll);
56
+ resizeObserver.unobserve(element);
57
+ };
58
+ }, [element]);
59
+ return {
60
+ ...scrollPosition,
61
+ element,
62
+ setRef
63
+ };
64
+ };
65
+
66
+ //#endregion
67
+ export { useElementScroll as default };
68
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useElementScroll/index.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"}
@@ -0,0 +1,14 @@
1
+ //#region src/hooks/useImage/index.d.ts
2
+ interface Options {
3
+ retryCount?: number;
4
+ retryDelay?: number;
5
+ }
6
+ declare const useImage: (src: string, options?: Options) => {
7
+ loading: boolean;
8
+ error: string | Event | null;
9
+ loaded: boolean;
10
+ retry: () => void;
11
+ };
12
+ //#endregion
13
+ export { useImage };
14
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,56 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ //#region src/hooks/useImage/index.ts
4
+ const useImage = (src, options = {}) => {
5
+ const { retryCount = 0, retryDelay = 1e3 } = options;
6
+ const [loading, setLoading] = useState(true);
7
+ const [error, setError] = useState(null);
8
+ const [loaded, setLoaded] = useState(false);
9
+ const [attemptCount, setAttemptCount] = useState(0);
10
+ const loadImage = useCallback(() => {
11
+ if (!src) {
12
+ setLoading(false);
13
+ setLoaded(false);
14
+ return;
15
+ }
16
+ setLoading(true);
17
+ setError(null);
18
+ const img = new Image();
19
+ img.src = src;
20
+ img.onload = () => {
21
+ setLoading(false);
22
+ setLoaded(true);
23
+ setError(null);
24
+ setAttemptCount(0);
25
+ };
26
+ img.onerror = (err) => {
27
+ setLoading(false);
28
+ setLoaded(false);
29
+ setError(err);
30
+ if (attemptCount < retryCount) setTimeout(() => {
31
+ setAttemptCount((prev) => prev + 1);
32
+ }, retryDelay);
33
+ };
34
+ }, [
35
+ src,
36
+ attemptCount,
37
+ retryCount,
38
+ retryDelay
39
+ ]);
40
+ useEffect(() => {
41
+ loadImage();
42
+ }, [loadImage]);
43
+ return {
44
+ loading,
45
+ error,
46
+ loaded,
47
+ retry: useCallback(() => {
48
+ setAttemptCount(0);
49
+ loadImage();
50
+ }, [loadImage])
51
+ };
52
+ };
53
+
54
+ //#endregion
55
+ export { useImage as default };
56
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/hooks/useImage/index.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\n\ninterface Options {\n retryCount?: number;\n retryDelay?: number;\n}\n\nconst useImage = (src: string, options: Options = {}) => {\n const { retryCount = 0, retryDelay = 1000 } = options;\n\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | Event | null>(null);\n const [loaded, setLoaded] = useState(false);\n const [attemptCount, setAttemptCount] = useState(0);\n\n const loadImage = useCallback(() => {\n if (!src) {\n setLoading(false);\n setLoaded(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n const img = new Image();\n img.src = src;\n\n img.onload = () => {\n setLoading(false);\n setLoaded(true);\n setError(null);\n setAttemptCount(0);\n };\n\n img.onerror = err => {\n setLoading(false);\n setLoaded(false);\n setError(err);\n\n if (attemptCount < retryCount) {\n setTimeout(() => {\n setAttemptCount(prev => prev + 1);\n }, retryDelay);\n }\n };\n }, [src, attemptCount, retryCount, retryDelay]);\n\n useEffect(() => {\n loadImage();\n }, [loadImage]);\n\n const retry = useCallback(() => {\n setAttemptCount(0);\n loadImage();\n }, [loadImage]);\n\n return {\n loading,\n error,\n loaded,\n retry,\n };\n};\n\nexport default useImage;\n"],"mappings":";;;AAOA,MAAM,YAAY,KAAa,UAAmB,EAAE,KAAK;CACvD,MAAM,EAAE,aAAa,GAAG,aAAa,QAAS;CAE9C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,CAAC,OAAO,YAAY,SAAgC,KAAK;CAC/D,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;CAC3C,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CAEnD,MAAM,YAAY,kBAAkB;AAClC,MAAI,CAAC,KAAK;AACR,cAAW,MAAM;AACjB,aAAU,MAAM;AAChB;;AAGF,aAAW,KAAK;AAChB,WAAS,KAAK;EAEd,MAAM,MAAM,IAAI,OAAO;AACvB,MAAI,MAAM;AAEV,MAAI,eAAe;AACjB,cAAW,MAAM;AACjB,aAAU,KAAK;AACf,YAAS,KAAK;AACd,mBAAgB,EAAE;;AAGpB,MAAI,WAAU,QAAO;AACnB,cAAW,MAAM;AACjB,aAAU,MAAM;AAChB,YAAS,IAAI;AAEb,OAAI,eAAe,WACjB,kBAAiB;AACf,qBAAgB,SAAQ,OAAO,EAAE;MAChC,WAAW;;IAGjB;EAAC;EAAK;EAAc;EAAY;EAAW,CAAC;AAE/C,iBAAgB;AACd,aAAW;IACV,CAAC,UAAU,CAAC;AAOf,QAAO;EACL;EACA;EACA;EACA,OATY,kBAAkB;AAC9B,mBAAgB,EAAE;AAClB,cAAW;KACV,CAAC,UAAU,CAAC;EAOd"}