@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.
- package/package.json +94 -0
- package/src/components/DropZone.native.tsx +96 -0
- package/src/components/DropZone.styles.tsx +99 -0
- package/src/components/DropZone.web.tsx +178 -0
- package/src/components/FilePickerButton.native.tsx +82 -0
- package/src/components/FilePickerButton.styles.tsx +112 -0
- package/src/components/FilePickerButton.web.tsx +84 -0
- package/src/components/UploadProgress.native.tsx +203 -0
- package/src/components/UploadProgress.styles.tsx +90 -0
- package/src/components/UploadProgress.web.tsx +201 -0
- package/src/components/index.native.ts +8 -0
- package/src/components/index.ts +6 -0
- package/src/components/index.web.ts +8 -0
- package/src/constants.ts +336 -0
- package/src/examples/index.ts +181 -0
- package/src/hooks/createUseFilePickerHook.ts +169 -0
- package/src/hooks/createUseFileUploadHook.ts +173 -0
- package/src/hooks/index.native.ts +12 -0
- package/src/hooks/index.ts +12 -0
- package/src/hooks/index.web.ts +12 -0
- package/src/index.native.ts +142 -0
- package/src/index.ts +139 -0
- package/src/index.web.ts +142 -0
- package/src/permissions/index.native.ts +8 -0
- package/src/permissions/index.ts +8 -0
- package/src/permissions/index.web.ts +8 -0
- package/src/permissions/permissions.native.ts +177 -0
- package/src/permissions/permissions.web.ts +96 -0
- package/src/picker/FilePicker.native.ts +407 -0
- package/src/picker/FilePicker.web.ts +366 -0
- package/src/picker/index.native.ts +2 -0
- package/src/picker/index.ts +2 -0
- package/src/picker/index.web.ts +2 -0
- package/src/types.ts +990 -0
- package/src/uploader/ChunkedUploader.ts +312 -0
- package/src/uploader/FileUploader.native.ts +435 -0
- package/src/uploader/FileUploader.web.ts +350 -0
- package/src/uploader/UploadQueue.ts +519 -0
- package/src/uploader/index.native.ts +4 -0
- package/src/uploader/index.ts +4 -0
- package/src/uploader/index.web.ts +4 -0
- package/src/utils.ts +586 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { ViewStyle, StyleProp } from 'react-native';
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// FILE TYPES
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supported file type categories for filtering.
|
|
10
|
+
*/
|
|
11
|
+
export type FileType =
|
|
12
|
+
| 'image'
|
|
13
|
+
| 'video'
|
|
14
|
+
| 'audio'
|
|
15
|
+
| 'document'
|
|
16
|
+
| 'archive'
|
|
17
|
+
| 'any';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Represents a picked file.
|
|
21
|
+
*/
|
|
22
|
+
export interface PickedFile {
|
|
23
|
+
/** Unique identifier for this file */
|
|
24
|
+
id: string;
|
|
25
|
+
|
|
26
|
+
/** Original file name */
|
|
27
|
+
name: string;
|
|
28
|
+
|
|
29
|
+
/** File size in bytes */
|
|
30
|
+
size: number;
|
|
31
|
+
|
|
32
|
+
/** MIME type */
|
|
33
|
+
type: string;
|
|
34
|
+
|
|
35
|
+
/** File URI (file:// on native, blob: or data: on web) */
|
|
36
|
+
uri: string;
|
|
37
|
+
|
|
38
|
+
/** File extension (without dot) */
|
|
39
|
+
extension: string;
|
|
40
|
+
|
|
41
|
+
/** Last modified timestamp (if available) */
|
|
42
|
+
lastModified?: number;
|
|
43
|
+
|
|
44
|
+
/** Image/video dimensions (if applicable) */
|
|
45
|
+
dimensions?: { width: number; height: number };
|
|
46
|
+
|
|
47
|
+
/** Duration in ms for audio/video (if applicable) */
|
|
48
|
+
duration?: number;
|
|
49
|
+
|
|
50
|
+
/** Thumbnail URI for images/videos (native only) */
|
|
51
|
+
thumbnailUri?: string;
|
|
52
|
+
|
|
53
|
+
/** Get file as ArrayBuffer */
|
|
54
|
+
getArrayBuffer(): Promise<ArrayBufferLike>;
|
|
55
|
+
|
|
56
|
+
/** Get file as Blob (web) or base64 (native) */
|
|
57
|
+
getData(): Promise<Blob | string>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================
|
|
61
|
+
// FILE PICKER CONFIGURATION
|
|
62
|
+
// ============================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* File picker configuration.
|
|
66
|
+
*/
|
|
67
|
+
export interface FilePickerConfig {
|
|
68
|
+
/** Allowed file types. Default: ['any'] */
|
|
69
|
+
allowedTypes: FileType[];
|
|
70
|
+
|
|
71
|
+
/** Custom MIME types to accept (overrides allowedTypes) */
|
|
72
|
+
customMimeTypes?: string[];
|
|
73
|
+
|
|
74
|
+
/** Custom file extensions to accept (e.g., ['.json', '.csv']) */
|
|
75
|
+
customExtensions?: string[];
|
|
76
|
+
|
|
77
|
+
/** Allow multiple file selection. Default: false */
|
|
78
|
+
multiple: boolean;
|
|
79
|
+
|
|
80
|
+
/** Maximum number of files (when multiple=true). Default: unlimited */
|
|
81
|
+
maxFiles?: number;
|
|
82
|
+
|
|
83
|
+
/** Maximum file size in bytes. Default: unlimited */
|
|
84
|
+
maxFileSize?: number;
|
|
85
|
+
|
|
86
|
+
/** Maximum total size for all files. Default: unlimited */
|
|
87
|
+
maxTotalSize?: number;
|
|
88
|
+
|
|
89
|
+
/** For images: allow camera capture (native). Default: true */
|
|
90
|
+
allowCamera?: boolean;
|
|
91
|
+
|
|
92
|
+
/** For images: include from photo library (native). Default: true */
|
|
93
|
+
allowLibrary?: boolean;
|
|
94
|
+
|
|
95
|
+
/** Image quality for camera captures (0-100). Default: 80 */
|
|
96
|
+
imageQuality?: number;
|
|
97
|
+
|
|
98
|
+
/** Max image dimensions (will resize if larger) */
|
|
99
|
+
maxImageDimensions?: { width: number; height: number };
|
|
100
|
+
|
|
101
|
+
/** Include file thumbnails (for images/videos). Default: false */
|
|
102
|
+
includeThumbnails?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reason why a file was rejected.
|
|
107
|
+
*/
|
|
108
|
+
export type RejectionReason = 'size' | 'type' | 'count' | 'total_size' | 'dimensions';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Information about a rejected file.
|
|
112
|
+
*/
|
|
113
|
+
export interface RejectedFile {
|
|
114
|
+
name: string;
|
|
115
|
+
reason: RejectionReason;
|
|
116
|
+
message: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Result of file picking operation.
|
|
121
|
+
*/
|
|
122
|
+
export interface FilePickerResult {
|
|
123
|
+
/** Whether user cancelled the picker */
|
|
124
|
+
cancelled: boolean;
|
|
125
|
+
|
|
126
|
+
/** Picked files (empty if cancelled) */
|
|
127
|
+
files: PickedFile[];
|
|
128
|
+
|
|
129
|
+
/** Any files that were rejected (size limit, type, etc.) */
|
|
130
|
+
rejected: RejectedFile[];
|
|
131
|
+
|
|
132
|
+
/** Error if picker failed */
|
|
133
|
+
error?: FilePickerError;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Result of file validation.
|
|
138
|
+
*/
|
|
139
|
+
export interface ValidationResult {
|
|
140
|
+
/** Valid files */
|
|
141
|
+
accepted: PickedFile[];
|
|
142
|
+
|
|
143
|
+
/** Invalid files with reasons */
|
|
144
|
+
rejected: RejectedFile[];
|
|
145
|
+
|
|
146
|
+
/** Whether all files passed validation */
|
|
147
|
+
isValid: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================
|
|
151
|
+
// CAMERA OPTIONS (for native capture)
|
|
152
|
+
// ============================================
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Media type for camera capture.
|
|
156
|
+
*/
|
|
157
|
+
export type CameraMediaType = 'photo' | 'video' | 'mixed';
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Options for camera capture.
|
|
161
|
+
*/
|
|
162
|
+
export interface CameraOptions {
|
|
163
|
+
/** Media type to capture. Default: 'photo' */
|
|
164
|
+
mediaType?: CameraMediaType;
|
|
165
|
+
|
|
166
|
+
/** JPEG quality (0-100). Default: 80 */
|
|
167
|
+
quality?: number;
|
|
168
|
+
|
|
169
|
+
/** Maximum video duration in seconds */
|
|
170
|
+
maxDuration?: number;
|
|
171
|
+
|
|
172
|
+
/** Save captured media to photo library. Default: true */
|
|
173
|
+
saveToLibrary?: boolean;
|
|
174
|
+
|
|
175
|
+
/** Use front camera. Default: false */
|
|
176
|
+
useFrontCamera?: boolean;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ============================================
|
|
180
|
+
// FILE PICKER STATE
|
|
181
|
+
// ============================================
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* File picker state machine states.
|
|
185
|
+
*/
|
|
186
|
+
export type FilePickerState = 'idle' | 'picking' | 'processing' | 'error';
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Comprehensive file picker status.
|
|
190
|
+
*/
|
|
191
|
+
export interface FilePickerStatus {
|
|
192
|
+
/** Current state */
|
|
193
|
+
state: FilePickerState;
|
|
194
|
+
|
|
195
|
+
/** Current permission status */
|
|
196
|
+
permission: PermissionStatus;
|
|
197
|
+
|
|
198
|
+
/** Error if state is 'error' */
|
|
199
|
+
error?: FilePickerError;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================
|
|
203
|
+
// FILE PICKER INTERFACE
|
|
204
|
+
// ============================================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Core file picker interface implemented by platform-specific classes.
|
|
208
|
+
*/
|
|
209
|
+
export interface IFilePicker {
|
|
210
|
+
/** Current status */
|
|
211
|
+
readonly status: FilePickerStatus;
|
|
212
|
+
|
|
213
|
+
/** Check permission status (native only - photos/media access) */
|
|
214
|
+
checkPermission(): Promise<PermissionStatus>;
|
|
215
|
+
|
|
216
|
+
/** Request permission (native only) */
|
|
217
|
+
requestPermission(): Promise<PermissionStatus>;
|
|
218
|
+
|
|
219
|
+
/** Open file picker dialog */
|
|
220
|
+
pick(config?: Partial<FilePickerConfig>): Promise<FilePickerResult>;
|
|
221
|
+
|
|
222
|
+
/** Open camera to capture image/video (native) */
|
|
223
|
+
captureFromCamera(options?: CameraOptions): Promise<FilePickerResult>;
|
|
224
|
+
|
|
225
|
+
/** Validate files against config without picking */
|
|
226
|
+
validateFiles(files: File[] | PickedFile[], config?: Partial<FilePickerConfig>): ValidationResult;
|
|
227
|
+
|
|
228
|
+
/** Subscribe to state changes */
|
|
229
|
+
onStateChange(callback: (status: FilePickerStatus) => void): () => void;
|
|
230
|
+
|
|
231
|
+
/** Clean up resources */
|
|
232
|
+
dispose(): void;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ============================================
|
|
236
|
+
// UPLOAD CONFIGURATION
|
|
237
|
+
// ============================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* HTTP method for uploads.
|
|
241
|
+
*/
|
|
242
|
+
export type UploadMethod = 'POST' | 'PUT' | 'PATCH';
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Retry delay strategy.
|
|
246
|
+
*/
|
|
247
|
+
export type RetryDelayStrategy = 'fixed' | 'exponential';
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Upload configuration.
|
|
251
|
+
*/
|
|
252
|
+
export interface UploadConfig {
|
|
253
|
+
/** Target URL for upload */
|
|
254
|
+
url: string;
|
|
255
|
+
|
|
256
|
+
/** HTTP method. Default: 'POST' */
|
|
257
|
+
method: UploadMethod;
|
|
258
|
+
|
|
259
|
+
/** Custom headers */
|
|
260
|
+
headers?: Record<string, string>;
|
|
261
|
+
|
|
262
|
+
/** Form field name for file. Default: 'file' */
|
|
263
|
+
fieldName: string;
|
|
264
|
+
|
|
265
|
+
/** Additional form data to send */
|
|
266
|
+
formData?: Record<string, string | number | boolean>;
|
|
267
|
+
|
|
268
|
+
/** Use multipart form data. Default: true */
|
|
269
|
+
multipart: boolean;
|
|
270
|
+
|
|
271
|
+
/** Number of concurrent uploads. Default: 3 */
|
|
272
|
+
concurrency: number;
|
|
273
|
+
|
|
274
|
+
/** Request timeout in ms. Default: 30000 */
|
|
275
|
+
timeout: number;
|
|
276
|
+
|
|
277
|
+
/** Enable retry on failure. Default: true */
|
|
278
|
+
retryEnabled: boolean;
|
|
279
|
+
|
|
280
|
+
/** Maximum retry attempts. Default: 3 */
|
|
281
|
+
maxRetries: number;
|
|
282
|
+
|
|
283
|
+
/** Retry delay strategy */
|
|
284
|
+
retryDelay: RetryDelayStrategy;
|
|
285
|
+
|
|
286
|
+
/** Base retry delay in ms. Default: 1000 */
|
|
287
|
+
retryDelayMs: number;
|
|
288
|
+
|
|
289
|
+
/** Enable chunked upload for large files. Default: false */
|
|
290
|
+
chunkedUpload: boolean;
|
|
291
|
+
|
|
292
|
+
/** Chunk size in bytes. Default: 10MB */
|
|
293
|
+
chunkSize: number;
|
|
294
|
+
|
|
295
|
+
/** File size threshold for auto-enabling chunked upload. Default: 50MB */
|
|
296
|
+
chunkedUploadThreshold: number;
|
|
297
|
+
|
|
298
|
+
/** Enable background upload (native only). Default: false */
|
|
299
|
+
backgroundUpload: boolean;
|
|
300
|
+
|
|
301
|
+
/** Custom request transformer */
|
|
302
|
+
transformRequest?: (file: PickedFile, formData: FormData) => FormData | Promise<FormData>;
|
|
303
|
+
|
|
304
|
+
/** Custom response parser */
|
|
305
|
+
parseResponse?: <T = unknown>(response: Response) => Promise<T>;
|
|
306
|
+
|
|
307
|
+
/** Abort signal for cancellation */
|
|
308
|
+
signal?: AbortSignal;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ============================================
|
|
312
|
+
// UPLOAD STATE
|
|
313
|
+
// ============================================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Upload state machine states.
|
|
317
|
+
*/
|
|
318
|
+
export type UploadState =
|
|
319
|
+
| 'pending'
|
|
320
|
+
| 'uploading'
|
|
321
|
+
| 'paused'
|
|
322
|
+
| 'completed'
|
|
323
|
+
| 'failed'
|
|
324
|
+
| 'cancelled';
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Upload progress information.
|
|
328
|
+
*/
|
|
329
|
+
export interface UploadProgressInfo {
|
|
330
|
+
/** Upload ID */
|
|
331
|
+
id: string;
|
|
332
|
+
|
|
333
|
+
/** File being uploaded */
|
|
334
|
+
file: PickedFile;
|
|
335
|
+
|
|
336
|
+
/** Current state */
|
|
337
|
+
state: UploadState;
|
|
338
|
+
|
|
339
|
+
/** Bytes uploaded */
|
|
340
|
+
bytesUploaded: number;
|
|
341
|
+
|
|
342
|
+
/** Total bytes */
|
|
343
|
+
bytesTotal: number;
|
|
344
|
+
|
|
345
|
+
/** Progress percentage (0-100) */
|
|
346
|
+
percentage: number;
|
|
347
|
+
|
|
348
|
+
/** Upload speed in bytes/second */
|
|
349
|
+
speed: number;
|
|
350
|
+
|
|
351
|
+
/** Estimated time remaining in ms */
|
|
352
|
+
estimatedTimeRemaining: number;
|
|
353
|
+
|
|
354
|
+
/** Number of retry attempts made */
|
|
355
|
+
retryCount: number;
|
|
356
|
+
|
|
357
|
+
/** For chunked uploads: current chunk index */
|
|
358
|
+
currentChunk?: number;
|
|
359
|
+
|
|
360
|
+
/** For chunked uploads: total chunks */
|
|
361
|
+
totalChunks?: number;
|
|
362
|
+
|
|
363
|
+
/** Error if failed */
|
|
364
|
+
error?: UploadError;
|
|
365
|
+
|
|
366
|
+
/** Timestamp when upload started */
|
|
367
|
+
startedAt?: number;
|
|
368
|
+
|
|
369
|
+
/** Timestamp when upload completed */
|
|
370
|
+
completedAt?: number;
|
|
371
|
+
|
|
372
|
+
/** Upload configuration used */
|
|
373
|
+
config: UploadConfig;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Result of a completed upload.
|
|
378
|
+
*/
|
|
379
|
+
export interface UploadResult {
|
|
380
|
+
/** Upload ID */
|
|
381
|
+
id: string;
|
|
382
|
+
|
|
383
|
+
/** Whether upload was successful */
|
|
384
|
+
success: boolean;
|
|
385
|
+
|
|
386
|
+
/** Server response (if successful) */
|
|
387
|
+
response?: unknown;
|
|
388
|
+
|
|
389
|
+
/** HTTP status code */
|
|
390
|
+
statusCode?: number;
|
|
391
|
+
|
|
392
|
+
/** Error (if failed) */
|
|
393
|
+
error?: UploadError;
|
|
394
|
+
|
|
395
|
+
/** Final progress state */
|
|
396
|
+
progress: UploadProgressInfo;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ============================================
|
|
400
|
+
// UPLOAD QUEUE
|
|
401
|
+
// ============================================
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Queue status information.
|
|
405
|
+
*/
|
|
406
|
+
export interface QueueStatus {
|
|
407
|
+
/** Total files in queue */
|
|
408
|
+
total: number;
|
|
409
|
+
|
|
410
|
+
/** Pending uploads */
|
|
411
|
+
pending: number;
|
|
412
|
+
|
|
413
|
+
/** Currently uploading */
|
|
414
|
+
uploading: number;
|
|
415
|
+
|
|
416
|
+
/** Completed uploads */
|
|
417
|
+
completed: number;
|
|
418
|
+
|
|
419
|
+
/** Failed uploads */
|
|
420
|
+
failed: number;
|
|
421
|
+
|
|
422
|
+
/** Whether queue is processing */
|
|
423
|
+
isProcessing: boolean;
|
|
424
|
+
|
|
425
|
+
/** Whether queue is paused */
|
|
426
|
+
isPaused: boolean;
|
|
427
|
+
|
|
428
|
+
/** Overall progress percentage */
|
|
429
|
+
overallProgress: number;
|
|
430
|
+
|
|
431
|
+
/** Total bytes uploaded across all files */
|
|
432
|
+
totalBytesUploaded: number;
|
|
433
|
+
|
|
434
|
+
/** Total bytes to upload */
|
|
435
|
+
totalBytes: number;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ============================================
|
|
439
|
+
// FILE UPLOADER INTERFACE
|
|
440
|
+
// ============================================
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Core file uploader interface implemented by platform-specific classes.
|
|
444
|
+
*/
|
|
445
|
+
export interface IFileUploader {
|
|
446
|
+
/** Current queue status */
|
|
447
|
+
readonly queueStatus: QueueStatus;
|
|
448
|
+
|
|
449
|
+
/** All uploads */
|
|
450
|
+
readonly uploads: Map<string, UploadProgressInfo>;
|
|
451
|
+
|
|
452
|
+
/** Add file(s) to upload queue */
|
|
453
|
+
add(files: PickedFile | PickedFile[], config: UploadConfig): string[];
|
|
454
|
+
|
|
455
|
+
/** Start processing the queue */
|
|
456
|
+
start(): void;
|
|
457
|
+
|
|
458
|
+
/** Pause all uploads */
|
|
459
|
+
pause(): void;
|
|
460
|
+
|
|
461
|
+
/** Resume paused uploads */
|
|
462
|
+
resume(): void;
|
|
463
|
+
|
|
464
|
+
/** Cancel specific upload */
|
|
465
|
+
cancel(uploadId: string): void;
|
|
466
|
+
|
|
467
|
+
/** Cancel all uploads */
|
|
468
|
+
cancelAll(): void;
|
|
469
|
+
|
|
470
|
+
/** Retry failed upload */
|
|
471
|
+
retry(uploadId: string): void;
|
|
472
|
+
|
|
473
|
+
/** Retry all failed uploads */
|
|
474
|
+
retryAll(): void;
|
|
475
|
+
|
|
476
|
+
/** Remove completed/failed upload from queue */
|
|
477
|
+
remove(uploadId: string): void;
|
|
478
|
+
|
|
479
|
+
/** Clear completed uploads from queue */
|
|
480
|
+
clearCompleted(): void;
|
|
481
|
+
|
|
482
|
+
/** Get upload by ID */
|
|
483
|
+
getUpload(uploadId: string): UploadProgressInfo | undefined;
|
|
484
|
+
|
|
485
|
+
/** Subscribe to queue status changes */
|
|
486
|
+
onQueueChange(callback: (status: QueueStatus) => void): () => void;
|
|
487
|
+
|
|
488
|
+
/** Subscribe to individual upload progress */
|
|
489
|
+
onProgress(uploadId: string, callback: (progress: UploadProgressInfo) => void): () => void;
|
|
490
|
+
|
|
491
|
+
/** Subscribe to upload completion */
|
|
492
|
+
onComplete(callback: (result: UploadResult) => void): () => void;
|
|
493
|
+
|
|
494
|
+
/** Subscribe to upload errors */
|
|
495
|
+
onError(callback: (error: UploadError, uploadId: string) => void): () => void;
|
|
496
|
+
|
|
497
|
+
/** Clean up resources */
|
|
498
|
+
dispose(): void;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ============================================
|
|
502
|
+
// ERROR TYPES
|
|
503
|
+
// ============================================
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* File picker error codes.
|
|
507
|
+
*/
|
|
508
|
+
export type FilePickerErrorCode =
|
|
509
|
+
| 'PERMISSION_DENIED'
|
|
510
|
+
| 'PERMISSION_BLOCKED'
|
|
511
|
+
| 'CANCELLED'
|
|
512
|
+
| 'INVALID_TYPE'
|
|
513
|
+
| 'SIZE_EXCEEDED'
|
|
514
|
+
| 'COUNT_EXCEEDED'
|
|
515
|
+
| 'NOT_SUPPORTED'
|
|
516
|
+
| 'UNKNOWN';
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* File picker error object.
|
|
520
|
+
*/
|
|
521
|
+
export interface FilePickerError {
|
|
522
|
+
code: FilePickerErrorCode;
|
|
523
|
+
message: string;
|
|
524
|
+
originalError?: Error;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Upload error codes.
|
|
529
|
+
*/
|
|
530
|
+
export type UploadErrorCode =
|
|
531
|
+
| 'NETWORK_ERROR'
|
|
532
|
+
| 'TIMEOUT'
|
|
533
|
+
| 'SERVER_ERROR'
|
|
534
|
+
| 'ABORTED'
|
|
535
|
+
| 'INVALID_RESPONSE'
|
|
536
|
+
| 'FILE_NOT_FOUND'
|
|
537
|
+
| 'CHUNK_FAILED'
|
|
538
|
+
| 'UNKNOWN';
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Upload error object.
|
|
542
|
+
*/
|
|
543
|
+
export interface UploadError {
|
|
544
|
+
code: UploadErrorCode;
|
|
545
|
+
message: string;
|
|
546
|
+
statusCode?: number;
|
|
547
|
+
originalError?: Error;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ============================================
|
|
551
|
+
// PERMISSION TYPES
|
|
552
|
+
// ============================================
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Permission status values.
|
|
556
|
+
*/
|
|
557
|
+
export type PermissionStatus =
|
|
558
|
+
| 'granted'
|
|
559
|
+
| 'denied'
|
|
560
|
+
| 'undetermined'
|
|
561
|
+
| 'blocked'
|
|
562
|
+
| 'unavailable';
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Result of a permission check or request.
|
|
566
|
+
*/
|
|
567
|
+
export interface PermissionResult {
|
|
568
|
+
/** Photo library access (for image picking) */
|
|
569
|
+
photoLibrary: PermissionStatus;
|
|
570
|
+
|
|
571
|
+
/** Camera access (for capture) */
|
|
572
|
+
camera: PermissionStatus;
|
|
573
|
+
|
|
574
|
+
/** Whether permission can be requested again */
|
|
575
|
+
canAskAgain: boolean;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ============================================
|
|
579
|
+
// HOOK TYPES
|
|
580
|
+
// ============================================
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Options for the useFilePicker hook.
|
|
584
|
+
*/
|
|
585
|
+
export interface UseFilePickerOptions {
|
|
586
|
+
/** Default picker configuration */
|
|
587
|
+
config?: Partial<FilePickerConfig>;
|
|
588
|
+
|
|
589
|
+
/** Auto request permission on mount */
|
|
590
|
+
autoRequestPermission?: boolean;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Result returned by the useFilePicker hook.
|
|
595
|
+
*/
|
|
596
|
+
export interface UseFilePickerResult {
|
|
597
|
+
// State
|
|
598
|
+
/** Current status */
|
|
599
|
+
status: FilePickerStatus;
|
|
600
|
+
|
|
601
|
+
/** Whether picker is open */
|
|
602
|
+
isPicking: boolean;
|
|
603
|
+
|
|
604
|
+
/** Permission result */
|
|
605
|
+
permission: PermissionResult | null;
|
|
606
|
+
|
|
607
|
+
/** Current error (if any) */
|
|
608
|
+
error: FilePickerError | null;
|
|
609
|
+
|
|
610
|
+
/** Last picked files */
|
|
611
|
+
files: PickedFile[];
|
|
612
|
+
|
|
613
|
+
// Actions
|
|
614
|
+
/** Open file picker */
|
|
615
|
+
pick: (config?: Partial<FilePickerConfig>) => Promise<FilePickerResult>;
|
|
616
|
+
|
|
617
|
+
/** Open camera to capture */
|
|
618
|
+
captureFromCamera: (options?: CameraOptions) => Promise<FilePickerResult>;
|
|
619
|
+
|
|
620
|
+
/** Clear picked files */
|
|
621
|
+
clear: () => void;
|
|
622
|
+
|
|
623
|
+
// Permissions
|
|
624
|
+
/** Check permission status */
|
|
625
|
+
checkPermission: () => Promise<PermissionResult>;
|
|
626
|
+
|
|
627
|
+
/** Request permission */
|
|
628
|
+
requestPermission: () => Promise<PermissionResult>;
|
|
629
|
+
|
|
630
|
+
// Validation
|
|
631
|
+
/** Validate files against current config */
|
|
632
|
+
validateFiles: (files: File[] | PickedFile[]) => ValidationResult;
|
|
633
|
+
|
|
634
|
+
// Ref
|
|
635
|
+
/** File picker instance ref */
|
|
636
|
+
pickerRef: RefObject<IFilePicker | null>;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Options for the useFileUpload hook.
|
|
641
|
+
*/
|
|
642
|
+
export interface UseFileUploadOptions {
|
|
643
|
+
/** Default upload configuration (url is required when adding files) */
|
|
644
|
+
config?: Partial<Omit<UploadConfig, 'url'>>;
|
|
645
|
+
|
|
646
|
+
/** Auto-start uploads when files are added */
|
|
647
|
+
autoStart?: boolean;
|
|
648
|
+
|
|
649
|
+
/** Maximum concurrent uploads */
|
|
650
|
+
concurrency?: number;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Result returned by the useFileUpload hook.
|
|
655
|
+
*/
|
|
656
|
+
export interface UseFileUploadResult {
|
|
657
|
+
// Queue state
|
|
658
|
+
/** Current queue status */
|
|
659
|
+
queueStatus: QueueStatus;
|
|
660
|
+
|
|
661
|
+
/** All uploads as array */
|
|
662
|
+
uploads: UploadProgressInfo[];
|
|
663
|
+
|
|
664
|
+
// Derived state
|
|
665
|
+
/** Whether any upload is in progress */
|
|
666
|
+
isUploading: boolean;
|
|
667
|
+
|
|
668
|
+
/** Whether queue is paused */
|
|
669
|
+
isPaused: boolean;
|
|
670
|
+
|
|
671
|
+
/** Whether there are failed uploads */
|
|
672
|
+
hasFailedUploads: boolean;
|
|
673
|
+
|
|
674
|
+
// Actions
|
|
675
|
+
/** Add files to upload queue */
|
|
676
|
+
addFiles: (files: PickedFile | PickedFile[], config: UploadConfig) => string[];
|
|
677
|
+
|
|
678
|
+
/** Start processing queue */
|
|
679
|
+
start: () => void;
|
|
680
|
+
|
|
681
|
+
/** Pause all uploads */
|
|
682
|
+
pause: () => void;
|
|
683
|
+
|
|
684
|
+
/** Resume paused uploads */
|
|
685
|
+
resume: () => void;
|
|
686
|
+
|
|
687
|
+
/** Cancel specific upload */
|
|
688
|
+
cancel: (uploadId: string) => void;
|
|
689
|
+
|
|
690
|
+
/** Cancel all uploads */
|
|
691
|
+
cancelAll: () => void;
|
|
692
|
+
|
|
693
|
+
/** Retry failed upload */
|
|
694
|
+
retry: (uploadId: string) => void;
|
|
695
|
+
|
|
696
|
+
/** Retry all failed uploads */
|
|
697
|
+
retryAll: () => void;
|
|
698
|
+
|
|
699
|
+
/** Remove upload from queue */
|
|
700
|
+
remove: (uploadId: string) => void;
|
|
701
|
+
|
|
702
|
+
/** Clear completed uploads */
|
|
703
|
+
clearCompleted: () => void;
|
|
704
|
+
|
|
705
|
+
/** Get specific upload */
|
|
706
|
+
getUpload: (uploadId: string) => UploadProgressInfo | undefined;
|
|
707
|
+
|
|
708
|
+
// Ref
|
|
709
|
+
/** File uploader instance ref */
|
|
710
|
+
uploaderRef: RefObject<IFileUploader | null>;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ============================================
|
|
714
|
+
// COMPONENT PROPS
|
|
715
|
+
// ============================================
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Button variants.
|
|
719
|
+
*/
|
|
720
|
+
export type ButtonVariant = 'solid' | 'outline' | 'ghost';
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Size options.
|
|
724
|
+
*/
|
|
725
|
+
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Intent/color options.
|
|
729
|
+
*/
|
|
730
|
+
export type Intent = 'primary' | 'secondary' | 'success' | 'warning' | 'error';
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Props for the FilePickerButton component.
|
|
734
|
+
*/
|
|
735
|
+
export interface FilePickerButtonProps {
|
|
736
|
+
/** Button label */
|
|
737
|
+
children?: React.ReactNode;
|
|
738
|
+
|
|
739
|
+
/** Picker configuration */
|
|
740
|
+
pickerConfig?: Partial<FilePickerConfig>;
|
|
741
|
+
|
|
742
|
+
/** Called when files are picked */
|
|
743
|
+
onPick?: (result: FilePickerResult) => void;
|
|
744
|
+
|
|
745
|
+
/** Called on error */
|
|
746
|
+
onError?: (error: FilePickerError) => void;
|
|
747
|
+
|
|
748
|
+
/** Disabled state */
|
|
749
|
+
disabled?: boolean;
|
|
750
|
+
|
|
751
|
+
/** Loading state */
|
|
752
|
+
loading?: boolean;
|
|
753
|
+
|
|
754
|
+
/** Button variant */
|
|
755
|
+
variant?: ButtonVariant;
|
|
756
|
+
|
|
757
|
+
/** Button size */
|
|
758
|
+
size?: Size;
|
|
759
|
+
|
|
760
|
+
/** Intent/color */
|
|
761
|
+
intent?: Intent;
|
|
762
|
+
|
|
763
|
+
/** Left icon name (Material Design Icons) */
|
|
764
|
+
leftIcon?: string;
|
|
765
|
+
|
|
766
|
+
/** Custom styles */
|
|
767
|
+
style?: StyleProp<ViewStyle>;
|
|
768
|
+
|
|
769
|
+
/** Test ID */
|
|
770
|
+
testID?: string;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* DropZone state information.
|
|
775
|
+
*/
|
|
776
|
+
export interface DropZoneState {
|
|
777
|
+
/** Whether a file is being dragged over the zone */
|
|
778
|
+
isDragActive: boolean;
|
|
779
|
+
|
|
780
|
+
/** Whether the dragged file is invalid */
|
|
781
|
+
isDragReject: boolean;
|
|
782
|
+
|
|
783
|
+
/** Whether files are being processed */
|
|
784
|
+
isProcessing: boolean;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Props for the DropZone component.
|
|
789
|
+
*/
|
|
790
|
+
export interface DropZoneProps {
|
|
791
|
+
/** Called when files are dropped/selected */
|
|
792
|
+
onDrop?: (files: PickedFile[]) => void;
|
|
793
|
+
|
|
794
|
+
/** Called on validation errors */
|
|
795
|
+
onReject?: (rejected: RejectedFile[]) => void;
|
|
796
|
+
|
|
797
|
+
/** Picker configuration for validation */
|
|
798
|
+
config?: Partial<FilePickerConfig>;
|
|
799
|
+
|
|
800
|
+
/** Children to render inside dropzone */
|
|
801
|
+
children?: React.ReactNode | ((state: DropZoneState) => React.ReactNode);
|
|
802
|
+
|
|
803
|
+
/** Disabled state */
|
|
804
|
+
disabled?: boolean;
|
|
805
|
+
|
|
806
|
+
/** Custom styles */
|
|
807
|
+
style?: StyleProp<ViewStyle>;
|
|
808
|
+
|
|
809
|
+
/** Active/hover style */
|
|
810
|
+
activeStyle?: StyleProp<ViewStyle>;
|
|
811
|
+
|
|
812
|
+
/** Reject/invalid style */
|
|
813
|
+
rejectStyle?: StyleProp<ViewStyle>;
|
|
814
|
+
|
|
815
|
+
/** Test ID */
|
|
816
|
+
testID?: string;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Progress bar variants.
|
|
821
|
+
*/
|
|
822
|
+
export type ProgressVariant = 'linear' | 'circular';
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Props for the UploadProgress component.
|
|
826
|
+
*/
|
|
827
|
+
export interface UploadProgressProps {
|
|
828
|
+
/** Upload progress data */
|
|
829
|
+
upload: UploadProgressInfo;
|
|
830
|
+
|
|
831
|
+
/** Show file name */
|
|
832
|
+
showFileName?: boolean;
|
|
833
|
+
|
|
834
|
+
/** Show file size */
|
|
835
|
+
showFileSize?: boolean;
|
|
836
|
+
|
|
837
|
+
/** Show speed */
|
|
838
|
+
showSpeed?: boolean;
|
|
839
|
+
|
|
840
|
+
/** Show ETA */
|
|
841
|
+
showETA?: boolean;
|
|
842
|
+
|
|
843
|
+
/** Allow cancel */
|
|
844
|
+
showCancel?: boolean;
|
|
845
|
+
|
|
846
|
+
/** Allow retry (when failed) */
|
|
847
|
+
showRetry?: boolean;
|
|
848
|
+
|
|
849
|
+
/** Called when cancel is clicked */
|
|
850
|
+
onCancel?: () => void;
|
|
851
|
+
|
|
852
|
+
/** Called when retry is clicked */
|
|
853
|
+
onRetry?: () => void;
|
|
854
|
+
|
|
855
|
+
/** Progress bar variant */
|
|
856
|
+
variant?: ProgressVariant;
|
|
857
|
+
|
|
858
|
+
/** Size */
|
|
859
|
+
size?: Size;
|
|
860
|
+
|
|
861
|
+
/** Custom styles */
|
|
862
|
+
style?: StyleProp<ViewStyle>;
|
|
863
|
+
|
|
864
|
+
/** Test ID */
|
|
865
|
+
testID?: string;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// ============================================
|
|
869
|
+
// PRESETS
|
|
870
|
+
// ============================================
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* File picker presets for common use cases.
|
|
874
|
+
*/
|
|
875
|
+
export interface FilePickerPresets {
|
|
876
|
+
/** Avatar/profile image upload */
|
|
877
|
+
avatar: Partial<FilePickerConfig>;
|
|
878
|
+
|
|
879
|
+
/** Single document upload */
|
|
880
|
+
document: Partial<FilePickerConfig>;
|
|
881
|
+
|
|
882
|
+
/** Multiple documents */
|
|
883
|
+
documents: Partial<FilePickerConfig>;
|
|
884
|
+
|
|
885
|
+
/** Image upload with optimization */
|
|
886
|
+
image: Partial<FilePickerConfig>;
|
|
887
|
+
|
|
888
|
+
/** Multiple images */
|
|
889
|
+
images: Partial<FilePickerConfig>;
|
|
890
|
+
|
|
891
|
+
/** Video upload */
|
|
892
|
+
video: Partial<FilePickerConfig>;
|
|
893
|
+
|
|
894
|
+
/** Any file type */
|
|
895
|
+
files: Partial<FilePickerConfig>;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Upload presets for common use cases.
|
|
900
|
+
*/
|
|
901
|
+
export interface UploadPresets {
|
|
902
|
+
/** Simple single file upload */
|
|
903
|
+
simple: Partial<UploadConfig>;
|
|
904
|
+
|
|
905
|
+
/** Large file with chunking */
|
|
906
|
+
largeFile: Partial<UploadConfig>;
|
|
907
|
+
|
|
908
|
+
/** Background upload (native) */
|
|
909
|
+
background: Partial<UploadConfig>;
|
|
910
|
+
|
|
911
|
+
/** High reliability with retries */
|
|
912
|
+
reliable: Partial<UploadConfig>;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// ============================================
|
|
916
|
+
// FACTORY TYPES
|
|
917
|
+
// ============================================
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Factory function type for creating file picker instances.
|
|
921
|
+
*/
|
|
922
|
+
export type CreateFilePickerFactory = () => IFilePicker;
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Options for creating file uploader instances.
|
|
926
|
+
*/
|
|
927
|
+
export interface CreateFileUploaderOptions {
|
|
928
|
+
/** Maximum concurrent uploads */
|
|
929
|
+
concurrency?: number;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Factory function type for creating file uploader instances.
|
|
934
|
+
*/
|
|
935
|
+
export type CreateFileUploaderFactory = (options?: CreateFileUploaderOptions) => IFileUploader;
|
|
936
|
+
|
|
937
|
+
// ============================================
|
|
938
|
+
// CHUNKED UPLOAD TYPES
|
|
939
|
+
// ============================================
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Chunk progress information.
|
|
943
|
+
*/
|
|
944
|
+
export interface ChunkProgress {
|
|
945
|
+
/** Current chunk index (1-based) */
|
|
946
|
+
currentChunk: number;
|
|
947
|
+
|
|
948
|
+
/** Total number of chunks */
|
|
949
|
+
totalChunks: number;
|
|
950
|
+
|
|
951
|
+
/** Bytes uploaded so far */
|
|
952
|
+
bytesUploaded: number;
|
|
953
|
+
|
|
954
|
+
/** Total bytes to upload */
|
|
955
|
+
bytesTotal: number;
|
|
956
|
+
|
|
957
|
+
/** Progress percentage (0-100) */
|
|
958
|
+
percentage: number;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Chunk upload configuration.
|
|
963
|
+
*/
|
|
964
|
+
export interface ChunkUploadConfig extends UploadConfig {
|
|
965
|
+
/** Unique file identifier for chunk assembly */
|
|
966
|
+
fileId: string;
|
|
967
|
+
|
|
968
|
+
/** Endpoint for finalizing chunked upload */
|
|
969
|
+
finalizeUrl?: string;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Server response for chunk upload.
|
|
974
|
+
*/
|
|
975
|
+
export interface ChunkUploadResponse {
|
|
976
|
+
/** Whether chunk was accepted */
|
|
977
|
+
success: boolean;
|
|
978
|
+
|
|
979
|
+
/** Chunk index received */
|
|
980
|
+
chunkIndex: number;
|
|
981
|
+
|
|
982
|
+
/** Whether all chunks have been received */
|
|
983
|
+
complete?: boolean;
|
|
984
|
+
|
|
985
|
+
/** Final file URL (if complete) */
|
|
986
|
+
fileUrl?: string;
|
|
987
|
+
|
|
988
|
+
/** Error message */
|
|
989
|
+
error?: string;
|
|
990
|
+
}
|