@idealyst/files 1.2.96

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 (42) hide show
  1. package/package.json +94 -0
  2. package/src/components/DropZone.native.tsx +96 -0
  3. package/src/components/DropZone.styles.tsx +99 -0
  4. package/src/components/DropZone.web.tsx +178 -0
  5. package/src/components/FilePickerButton.native.tsx +82 -0
  6. package/src/components/FilePickerButton.styles.tsx +112 -0
  7. package/src/components/FilePickerButton.web.tsx +84 -0
  8. package/src/components/UploadProgress.native.tsx +203 -0
  9. package/src/components/UploadProgress.styles.tsx +90 -0
  10. package/src/components/UploadProgress.web.tsx +201 -0
  11. package/src/components/index.native.ts +8 -0
  12. package/src/components/index.ts +6 -0
  13. package/src/components/index.web.ts +8 -0
  14. package/src/constants.ts +336 -0
  15. package/src/examples/index.ts +181 -0
  16. package/src/hooks/createUseFilePickerHook.ts +169 -0
  17. package/src/hooks/createUseFileUploadHook.ts +173 -0
  18. package/src/hooks/index.native.ts +12 -0
  19. package/src/hooks/index.ts +12 -0
  20. package/src/hooks/index.web.ts +12 -0
  21. package/src/index.native.ts +142 -0
  22. package/src/index.ts +139 -0
  23. package/src/index.web.ts +142 -0
  24. package/src/permissions/index.native.ts +8 -0
  25. package/src/permissions/index.ts +8 -0
  26. package/src/permissions/index.web.ts +8 -0
  27. package/src/permissions/permissions.native.ts +177 -0
  28. package/src/permissions/permissions.web.ts +96 -0
  29. package/src/picker/FilePicker.native.ts +407 -0
  30. package/src/picker/FilePicker.web.ts +366 -0
  31. package/src/picker/index.native.ts +2 -0
  32. package/src/picker/index.ts +2 -0
  33. package/src/picker/index.web.ts +2 -0
  34. package/src/types.ts +990 -0
  35. package/src/uploader/ChunkedUploader.ts +312 -0
  36. package/src/uploader/FileUploader.native.ts +435 -0
  37. package/src/uploader/FileUploader.web.ts +350 -0
  38. package/src/uploader/UploadQueue.ts +519 -0
  39. package/src/uploader/index.native.ts +4 -0
  40. package/src/uploader/index.ts +4 -0
  41. package/src/uploader/index.web.ts +4 -0
  42. package/src/utils.ts +586 -0
@@ -0,0 +1,336 @@
1
+ import type {
2
+ FilePickerConfig,
3
+ UploadConfig,
4
+ FilePickerStatus,
5
+ QueueStatus,
6
+ FilePickerPresets,
7
+ UploadPresets,
8
+ FileType,
9
+ } from './types';
10
+
11
+ // ============================================
12
+ // FILE TYPE MAPPINGS
13
+ // ============================================
14
+
15
+ /**
16
+ * MIME types for each file type category.
17
+ */
18
+ export const FILE_TYPE_MIME_TYPES: Record<FileType, string[]> = {
19
+ image: [
20
+ 'image/jpeg',
21
+ 'image/png',
22
+ 'image/gif',
23
+ 'image/webp',
24
+ 'image/heic',
25
+ 'image/heif',
26
+ 'image/svg+xml',
27
+ 'image/bmp',
28
+ 'image/tiff',
29
+ ],
30
+ video: [
31
+ 'video/mp4',
32
+ 'video/quicktime',
33
+ 'video/webm',
34
+ 'video/x-msvideo',
35
+ 'video/x-matroska',
36
+ 'video/mpeg',
37
+ 'video/3gpp',
38
+ ],
39
+ audio: [
40
+ 'audio/mpeg',
41
+ 'audio/wav',
42
+ 'audio/ogg',
43
+ 'audio/mp4',
44
+ 'audio/webm',
45
+ 'audio/x-m4a',
46
+ 'audio/aac',
47
+ 'audio/flac',
48
+ ],
49
+ document: [
50
+ 'application/pdf',
51
+ 'application/msword',
52
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
53
+ 'application/vnd.ms-excel',
54
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
55
+ 'application/vnd.ms-powerpoint',
56
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
57
+ 'text/plain',
58
+ 'text/csv',
59
+ 'text/markdown',
60
+ 'application/rtf',
61
+ ],
62
+ archive: [
63
+ 'application/zip',
64
+ 'application/x-rar-compressed',
65
+ 'application/x-tar',
66
+ 'application/gzip',
67
+ 'application/x-7z-compressed',
68
+ ],
69
+ any: ['*/*'],
70
+ };
71
+
72
+ /**
73
+ * File extensions for each file type category.
74
+ */
75
+ export const FILE_TYPE_EXTENSIONS: Record<FileType, string[]> = {
76
+ image: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.heic', '.heif', '.svg', '.bmp', '.tiff'],
77
+ video: ['.mp4', '.mov', '.webm', '.avi', '.mkv', '.mpeg', '.3gp'],
78
+ audio: ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac', '.wma'],
79
+ document: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.csv', '.md', '.rtf'],
80
+ archive: ['.zip', '.rar', '.tar', '.gz', '.7z'],
81
+ any: ['*'],
82
+ };
83
+
84
+ /**
85
+ * Document picker type mappings for react-native-document-picker.
86
+ */
87
+ export const DOCUMENT_PICKER_TYPES: Record<FileType, string[]> = {
88
+ image: ['public.image'],
89
+ video: ['public.movie'],
90
+ audio: ['public.audio'],
91
+ document: ['public.content', 'public.data'],
92
+ archive: ['public.archive'],
93
+ any: ['public.item'],
94
+ };
95
+
96
+ // ============================================
97
+ // DEFAULT CONFIGURATIONS
98
+ // ============================================
99
+
100
+ /**
101
+ * Default file picker configuration.
102
+ */
103
+ export const DEFAULT_FILE_PICKER_CONFIG: FilePickerConfig = {
104
+ allowedTypes: ['any'],
105
+ multiple: false,
106
+ allowCamera: true,
107
+ allowLibrary: true,
108
+ imageQuality: 80,
109
+ includeThumbnails: false,
110
+ };
111
+
112
+ /**
113
+ * Default upload configuration.
114
+ */
115
+ export const DEFAULT_UPLOAD_CONFIG: Omit<UploadConfig, 'url'> = {
116
+ method: 'POST',
117
+ fieldName: 'file',
118
+ multipart: true,
119
+ concurrency: 3,
120
+ timeout: 30000,
121
+ retryEnabled: true,
122
+ maxRetries: 3,
123
+ retryDelay: 'exponential',
124
+ retryDelayMs: 1000,
125
+ chunkedUpload: false,
126
+ chunkSize: 10 * 1024 * 1024, // 10MB
127
+ chunkedUploadThreshold: 50 * 1024 * 1024, // 50MB
128
+ backgroundUpload: false,
129
+ };
130
+
131
+ // ============================================
132
+ // INITIAL STATES
133
+ // ============================================
134
+
135
+ /**
136
+ * Initial file picker status.
137
+ */
138
+ export const INITIAL_FILE_PICKER_STATUS: FilePickerStatus = {
139
+ state: 'idle',
140
+ permission: 'undetermined',
141
+ };
142
+
143
+ /**
144
+ * Initial queue status.
145
+ */
146
+ export const INITIAL_QUEUE_STATUS: QueueStatus = {
147
+ total: 0,
148
+ pending: 0,
149
+ uploading: 0,
150
+ completed: 0,
151
+ failed: 0,
152
+ isProcessing: false,
153
+ isPaused: false,
154
+ overallProgress: 0,
155
+ totalBytesUploaded: 0,
156
+ totalBytes: 0,
157
+ };
158
+
159
+ // ============================================
160
+ // PRESETS
161
+ // ============================================
162
+
163
+ /**
164
+ * File picker presets for common use cases.
165
+ */
166
+ export const FILE_PICKER_PRESETS: FilePickerPresets = {
167
+ avatar: {
168
+ allowedTypes: ['image'],
169
+ multiple: false,
170
+ maxFileSize: 5 * 1024 * 1024, // 5MB
171
+ maxImageDimensions: { width: 1024, height: 1024 },
172
+ imageQuality: 85,
173
+ allowCamera: true,
174
+ includeThumbnails: true,
175
+ },
176
+
177
+ document: {
178
+ allowedTypes: ['document'],
179
+ multiple: false,
180
+ maxFileSize: 25 * 1024 * 1024, // 25MB
181
+ },
182
+
183
+ documents: {
184
+ allowedTypes: ['document'],
185
+ multiple: true,
186
+ maxFiles: 10,
187
+ maxFileSize: 25 * 1024 * 1024,
188
+ maxTotalSize: 100 * 1024 * 1024, // 100MB total
189
+ },
190
+
191
+ image: {
192
+ allowedTypes: ['image'],
193
+ multiple: false,
194
+ maxFileSize: 10 * 1024 * 1024, // 10MB
195
+ imageQuality: 85,
196
+ includeThumbnails: true,
197
+ },
198
+
199
+ images: {
200
+ allowedTypes: ['image'],
201
+ multiple: true,
202
+ maxFiles: 20,
203
+ maxFileSize: 10 * 1024 * 1024,
204
+ maxTotalSize: 50 * 1024 * 1024,
205
+ imageQuality: 80,
206
+ includeThumbnails: true,
207
+ },
208
+
209
+ video: {
210
+ allowedTypes: ['video'],
211
+ multiple: false,
212
+ maxFileSize: 100 * 1024 * 1024, // 100MB
213
+ includeThumbnails: true,
214
+ },
215
+
216
+ files: {
217
+ allowedTypes: ['any'],
218
+ multiple: true,
219
+ maxFiles: 50,
220
+ maxFileSize: 50 * 1024 * 1024,
221
+ },
222
+ };
223
+
224
+ /**
225
+ * Upload presets for common use cases.
226
+ */
227
+ export const UPLOAD_PRESETS: UploadPresets = {
228
+ simple: {
229
+ concurrency: 1,
230
+ retryEnabled: false,
231
+ chunkedUpload: false,
232
+ },
233
+
234
+ largeFile: {
235
+ chunkedUpload: true,
236
+ chunkSize: 10 * 1024 * 1024, // 10MB chunks
237
+ timeout: 120000, // 2 minutes per chunk
238
+ retryEnabled: true,
239
+ maxRetries: 5,
240
+ },
241
+
242
+ background: {
243
+ backgroundUpload: true,
244
+ retryEnabled: true,
245
+ maxRetries: 5,
246
+ retryDelay: 'exponential',
247
+ },
248
+
249
+ reliable: {
250
+ retryEnabled: true,
251
+ maxRetries: 5,
252
+ retryDelay: 'exponential',
253
+ retryDelayMs: 2000,
254
+ timeout: 60000,
255
+ },
256
+ };
257
+
258
+ // ============================================
259
+ // SIZE LIMITS
260
+ // ============================================
261
+
262
+ /**
263
+ * Size limit constants.
264
+ */
265
+ export const SIZE_LIMITS = {
266
+ /** Default max file size: 100MB */
267
+ DEFAULT_MAX_FILE_SIZE: 100 * 1024 * 1024,
268
+
269
+ /** Large file threshold for chunked upload: 50MB */
270
+ LARGE_FILE_THRESHOLD: 50 * 1024 * 1024,
271
+
272
+ /** Default chunk size: 10MB */
273
+ DEFAULT_CHUNK_SIZE: 10 * 1024 * 1024,
274
+
275
+ /** Min chunk size: 1MB */
276
+ MIN_CHUNK_SIZE: 1 * 1024 * 1024,
277
+
278
+ /** Max chunk size: 100MB */
279
+ MAX_CHUNK_SIZE: 100 * 1024 * 1024,
280
+ } as const;
281
+
282
+ // ============================================
283
+ // TIMING
284
+ // ============================================
285
+
286
+ /**
287
+ * Timing constants.
288
+ */
289
+ export const TIMING = {
290
+ /** Progress update interval in ms */
291
+ PROGRESS_UPDATE_INTERVAL: 100,
292
+
293
+ /** Speed calculation window in ms */
294
+ SPEED_CALCULATION_WINDOW: 2000,
295
+
296
+ /** Default request timeout */
297
+ DEFAULT_TIMEOUT: 30000,
298
+
299
+ /** Base retry delay */
300
+ BASE_RETRY_DELAY: 1000,
301
+
302
+ /** Max retry delay */
303
+ MAX_RETRY_DELAY: 30000,
304
+ } as const;
305
+
306
+ // ============================================
307
+ // ERROR MESSAGES
308
+ // ============================================
309
+
310
+ /**
311
+ * Error messages for file picker errors.
312
+ */
313
+ export const FILE_PICKER_ERROR_MESSAGES: Record<string, string> = {
314
+ PERMISSION_DENIED: 'Permission to access files was denied',
315
+ PERMISSION_BLOCKED: 'Permission to access files is blocked. Please enable it in settings.',
316
+ CANCELLED: 'File selection was cancelled',
317
+ INVALID_TYPE: 'Selected file type is not allowed',
318
+ SIZE_EXCEEDED: 'File size exceeds the maximum limit',
319
+ COUNT_EXCEEDED: 'Maximum number of files exceeded',
320
+ NOT_SUPPORTED: 'File picker is not supported on this platform',
321
+ UNKNOWN: 'An unknown error occurred',
322
+ };
323
+
324
+ /**
325
+ * Error messages for upload errors.
326
+ */
327
+ export const UPLOAD_ERROR_MESSAGES: Record<string, string> = {
328
+ NETWORK_ERROR: 'Network error occurred during upload',
329
+ TIMEOUT: 'Upload timed out',
330
+ SERVER_ERROR: 'Server returned an error',
331
+ ABORTED: 'Upload was cancelled',
332
+ INVALID_RESPONSE: 'Server returned an invalid response',
333
+ FILE_NOT_FOUND: 'File not found',
334
+ CHUNK_FAILED: 'Failed to upload file chunk',
335
+ UNKNOWN: 'An unknown error occurred during upload',
336
+ };
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Files Package Examples
3
+ *
4
+ * This file demonstrates common usage patterns for @idealyst/files.
5
+ */
6
+
7
+ export const basicUploadExample = `
8
+ import { useFilePicker, useFileUpload, FilePickerButton, UploadProgress } from '@idealyst/files';
9
+ import { View } from 'react-native';
10
+
11
+ function BasicUploadExample() {
12
+ const { addFiles, uploads, isUploading } = useFileUpload({
13
+ autoStart: true,
14
+ });
15
+
16
+ const handlePick = (result) => {
17
+ if (!result.cancelled) {
18
+ addFiles(result.files, {
19
+ url: 'https://api.example.com/upload',
20
+ fieldName: 'file',
21
+ });
22
+ }
23
+ };
24
+
25
+ return (
26
+ <View>
27
+ <FilePickerButton
28
+ pickerConfig={{ allowedTypes: ['image'], multiple: true }}
29
+ onPick={handlePick}
30
+ >
31
+ Select Images
32
+ </FilePickerButton>
33
+
34
+ {uploads.map(upload => (
35
+ <UploadProgress
36
+ key={upload.id}
37
+ upload={upload}
38
+ showSpeed
39
+ showETA
40
+ />
41
+ ))}
42
+ </View>
43
+ );
44
+ }
45
+ `;
46
+
47
+ export const dropZoneExample = `
48
+ import { DropZone, useFileUpload } from '@idealyst/files';
49
+ import { View, Text } from 'react-native';
50
+
51
+ function DropZoneExample() {
52
+ const { addFiles, uploads } = useFileUpload({ autoStart: true });
53
+
54
+ const handleDrop = (files) => {
55
+ addFiles(files, {
56
+ url: 'https://api.example.com/upload',
57
+ });
58
+ };
59
+
60
+ return (
61
+ <View>
62
+ <DropZone
63
+ config={{ allowedTypes: ['document'], multiple: true, maxFiles: 10 }}
64
+ onDrop={handleDrop}
65
+ onReject={(rejected) => console.log('Rejected:', rejected)}
66
+ >
67
+ {(state) => (
68
+ <View>
69
+ <Text>
70
+ {state.isDragActive ? 'Drop files here!' : 'Drag & drop files'}
71
+ </Text>
72
+ <Text>Supports PDF, Word, Excel</Text>
73
+ </View>
74
+ )}
75
+ </DropZone>
76
+ </View>
77
+ );
78
+ }
79
+ `;
80
+
81
+ export const chunkedUploadExample = `
82
+ import { useFilePicker, useFileUpload, UPLOAD_PRESETS } from '@idealyst/files';
83
+ import { View, Button } from 'react-native';
84
+
85
+ function LargeFileUploadExample() {
86
+ const { pick } = useFilePicker({
87
+ config: { allowedTypes: ['video'], maxFileSize: 500 * 1024 * 1024 }, // 500MB
88
+ });
89
+
90
+ const { addFiles, uploads, queueStatus } = useFileUpload();
91
+
92
+ const handleUpload = async () => {
93
+ const result = await pick();
94
+ if (!result.cancelled) {
95
+ addFiles(result.files, {
96
+ url: 'https://api.example.com/upload/chunked',
97
+ ...UPLOAD_PRESETS.largeFile, // Enables chunked upload
98
+ });
99
+ }
100
+ };
101
+
102
+ return (
103
+ <View>
104
+ <Button title="Upload Large Video" onPress={handleUpload} />
105
+ <Text>Progress: {queueStatus.overallProgress.toFixed(1)}%</Text>
106
+ {uploads.map(upload => (
107
+ <Text key={upload.id}>
108
+ {upload.file.name}: Chunk {upload.currentChunk}/{upload.totalChunks}
109
+ </Text>
110
+ ))}
111
+ </View>
112
+ );
113
+ }
114
+ `;
115
+
116
+ export const backgroundUploadExample = `
117
+ import { useFilePicker, useFileUpload, UPLOAD_PRESETS } from '@idealyst/files';
118
+ import { View, Button, Text } from 'react-native';
119
+
120
+ function BackgroundUploadExample() {
121
+ const { pick } = useFilePicker();
122
+ const { addFiles, queueStatus, uploads } = useFileUpload();
123
+
124
+ const handleUpload = async () => {
125
+ const result = await pick({ allowedTypes: ['any'], multiple: true });
126
+ if (!result.cancelled) {
127
+ addFiles(result.files, {
128
+ url: 'https://api.example.com/upload',
129
+ ...UPLOAD_PRESETS.background, // Enables background upload on native
130
+ });
131
+ }
132
+ };
133
+
134
+ return (
135
+ <View>
136
+ <Button title="Upload Files" onPress={handleUpload} />
137
+ <Text>
138
+ Uploading {queueStatus.uploading} of {queueStatus.total} files
139
+ </Text>
140
+ <Text>
141
+ Uploads will continue even if you switch apps
142
+ </Text>
143
+ </View>
144
+ );
145
+ }
146
+ `;
147
+
148
+ export const customValidationExample = `
149
+ import { useFilePicker, FILE_PICKER_PRESETS } from '@idealyst/files';
150
+ import { View, Button, Text } from 'react-native';
151
+
152
+ function AvatarUploadExample() {
153
+ const { pick, files, error } = useFilePicker({
154
+ config: {
155
+ ...FILE_PICKER_PRESETS.avatar,
156
+ maxFileSize: 2 * 1024 * 1024, // 2MB max
157
+ maxImageDimensions: { width: 512, height: 512 },
158
+ },
159
+ });
160
+
161
+ const handleSelect = async () => {
162
+ const result = await pick();
163
+ if (result.rejected.length > 0) {
164
+ // Handle validation errors
165
+ result.rejected.forEach(r => {
166
+ console.log(\`\${r.name}: \${r.message}\`);
167
+ });
168
+ }
169
+ };
170
+
171
+ return (
172
+ <View>
173
+ <Button title="Select Avatar" onPress={handleSelect} />
174
+ {files[0] && (
175
+ <Image source={{ uri: files[0].uri }} style={{ width: 100, height: 100 }} />
176
+ )}
177
+ {error && <Text style={{ color: 'red' }}>{error.message}</Text>}
178
+ </View>
179
+ );
180
+ }
181
+ `;
@@ -0,0 +1,169 @@
1
+ import { useState, useCallback, useEffect, useRef } from 'react';
2
+ import type {
3
+ UseFilePickerOptions,
4
+ UseFilePickerResult,
5
+ FilePickerStatus,
6
+ FilePickerError,
7
+ PickedFile,
8
+ FilePickerResult,
9
+ FilePickerConfig,
10
+ PermissionResult,
11
+ ValidationResult,
12
+ IFilePicker,
13
+ CreateFilePickerFactory,
14
+ CameraOptions,
15
+ } from '../types';
16
+ import { INITIAL_FILE_PICKER_STATUS } from '../constants';
17
+ import { checkPermissions, requestPermissions } from '../permissions';
18
+
19
+ /**
20
+ * Create a useFilePicker hook with the given file picker factory.
21
+ */
22
+ export function createUseFilePickerHook(createFilePicker: CreateFilePickerFactory) {
23
+ return function useFilePicker(options: UseFilePickerOptions = {}): UseFilePickerResult {
24
+ const { config = {}, autoRequestPermission = false } = options;
25
+
26
+ const pickerRef = useRef<IFilePicker | null>(null);
27
+ const configRef = useRef<Partial<FilePickerConfig>>(config);
28
+
29
+ const [status, setStatus] = useState<FilePickerStatus>(INITIAL_FILE_PICKER_STATUS);
30
+ const [permission, setPermission] = useState<PermissionResult | null>(null);
31
+ const [error, setError] = useState<FilePickerError | null>(null);
32
+ const [files, setFiles] = useState<PickedFile[]>([]);
33
+
34
+ // Update config ref when it changes
35
+ useEffect(() => {
36
+ configRef.current = config;
37
+ }, [config]);
38
+
39
+ // Initialize picker
40
+ useEffect(() => {
41
+ const picker = createFilePicker();
42
+ pickerRef.current = picker;
43
+
44
+ // Subscribe to state changes
45
+ const unsubscribe = picker.onStateChange((newStatus) => {
46
+ setStatus(newStatus);
47
+ if (newStatus.error) {
48
+ setError(newStatus.error);
49
+ }
50
+ });
51
+
52
+ // Check or request permissions
53
+ const initPermissions = async () => {
54
+ if (autoRequestPermission) {
55
+ const result = await requestPermissions();
56
+ setPermission(result);
57
+ } else {
58
+ const result = await checkPermissions();
59
+ setPermission(result);
60
+ }
61
+ };
62
+
63
+ initPermissions();
64
+
65
+ return () => {
66
+ unsubscribe();
67
+ picker.dispose();
68
+ pickerRef.current = null;
69
+ };
70
+ }, [autoRequestPermission]);
71
+
72
+ /**
73
+ * Open file picker.
74
+ */
75
+ const pick = useCallback(async (overrideConfig?: Partial<FilePickerConfig>): Promise<FilePickerResult> => {
76
+ if (!pickerRef.current) {
77
+ return { cancelled: true, files: [], rejected: [] };
78
+ }
79
+
80
+ setError(null);
81
+ const finalConfig = { ...configRef.current, ...overrideConfig };
82
+ const result = await pickerRef.current.pick(finalConfig);
83
+
84
+ if (!result.cancelled && result.files.length > 0) {
85
+ setFiles(result.files);
86
+ }
87
+
88
+ if (result.error) {
89
+ setError(result.error);
90
+ }
91
+
92
+ return result;
93
+ }, []);
94
+
95
+ /**
96
+ * Open camera to capture.
97
+ */
98
+ const captureFromCamera = useCallback(async (captureOptions?: CameraOptions): Promise<FilePickerResult> => {
99
+ if (!pickerRef.current) {
100
+ return { cancelled: true, files: [], rejected: [] };
101
+ }
102
+
103
+ setError(null);
104
+ const result = await pickerRef.current.captureFromCamera(captureOptions);
105
+
106
+ if (!result.cancelled && result.files.length > 0) {
107
+ setFiles(result.files);
108
+ }
109
+
110
+ if (result.error) {
111
+ setError(result.error);
112
+ }
113
+
114
+ return result;
115
+ }, []);
116
+
117
+ /**
118
+ * Clear picked files.
119
+ */
120
+ const clear = useCallback(() => {
121
+ setFiles([]);
122
+ setError(null);
123
+ }, []);
124
+
125
+ /**
126
+ * Check permissions.
127
+ */
128
+ const checkPermissionStatus = useCallback(async (): Promise<PermissionResult> => {
129
+ const result = await checkPermissions();
130
+ setPermission(result);
131
+ return result;
132
+ }, []);
133
+
134
+ /**
135
+ * Request permissions.
136
+ */
137
+ const requestPermissionStatus = useCallback(async (): Promise<PermissionResult> => {
138
+ const result = await requestPermissions();
139
+ setPermission(result);
140
+ return result;
141
+ }, []);
142
+
143
+ /**
144
+ * Validate files against current config.
145
+ */
146
+ const validateFiles = useCallback((filesToValidate: File[] | PickedFile[]): ValidationResult => {
147
+ if (!pickerRef.current) {
148
+ return { accepted: [], rejected: [], isValid: false };
149
+ }
150
+
151
+ return pickerRef.current.validateFiles(filesToValidate, configRef.current);
152
+ }, []);
153
+
154
+ return {
155
+ status,
156
+ isPicking: status.state === 'picking' || status.state === 'processing',
157
+ permission,
158
+ error,
159
+ files,
160
+ pick,
161
+ captureFromCamera,
162
+ clear,
163
+ checkPermission: checkPermissionStatus,
164
+ requestPermission: requestPermissionStatus,
165
+ validateFiles,
166
+ pickerRef,
167
+ };
168
+ };
169
+ }