@mohasinac/react 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,722 @@
1
+ // src/hooks/useMediaQuery.ts
2
+ import { useState, useEffect } from "react";
3
+ function useMediaQuery(query) {
4
+ const [matches, setMatches] = useState(
5
+ // Lazy initializer: use real value on first client render, suppressing the flash.
6
+ // Returns false on server (no window) — consistent with no-match default.
7
+ () => typeof window !== "undefined" && window.matchMedia(query).matches
8
+ );
9
+ useEffect(() => {
10
+ if (typeof window === "undefined") return;
11
+ const media = window.matchMedia(query);
12
+ setMatches(media.matches);
13
+ const listener = (e) => setMatches(e.matches);
14
+ media.addEventListener("change", listener);
15
+ return () => media.removeEventListener("change", listener);
16
+ }, [query]);
17
+ return matches;
18
+ }
19
+
20
+ // src/hooks/useBreakpoint.ts
21
+ function useBreakpoint() {
22
+ const isMobile = useMediaQuery("(max-width: 767px)");
23
+ const isTablet = useMediaQuery("(min-width: 768px) and (max-width: 1023px)");
24
+ const isDesktop = useMediaQuery("(min-width: 1024px)");
25
+ const breakpoint = isMobile ? "mobile" : isTablet ? "tablet" : "desktop";
26
+ return {
27
+ isMobile,
28
+ isTablet,
29
+ isDesktop,
30
+ breakpoint
31
+ };
32
+ }
33
+
34
+ // src/hooks/useClickOutside.ts
35
+ import { useEffect as useEffect2, useRef } from "react";
36
+ var EMPTY_REFS = [];
37
+ function useClickOutside(ref, callback, options = {}) {
38
+ const {
39
+ enabled = true,
40
+ eventType = "mousedown",
41
+ additionalRefs = EMPTY_REFS
42
+ } = options;
43
+ const callbackRef = useRef(callback);
44
+ useEffect2(() => {
45
+ callbackRef.current = callback;
46
+ }, [callback]);
47
+ useEffect2(() => {
48
+ if (!enabled) return;
49
+ const handleClickOutside = (event) => {
50
+ if (ref.current && !ref.current.contains(event.target)) {
51
+ const isOutsideAll = additionalRefs.every(
52
+ (additionalRef) => !additionalRef.current || !additionalRef.current.contains(event.target)
53
+ );
54
+ if (isOutsideAll) {
55
+ callbackRef.current(event);
56
+ }
57
+ }
58
+ };
59
+ document.addEventListener(eventType, handleClickOutside);
60
+ document.addEventListener("touchstart", handleClickOutside);
61
+ return () => {
62
+ document.removeEventListener(eventType, handleClickOutside);
63
+ document.removeEventListener("touchstart", handleClickOutside);
64
+ };
65
+ }, [ref, enabled, eventType, additionalRefs]);
66
+ }
67
+
68
+ // src/hooks/useKeyPress.ts
69
+ import { useEffect as useEffect3, useCallback, useRef as useRef2, useMemo } from "react";
70
+ function useKeyPress(key, callback, options = {}) {
71
+ const {
72
+ enabled = true,
73
+ eventType = "keydown",
74
+ preventDefault = false,
75
+ ctrl = false,
76
+ shift = false,
77
+ alt = false,
78
+ meta = false,
79
+ target = typeof document !== "undefined" ? document : null
80
+ } = options;
81
+ const keys = useMemo(() => Array.isArray(key) ? key : [key], [key]);
82
+ const callbackRef = useRef2(callback);
83
+ useEffect3(() => {
84
+ callbackRef.current = callback;
85
+ }, [callback]);
86
+ const handleKeyPress = useCallback(
87
+ (event) => {
88
+ const isKeyMatch = keys.some((k) => event.key === k || event.code === k);
89
+ if (!isKeyMatch) return;
90
+ const modifiersMatch = event.ctrlKey === ctrl && event.shiftKey === shift && event.altKey === alt && event.metaKey === meta;
91
+ if (!modifiersMatch) return;
92
+ if (preventDefault) {
93
+ event.preventDefault();
94
+ }
95
+ callbackRef.current(event);
96
+ },
97
+ [keys, ctrl, shift, alt, meta, preventDefault]
98
+ );
99
+ useEffect3(() => {
100
+ if (!enabled || !target) return;
101
+ target.addEventListener(eventType, handleKeyPress);
102
+ return () => {
103
+ target.removeEventListener(eventType, handleKeyPress);
104
+ };
105
+ }, [enabled, eventType, handleKeyPress, target]);
106
+ }
107
+
108
+ // src/hooks/useLongPress.ts
109
+ import { useRef as useRef3, useCallback as useCallback2, useEffect as useEffect4 } from "react";
110
+ function useLongPress(callback, ms = 500) {
111
+ const timerRef = useRef3(null);
112
+ const callbackRef = useRef3(callback);
113
+ useEffect4(() => {
114
+ callbackRef.current = callback;
115
+ }, [callback]);
116
+ const start = useCallback2(() => {
117
+ timerRef.current = setTimeout(() => {
118
+ callbackRef.current();
119
+ }, ms);
120
+ }, [ms]);
121
+ const cancel = useCallback2(() => {
122
+ if (timerRef.current !== null) {
123
+ clearTimeout(timerRef.current);
124
+ timerRef.current = null;
125
+ }
126
+ }, []);
127
+ useEffect4(() => {
128
+ return () => {
129
+ if (timerRef.current !== null) {
130
+ clearTimeout(timerRef.current);
131
+ }
132
+ };
133
+ }, []);
134
+ return {
135
+ onMouseDown: start,
136
+ onMouseUp: cancel,
137
+ onMouseLeave: cancel,
138
+ onTouchStart: start,
139
+ onTouchEnd: cancel
140
+ };
141
+ }
142
+
143
+ // src/hooks/useGesture.ts
144
+ import { useRef as useRef4, useEffect as useEffect5 } from "react";
145
+ function useGesture(ref, options = {}) {
146
+ const {
147
+ onTap,
148
+ onDoubleTap,
149
+ onPinch,
150
+ onPinching,
151
+ onRotate,
152
+ onRotating,
153
+ doubleTapDelay = 300,
154
+ tapMovementThreshold = 10,
155
+ preventDefault = false
156
+ } = options;
157
+ const touchStartRef = useRef4(
158
+ null
159
+ );
160
+ const lastTapRef = useRef4(0);
161
+ const initialPinchDistanceRef = useRef4(0);
162
+ const initialRotationRef = useRef4(0);
163
+ const onTapRef = useRef4(onTap);
164
+ const onDoubleTapRef = useRef4(onDoubleTap);
165
+ const onPinchRef = useRef4(onPinch);
166
+ const onPinchingRef = useRef4(onPinching);
167
+ const onRotateRef = useRef4(onRotate);
168
+ const onRotatingRef = useRef4(onRotating);
169
+ useEffect5(() => {
170
+ onTapRef.current = onTap;
171
+ }, [onTap]);
172
+ useEffect5(() => {
173
+ onDoubleTapRef.current = onDoubleTap;
174
+ }, [onDoubleTap]);
175
+ useEffect5(() => {
176
+ onPinchRef.current = onPinch;
177
+ }, [onPinch]);
178
+ useEffect5(() => {
179
+ onPinchingRef.current = onPinching;
180
+ }, [onPinching]);
181
+ useEffect5(() => {
182
+ onRotateRef.current = onRotate;
183
+ }, [onRotate]);
184
+ useEffect5(() => {
185
+ onRotatingRef.current = onRotating;
186
+ }, [onRotating]);
187
+ useEffect5(() => {
188
+ const element = ref.current;
189
+ if (!element) return;
190
+ const getDistance = (touch1, touch2) => {
191
+ const dx = touch1.clientX - touch2.clientX;
192
+ const dy = touch1.clientY - touch2.clientY;
193
+ return Math.sqrt(dx * dx + dy * dy);
194
+ };
195
+ const getAngle = (touch1, touch2) => {
196
+ const dx = touch1.clientX - touch2.clientX;
197
+ const dy = touch1.clientY - touch2.clientY;
198
+ return Math.atan2(dy, dx) * (180 / Math.PI);
199
+ };
200
+ const handleTouchStart = (e) => {
201
+ if (preventDefault) e.preventDefault();
202
+ const touch = e.touches[0];
203
+ touchStartRef.current = {
204
+ x: touch.clientX,
205
+ y: touch.clientY,
206
+ time: Date.now()
207
+ };
208
+ if (e.touches.length === 2) {
209
+ initialPinchDistanceRef.current = getDistance(
210
+ e.touches[0],
211
+ e.touches[1]
212
+ );
213
+ initialRotationRef.current = getAngle(e.touches[0], e.touches[1]);
214
+ }
215
+ };
216
+ const handleTouchMove = (e) => {
217
+ var _a, _b;
218
+ if (!touchStartRef.current) return;
219
+ if (e.touches.length === 2 && (onPinchRef.current || onPinchingRef.current || onRotateRef.current || onRotatingRef.current)) {
220
+ if (preventDefault) e.preventDefault();
221
+ const currentDistance = getDistance(e.touches[0], e.touches[1]);
222
+ const currentAngle = getAngle(e.touches[0], e.touches[1]);
223
+ if (initialPinchDistanceRef.current > 0) {
224
+ (_a = onPinchingRef.current) == null ? void 0 : _a.call(
225
+ onPinchingRef,
226
+ currentDistance / initialPinchDistanceRef.current
227
+ );
228
+ }
229
+ if (initialRotationRef.current !== 0) {
230
+ (_b = onRotatingRef.current) == null ? void 0 : _b.call(onRotatingRef, currentAngle - initialRotationRef.current);
231
+ }
232
+ }
233
+ };
234
+ const handleTouchEnd = (e) => {
235
+ var _a, _b, _c;
236
+ if (!touchStartRef.current) return;
237
+ if (preventDefault) e.preventDefault();
238
+ const touch = e.changedTouches[0];
239
+ const deltaX = Math.abs(touch.clientX - touchStartRef.current.x);
240
+ const deltaY = Math.abs(touch.clientY - touchStartRef.current.y);
241
+ if (deltaX < tapMovementThreshold && deltaY < tapMovementThreshold) {
242
+ const now = Date.now();
243
+ const timeSinceLastTap = now - lastTapRef.current;
244
+ if (onDoubleTapRef.current && timeSinceLastTap < doubleTapDelay) {
245
+ onDoubleTapRef.current(
246
+ touchStartRef.current.x,
247
+ touchStartRef.current.y
248
+ );
249
+ lastTapRef.current = 0;
250
+ } else {
251
+ (_a = onTapRef.current) == null ? void 0 : _a.call(onTapRef, touchStartRef.current.x, touchStartRef.current.y);
252
+ lastTapRef.current = now;
253
+ }
254
+ }
255
+ if (e.touches.length === 0 && initialPinchDistanceRef.current > 0) {
256
+ const currentDistance = getDistance(
257
+ e.changedTouches[0],
258
+ e.changedTouches[1] || e.changedTouches[0]
259
+ );
260
+ (_b = onPinchRef.current) == null ? void 0 : _b.call(
261
+ onPinchRef,
262
+ currentDistance / initialPinchDistanceRef.current,
263
+ currentDistance
264
+ );
265
+ initialPinchDistanceRef.current = 0;
266
+ }
267
+ if (e.touches.length === 0 && initialRotationRef.current !== 0) {
268
+ const currentAngle = getAngle(
269
+ e.changedTouches[0],
270
+ e.changedTouches[1] || e.changedTouches[0]
271
+ );
272
+ (_c = onRotateRef.current) == null ? void 0 : _c.call(onRotateRef, currentAngle - initialRotationRef.current);
273
+ initialRotationRef.current = 0;
274
+ }
275
+ touchStartRef.current = null;
276
+ };
277
+ const handleMouseDown = (e) => {
278
+ if (preventDefault) e.preventDefault();
279
+ touchStartRef.current = { x: e.clientX, y: e.clientY, time: Date.now() };
280
+ };
281
+ const handleMouseUp = (e) => {
282
+ var _a;
283
+ if (!touchStartRef.current) return;
284
+ if (preventDefault) e.preventDefault();
285
+ const deltaX = Math.abs(e.clientX - touchStartRef.current.x);
286
+ const deltaY = Math.abs(e.clientY - touchStartRef.current.y);
287
+ if (deltaX < tapMovementThreshold && deltaY < tapMovementThreshold) {
288
+ const now = Date.now();
289
+ const timeSinceLastTap = now - lastTapRef.current;
290
+ if (onDoubleTapRef.current && timeSinceLastTap < doubleTapDelay) {
291
+ onDoubleTapRef.current(
292
+ touchStartRef.current.x,
293
+ touchStartRef.current.y
294
+ );
295
+ lastTapRef.current = 0;
296
+ } else {
297
+ (_a = onTapRef.current) == null ? void 0 : _a.call(onTapRef, touchStartRef.current.x, touchStartRef.current.y);
298
+ lastTapRef.current = now;
299
+ }
300
+ }
301
+ touchStartRef.current = null;
302
+ };
303
+ element.addEventListener("touchstart", handleTouchStart, {
304
+ passive: !preventDefault
305
+ });
306
+ element.addEventListener("touchmove", handleTouchMove, {
307
+ passive: !preventDefault
308
+ });
309
+ element.addEventListener("touchend", handleTouchEnd, {
310
+ passive: !preventDefault
311
+ });
312
+ element.addEventListener("mousedown", handleMouseDown);
313
+ element.addEventListener("mouseup", handleMouseUp);
314
+ return () => {
315
+ element.removeEventListener("touchstart", handleTouchStart);
316
+ element.removeEventListener("touchmove", handleTouchMove);
317
+ element.removeEventListener("touchend", handleTouchEnd);
318
+ element.removeEventListener("mousedown", handleMouseDown);
319
+ element.removeEventListener("mouseup", handleMouseUp);
320
+ };
321
+ }, [ref, doubleTapDelay, tapMovementThreshold, preventDefault]);
322
+ }
323
+
324
+ // src/hooks/useSwipe.ts
325
+ import { useRef as useRef5, useEffect as useEffect6 } from "react";
326
+ function useSwipe(ref, options = {}) {
327
+ const {
328
+ minSwipeDistance = 50,
329
+ maxSwipeTime = 300,
330
+ velocityThreshold = 0.3,
331
+ onSwipe,
332
+ onSwipeLeft,
333
+ onSwipeRight,
334
+ onSwipeUp,
335
+ onSwipeDown,
336
+ onSwiping,
337
+ onSwipeStart,
338
+ onSwipeEnd,
339
+ preventDefault = false
340
+ } = options;
341
+ const touchStartRef = useRef5(
342
+ null
343
+ );
344
+ const isSwiping = useRef5(false);
345
+ const onSwipeRef = useRef5(onSwipe);
346
+ const onSwipeLeftRef = useRef5(onSwipeLeft);
347
+ const onSwipeRightRef = useRef5(onSwipeRight);
348
+ const onSwipeUpRef = useRef5(onSwipeUp);
349
+ const onSwipeDownRef = useRef5(onSwipeDown);
350
+ const onSwipingRef = useRef5(onSwiping);
351
+ const onSwipeStartRef = useRef5(onSwipeStart);
352
+ const onSwipeEndRef = useRef5(onSwipeEnd);
353
+ useEffect6(() => {
354
+ onSwipeRef.current = onSwipe;
355
+ }, [onSwipe]);
356
+ useEffect6(() => {
357
+ onSwipeLeftRef.current = onSwipeLeft;
358
+ }, [onSwipeLeft]);
359
+ useEffect6(() => {
360
+ onSwipeRightRef.current = onSwipeRight;
361
+ }, [onSwipeRight]);
362
+ useEffect6(() => {
363
+ onSwipeUpRef.current = onSwipeUp;
364
+ }, [onSwipeUp]);
365
+ useEffect6(() => {
366
+ onSwipeDownRef.current = onSwipeDown;
367
+ }, [onSwipeDown]);
368
+ useEffect6(() => {
369
+ onSwipingRef.current = onSwiping;
370
+ }, [onSwiping]);
371
+ useEffect6(() => {
372
+ onSwipeStartRef.current = onSwipeStart;
373
+ }, [onSwipeStart]);
374
+ useEffect6(() => {
375
+ onSwipeEndRef.current = onSwipeEnd;
376
+ }, [onSwipeEnd]);
377
+ useEffect6(() => {
378
+ const element = ref.current;
379
+ if (!element) return;
380
+ const processSwipe = (deltaX, deltaY, deltaTime) => {
381
+ var _a, _b, _c, _d, _e;
382
+ const absX = Math.abs(deltaX);
383
+ const absY = Math.abs(deltaY);
384
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
385
+ const velocity = distance / deltaTime;
386
+ if (distance < minSwipeDistance || deltaTime > maxSwipeTime || velocity < velocityThreshold)
387
+ return;
388
+ let direction;
389
+ if (absX > absY) {
390
+ direction = deltaX > 0 ? "right" : "left";
391
+ if (direction === "left") (_a = onSwipeLeftRef.current) == null ? void 0 : _a.call(onSwipeLeftRef, distance, velocity);
392
+ else (_b = onSwipeRightRef.current) == null ? void 0 : _b.call(onSwipeRightRef, distance, velocity);
393
+ } else {
394
+ direction = deltaY > 0 ? "down" : "up";
395
+ if (direction === "up") (_c = onSwipeUpRef.current) == null ? void 0 : _c.call(onSwipeUpRef, distance, velocity);
396
+ else (_d = onSwipeDownRef.current) == null ? void 0 : _d.call(onSwipeDownRef, distance, velocity);
397
+ }
398
+ (_e = onSwipeRef.current) == null ? void 0 : _e.call(onSwipeRef, direction, distance, velocity);
399
+ };
400
+ const handleTouchStart = (e) => {
401
+ var _a;
402
+ if (preventDefault) e.preventDefault();
403
+ const touch = e.touches[0];
404
+ touchStartRef.current = {
405
+ x: touch.clientX,
406
+ y: touch.clientY,
407
+ time: Date.now()
408
+ };
409
+ isSwiping.current = true;
410
+ (_a = onSwipeStartRef.current) == null ? void 0 : _a.call(onSwipeStartRef);
411
+ };
412
+ const handleTouchMove = (e) => {
413
+ var _a;
414
+ if (!touchStartRef.current || !isSwiping.current) return;
415
+ if (preventDefault) e.preventDefault();
416
+ (_a = onSwipingRef.current) == null ? void 0 : _a.call(
417
+ onSwipingRef,
418
+ e.touches[0].clientX - touchStartRef.current.x,
419
+ e.touches[0].clientY - touchStartRef.current.y
420
+ );
421
+ };
422
+ const handleTouchEnd = (e) => {
423
+ var _a;
424
+ if (!touchStartRef.current || !isSwiping.current) return;
425
+ if (preventDefault) e.preventDefault();
426
+ const touch = e.changedTouches[0];
427
+ processSwipe(
428
+ touch.clientX - touchStartRef.current.x,
429
+ touch.clientY - touchStartRef.current.y,
430
+ Date.now() - touchStartRef.current.time
431
+ );
432
+ touchStartRef.current = null;
433
+ isSwiping.current = false;
434
+ (_a = onSwipeEndRef.current) == null ? void 0 : _a.call(onSwipeEndRef);
435
+ };
436
+ const handleMouseDown = (e) => {
437
+ var _a;
438
+ if (preventDefault) e.preventDefault();
439
+ touchStartRef.current = { x: e.clientX, y: e.clientY, time: Date.now() };
440
+ isSwiping.current = true;
441
+ (_a = onSwipeStartRef.current) == null ? void 0 : _a.call(onSwipeStartRef);
442
+ };
443
+ const handleMouseMove = (e) => {
444
+ var _a;
445
+ if (!touchStartRef.current || !isSwiping.current) return;
446
+ (_a = onSwipingRef.current) == null ? void 0 : _a.call(
447
+ onSwipingRef,
448
+ e.clientX - touchStartRef.current.x,
449
+ e.clientY - touchStartRef.current.y
450
+ );
451
+ };
452
+ const handleMouseUp = (e) => {
453
+ var _a;
454
+ if (!touchStartRef.current || !isSwiping.current) return;
455
+ if (preventDefault) e.preventDefault();
456
+ processSwipe(
457
+ e.clientX - touchStartRef.current.x,
458
+ e.clientY - touchStartRef.current.y,
459
+ Date.now() - touchStartRef.current.time
460
+ );
461
+ touchStartRef.current = null;
462
+ isSwiping.current = false;
463
+ (_a = onSwipeEndRef.current) == null ? void 0 : _a.call(onSwipeEndRef);
464
+ };
465
+ element.addEventListener("touchstart", handleTouchStart, {
466
+ passive: !preventDefault
467
+ });
468
+ element.addEventListener("touchmove", handleTouchMove, {
469
+ passive: !preventDefault
470
+ });
471
+ element.addEventListener("touchend", handleTouchEnd, {
472
+ passive: !preventDefault
473
+ });
474
+ element.addEventListener("mousedown", handleMouseDown);
475
+ document.addEventListener("mousemove", handleMouseMove);
476
+ document.addEventListener("mouseup", handleMouseUp);
477
+ return () => {
478
+ element.removeEventListener("touchstart", handleTouchStart);
479
+ element.removeEventListener("touchmove", handleTouchMove);
480
+ element.removeEventListener("touchend", handleTouchEnd);
481
+ element.removeEventListener("mousedown", handleMouseDown);
482
+ document.removeEventListener("mousemove", handleMouseMove);
483
+ document.removeEventListener("mouseup", handleMouseUp);
484
+ };
485
+ }, [ref, minSwipeDistance, maxSwipeTime, velocityThreshold, preventDefault]);
486
+ }
487
+
488
+ // src/hooks/usePullToRefresh.ts
489
+ import { useRef as useRef6, useState as useState2, useCallback as useCallback3, useEffect as useEffect7 } from "react";
490
+ function usePullToRefresh(onRefresh, options = {}) {
491
+ const { threshold = 80 } = options;
492
+ const containerRef = useRef6(null);
493
+ const [isPulling, setIsPulling] = useState2(false);
494
+ const [progress, setProgress] = useState2(0);
495
+ const startYRef = useRef6(null);
496
+ const thresholdReachedRef = useRef6(false);
497
+ const onRefreshRef = useRef6(onRefresh);
498
+ useEffect7(() => {
499
+ onRefreshRef.current = onRefresh;
500
+ }, [onRefresh]);
501
+ const handleTouchStart = useCallback3((e) => {
502
+ const el = containerRef.current;
503
+ if (!el || el.scrollTop > 0) return;
504
+ startYRef.current = e.touches[0].clientY;
505
+ thresholdReachedRef.current = false;
506
+ }, []);
507
+ const handleTouchMove = useCallback3(
508
+ (e) => {
509
+ if (startYRef.current === null) return;
510
+ const delta = e.touches[0].clientY - startYRef.current;
511
+ if (delta <= 0) {
512
+ setProgress(0);
513
+ setIsPulling(false);
514
+ return;
515
+ }
516
+ const prog = Math.min(delta / threshold, 1);
517
+ setProgress(prog);
518
+ setIsPulling(true);
519
+ thresholdReachedRef.current = prog >= 1;
520
+ },
521
+ [threshold]
522
+ );
523
+ const handleTouchEnd = useCallback3(async () => {
524
+ setIsPulling(false);
525
+ setProgress(0);
526
+ startYRef.current = null;
527
+ if (thresholdReachedRef.current) {
528
+ thresholdReachedRef.current = false;
529
+ await onRefreshRef.current();
530
+ }
531
+ }, []);
532
+ useEffect7(() => {
533
+ const el = containerRef.current;
534
+ if (!el) return;
535
+ el.addEventListener("touchstart", handleTouchStart, { passive: true });
536
+ el.addEventListener("touchmove", handleTouchMove, { passive: true });
537
+ el.addEventListener("touchend", handleTouchEnd);
538
+ return () => {
539
+ el.removeEventListener("touchstart", handleTouchStart);
540
+ el.removeEventListener("touchmove", handleTouchMove);
541
+ el.removeEventListener("touchend", handleTouchEnd);
542
+ };
543
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd]);
544
+ return { containerRef, isPulling, progress };
545
+ }
546
+
547
+ // src/hooks/useCountdown.ts
548
+ import { useState as useState3, useEffect as useEffect8 } from "react";
549
+ function resolveMs(endDate) {
550
+ if (!endDate) return NaN;
551
+ if (endDate instanceof Date) return endDate.getTime();
552
+ if (typeof endDate === "object" && endDate !== null && "_seconds" in endDate && typeof endDate._seconds === "number") {
553
+ return endDate._seconds * 1e3;
554
+ }
555
+ return new Date(endDate).getTime();
556
+ }
557
+ function useCountdown(endDate) {
558
+ const getRemaining = () => {
559
+ if (!endDate) return null;
560
+ const ms = resolveMs(endDate);
561
+ if (Number.isNaN(ms)) return null;
562
+ const diff = ms - Date.now();
563
+ if (diff <= 0) return null;
564
+ return {
565
+ days: Math.floor(diff / (1e3 * 60 * 60 * 24)),
566
+ hours: Math.floor(diff % (1e3 * 60 * 60 * 24) / (1e3 * 60 * 60)),
567
+ minutes: Math.floor(diff % (1e3 * 60 * 60) / (1e3 * 60)),
568
+ seconds: Math.floor(diff % (1e3 * 60) / 1e3)
569
+ };
570
+ };
571
+ const [remaining, setRemaining] = useState3(
572
+ getRemaining
573
+ );
574
+ useEffect8(() => {
575
+ const id = setInterval(() => setRemaining(getRemaining()), 1e3);
576
+ return () => clearInterval(id);
577
+ }, [endDate]);
578
+ return remaining;
579
+ }
580
+
581
+ // src/hooks/useCamera.ts
582
+ import { useCallback as useCallback4, useEffect as useEffect9, useRef as useRef7, useState as useState4 } from "react";
583
+ function useCamera() {
584
+ const isSupported = typeof navigator !== "undefined" && typeof navigator.mediaDevices !== "undefined" && typeof navigator.mediaDevices.getUserMedia === "function";
585
+ const videoRef = useRef7(null);
586
+ const streamRef = useRef7(null);
587
+ const mediaRecorderRef = useRef7(null);
588
+ const chunksRef = useRef7([]);
589
+ const currentFacingModeRef = useRef7("environment");
590
+ const stopRecordingResolveRef = useRef7(null);
591
+ const [stream, setStream] = useState4(null);
592
+ const [isActive, setIsActive] = useState4(false);
593
+ const [isCapturing, setIsCapturing] = useState4(false);
594
+ const [error, setError] = useState4(null);
595
+ const stopCamera = useCallback4(() => {
596
+ if (streamRef.current) {
597
+ streamRef.current.getTracks().forEach((t) => t.stop());
598
+ streamRef.current = null;
599
+ }
600
+ if (videoRef.current) {
601
+ videoRef.current.srcObject = null;
602
+ }
603
+ setStream(null);
604
+ setIsActive(false);
605
+ setIsCapturing(false);
606
+ }, []);
607
+ useEffect9(() => {
608
+ return () => {
609
+ stopCamera();
610
+ };
611
+ }, [stopCamera]);
612
+ const startCamera = useCallback4(
613
+ async (options) => {
614
+ var _a, _b;
615
+ if (!isSupported) {
616
+ setError("Camera is not supported on this device");
617
+ return;
618
+ }
619
+ setError(null);
620
+ stopCamera();
621
+ const facingMode = (_a = options == null ? void 0 : options.facingMode) != null ? _a : "environment";
622
+ currentFacingModeRef.current = facingMode;
623
+ const videoConstraints = (options == null ? void 0 : options.video) !== void 0 ? options.video : { facingMode };
624
+ try {
625
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
626
+ video: videoConstraints,
627
+ audio: (_b = options == null ? void 0 : options.audio) != null ? _b : false
628
+ });
629
+ streamRef.current = mediaStream;
630
+ setStream(mediaStream);
631
+ setIsActive(true);
632
+ if (videoRef.current) {
633
+ videoRef.current.srcObject = mediaStream;
634
+ }
635
+ } catch (err) {
636
+ const message = err instanceof DOMException && err.name === "NotAllowedError" ? "Camera permission denied. Please allow access in your browser settings." : "Camera unavailable";
637
+ setError(message);
638
+ }
639
+ },
640
+ [isSupported, stopCamera]
641
+ );
642
+ const takePhoto = useCallback4(() => {
643
+ const video = videoRef.current;
644
+ if (!video || !isActive) return null;
645
+ const canvas = document.createElement("canvas");
646
+ canvas.width = video.videoWidth;
647
+ canvas.height = video.videoHeight;
648
+ const ctx = canvas.getContext("2d");
649
+ if (!ctx) return null;
650
+ ctx.drawImage(video, 0, 0);
651
+ let result = null;
652
+ canvas.toBlob(
653
+ (blob) => {
654
+ result = blob;
655
+ },
656
+ "image/webp",
657
+ 0.92
658
+ );
659
+ return result;
660
+ }, [isActive]);
661
+ const startRecording = useCallback4(() => {
662
+ if (!streamRef.current || !isActive) return;
663
+ chunksRef.current = [];
664
+ const recorder = new MediaRecorder(streamRef.current, {
665
+ mimeType: "video/webm"
666
+ });
667
+ recorder.ondataavailable = (e) => {
668
+ if (e.data.size > 0) chunksRef.current.push(e.data);
669
+ };
670
+ recorder.onstop = () => {
671
+ const blob = new Blob(chunksRef.current, { type: "video/webm" });
672
+ setIsCapturing(false);
673
+ if (stopRecordingResolveRef.current) {
674
+ stopRecordingResolveRef.current(blob);
675
+ stopRecordingResolveRef.current = null;
676
+ }
677
+ };
678
+ mediaRecorderRef.current = recorder;
679
+ recorder.start();
680
+ setIsCapturing(true);
681
+ }, [isActive]);
682
+ const stopRecording = useCallback4(() => {
683
+ return new Promise((resolve) => {
684
+ stopRecordingResolveRef.current = resolve;
685
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
686
+ mediaRecorderRef.current.stop();
687
+ } else {
688
+ resolve(new Blob([], { type: "video/webm" }));
689
+ }
690
+ });
691
+ }, []);
692
+ const switchCamera = useCallback4(async () => {
693
+ const next = currentFacingModeRef.current === "environment" ? "user" : "environment";
694
+ await startCamera({ facingMode: next });
695
+ }, [startCamera]);
696
+ return {
697
+ isSupported,
698
+ isActive,
699
+ isCapturing,
700
+ stream,
701
+ error,
702
+ videoRef,
703
+ startCamera,
704
+ stopCamera,
705
+ takePhoto,
706
+ startRecording,
707
+ stopRecording,
708
+ switchCamera
709
+ };
710
+ }
711
+ export {
712
+ useBreakpoint,
713
+ useCamera,
714
+ useClickOutside,
715
+ useCountdown,
716
+ useGesture,
717
+ useKeyPress,
718
+ useLongPress,
719
+ useMediaQuery,
720
+ usePullToRefresh,
721
+ useSwipe
722
+ };