@umituz/react-native-design-system 4.23.104 → 4.23.106

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "4.23.104",
3
+ "version": "4.23.106",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -30,7 +30,12 @@ export async function downloadFile(url: string, dest?: string): Promise<FileOper
30
30
  } catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
31
31
  }
32
32
 
33
- export async function downloadFileWithProgress(url: string, cacheDir: string, onProgress?: DownloadProgressCallback): Promise<DownloadWithProgressResult> {
33
+ export async function downloadFileWithProgress(
34
+ url: string,
35
+ cacheDir: string,
36
+ onProgress?: DownloadProgressCallback,
37
+ signal?: AbortSignal
38
+ ): Promise<DownloadWithProgressResult> {
34
39
  try {
35
40
  const dir = new Directory(cacheDir);
36
41
  if (!dir.exists) dir.create({ intermediates: true, idempotent: true });
@@ -38,9 +43,9 @@ export async function downloadFileWithProgress(url: string, cacheDir: string, on
38
43
  const destUri = getCacheUri(url, cacheDir);
39
44
  if (new File(destUri).exists) return { success: true, uri: destUri, fromCache: true };
40
45
 
41
- const response = await fetch(url);
46
+ const response = await fetch(url, { signal });
42
47
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
43
-
48
+
44
49
  const totalBytes = parseInt(response.headers.get("content-length") || "0", 10);
45
50
  if (!response.body) return { ...(await downloadFile(url, destUri)), fromCache: false };
46
51
 
@@ -48,20 +53,29 @@ export async function downloadFileWithProgress(url: string, cacheDir: string, on
48
53
  const chunks: Uint8Array[] = [];
49
54
  let received = 0;
50
55
 
51
- while (true) {
52
- const { done, value } = await reader.read();
53
- if (done) break;
54
- chunks.push(value);
55
- received += value.length;
56
- onProgress?.({ totalBytesWritten: received, totalBytesExpectedToWrite: totalBytes || received });
57
- }
56
+ try {
57
+ while (true) {
58
+ if (signal?.aborted) {
59
+ await reader.cancel();
60
+ throw new Error("Download aborted");
61
+ }
58
62
 
59
- const all = new Uint8Array(received);
60
- let pos = 0;
61
- for (const c of chunks) { all.set(c, pos); pos += c.length; }
62
- new File(destUri).write(all);
63
+ const { done, value } = await reader.read();
64
+ if (done) break;
65
+ chunks.push(value);
66
+ received += value.length;
67
+ onProgress?.({ totalBytesWritten: received, totalBytesExpectedToWrite: totalBytes || received });
68
+ }
63
69
 
64
- return { success: true, uri: destUri, fromCache: false };
70
+ const all = new Uint8Array(received);
71
+ let pos = 0;
72
+ for (const c of chunks) { all.set(c, pos); pos += c.length; }
73
+ new File(destUri).write(all);
74
+
75
+ return { success: true, uri: destUri, fromCache: false };
76
+ } finally {
77
+ reader.releaseLock();
78
+ }
65
79
  } catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
66
80
  }
67
81
 
@@ -2,7 +2,7 @@
2
2
  * Presentation - Image Gallery Hook
3
3
  */
4
4
 
5
- import { useState, useCallback, useMemo } from 'react';
5
+ import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
6
6
  import { ImageViewerService } from '../../infrastructure/services/ImageViewerService';
7
7
  import type {
8
8
  ImageViewerItem,
@@ -29,6 +29,13 @@ export const useImageGallery = (
29
29
  defaultOptions || ImageViewerService.getDefaultOptions()
30
30
  );
31
31
 
32
+ // Use ref to track latest options and avoid stale closures
33
+ const optionsRef = useRef(galleryOptions);
34
+
35
+ useEffect(() => {
36
+ optionsRef.current = galleryOptions;
37
+ }, [galleryOptions]);
38
+
32
39
  const open = useCallback(
33
40
  (
34
41
  imageData: ImageViewerItem[] | string[],
@@ -44,32 +51,32 @@ export const useImageGallery = (
44
51
  setCurrentIndex(options?.index ?? startIndex);
45
52
 
46
53
  if (options) {
47
- setGalleryOptions({
48
- ...galleryOptions,
54
+ setGalleryOptions(prev => ({
55
+ ...prev,
49
56
  ...options,
50
- });
57
+ }));
51
58
  }
52
59
 
53
60
  setVisible(true);
54
61
  },
55
- [galleryOptions]
62
+ []
56
63
  );
57
64
 
58
65
  const close = useCallback(() => {
59
66
  setVisible(false);
60
67
 
61
- if (galleryOptions.onDismiss) {
62
- galleryOptions.onDismiss();
68
+ if (optionsRef.current.onDismiss) {
69
+ optionsRef.current.onDismiss();
63
70
  }
64
- }, [galleryOptions]);
71
+ }, []);
65
72
 
66
73
  const setIndex = useCallback((index: number) => {
67
74
  setCurrentIndex(index);
68
75
 
69
- if (galleryOptions.onIndexChange) {
70
- galleryOptions.onIndexChange(index);
76
+ if (optionsRef.current.onIndexChange) {
77
+ optionsRef.current.onIndexChange(index);
71
78
  }
72
- }, [galleryOptions]);
79
+ }, []);
73
80
 
74
81
  const options = useMemo(() => ({
75
82
  backgroundColor: galleryOptions.backgroundColor || '#000000',
@@ -40,7 +40,7 @@ export function useAppInitialization(
40
40
  useEffect(() => {
41
41
  onReadyRef.current = onReady;
42
42
  onErrorRef.current = onError;
43
- });
43
+ }, [onReady, onError]);
44
44
 
45
45
  useEffect(() => {
46
46
  if (skip) {
@@ -88,18 +88,24 @@ export const useCalendarPresentation = (): UseCalendarReturn => {
88
88
  // Load events on mount
89
89
  useEffect(() => {
90
90
  actions.loadEvents();
91
- }, []);
91
+ }, [actions.loadEvents]);
92
92
 
93
93
  // Get events for selected date
94
94
  const selectedDateEvents = useMemo(() => {
95
- return actions.getEventsForDate(selectedDate);
95
+ return events.filter(event => {
96
+ const eventDate = new Date(event.date);
97
+ return eventDate.toDateString() === selectedDate.toDateString();
98
+ });
96
99
  }, [selectedDate, events]);
97
100
 
98
101
  // Get events for current month
99
102
  const currentMonthEvents = useMemo(() => {
100
103
  const year = currentMonth.getFullYear();
101
104
  const month = currentMonth.getMonth();
102
- return actions.getEventsForMonth(year, month);
105
+ return events.filter(event => {
106
+ const eventDate = new Date(event.date);
107
+ return eventDate.getFullYear() === year && eventDate.getMonth() === month;
108
+ });
103
109
  }, [currentMonth, events]);
104
110
 
105
111
  return {
@@ -33,16 +33,22 @@ export const useOnboardingNavigation = (
33
33
  const [currentIndex, setCurrentIndex] = useState(0);
34
34
 
35
35
  const goToNext = useCallback(() => {
36
- if (currentIndex < totalSlides - 1) {
37
- setCurrentIndex(currentIndex + 1);
38
- }
39
- }, [currentIndex, totalSlides]);
36
+ setCurrentIndex(prev => {
37
+ if (prev < totalSlides - 1) {
38
+ return prev + 1;
39
+ }
40
+ return prev;
41
+ });
42
+ }, [totalSlides]);
40
43
 
41
44
  const goToPrevious = useCallback(() => {
42
- if (currentIndex > 0) {
43
- setCurrentIndex(currentIndex - 1);
44
- }
45
- }, [currentIndex]);
45
+ setCurrentIndex(prev => {
46
+ if (prev > 0) {
47
+ return prev - 1;
48
+ }
49
+ return prev;
50
+ });
51
+ }, []);
46
52
 
47
53
  const complete = useCallback(async () => {
48
54
  if (onComplete) {
@@ -3,7 +3,7 @@
3
3
  * Handles swipe gestures for onboarding navigation
4
4
  */
5
5
 
6
- import { useRef } from "react";
6
+ import { useRef, useEffect } from "react";
7
7
  import { PanResponder } from "react-native";
8
8
 
9
9
  interface UseOnboardingGesturesProps {
@@ -19,6 +19,24 @@ export const useOnboardingGestures = ({
19
19
  onNext,
20
20
  onBack,
21
21
  }: UseOnboardingGesturesProps) => {
22
+ // Use refs to track latest values and avoid stale closures
23
+ const latestPropsRef = useRef({
24
+ isFirstSlide,
25
+ isAnswerValid,
26
+ onNext,
27
+ onBack,
28
+ });
29
+
30
+ // Update refs on every render to ensure PanResponder has fresh values
31
+ useEffect(() => {
32
+ latestPropsRef.current = {
33
+ isFirstSlide,
34
+ isAnswerValid,
35
+ onNext,
36
+ onBack,
37
+ };
38
+ });
39
+
22
40
  const panResponder = useRef(
23
41
  PanResponder.create({
24
42
  onMoveShouldSetPanResponder: (_, gestureState) => {
@@ -26,6 +44,9 @@ export const useOnboardingGestures = ({
26
44
  return Math.abs(gestureState.dx) > 20 && Math.abs(gestureState.dy) < 40;
27
45
  },
28
46
  onPanResponderRelease: (_, gestureState) => {
47
+ // Read from ref to get latest values
48
+ const { isFirstSlide, isAnswerValid, onNext, onBack } = latestPropsRef.current;
49
+
29
50
  if (gestureState.dx > 50) {
30
51
  // Swipe Right -> Previous
31
52
  if (!isFirstSlide) {
@@ -86,8 +86,12 @@ export function usePrefetchOnMount<TData = unknown>(
86
86
  options: PrefetchOptions = {},
87
87
  ) {
88
88
  const queryClient = useQueryClient();
89
+ const hasPrefetchedRef = useRef(false);
89
90
 
90
91
  useEffect(() => {
92
+ if (hasPrefetchedRef.current) return;
93
+
94
+ hasPrefetchedRef.current = true;
91
95
  queryClient.prefetchQuery({
92
96
  queryKey,
93
97
  queryFn,