@uploadista/react-native-core 0.0.20-beta.9 → 0.1.0-beta.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["initialState: UploadState","initialState","initialState: FlowUploadState","initialState","processedInputs: Record<string, unknown>","initialState: MultiUploadState","newItems: UploadItemState[]","uploadInput: Blob","result: FilePickResult","parts","getStatusColor","styles","styles","styles","styles","contextValue: FlowContextValue","renderProps: FlowRenderProps","contextValue: FlowInputContextValue","renderProps: FlowSubmitRenderProps","renderProps: FlowCancelRenderProps","renderProps: FlowResetRenderProps"],"sources":["../src/hooks/uploadista-context.ts","../src/types/platform-types.ts","../src/hooks/use-uploadista-context.ts","../src/hooks/use-upload.ts","../src/hooks/use-camera-upload.ts","../src/hooks/use-file-upload.ts","../src/contexts/flow-manager-context.tsx","../src/hooks/use-flow.ts","../src/hooks/use-multi-upload.ts","../src/hooks/use-gallery-upload.ts","../src/hooks/use-upload-metrics.ts","../src/utils/fileHelpers.ts","../src/utils/permissions.ts","../src/utils/uriHelpers.ts","../src/components/UploadProgress.tsx","../src/components/CameraUploadButton.tsx","../src/components/FileUploadButton.tsx","../src/components/GalleryUploadButton.tsx","../src/components/UploadList.tsx","../src/components/flow-primitives.tsx"],"sourcesContent":["import type { UploadistaEvent } from \"@uploadista/client-core\";\nimport { createContext } from \"react\";\nimport type { FileSystemProvider } from \"../types\";\nimport type { UseUploadistaClientReturn } from \"./use-uploadista-client\";\n\nexport interface UploadistaContextType extends UseUploadistaClientReturn {\n fileSystemProvider: FileSystemProvider;\n /**\n * Subscribe to events (used internally by hooks)\n * @internal\n */\n subscribeToEvents: (handler: (event: UploadistaEvent) => void) => () => void;\n}\n\nexport const UploadistaContext = createContext<\n UploadistaContextType | undefined\n>(undefined);\n","/**\n * Platform-specific type definitions for React Native\n *\n * React Native's Blob implementation differs from the browser's Blob API.\n * This file provides proper type definitions and guards for platform-specific behavior.\n */\n\n/**\n * BufferSource represents data that can be passed to Blob constructor\n * Includes both ArrayBuffer and typed arrays (Uint8Array, etc.)\n */\nexport type BufferSource = ArrayBuffer | ArrayBufferView;\n\n/**\n * React Native Blob constructor options\n * Extends standard BlobPropertyBag with platform-specific properties\n */\nexport interface ReactNativeBlobOptions {\n /** MIME type of the blob */\n type?: string;\n /** Platform-specific: file path for optimization (React Native only) */\n path?: string;\n}\n\n/**\n * React Native Blob constructor type\n * Unlike browser Blob, accepts BufferSource in the parts array\n */\nexport interface ReactNativeBlobConstructor {\n new (\n parts?: Array<BufferSource | Blob | string>,\n options?: ReactNativeBlobOptions,\n ): Blob;\n prototype: Blob;\n}\n\n/**\n * Type guard to check if a value is ArrayBuffer\n */\nexport function isArrayBuffer(value: unknown): value is ArrayBuffer {\n return value instanceof ArrayBuffer;\n}\n\n/**\n * Type guard to check if a value is ArrayBufferView (typed array)\n */\nexport function isArrayBufferView(value: unknown): value is ArrayBufferView {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"buffer\" in value &&\n value.buffer instanceof ArrayBuffer\n );\n}\n\n/**\n * Type guard to check if a value is BufferSource\n */\nexport function isBufferSource(value: unknown): value is BufferSource {\n return isArrayBuffer(value) || isArrayBufferView(value);\n}\n\n/**\n * Type guard to check if we're in React Native environment\n * (checks for navigator.product === 'ReactNative')\n */\nexport function isReactNativeEnvironment(): boolean {\n const g = globalThis as typeof globalThis & {\n navigator?: { product?: string };\n };\n return (\n typeof g !== \"undefined\" &&\n typeof g.navigator !== \"undefined\" &&\n g.navigator.product === \"ReactNative\"\n );\n}\n\n/**\n * Create a Blob from BufferSource with proper typing for React Native\n *\n * This function handles the platform differences between browser and React Native Blob APIs.\n * React Native's Blob constructor accepts BufferSource directly, while browser Blob requires\n * conversion to Uint8Array first in some cases.\n *\n * @param data - ArrayBuffer or typed array to convert to Blob\n * @param options - Blob options including MIME type\n * @returns Platform-appropriate Blob instance\n *\n * @example\n * ```typescript\n * const arrayBuffer = await fileSystemProvider.readFile(uri);\n * const blob = createBlobFromBuffer(arrayBuffer, {\n * type: 'image/jpeg'\n * });\n * ```\n */\nexport function createBlobFromBuffer(\n data: BufferSource,\n options?: ReactNativeBlobOptions,\n): Blob {\n // Convert ArrayBuffer to Uint8Array for consistent handling\n const uint8Array = data instanceof ArrayBuffer ? new Uint8Array(data) : data;\n\n // In React Native, Blob constructor accepts BufferSource\n // Cast to ReactNativeBlobConstructor to use the correct signature\n const BlobConstructor = Blob as unknown as ReactNativeBlobConstructor;\n return new BlobConstructor([uint8Array], options);\n}\n","import { useContext } from \"react\";\nimport { UploadistaContext } from \"./uploadista-context\";\n\n/**\n * Hook to access the Uploadista client instance\n * Must be used within an UploadistaProvider\n * @throws Error if used outside of UploadistaProvider\n * @returns The Uploadista client and file system provider\n */\nexport function useUploadistaContext() {\n const context = useContext(UploadistaContext);\n\n if (!context) {\n throw new Error(\n \"useUploadistaClient must be used within an UploadistaProvider\",\n );\n }\n\n return context;\n}\n","import type {\n UploadistaUploadOptions,\n UploadMetrics,\n} from \"@uploadista/client-core\";\nimport {\n UploadManager,\n type UploadState,\n type UploadStatus,\n} from \"@uploadista/client-core\";\nimport type { UploadFile } from \"@uploadista/core/types\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { FilePickResult } from \"../types\";\nimport { createBlobFromBuffer } from \"../types/platform-types\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n// Re-export types from core for convenience\nexport type { UploadState, UploadStatus };\n\nexport interface UseUploadOptions {\n /**\n * Upload metadata to attach to the file\n */\n metadata?: Record<string, string>;\n\n /**\n * Whether to defer the upload size calculation\n */\n uploadLengthDeferred?: boolean;\n\n /**\n * Manual upload size override\n */\n uploadSize?: number;\n\n /**\n * Called when upload progress updates\n */\n onProgress?: (\n uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n\n /**\n * Called when a chunk completes\n */\n onChunkComplete?: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => void;\n\n /**\n * Called when upload succeeds\n */\n onSuccess?: (result: UploadFile) => void;\n\n /**\n * Called when upload fails\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when upload is aborted\n */\n onAbort?: () => void;\n\n /**\n * Custom retry logic\n */\n onShouldRetry?: (error: Error, retryAttempt: number) => boolean;\n}\n\nexport interface UseUploadReturn {\n /**\n * Current upload state\n */\n state: UploadState;\n\n /**\n * Start uploading a file from a file pick result\n */\n upload: (file: FilePickResult) => Promise<void>;\n\n /**\n * Abort the current upload\n */\n abort: () => void;\n\n /**\n * Reset the upload state to idle\n */\n reset: () => void;\n\n /**\n * Retry the last failed upload\n */\n retry: () => void;\n\n /**\n * Whether an upload is currently active\n */\n isUploading: boolean;\n\n /**\n * Whether the upload can be retried\n */\n canRetry: boolean;\n\n /**\n * Upload metrics and performance insights from the client\n */\n metrics: UploadMetrics;\n}\n\nconst initialState: UploadState = {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: null,\n error: null,\n result: null,\n};\n\n/**\n * React Native hook for managing individual file uploads with full state management.\n * Provides upload progress tracking, error handling, abort functionality, and retry logic.\n *\n * Must be used within an UploadistaProvider.\n *\n * @param options - Upload configuration and event handlers\n * @returns Upload state and control methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const upload = useUpload({\n * onSuccess: (result) => console.log('Upload complete:', result),\n * onError: (error) => console.error('Upload failed:', error),\n * onProgress: (progress) => console.log('Progress:', progress + '%'),\n * });\n *\n * const handleFilePick = async () => {\n * const file = await pickFile();\n * if (file) await upload.upload(file);\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick File\" onPress={handleFilePick} />\n * {upload.isUploading && <Text>Progress: {upload.state.progress}%</Text>}\n * {upload.state.error && <Text>Error: {upload.state.error.message}</Text>}\n * {upload.canRetry && <Button title=\"Retry\" onPress={upload.retry} />}\n * <Button title=\"Abort\" onPress={upload.abort} disabled={!upload.isUploading} />\n * </View>\n * );\n * }\n * ```\n */\nexport function useUpload(options: UseUploadOptions = {}): UseUploadReturn {\n const { client, fileSystemProvider } = useUploadistaContext();\n const [state, setState] = useState<UploadState>(initialState);\n const managerRef = useRef<UploadManager | null>(null);\n const lastFileRef = useRef<FilePickResult | null>(null);\n\n // Create UploadManager instance\n useEffect(() => {\n // Create upload function that handles React Native file reading\n const uploadFn = async (input: unknown, opts: UploadistaUploadOptions) => {\n const file = input as FilePickResult;\n\n if (file.status === \"success\") {\n // Read file content from React Native file system\n const fileContent = await fileSystemProvider.readFile(file.data.uri);\n\n // Create a Blob from the file content using platform-aware utility\n const blob = createBlobFromBuffer(fileContent, {\n type: file.data.mimeType || \"application/octet-stream\",\n });\n\n // Upload the Blob\n return client.upload(blob, opts);\n }\n\n return Promise.resolve({ abort: () => {} });\n };\n\n managerRef.current = new UploadManager(\n uploadFn,\n {\n onStateChange: setState,\n onProgress: options.onProgress,\n onChunkComplete: options.onChunkComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n onAbort: options.onAbort,\n },\n {\n metadata: options.metadata,\n uploadLengthDeferred: options.uploadLengthDeferred,\n uploadSize: options.uploadSize,\n onShouldRetry: options.onShouldRetry,\n },\n );\n\n return () => {\n managerRef.current?.cleanup();\n };\n }, [client, fileSystemProvider, options]);\n\n // Upload function - stores file reference for retry\n const upload = useCallback(async (file: FilePickResult) => {\n lastFileRef.current = file;\n await managerRef.current?.upload(file);\n }, []);\n\n // Abort function\n const abort = useCallback(() => {\n managerRef.current?.abort();\n }, []);\n\n // Reset function\n const reset = useCallback(() => {\n managerRef.current?.reset();\n lastFileRef.current = null;\n }, []);\n\n // Retry function\n const retry = useCallback(() => {\n if (lastFileRef.current && managerRef.current?.canRetry()) {\n managerRef.current.retry();\n }\n }, []);\n\n // Derive computed values from state\n const isUploading = state.status === \"uploading\";\n const canRetry = managerRef.current?.canRetry() ?? false;\n\n // Create metrics object that delegates to the upload client\n const metrics: UploadMetrics = {\n getInsights: () => client.getChunkingInsights(),\n exportMetrics: () => client.exportMetrics(),\n getNetworkMetrics: () => client.getNetworkMetrics(),\n getNetworkCondition: () => client.getNetworkCondition(),\n resetMetrics: () => client.resetMetrics(),\n };\n\n return {\n state,\n upload,\n abort,\n reset,\n retry,\n isUploading,\n canRetry,\n metrics,\n };\n}\n","import { useCallback } from \"react\";\nimport type { UseCameraUploadOptions } from \"../types\";\nimport { useUpload } from \"./use-upload\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n/**\n * Hook for capturing photos and uploading them\n * Handles camera permissions and capture flow\n * @param options - Camera upload configuration\n * @returns Upload state and camera capture/upload function\n */\nexport function useCameraUpload(options?: UseCameraUploadOptions) {\n const { fileSystemProvider } = useUploadistaContext();\n const uploadHook = useUpload({\n metadata: options?.metadata,\n onSuccess: options?.onSuccess,\n onError: options?.onError,\n onProgress: options?.onProgress,\n });\n\n // Capture and upload photo\n const captureAndUpload = useCallback(async () => {\n try {\n // Capture photo with camera\n const photo = await fileSystemProvider.pickCamera(options?.cameraOptions);\n\n // Upload captured photo\n await uploadHook.upload(photo);\n } catch (error) {\n console.error(\"Camera capture error:\", error);\n }\n }, [fileSystemProvider, options?.cameraOptions, uploadHook]);\n\n return {\n ...uploadHook,\n captureAndUpload,\n };\n}\n","import { useCallback } from \"react\";\nimport type { UseFileUploadOptions } from \"../types\";\nimport { useUpload } from \"./use-upload\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n/**\n * Hook for selecting and uploading generic files (documents, etc.)\n * @param options - File upload configuration\n * @returns Upload state and file picker/upload function\n */\nexport function useFileUpload(options?: UseFileUploadOptions) {\n const { fileSystemProvider } = useUploadistaContext();\n const uploadHook = useUpload({\n metadata: options?.metadata,\n onSuccess: options?.onSuccess,\n onError: options?.onError,\n onProgress: options?.onProgress,\n });\n\n // Pick and upload file\n const pickAndUpload = useCallback(async () => {\n try {\n // Pick file\n const file = await fileSystemProvider.pickDocument({\n allowedTypes: options?.allowedTypes,\n });\n\n // Upload file\n await uploadHook.upload(file);\n } catch (error) {\n console.error(\"File selection error:\", error);\n throw error;\n }\n }, [fileSystemProvider, options?.allowedTypes, uploadHook]);\n\n return {\n ...uploadHook,\n pickAndUpload,\n };\n}\n","import type { UploadistaEvent } from \"@uploadista/client-core\";\nimport {\n FlowManager,\n type FlowManagerCallbacks,\n type FlowUploadOptions,\n} from \"@uploadista/client-core\";\nimport { EventType, type FlowEvent } from \"@uploadista/core/flow\";\nimport { UploadEventType } from \"@uploadista/core/types\";\nimport type { ReactNode } from \"react\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n} from \"react\";\nimport { useUploadistaContext } from \"../hooks/use-uploadista-context\";\nimport type { ReactNativeUploadInput } from \"../types\";\n\n/**\n * Type guard to check if an event is a flow event\n */\nfunction isFlowEvent(event: UploadistaEvent): event is FlowEvent {\n const flowEvent = event as FlowEvent;\n return (\n flowEvent.eventType === EventType.FlowStart ||\n flowEvent.eventType === EventType.FlowEnd ||\n flowEvent.eventType === EventType.FlowError ||\n flowEvent.eventType === EventType.NodeStart ||\n flowEvent.eventType === EventType.NodeEnd ||\n flowEvent.eventType === EventType.NodePause ||\n flowEvent.eventType === EventType.NodeResume ||\n flowEvent.eventType === EventType.NodeError\n );\n}\n\n/**\n * Internal manager registry entry with ref counting\n */\ninterface ManagerEntry {\n manager: FlowManager<unknown>;\n refCount: number;\n flowId: string;\n}\n\n/**\n * Context value providing access to flow managers\n */\ninterface FlowManagerContextValue {\n /**\n * Get or create a flow manager for the given flow ID.\n * Increments ref count - must call releaseManager when done.\n *\n * @param flowId - Unique identifier for the flow\n * @param callbacks - Callbacks for state changes and lifecycle events\n * @param options - Flow configuration options\n * @returns FlowManager instance\n */\n getManager: (\n flowId: string,\n callbacks: FlowManagerCallbacks,\n options: FlowUploadOptions,\n ) => FlowManager<unknown>;\n\n /**\n * Release a flow manager reference.\n * Decrements ref count and cleans up when reaching zero.\n *\n * @param flowId - Unique identifier for the flow to release\n */\n releaseManager: (flowId: string) => void;\n}\n\nconst FlowManagerContext = createContext<FlowManagerContextValue | undefined>(\n undefined,\n);\n\n/**\n * Props for FlowManagerProvider\n */\ninterface FlowManagerProviderProps {\n children: ReactNode;\n}\n\n/**\n * Provider that manages FlowManager instances with ref counting and event routing.\n * Ensures managers persist across component re-renders and are only cleaned up\n * when all consuming components unmount.\n *\n * This provider should be nested inside UploadistaProvider to access the upload client\n * and event subscription system.\n *\n * @example\n * ```tsx\n * <UploadistaProvider baseUrl=\"https://api.example.com\" storageId=\"default\">\n * <FlowManagerProvider>\n * <App />\n * </FlowManagerProvider>\n * </UploadistaProvider>\n * ```\n */\nexport function FlowManagerProvider({ children }: FlowManagerProviderProps) {\n const { client, subscribeToEvents } = useUploadistaContext();\n const managersRef = useRef(new Map<string, ManagerEntry>());\n\n // Subscribe to all events and route to appropriate managers\n useEffect(() => {\n const unsubscribe = subscribeToEvents((event: UploadistaEvent) => {\n // Route flow events to all managers (they filter by jobId internally)\n if (isFlowEvent(event)) {\n for (const entry of managersRef.current.values()) {\n entry.manager.handleFlowEvent(event);\n }\n return;\n }\n\n // Route upload progress events to all managers\n if (\n \"type\" in event &&\n event.type === UploadEventType.UPLOAD_PROGRESS &&\n \"data\" in event\n ) {\n for (const entry of managersRef.current.values()) {\n entry.manager.handleUploadProgress(\n event.data.id,\n event.data.progress,\n event.data.total,\n );\n }\n }\n });\n\n return unsubscribe;\n }, [subscribeToEvents]);\n\n const getManager = useCallback(\n (\n flowId: string,\n callbacks: FlowManagerCallbacks,\n options: FlowUploadOptions,\n ): FlowManager<unknown> => {\n const existing = managersRef.current.get(flowId);\n\n if (existing) {\n // Increment ref count for existing manager\n existing.refCount++;\n return existing.manager;\n }\n\n // Create new manager using client from hook scope\n const manager = new FlowManager<ReactNativeUploadInput>(\n client.uploadWithFlow,\n callbacks,\n options,\n client.multiInputFlowUpload,\n );\n\n managersRef.current.set(flowId, {\n manager,\n refCount: 1,\n flowId,\n });\n\n return manager;\n },\n [client],\n );\n\n const releaseManager = useCallback((flowId: string) => {\n const existing = managersRef.current.get(flowId);\n if (!existing) return;\n\n existing.refCount--;\n\n // Clean up when no more refs\n if (existing.refCount <= 0) {\n existing.manager.cleanup();\n managersRef.current.delete(flowId);\n }\n }, []);\n\n return (\n <FlowManagerContext.Provider value={{ getManager, releaseManager }}>\n {children}\n </FlowManagerContext.Provider>\n );\n}\n\n/**\n * Hook to access the FlowManager context.\n * Must be used within a FlowManagerProvider.\n *\n * @returns FlowManager context value with getManager and releaseManager functions\n * @throws Error if used outside of FlowManagerProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { getManager, releaseManager } = useFlowManagerContext();\n * // Use to create managers...\n * }\n * ```\n */\nexport function useFlowManagerContext(): FlowManagerContextValue {\n const context = useContext(FlowManagerContext);\n\n if (context === undefined) {\n throw new Error(\n \"useFlowManagerContext must be used within a FlowManagerProvider. \" +\n \"Make sure to wrap your component tree with <FlowManagerProvider>.\",\n );\n }\n\n return context;\n}\n","import type {\n FlowManager,\n FlowUploadState,\n FlowUploadStatus,\n InputExecutionState,\n} from \"@uploadista/client-core\";\nimport type { TypedOutput } from \"@uploadista/core/flow\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useFlowManagerContext } from \"../contexts/flow-manager-context\";\nimport type { FilePickResult } from \"../types\";\nimport { createBlobFromBuffer } from \"../types/platform-types\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n// Re-export types from core for convenience\nexport type { FlowUploadState, FlowUploadStatus, InputExecutionState };\n\n/**\n * Input metadata discovered from the flow\n */\nexport interface FlowInputMetadata {\n /** Input node ID */\n nodeId: string;\n /** Human-readable node name */\n nodeName: string;\n /** Node description explaining what input is needed */\n nodeDescription: string;\n /** Input type ID from inputTypeRegistry - describes how clients interact with this node */\n inputTypeId?: string;\n /** Whether this input is required */\n required: boolean;\n}\n\n/**\n * Options for the useFlow hook\n */\nexport interface UseFlowOptions {\n /** Flow ID to execute */\n flowId: string;\n /** Storage ID for the upload */\n storageId: string;\n /** Output node ID for the flow */\n outputNodeId?: string;\n /** Metadata to pass to flow */\n metadata?: Record<string, unknown>;\n /** Called when upload succeeds (receives typed outputs from all output nodes) */\n onSuccess?: (outputs: TypedOutput[]) => void;\n /** Called when the flow completes successfully (receives full flow outputs) */\n onFlowComplete?: (outputs: TypedOutput[]) => void;\n /** Called when upload fails */\n onError?: (error: Error) => void;\n /** Called when upload progress updates */\n onProgress?: (\n progress: number,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n /** Called when a chunk completes */\n onChunkComplete?: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => void;\n}\n\n/**\n * Return value from the useFlow hook with upload control methods and state.\n *\n * @property state - Complete flow upload state with progress and outputs\n * @property inputMetadata - Metadata about discovered input nodes (null until discovered)\n * @property inputStates - Per-input execution state for multi-input flows\n * @property inputs - Current input values set via setInput()\n * @property setInput - Set an input value for a specific node (for progressive provision)\n * @property execute - Execute the flow with current inputs (auto-detects types)\n * @property upload - Convenience method for single-file upload (same as execute with one file input)\n * @property abort - Cancel the current upload and flow execution\n * @property reset - Reset state to idle (clears all data)\n * @property retry - Retry the last failed upload\n * @property isActive - True when upload or processing is active\n * @property isUploadingFile - True only during file upload phase\n * @property isProcessing - True only during flow processing phase\n * @property isDiscoveringInputs - True while discovering flow inputs\n * @property canRetry - True if a retry is possible\n */\nexport interface UseFlowReturn {\n /**\n * Current upload state\n */\n state: FlowUploadState;\n\n /**\n * Discovered input nodes metadata (null until discovery completes)\n */\n inputMetadata: FlowInputMetadata[] | null;\n\n /**\n * Per-input execution state for multi-input flows\n */\n inputStates: ReadonlyMap<string, InputExecutionState>;\n\n /**\n * Current inputs set via setInput()\n */\n inputs: Record<string, unknown>;\n\n /**\n * Set an input value for a specific node.\n * For progressive input provision before calling execute().\n *\n * @param nodeId - The input node ID\n * @param value - The input value (FilePickResult, URL string, or structured data)\n */\n setInput: (nodeId: string, value: FilePickResult | string | unknown) => void;\n\n /**\n * Execute the flow with current inputs.\n * Automatically detects input types and routes appropriately.\n * For single input, uses standard upload path.\n * For multiple inputs, requires multiInputUploadFn.\n */\n execute: () => Promise<void>;\n\n /**\n * Upload a single file through the flow (convenience method).\n * Equivalent to setInput(firstNodeId, file) + execute().\n *\n * @param file - FilePickResult from a picker\n */\n upload: (file: FilePickResult) => Promise<void>;\n\n /**\n * Abort the current upload\n */\n abort: () => void;\n\n /**\n * Reset the upload state and clear all inputs\n */\n reset: () => void;\n\n /**\n * Retry the last failed upload\n */\n retry: () => void;\n\n /**\n * Whether an upload or flow execution is in progress (uploading OR processing)\n */\n isActive: boolean;\n\n /**\n * Whether the file is currently being uploaded (chunks being sent)\n */\n isUploadingFile: boolean;\n\n /**\n * Whether the flow is currently processing (after upload completes)\n */\n isProcessing: boolean;\n\n /**\n * Whether the hook is discovering flow inputs\n */\n isDiscoveringInputs: boolean;\n\n /**\n * Whether a retry is possible (after error or abort with stored inputs)\n */\n canRetry: boolean;\n}\n\nconst initialState: FlowUploadState = {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: null,\n error: null,\n jobId: null,\n flowStarted: false,\n currentNodeName: null,\n currentNodeType: null,\n flowOutputs: null,\n};\n\n/**\n * React Native hook for executing flows with single or multiple inputs.\n * Automatically discovers input nodes and detects input types (File, URL, structured data).\n * Supports progressive input provision via setInput() and execute().\n *\n * This is the unified flow hook that replaces useFlowUpload for advanced use cases.\n * It provides:\n * - Auto-discovery of flow input nodes\n * - Automatic input type detection (FilePickResult -> upload, string -> URL, object -> data)\n * - Progressive input provision via setInput()\n * - Multi-input support with parallel coordination\n * - Per-input state tracking\n *\n * Must be used within FlowManagerProvider (which must be within UploadistaProvider).\n * Flow events are automatically routed by the provider to the appropriate manager.\n *\n * @param options - Flow upload configuration including flow ID and event handlers\n * @returns Flow upload state and control methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const flow = useFlow({\n * flowId: 'image-processing-flow',\n * storageId: 'my-storage',\n * onSuccess: (outputs) => console.log('Flow complete:', outputs),\n * onError: (error) => console.error('Flow failed:', error),\n * });\n *\n * const handlePickFile = async () => {\n * const file = await fileSystemProvider.pickDocument();\n * if (file) {\n * await flow.upload(file);\n * }\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick File\" onPress={handlePickFile} />\n * {flow.isActive && <Text>Progress: {flow.state.progress}%</Text>}\n * {flow.inputMetadata && (\n * <Text>Found {flow.inputMetadata.length} input nodes</Text>\n * )}\n * <Button title=\"Abort\" onPress={flow.abort} disabled={!flow.isActive} />\n * </View>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Multi-input flow\n * function MultiInputComponent() {\n * const flow = useFlow({\n * flowId: 'multi-source-flow',\n * storageId: 'my-storage',\n * });\n *\n * const handlePickPrimary = async () => {\n * const file = await fileSystemProvider.pickDocument();\n * if (file.status === 'success') {\n * flow.setInput('primary-input', file);\n * }\n * };\n *\n * const handleSetUrl = (url: string) => {\n * flow.setInput('url-input', url);\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick Primary\" onPress={handlePickPrimary} />\n * <TextInput onChangeText={handleSetUrl} placeholder=\"Enter URL\" />\n * <Button title=\"Execute\" onPress={flow.execute} />\n * </View>\n * );\n * }\n * ```\n *\n * @see {@link useFlowUpload} for a simpler file-only upload hook\n */\nexport function useFlow(options: UseFlowOptions): UseFlowReturn {\n const { client, fileSystemProvider } = useUploadistaContext();\n const { getManager, releaseManager } = useFlowManagerContext();\n const [state, setState] = useState<FlowUploadState>(initialState);\n const [inputMetadata, setInputMetadata] = useState<\n FlowInputMetadata[] | null\n >(null);\n const [isDiscoveringInputs, setIsDiscoveringInputs] = useState(false);\n const [inputs, setInputs] = useState<Record<string, unknown>>({});\n const [inputStates, setInputStates] = useState<\n ReadonlyMap<string, InputExecutionState>\n >(new Map());\n const managerRef = useRef<FlowManager<unknown> | null>(null);\n const lastInputsRef = useRef<Record<string, unknown> | null>(null);\n\n // Store callbacks in refs so they can be updated without recreating the manager\n const callbacksRef = useRef(options);\n\n // Update refs on every render to capture latest callbacks\n useEffect(() => {\n callbacksRef.current = options;\n });\n\n // Auto-discover flow inputs on mount\n useEffect(() => {\n const discoverInputs = async () => {\n setIsDiscoveringInputs(true);\n try {\n const { flow } = await client.getFlow(options.flowId);\n\n // Find all input nodes\n const inputNodes = flow.nodes.filter((node) => node.type === \"input\");\n\n const metadata: FlowInputMetadata[] = inputNodes.map((node) => ({\n nodeId: node.id,\n nodeName: node.name,\n nodeDescription: node.description,\n inputTypeId: node.inputTypeId,\n required: true,\n }));\n\n setInputMetadata(metadata);\n } catch (error) {\n console.error(\"Failed to discover flow inputs:\", error);\n } finally {\n setIsDiscoveringInputs(false);\n }\n };\n\n discoverInputs();\n }, [client, options.flowId]);\n\n // Get or create manager from context when component mounts\n // biome-ignore lint/correctness/useExhaustiveDependencies: we don't want to recreate the manager on every render\n useEffect(() => {\n const flowId = options.flowId;\n\n // Create stable callback wrappers that call the latest callbacks via refs\n const stableCallbacks = {\n onStateChange: (newState: FlowUploadState) => {\n setState(newState);\n },\n onProgress: (\n _uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => {\n if (callbacksRef.current.onProgress) {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n callbacksRef.current.onProgress(progress, bytesUploaded, totalBytes);\n }\n },\n onChunkComplete: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => {\n callbacksRef.current.onChunkComplete?.(\n chunkSize,\n bytesAccepted,\n bytesTotal,\n );\n },\n onFlowComplete: (outputs: TypedOutput[]) => {\n callbacksRef.current.onFlowComplete?.(outputs);\n },\n onSuccess: (outputs: TypedOutput[]) => {\n callbacksRef.current.onSuccess?.(outputs);\n },\n onError: (error: Error) => {\n callbacksRef.current.onError?.(error);\n },\n onAbort: () => {\n // onAbort not exposed in public API\n },\n };\n\n // Get manager from context (creates if doesn't exist, increments ref count)\n managerRef.current = getManager(flowId, stableCallbacks, {\n flowConfig: {\n flowId: options.flowId,\n storageId: options.storageId,\n outputNodeId: options.outputNodeId,\n metadata: options.metadata as Record<string, string> | undefined,\n },\n onChunkComplete: options.onChunkComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n });\n\n // Set up interval to poll input states for multi-input flows\n const pollInterval = setInterval(() => {\n if (managerRef.current) {\n const states = managerRef.current.getInputStates();\n if (states.size > 0) {\n setInputStates(new Map(states));\n }\n }\n }, 100); // Poll every 100ms\n\n // Release manager when component unmounts or flowId changes\n return () => {\n clearInterval(pollInterval);\n releaseManager(flowId);\n managerRef.current = null;\n };\n }, [\n options.flowId,\n options.storageId,\n options.outputNodeId,\n getManager,\n releaseManager,\n ]);\n\n // Set an input value\n const setInput = useCallback(\n (nodeId: string, value: FilePickResult | string | unknown) => {\n setInputs((prev) => ({ ...prev, [nodeId]: value }));\n },\n [],\n );\n\n // Helper to convert FilePickResult to Blob\n const filePickToBlob = useCallback(\n async (file: FilePickResult): Promise<Blob | null> => {\n if (file.status === \"cancelled\") {\n return null;\n }\n if (file.status === \"error\") {\n throw file.error;\n }\n\n const fileContent = await fileSystemProvider.readFile(file.data.uri);\n return createBlobFromBuffer(fileContent, {\n type: file.data.mimeType || \"application/octet-stream\",\n });\n },\n [fileSystemProvider],\n );\n\n // Execute flow with current inputs\n const execute = useCallback(async () => {\n if (!managerRef.current) {\n throw new Error(\"FlowManager not initialized\");\n }\n\n if (Object.keys(inputs).length === 0) {\n throw new Error(\n \"No inputs provided. Use setInput() to provide inputs before calling execute()\",\n );\n }\n\n // Store inputs for retry\n lastInputsRef.current = { ...inputs };\n\n // Convert FilePickResults to Blobs\n const processedInputs: Record<string, unknown> = {};\n\n for (const [nodeId, value] of Object.entries(inputs)) {\n // Check if value is a FilePickResult\n if (\n value &&\n typeof value === \"object\" &&\n \"status\" in value &&\n (value.status === \"success\" ||\n value.status === \"cancelled\" ||\n value.status === \"error\")\n ) {\n const blob = await filePickToBlob(value as FilePickResult);\n if (blob) {\n processedInputs[nodeId] = blob;\n }\n // If blob is null (cancelled), skip this input\n } else {\n // Pass through strings (URLs) and other values as-is\n processedInputs[nodeId] = value;\n }\n }\n\n if (Object.keys(processedInputs).length === 0) {\n throw new Error(\n \"No valid inputs after processing. All files may have been cancelled.\",\n );\n }\n\n await managerRef.current.executeFlow(processedInputs);\n }, [inputs, filePickToBlob]);\n\n // Convenience method for single file upload\n const upload = useCallback(\n async (file: FilePickResult) => {\n // Handle cancelled picker\n if (file.status === \"cancelled\") {\n return;\n }\n\n // Handle picker error\n if (file.status === \"error\") {\n options.onError?.(file.error);\n return;\n }\n\n if (!managerRef.current) {\n throw new Error(\"FlowManager not initialized\");\n }\n\n // Store for retry\n if (inputMetadata && inputMetadata.length > 0) {\n const firstInputNode = inputMetadata[0];\n if (firstInputNode) {\n lastInputsRef.current = { [firstInputNode.nodeId]: file };\n }\n }\n\n try {\n const fileContent = await fileSystemProvider.readFile(file.data.uri);\n const blob = createBlobFromBuffer(fileContent, {\n type: file.data.mimeType || \"application/octet-stream\",\n });\n\n // If we have input metadata, use the first input node\n if (inputMetadata && inputMetadata.length > 0) {\n const firstInputNode = inputMetadata[0];\n if (!firstInputNode) {\n throw new Error(\"No input nodes found\");\n }\n setInputs({ [firstInputNode.nodeId]: file });\n await managerRef.current.executeFlow({\n [firstInputNode.nodeId]: blob,\n });\n } else {\n // Fall back to direct upload (manager will handle discovery)\n await managerRef.current.upload(blob);\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n },\n [inputMetadata, fileSystemProvider, options],\n );\n\n const abort = useCallback(() => {\n managerRef.current?.abort();\n }, []);\n\n const reset = useCallback(() => {\n managerRef.current?.reset();\n setInputs({});\n setInputStates(new Map());\n lastInputsRef.current = null;\n }, []);\n\n const retry = useCallback(() => {\n if (\n lastInputsRef.current &&\n (state.status === \"error\" || state.status === \"aborted\")\n ) {\n // Restore inputs and re-execute\n setInputs(lastInputsRef.current);\n execute();\n }\n }, [execute, state.status]);\n\n // Derive computed values from state (reactive to state changes)\n const isActive =\n state.status === \"uploading\" || state.status === \"processing\";\n const isUploadingFile = state.status === \"uploading\";\n const isProcessing = state.status === \"processing\";\n const canRetry =\n (state.status === \"error\" || state.status === \"aborted\") &&\n lastInputsRef.current !== null;\n\n return {\n state,\n inputMetadata,\n inputStates,\n inputs,\n setInput,\n execute,\n upload,\n abort,\n reset,\n retry,\n isActive,\n isUploadingFile,\n isProcessing,\n isDiscoveringInputs,\n canRetry,\n };\n}\n","import type { UploadFile } from \"@uploadista/core/types\";\nimport { useCallback, useRef, useState } from \"react\";\nimport type { FilePickResult, UseMultiUploadOptions } from \"../types\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\nexport interface UploadItemState {\n id: string;\n file: Extract<FilePickResult, { status: \"success\" }>;\n status: \"idle\" | \"uploading\" | \"success\" | \"error\" | \"aborted\";\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error: Error | null;\n result: UploadFile | null;\n}\n\nexport interface MultiUploadState {\n items: UploadItemState[];\n totalProgress: number;\n totalUploaded: number;\n totalBytes: number;\n activeCount: number;\n completedCount: number;\n failedCount: number;\n}\n\nconst initialState: MultiUploadState = {\n items: [],\n totalProgress: 0,\n totalUploaded: 0,\n totalBytes: 0,\n activeCount: 0,\n completedCount: 0,\n failedCount: 0,\n};\n\n/**\n * Hook for managing multiple concurrent file uploads with progress tracking.\n * Each file is uploaded independently using the core upload client.\n *\n * Must be used within an UploadistaProvider.\n *\n * @param options - Multi-upload configuration options\n * @returns Multi-upload state and control methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const multiUpload = useMultiUpload({\n * maxConcurrent: 3,\n * onSuccess: (result) => console.log('File uploaded:', result),\n * onError: (error) => console.error('Upload failed:', error),\n * });\n *\n * const handlePickFiles = async () => {\n * const files = await fileSystemProvider.pickImage({ allowMultiple: true });\n * multiUpload.addFiles(files);\n * await multiUpload.startUploads();\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick Files\" onPress={handlePickFiles} />\n * <Text>Progress: {multiUpload.state.totalProgress}%</Text>\n * <Text>Active: {multiUpload.state.activeCount}</Text>\n * <Text>Completed: {multiUpload.state.completedCount}/{multiUpload.state.items.length}</Text>\n * </View>\n * );\n * }\n * ```\n */\nexport function useMultiUpload(options: UseMultiUploadOptions = {}) {\n const { client } = useUploadistaContext();\n const [state, setState] = useState<MultiUploadState>(initialState);\n const abortControllersRef = useRef<Map<string, { abort: () => void }>>(\n new Map(),\n );\n const nextIdRef = useRef(0);\n // Use ref to track items synchronously\n const itemsRef = useRef<UploadItemState[]>([]);\n\n const generateId = useCallback(() => {\n return `upload-${Date.now()}-${nextIdRef.current++}`;\n }, []);\n\n const updateAggregateStats = useCallback((items: UploadItemState[]) => {\n const totalBytes = items.reduce((sum, item) => sum + item.totalBytes, 0);\n const totalUploaded = items.reduce(\n (sum, item) => sum + item.bytesUploaded,\n 0,\n );\n const totalProgress =\n totalBytes > 0 ? Math.round((totalUploaded / totalBytes) * 100) : 0;\n const activeCount = items.filter(\n (item) => item.status === \"uploading\",\n ).length;\n const completedCount = items.filter(\n (item) => item.status === \"success\",\n ).length;\n const failedCount = items.filter((item) => item.status === \"error\").length;\n\n // Update ref synchronously\n itemsRef.current = items;\n\n setState((prev) => ({\n ...prev,\n items,\n totalProgress,\n totalUploaded,\n totalBytes,\n activeCount,\n completedCount,\n failedCount,\n }));\n }, []);\n\n const addFiles = useCallback(\n (files: FilePickResult[]) => {\n // Filter out cancelled and error results, only keep successful picks\n const successfulFiles = files.filter(\n (file): file is Extract<FilePickResult, { status: \"success\" }> =>\n file.status === \"success\",\n );\n\n const newItems: UploadItemState[] = successfulFiles.map((file) => ({\n id: generateId(),\n file,\n status: \"idle\" as const,\n progress: 0,\n bytesUploaded: 0,\n totalBytes: file.data.size,\n error: null,\n result: null,\n }));\n\n // Update ref synchronously\n const updatedItems = [...itemsRef.current, ...newItems];\n itemsRef.current = updatedItems;\n\n setState((prev) => {\n const totalBytes = updatedItems.reduce(\n (sum, item) => sum + item.totalBytes,\n 0,\n );\n return {\n ...prev,\n items: updatedItems,\n totalBytes,\n };\n });\n\n return newItems.map((item) => item.id);\n },\n [generateId],\n );\n\n const uploadSingleItem = useCallback(\n async (item: UploadItemState) => {\n try {\n console.log(\"Uploading item:\", item.file.data.name);\n // Update status to uploading\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id ? { ...i, status: \"uploading\" as const } : i,\n );\n updateAggregateStats(updatedItems);\n\n // Convert file URI to Blob using fetch (React Native compatible)\n // React Native's Blob doesn't support ArrayBuffer/Uint8Array constructor\n const response = await fetch(item.file.data.uri);\n const blob = await response.blob();\n\n // Override blob type if we have mimeType from picker\n // Use type assertion to handle differences between Expo's BlobOptions and standard BlobPropertyBag\n let uploadInput: Blob = blob;\n if (item.file.data.mimeType) {\n const blobOptions = {\n type: item.file.data.mimeType,\n lastModified: Date.now(),\n };\n // biome-ignore lint/suspicious/noExplicitAny: Expo and bare RN have incompatible Blob types\n uploadInput = new Blob([blob], blobOptions as any);\n }\n\n // Start upload using the client\n console.log(\"Uploading input:\", uploadInput);\n const uploadPromise = client.upload(uploadInput, {\n metadata: options.metadata,\n\n onProgress: (\n _uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id\n ? {\n ...i,\n progress,\n bytesUploaded,\n totalBytes: totalBytes || i.totalBytes,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n },\n\n onSuccess: (result: UploadFile) => {\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id\n ? {\n ...i,\n status: \"success\" as const,\n progress: 100,\n result,\n bytesUploaded: result.size || i.totalBytes,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n\n options.onSuccess?.(result);\n abortControllersRef.current.delete(item.id);\n },\n\n onError: (error: Error) => {\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id ? { ...i, status: \"error\" as const, error } : i,\n );\n updateAggregateStats(updatedItems);\n\n options.onError?.(error);\n abortControllersRef.current.delete(item.id);\n },\n });\n\n // Store abort controller\n const controller = await uploadPromise;\n abortControllersRef.current.set(item.id, controller);\n } catch (error) {\n console.error(\"Error uploading item:\", error);\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id\n ? {\n ...i,\n status: \"error\" as const,\n error: error as Error,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n\n options.onError?.(error as Error);\n abortControllersRef.current.delete(item.id);\n }\n },\n [client, options, updateAggregateStats],\n );\n\n const startUploads = useCallback(\n async (itemIds?: string[]) => {\n const maxConcurrent = options.maxConcurrent || 3;\n\n // Get items from ref (synchronous access to latest items)\n const itemsToUpload = itemIds\n ? itemsRef.current.filter(\n (item) => itemIds.includes(item.id) && item.status === \"idle\",\n )\n : itemsRef.current.filter((item) => item.status === \"idle\");\n\n console.log(\"Items to upload:\", itemsToUpload.length, itemsToUpload);\n\n // Process items in batches\n for (let i = 0; i < itemsToUpload.length; i += maxConcurrent) {\n const batch = itemsToUpload.slice(i, i + maxConcurrent);\n await Promise.all(batch.map((item) => uploadSingleItem(item)));\n }\n },\n [options.maxConcurrent, uploadSingleItem],\n );\n\n const removeItem = useCallback(\n (id: string) => {\n const controller = abortControllersRef.current.get(id);\n if (controller) {\n controller.abort();\n abortControllersRef.current.delete(id);\n }\n\n const updatedItems = itemsRef.current.filter((item) => item.id !== id);\n updateAggregateStats(updatedItems);\n },\n [updateAggregateStats],\n );\n\n const abortItem = useCallback(\n (id: string) => {\n const controller = abortControllersRef.current.get(id);\n if (controller) {\n controller.abort();\n abortControllersRef.current.delete(id);\n }\n\n const updatedItems = itemsRef.current.map((item) =>\n item.id === id ? { ...item, status: \"aborted\" as const } : item,\n );\n updateAggregateStats(updatedItems);\n },\n [updateAggregateStats],\n );\n\n const clear = useCallback(() => {\n // Abort all active uploads\n abortControllersRef.current.forEach((controller) => {\n controller.abort();\n });\n abortControllersRef.current.clear();\n\n // Clear ref\n itemsRef.current = [];\n\n setState(initialState);\n }, []);\n\n const retryItem = useCallback(\n async (id: string) => {\n const item = itemsRef.current.find((i) => i.id === id);\n if (item && (item.status === \"error\" || item.status === \"aborted\")) {\n // Reset item status to idle\n const updatedItems = itemsRef.current.map((i) =>\n i.id === id\n ? {\n ...i,\n status: \"idle\" as const,\n progress: 0,\n bytesUploaded: 0,\n error: null,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n\n // Upload it (get the reset item from the updated items)\n const resetItem = itemsRef.current.find((i) => i.id === id);\n if (resetItem) {\n await uploadSingleItem(resetItem);\n }\n }\n },\n [uploadSingleItem, updateAggregateStats],\n );\n\n return {\n state,\n addFiles,\n startUploads,\n removeItem,\n abortItem,\n retryItem,\n clear,\n };\n}\n","import { useCallback } from \"react\";\nimport type { FilePickResult, UseGalleryUploadOptions } from \"../types\";\nimport { useMultiUpload } from \"./use-multi-upload\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n/**\n * Hook for selecting and uploading photos/videos from gallery\n * Handles batch selection and concurrent uploads\n * @param options - Gallery upload configuration\n * @returns Upload state and gallery selection/upload function\n */\nexport function useGalleryUpload(options?: UseGalleryUploadOptions) {\n const { fileSystemProvider } = useUploadistaContext();\n const uploadHook = useMultiUpload({\n maxConcurrent: 3,\n metadata: options?.metadata,\n onSuccess: options?.onSuccess,\n onError: options?.onError,\n });\n\n // Select and upload media from gallery\n const selectAndUpload = useCallback(async () => {\n let result: FilePickResult;\n\n // Select appropriate media type\n if (options?.mediaType === \"video\") {\n result = await fileSystemProvider.pickVideo({\n allowMultiple: options?.allowMultiple ?? true,\n });\n } else if (options?.mediaType === \"photo\") {\n result = await fileSystemProvider.pickImage({\n allowMultiple: options?.allowMultiple ?? true,\n });\n } else {\n // For 'mixed' or default, use pickImage first (can be extended to support both)\n result = await fileSystemProvider.pickImage({\n allowMultiple: options?.allowMultiple ?? true,\n });\n }\n\n // Handle cancelled picker\n if (result.status === \"cancelled\") {\n return [];\n }\n\n // Handle picker error\n if (result.status === \"error\") {\n console.error(\"Gallery selection error:\", result.error);\n options?.onError?.(result.error);\n return [];\n }\n\n // Success - add file and start upload\n const itemIds = uploadHook.addFiles([result]);\n await uploadHook.startUploads(itemIds);\n\n return itemIds;\n }, [\n fileSystemProvider,\n options?.allowMultiple,\n options?.mediaType,\n options?.onError,\n uploadHook,\n ]);\n\n return {\n ...uploadHook,\n selectAndUpload,\n };\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type { UploadMetrics } from \"../types\";\n\n/**\n * Hook for tracking upload performance metrics\n * @returns Metrics object and methods to track uploads\n */\nexport function useUploadMetrics() {\n const startTimeRef = useRef<number | null>(null);\n const startBytesRef = useRef<number>(0);\n const peakSpeedRef = useRef<number>(0);\n\n const [metrics, setMetrics] = useState<UploadMetrics>({\n totalBytes: 0,\n durationMs: 0,\n avgSpeed: 0,\n peakSpeed: 0,\n retries: 0,\n });\n\n // Start tracking\n const start = useCallback(() => {\n startTimeRef.current = Date.now();\n startBytesRef.current = 0;\n peakSpeedRef.current = 0;\n }, []);\n\n // Update metrics based on current progress\n const update = useCallback(\n (uploadedBytes: number, _totalBytes: number, currentRetries = 0) => {\n if (!startTimeRef.current) {\n return;\n }\n\n const now = Date.now();\n const durationMs = now - startTimeRef.current;\n const speed = durationMs > 0 ? (uploadedBytes / durationMs) * 1000 : 0;\n\n if (speed > peakSpeedRef.current) {\n peakSpeedRef.current = speed;\n }\n\n setMetrics({\n totalBytes: uploadedBytes,\n durationMs,\n avgSpeed: durationMs > 0 ? (uploadedBytes / durationMs) * 1000 : 0,\n peakSpeed: peakSpeedRef.current,\n retries: currentRetries,\n });\n },\n [],\n );\n\n // End tracking and return final metrics\n const end = useCallback(() => {\n const finalMetrics = metrics;\n startTimeRef.current = null;\n return finalMetrics;\n }, [metrics]);\n\n // Reset metrics\n const reset = useCallback(() => {\n startTimeRef.current = null;\n startBytesRef.current = 0;\n peakSpeedRef.current = 0;\n setMetrics({\n totalBytes: 0,\n durationMs: 0,\n avgSpeed: 0,\n peakSpeed: 0,\n retries: 0,\n });\n }, []);\n\n return {\n metrics,\n start,\n update,\n end,\n reset,\n };\n}\n","/**\n * File utility functions for React Native uploads\n */\n\n/**\n * Format file size to human readable string\n * @param bytes - Size in bytes\n * @returns Formatted size string (e.g., \"1.5 MB\")\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;\n}\n\n/**\n * Get MIME type from file name\n * @param fileName - File name with extension\n * @returns MIME type or 'application/octet-stream' as fallback\n */\nexport function getMimeTypeFromFileName(fileName: string): string {\n const mimeTypes: Record<string, string> = {\n // Images\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".bmp\": \"image/bmp\",\n \".webp\": \"image/webp\",\n \".svg\": \"image/svg+xml\",\n\n // Videos\n \".mp4\": \"video/mp4\",\n \".avi\": \"video/x-msvideo\",\n \".mov\": \"video/quicktime\",\n \".wmv\": \"video/x-ms-wmv\",\n \".flv\": \"video/x-flv\",\n \".mkv\": \"video/x-matroska\",\n \".webm\": \"video/webm\",\n\n // Audio\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n \".aac\": \"audio/aac\",\n \".flac\": \"audio/flac\",\n \".m4a\": \"audio/mp4\",\n\n // Documents\n \".pdf\": \"application/pdf\",\n \".doc\": \"application/msword\",\n \".docx\":\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \".xls\": \"application/vnd.ms-excel\",\n \".xlsx\":\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \".ppt\": \"application/vnd.ms-powerpoint\",\n \".pptx\":\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \".txt\": \"text/plain\",\n \".csv\": \"text/csv\",\n \".json\": \"application/json\",\n \".xml\": \"application/xml\",\n \".zip\": \"application/zip\",\n };\n\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return mimeTypes[ext] || \"application/octet-stream\";\n}\n\n/**\n * Check if file type is allowed\n * @param fileName - File name to check\n * @param allowedTypes - Array of allowed MIME types (e.g., ['image/jpeg', 'image/png'])\n * @returns True if file type is allowed\n */\nexport function isFileTypeAllowed(\n fileName: string,\n allowedTypes: string[],\n): boolean {\n if (!allowedTypes || allowedTypes.length === 0) {\n return true;\n }\n\n const mimeType = getMimeTypeFromFileName(fileName);\n return allowedTypes.some((allowed) => {\n if (allowed.endsWith(\"/*\")) {\n // Handle wildcard patterns like 'image/*'\n const [type] = allowed.split(\"/\");\n return mimeType.startsWith(`${type}/`);\n }\n return allowed === mimeType;\n });\n}\n\n/**\n * Check if file size is within limits\n * @param fileSize - File size in bytes\n * @param maxSize - Maximum allowed size in bytes (optional)\n * @param minSize - Minimum allowed size in bytes (optional)\n * @returns True if file size is within limits\n */\nexport function isFileSizeValid(\n fileSize: number,\n maxSize?: number,\n minSize?: number,\n): boolean {\n if (maxSize !== undefined && fileSize > maxSize) {\n return false;\n }\n\n if (minSize !== undefined && fileSize < minSize) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Get file extension\n * @param fileName - File name\n * @returns File extension without dot (e.g., 'pdf' for 'document.pdf')\n */\nexport function getFileExtension(fileName: string): string {\n const lastDot = fileName.lastIndexOf(\".\");\n if (lastDot === -1) return \"\";\n return fileName.slice(lastDot + 1).toLowerCase();\n}\n\n/**\n * Get file name without extension\n * @param fileName - File name\n * @returns File name without extension\n */\nexport function getFileNameWithoutExtension(fileName: string): string {\n const lastDot = fileName.lastIndexOf(\".\");\n if (lastDot === -1) return fileName;\n return fileName.slice(0, lastDot);\n}\n\n/**\n * Check if file is an image\n * @param fileName - File name\n * @returns True if file is an image\n */\nexport function isImageFile(fileName: string): boolean {\n const imageExtensions = [\n \".jpg\",\n \".jpeg\",\n \".png\",\n \".gif\",\n \".bmp\",\n \".webp\",\n \".svg\",\n ];\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return imageExtensions.includes(ext);\n}\n\n/**\n * Check if file is a video\n * @param fileName - File name\n * @returns True if file is a video\n */\nexport function isVideoFile(fileName: string): boolean {\n const videoExtensions = [\n \".mp4\",\n \".avi\",\n \".mov\",\n \".wmv\",\n \".flv\",\n \".mkv\",\n \".webm\",\n ];\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return videoExtensions.includes(ext);\n}\n\n/**\n * Check if file is a document\n * @param fileName - File name\n * @returns True if file is a document\n */\nexport function isDocumentFile(fileName: string): boolean {\n const docExtensions = [\n \".pdf\",\n \".doc\",\n \".docx\",\n \".xls\",\n \".xlsx\",\n \".ppt\",\n \".pptx\",\n \".txt\",\n \".csv\",\n ];\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return docExtensions.includes(ext);\n}\n","/**\n * Permission utility functions for React Native uploads\n * Handles camera, gallery, and file access permissions\n */\n\n/**\n * Permission types\n */\nexport enum PermissionType {\n CAMERA = \"CAMERA\",\n PHOTO_LIBRARY = \"PHOTO_LIBRARY\",\n WRITE_STORAGE = \"WRITE_STORAGE\",\n READ_STORAGE = \"READ_STORAGE\",\n}\n\n/**\n * Permission status\n */\nexport enum PermissionStatus {\n GRANTED = \"granted\",\n DENIED = \"denied\",\n NOT_DETERMINED = \"not_determined\",\n RESTRICTED = \"restricted\",\n}\n\n/**\n * Request camera permission\n * This is primarily used to check if we should attempt camera operations\n * Actual permission requests are handled by the file system providers\n *\n * @returns Promise resolving to true if permission is granted or already granted\n */\nexport async function requestCameraPermission(): Promise<boolean> {\n try {\n // Permission requests are handled by the file system provider implementations\n // This function serves as a placeholder for app-level permission handling\n console.log(\n \"Camera permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request camera permission:\", error);\n return false;\n }\n}\n\n/**\n * Request photo library permission\n * @returns Promise resolving to true if permission is granted\n */\nexport async function requestPhotoLibraryPermission(): Promise<boolean> {\n try {\n console.log(\n \"Photo library permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request photo library permission:\", error);\n return false;\n }\n}\n\n/**\n * Request storage read permission\n * @returns Promise resolving to true if permission is granted\n */\nexport async function requestStorageReadPermission(): Promise<boolean> {\n try {\n console.log(\n \"Storage read permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request storage read permission:\", error);\n return false;\n }\n}\n\n/**\n * Request storage write permission\n * @returns Promise resolving to true if permission is granted\n */\nexport async function requestStorageWritePermission(): Promise<boolean> {\n try {\n console.log(\n \"Storage write permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request storage write permission:\", error);\n return false;\n }\n}\n\n/**\n * Request multiple permissions at once\n * @param permissions - Array of permission types to request\n * @returns Promise resolving to true if all permissions are granted\n */\nexport async function requestPermissions(\n permissions: PermissionType[],\n): Promise<boolean> {\n try {\n const results = await Promise.all(\n permissions.map(async (permission) => {\n switch (permission) {\n case PermissionType.CAMERA:\n return requestCameraPermission();\n case PermissionType.PHOTO_LIBRARY:\n return requestPhotoLibraryPermission();\n case PermissionType.READ_STORAGE:\n return requestStorageReadPermission();\n case PermissionType.WRITE_STORAGE:\n return requestStorageWritePermission();\n default:\n return false;\n }\n }),\n );\n\n return results.every((result) => result);\n } catch (error) {\n console.error(\"Failed to request permissions:\", error);\n return false;\n }\n}\n\n/**\n * Check if all required permissions are granted\n * @param permissions - Array of permission types to check\n * @returns Promise resolving to true if all permissions are granted\n */\nexport async function hasPermissions(\n _permissions: PermissionType[],\n): Promise<boolean> {\n try {\n // In React Native, permission checking is typically handled by the platform\n // This is a placeholder that assumes permissions will be handled by the file system provider\n return true;\n } catch (error) {\n console.error(\"Failed to check permissions:\", error);\n return false;\n }\n}\n\n/**\n * Get permission status\n * @param permission - Permission type to check\n * @returns Promise resolving to permission status\n */\nexport async function getPermissionStatus(\n _permission: PermissionType,\n): Promise<PermissionStatus> {\n try {\n // This is a placeholder implementation\n // Real implementation would use platform-specific APIs\n return PermissionStatus.GRANTED;\n } catch (error) {\n console.error(\"Failed to get permission status:\", error);\n return PermissionStatus.DENIED;\n }\n}\n\n/**\n * Open app settings to request permissions\n * Guides user to app settings where they can manually enable permissions\n */\nexport function openAppSettings(): void {\n try {\n // This would typically use react-native-app-settings or similar\n console.log(\n \"Opening app settings (requires react-native-app-settings or platform implementation)\",\n );\n } catch (error) {\n console.error(\"Failed to open app settings:\", error);\n }\n}\n","/**\n * URI utility functions for React Native file handling\n */\n\n/**\n * Extract file name from URI\n * @param uri - File URI\n * @returns File name extracted from URI\n */\nexport function getFileNameFromUri(uri: string): string {\n try {\n // Handle different URI formats\n if (uri.startsWith(\"file://\")) {\n // File URI format\n const path = uri.replace(\"file://\", \"\");\n return path.split(\"/\").pop() || \"file\";\n }\n\n if (uri.startsWith(\"content://\")) {\n // Content URI format (Android)\n const parts = uri.split(\"/\");\n return parts[parts.length - 1] || \"file\";\n }\n\n // Assume it's a path or other format\n const parts = uri.split(\"/\");\n return parts[parts.length - 1] || \"file\";\n } catch {\n return \"file\";\n }\n}\n\n/**\n * Convert file path to file URI\n * @param filePath - File path\n * @returns File URI\n */\nexport function pathToUri(filePath: string): string {\n if (filePath.startsWith(\"file://\")) {\n return filePath;\n }\n\n if (filePath.startsWith(\"content://\")) {\n return filePath;\n }\n\n // Convert to file URI\n return `file://${filePath}`;\n}\n\n/**\n * Convert file URI to file path\n * @param uri - File URI\n * @returns File path\n */\nexport function uriToPath(uri: string): string {\n if (uri.startsWith(\"file://\")) {\n return uri.replace(\"file://\", \"\");\n }\n\n if (uri.startsWith(\"content://\")) {\n // Content URIs cannot be converted to paths directly\n return uri;\n }\n\n return uri;\n}\n\n/**\n * Get directory from URI\n * @param uri - File URI\n * @returns Directory path\n */\nexport function getDirectoryFromUri(uri: string): string {\n try {\n const path = uriToPath(uri);\n const parts = path.split(\"/\");\n parts.pop(); // Remove file name\n return parts.join(\"/\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Check if URI is a content URI (Android specific)\n * @param uri - URI to check\n * @returns True if URI is a content URI\n */\nexport function isContentUri(uri: string): boolean {\n return uri.startsWith(\"content://\");\n}\n\n/**\n * Check if URI is a file URI\n * @param uri - URI to check\n * @returns True if URI is a file URI\n */\nexport function isFileUri(uri: string): boolean {\n return uri.startsWith(\"file://\");\n}\n\n/**\n * Normalize URI for cross-platform compatibility\n * @param uri - URI to normalize\n * @returns Normalized URI\n */\nexport function normalizeUri(uri: string): string {\n // Remove duplicate slashes (but keep protocol slashes)\n return uri.replace(/([^:]\\/)\\/+/g, \"$1\");\n}\n\n/**\n * Get MIME type hint from URI\n * @param uri - File URI\n * @returns MIME type hint based on file extension\n */\nexport function getMimeTypeFromUri(uri: string): string {\n const fileName = getFileNameFromUri(uri);\n\n const mimeTypes: Record<string, string> = {\n // Images\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".bmp\": \"image/bmp\",\n \".webp\": \"image/webp\",\n\n // Videos\n \".mp4\": \"video/mp4\",\n \".mov\": \"video/quicktime\",\n \".avi\": \"video/x-msvideo\",\n\n // Audio\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n \".aac\": \"audio/aac\",\n\n // Documents\n \".pdf\": \"application/pdf\",\n \".txt\": \"text/plain\",\n \".json\": \"application/json\",\n };\n\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return mimeTypes[ext] || \"application/octet-stream\";\n}\n","import { ActivityIndicator, StyleSheet, Text, View } from \"react-native\";\nimport type { UploadState } from \"../hooks/use-upload\";\nimport { formatFileSize } from \"../utils\";\n\nexport interface UploadProgressProps {\n /** Upload state information */\n state: UploadState;\n /** Optional custom label */\n label?: string;\n}\n\n/**\n * Component to display upload progress with percentage, size, and speed\n */\nexport function UploadProgress({ state, label }: UploadProgressProps) {\n const getStatusColor = () => {\n switch (state.status) {\n case \"uploading\":\n return \"#007AFF\";\n case \"success\":\n return \"#34C759\";\n case \"error\":\n case \"aborted\":\n return \"#FF3B30\";\n default:\n return \"#999999\";\n }\n };\n\n const renderContent = () => {\n switch (state.status) {\n case \"idle\":\n return (\n <View style={styles.container}>\n <Text style={styles.label}>{label || \"Ready to upload\"}</Text>\n </View>\n );\n\n case \"uploading\":\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={styles.label}>{label || \"Uploading\"}</Text>\n <Text style={styles.percentage}>{state.progress}%</Text>\n </View>\n\n {/* Progress bar */}\n <View style={styles.progressBarContainer}>\n <View\n style={[\n styles.progressBar,\n {\n width: `${state.progress}%`,\n backgroundColor: getStatusColor(),\n },\n ]}\n />\n </View>\n\n {/* Details row */}\n <View style={styles.detailsRow}>\n <Text style={styles.detail}>\n {formatFileSize(state.bytesUploaded)} /{\" \"}\n {formatFileSize(state.totalBytes || 0)}\n </Text>\n </View>\n </View>\n );\n\n case \"success\":\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={[styles.label, { color: getStatusColor() }]}>\n {label || \"Upload complete\"}\n </Text>\n <Text style={[styles.percentage, { color: getStatusColor() }]}>\n ✓\n </Text>\n </View>\n <Text style={[styles.detail, { color: getStatusColor() }]}>\n {formatFileSize(state.totalBytes || 0)}\n </Text>\n </View>\n );\n\n case \"error\":\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={[styles.label, { color: getStatusColor() }]}>\n {label || \"Upload failed\"}\n </Text>\n <Text style={[styles.percentage, { color: getStatusColor() }]}>\n ✕\n </Text>\n </View>\n {state.error && (\n <Text style={[styles.detail, { color: getStatusColor() }]}>\n {state.error.message}\n </Text>\n )}\n </View>\n );\n\n case \"aborted\":\n return (\n <View style={styles.container}>\n <Text style={[styles.label, { color: getStatusColor() }]}>\n {label || \"Upload cancelled\"}\n </Text>\n </View>\n );\n\n default:\n return null;\n }\n };\n\n return (\n <View\n style={[\n styles.wrapper,\n {\n borderLeftColor: getStatusColor(),\n },\n ]}\n >\n {state.status === \"uploading\" && (\n <ActivityIndicator\n size=\"small\"\n color={getStatusColor()}\n style={styles.spinner}\n />\n )}\n {renderContent()}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n wrapper: {\n flexDirection: \"row\",\n alignItems: \"flex-start\",\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderLeftWidth: 4,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n gap: 8,\n },\n spinner: {\n marginTop: 4,\n },\n container: {\n flex: 1,\n gap: 4,\n },\n headerRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n label: {\n fontSize: 14,\n fontWeight: \"600\",\n color: \"#333333\",\n flex: 1,\n },\n percentage: {\n fontSize: 14,\n fontWeight: \"600\",\n color: \"#007AFF\",\n minWidth: 36,\n textAlign: \"right\",\n },\n progressBarContainer: {\n height: 4,\n backgroundColor: \"#e0e0e0\",\n borderRadius: 2,\n overflow: \"hidden\",\n },\n progressBar: {\n height: \"100%\",\n borderRadius: 2,\n },\n detailsRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n detail: {\n fontSize: 12,\n color: \"#666666\",\n },\n});\n","import { type ReactNode, useEffect } from \"react\";\nimport {\n ActivityIndicator,\n Pressable,\n StyleSheet,\n Text,\n View,\n} from \"react-native\";\nimport { useCameraUpload } from \"../hooks\";\nimport type { UseCameraUploadOptions } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface CameraUploadButtonProps {\n /** Options for camera upload */\n options?: UseCameraUploadOptions;\n /** Button label text */\n label?: string;\n /** Custom button content */\n children?: ReactNode;\n /** Callback when upload completes successfully */\n onSuccess?: (result: unknown) => void;\n /** Callback when upload fails */\n onError?: (error: Error) => void;\n /** Callback when upload is cancelled */\n onCancel?: () => void;\n /** Whether to show progress inline */\n showProgress?: boolean;\n}\n\n/**\n * Button component for camera capture and upload\n * Triggers camera on press and handles upload with progress display\n */\nexport function CameraUploadButton({\n options,\n label = \"Take Photo\",\n children,\n onSuccess,\n onError,\n onCancel,\n showProgress = true,\n}: CameraUploadButtonProps) {\n const { state, captureAndUpload } = useCameraUpload(options);\n\n const handlePress = async () => {\n try {\n await captureAndUpload();\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message.includes(\"cancelled\") ||\n error.message.includes(\"aborted\")\n ) {\n onCancel?.();\n } else {\n onError?.(error);\n }\n }\n }\n };\n\n const isLoading = state.status === \"uploading\";\n const isDisabled = isLoading || state.status === \"aborted\";\n\n useEffect(() => {\n if (state.status === \"success\" && state.result) {\n onSuccess?.(state.result);\n }\n }, [state.status, state.result, onSuccess]);\n\n useEffect(() => {\n if (state.status === \"error\" && state.error) {\n onError?.(state.error);\n }\n }, [state.status, state.error, onError]);\n\n return (\n <View style={styles.container}>\n <Pressable\n style={[styles.button, isDisabled && styles.buttonDisabled]}\n onPress={handlePress}\n disabled={isDisabled}\n >\n {isLoading && (\n <ActivityIndicator\n size=\"small\"\n color=\"#FFFFFF\"\n style={styles.spinner}\n />\n )}\n <Text style={styles.buttonText}>{children || label}</Text>\n </Pressable>\n {showProgress && state.status !== \"idle\" && (\n <View style={styles.progressContainer}>\n <UploadProgress state={state} label=\"Camera upload\" />\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n button: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n paddingVertical: 12,\n paddingHorizontal: 16,\n backgroundColor: \"#007AFF\",\n borderRadius: 8,\n gap: 8,\n },\n buttonDisabled: {\n opacity: 0.6,\n },\n buttonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FFFFFF\",\n },\n spinner: {\n marginRight: 4,\n },\n progressContainer: {\n marginTop: 4,\n },\n});\n","import { type ReactNode, useEffect } from \"react\";\nimport {\n ActivityIndicator,\n Pressable,\n StyleSheet,\n Text,\n View,\n} from \"react-native\";\nimport { useFileUpload } from \"../hooks\";\nimport type { UseFileUploadOptions } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface FileUploadButtonProps {\n /** Options for file upload */\n options?: UseFileUploadOptions;\n /** Button label text */\n label?: string;\n /** Custom button content */\n children?: ReactNode;\n /** Callback when upload completes successfully */\n onSuccess?: (result: unknown) => void;\n /** Callback when upload fails */\n onError?: (error: Error) => void;\n /** Callback when upload is cancelled */\n onCancel?: () => void;\n /** Whether to show progress inline */\n showProgress?: boolean;\n}\n\n/**\n * Button component for document/file selection and upload\n * Generic file picker with progress display\n */\nexport function FileUploadButton({\n options,\n label = \"Choose File\",\n children,\n onSuccess,\n onError,\n onCancel,\n showProgress = true,\n}: FileUploadButtonProps) {\n const { state, pickAndUpload } = useFileUpload(options);\n\n const handlePress = async () => {\n try {\n await pickAndUpload();\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message.includes(\"cancelled\") ||\n error.message.includes(\"aborted\")\n ) {\n onCancel?.();\n } else {\n onError?.(error);\n }\n }\n }\n };\n\n const isLoading = state.status === \"uploading\";\n const isDisabled = isLoading || state.status === \"aborted\";\n\n useEffect(() => {\n if (state.status === \"success\" && state.result) {\n onSuccess?.(state.result);\n }\n }, [state.status, state.result, onSuccess]);\n\n useEffect(() => {\n if (state.status === \"error\" && state.error) {\n onError?.(state.error);\n }\n }, [state.status, state.error, onError]);\n\n return (\n <View style={styles.container}>\n <Pressable\n style={[styles.button, isDisabled && styles.buttonDisabled]}\n onPress={handlePress}\n disabled={isDisabled}\n >\n {isLoading && (\n <ActivityIndicator\n size=\"small\"\n color=\"#FFFFFF\"\n style={styles.spinner}\n />\n )}\n <Text style={styles.buttonText}>{children || label}</Text>\n </Pressable>\n {showProgress && state.status !== \"idle\" && (\n <View style={styles.progressContainer}>\n <UploadProgress state={state} label=\"File upload\" />\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n button: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n paddingVertical: 12,\n paddingHorizontal: 16,\n backgroundColor: \"#FF9500\",\n borderRadius: 8,\n gap: 8,\n },\n buttonDisabled: {\n opacity: 0.6,\n },\n buttonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FFFFFF\",\n },\n spinner: {\n marginRight: 4,\n },\n progressContainer: {\n marginTop: 4,\n },\n});\n","import React, { type ReactNode } from \"react\";\nimport {\n ActivityIndicator,\n FlatList,\n Pressable,\n StyleSheet,\n Text,\n View,\n} from \"react-native\";\nimport { useGalleryUpload } from \"../hooks\";\nimport type { UseGalleryUploadOptions } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface GalleryUploadButtonProps {\n /** Options for gallery upload */\n options?: UseGalleryUploadOptions;\n /** Button label text */\n label?: string;\n /** Custom button content */\n children?: ReactNode;\n /** Callback when all uploads complete successfully */\n onSuccess?: (results: unknown[]) => void;\n /** Callback when any upload fails */\n onError?: (error: Error) => void;\n /** Callback when upload is cancelled */\n onCancel?: () => void;\n /** Whether to show individual progress for each file */\n showProgress?: boolean;\n}\n\n/**\n * Button component for gallery selection and batch upload\n * Triggers gallery picker on press and handles concurrent uploads\n */\nexport function GalleryUploadButton({\n options,\n label = \"Select from Gallery\",\n children,\n onSuccess,\n onError,\n onCancel,\n showProgress = true,\n}: GalleryUploadButtonProps) {\n const { state, selectAndUpload } = useGalleryUpload(options);\n\n const handlePress = async () => {\n try {\n await selectAndUpload();\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message.includes(\"cancelled\") ||\n error.message.includes(\"aborted\")\n ) {\n onCancel?.();\n } else {\n onError?.(error);\n }\n }\n }\n };\n\n const isLoading = state.items.some((item) => item.status === \"uploading\");\n const hasItems = state.items.length > 0;\n const allComplete =\n hasItems &&\n state.items.every(\n (item) => item.status !== \"uploading\" && item.status !== \"idle\",\n );\n\n React.useEffect(() => {\n if (allComplete) {\n const results = state.items\n .filter((item) => item.status === \"success\")\n .map((item) => item.result);\n if (results.length > 0) {\n onSuccess?.(results);\n }\n }\n }, [allComplete, state.items, onSuccess]);\n\n React.useEffect(() => {\n const errors = state.items.filter((item) => item.status === \"error\");\n const firstError = errors[0]?.error;\n if (firstError) {\n onError?.(firstError);\n }\n }, [state.items, onError]);\n\n const renderItem = ({ item }: { item: (typeof state.items)[0] }) => (\n <View key={item.id} style={styles.itemContainer}>\n <UploadProgress\n state={{\n status: item.status,\n progress: item.progress,\n bytesUploaded: item.bytesUploaded,\n totalBytes: item.totalBytes,\n error: item.error,\n result: item.result,\n }}\n label={item.file.data.name}\n />\n </View>\n );\n\n return (\n <View style={styles.container}>\n <Pressable\n style={[styles.button, isLoading && styles.buttonDisabled]}\n onPress={handlePress}\n disabled={isLoading}\n >\n {isLoading && (\n <ActivityIndicator\n size=\"small\"\n color=\"#FFFFFF\"\n style={styles.spinner}\n />\n )}\n <Text style={styles.buttonText}>\n {children || label}\n {hasItems && ` (${state.items.length})`}\n </Text>\n </Pressable>\n\n {hasItems && (\n <View style={styles.statsContainer}>\n <Text style={styles.statsText}>\n Progress: {state.items.filter((i) => i.status === \"success\").length}\n /{state.items.length} uploaded\n </Text>\n <Text style={styles.statsText}>Overall: {state.totalProgress}%</Text>\n </View>\n )}\n\n {showProgress && hasItems && (\n <FlatList\n scrollEnabled={false}\n data={state.items}\n renderItem={renderItem}\n keyExtractor={(item) => item.id}\n style={styles.listContainer}\n contentContainerStyle={styles.listContent}\n ItemSeparatorComponent={() => <View style={styles.separator} />}\n />\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n button: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n paddingVertical: 12,\n paddingHorizontal: 16,\n backgroundColor: \"#34C759\",\n borderRadius: 8,\n gap: 8,\n },\n buttonDisabled: {\n opacity: 0.6,\n },\n buttonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FFFFFF\",\n },\n spinner: {\n marginRight: 4,\n },\n statsContainer: {\n paddingVertical: 8,\n paddingHorizontal: 12,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n gap: 4,\n },\n statsText: {\n fontSize: 12,\n color: \"#666666\",\n },\n listContainer: {\n maxHeight: 400,\n },\n listContent: {\n gap: 8,\n },\n itemContainer: {\n paddingHorizontal: 0,\n },\n separator: {\n height: 4,\n },\n});\n","import { FlatList, Pressable, StyleSheet, Text, View } from \"react-native\";\nimport type { UploadItem } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface UploadListProps {\n /** List of upload items to display */\n items: UploadItem[];\n /** Callback when remove item is pressed */\n onRemove?: (id: string) => void;\n /** Callback when item is pressed */\n onItemPress?: (item: UploadItem) => void;\n /** Whether to show remove button */\n showRemoveButton?: boolean;\n}\n\n/**\n * Component to display a list of upload items with individual progress\n * Shows status indicators and allows removal of items\n */\nexport function UploadList({\n items,\n onRemove,\n onItemPress,\n showRemoveButton = true,\n}: UploadListProps) {\n const renderItem = ({ item }: { item: UploadItem }) => (\n <Pressable\n style={[\n styles.itemContainer,\n { borderLeftColor: getStatusColor(item.progress.state) },\n ]}\n onPress={() => onItemPress?.(item)}\n >\n <View style={styles.itemContent}>\n {item.file.status === \"success\" && (\n <View style={styles.itemHeader}>\n <Text style={styles.fileName} numberOfLines={1}>\n {item.file.data.name}\n </Text>\n <Text style={styles.fileSize}>\n {getFileSizeDisplay(item.file.data.size)}\n </Text>\n </View>\n )}\n {item.file.status === \"error\" && (\n <Text style={styles.errorText}>{item.progress.error?.message}</Text>\n )}\n <View style={styles.progressWrapper}>\n <UploadProgress\n state={{\n status:\n item.progress.state === \"pending\"\n ? \"idle\"\n : item.progress.state === \"cancelled\"\n ? \"aborted\"\n : item.progress.state,\n progress: item.progress.progress,\n bytesUploaded: item.progress.uploadedBytes,\n totalBytes: item.progress.totalBytes,\n error: item.progress.error || null,\n result: item.result ?? null,\n }}\n />\n </View>\n </View>\n {showRemoveButton &&\n item.progress.state !== \"uploading\" &&\n item.progress.state !== \"pending\" && (\n <Pressable\n style={styles.removeButton}\n onPress={() => onRemove?.(item.id)}\n hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}\n >\n <Text style={styles.removeButtonText}>✕</Text>\n </Pressable>\n )}\n </Pressable>\n );\n\n if (items.length === 0) {\n return (\n <View style={styles.emptyContainer}>\n <Text style={styles.emptyText}>No uploads</Text>\n </View>\n );\n }\n\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={styles.headerText}>Uploads ({items.length})</Text>\n <Text style={styles.headerSubtext}>\n {items.filter((i) => i.progress.state === \"success\").length} complete\n </Text>\n </View>\n <FlatList\n scrollEnabled={false}\n data={items}\n renderItem={renderItem}\n keyExtractor={(item) => item.id}\n ItemSeparatorComponent={() => <View style={styles.separator} />}\n contentContainerStyle={styles.listContent}\n />\n </View>\n );\n}\n\n// Helper functions\nfunction getStatusColor(state: string): string {\n switch (state) {\n case \"success\":\n return \"#34C759\";\n case \"error\":\n case \"cancelled\":\n return \"#FF3B30\";\n case \"uploading\":\n case \"pending\":\n return \"#007AFF\";\n default:\n return \"#999999\";\n }\n}\n\nfunction getFileSizeDisplay(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${Math.round((bytes / k ** i) * 10) / 10} ${sizes[i]}`;\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n headerRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n paddingHorizontal: 12,\n paddingVertical: 8,\n backgroundColor: \"#f9f9f9\",\n borderRadius: 4,\n },\n headerText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#333333\",\n },\n errorText: {\n fontSize: 14,\n color: \"#FF3B30\",\n },\n headerSubtext: {\n fontSize: 14,\n color: \"#666666\",\n },\n listContent: {\n gap: 8,\n },\n itemContainer: {\n flexDirection: \"row\",\n alignItems: \"center\",\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderLeftWidth: 4,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n gap: 8,\n },\n itemContent: {\n flex: 1,\n gap: 6,\n },\n itemHeader: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n fileName: {\n fontSize: 14,\n fontWeight: \"500\",\n color: \"#333333\",\n flex: 1,\n },\n fileSize: {\n fontSize: 12,\n color: \"#999999\",\n marginLeft: 8,\n },\n progressWrapper: {\n marginTop: 2,\n },\n removeButton: {\n width: 32,\n height: 32,\n justifyContent: \"center\",\n alignItems: \"center\",\n borderRadius: 16,\n backgroundColor: \"#FFE5E5\",\n },\n removeButtonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FF3B30\",\n },\n separator: {\n height: 4,\n },\n emptyContainer: {\n paddingVertical: 24,\n paddingHorizontal: 12,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n emptyText: {\n fontSize: 14,\n color: \"#999999\",\n fontStyle: \"italic\",\n },\n});\n","import type {\n FlowUploadState,\n FlowUploadStatus,\n InputExecutionState,\n} from \"@uploadista/client-core\";\nimport type { TypedOutput } from \"@uploadista/core/flow\";\nimport { createContext, type ReactNode, useCallback, useContext } from \"react\";\nimport {\n type FlowInputMetadata,\n type UseFlowOptions,\n useFlow,\n} from \"../hooks/use-flow\";\nimport { useUploadistaContext } from \"../hooks/use-uploadista-context\";\nimport type { FilePickResult } from \"../types\";\n\n// Re-export types for convenience\nexport type {\n FlowUploadState,\n FlowUploadStatus,\n InputExecutionState,\n FlowInputMetadata,\n};\n\n// ============ FLOW CONTEXT ============\n\n/**\n * Context value provided by the Flow component root.\n * Contains all flow state and actions.\n */\nexport interface FlowContextValue {\n /** Current upload state */\n state: FlowUploadState;\n /** Discovered input nodes metadata (null until discovery completes) */\n inputMetadata: FlowInputMetadata[] | null;\n /** Current input values set via setInput() */\n inputs: Record<string, unknown>;\n /** Per-input execution state for multi-input flows */\n inputStates: ReadonlyMap<string, InputExecutionState>;\n\n /** Set an input value for a specific node */\n setInput: (nodeId: string, value: unknown) => void;\n /** Execute the flow with current inputs */\n execute: () => Promise<void>;\n /** Upload a single file through the flow */\n upload: (file: FilePickResult) => Promise<void>;\n /** Abort the current upload */\n abort: () => void;\n /** Reset the upload state and clear all inputs */\n reset: () => void;\n\n /** Whether an upload or flow execution is in progress */\n isActive: boolean;\n /** Whether the file is currently being uploaded */\n isUploadingFile: boolean;\n /** Whether the flow is currently processing */\n isProcessing: boolean;\n /** Whether the hook is discovering flow inputs */\n isDiscoveringInputs: boolean;\n\n /** Pick a file and set it for a specific input node */\n pickFileForInput: (nodeId: string) => Promise<void>;\n /** Pick a file and start upload immediately (single-file flows) */\n pickAndUpload: () => Promise<void>;\n}\n\nconst FlowContext = createContext<FlowContextValue | null>(null);\n\n/**\n * Hook to access flow context from within a Flow component.\n * @throws Error if used outside of a Flow component\n */\nexport function useFlowContext(): FlowContextValue {\n const context = useContext(FlowContext);\n if (!context) {\n throw new Error(\n \"useFlowContext must be used within a <Flow> component. \" +\n 'Wrap your component tree with <Flow flowId=\"...\" storageId=\"...\">',\n );\n }\n return context;\n}\n\n// ============ FLOW INPUT CONTEXT ============\n\n/**\n * Context value for a specific input node within a Flow.\n */\nexport interface FlowInputContextValue {\n /** Input node ID */\n nodeId: string;\n /** Input metadata from flow discovery */\n metadata: FlowInputMetadata;\n /** Current value for this input */\n value: unknown;\n /** Set the value for this input */\n setValue: (value: unknown) => void;\n /** Per-input execution state (if available) */\n state: InputExecutionState | undefined;\n /** Pick a file for this input */\n pickFile: () => Promise<void>;\n}\n\nconst FlowInputContext = createContext<FlowInputContextValue | null>(null);\n\n/**\n * Hook to access flow input context from within a Flow.Input component.\n * @throws Error if used outside of a Flow.Input component\n */\nexport function useFlowInputContext(): FlowInputContextValue {\n const context = useContext(FlowInputContext);\n if (!context) {\n throw new Error(\n \"useFlowInputContext must be used within a <Flow.Input> component. \" +\n 'Wrap your component with <Flow.Input nodeId=\"...\">',\n );\n }\n return context;\n}\n\n// ============ FLOW ROOT COMPONENT ============\n\n/**\n * Render props for the Flow root component.\n */\nexport interface FlowRenderProps extends FlowContextValue {\n /** Alias for execute() */\n submit: () => Promise<void>;\n /** Alias for abort() */\n cancel: () => void;\n}\n\n/**\n * Props for the Flow root component.\n */\nexport interface FlowProps {\n /** Flow ID to execute */\n flowId: string;\n /** Storage ID for file uploads */\n storageId: string;\n /** Optional output node ID to wait for */\n outputNodeId?: string;\n /** Optional metadata to include with the flow execution */\n metadata?: Record<string, string>;\n /** Called when flow completes successfully */\n onSuccess?: (outputs: TypedOutput[]) => void;\n /** Called when flow fails */\n onError?: (error: Error) => void;\n /** Called on upload progress */\n onProgress?: (\n progress: number,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n /** Called when flow completes with all outputs */\n onFlowComplete?: (outputs: TypedOutput[]) => void;\n /** Children to render (can be render function or ReactNode) */\n children: ReactNode | ((props: FlowRenderProps) => ReactNode);\n}\n\n/**\n * Root component for flow-based uploads on React Native.\n * Provides context for all Flow sub-components.\n *\n * @example\n * ```tsx\n * <Flow flowId=\"image-optimizer\" storageId=\"s3\" onSuccess={handleSuccess}>\n * <Flow.Inputs>\n * {({ inputs, isLoading }) => (\n * inputs.map(input => (\n * <Flow.Input key={input.nodeId} nodeId={input.nodeId}>\n * {({ metadata, pickFile }) => (\n * <Button onPress={pickFile} title={metadata.nodeName} />\n * )}\n * </Flow.Input>\n * ))\n * )}\n * </Flow.Inputs>\n * <Flow.Submit>\n * <Text>Process</Text>\n * </Flow.Submit>\n * </Flow>\n * ```\n */\nfunction FlowRoot({\n flowId,\n storageId,\n outputNodeId,\n metadata,\n onSuccess,\n onError,\n onProgress,\n onFlowComplete,\n children,\n}: FlowProps) {\n const { fileSystemProvider } = useUploadistaContext();\n\n const options: UseFlowOptions = {\n flowId,\n storageId,\n outputNodeId,\n metadata,\n onSuccess,\n onError,\n onProgress,\n onFlowComplete,\n };\n\n const flow = useFlow(options);\n\n // Pick a file for a specific input node\n const pickFileForInput = useCallback(\n async (nodeId: string) => {\n if (!fileSystemProvider?.pickDocument) {\n throw new Error(\"File picker not available\");\n }\n const result = await fileSystemProvider.pickDocument();\n if (result.status === \"success\") {\n flow.setInput(nodeId, result);\n }\n },\n [fileSystemProvider, flow],\n );\n\n // Pick a file and start upload immediately\n const pickAndUpload = useCallback(async () => {\n if (!fileSystemProvider?.pickDocument) {\n throw new Error(\"File picker not available\");\n }\n const result = await fileSystemProvider.pickDocument();\n if (result.status === \"success\") {\n await flow.upload(result);\n }\n }, [fileSystemProvider, flow]);\n\n const contextValue: FlowContextValue = {\n state: flow.state,\n inputMetadata: flow.inputMetadata,\n inputs: flow.inputs,\n inputStates: flow.inputStates,\n setInput: flow.setInput,\n execute: flow.execute,\n upload: flow.upload,\n abort: flow.abort,\n reset: flow.reset,\n isActive: flow.isActive,\n isUploadingFile: flow.isUploadingFile,\n isProcessing: flow.isProcessing,\n isDiscoveringInputs: flow.isDiscoveringInputs,\n pickFileForInput,\n pickAndUpload,\n };\n\n const renderProps: FlowRenderProps = {\n ...contextValue,\n submit: flow.execute,\n cancel: flow.abort,\n };\n\n return (\n <FlowContext.Provider value={contextValue}>\n {typeof children === \"function\" ? children(renderProps) : children}\n </FlowContext.Provider>\n );\n}\n\n// ============ INPUTS DISCOVERY PRIMITIVE ============\n\n/**\n * Render props for Flow.Inputs component.\n */\nexport interface FlowInputsRenderProps {\n /** Discovered input metadata */\n inputs: FlowInputMetadata[];\n /** Whether inputs are still being discovered */\n isLoading: boolean;\n}\n\n/**\n * Props for Flow.Inputs component.\n */\nexport interface FlowInputsProps {\n /** Render function receiving discovered inputs */\n children: (props: FlowInputsRenderProps) => ReactNode;\n}\n\n/**\n * Auto-discovers flow input nodes and provides them via render props.\n *\n * @example\n * ```tsx\n * <Flow.Inputs>\n * {({ inputs, isLoading }) => (\n * isLoading ? <ActivityIndicator /> : (\n * inputs.map(input => (\n * <Flow.Input key={input.nodeId} nodeId={input.nodeId}>\n * ...\n * </Flow.Input>\n * ))\n * )\n * )}\n * </Flow.Inputs>\n * ```\n */\nfunction FlowInputs({ children }: FlowInputsProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowInputsRenderProps = {\n inputs: flow.inputMetadata ?? [],\n isLoading: flow.isDiscoveringInputs,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ INPUT PRIMITIVE ============\n\n/**\n * Props for Flow.Input component.\n */\nexport interface FlowInputProps {\n /** Input node ID */\n nodeId: string;\n /** Children (can be render function or regular children) */\n children: ReactNode | ((props: FlowInputContextValue) => ReactNode);\n}\n\n/**\n * Scoped input context provider for a specific input node.\n * Children can access input-specific state via useFlowInputContext().\n *\n * @example\n * ```tsx\n * <Flow.Input nodeId=\"video-input\">\n * {({ metadata, value, pickFile }) => (\n * <View>\n * <Text>{metadata.nodeName}</Text>\n * <Button onPress={pickFile} title=\"Select File\" />\n * {value && <Text>Selected: {value.data?.name}</Text>}\n * </View>\n * )}\n * </Flow.Input>\n * ```\n */\nfunction FlowInput({ nodeId, children }: FlowInputProps) {\n const flow = useFlowContext();\n\n const metadata = flow.inputMetadata?.find((m) => m.nodeId === nodeId);\n\n if (!metadata) {\n // Input not yet discovered or doesn't exist\n return null;\n }\n\n const pickFile = async () => {\n await flow.pickFileForInput(nodeId);\n };\n\n const contextValue: FlowInputContextValue = {\n nodeId,\n metadata,\n value: flow.inputs[nodeId],\n setValue: (value) => flow.setInput(nodeId, value),\n state: flow.inputStates.get(nodeId),\n pickFile,\n };\n\n return (\n <FlowInputContext.Provider value={contextValue}>\n {typeof children === \"function\" ? children(contextValue) : children}\n </FlowInputContext.Provider>\n );\n}\n\n// ============ INPUT FILE PICKER PRIMITIVE ============\n\n/**\n * Render props for Flow.Input.FilePicker component.\n */\nexport interface FlowInputFilePickerRenderProps {\n /** Current value for this input */\n value: unknown;\n /** Whether a file is selected */\n hasFile: boolean;\n /** File name (if value is FilePickResult) */\n fileName: string | null;\n /** File size in bytes (if value is FilePickResult) */\n fileSize: number | null;\n /** Per-input progress (if available) */\n progress: number;\n /** Per-input status (if available) */\n status: string;\n /** Open file picker */\n pickFile: () => Promise<void>;\n /** Clear the input value */\n clear: () => void;\n}\n\n/**\n * Props for Flow.Input.FilePicker component.\n */\nexport interface FlowInputFilePickerProps {\n /** Render function receiving file picker state */\n children: (props: FlowInputFilePickerRenderProps) => ReactNode;\n}\n\n/**\n * File picker for a specific input within a Flow.Input.\n * Sets the input value but does NOT trigger upload until Flow.Submit is pressed.\n */\nfunction FlowInputFilePicker({ children }: FlowInputFilePickerProps) {\n const input = useFlowInputContext();\n\n // Check if value is a FilePickResult\n const fileResult = input.value as FilePickResult | undefined;\n const hasFile = fileResult?.status === \"success\";\n const fileName = hasFile ? (fileResult?.data?.name ?? null) : null;\n const fileSize = hasFile ? (fileResult?.data?.size ?? null) : null;\n\n const renderProps: FlowInputFilePickerRenderProps = {\n value: input.value,\n hasFile,\n fileName,\n fileSize,\n progress: input.state?.progress ?? 0,\n status: input.state?.status ?? \"idle\",\n pickFile: input.pickFile,\n clear: () => input.setValue(undefined),\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ INPUT PREVIEW PRIMITIVE ============\n\n/**\n * Render props for Flow.Input.Preview component.\n */\nexport interface FlowInputPreviewRenderProps {\n /** Current value */\n value: unknown;\n /** Whether a file is selected */\n hasFile: boolean;\n /** Whether value is a URL string */\n isUrl: boolean;\n /** File name (if value is FilePickResult) */\n fileName: string | null;\n /** File size in bytes (if value is FilePickResult) */\n fileSize: number | null;\n /** File URI (if value is FilePickResult) */\n fileUri: string | null;\n /** Clear the input value */\n clear: () => void;\n}\n\n/**\n * Props for Flow.Input.Preview component.\n */\nexport interface FlowInputPreviewProps {\n /** Render function receiving preview state */\n children: (props: FlowInputPreviewRenderProps) => ReactNode;\n}\n\n/**\n * Preview component for showing the selected value within a Flow.Input.\n */\nfunction FlowInputPreview({ children }: FlowInputPreviewProps) {\n const input = useFlowInputContext();\n\n // Check if value is a FilePickResult\n const fileResult = input.value as FilePickResult | undefined;\n const hasFile = fileResult?.status === \"success\";\n const isUrl =\n typeof input.value === \"string\" && (input.value as string).length > 0;\n\n const renderProps: FlowInputPreviewRenderProps = {\n value: input.value,\n hasFile,\n isUrl,\n fileName: hasFile ? (fileResult?.data?.name ?? null) : null,\n fileSize: hasFile ? (fileResult?.data?.size ?? null) : null,\n fileUri: hasFile ? (fileResult?.data?.uri ?? null) : null,\n clear: () => input.setValue(undefined),\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ PROGRESS PRIMITIVE ============\n\n/**\n * Render props for Flow.Progress component.\n */\nexport interface FlowProgressRenderProps {\n /** Progress percentage (0-100) */\n progress: number;\n /** Bytes uploaded so far */\n bytesUploaded: number;\n /** Total bytes to upload (null if unknown) */\n totalBytes: number | null;\n /** Current status */\n status: FlowUploadStatus;\n}\n\n/**\n * Props for Flow.Progress component.\n */\nexport interface FlowProgressProps {\n /** Render function receiving progress state */\n children: (props: FlowProgressRenderProps) => ReactNode;\n}\n\n/**\n * Progress display component within a Flow.\n */\nfunction FlowProgress({ children }: FlowProgressProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowProgressRenderProps = {\n progress: flow.state.progress,\n bytesUploaded: flow.state.bytesUploaded,\n totalBytes: flow.state.totalBytes,\n status: flow.state.status,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ STATUS PRIMITIVE ============\n\n/**\n * Render props for Flow.Status component.\n */\nexport interface FlowStatusRenderProps {\n /** Current status */\n status: FlowUploadStatus;\n /** Current node being processed (if any) */\n currentNodeName: string | null;\n /** Current node type (if any) */\n currentNodeType: string | null;\n /** Error (if status is error) */\n error: Error | null;\n /** Job ID (if started) */\n jobId: string | null;\n /** Whether flow has started */\n flowStarted: boolean;\n /** Flow outputs (if completed) */\n flowOutputs: TypedOutput[] | null;\n}\n\n/**\n * Props for Flow.Status component.\n */\nexport interface FlowStatusProps {\n /** Render function receiving status state */\n children: (props: FlowStatusRenderProps) => ReactNode;\n}\n\n/**\n * Status display component within a Flow.\n */\nfunction FlowStatus({ children }: FlowStatusProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowStatusRenderProps = {\n status: flow.state.status,\n currentNodeName: flow.state.currentNodeName,\n currentNodeType: flow.state.currentNodeType,\n error: flow.state.error,\n jobId: flow.state.jobId,\n flowStarted: flow.state.flowStarted,\n flowOutputs: flow.state.flowOutputs,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ERROR PRIMITIVE ============\n\n/**\n * Render props for Flow.Error component.\n */\nexport interface FlowErrorRenderProps {\n /** Error object (null if no error) */\n error: Error | null;\n /** Whether there is an error */\n hasError: boolean;\n /** Error message */\n message: string | null;\n /** Reset the flow */\n reset: () => void;\n}\n\n/**\n * Props for Flow.Error component.\n */\nexport interface FlowErrorProps {\n /** Render function receiving error state */\n children: (props: FlowErrorRenderProps) => ReactNode;\n}\n\n/**\n * Error display component within a Flow.\n */\nfunction FlowError({ children }: FlowErrorProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowErrorRenderProps = {\n error: flow.state.error,\n hasError: flow.state.status === \"error\",\n message: flow.state.error?.message ?? null,\n reset: flow.reset,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ACTION PRIMITIVES ============\n\n/**\n * Render props for Flow.Submit component.\n */\nexport interface FlowSubmitRenderProps {\n /** Execute the flow */\n submit: () => Promise<void>;\n /** Whether the button should be disabled */\n isDisabled: boolean;\n /** Whether currently submitting */\n isSubmitting: boolean;\n}\n\n/**\n * Props for Flow.Submit component.\n */\nexport interface FlowSubmitProps {\n /** Render function receiving submit state */\n children: ReactNode | ((props: FlowSubmitRenderProps) => ReactNode);\n /** Additional disabled state */\n disabled?: boolean;\n}\n\n/**\n * Submit primitive that executes the flow with current inputs.\n * Provides render props for building custom submit buttons.\n * Automatically disabled when uploading.\n */\nfunction FlowSubmit({ children, disabled }: FlowSubmitProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowSubmitRenderProps = {\n submit: flow.execute,\n isDisabled: disabled || flow.isActive || Object.keys(flow.inputs).length === 0,\n isSubmitting: flow.isActive,\n };\n\n return <>{typeof children === \"function\" ? children(renderProps) : children}</>;\n}\n\n/**\n * Render props for Flow.Cancel component.\n */\nexport interface FlowCancelRenderProps {\n /** Cancel the flow */\n cancel: () => void;\n /** Whether the button should be disabled */\n isDisabled: boolean;\n}\n\n/**\n * Props for Flow.Cancel component.\n */\nexport interface FlowCancelProps {\n /** Render function receiving cancel state */\n children: ReactNode | ((props: FlowCancelRenderProps) => ReactNode);\n}\n\n/**\n * Cancel primitive that aborts the current upload.\n */\nfunction FlowCancel({ children }: FlowCancelProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowCancelRenderProps = {\n cancel: flow.abort,\n isDisabled: !flow.isActive,\n };\n\n return <>{typeof children === \"function\" ? children(renderProps) : children}</>;\n}\n\n/**\n * Render props for Flow.Reset component.\n */\nexport interface FlowResetRenderProps {\n /** Reset the flow */\n reset: () => void;\n /** Whether the button should be disabled */\n isDisabled: boolean;\n}\n\n/**\n * Props for Flow.Reset component.\n */\nexport interface FlowResetProps {\n /** Render function receiving reset state */\n children: ReactNode | ((props: FlowResetRenderProps) => ReactNode);\n}\n\n/**\n * Reset primitive that clears all inputs and resets to idle state.\n */\nfunction FlowReset({ children }: FlowResetProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowResetRenderProps = {\n reset: flow.reset,\n isDisabled: flow.isActive,\n };\n\n return <>{typeof children === \"function\" ? children(renderProps) : children}</>;\n}\n\n// ============ QUICK UPLOAD PRIMITIVE ============\n\n/**\n * Render props for Flow.QuickUpload component.\n */\nexport interface FlowQuickUploadRenderProps {\n /** Whether currently uploading */\n isUploading: boolean;\n /** Progress percentage (0-100) */\n progress: number;\n /** Current status */\n status: FlowUploadStatus;\n /** Pick a file and start upload immediately */\n pickAndUpload: () => Promise<void>;\n /** Abort the current upload */\n abort: () => void;\n}\n\n/**\n * Props for Flow.QuickUpload component.\n */\nexport interface FlowQuickUploadProps {\n /** Render function receiving quick upload state */\n children: (props: FlowQuickUploadRenderProps) => ReactNode;\n}\n\n/**\n * Quick upload component for single-file flows.\n * Picks a file and starts upload immediately.\n *\n * @example\n * ```tsx\n * <Flow.QuickUpload>\n * {({ isUploading, progress, pickAndUpload, abort }) => (\n * <Button\n * onPress={isUploading ? abort : pickAndUpload}\n * title={isUploading ? `Uploading ${progress}%` : 'Upload File'}\n * />\n * )}\n * </Flow.QuickUpload>\n * ```\n */\nfunction FlowQuickUpload({ children }: FlowQuickUploadProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowQuickUploadRenderProps = {\n isUploading: flow.isActive,\n progress: flow.state.progress,\n status: flow.state.status,\n pickAndUpload: flow.pickAndUpload,\n abort: flow.abort,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ COMPOUND COMPONENT EXPORT ============\n\n/**\n * Flow compound component for flow-based file uploads on React Native.\n *\n * Provides a composable, headless API for building flow upload interfaces.\n * All sub-components use render props for complete UI control.\n *\n * @example Quick Upload (Single File)\n * ```tsx\n * <Flow flowId=\"image-optimizer\" storageId=\"s3\" onSuccess={handleSuccess}>\n * <Flow.QuickUpload>\n * {({ isUploading, progress, pickAndUpload, abort }) => (\n * <View>\n * <Button\n * onPress={isUploading ? abort : pickAndUpload}\n * title={isUploading ? 'Cancel' : 'Upload Image'}\n * />\n * {isUploading && <Text>{progress}%</Text>}\n * </View>\n * )}\n * </Flow.QuickUpload>\n * </Flow>\n * ```\n *\n * @example Multi-Input Flow\n * ```tsx\n * <Flow flowId=\"video-processor\" storageId=\"s3\">\n * <Flow.Inputs>\n * {({ inputs, isLoading }) => (\n * isLoading ? <ActivityIndicator /> : inputs.map(input => (\n * <Flow.Input key={input.nodeId} nodeId={input.nodeId}>\n * {({ metadata, pickFile, value }) => (\n * <View>\n * <Text>{metadata.nodeName}</Text>\n * <Button onPress={pickFile} title=\"Select File\" />\n * <Flow.Input.Preview>\n * {({ hasFile, fileName }) => hasFile && <Text>{fileName}</Text>}\n * </Flow.Input.Preview>\n * </View>\n * )}\n * </Flow.Input>\n * ))\n * )}\n * </Flow.Inputs>\n * <CustomSubmitButton />\n * </Flow>\n * ```\n */\nexport const Flow = Object.assign(FlowRoot, {\n Inputs: FlowInputs,\n Input: Object.assign(FlowInput, {\n FilePicker: FlowInputFilePicker,\n Preview: FlowInputPreview,\n }),\n Progress: FlowProgress,\n Status: FlowStatus,\n Error: FlowError,\n Submit: FlowSubmit,\n Cancel: FlowCancel,\n Reset: FlowReset,\n QuickUpload: FlowQuickUpload,\n});\n"],"mappings":"ydAcA,MAAa,EAAoB,EAE/B,IAAA,GAAU,CCgFZ,SAAgB,EACd,EACA,EACM,CAEN,IAAM,EAAa,aAAgB,YAAc,IAAI,WAAW,EAAK,CAAG,EAKxE,OAAO,IADiB,KACG,CAAC,EAAW,CAAE,EAAQ,CCjGnD,SAAgB,GAAuB,CACrC,IAAM,EAAU,EAAW,EAAkB,CAE7C,GAAI,CAAC,EACH,MAAU,MACR,gEACD,CAGH,OAAO,ECiGT,MAAMA,EAA4B,CAChC,OAAQ,OACR,SAAU,EACV,cAAe,EACf,WAAY,KACZ,MAAO,KACP,OAAQ,KACT,CAqCD,SAAgB,EAAU,EAA4B,EAAE,CAAmB,CACzE,GAAM,CAAE,SAAQ,sBAAuB,GAAsB,CACvD,CAAC,EAAO,GAAY,EAAsBC,EAAa,CACvD,EAAa,EAA6B,KAAK,CAC/C,EAAc,EAA8B,KAAK,CAoFvD,OAjFA,OAqBE,EAAW,QAAU,IAAI,EAnBR,MAAO,EAAgB,IAAkC,CACxE,IAAM,EAAO,EAEb,GAAI,EAAK,SAAW,UAAW,CAK7B,IAAM,EAAO,EAHO,MAAM,EAAmB,SAAS,EAAK,KAAK,IAAI,CAGrB,CAC7C,KAAM,EAAK,KAAK,UAAY,2BAC7B,CAAC,CAGF,OAAO,EAAO,OAAO,EAAM,EAAK,CAGlC,OAAO,QAAQ,QAAQ,CAAE,UAAa,GAAI,CAAC,EAK3C,CACE,cAAe,EACf,WAAY,EAAQ,WACpB,gBAAiB,EAAQ,gBACzB,UAAW,EAAQ,UACnB,QAAS,EAAQ,QACjB,QAAS,EAAQ,QAClB,CACD,CACE,SAAU,EAAQ,SAClB,qBAAsB,EAAQ,qBAC9B,WAAY,EAAQ,WACpB,cAAe,EAAQ,cACxB,CACF,KAEY,CACX,EAAW,SAAS,SAAS,GAE9B,CAAC,EAAQ,EAAoB,EAAQ,CAAC,CAuClC,CACL,QACA,OAtCa,EAAY,KAAO,IAAyB,CACzD,EAAY,QAAU,EACtB,MAAM,EAAW,SAAS,OAAO,EAAK,EACrC,EAAE,CAAC,CAoCJ,MAjCY,MAAkB,CAC9B,EAAW,SAAS,OAAO,EAC1B,EAAE,CAAC,CAgCJ,MA7BY,MAAkB,CAC9B,EAAW,SAAS,OAAO,CAC3B,EAAY,QAAU,MACrB,EAAE,CAAC,CA2BJ,MAxBY,MAAkB,CAC1B,EAAY,SAAW,EAAW,SAAS,UAAU,EACvD,EAAW,QAAQ,OAAO,EAE3B,EAAE,CAAC,CAqBJ,YAlBkB,EAAM,SAAW,YAmBnC,SAlBe,EAAW,SAAS,UAAU,EAAI,GAmBjD,QAhB6B,CAC7B,gBAAmB,EAAO,qBAAqB,CAC/C,kBAAqB,EAAO,eAAe,CAC3C,sBAAyB,EAAO,mBAAmB,CACnD,wBAA2B,EAAO,qBAAqB,CACvD,iBAAoB,EAAO,cAAc,CAC1C,CAWA,CCrPH,SAAgB,EAAgB,EAAkC,CAChE,GAAM,CAAE,sBAAuB,GAAsB,CAC/C,EAAa,EAAU,CAC3B,SAAU,GAAS,SACnB,UAAW,GAAS,UACpB,QAAS,GAAS,QAClB,WAAY,GAAS,WACtB,CAAC,CAGI,EAAmB,EAAY,SAAY,CAC/C,GAAI,CAEF,IAAM,EAAQ,MAAM,EAAmB,WAAW,GAAS,cAAc,CAGzE,MAAM,EAAW,OAAO,EAAM,OACvB,EAAO,CACd,QAAQ,MAAM,wBAAyB,EAAM,GAE9C,CAAC,EAAoB,GAAS,cAAe,EAAW,CAAC,CAE5D,MAAO,CACL,GAAG,EACH,mBACD,CC1BH,SAAgB,EAAc,EAAgC,CAC5D,GAAM,CAAE,sBAAuB,GAAsB,CAC/C,EAAa,EAAU,CAC3B,SAAU,GAAS,SACnB,UAAW,GAAS,UACpB,QAAS,GAAS,QAClB,WAAY,GAAS,WACtB,CAAC,CAGI,EAAgB,EAAY,SAAY,CAC5C,GAAI,CAEF,IAAM,EAAO,MAAM,EAAmB,aAAa,CACjD,aAAc,GAAS,aACxB,CAAC,CAGF,MAAM,EAAW,OAAO,EAAK,OACtB,EAAO,CAEd,MADA,QAAQ,MAAM,wBAAyB,EAAM,CACvC,IAEP,CAAC,EAAoB,GAAS,aAAc,EAAW,CAAC,CAE3D,MAAO,CACL,GAAG,EACH,gBACD,CChBH,SAAS,GAAY,EAA4C,CAC/D,IAAM,EAAY,EAClB,OACE,EAAU,YAAc,EAAU,WAClC,EAAU,YAAc,EAAU,SAClC,EAAU,YAAc,EAAU,WAClC,EAAU,YAAc,EAAU,WAClC,EAAU,YAAc,EAAU,SAClC,EAAU,YAAc,EAAU,WAClC,EAAU,YAAc,EAAU,YAClC,EAAU,YAAc,EAAU,UAyCtC,MAAM,EAAqB,EACzB,IAAA,GACD,CA0BD,SAAgB,GAAoB,CAAE,YAAsC,CAC1E,GAAM,CAAE,SAAQ,qBAAsB,GAAsB,CACtD,EAAc,EAAO,IAAI,IAA4B,CAG3D,MACsB,EAAmB,GAA2B,CAEhE,GAAI,GAAY,EAAM,CAAE,CACtB,IAAK,IAAM,KAAS,EAAY,QAAQ,QAAQ,CAC9C,EAAM,QAAQ,gBAAgB,EAAM,CAEtC,OAIF,GACE,SAAU,GACV,EAAM,OAAS,EAAgB,iBAC/B,SAAU,EAEV,IAAK,IAAM,KAAS,EAAY,QAAQ,QAAQ,CAC9C,EAAM,QAAQ,qBACZ,EAAM,KAAK,GACX,EAAM,KAAK,SACX,EAAM,KAAK,MACZ,EAGL,CAGD,CAAC,EAAkB,CAAC,CAEvB,IAAM,EAAa,GAEf,EACA,EACA,IACyB,CACzB,IAAM,EAAW,EAAY,QAAQ,IAAI,EAAO,CAEhD,GAAI,EAGF,MADA,GAAS,WACF,EAAS,QAIlB,IAAM,EAAU,IAAI,EAClB,EAAO,eACP,EACA,EACA,EAAO,qBACR,CAQD,OANA,EAAY,QAAQ,IAAI,EAAQ,CAC9B,UACA,SAAU,EACV,SACD,CAAC,CAEK,GAET,CAAC,EAAO,CACT,CAEK,EAAiB,EAAa,GAAmB,CACrD,IAAM,EAAW,EAAY,QAAQ,IAAI,EAAO,CAC3C,IAEL,EAAS,WAGL,EAAS,UAAY,IACvB,EAAS,QAAQ,SAAS,CAC1B,EAAY,QAAQ,OAAO,EAAO,IAEnC,EAAE,CAAC,CAEN,OACE,EAAC,EAAmB,SAAA,CAAS,MAAO,CAAE,aAAY,iBAAgB,CAC/D,YAC2B,CAmBlC,SAAgB,GAAiD,CAC/D,IAAM,EAAU,EAAW,EAAmB,CAE9C,GAAI,IAAY,IAAA,GACd,MAAU,MACR,qIAED,CAGH,OAAO,EC3CT,MAAMC,GAAgC,CACpC,OAAQ,OACR,SAAU,EACV,cAAe,EACf,WAAY,KACZ,MAAO,KACP,MAAO,KACP,YAAa,GACb,gBAAiB,KACjB,gBAAiB,KACjB,YAAa,KACd,CAmFD,SAAgB,EAAQ,EAAwC,CAC9D,GAAM,CAAE,SAAQ,sBAAuB,GAAsB,CACvD,CAAE,aAAY,kBAAmB,GAAuB,CACxD,CAAC,EAAO,GAAY,EAA0BC,GAAa,CAC3D,CAAC,EAAe,GAAoB,EAExC,KAAK,CACD,CAAC,EAAqB,GAA0B,EAAS,GAAM,CAC/D,CAAC,EAAQ,GAAa,EAAkC,EAAE,CAAC,CAC3D,CAAC,EAAa,GAAkB,EAEpC,IAAI,IAAM,CACN,EAAa,EAAoC,KAAK,CACtD,EAAgB,EAAuC,KAAK,CAG5D,EAAe,EAAO,EAAQ,CAGpC,MAAgB,CACd,EAAa,QAAU,GACvB,CAGF,MAAgB,EACS,SAAY,CACjC,EAAuB,GAAK,CAC5B,GAAI,CACF,GAAM,CAAE,QAAS,MAAM,EAAO,QAAQ,EAAQ,OAAO,CAarD,EAVmB,EAAK,MAAM,OAAQ,GAAS,EAAK,OAAS,QAAQ,CAEpB,IAAK,IAAU,CAC9D,OAAQ,EAAK,GACb,SAAU,EAAK,KACf,gBAAiB,EAAK,YACtB,YAAa,EAAK,YAClB,SAAU,GACX,EAAE,CAEuB,OACnB,EAAO,CACd,QAAQ,MAAM,kCAAmC,EAAM,QAC/C,CACR,EAAuB,GAAM,KAIjB,EACf,CAAC,EAAQ,EAAQ,OAAO,CAAC,CAI5B,MAAgB,CACd,IAAM,EAAS,EAAQ,OA6CvB,EAAW,QAAU,EAAW,EA1CR,CACtB,cAAgB,GAA8B,CAC5C,EAAS,EAAS,EAEpB,YACE,EACA,EACA,IACG,CACH,GAAI,EAAa,QAAQ,WAAY,CACnC,IAAM,EAAW,EACb,KAAK,MAAO,EAAgB,EAAc,IAAI,CAC9C,EACJ,EAAa,QAAQ,WAAW,EAAU,EAAe,EAAW,GAGxE,iBACE,EACA,EACA,IACG,CACH,EAAa,QAAQ,kBACnB,EACA,EACA,EACD,EAEH,eAAiB,GAA2B,CAC1C,EAAa,QAAQ,iBAAiB,EAAQ,EAEhD,UAAY,GAA2B,CACrC,EAAa,QAAQ,YAAY,EAAQ,EAE3C,QAAU,GAAiB,CACzB,EAAa,QAAQ,UAAU,EAAM,EAEvC,YAAe,GAGhB,CAGwD,CACvD,WAAY,CACV,OAAQ,EAAQ,OAChB,UAAW,EAAQ,UACnB,aAAc,EAAQ,aACtB,SAAU,EAAQ,SACnB,CACD,gBAAiB,EAAQ,gBACzB,UAAW,EAAQ,UACnB,QAAS,EAAQ,QAClB,CAAC,CAGF,IAAM,EAAe,gBAAkB,CACrC,GAAI,EAAW,QAAS,CACtB,IAAM,EAAS,EAAW,QAAQ,gBAAgB,CAC9C,EAAO,KAAO,GAChB,EAAe,IAAI,IAAI,EAAO,CAAC,GAGlC,IAAI,CAGP,UAAa,CACX,cAAc,EAAa,CAC3B,EAAe,EAAO,CACtB,EAAW,QAAU,OAEtB,CACD,EAAQ,OACR,EAAQ,UACR,EAAQ,aACR,EACA,EACD,CAAC,CAGF,IAAM,EAAW,GACd,EAAgB,IAA6C,CAC5D,EAAW,IAAU,CAAE,GAAG,GAAO,GAAS,EAAO,EAAE,EAErD,EAAE,CACH,CAGK,EAAiB,EACrB,KAAO,IAA+C,CACpD,GAAI,EAAK,SAAW,YAClB,OAAO,KAET,GAAI,EAAK,SAAW,QAClB,MAAM,EAAK,MAIb,OAAO,EADa,MAAM,EAAmB,SAAS,EAAK,KAAK,IAAI,CAC3B,CACvC,KAAM,EAAK,KAAK,UAAY,2BAC7B,CAAC,EAEJ,CAAC,EAAmB,CACrB,CAGK,EAAU,EAAY,SAAY,CACtC,GAAI,CAAC,EAAW,QACd,MAAU,MAAM,8BAA8B,CAGhD,GAAI,OAAO,KAAK,EAAO,CAAC,SAAW,EACjC,MAAU,MACR,gFACD,CAIH,EAAc,QAAU,CAAE,GAAG,EAAQ,CAGrC,IAAMC,EAA2C,EAAE,CAEnD,IAAK,GAAM,CAAC,EAAQ,KAAU,OAAO,QAAQ,EAAO,CAElD,GACE,GACA,OAAO,GAAU,UACjB,WAAY,IACX,EAAM,SAAW,WAChB,EAAM,SAAW,aACjB,EAAM,SAAW,SACnB,CACA,IAAM,EAAO,MAAM,EAAe,EAAwB,CACtD,IACF,EAAgB,GAAU,QAK5B,EAAgB,GAAU,EAI9B,GAAI,OAAO,KAAK,EAAgB,CAAC,SAAW,EAC1C,MAAU,MACR,uEACD,CAGH,MAAM,EAAW,QAAQ,YAAY,EAAgB,EACpD,CAAC,EAAQ,EAAe,CAAC,CAsF5B,MAAO,CACL,QACA,gBACA,cACA,SACA,WACA,UACA,OA1Fa,EACb,KAAO,IAAyB,CAE1B,KAAK,SAAW,YAKpB,IAAI,EAAK,SAAW,QAAS,CAC3B,EAAQ,UAAU,EAAK,MAAM,CAC7B,OAGF,GAAI,CAAC,EAAW,QACd,MAAU,MAAM,8BAA8B,CAIhD,GAAI,GAAiB,EAAc,OAAS,EAAG,CAC7C,IAAM,EAAiB,EAAc,GACjC,IACF,EAAc,QAAU,EAAG,EAAe,QAAS,EAAM,EAI7D,GAAI,CAEF,IAAM,EAAO,EADO,MAAM,EAAmB,SAAS,EAAK,KAAK,IAAI,CACrB,CAC7C,KAAM,EAAK,KAAK,UAAY,2BAC7B,CAAC,CAGF,GAAI,GAAiB,EAAc,OAAS,EAAG,CAC7C,IAAM,EAAiB,EAAc,GACrC,GAAI,CAAC,EACH,MAAU,MAAM,uBAAuB,CAEzC,EAAU,EAAG,EAAe,QAAS,EAAM,CAAC,CAC5C,MAAM,EAAW,QAAQ,YAAY,EAClC,EAAe,QAAS,EAC1B,CAAC,MAGF,MAAM,EAAW,QAAQ,OAAO,EAAK,OAEhC,EAAO,CACd,EAAQ,UAAU,EAAe,IAGrC,CAAC,EAAe,EAAoB,EAAQ,CAC7C,CAyCC,MAvCY,MAAkB,CAC9B,EAAW,SAAS,OAAO,EAC1B,EAAE,CAAC,CAsCJ,MApCY,MAAkB,CAC9B,EAAW,SAAS,OAAO,CAC3B,EAAU,EAAE,CAAC,CACb,EAAe,IAAI,IAAM,CACzB,EAAc,QAAU,MACvB,EAAE,CAAC,CAgCJ,MA9BY,MAAkB,CAE5B,EAAc,UACb,EAAM,SAAW,SAAW,EAAM,SAAW,aAG9C,EAAU,EAAc,QAAQ,CAChC,GAAS,GAEV,CAAC,EAAS,EAAM,OAAO,CAAC,CAsBzB,SAlBA,EAAM,SAAW,aAAe,EAAM,SAAW,aAmBjD,gBAlBsB,EAAM,SAAW,YAmBvC,aAlBmB,EAAM,SAAW,aAmBpC,sBACA,UAlBC,EAAM,SAAW,SAAW,EAAM,SAAW,YAC9C,EAAc,UAAY,KAkB3B,CCpiBH,MAAMC,EAAiC,CACrC,MAAO,EAAE,CACT,cAAe,EACf,cAAe,EACf,WAAY,EACZ,YAAa,EACb,eAAgB,EAChB,YAAa,EACd,CAqCD,SAAgB,EAAe,EAAiC,EAAE,CAAE,CAClE,GAAM,CAAE,UAAW,GAAsB,CACnC,CAAC,EAAO,GAAY,EAA2B,EAAa,CAC5D,EAAsB,EAC1B,IAAI,IACL,CACK,EAAY,EAAO,EAAE,CAErB,EAAW,EAA0B,EAAE,CAAC,CAExC,EAAa,MACV,UAAU,KAAK,KAAK,CAAC,GAAG,EAAU,YACxC,EAAE,CAAC,CAEA,EAAuB,EAAa,GAA6B,CACrE,IAAM,EAAa,EAAM,QAAQ,EAAK,IAAS,EAAM,EAAK,WAAY,EAAE,CAClE,EAAgB,EAAM,QACzB,EAAK,IAAS,EAAM,EAAK,cAC1B,EACD,CACK,EACJ,EAAa,EAAI,KAAK,MAAO,EAAgB,EAAc,IAAI,CAAG,EAC9D,EAAc,EAAM,OACvB,GAAS,EAAK,SAAW,YAC3B,CAAC,OACI,EAAiB,EAAM,OAC1B,GAAS,EAAK,SAAW,UAC3B,CAAC,OACI,EAAc,EAAM,OAAQ,GAAS,EAAK,SAAW,QAAQ,CAAC,OAGpE,EAAS,QAAU,EAEnB,EAAU,IAAU,CAClB,GAAG,EACH,QACA,gBACA,gBACA,aACA,cACA,iBACA,cACD,EAAE,EACF,EAAE,CAAC,CAEA,EAAW,EACd,GAA4B,CAO3B,IAAMC,EALkB,EAAM,OAC3B,GACC,EAAK,SAAW,UACnB,CAEmD,IAAK,IAAU,CACjE,GAAI,GAAY,CAChB,OACA,OAAQ,OACR,SAAU,EACV,cAAe,EACf,WAAY,EAAK,KAAK,KACtB,MAAO,KACP,OAAQ,KACT,EAAE,CAGG,EAAe,CAAC,GAAG,EAAS,QAAS,GAAG,EAAS,CAevD,MAdA,GAAS,QAAU,EAEnB,EAAU,GAAS,CACjB,IAAM,EAAa,EAAa,QAC7B,EAAK,IAAS,EAAM,EAAK,WAC1B,EACD,CACD,MAAO,CACL,GAAG,EACH,MAAO,EACP,aACD,EACD,CAEK,EAAS,IAAK,GAAS,EAAK,GAAG,EAExC,CAAC,EAAW,CACb,CAEK,EAAmB,EACvB,KAAO,IAA0B,CAC/B,GAAI,CACF,QAAQ,IAAI,kBAAmB,EAAK,KAAK,KAAK,KAAK,CAKnD,EAHqB,EAAS,QAAQ,IAAK,GACzC,EAAE,KAAO,EAAK,GAAK,CAAE,GAAG,EAAG,OAAQ,YAAsB,CAAG,EAC7D,CACiC,CAKlC,IAAM,EAAO,MADI,MAAM,MAAM,EAAK,KAAK,KAAK,IAAI,EACpB,MAAM,CAI9BC,EAAoB,EACxB,GAAI,EAAK,KAAK,KAAK,SAAU,CAC3B,IAAM,EAAc,CAClB,KAAM,EAAK,KAAK,KAAK,SACrB,aAAc,KAAK,KAAK,CACzB,CAED,EAAc,IAAI,KAAK,CAAC,EAAK,CAAE,EAAmB,CAIpD,QAAQ,IAAI,mBAAoB,EAAY,CAwD5C,IAAM,EAAa,MAvDG,EAAO,OAAO,EAAa,CAC/C,SAAU,EAAQ,SAElB,YACE,EACA,EACA,IACG,CACH,IAAM,EAAW,EACb,KAAK,MAAO,EAAgB,EAAc,IAAI,CAC9C,EAYJ,EAVqB,EAAS,QAAQ,IAAK,GACzC,EAAE,KAAO,EAAK,GACV,CACE,GAAG,EACH,WACA,gBACA,WAAY,GAAc,EAAE,WAC7B,CACD,EACL,CACiC,EAGpC,UAAY,GAAuB,CAYjC,EAXqB,EAAS,QAAQ,IAAK,GACzC,EAAE,KAAO,EAAK,GACV,CACE,GAAG,EACH,OAAQ,UACR,SAAU,IACV,SACA,cAAe,EAAO,MAAQ,EAAE,WACjC,CACD,EACL,CACiC,CAElC,EAAQ,YAAY,EAAO,CAC3B,EAAoB,QAAQ,OAAO,EAAK,GAAG,EAG7C,QAAU,GAAiB,CAIzB,EAHqB,EAAS,QAAQ,IAAK,GACzC,EAAE,KAAO,EAAK,GAAK,CAAE,GAAG,EAAG,OAAQ,QAAkB,QAAO,CAAG,EAChE,CACiC,CAElC,EAAQ,UAAU,EAAM,CACxB,EAAoB,QAAQ,OAAO,EAAK,GAAG,EAE9C,CAAC,CAIF,EAAoB,QAAQ,IAAI,EAAK,GAAI,EAAW,OAC7C,EAAO,CACd,QAAQ,MAAM,wBAAyB,EAAM,CAU7C,EATqB,EAAS,QAAQ,IAAK,GACzC,EAAE,KAAO,EAAK,GACV,CACE,GAAG,EACH,OAAQ,QACD,QACR,CACD,EACL,CACiC,CAElC,EAAQ,UAAU,EAAe,CACjC,EAAoB,QAAQ,OAAO,EAAK,GAAG,GAG/C,CAAC,EAAQ,EAAS,EAAqB,CACxC,CAEK,EAAe,EACnB,KAAO,IAAuB,CAC5B,IAAM,EAAgB,EAAQ,eAAiB,EAGzC,EAAgB,EAClB,EAAS,QAAQ,OACd,GAAS,EAAQ,SAAS,EAAK,GAAG,EAAI,EAAK,SAAW,OACxD,CACD,EAAS,QAAQ,OAAQ,GAAS,EAAK,SAAW,OAAO,CAE7D,QAAQ,IAAI,mBAAoB,EAAc,OAAQ,EAAc,CAGpE,IAAK,IAAI,EAAI,EAAG,EAAI,EAAc,OAAQ,GAAK,EAAe,CAC5D,IAAM,EAAQ,EAAc,MAAM,EAAG,EAAI,EAAc,CACvD,MAAM,QAAQ,IAAI,EAAM,IAAK,GAAS,EAAiB,EAAK,CAAC,CAAC,GAGlE,CAAC,EAAQ,cAAe,EAAiB,CAC1C,CAEK,EAAa,EAChB,GAAe,CACd,IAAM,EAAa,EAAoB,QAAQ,IAAI,EAAG,CAClD,IACF,EAAW,OAAO,CAClB,EAAoB,QAAQ,OAAO,EAAG,EAIxC,EADqB,EAAS,QAAQ,OAAQ,GAAS,EAAK,KAAO,EAAG,CACpC,EAEpC,CAAC,EAAqB,CACvB,CAEK,EAAY,EACf,GAAe,CACd,IAAM,EAAa,EAAoB,QAAQ,IAAI,EAAG,CAClD,IACF,EAAW,OAAO,CAClB,EAAoB,QAAQ,OAAO,EAAG,EAMxC,EAHqB,EAAS,QAAQ,IAAK,GACzC,EAAK,KAAO,EAAK,CAAE,GAAG,EAAM,OAAQ,UAAoB,CAAG,EAC5D,CACiC,EAEpC,CAAC,EAAqB,CACvB,CAEK,EAAQ,MAAkB,CAE9B,EAAoB,QAAQ,QAAS,GAAe,CAClD,EAAW,OAAO,EAClB,CACF,EAAoB,QAAQ,OAAO,CAGnC,EAAS,QAAU,EAAE,CAErB,EAAS,EAAa,EACrB,EAAE,CAAC,CA8BN,MAAO,CACL,QACA,WACA,eACA,aACA,YACA,UAlCgB,EAChB,KAAO,IAAe,CACpB,IAAM,EAAO,EAAS,QAAQ,KAAM,GAAM,EAAE,KAAO,EAAG,CACtD,GAAI,IAAS,EAAK,SAAW,SAAW,EAAK,SAAW,WAAY,CAalE,EAXqB,EAAS,QAAQ,IAAK,GACzC,EAAE,KAAO,EACL,CACE,GAAG,EACH,OAAQ,OACR,SAAU,EACV,cAAe,EACf,MAAO,KACR,CACD,EACL,CACiC,CAGlC,IAAM,EAAY,EAAS,QAAQ,KAAM,GAAM,EAAE,KAAO,EAAG,CACvD,GACF,MAAM,EAAiB,EAAU,GAIvC,CAAC,EAAkB,EAAqB,CACzC,CASC,QACD,CChWH,SAAgB,EAAiB,EAAmC,CAClE,GAAM,CAAE,sBAAuB,GAAsB,CAC/C,EAAa,EAAe,CAChC,cAAe,EACf,SAAU,GAAS,SACnB,UAAW,GAAS,UACpB,QAAS,GAAS,QACnB,CAAC,CAGI,EAAkB,EAAY,SAAY,CAC9C,IAAIC,EAmBJ,GAhBA,AAUE,EAVE,GAAS,YAAc,QAChB,MAAM,EAAmB,UAAU,CAC1C,cAAe,GAAS,eAAiB,GAC1C,CAAC,EACO,GAAS,UACT,MAAM,EAAmB,UAAU,CAC1C,cAAe,GAAS,eAAiB,GAC1C,CAAC,EASA,EAAO,SAAW,YACpB,MAAO,EAAE,CAIX,GAAI,EAAO,SAAW,QAGpB,OAFA,QAAQ,MAAM,2BAA4B,EAAO,MAAM,CACvD,GAAS,UAAU,EAAO,MAAM,CACzB,EAAE,CAIX,IAAM,EAAU,EAAW,SAAS,CAAC,EAAO,CAAC,CAG7C,OAFA,MAAM,EAAW,aAAa,EAAQ,CAE/B,GACN,CACD,EACA,GAAS,cACT,GAAS,UACT,GAAS,QACT,EACD,CAAC,CAEF,MAAO,CACL,GAAG,EACH,kBACD,CC7DH,SAAgB,IAAmB,CACjC,IAAM,EAAe,EAAsB,KAAK,CAC1C,EAAgB,EAAe,EAAE,CACjC,EAAe,EAAe,EAAE,CAEhC,CAAC,EAAS,GAAc,EAAwB,CACpD,WAAY,EACZ,WAAY,EACZ,SAAU,EACV,UAAW,EACX,QAAS,EACV,CAAC,CAwDF,MAAO,CACL,UACA,MAvDY,MAAkB,CAC9B,EAAa,QAAU,KAAK,KAAK,CACjC,EAAc,QAAU,EACxB,EAAa,QAAU,GACtB,EAAE,CAAC,CAoDJ,OAjDa,GACZ,EAAuB,EAAqB,EAAiB,IAAM,CAClE,GAAI,CAAC,EAAa,QAChB,OAIF,IAAM,EADM,KAAK,KAAK,CACG,EAAa,QAChC,EAAQ,EAAa,EAAK,EAAgB,EAAc,IAAO,EAEjE,EAAQ,EAAa,UACvB,EAAa,QAAU,GAGzB,EAAW,CACT,WAAY,EACZ,aACA,SAAU,EAAa,EAAK,EAAgB,EAAc,IAAO,EACjE,UAAW,EAAa,QACxB,QAAS,EACV,CAAC,EAEJ,EAAE,CACH,CA2BC,IAxBU,MAAkB,CAC5B,IAAM,EAAe,EAErB,MADA,GAAa,QAAU,KAChB,GACN,CAAC,EAAQ,CAAC,CAqBX,MAlBY,MAAkB,CAC9B,EAAa,QAAU,KACvB,EAAc,QAAU,EACxB,EAAa,QAAU,EACvB,EAAW,CACT,WAAY,EACZ,WAAY,EACZ,SAAU,EACV,UAAW,EACX,QAAS,EACV,CAAC,EACD,EAAE,CAAC,CAQL,CCvEH,SAAgB,EAAe,EAAuB,CACpD,GAAI,IAAU,EAAG,MAAO,UAExB,IAAM,EAAI,KACJ,EAAQ,CAAC,QAAS,KAAM,KAAM,KAAK,CACnC,EAAI,KAAK,MAAM,KAAK,IAAI,EAAM,CAAG,KAAK,IAAI,EAAE,CAAC,CAEnD,MAAO,GAAG,KAAK,MAAO,EAAQ,GAAK,EAAK,IAAI,CAAG,IAAI,GAAG,EAAM,KAQ9D,SAAgB,EAAwB,EAA0B,CA8ChE,MA7C0C,CAExC,OAAQ,aACR,QAAS,aACT,OAAQ,YACR,OAAQ,YACR,OAAQ,YACR,QAAS,aACT,OAAQ,gBAGR,OAAQ,YACR,OAAQ,kBACR,OAAQ,kBACR,OAAQ,iBACR,OAAQ,cACR,OAAQ,mBACR,QAAS,aAGT,OAAQ,aACR,OAAQ,YACR,OAAQ,YACR,QAAS,aACT,OAAQ,YAGR,OAAQ,kBACR,OAAQ,qBACR,QACE,0EACF,OAAQ,2BACR,QACE,oEACF,OAAQ,gCACR,QACE,4EACF,OAAQ,aACR,OAAQ,WACR,QAAS,mBACT,OAAQ,kBACR,OAAQ,kBACT,CAEW,EAAS,aAAa,CAAC,MAAM,EAAS,YAAY,IAAI,CAAC,GAC1C,2BAS3B,SAAgB,GACd,EACA,EACS,CACT,GAAI,CAAC,GAAgB,EAAa,SAAW,EAC3C,MAAO,GAGT,IAAM,EAAW,EAAwB,EAAS,CAClD,OAAO,EAAa,KAAM,GAAY,CACpC,GAAI,EAAQ,SAAS,KAAK,CAAE,CAE1B,GAAM,CAAC,GAAQ,EAAQ,MAAM,IAAI,CACjC,OAAO,EAAS,WAAW,GAAG,EAAK,GAAG,CAExC,OAAO,IAAY,GACnB,CAUJ,SAAgB,GACd,EACA,EACA,EACS,CAST,MAJA,EAJI,IAAY,IAAA,IAAa,EAAW,GAIpC,IAAY,IAAA,IAAa,EAAW,GAY1C,SAAgB,GAAiB,EAA0B,CACzD,IAAM,EAAU,EAAS,YAAY,IAAI,CAEzC,OADI,IAAY,GAAW,GACpB,EAAS,MAAM,EAAU,EAAE,CAAC,aAAa,CAQlD,SAAgB,GAA4B,EAA0B,CACpE,IAAM,EAAU,EAAS,YAAY,IAAI,CAEzC,OADI,IAAY,GAAW,EACpB,EAAS,MAAM,EAAG,EAAQ,CAQnC,SAAgB,GAAY,EAA2B,CACrD,IAAM,EAAkB,CACtB,OACA,QACA,OACA,OACA,OACA,QACA,OACD,CACK,EAAM,EAAS,aAAa,CAAC,MAAM,EAAS,YAAY,IAAI,CAAC,CACnE,OAAO,EAAgB,SAAS,EAAI,CAQtC,SAAgB,GAAY,EAA2B,CACrD,IAAM,EAAkB,CACtB,OACA,OACA,OACA,OACA,OACA,OACA,QACD,CACK,EAAM,EAAS,aAAa,CAAC,MAAM,EAAS,YAAY,IAAI,CAAC,CACnE,OAAO,EAAgB,SAAS,EAAI,CAQtC,SAAgB,GAAe,EAA2B,CACxD,IAAM,EAAgB,CACpB,OACA,OACA,QACA,OACA,QACA,OACA,QACA,OACA,OACD,CACK,EAAM,EAAS,aAAa,CAAC,MAAM,EAAS,YAAY,IAAI,CAAC,CACnE,OAAO,EAAc,SAAS,EAAI,CC/LpC,IAAY,EAAA,SAAA,EAAL,OACL,GAAA,OAAA,SACA,EAAA,cAAA,gBACA,EAAA,cAAA,gBACA,EAAA,aAAA,sBAMU,EAAA,SAAA,EAAL,OACL,GAAA,QAAA,UACA,EAAA,OAAA,SACA,EAAA,eAAA,iBACA,EAAA,WAAA,oBAUF,eAAsB,GAA4C,CAChE,GAAI,CAMF,OAHA,QAAQ,IACN,gEACD,CACM,SACA,EAAO,CAEd,OADA,QAAQ,MAAM,uCAAwC,EAAM,CACrD,IAQX,eAAsB,GAAkD,CACtE,GAAI,CAIF,OAHA,QAAQ,IACN,uEACD,CACM,SACA,EAAO,CAEd,OADA,QAAQ,MAAM,8CAA+C,EAAM,CAC5D,IAQX,eAAsB,GAAiD,CACrE,GAAI,CAIF,OAHA,QAAQ,IACN,sEACD,CACM,SACA,EAAO,CAEd,OADA,QAAQ,MAAM,6CAA8C,EAAM,CAC3D,IAQX,eAAsB,GAAkD,CACtE,GAAI,CAIF,OAHA,QAAQ,IACN,uEACD,CACM,SACA,EAAO,CAEd,OADA,QAAQ,MAAM,8CAA+C,EAAM,CAC5D,IASX,eAAsB,GACpB,EACkB,CAClB,GAAI,CAkBF,OAjBgB,MAAM,QAAQ,IAC5B,EAAY,IAAI,KAAO,IAAe,CACpC,OAAQ,EAAR,CACE,KAAK,EAAe,OAClB,OAAO,GAAyB,CAClC,KAAK,EAAe,cAClB,OAAO,GAA+B,CACxC,KAAK,EAAe,aAClB,OAAO,GAA8B,CACvC,KAAK,EAAe,cAClB,OAAO,GAA+B,CACxC,QACE,MAAO,KAEX,CACH,EAEc,MAAO,GAAW,EAAO,OACjC,EAAO,CAEd,OADA,QAAQ,MAAM,iCAAkC,EAAM,CAC/C,IASX,eAAsB,GACpB,EACkB,CAClB,GAAI,CAGF,MAAO,SACA,EAAO,CAEd,OADA,QAAQ,MAAM,+BAAgC,EAAM,CAC7C,IASX,eAAsB,GACpB,EAC2B,CAC3B,GAAI,CAGF,OAAO,EAAiB,cACjB,EAAO,CAEd,OADA,QAAQ,MAAM,mCAAoC,EAAM,CACjD,EAAiB,QAQ5B,SAAgB,IAAwB,CACtC,GAAI,CAEF,QAAQ,IACN,uFACD,OACM,EAAO,CACd,QAAQ,MAAM,+BAAgC,EAAM,ECrKxD,SAAgB,EAAmB,EAAqB,CACtD,GAAI,CAEF,GAAI,EAAI,WAAW,UAAU,CAG3B,OADa,EAAI,QAAQ,UAAW,GAAG,CAC3B,MAAM,IAAI,CAAC,KAAK,EAAI,OAGlC,GAAI,EAAI,WAAW,aAAa,CAAE,CAEhC,IAAMC,EAAQ,EAAI,MAAM,IAAI,CAC5B,OAAOA,EAAMA,EAAM,OAAS,IAAM,OAIpC,IAAM,EAAQ,EAAI,MAAM,IAAI,CAC5B,OAAO,EAAM,EAAM,OAAS,IAAM,YAC5B,CACN,MAAO,QASX,SAAgB,GAAU,EAA0B,CAUlD,OATI,EAAS,WAAW,UAAU,EAI9B,EAAS,WAAW,aAAa,CAC5B,EAIF,UAAU,IAQnB,SAAgB,EAAU,EAAqB,CAU7C,OATI,EAAI,WAAW,UAAU,CACpB,EAAI,QAAQ,UAAW,GAAG,EAG/B,EAAI,WAAW,aAAa,CAEvB,GAWX,SAAgB,GAAoB,EAAqB,CACvD,GAAI,CAEF,IAAM,EADO,EAAU,EAAI,CACR,MAAM,IAAI,CAE7B,OADA,EAAM,KAAK,CACJ,EAAM,KAAK,IAAI,MAChB,CACN,MAAO,IASX,SAAgB,GAAa,EAAsB,CACjD,OAAO,EAAI,WAAW,aAAa,CAQrC,SAAgB,GAAU,EAAsB,CAC9C,OAAO,EAAI,WAAW,UAAU,CAQlC,SAAgB,GAAa,EAAqB,CAEhD,OAAO,EAAI,QAAQ,eAAgB,KAAK,CAQ1C,SAAgB,EAAmB,EAAqB,CACtD,IAAM,EAAW,EAAmB,EAAI,CA4BxC,MA1B0C,CAExC,OAAQ,aACR,QAAS,aACT,OAAQ,YACR,OAAQ,YACR,OAAQ,YACR,QAAS,aAGT,OAAQ,YACR,OAAQ,kBACR,OAAQ,kBAGR,OAAQ,aACR,OAAQ,YACR,OAAQ,YAGR,OAAQ,kBACR,OAAQ,aACR,QAAS,mBACV,CAEW,EAAS,aAAa,CAAC,MAAM,EAAS,YAAY,IAAI,CAAC,GAC1C,2BCpI3B,SAAgB,EAAe,CAAE,QAAO,SAA8B,CACpE,IAAMC,MAAuB,CAC3B,OAAQ,EAAM,OAAd,CACE,IAAK,YACH,MAAO,UACT,IAAK,UACH,MAAO,UACT,IAAK,QACL,IAAK,UACH,MAAO,UACT,QACE,MAAO,YA8Fb,OACE,EAAC,EAAA,CACC,MAAO,CACLC,EAAO,QACP,CACE,gBAAiBD,GAAgB,CAClC,CACF,WAEA,EAAM,SAAW,aAChB,EAAC,EAAA,CACC,KAAK,QACL,MAAOA,GAAgB,CACvB,MAAOC,EAAO,SACd,MAxGoB,CAC1B,OAAQ,EAAM,OAAd,CACE,IAAK,OACH,OACE,EAAC,EAAA,CAAK,MAAOA,EAAO,mBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,eAAQ,GAAS,mBAAyB,EACzD,CAGX,IAAK,YACH,OACE,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,eAAQ,GAAS,aAAmB,CACxD,EAAC,EAAA,CAAK,MAAOA,EAAO,qBAAa,EAAM,SAAS,IAAA,EAAQ,CAAA,EACnD,CAGP,EAAC,EAAA,CAAK,MAAOA,EAAO,8BAClB,EAAC,EAAA,CACC,MAAO,CACLA,EAAO,YACP,CACE,MAAO,GAAG,EAAM,SAAS,GACzB,gBAAiBD,GAAgB,CAClC,CACF,CAAA,CACD,EACG,CAGP,EAAC,EAAA,CAAK,MAAOC,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,iBACjB,EAAe,EAAM,cAAc,CAAC,KAAG,IACvC,EAAe,EAAM,YAAc,EAAE,GACjC,EACF,GACF,CAGX,IAAK,UACH,OACE,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAO,CAACA,EAAO,MAAO,CAAE,MAAOD,GAAgB,CAAE,CAAC,UACrD,GAAS,mBACL,CACP,EAAC,EAAA,CAAK,MAAO,CAACC,EAAO,WAAY,CAAE,MAAOD,GAAgB,CAAE,CAAC,UAAE,KAExD,CAAA,EACF,CACP,EAAC,EAAA,CAAK,MAAO,CAACC,EAAO,OAAQ,CAAE,MAAOD,GAAgB,CAAE,CAAC,UACtD,EAAe,EAAM,YAAc,EAAE,EACjC,CAAA,EACF,CAGX,IAAK,QACH,OACE,EAAC,EAAA,CAAK,MAAOC,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAO,CAACA,EAAO,MAAO,CAAE,MAAOD,GAAgB,CAAE,CAAC,UACrD,GAAS,iBACL,CACP,EAAC,EAAA,CAAK,MAAO,CAACC,EAAO,WAAY,CAAE,MAAOD,GAAgB,CAAE,CAAC,UAAE,KAExD,CAAA,EACF,CACN,EAAM,OACL,EAAC,EAAA,CAAK,MAAO,CAACC,EAAO,OAAQ,CAAE,MAAOD,GAAgB,CAAE,CAAC,UACtD,EAAM,MAAM,SACR,CAAA,EAEJ,CAGX,IAAK,UACH,OACE,EAAC,EAAA,CAAK,MAAOC,EAAO,mBAClB,EAAC,EAAA,CAAK,MAAO,CAACA,EAAO,MAAO,CAAE,MAAOD,GAAgB,CAAE,CAAC,UACrD,GAAS,oBACL,EACF,CAGX,QACE,OAAO,SAoBO,CAAA,EACX,CAIX,MAAMC,EAAS,EAAW,OAAO,CAC/B,QAAS,CACP,cAAe,MACf,WAAY,aACZ,gBAAiB,EACjB,kBAAmB,GACnB,gBAAiB,EACjB,gBAAiB,UACjB,aAAc,EACd,IAAK,EACN,CACD,QAAS,CACP,UAAW,EACZ,CACD,UAAW,CACT,KAAM,EACN,IAAK,EACN,CACD,UAAW,CACT,cAAe,MACf,eAAgB,gBAChB,WAAY,SACb,CACD,MAAO,CACL,SAAU,GACV,WAAY,MACZ,MAAO,UACP,KAAM,EACP,CACD,WAAY,CACV,SAAU,GACV,WAAY,MACZ,MAAO,UACP,SAAU,GACV,UAAW,QACZ,CACD,qBAAsB,CACpB,OAAQ,EACR,gBAAiB,UACjB,aAAc,EACd,SAAU,SACX,CACD,YAAa,CACX,OAAQ,OACR,aAAc,EACf,CACD,WAAY,CACV,cAAe,MACf,eAAgB,gBAChB,WAAY,SACb,CACD,OAAQ,CACN,SAAU,GACV,MAAO,UACR,CACF,CAAC,CClKF,SAAgB,GAAmB,CACjC,UACA,QAAQ,aACR,WACA,YACA,UACA,WACA,eAAe,IACW,CAC1B,GAAM,CAAE,QAAO,oBAAqB,EAAgB,EAAQ,CAEtD,EAAc,SAAY,CAC9B,GAAI,CACF,MAAM,GAAkB,OACjB,EAAO,CACV,aAAiB,QAEjB,EAAM,QAAQ,SAAS,YAAY,EACnC,EAAM,QAAQ,SAAS,UAAU,CAEjC,KAAY,CAEZ,IAAU,EAAM,IAMlB,EAAY,EAAM,SAAW,YAC7B,EAAa,GAAa,EAAM,SAAW,UAcjD,OAZA,MAAgB,CACV,EAAM,SAAW,WAAa,EAAM,QACtC,IAAY,EAAM,OAAO,EAE1B,CAAC,EAAM,OAAQ,EAAM,OAAQ,EAAU,CAAC,CAE3C,MAAgB,CACV,EAAM,SAAW,SAAW,EAAM,OACpC,IAAU,EAAM,MAAM,EAEvB,CAAC,EAAM,OAAQ,EAAM,MAAO,EAAQ,CAAC,CAGtC,EAAC,EAAA,CAAK,MAAOC,EAAO,oBAClB,EAAC,EAAA,CACC,MAAO,CAACA,EAAO,OAAQ,GAAcA,EAAO,eAAe,CAC3D,QAAS,EACT,SAAU,YAET,GACC,EAAC,EAAA,CACC,KAAK,QACL,MAAM,UACN,MAAOA,EAAO,SACd,CAEJ,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAAa,GAAY,GAAa,CAAA,EAChD,CACX,GAAgB,EAAM,SAAW,QAChC,EAAC,EAAA,CAAK,MAAOA,EAAO,2BAClB,EAAC,EAAA,CAAsB,QAAO,MAAM,iBAAkB,EACjD,CAAA,EAEJ,CAIX,MAAMA,EAAS,EAAW,OAAO,CAC/B,UAAW,CACT,IAAK,EACN,CACD,OAAQ,CACN,cAAe,MACf,WAAY,SACZ,eAAgB,SAChB,gBAAiB,GACjB,kBAAmB,GACnB,gBAAiB,UACjB,aAAc,EACd,IAAK,EACN,CACD,eAAgB,CACd,QAAS,GACV,CACD,WAAY,CACV,SAAU,GACV,WAAY,MACZ,MAAO,UACR,CACD,QAAS,CACP,YAAa,EACd,CACD,kBAAmB,CACjB,UAAW,EACZ,CACF,CAAC,CChGF,SAAgB,GAAiB,CAC/B,UACA,QAAQ,cACR,WACA,YACA,UACA,WACA,eAAe,IACS,CACxB,GAAM,CAAE,QAAO,iBAAkB,EAAc,EAAQ,CAEjD,EAAc,SAAY,CAC9B,GAAI,CACF,MAAM,GAAe,OACd,EAAO,CACV,aAAiB,QAEjB,EAAM,QAAQ,SAAS,YAAY,EACnC,EAAM,QAAQ,SAAS,UAAU,CAEjC,KAAY,CAEZ,IAAU,EAAM,IAMlB,EAAY,EAAM,SAAW,YAC7B,EAAa,GAAa,EAAM,SAAW,UAcjD,OAZA,MAAgB,CACV,EAAM,SAAW,WAAa,EAAM,QACtC,IAAY,EAAM,OAAO,EAE1B,CAAC,EAAM,OAAQ,EAAM,OAAQ,EAAU,CAAC,CAE3C,MAAgB,CACV,EAAM,SAAW,SAAW,EAAM,OACpC,IAAU,EAAM,MAAM,EAEvB,CAAC,EAAM,OAAQ,EAAM,MAAO,EAAQ,CAAC,CAGtC,EAAC,EAAA,CAAK,MAAOC,EAAO,oBAClB,EAAC,EAAA,CACC,MAAO,CAACA,EAAO,OAAQ,GAAcA,EAAO,eAAe,CAC3D,QAAS,EACT,SAAU,YAET,GACC,EAAC,EAAA,CACC,KAAK,QACL,MAAM,UACN,MAAOA,EAAO,SACd,CAEJ,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAAa,GAAY,GAAa,CAAA,EAChD,CACX,GAAgB,EAAM,SAAW,QAChC,EAAC,EAAA,CAAK,MAAOA,EAAO,2BAClB,EAAC,EAAA,CAAsB,QAAO,MAAM,eAAgB,EAC/C,CAAA,EAEJ,CAIX,MAAMA,EAAS,EAAW,OAAO,CAC/B,UAAW,CACT,IAAK,EACN,CACD,OAAQ,CACN,cAAe,MACf,WAAY,SACZ,eAAgB,SAChB,gBAAiB,GACjB,kBAAmB,GACnB,gBAAiB,UACjB,aAAc,EACd,IAAK,EACN,CACD,eAAgB,CACd,QAAS,GACV,CACD,WAAY,CACV,SAAU,GACV,WAAY,MACZ,MAAO,UACR,CACD,QAAS,CACP,YAAa,EACd,CACD,kBAAmB,CACjB,UAAW,EACZ,CACF,CAAC,CC/FF,SAAgB,GAAoB,CAClC,UACA,QAAQ,sBACR,WACA,YACA,UACA,WACA,eAAe,IACY,CAC3B,GAAM,CAAE,QAAO,mBAAoB,EAAiB,EAAQ,CAEtD,EAAc,SAAY,CAC9B,GAAI,CACF,MAAM,GAAiB,OAChB,EAAO,CACV,aAAiB,QAEjB,EAAM,QAAQ,SAAS,YAAY,EACnC,EAAM,QAAQ,SAAS,UAAU,CAEjC,KAAY,CAEZ,IAAU,EAAM,IAMlB,EAAY,EAAM,MAAM,KAAM,GAAS,EAAK,SAAW,YAAY,CACnE,EAAW,EAAM,MAAM,OAAS,EAChC,EACJ,GACA,EAAM,MAAM,MACT,GAAS,EAAK,SAAW,aAAe,EAAK,SAAW,OAC1D,CAqCH,OAnCA,EAAM,cAAgB,CACpB,GAAI,EAAa,CACf,IAAM,EAAU,EAAM,MACnB,OAAQ,GAAS,EAAK,SAAW,UAAU,CAC3C,IAAK,GAAS,EAAK,OAAO,CACzB,EAAQ,OAAS,GACnB,IAAY,EAAQ,GAGvB,CAAC,EAAa,EAAM,MAAO,EAAU,CAAC,CAEzC,EAAM,cAAgB,CAEpB,IAAM,EADS,EAAM,MAAM,OAAQ,GAAS,EAAK,SAAW,QAAQ,CAC1C,IAAI,MAC1B,GACF,IAAU,EAAW,EAEtB,CAAC,EAAM,MAAO,EAAQ,CAAC,CAmBxB,EAAC,EAAA,CAAK,MAAOC,EAAO,oBAClB,EAAC,EAAA,CACC,MAAO,CAACA,EAAO,OAAQ,GAAaA,EAAO,eAAe,CAC1D,QAAS,EACT,SAAU,YAET,GACC,EAAC,EAAA,CACC,KAAK,QACL,MAAM,UACN,MAAOA,EAAO,SACd,CAEJ,EAAC,EAAA,CAAK,MAAOA,EAAO,qBACjB,GAAY,EACZ,GAAY,KAAK,EAAM,MAAM,OAAO,GAAA,EAChC,CAAA,EACG,CAEX,GACC,EAAC,EAAA,CAAK,MAAOA,EAAO,yBAClB,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAAW,aAClB,EAAM,MAAM,OAAQ,GAAM,EAAE,SAAW,UAAU,CAAC,OAAO,IAClE,EAAM,MAAM,OAAO,cAChB,CACP,EAAC,EAAA,CAAK,MAAOA,EAAO,oBAAW,YAAU,EAAM,cAAc,MAAQ,CAAA,EAChE,CAGR,GAAgB,GACf,EAAC,EAAA,CACC,cAAe,GACf,KAAM,EAAM,MACA,YAlDA,CAAE,UACpB,EAAC,EAAA,CAAmB,MAAOA,EAAO,uBAChC,EAAC,EAAA,CACC,MAAO,CACL,OAAQ,EAAK,OACb,SAAU,EAAK,SACf,cAAe,EAAK,cACpB,WAAY,EAAK,WACjB,MAAO,EAAK,MACZ,OAAQ,EAAK,OACd,CACD,MAAO,EAAK,KAAK,KAAK,MACtB,EAXO,EAAK,GAYT,CAsCD,aAAe,GAAS,EAAK,GAC7B,MAAOA,EAAO,cACd,sBAAuBA,EAAO,YAC9B,2BAA8B,EAAC,EAAA,CAAK,MAAOA,EAAO,UAAA,CAAa,EAC/D,GAEC,CAIX,MAAMA,EAAS,EAAW,OAAO,CAC/B,UAAW,CACT,IAAK,EACN,CACD,OAAQ,CACN,cAAe,MACf,WAAY,SACZ,eAAgB,SAChB,gBAAiB,GACjB,kBAAmB,GACnB,gBAAiB,UACjB,aAAc,EACd,IAAK,EACN,CACD,eAAgB,CACd,QAAS,GACV,CACD,WAAY,CACV,SAAU,GACV,WAAY,MACZ,MAAO,UACR,CACD,QAAS,CACP,YAAa,EACd,CACD,eAAgB,CACd,gBAAiB,EACjB,kBAAmB,GACnB,gBAAiB,UACjB,aAAc,EACd,IAAK,EACN,CACD,UAAW,CACT,SAAU,GACV,MAAO,UACR,CACD,cAAe,CACb,UAAW,IACZ,CACD,YAAa,CACX,IAAK,EACN,CACD,cAAe,CACb,kBAAmB,EACpB,CACD,UAAW,CACT,OAAQ,EACT,CACF,CAAC,CCnLF,SAAgB,GAAW,CACzB,QACA,WACA,cACA,mBAAmB,IACD,CA+DlB,OARI,EAAM,SAAW,EAEjB,EAAC,EAAA,CAAK,MAAO,EAAO,wBAClB,EAAC,EAAA,CAAK,MAAO,EAAO,mBAAW,cAAiB,EAC3C,CAKT,EAAC,EAAA,CAAK,MAAO,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAO,EAAO,oBAClB,EAAC,EAAA,CAAK,MAAO,EAAO,qBAAY,YAAU,EAAM,OAAO,MAAQ,CAC/D,EAAC,EAAA,CAAK,MAAO,EAAO,wBACjB,EAAM,OAAQ,GAAM,EAAE,SAAS,QAAU,UAAU,CAAC,OAAO,YAAA,EACvD,CAAA,EACF,CACP,EAAC,EAAA,CACC,cAAe,GACf,KAAM,EACM,YAzEE,CAAE,UACpB,EAAC,EAAA,CACC,MAAO,CACL,EAAO,cACP,CAAE,gBAAiB,GAAe,EAAK,SAAS,MAAM,CAAE,CACzD,CACD,YAAe,IAAc,EAAK,WAElC,EAAC,EAAA,CAAK,MAAO,EAAO,sBACjB,EAAK,KAAK,SAAW,WACpB,EAAC,EAAA,CAAK,MAAO,EAAO,qBAClB,EAAC,EAAA,CAAK,MAAO,EAAO,SAAU,cAAe,WAC1C,EAAK,KAAK,KAAK,MACX,CACP,EAAC,EAAA,CAAK,MAAO,EAAO,kBACjB,GAAmB,EAAK,KAAK,KAAK,KAAK,EACnC,CAAA,EACF,CAER,EAAK,KAAK,SAAW,SACpB,EAAC,EAAA,CAAK,MAAO,EAAO,mBAAY,EAAK,SAAS,OAAO,SAAe,CAEtE,EAAC,EAAA,CAAK,MAAO,EAAO,yBAClB,EAAC,EAAA,CACC,MAAO,CACL,OACE,EAAK,SAAS,QAAU,UACpB,OACA,EAAK,SAAS,QAAU,YACtB,UACA,EAAK,SAAS,MACtB,SAAU,EAAK,SAAS,SACxB,cAAe,EAAK,SAAS,cAC7B,WAAY,EAAK,SAAS,WAC1B,MAAO,EAAK,SAAS,OAAS,KAC9B,OAAQ,EAAK,QAAU,KACxB,CAAA,CACD,EACG,GACF,CACN,GACC,EAAK,SAAS,QAAU,aACxB,EAAK,SAAS,QAAU,WACtB,EAAC,EAAA,CACC,MAAO,EAAO,aACd,YAAe,IAAW,EAAK,GAAG,CAClC,QAAS,CAAE,IAAK,EAAG,MAAO,EAAG,OAAQ,EAAG,KAAM,EAAG,UAEjD,EAAC,EAAA,CAAK,MAAO,EAAO,0BAAkB,KAAQ,EACpC,CAAA,EAEN,CAuBR,aAAe,GAAS,EAAK,GAC7B,2BAA8B,EAAC,EAAA,CAAK,MAAO,EAAO,UAAA,CAAa,CAC/D,sBAAuB,EAAO,aAC9B,CAAA,EACG,CAKX,SAAS,GAAe,EAAuB,CAC7C,OAAQ,EAAR,CACE,IAAK,UACH,MAAO,UACT,IAAK,QACL,IAAK,YACH,MAAO,UACT,IAAK,YACL,IAAK,UACH,MAAO,UACT,QACE,MAAO,WAIb,SAAS,GAAmB,EAAuB,CACjD,GAAI,IAAU,EAAG,MAAO,MACxB,IAAM,EAAI,KACJ,EAAQ,CAAC,IAAK,KAAM,KAAM,KAAK,CAC/B,EAAI,KAAK,MAAM,KAAK,IAAI,EAAM,CAAG,KAAK,IAAI,EAAE,CAAC,CACnD,MAAO,GAAG,KAAK,MAAO,EAAQ,GAAK,EAAK,GAAG,CAAG,GAAG,GAAG,EAAM,KAG5D,MAAM,EAAS,EAAW,OAAO,CAC/B,UAAW,CACT,IAAK,EACN,CACD,UAAW,CACT,cAAe,MACf,eAAgB,gBAChB,WAAY,SACZ,kBAAmB,GACnB,gBAAiB,EACjB,gBAAiB,UACjB,aAAc,EACf,CACD,WAAY,CACV,SAAU,GACV,WAAY,MACZ,MAAO,UACR,CACD,UAAW,CACT,SAAU,GACV,MAAO,UACR,CACD,cAAe,CACb,SAAU,GACV,MAAO,UACR,CACD,YAAa,CACX,IAAK,EACN,CACD,cAAe,CACb,cAAe,MACf,WAAY,SACZ,gBAAiB,EACjB,kBAAmB,GACnB,gBAAiB,EACjB,gBAAiB,UACjB,aAAc,EACd,IAAK,EACN,CACD,YAAa,CACX,KAAM,EACN,IAAK,EACN,CACD,WAAY,CACV,cAAe,MACf,eAAgB,gBAChB,WAAY,SACb,CACD,SAAU,CACR,SAAU,GACV,WAAY,MACZ,MAAO,UACP,KAAM,EACP,CACD,SAAU,CACR,SAAU,GACV,MAAO,UACP,WAAY,EACb,CACD,gBAAiB,CACf,UAAW,EACZ,CACD,aAAc,CACZ,MAAO,GACP,OAAQ,GACR,eAAgB,SAChB,WAAY,SACZ,aAAc,GACd,gBAAiB,UAClB,CACD,iBAAkB,CAChB,SAAU,GACV,WAAY,MACZ,MAAO,UACR,CACD,UAAW,CACT,OAAQ,EACT,CACD,eAAgB,CACd,gBAAiB,GACjB,kBAAmB,GACnB,gBAAiB,UACjB,aAAc,EACd,WAAY,SACZ,eAAgB,SACjB,CACD,UAAW,CACT,SAAU,GACV,MAAO,UACP,UAAW,SACZ,CACF,CAAC,CC7JI,EAAc,EAAuC,KAAK,CAMhE,SAAgB,GAAmC,CACjD,IAAM,EAAU,EAAW,EAAY,CACvC,GAAI,CAAC,EACH,MAAU,MACR,2HAED,CAEH,OAAO,EAuBT,MAAM,EAAmB,EAA4C,KAAK,CAM1E,SAAgB,GAA6C,CAC3D,IAAM,EAAU,EAAW,EAAiB,CAC5C,GAAI,CAAC,EACH,MAAU,MACR,uHAED,CAEH,OAAO,EAmET,SAAS,GAAS,CAChB,SACA,YACA,eACA,WACA,YACA,UACA,aACA,iBACA,YACY,CACZ,GAAM,CAAE,sBAAuB,GAAsB,CAa/C,EAAO,EAXmB,CAC9B,SACA,YACA,eACA,WACA,YACA,UACA,aACA,iBACD,CAE4B,CAGvB,EAAmB,EACvB,KAAO,IAAmB,CACxB,GAAI,CAAC,GAAoB,aACvB,MAAU,MAAM,4BAA4B,CAE9C,IAAM,EAAS,MAAM,EAAmB,cAAc,CAClD,EAAO,SAAW,WACpB,EAAK,SAAS,EAAQ,EAAO,EAGjC,CAAC,EAAoB,EAAK,CAC3B,CAGK,EAAgB,EAAY,SAAY,CAC5C,GAAI,CAAC,GAAoB,aACvB,MAAU,MAAM,4BAA4B,CAE9C,IAAM,EAAS,MAAM,EAAmB,cAAc,CAClD,EAAO,SAAW,WACpB,MAAM,EAAK,OAAO,EAAO,EAE1B,CAAC,EAAoB,EAAK,CAAC,CAExBC,EAAiC,CACrC,MAAO,EAAK,MACZ,cAAe,EAAK,cACpB,OAAQ,EAAK,OACb,YAAa,EAAK,YAClB,SAAU,EAAK,SACf,QAAS,EAAK,QACd,OAAQ,EAAK,OACb,MAAO,EAAK,MACZ,MAAO,EAAK,MACZ,SAAU,EAAK,SACf,gBAAiB,EAAK,gBACtB,aAAc,EAAK,aACnB,oBAAqB,EAAK,oBAC1B,mBACA,gBACD,CAEKC,EAA+B,CACnC,GAAG,EACH,OAAQ,EAAK,QACb,OAAQ,EAAK,MACd,CAED,OACE,EAAC,EAAY,SAAA,CAAS,MAAO,WAC1B,OAAO,GAAa,WAAa,EAAS,EAAY,CAAG,GACrC,CA0C3B,SAAS,GAAW,CAAE,YAA6B,CACjD,IAAM,EAAO,GAAgB,CAO7B,OAAO,EAAA,EAAA,CAAA,SAAG,EALiC,CACzC,OAAQ,EAAK,eAAiB,EAAE,CAChC,UAAW,EAAK,oBACjB,CAE8B,CAAA,CAAI,CAgCrC,SAAS,GAAU,CAAE,SAAQ,YAA4B,CACvD,IAAM,EAAO,GAAgB,CAEvB,EAAW,EAAK,eAAe,KAAM,GAAM,EAAE,SAAW,EAAO,CAErE,GAAI,CAAC,EAEH,OAAO,KAOT,IAAMC,EAAsC,CAC1C,SACA,WACA,MAAO,EAAK,OAAO,GACnB,SAAW,GAAU,EAAK,SAAS,EAAQ,EAAM,CACjD,MAAO,EAAK,YAAY,IAAI,EAAO,CACnC,SAVe,SAAY,CAC3B,MAAM,EAAK,iBAAiB,EAAO,EAUpC,CAED,OACE,EAAC,EAAiB,SAAA,CAAS,MAAO,WAC/B,OAAO,GAAa,WAAa,EAAS,EAAa,CAAG,GACjC,CAwChC,SAAS,GAAoB,CAAE,YAAsC,CACnE,IAAM,EAAQ,GAAqB,CAG7B,EAAa,EAAM,MACnB,EAAU,GAAY,SAAW,UACjC,EAAW,EAAW,GAAY,MAAM,MAAQ,KAAQ,KACxD,EAAW,EAAW,GAAY,MAAM,MAAQ,KAAQ,KAa9D,OAAO,EAAA,EAAA,CAAA,SAAG,EAX0C,CAClD,MAAO,EAAM,MACb,UACA,WACA,WACA,SAAU,EAAM,OAAO,UAAY,EACnC,OAAQ,EAAM,OAAO,QAAU,OAC/B,SAAU,EAAM,SAChB,UAAa,EAAM,SAAS,IAAA,GAAU,CACvC,CAE8B,CAAA,CAAI,CAoCrC,SAAS,GAAiB,CAAE,YAAmC,CAC7D,IAAM,EAAQ,GAAqB,CAG7B,EAAa,EAAM,MACnB,EAAU,GAAY,SAAW,UACjC,EACJ,OAAO,EAAM,OAAU,UAAa,EAAM,MAAiB,OAAS,EAYtE,OAAO,EAAA,EAAA,CAAA,SAAG,EAVuC,CAC/C,MAAO,EAAM,MACb,UACA,QACA,SAAU,EAAW,GAAY,MAAM,MAAQ,KAAQ,KACvD,SAAU,EAAW,GAAY,MAAM,MAAQ,KAAQ,KACvD,QAAS,EAAW,GAAY,MAAM,KAAO,KAAQ,KACrD,UAAa,EAAM,SAAS,IAAA,GAAU,CACvC,CAE8B,CAAA,CAAI,CA8BrC,SAAS,GAAa,CAAE,YAA+B,CACrD,IAAM,EAAO,GAAgB,CAS7B,OAAO,EAAA,EAAA,CAAA,SAAG,EAPmC,CAC3C,SAAU,EAAK,MAAM,SACrB,cAAe,EAAK,MAAM,cAC1B,WAAY,EAAK,MAAM,WACvB,OAAQ,EAAK,MAAM,OACpB,CAE8B,CAAA,CAAI,CAoCrC,SAAS,GAAW,CAAE,YAA6B,CACjD,IAAM,EAAO,GAAgB,CAY7B,OAAO,EAAA,EAAA,CAAA,SAAG,EAViC,CACzC,OAAQ,EAAK,MAAM,OACnB,gBAAiB,EAAK,MAAM,gBAC5B,gBAAiB,EAAK,MAAM,gBAC5B,MAAO,EAAK,MAAM,MAClB,MAAO,EAAK,MAAM,MAClB,YAAa,EAAK,MAAM,YACxB,YAAa,EAAK,MAAM,YACzB,CAE8B,CAAA,CAAI,CA8BrC,SAAS,GAAU,CAAE,YAA4B,CAC/C,IAAM,EAAO,GAAgB,CAS7B,OAAO,EAAA,EAAA,CAAA,SAAG,EAPgC,CACxC,MAAO,EAAK,MAAM,MAClB,SAAU,EAAK,MAAM,SAAW,QAChC,QAAS,EAAK,MAAM,OAAO,SAAW,KACtC,MAAO,EAAK,MACb,CAE8B,CAAA,CAAI,CAgCrC,SAAS,GAAW,CAAE,WAAU,YAA6B,CAC3D,IAAM,EAAO,GAAgB,CAEvBC,EAAqC,CACzC,OAAQ,EAAK,QACb,WAAY,GAAY,EAAK,UAAY,OAAO,KAAK,EAAK,OAAO,CAAC,SAAW,EAC7E,aAAc,EAAK,SACpB,CAED,OAAO,EAAA,EAAA,CAAA,SAAG,OAAO,GAAa,WAAa,EAAS,EAAY,CAAG,EAAA,CAAY,CAwBjF,SAAS,GAAW,CAAE,YAA6B,CACjD,IAAM,EAAO,GAAgB,CAEvBC,EAAqC,CACzC,OAAQ,EAAK,MACb,WAAY,CAAC,EAAK,SACnB,CAED,OAAO,EAAA,EAAA,CAAA,SAAG,OAAO,GAAa,WAAa,EAAS,EAAY,CAAG,EAAA,CAAY,CAwBjF,SAAS,GAAU,CAAE,YAA4B,CAC/C,IAAM,EAAO,GAAgB,CAEvBC,EAAoC,CACxC,MAAO,EAAK,MACZ,WAAY,EAAK,SAClB,CAED,OAAO,EAAA,EAAA,CAAA,SAAG,OAAO,GAAa,WAAa,EAAS,EAAY,CAAG,EAAA,CAAY,CA6CjF,SAAS,GAAgB,CAAE,YAAkC,CAC3D,IAAM,EAAO,GAAgB,CAU7B,OAAO,EAAA,EAAA,CAAA,SAAG,EARsC,CAC9C,YAAa,EAAK,SAClB,SAAU,EAAK,MAAM,SACrB,OAAQ,EAAK,MAAM,OACnB,cAAe,EAAK,cACpB,MAAO,EAAK,MACb,CAE8B,CAAA,CAAI,CAoDrC,MAAa,GAAO,OAAO,OAAO,GAAU,CAC1C,OAAQ,GACR,MAAO,OAAO,OAAO,GAAW,CAC9B,WAAY,GACZ,QAAS,GACV,CAAC,CACF,SAAU,GACV,OAAQ,GACR,MAAO,GACP,OAAQ,GACR,OAAQ,GACR,MAAO,GACP,YAAa,GACd,CAAC"}
1
+ {"version":3,"file":"index.mjs","names":["initialState","initialState","parts","getStatusColor","styles","styles","styles","styles","UploadProgress","item"],"sources":["../src/hooks/uploadista-context.ts","../src/types/platform-types.ts","../src/hooks/use-uploadista-context.ts","../src/hooks/use-upload.ts","../src/hooks/use-camera-upload.ts","../src/hooks/use-file-upload.ts","../src/contexts/flow-manager-context.tsx","../src/hooks/use-flow.ts","../src/hooks/use-multi-upload.ts","../src/hooks/use-gallery-upload.ts","../src/hooks/use-upload-metrics.ts","../src/utils/fileHelpers.ts","../src/utils/permissions.ts","../src/utils/uriHelpers.ts","../src/components/UploadProgress.tsx","../src/components/CameraUploadButton.tsx","../src/components/FileUploadButton.tsx","../src/components/flow-primitives.tsx","../src/components/GalleryUploadButton.tsx","../src/components/UploadList.tsx","../src/components/upload-primitives.tsx"],"sourcesContent":["import type { UploadistaEvent } from \"@uploadista/client-core\";\nimport { createContext } from \"react\";\nimport type { FileSystemProvider } from \"../types\";\nimport type { UseUploadistaClientReturn } from \"./use-uploadista-client\";\n\nexport interface UploadistaContextType extends UseUploadistaClientReturn {\n fileSystemProvider: FileSystemProvider;\n /**\n * Subscribe to events (used internally by hooks)\n * @internal\n */\n subscribeToEvents: (handler: (event: UploadistaEvent) => void) => () => void;\n}\n\nexport const UploadistaContext = createContext<\n UploadistaContextType | undefined\n>(undefined);\n","/**\n * Platform-specific type definitions for React Native\n *\n * React Native's Blob implementation differs from the browser's Blob API.\n * This file provides proper type definitions and guards for platform-specific behavior.\n */\n\n/**\n * BufferSource represents data that can be passed to Blob constructor\n * Includes both ArrayBuffer and typed arrays (Uint8Array, etc.)\n */\nexport type BufferSource = ArrayBuffer | ArrayBufferView;\n\n/**\n * React Native Blob constructor options\n * Extends standard BlobPropertyBag with platform-specific properties\n */\nexport interface ReactNativeBlobOptions {\n /** MIME type of the blob */\n type?: string;\n /** Platform-specific: file path for optimization (React Native only) */\n path?: string;\n}\n\n/**\n * React Native Blob constructor type\n * Unlike browser Blob, accepts BufferSource in the parts array\n */\nexport interface ReactNativeBlobConstructor {\n new (\n parts?: Array<BufferSource | Blob | string>,\n options?: ReactNativeBlobOptions,\n ): Blob;\n prototype: Blob;\n}\n\n/**\n * Type guard to check if a value is ArrayBuffer\n */\nexport function isArrayBuffer(value: unknown): value is ArrayBuffer {\n return value instanceof ArrayBuffer;\n}\n\n/**\n * Type guard to check if a value is ArrayBufferView (typed array)\n */\nexport function isArrayBufferView(value: unknown): value is ArrayBufferView {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"buffer\" in value &&\n value.buffer instanceof ArrayBuffer\n );\n}\n\n/**\n * Type guard to check if a value is BufferSource\n */\nexport function isBufferSource(value: unknown): value is BufferSource {\n return isArrayBuffer(value) || isArrayBufferView(value);\n}\n\n/**\n * Type guard to check if we're in React Native environment\n * (checks for navigator.product === 'ReactNative')\n */\nexport function isReactNativeEnvironment(): boolean {\n const g = globalThis as typeof globalThis & {\n navigator?: { product?: string };\n };\n return (\n typeof g !== \"undefined\" &&\n typeof g.navigator !== \"undefined\" &&\n g.navigator.product === \"ReactNative\"\n );\n}\n\n/**\n * Create a Blob from BufferSource with proper typing for React Native\n *\n * This function handles the platform differences between browser and React Native Blob APIs.\n * React Native's Blob constructor accepts BufferSource directly, while browser Blob requires\n * conversion to Uint8Array first in some cases.\n *\n * @param data - ArrayBuffer or typed array to convert to Blob\n * @param options - Blob options including MIME type\n * @returns Platform-appropriate Blob instance\n *\n * @example\n * ```typescript\n * const arrayBuffer = await fileSystemProvider.readFile(uri);\n * const blob = createBlobFromBuffer(arrayBuffer, {\n * type: 'image/jpeg'\n * });\n * ```\n */\nexport function createBlobFromBuffer(\n data: BufferSource,\n options?: ReactNativeBlobOptions,\n): Blob {\n // Convert ArrayBuffer to Uint8Array for consistent handling\n const uint8Array = data instanceof ArrayBuffer ? new Uint8Array(data) : data;\n\n // In React Native, Blob constructor accepts BufferSource\n // Cast to ReactNativeBlobConstructor to use the correct signature\n const BlobConstructor = Blob as unknown as ReactNativeBlobConstructor;\n return new BlobConstructor([uint8Array], options);\n}\n","import { useContext } from \"react\";\nimport { UploadistaContext } from \"./uploadista-context\";\n\n/**\n * Hook to access the Uploadista client instance\n * Must be used within an UploadistaProvider\n * @throws Error if used outside of UploadistaProvider\n * @returns The Uploadista client and file system provider\n */\nexport function useUploadistaContext() {\n const context = useContext(UploadistaContext);\n\n if (!context) {\n throw new Error(\n \"useUploadistaClient must be used within an UploadistaProvider\",\n );\n }\n\n return context;\n}\n","import type {\n UploadistaUploadOptions,\n UploadMetrics,\n} from \"@uploadista/client-core\";\nimport {\n UploadManager,\n type UploadState,\n type UploadStatus,\n} from \"@uploadista/client-core\";\nimport type { UploadFile } from \"@uploadista/core/types\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { FilePickResult } from \"../types\";\nimport { createBlobFromBuffer } from \"../types/platform-types\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n// Re-export types from core for convenience\nexport type { UploadState, UploadStatus };\n\nexport interface UseUploadOptions {\n /**\n * Upload metadata to attach to the file\n */\n metadata?: Record<string, string>;\n\n /**\n * Whether to defer the upload size calculation\n */\n uploadLengthDeferred?: boolean;\n\n /**\n * Manual upload size override\n */\n uploadSize?: number;\n\n /**\n * Called when upload progress updates\n */\n onProgress?: (\n uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n\n /**\n * Called when a chunk completes\n */\n onChunkComplete?: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => void;\n\n /**\n * Called when upload succeeds\n */\n onSuccess?: (result: UploadFile) => void;\n\n /**\n * Called when upload fails\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when upload is aborted\n */\n onAbort?: () => void;\n\n /**\n * Custom retry logic\n */\n onShouldRetry?: (error: Error, retryAttempt: number) => boolean;\n}\n\nexport interface UseUploadReturn {\n /**\n * Current upload state\n */\n state: UploadState;\n\n /**\n * Start uploading a file from a file pick result\n */\n upload: (file: FilePickResult) => Promise<void>;\n\n /**\n * Abort the current upload\n */\n abort: () => void;\n\n /**\n * Reset the upload state to idle\n */\n reset: () => void;\n\n /**\n * Retry the last failed upload\n */\n retry: () => void;\n\n /**\n * Whether an upload is currently active\n */\n isUploading: boolean;\n\n /**\n * Whether the upload can be retried\n */\n canRetry: boolean;\n\n /**\n * Upload metrics and performance insights from the client\n */\n metrics: UploadMetrics;\n}\n\nconst initialState: UploadState = {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: null,\n error: null,\n result: null,\n};\n\n/**\n * React Native hook for managing individual file uploads with full state management.\n * Provides upload progress tracking, error handling, abort functionality, and retry logic.\n *\n * Must be used within an UploadistaProvider.\n *\n * @param options - Upload configuration and event handlers\n * @returns Upload state and control methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const upload = useUpload({\n * onSuccess: (result) => console.log('Upload complete:', result),\n * onError: (error) => console.error('Upload failed:', error),\n * onProgress: (progress) => console.log('Progress:', progress + '%'),\n * });\n *\n * const handleFilePick = async () => {\n * const file = await pickFile();\n * if (file) await upload.upload(file);\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick File\" onPress={handleFilePick} />\n * {upload.isUploading && <Text>Progress: {upload.state.progress}%</Text>}\n * {upload.state.error && <Text>Error: {upload.state.error.message}</Text>}\n * {upload.canRetry && <Button title=\"Retry\" onPress={upload.retry} />}\n * <Button title=\"Abort\" onPress={upload.abort} disabled={!upload.isUploading} />\n * </View>\n * );\n * }\n * ```\n */\nexport function useUpload(options: UseUploadOptions = {}): UseUploadReturn {\n const { client, fileSystemProvider } = useUploadistaContext();\n const [state, setState] = useState<UploadState>(initialState);\n const managerRef = useRef<UploadManager | null>(null);\n const lastFileRef = useRef<FilePickResult | null>(null);\n\n // Create UploadManager instance\n useEffect(() => {\n // Create upload function that handles React Native file reading\n const uploadFn = async (input: unknown, opts: UploadistaUploadOptions) => {\n const file = input as FilePickResult;\n\n if (file.status === \"success\") {\n // Read file content from React Native file system\n const fileContent = await fileSystemProvider.readFile(file.data.uri);\n\n // Create a Blob from the file content using platform-aware utility\n const blob = createBlobFromBuffer(fileContent, {\n type: file.data.mimeType || \"application/octet-stream\",\n });\n\n // Upload the Blob\n return client.upload(blob, opts);\n }\n\n return Promise.resolve({ abort: () => {} });\n };\n\n managerRef.current = new UploadManager(\n uploadFn,\n {\n onStateChange: setState,\n onProgress: options.onProgress,\n onChunkComplete: options.onChunkComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n onAbort: options.onAbort,\n },\n {\n metadata: options.metadata,\n uploadLengthDeferred: options.uploadLengthDeferred,\n uploadSize: options.uploadSize,\n onShouldRetry: options.onShouldRetry,\n },\n );\n\n return () => {\n managerRef.current?.cleanup();\n };\n }, [client, fileSystemProvider, options]);\n\n // Upload function - stores file reference for retry\n const upload = useCallback(async (file: FilePickResult) => {\n lastFileRef.current = file;\n await managerRef.current?.upload(file);\n }, []);\n\n // Abort function\n const abort = useCallback(() => {\n managerRef.current?.abort();\n }, []);\n\n // Reset function\n const reset = useCallback(() => {\n managerRef.current?.reset();\n lastFileRef.current = null;\n }, []);\n\n // Retry function\n const retry = useCallback(() => {\n if (lastFileRef.current && managerRef.current?.canRetry()) {\n managerRef.current.retry();\n }\n }, []);\n\n // Derive computed values from state\n const isUploading = state.status === \"uploading\";\n const canRetry = managerRef.current?.canRetry() ?? false;\n\n // Create metrics object that delegates to the upload client\n const metrics: UploadMetrics = {\n getInsights: () => client.getChunkingInsights(),\n exportMetrics: () => client.exportMetrics(),\n getNetworkMetrics: () => client.getNetworkMetrics(),\n getNetworkCondition: () => client.getNetworkCondition(),\n resetMetrics: () => client.resetMetrics(),\n };\n\n return {\n state,\n upload,\n abort,\n reset,\n retry,\n isUploading,\n canRetry,\n metrics,\n };\n}\n","import { useCallback } from \"react\";\nimport type { UseCameraUploadOptions } from \"../types\";\nimport { useUpload } from \"./use-upload\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n/**\n * Hook for capturing photos and uploading them\n * Handles camera permissions and capture flow\n * @param options - Camera upload configuration\n * @returns Upload state and camera capture/upload function\n */\nexport function useCameraUpload(options?: UseCameraUploadOptions) {\n const { fileSystemProvider } = useUploadistaContext();\n const uploadHook = useUpload({\n metadata: options?.metadata,\n onSuccess: options?.onSuccess,\n onError: options?.onError,\n onProgress: options?.onProgress,\n });\n\n // Capture and upload photo\n const captureAndUpload = useCallback(async () => {\n try {\n // Capture photo with camera\n const photo = await fileSystemProvider.pickCamera(options?.cameraOptions);\n\n // Upload captured photo\n await uploadHook.upload(photo);\n } catch (error) {\n console.error(\"Camera capture error:\", error);\n }\n }, [fileSystemProvider, options?.cameraOptions, uploadHook]);\n\n return {\n ...uploadHook,\n captureAndUpload,\n };\n}\n","import { useCallback } from \"react\";\nimport type { UseFileUploadOptions } from \"../types\";\nimport { useUpload } from \"./use-upload\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n/**\n * Hook for selecting and uploading generic files (documents, etc.)\n * @param options - File upload configuration\n * @returns Upload state and file picker/upload function\n */\nexport function useFileUpload(options?: UseFileUploadOptions) {\n const { fileSystemProvider } = useUploadistaContext();\n const uploadHook = useUpload({\n metadata: options?.metadata,\n onSuccess: options?.onSuccess,\n onError: options?.onError,\n onProgress: options?.onProgress,\n });\n\n // Pick and upload file\n const pickAndUpload = useCallback(async () => {\n try {\n // Pick file\n const file = await fileSystemProvider.pickDocument({\n allowedTypes: options?.allowedTypes,\n });\n\n // Upload file\n await uploadHook.upload(file);\n } catch (error) {\n console.error(\"File selection error:\", error);\n throw error;\n }\n }, [fileSystemProvider, options?.allowedTypes, uploadHook]);\n\n return {\n ...uploadHook,\n pickAndUpload,\n };\n}\n","import type { UploadistaEvent } from \"@uploadista/client-core\";\nimport {\n FlowManager,\n type FlowManagerCallbacks,\n type FlowUploadOptions,\n} from \"@uploadista/client-core\";\nimport { EventType, type FlowEvent } from \"@uploadista/core/flow\";\nimport { UploadEventType } from \"@uploadista/core/types\";\nimport type { ReactNode } from \"react\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n} from \"react\";\nimport { useUploadistaContext } from \"../hooks/use-uploadista-context\";\nimport type { ReactNativeUploadInput } from \"../types\";\n\n/**\n * Type guard to check if an event is a flow event\n */\nfunction isFlowEvent(event: UploadistaEvent): event is FlowEvent {\n const flowEvent = event as FlowEvent;\n return (\n flowEvent.eventType === EventType.FlowStart ||\n flowEvent.eventType === EventType.FlowEnd ||\n flowEvent.eventType === EventType.FlowError ||\n flowEvent.eventType === EventType.NodeStart ||\n flowEvent.eventType === EventType.NodeEnd ||\n flowEvent.eventType === EventType.NodePause ||\n flowEvent.eventType === EventType.NodeResume ||\n flowEvent.eventType === EventType.NodeError\n );\n}\n\n/**\n * Internal manager registry entry with ref counting\n */\ninterface ManagerEntry {\n manager: FlowManager<unknown>;\n refCount: number;\n flowId: string;\n}\n\n/**\n * Context value providing access to flow managers\n */\ninterface FlowManagerContextValue {\n /**\n * Get or create a flow manager for the given flow ID.\n * Increments ref count - must call releaseManager when done.\n *\n * @param flowId - Unique identifier for the flow\n * @param callbacks - Callbacks for state changes and lifecycle events\n * @param options - Flow configuration options\n * @returns FlowManager instance\n */\n getManager: (\n flowId: string,\n callbacks: FlowManagerCallbacks,\n options: FlowUploadOptions,\n ) => FlowManager<unknown>;\n\n /**\n * Release a flow manager reference.\n * Decrements ref count and cleans up when reaching zero.\n *\n * @param flowId - Unique identifier for the flow to release\n */\n releaseManager: (flowId: string) => void;\n}\n\nconst FlowManagerContext = createContext<FlowManagerContextValue | undefined>(\n undefined,\n);\n\n/**\n * Props for FlowManagerProvider\n */\ninterface FlowManagerProviderProps {\n children: ReactNode;\n}\n\n/**\n * Provider that manages FlowManager instances with ref counting and event routing.\n * Ensures managers persist across component re-renders and are only cleaned up\n * when all consuming components unmount.\n *\n * This provider should be nested inside UploadistaProvider to access the upload client\n * and event subscription system.\n *\n * @example\n * ```tsx\n * <UploadistaProvider baseUrl=\"https://api.example.com\" storageId=\"default\">\n * <FlowManagerProvider>\n * <App />\n * </FlowManagerProvider>\n * </UploadistaProvider>\n * ```\n */\nexport function FlowManagerProvider({ children }: FlowManagerProviderProps) {\n const { client, subscribeToEvents } = useUploadistaContext();\n const managersRef = useRef(new Map<string, ManagerEntry>());\n\n // Subscribe to all events and route to appropriate managers\n useEffect(() => {\n const unsubscribe = subscribeToEvents((event: UploadistaEvent) => {\n // Route flow events to all managers (they filter by jobId internally)\n if (isFlowEvent(event)) {\n for (const entry of managersRef.current.values()) {\n entry.manager.handleFlowEvent(event);\n }\n return;\n }\n\n // Route upload progress events to all managers\n if (\n \"type\" in event &&\n event.type === UploadEventType.UPLOAD_PROGRESS &&\n \"data\" in event\n ) {\n for (const entry of managersRef.current.values()) {\n entry.manager.handleUploadProgress(\n event.data.id,\n event.data.progress,\n event.data.total,\n );\n }\n }\n });\n\n return unsubscribe;\n }, [subscribeToEvents]);\n\n const getManager = useCallback(\n (\n flowId: string,\n callbacks: FlowManagerCallbacks,\n options: FlowUploadOptions,\n ): FlowManager<unknown> => {\n const existing = managersRef.current.get(flowId);\n\n if (existing) {\n // Increment ref count for existing manager\n existing.refCount++;\n return existing.manager;\n }\n\n // Create new manager using client from hook scope\n const manager = new FlowManager<ReactNativeUploadInput>(\n client.uploadWithFlow,\n callbacks,\n options,\n client.multiInputFlowUpload,\n );\n\n managersRef.current.set(flowId, {\n manager,\n refCount: 1,\n flowId,\n });\n\n return manager;\n },\n [client],\n );\n\n const releaseManager = useCallback((flowId: string) => {\n const existing = managersRef.current.get(flowId);\n if (!existing) return;\n\n existing.refCount--;\n\n // Clean up when no more refs\n if (existing.refCount <= 0) {\n existing.manager.cleanup();\n managersRef.current.delete(flowId);\n }\n }, []);\n\n return (\n <FlowManagerContext.Provider value={{ getManager, releaseManager }}>\n {children}\n </FlowManagerContext.Provider>\n );\n}\n\n/**\n * Hook to access the FlowManager context.\n * Must be used within a FlowManagerProvider.\n *\n * @returns FlowManager context value with getManager and releaseManager functions\n * @throws Error if used outside of FlowManagerProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { getManager, releaseManager } = useFlowManagerContext();\n * // Use to create managers...\n * }\n * ```\n */\nexport function useFlowManagerContext(): FlowManagerContextValue {\n const context = useContext(FlowManagerContext);\n\n if (context === undefined) {\n throw new Error(\n \"useFlowManagerContext must be used within a FlowManagerProvider. \" +\n \"Make sure to wrap your component tree with <FlowManagerProvider>.\",\n );\n }\n\n return context;\n}\n","import type {\n FlowManager,\n FlowUploadState,\n FlowUploadStatus,\n InputExecutionState,\n} from \"@uploadista/client-core\";\nimport type { TypedOutput } from \"@uploadista/core/flow\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useFlowManagerContext } from \"../contexts/flow-manager-context\";\nimport type { FilePickResult } from \"../types\";\nimport { createBlobFromBuffer } from \"../types/platform-types\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n// Re-export types from core for convenience\nexport type { FlowUploadState, FlowUploadStatus, InputExecutionState };\n\n/**\n * Input metadata discovered from the flow\n */\nexport interface FlowInputMetadata {\n /** Input node ID */\n nodeId: string;\n /** Human-readable node name */\n nodeName: string;\n /** Node description explaining what input is needed */\n nodeDescription: string;\n /** Input type ID from inputTypeRegistry - describes how clients interact with this node */\n inputTypeId?: string;\n /** Whether this input is required */\n required: boolean;\n}\n\n/**\n * Options for the useFlow hook\n */\nexport interface UseFlowOptions {\n /** Flow ID to execute */\n flowId: string;\n /** Storage ID for the upload */\n storageId: string;\n /** Output node ID for the flow */\n outputNodeId?: string;\n /** Metadata to pass to flow */\n metadata?: Record<string, unknown>;\n /** Called when upload succeeds (receives typed outputs from all output nodes) */\n onSuccess?: (outputs: TypedOutput[]) => void;\n /** Called when the flow completes successfully (receives full flow outputs) */\n onFlowComplete?: (outputs: TypedOutput[]) => void;\n /** Called when upload fails */\n onError?: (error: Error) => void;\n /** Called when upload progress updates */\n onProgress?: (\n progress: number,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n /** Called when a chunk completes */\n onChunkComplete?: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => void;\n}\n\n/**\n * Return value from the useFlow hook with upload control methods and state.\n *\n * @property state - Complete flow upload state with progress and outputs\n * @property inputMetadata - Metadata about discovered input nodes (null until discovered)\n * @property inputStates - Per-input execution state for multi-input flows\n * @property inputs - Current input values set via setInput()\n * @property setInput - Set an input value for a specific node (for progressive provision)\n * @property execute - Execute the flow with current inputs (auto-detects types)\n * @property upload - Convenience method for single-file upload (same as execute with one file input)\n * @property abort - Cancel the current upload and flow execution\n * @property reset - Reset state to idle (clears all data)\n * @property retry - Retry the last failed upload\n * @property isActive - True when upload or processing is active\n * @property isUploadingFile - True only during file upload phase\n * @property isProcessing - True only during flow processing phase\n * @property isDiscoveringInputs - True while discovering flow inputs\n * @property canRetry - True if a retry is possible\n */\nexport interface UseFlowReturn {\n /**\n * Current upload state\n */\n state: FlowUploadState;\n\n /**\n * Discovered input nodes metadata (null until discovery completes)\n */\n inputMetadata: FlowInputMetadata[] | null;\n\n /**\n * Per-input execution state for multi-input flows\n */\n inputStates: ReadonlyMap<string, InputExecutionState>;\n\n /**\n * Current inputs set via setInput()\n */\n inputs: Record<string, unknown>;\n\n /**\n * Set an input value for a specific node.\n * For progressive input provision before calling execute().\n *\n * @param nodeId - The input node ID\n * @param value - The input value (FilePickResult, URL string, or structured data)\n */\n setInput: (nodeId: string, value: FilePickResult | string | unknown) => void;\n\n /**\n * Execute the flow with current inputs.\n * Automatically detects input types and routes appropriately.\n * For single input, uses standard upload path.\n * For multiple inputs, requires multiInputUploadFn.\n */\n execute: () => Promise<void>;\n\n /**\n * Upload a single file through the flow (convenience method).\n * Equivalent to setInput(firstNodeId, file) + execute().\n *\n * @param file - FilePickResult from a picker\n */\n upload: (file: FilePickResult) => Promise<void>;\n\n /**\n * Abort the current upload\n */\n abort: () => void;\n\n /**\n * Reset the upload state and clear all inputs\n */\n reset: () => void;\n\n /**\n * Retry the last failed upload\n */\n retry: () => void;\n\n /**\n * Whether an upload or flow execution is in progress (uploading OR processing)\n */\n isActive: boolean;\n\n /**\n * Whether the file is currently being uploaded (chunks being sent)\n */\n isUploadingFile: boolean;\n\n /**\n * Whether the flow is currently processing (after upload completes)\n */\n isProcessing: boolean;\n\n /**\n * Whether the hook is discovering flow inputs\n */\n isDiscoveringInputs: boolean;\n\n /**\n * Whether a retry is possible (after error or abort with stored inputs)\n */\n canRetry: boolean;\n}\n\nconst initialState: FlowUploadState = {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: null,\n error: null,\n jobId: null,\n flowStarted: false,\n currentNodeName: null,\n currentNodeType: null,\n flowOutputs: null,\n};\n\n/**\n * React Native hook for executing flows with single or multiple inputs.\n * Automatically discovers input nodes and detects input types (File, URL, structured data).\n * Supports progressive input provision via setInput() and execute().\n *\n * This is the unified flow hook that replaces useFlowUpload for advanced use cases.\n * It provides:\n * - Auto-discovery of flow input nodes\n * - Automatic input type detection (FilePickResult -> upload, string -> URL, object -> data)\n * - Progressive input provision via setInput()\n * - Multi-input support with parallel coordination\n * - Per-input state tracking\n *\n * Must be used within FlowManagerProvider (which must be within UploadistaProvider).\n * Flow events are automatically routed by the provider to the appropriate manager.\n *\n * @param options - Flow upload configuration including flow ID and event handlers\n * @returns Flow upload state and control methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const flow = useFlow({\n * flowId: 'image-processing-flow',\n * storageId: 'my-storage',\n * onSuccess: (outputs) => console.log('Flow complete:', outputs),\n * onError: (error) => console.error('Flow failed:', error),\n * });\n *\n * const handlePickFile = async () => {\n * const file = await fileSystemProvider.pickDocument();\n * if (file) {\n * await flow.upload(file);\n * }\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick File\" onPress={handlePickFile} />\n * {flow.isActive && <Text>Progress: {flow.state.progress}%</Text>}\n * {flow.inputMetadata && (\n * <Text>Found {flow.inputMetadata.length} input nodes</Text>\n * )}\n * <Button title=\"Abort\" onPress={flow.abort} disabled={!flow.isActive} />\n * </View>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Multi-input flow\n * function MultiInputComponent() {\n * const flow = useFlow({\n * flowId: 'multi-source-flow',\n * storageId: 'my-storage',\n * });\n *\n * const handlePickPrimary = async () => {\n * const file = await fileSystemProvider.pickDocument();\n * if (file.status === 'success') {\n * flow.setInput('primary-input', file);\n * }\n * };\n *\n * const handleSetUrl = (url: string) => {\n * flow.setInput('url-input', url);\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick Primary\" onPress={handlePickPrimary} />\n * <TextInput onChangeText={handleSetUrl} placeholder=\"Enter URL\" />\n * <Button title=\"Execute\" onPress={flow.execute} />\n * </View>\n * );\n * }\n * ```\n *\n * @see {@link useFlowUpload} for a simpler file-only upload hook\n */\nexport function useFlow(options: UseFlowOptions): UseFlowReturn {\n const { client, fileSystemProvider } = useUploadistaContext();\n const { getManager, releaseManager } = useFlowManagerContext();\n const [state, setState] = useState<FlowUploadState>(initialState);\n const [inputMetadata, setInputMetadata] = useState<\n FlowInputMetadata[] | null\n >(null);\n const [isDiscoveringInputs, setIsDiscoveringInputs] = useState(false);\n const [inputs, setInputs] = useState<Record<string, unknown>>({});\n const [inputStates, setInputStates] = useState<\n ReadonlyMap<string, InputExecutionState>\n >(new Map());\n const managerRef = useRef<FlowManager<unknown> | null>(null);\n const lastInputsRef = useRef<Record<string, unknown> | null>(null);\n\n // Store callbacks in refs so they can be updated without recreating the manager\n const callbacksRef = useRef(options);\n\n // Update refs on every render to capture latest callbacks\n useEffect(() => {\n callbacksRef.current = options;\n });\n\n // Auto-discover flow inputs on mount\n useEffect(() => {\n const discoverInputs = async () => {\n setIsDiscoveringInputs(true);\n try {\n const { flow } = await client.getFlow(options.flowId);\n\n // Find all input nodes\n const inputNodes = flow.nodes.filter((node) => node.type === \"input\");\n\n const metadata: FlowInputMetadata[] = inputNodes.map((node) => ({\n nodeId: node.id,\n nodeName: node.name,\n nodeDescription: node.description,\n inputTypeId: node.inputTypeId,\n required: true,\n }));\n\n setInputMetadata(metadata);\n } catch (error) {\n console.error(\"Failed to discover flow inputs:\", error);\n } finally {\n setIsDiscoveringInputs(false);\n }\n };\n\n discoverInputs();\n }, [client, options.flowId]);\n\n // Get or create manager from context when component mounts\n // biome-ignore lint/correctness/useExhaustiveDependencies: we don't want to recreate the manager on every render\n useEffect(() => {\n const flowId = options.flowId;\n\n // Create stable callback wrappers that call the latest callbacks via refs\n const stableCallbacks = {\n onStateChange: (newState: FlowUploadState) => {\n setState(newState);\n },\n onProgress: (\n _uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => {\n if (callbacksRef.current.onProgress) {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n callbacksRef.current.onProgress(progress, bytesUploaded, totalBytes);\n }\n },\n onChunkComplete: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => {\n callbacksRef.current.onChunkComplete?.(\n chunkSize,\n bytesAccepted,\n bytesTotal,\n );\n },\n onFlowComplete: (outputs: TypedOutput[]) => {\n callbacksRef.current.onFlowComplete?.(outputs);\n },\n onSuccess: (outputs: TypedOutput[]) => {\n callbacksRef.current.onSuccess?.(outputs);\n },\n onError: (error: Error) => {\n callbacksRef.current.onError?.(error);\n },\n onAbort: () => {\n // onAbort not exposed in public API\n },\n };\n\n // Get manager from context (creates if doesn't exist, increments ref count)\n managerRef.current = getManager(flowId, stableCallbacks, {\n flowConfig: {\n flowId: options.flowId,\n storageId: options.storageId,\n outputNodeId: options.outputNodeId,\n metadata: options.metadata as Record<string, string> | undefined,\n },\n onChunkComplete: options.onChunkComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n });\n\n // Set up interval to poll input states for multi-input flows\n const pollInterval = setInterval(() => {\n if (managerRef.current) {\n const states = managerRef.current.getInputStates();\n if (states.size > 0) {\n setInputStates(new Map(states));\n }\n }\n }, 100); // Poll every 100ms\n\n // Release manager when component unmounts or flowId changes\n return () => {\n clearInterval(pollInterval);\n releaseManager(flowId);\n managerRef.current = null;\n };\n }, [\n options.flowId,\n options.storageId,\n options.outputNodeId,\n getManager,\n releaseManager,\n ]);\n\n // Set an input value\n const setInput = useCallback(\n (nodeId: string, value: FilePickResult | string | unknown) => {\n setInputs((prev) => ({ ...prev, [nodeId]: value }));\n },\n [],\n );\n\n // Helper to convert FilePickResult to Blob\n const filePickToBlob = useCallback(\n async (file: FilePickResult): Promise<Blob | null> => {\n if (file.status === \"cancelled\") {\n return null;\n }\n if (file.status === \"error\") {\n throw file.error;\n }\n\n const fileContent = await fileSystemProvider.readFile(file.data.uri);\n return createBlobFromBuffer(fileContent, {\n type: file.data.mimeType || \"application/octet-stream\",\n });\n },\n [fileSystemProvider],\n );\n\n // Execute flow with current inputs\n const execute = useCallback(async () => {\n if (!managerRef.current) {\n throw new Error(\"FlowManager not initialized\");\n }\n\n if (Object.keys(inputs).length === 0) {\n throw new Error(\n \"No inputs provided. Use setInput() to provide inputs before calling execute()\",\n );\n }\n\n // Store inputs for retry\n lastInputsRef.current = { ...inputs };\n\n // Convert FilePickResults to Blobs\n const processedInputs: Record<string, unknown> = {};\n\n for (const [nodeId, value] of Object.entries(inputs)) {\n // Check if value is a FilePickResult\n if (\n value &&\n typeof value === \"object\" &&\n \"status\" in value &&\n (value.status === \"success\" ||\n value.status === \"cancelled\" ||\n value.status === \"error\")\n ) {\n const blob = await filePickToBlob(value as FilePickResult);\n if (blob) {\n processedInputs[nodeId] = blob;\n }\n // If blob is null (cancelled), skip this input\n } else {\n // Pass through strings (URLs) and other values as-is\n processedInputs[nodeId] = value;\n }\n }\n\n if (Object.keys(processedInputs).length === 0) {\n throw new Error(\n \"No valid inputs after processing. All files may have been cancelled.\",\n );\n }\n\n await managerRef.current.executeFlow(processedInputs);\n }, [inputs, filePickToBlob]);\n\n // Convenience method for single file upload\n const upload = useCallback(\n async (file: FilePickResult) => {\n // Handle cancelled picker\n if (file.status === \"cancelled\") {\n return;\n }\n\n // Handle picker error\n if (file.status === \"error\") {\n options.onError?.(file.error);\n return;\n }\n\n if (!managerRef.current) {\n throw new Error(\"FlowManager not initialized\");\n }\n\n // Store for retry\n if (inputMetadata && inputMetadata.length > 0) {\n const firstInputNode = inputMetadata[0];\n if (firstInputNode) {\n lastInputsRef.current = { [firstInputNode.nodeId]: file };\n }\n }\n\n try {\n const fileContent = await fileSystemProvider.readFile(file.data.uri);\n const blob = createBlobFromBuffer(fileContent, {\n type: file.data.mimeType || \"application/octet-stream\",\n });\n\n // If we have input metadata, use the first input node\n if (inputMetadata && inputMetadata.length > 0) {\n const firstInputNode = inputMetadata[0];\n if (!firstInputNode) {\n throw new Error(\"No input nodes found\");\n }\n setInputs({ [firstInputNode.nodeId]: file });\n await managerRef.current.executeFlow({\n [firstInputNode.nodeId]: blob,\n });\n } else {\n // Fall back to direct upload (manager will handle discovery)\n await managerRef.current.upload(blob);\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n },\n [inputMetadata, fileSystemProvider, options],\n );\n\n const abort = useCallback(() => {\n managerRef.current?.abort();\n }, []);\n\n const reset = useCallback(() => {\n managerRef.current?.reset();\n setInputs({});\n setInputStates(new Map());\n lastInputsRef.current = null;\n }, []);\n\n const retry = useCallback(() => {\n if (\n lastInputsRef.current &&\n (state.status === \"error\" || state.status === \"aborted\")\n ) {\n // Restore inputs and re-execute\n setInputs(lastInputsRef.current);\n execute();\n }\n }, [execute, state.status]);\n\n // Derive computed values from state (reactive to state changes)\n const isActive =\n state.status === \"uploading\" || state.status === \"processing\";\n const isUploadingFile = state.status === \"uploading\";\n const isProcessing = state.status === \"processing\";\n const canRetry =\n (state.status === \"error\" || state.status === \"aborted\") &&\n lastInputsRef.current !== null;\n\n return {\n state,\n inputMetadata,\n inputStates,\n inputs,\n setInput,\n execute,\n upload,\n abort,\n reset,\n retry,\n isActive,\n isUploadingFile,\n isProcessing,\n isDiscoveringInputs,\n canRetry,\n };\n}\n","import type { UploadFile } from \"@uploadista/core/types\";\nimport { useCallback, useRef, useState } from \"react\";\nimport type { FilePickResult, UseMultiUploadOptions } from \"../types\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\nexport interface UploadItemState {\n id: string;\n file: Extract<FilePickResult, { status: \"success\" }>;\n status: \"idle\" | \"uploading\" | \"success\" | \"error\" | \"aborted\";\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error: Error | null;\n result: UploadFile | null;\n}\n\nexport interface MultiUploadState {\n items: UploadItemState[];\n totalProgress: number;\n totalUploaded: number;\n totalBytes: number;\n activeCount: number;\n completedCount: number;\n failedCount: number;\n}\n\nconst initialState: MultiUploadState = {\n items: [],\n totalProgress: 0,\n totalUploaded: 0,\n totalBytes: 0,\n activeCount: 0,\n completedCount: 0,\n failedCount: 0,\n};\n\n/**\n * Hook for managing multiple concurrent file uploads with progress tracking.\n * Each file is uploaded independently using the core upload client.\n *\n * Must be used within an UploadistaProvider.\n *\n * @param options - Multi-upload configuration options\n * @returns Multi-upload state and control methods\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const multiUpload = useMultiUpload({\n * maxConcurrent: 3,\n * onSuccess: (result) => console.log('File uploaded:', result),\n * onError: (error) => console.error('Upload failed:', error),\n * });\n *\n * const handlePickFiles = async () => {\n * const files = await fileSystemProvider.pickImage({ allowMultiple: true });\n * multiUpload.addFiles(files);\n * await multiUpload.startUploads();\n * };\n *\n * return (\n * <View>\n * <Button title=\"Pick Files\" onPress={handlePickFiles} />\n * <Text>Progress: {multiUpload.state.totalProgress}%</Text>\n * <Text>Active: {multiUpload.state.activeCount}</Text>\n * <Text>Completed: {multiUpload.state.completedCount}/{multiUpload.state.items.length}</Text>\n * </View>\n * );\n * }\n * ```\n */\nexport function useMultiUpload(options: UseMultiUploadOptions = {}) {\n const { client } = useUploadistaContext();\n const [state, setState] = useState<MultiUploadState>(initialState);\n const abortControllersRef = useRef<Map<string, { abort: () => void }>>(\n new Map(),\n );\n const nextIdRef = useRef(0);\n // Use ref to track items synchronously\n const itemsRef = useRef<UploadItemState[]>([]);\n\n const generateId = useCallback(() => {\n return `upload-${Date.now()}-${nextIdRef.current++}`;\n }, []);\n\n const updateAggregateStats = useCallback((items: UploadItemState[]) => {\n const totalBytes = items.reduce((sum, item) => sum + item.totalBytes, 0);\n const totalUploaded = items.reduce(\n (sum, item) => sum + item.bytesUploaded,\n 0,\n );\n const totalProgress =\n totalBytes > 0 ? Math.round((totalUploaded / totalBytes) * 100) : 0;\n const activeCount = items.filter(\n (item) => item.status === \"uploading\",\n ).length;\n const completedCount = items.filter(\n (item) => item.status === \"success\",\n ).length;\n const failedCount = items.filter((item) => item.status === \"error\").length;\n\n // Update ref synchronously\n itemsRef.current = items;\n\n setState((prev) => ({\n ...prev,\n items,\n totalProgress,\n totalUploaded,\n totalBytes,\n activeCount,\n completedCount,\n failedCount,\n }));\n }, []);\n\n const addFiles = useCallback(\n (files: FilePickResult[]) => {\n // Filter out cancelled and error results, only keep successful picks\n const successfulFiles = files.filter(\n (file): file is Extract<FilePickResult, { status: \"success\" }> =>\n file.status === \"success\",\n );\n\n const newItems: UploadItemState[] = successfulFiles.map((file) => ({\n id: generateId(),\n file,\n status: \"idle\" as const,\n progress: 0,\n bytesUploaded: 0,\n totalBytes: file.data.size,\n error: null,\n result: null,\n }));\n\n // Update ref synchronously\n const updatedItems = [...itemsRef.current, ...newItems];\n itemsRef.current = updatedItems;\n\n setState((prev) => {\n const totalBytes = updatedItems.reduce(\n (sum, item) => sum + item.totalBytes,\n 0,\n );\n return {\n ...prev,\n items: updatedItems,\n totalBytes,\n };\n });\n\n return newItems.map((item) => item.id);\n },\n [generateId],\n );\n\n const uploadSingleItem = useCallback(\n async (item: UploadItemState) => {\n try {\n console.log(\"Uploading item:\", item.file.data.name);\n // Update status to uploading\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id ? { ...i, status: \"uploading\" as const } : i,\n );\n updateAggregateStats(updatedItems);\n\n // Convert file URI to Blob using fetch (React Native compatible)\n // React Native's Blob doesn't support ArrayBuffer/Uint8Array constructor\n const response = await fetch(item.file.data.uri);\n const blob = await response.blob();\n\n // Override blob type if we have mimeType from picker\n // Use type assertion to handle differences between Expo's BlobOptions and standard BlobPropertyBag\n let uploadInput: Blob = blob;\n if (item.file.data.mimeType) {\n const blobOptions = {\n type: item.file.data.mimeType,\n lastModified: Date.now(),\n };\n // biome-ignore lint/suspicious/noExplicitAny: Expo and bare RN have incompatible Blob types\n uploadInput = new Blob([blob], blobOptions as any);\n }\n\n // Start upload using the client\n console.log(\"Uploading input:\", uploadInput);\n const uploadPromise = client.upload(uploadInput, {\n metadata: options.metadata,\n\n onProgress: (\n _uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id\n ? {\n ...i,\n progress,\n bytesUploaded,\n totalBytes: totalBytes || i.totalBytes,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n },\n\n onSuccess: (result: UploadFile) => {\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id\n ? {\n ...i,\n status: \"success\" as const,\n progress: 100,\n result,\n bytesUploaded: result.size || i.totalBytes,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n\n options.onSuccess?.(result);\n abortControllersRef.current.delete(item.id);\n },\n\n onError: (error: Error) => {\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id ? { ...i, status: \"error\" as const, error } : i,\n );\n updateAggregateStats(updatedItems);\n\n options.onError?.(error);\n abortControllersRef.current.delete(item.id);\n },\n });\n\n // Store abort controller\n const controller = await uploadPromise;\n abortControllersRef.current.set(item.id, controller);\n } catch (error) {\n console.error(\"Error uploading item:\", error);\n const updatedItems = itemsRef.current.map((i) =>\n i.id === item.id\n ? {\n ...i,\n status: \"error\" as const,\n error: error as Error,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n\n options.onError?.(error as Error);\n abortControllersRef.current.delete(item.id);\n }\n },\n [client, options, updateAggregateStats],\n );\n\n const startUploads = useCallback(\n async (itemIds?: string[]) => {\n const maxConcurrent = options.maxConcurrent || 3;\n\n // Get items from ref (synchronous access to latest items)\n const itemsToUpload = itemIds\n ? itemsRef.current.filter(\n (item) => itemIds.includes(item.id) && item.status === \"idle\",\n )\n : itemsRef.current.filter((item) => item.status === \"idle\");\n\n console.log(\"Items to upload:\", itemsToUpload.length, itemsToUpload);\n\n // Process items in batches\n for (let i = 0; i < itemsToUpload.length; i += maxConcurrent) {\n const batch = itemsToUpload.slice(i, i + maxConcurrent);\n await Promise.all(batch.map((item) => uploadSingleItem(item)));\n }\n },\n [options.maxConcurrent, uploadSingleItem],\n );\n\n const removeItem = useCallback(\n (id: string) => {\n const controller = abortControllersRef.current.get(id);\n if (controller) {\n controller.abort();\n abortControllersRef.current.delete(id);\n }\n\n const updatedItems = itemsRef.current.filter((item) => item.id !== id);\n updateAggregateStats(updatedItems);\n },\n [updateAggregateStats],\n );\n\n const abortItem = useCallback(\n (id: string) => {\n const controller = abortControllersRef.current.get(id);\n if (controller) {\n controller.abort();\n abortControllersRef.current.delete(id);\n }\n\n const updatedItems = itemsRef.current.map((item) =>\n item.id === id ? { ...item, status: \"aborted\" as const } : item,\n );\n updateAggregateStats(updatedItems);\n },\n [updateAggregateStats],\n );\n\n const clear = useCallback(() => {\n // Abort all active uploads\n abortControllersRef.current.forEach((controller) => {\n controller.abort();\n });\n abortControllersRef.current.clear();\n\n // Clear ref\n itemsRef.current = [];\n\n setState(initialState);\n }, []);\n\n const retryItem = useCallback(\n async (id: string) => {\n const item = itemsRef.current.find((i) => i.id === id);\n if (item && (item.status === \"error\" || item.status === \"aborted\")) {\n // Reset item status to idle\n const updatedItems = itemsRef.current.map((i) =>\n i.id === id\n ? {\n ...i,\n status: \"idle\" as const,\n progress: 0,\n bytesUploaded: 0,\n error: null,\n }\n : i,\n );\n updateAggregateStats(updatedItems);\n\n // Upload it (get the reset item from the updated items)\n const resetItem = itemsRef.current.find((i) => i.id === id);\n if (resetItem) {\n await uploadSingleItem(resetItem);\n }\n }\n },\n [uploadSingleItem, updateAggregateStats],\n );\n\n return {\n state,\n addFiles,\n startUploads,\n removeItem,\n abortItem,\n retryItem,\n clear,\n };\n}\n","import { useCallback } from \"react\";\nimport type { FilePickResult, UseGalleryUploadOptions } from \"../types\";\nimport { useMultiUpload } from \"./use-multi-upload\";\nimport { useUploadistaContext } from \"./use-uploadista-context\";\n\n/**\n * Hook for selecting and uploading photos/videos from gallery\n * Handles batch selection and concurrent uploads\n * @param options - Gallery upload configuration\n * @returns Upload state and gallery selection/upload function\n */\nexport function useGalleryUpload(options?: UseGalleryUploadOptions) {\n const { fileSystemProvider } = useUploadistaContext();\n const uploadHook = useMultiUpload({\n maxConcurrent: 3,\n metadata: options?.metadata,\n onSuccess: options?.onSuccess,\n onError: options?.onError,\n });\n\n // Select and upload media from gallery\n const selectAndUpload = useCallback(async () => {\n let result: FilePickResult;\n\n // Select appropriate media type\n if (options?.mediaType === \"video\") {\n result = await fileSystemProvider.pickVideo({\n allowMultiple: options?.allowMultiple ?? true,\n });\n } else if (options?.mediaType === \"photo\") {\n result = await fileSystemProvider.pickImage({\n allowMultiple: options?.allowMultiple ?? true,\n });\n } else {\n // For 'mixed' or default, use pickImage first (can be extended to support both)\n result = await fileSystemProvider.pickImage({\n allowMultiple: options?.allowMultiple ?? true,\n });\n }\n\n // Handle cancelled picker\n if (result.status === \"cancelled\") {\n return [];\n }\n\n // Handle picker error\n if (result.status === \"error\") {\n console.error(\"Gallery selection error:\", result.error);\n options?.onError?.(result.error);\n return [];\n }\n\n // Success - add file and start upload\n const itemIds = uploadHook.addFiles([result]);\n await uploadHook.startUploads(itemIds);\n\n return itemIds;\n }, [\n fileSystemProvider,\n options?.allowMultiple,\n options?.mediaType,\n options?.onError,\n uploadHook,\n ]);\n\n return {\n ...uploadHook,\n selectAndUpload,\n };\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type { UploadMetrics } from \"../types\";\n\n/**\n * Hook for tracking upload performance metrics\n * @returns Metrics object and methods to track uploads\n */\nexport function useUploadMetrics() {\n const startTimeRef = useRef<number | null>(null);\n const startBytesRef = useRef<number>(0);\n const peakSpeedRef = useRef<number>(0);\n\n const [metrics, setMetrics] = useState<UploadMetrics>({\n totalBytes: 0,\n durationMs: 0,\n avgSpeed: 0,\n peakSpeed: 0,\n retries: 0,\n });\n\n // Start tracking\n const start = useCallback(() => {\n startTimeRef.current = Date.now();\n startBytesRef.current = 0;\n peakSpeedRef.current = 0;\n }, []);\n\n // Update metrics based on current progress\n const update = useCallback(\n (uploadedBytes: number, _totalBytes: number, currentRetries = 0) => {\n if (!startTimeRef.current) {\n return;\n }\n\n const now = Date.now();\n const durationMs = now - startTimeRef.current;\n const speed = durationMs > 0 ? (uploadedBytes / durationMs) * 1000 : 0;\n\n if (speed > peakSpeedRef.current) {\n peakSpeedRef.current = speed;\n }\n\n setMetrics({\n totalBytes: uploadedBytes,\n durationMs,\n avgSpeed: durationMs > 0 ? (uploadedBytes / durationMs) * 1000 : 0,\n peakSpeed: peakSpeedRef.current,\n retries: currentRetries,\n });\n },\n [],\n );\n\n // End tracking and return final metrics\n const end = useCallback(() => {\n const finalMetrics = metrics;\n startTimeRef.current = null;\n return finalMetrics;\n }, [metrics]);\n\n // Reset metrics\n const reset = useCallback(() => {\n startTimeRef.current = null;\n startBytesRef.current = 0;\n peakSpeedRef.current = 0;\n setMetrics({\n totalBytes: 0,\n durationMs: 0,\n avgSpeed: 0,\n peakSpeed: 0,\n retries: 0,\n });\n }, []);\n\n return {\n metrics,\n start,\n update,\n end,\n reset,\n };\n}\n","/**\n * File utility functions for React Native uploads\n */\n\n/**\n * Format file size to human readable string\n * @param bytes - Size in bytes\n * @returns Formatted size string (e.g., \"1.5 MB\")\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;\n}\n\n/**\n * Get MIME type from file name\n * @param fileName - File name with extension\n * @returns MIME type or 'application/octet-stream' as fallback\n */\nexport function getMimeTypeFromFileName(fileName: string): string {\n const mimeTypes: Record<string, string> = {\n // Images\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".bmp\": \"image/bmp\",\n \".webp\": \"image/webp\",\n \".svg\": \"image/svg+xml\",\n\n // Videos\n \".mp4\": \"video/mp4\",\n \".avi\": \"video/x-msvideo\",\n \".mov\": \"video/quicktime\",\n \".wmv\": \"video/x-ms-wmv\",\n \".flv\": \"video/x-flv\",\n \".mkv\": \"video/x-matroska\",\n \".webm\": \"video/webm\",\n\n // Audio\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n \".aac\": \"audio/aac\",\n \".flac\": \"audio/flac\",\n \".m4a\": \"audio/mp4\",\n\n // Documents\n \".pdf\": \"application/pdf\",\n \".doc\": \"application/msword\",\n \".docx\":\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \".xls\": \"application/vnd.ms-excel\",\n \".xlsx\":\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \".ppt\": \"application/vnd.ms-powerpoint\",\n \".pptx\":\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \".txt\": \"text/plain\",\n \".csv\": \"text/csv\",\n \".json\": \"application/json\",\n \".xml\": \"application/xml\",\n \".zip\": \"application/zip\",\n };\n\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return mimeTypes[ext] || \"application/octet-stream\";\n}\n\n/**\n * Check if file type is allowed\n * @param fileName - File name to check\n * @param allowedTypes - Array of allowed MIME types (e.g., ['image/jpeg', 'image/png'])\n * @returns True if file type is allowed\n */\nexport function isFileTypeAllowed(\n fileName: string,\n allowedTypes: string[],\n): boolean {\n if (!allowedTypes || allowedTypes.length === 0) {\n return true;\n }\n\n const mimeType = getMimeTypeFromFileName(fileName);\n return allowedTypes.some((allowed) => {\n if (allowed.endsWith(\"/*\")) {\n // Handle wildcard patterns like 'image/*'\n const [type] = allowed.split(\"/\");\n return mimeType.startsWith(`${type}/`);\n }\n return allowed === mimeType;\n });\n}\n\n/**\n * Check if file size is within limits\n * @param fileSize - File size in bytes\n * @param maxSize - Maximum allowed size in bytes (optional)\n * @param minSize - Minimum allowed size in bytes (optional)\n * @returns True if file size is within limits\n */\nexport function isFileSizeValid(\n fileSize: number,\n maxSize?: number,\n minSize?: number,\n): boolean {\n if (maxSize !== undefined && fileSize > maxSize) {\n return false;\n }\n\n if (minSize !== undefined && fileSize < minSize) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Get file extension\n * @param fileName - File name\n * @returns File extension without dot (e.g., 'pdf' for 'document.pdf')\n */\nexport function getFileExtension(fileName: string): string {\n const lastDot = fileName.lastIndexOf(\".\");\n if (lastDot === -1) return \"\";\n return fileName.slice(lastDot + 1).toLowerCase();\n}\n\n/**\n * Get file name without extension\n * @param fileName - File name\n * @returns File name without extension\n */\nexport function getFileNameWithoutExtension(fileName: string): string {\n const lastDot = fileName.lastIndexOf(\".\");\n if (lastDot === -1) return fileName;\n return fileName.slice(0, lastDot);\n}\n\n/**\n * Check if file is an image\n * @param fileName - File name\n * @returns True if file is an image\n */\nexport function isImageFile(fileName: string): boolean {\n const imageExtensions = [\n \".jpg\",\n \".jpeg\",\n \".png\",\n \".gif\",\n \".bmp\",\n \".webp\",\n \".svg\",\n ];\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return imageExtensions.includes(ext);\n}\n\n/**\n * Check if file is a video\n * @param fileName - File name\n * @returns True if file is a video\n */\nexport function isVideoFile(fileName: string): boolean {\n const videoExtensions = [\n \".mp4\",\n \".avi\",\n \".mov\",\n \".wmv\",\n \".flv\",\n \".mkv\",\n \".webm\",\n ];\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return videoExtensions.includes(ext);\n}\n\n/**\n * Check if file is a document\n * @param fileName - File name\n * @returns True if file is a document\n */\nexport function isDocumentFile(fileName: string): boolean {\n const docExtensions = [\n \".pdf\",\n \".doc\",\n \".docx\",\n \".xls\",\n \".xlsx\",\n \".ppt\",\n \".pptx\",\n \".txt\",\n \".csv\",\n ];\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return docExtensions.includes(ext);\n}\n","/**\n * Permission utility functions for React Native uploads\n * Handles camera, gallery, and file access permissions\n */\n\n/**\n * Permission types\n */\nexport enum PermissionType {\n CAMERA = \"CAMERA\",\n PHOTO_LIBRARY = \"PHOTO_LIBRARY\",\n WRITE_STORAGE = \"WRITE_STORAGE\",\n READ_STORAGE = \"READ_STORAGE\",\n}\n\n/**\n * Permission status\n */\nexport enum PermissionStatus {\n GRANTED = \"granted\",\n DENIED = \"denied\",\n NOT_DETERMINED = \"not_determined\",\n RESTRICTED = \"restricted\",\n}\n\n/**\n * Request camera permission\n * This is primarily used to check if we should attempt camera operations\n * Actual permission requests are handled by the file system providers\n *\n * @returns Promise resolving to true if permission is granted or already granted\n */\nexport async function requestCameraPermission(): Promise<boolean> {\n try {\n // Permission requests are handled by the file system provider implementations\n // This function serves as a placeholder for app-level permission handling\n console.log(\n \"Camera permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request camera permission:\", error);\n return false;\n }\n}\n\n/**\n * Request photo library permission\n * @returns Promise resolving to true if permission is granted\n */\nexport async function requestPhotoLibraryPermission(): Promise<boolean> {\n try {\n console.log(\n \"Photo library permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request photo library permission:\", error);\n return false;\n }\n}\n\n/**\n * Request storage read permission\n * @returns Promise resolving to true if permission is granted\n */\nexport async function requestStorageReadPermission(): Promise<boolean> {\n try {\n console.log(\n \"Storage read permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request storage read permission:\", error);\n return false;\n }\n}\n\n/**\n * Request storage write permission\n * @returns Promise resolving to true if permission is granted\n */\nexport async function requestStorageWritePermission(): Promise<boolean> {\n try {\n console.log(\n \"Storage write permission requested (handled by file system provider)\",\n );\n return true;\n } catch (error) {\n console.error(\"Failed to request storage write permission:\", error);\n return false;\n }\n}\n\n/**\n * Request multiple permissions at once\n * @param permissions - Array of permission types to request\n * @returns Promise resolving to true if all permissions are granted\n */\nexport async function requestPermissions(\n permissions: PermissionType[],\n): Promise<boolean> {\n try {\n const results = await Promise.all(\n permissions.map(async (permission) => {\n switch (permission) {\n case PermissionType.CAMERA:\n return requestCameraPermission();\n case PermissionType.PHOTO_LIBRARY:\n return requestPhotoLibraryPermission();\n case PermissionType.READ_STORAGE:\n return requestStorageReadPermission();\n case PermissionType.WRITE_STORAGE:\n return requestStorageWritePermission();\n default:\n return false;\n }\n }),\n );\n\n return results.every((result) => result);\n } catch (error) {\n console.error(\"Failed to request permissions:\", error);\n return false;\n }\n}\n\n/**\n * Check if all required permissions are granted\n * @param permissions - Array of permission types to check\n * @returns Promise resolving to true if all permissions are granted\n */\nexport async function hasPermissions(\n _permissions: PermissionType[],\n): Promise<boolean> {\n try {\n // In React Native, permission checking is typically handled by the platform\n // This is a placeholder that assumes permissions will be handled by the file system provider\n return true;\n } catch (error) {\n console.error(\"Failed to check permissions:\", error);\n return false;\n }\n}\n\n/**\n * Get permission status\n * @param permission - Permission type to check\n * @returns Promise resolving to permission status\n */\nexport async function getPermissionStatus(\n _permission: PermissionType,\n): Promise<PermissionStatus> {\n try {\n // This is a placeholder implementation\n // Real implementation would use platform-specific APIs\n return PermissionStatus.GRANTED;\n } catch (error) {\n console.error(\"Failed to get permission status:\", error);\n return PermissionStatus.DENIED;\n }\n}\n\n/**\n * Open app settings to request permissions\n * Guides user to app settings where they can manually enable permissions\n */\nexport function openAppSettings(): void {\n try {\n // This would typically use react-native-app-settings or similar\n console.log(\n \"Opening app settings (requires react-native-app-settings or platform implementation)\",\n );\n } catch (error) {\n console.error(\"Failed to open app settings:\", error);\n }\n}\n","/**\n * URI utility functions for React Native file handling\n */\n\n/**\n * Extract file name from URI\n * @param uri - File URI\n * @returns File name extracted from URI\n */\nexport function getFileNameFromUri(uri: string): string {\n try {\n // Handle different URI formats\n if (uri.startsWith(\"file://\")) {\n // File URI format\n const path = uri.replace(\"file://\", \"\");\n return path.split(\"/\").pop() || \"file\";\n }\n\n if (uri.startsWith(\"content://\")) {\n // Content URI format (Android)\n const parts = uri.split(\"/\");\n return parts[parts.length - 1] || \"file\";\n }\n\n // Assume it's a path or other format\n const parts = uri.split(\"/\");\n return parts[parts.length - 1] || \"file\";\n } catch {\n return \"file\";\n }\n}\n\n/**\n * Convert file path to file URI\n * @param filePath - File path\n * @returns File URI\n */\nexport function pathToUri(filePath: string): string {\n if (filePath.startsWith(\"file://\")) {\n return filePath;\n }\n\n if (filePath.startsWith(\"content://\")) {\n return filePath;\n }\n\n // Convert to file URI\n return `file://${filePath}`;\n}\n\n/**\n * Convert file URI to file path\n * @param uri - File URI\n * @returns File path\n */\nexport function uriToPath(uri: string): string {\n if (uri.startsWith(\"file://\")) {\n return uri.replace(\"file://\", \"\");\n }\n\n if (uri.startsWith(\"content://\")) {\n // Content URIs cannot be converted to paths directly\n return uri;\n }\n\n return uri;\n}\n\n/**\n * Get directory from URI\n * @param uri - File URI\n * @returns Directory path\n */\nexport function getDirectoryFromUri(uri: string): string {\n try {\n const path = uriToPath(uri);\n const parts = path.split(\"/\");\n parts.pop(); // Remove file name\n return parts.join(\"/\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Check if URI is a content URI (Android specific)\n * @param uri - URI to check\n * @returns True if URI is a content URI\n */\nexport function isContentUri(uri: string): boolean {\n return uri.startsWith(\"content://\");\n}\n\n/**\n * Check if URI is a file URI\n * @param uri - URI to check\n * @returns True if URI is a file URI\n */\nexport function isFileUri(uri: string): boolean {\n return uri.startsWith(\"file://\");\n}\n\n/**\n * Normalize URI for cross-platform compatibility\n * @param uri - URI to normalize\n * @returns Normalized URI\n */\nexport function normalizeUri(uri: string): string {\n // Remove duplicate slashes (but keep protocol slashes)\n return uri.replace(/([^:]\\/)\\/+/g, \"$1\");\n}\n\n/**\n * Get MIME type hint from URI\n * @param uri - File URI\n * @returns MIME type hint based on file extension\n */\nexport function getMimeTypeFromUri(uri: string): string {\n const fileName = getFileNameFromUri(uri);\n\n const mimeTypes: Record<string, string> = {\n // Images\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".bmp\": \"image/bmp\",\n \".webp\": \"image/webp\",\n\n // Videos\n \".mp4\": \"video/mp4\",\n \".mov\": \"video/quicktime\",\n \".avi\": \"video/x-msvideo\",\n\n // Audio\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n \".aac\": \"audio/aac\",\n\n // Documents\n \".pdf\": \"application/pdf\",\n \".txt\": \"text/plain\",\n \".json\": \"application/json\",\n };\n\n const ext = fileName.toLowerCase().slice(fileName.lastIndexOf(\".\"));\n return mimeTypes[ext] || \"application/octet-stream\";\n}\n","import { ActivityIndicator, StyleSheet, Text, View } from \"react-native\";\nimport type { UploadState } from \"../hooks/use-upload\";\nimport { formatFileSize } from \"../utils\";\n\nexport interface UploadProgressProps {\n /** Upload state information */\n state: UploadState;\n /** Optional custom label */\n label?: string;\n}\n\n/**\n * Component to display upload progress with percentage, size, and speed\n */\nexport function UploadProgress({ state, label }: UploadProgressProps) {\n const getStatusColor = () => {\n switch (state.status) {\n case \"uploading\":\n return \"#007AFF\";\n case \"success\":\n return \"#34C759\";\n case \"error\":\n case \"aborted\":\n return \"#FF3B30\";\n default:\n return \"#999999\";\n }\n };\n\n const renderContent = () => {\n switch (state.status) {\n case \"idle\":\n return (\n <View style={styles.container}>\n <Text style={styles.label}>{label || \"Ready to upload\"}</Text>\n </View>\n );\n\n case \"uploading\":\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={styles.label}>{label || \"Uploading\"}</Text>\n <Text style={styles.percentage}>{state.progress}%</Text>\n </View>\n\n {/* Progress bar */}\n <View style={styles.progressBarContainer}>\n <View\n style={[\n styles.progressBar,\n {\n width: `${state.progress}%`,\n backgroundColor: getStatusColor(),\n },\n ]}\n />\n </View>\n\n {/* Details row */}\n <View style={styles.detailsRow}>\n <Text style={styles.detail}>\n {formatFileSize(state.bytesUploaded)} /{\" \"}\n {formatFileSize(state.totalBytes || 0)}\n </Text>\n </View>\n </View>\n );\n\n case \"success\":\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={[styles.label, { color: getStatusColor() }]}>\n {label || \"Upload complete\"}\n </Text>\n <Text style={[styles.percentage, { color: getStatusColor() }]}>\n ✓\n </Text>\n </View>\n <Text style={[styles.detail, { color: getStatusColor() }]}>\n {formatFileSize(state.totalBytes || 0)}\n </Text>\n </View>\n );\n\n case \"error\":\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={[styles.label, { color: getStatusColor() }]}>\n {label || \"Upload failed\"}\n </Text>\n <Text style={[styles.percentage, { color: getStatusColor() }]}>\n ✕\n </Text>\n </View>\n {state.error && (\n <Text style={[styles.detail, { color: getStatusColor() }]}>\n {state.error.message}\n </Text>\n )}\n </View>\n );\n\n case \"aborted\":\n return (\n <View style={styles.container}>\n <Text style={[styles.label, { color: getStatusColor() }]}>\n {label || \"Upload cancelled\"}\n </Text>\n </View>\n );\n\n default:\n return null;\n }\n };\n\n return (\n <View\n style={[\n styles.wrapper,\n {\n borderLeftColor: getStatusColor(),\n },\n ]}\n >\n {state.status === \"uploading\" && (\n <ActivityIndicator\n size=\"small\"\n color={getStatusColor()}\n style={styles.spinner}\n />\n )}\n {renderContent()}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n wrapper: {\n flexDirection: \"row\",\n alignItems: \"flex-start\",\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderLeftWidth: 4,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n gap: 8,\n },\n spinner: {\n marginTop: 4,\n },\n container: {\n flex: 1,\n gap: 4,\n },\n headerRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n label: {\n fontSize: 14,\n fontWeight: \"600\",\n color: \"#333333\",\n flex: 1,\n },\n percentage: {\n fontSize: 14,\n fontWeight: \"600\",\n color: \"#007AFF\",\n minWidth: 36,\n textAlign: \"right\",\n },\n progressBarContainer: {\n height: 4,\n backgroundColor: \"#e0e0e0\",\n borderRadius: 2,\n overflow: \"hidden\",\n },\n progressBar: {\n height: \"100%\",\n borderRadius: 2,\n },\n detailsRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n detail: {\n fontSize: 12,\n color: \"#666666\",\n },\n});\n","import { type ReactNode, useEffect } from \"react\";\nimport {\n ActivityIndicator,\n Pressable,\n StyleSheet,\n Text,\n View,\n} from \"react-native\";\nimport { useCameraUpload } from \"../hooks\";\nimport type { UseCameraUploadOptions } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface CameraUploadButtonProps {\n /** Options for camera upload */\n options?: UseCameraUploadOptions;\n /** Button label text */\n label?: string;\n /** Custom button content */\n children?: ReactNode;\n /** Callback when upload completes successfully */\n onSuccess?: (result: unknown) => void;\n /** Callback when upload fails */\n onError?: (error: Error) => void;\n /** Callback when upload is cancelled */\n onCancel?: () => void;\n /** Whether to show progress inline */\n showProgress?: boolean;\n}\n\n/**\n * Button component for camera capture and upload\n * Triggers camera on press and handles upload with progress display\n */\nexport function CameraUploadButton({\n options,\n label = \"Take Photo\",\n children,\n onSuccess,\n onError,\n onCancel,\n showProgress = true,\n}: CameraUploadButtonProps) {\n const { state, captureAndUpload } = useCameraUpload(options);\n\n const handlePress = async () => {\n try {\n await captureAndUpload();\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message.includes(\"cancelled\") ||\n error.message.includes(\"aborted\")\n ) {\n onCancel?.();\n } else {\n onError?.(error);\n }\n }\n }\n };\n\n const isLoading = state.status === \"uploading\";\n const isDisabled = isLoading || state.status === \"aborted\";\n\n useEffect(() => {\n if (state.status === \"success\" && state.result) {\n onSuccess?.(state.result);\n }\n }, [state.status, state.result, onSuccess]);\n\n useEffect(() => {\n if (state.status === \"error\" && state.error) {\n onError?.(state.error);\n }\n }, [state.status, state.error, onError]);\n\n return (\n <View style={styles.container}>\n <Pressable\n style={[styles.button, isDisabled && styles.buttonDisabled]}\n onPress={handlePress}\n disabled={isDisabled}\n >\n {isLoading && (\n <ActivityIndicator\n size=\"small\"\n color=\"#FFFFFF\"\n style={styles.spinner}\n />\n )}\n <Text style={styles.buttonText}>{children || label}</Text>\n </Pressable>\n {showProgress && state.status !== \"idle\" && (\n <View style={styles.progressContainer}>\n <UploadProgress state={state} label=\"Camera upload\" />\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n button: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n paddingVertical: 12,\n paddingHorizontal: 16,\n backgroundColor: \"#007AFF\",\n borderRadius: 8,\n gap: 8,\n },\n buttonDisabled: {\n opacity: 0.6,\n },\n buttonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FFFFFF\",\n },\n spinner: {\n marginRight: 4,\n },\n progressContainer: {\n marginTop: 4,\n },\n});\n","import { type ReactNode, useEffect } from \"react\";\nimport {\n ActivityIndicator,\n Pressable,\n StyleSheet,\n Text,\n View,\n} from \"react-native\";\nimport { useFileUpload } from \"../hooks\";\nimport type { UseFileUploadOptions } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface FileUploadButtonProps {\n /** Options for file upload */\n options?: UseFileUploadOptions;\n /** Button label text */\n label?: string;\n /** Custom button content */\n children?: ReactNode;\n /** Callback when upload completes successfully */\n onSuccess?: (result: unknown) => void;\n /** Callback when upload fails */\n onError?: (error: Error) => void;\n /** Callback when upload is cancelled */\n onCancel?: () => void;\n /** Whether to show progress inline */\n showProgress?: boolean;\n}\n\n/**\n * Button component for document/file selection and upload\n * Generic file picker with progress display\n */\nexport function FileUploadButton({\n options,\n label = \"Choose File\",\n children,\n onSuccess,\n onError,\n onCancel,\n showProgress = true,\n}: FileUploadButtonProps) {\n const { state, pickAndUpload } = useFileUpload(options);\n\n const handlePress = async () => {\n try {\n await pickAndUpload();\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message.includes(\"cancelled\") ||\n error.message.includes(\"aborted\")\n ) {\n onCancel?.();\n } else {\n onError?.(error);\n }\n }\n }\n };\n\n const isLoading = state.status === \"uploading\";\n const isDisabled = isLoading || state.status === \"aborted\";\n\n useEffect(() => {\n if (state.status === \"success\" && state.result) {\n onSuccess?.(state.result);\n }\n }, [state.status, state.result, onSuccess]);\n\n useEffect(() => {\n if (state.status === \"error\" && state.error) {\n onError?.(state.error);\n }\n }, [state.status, state.error, onError]);\n\n return (\n <View style={styles.container}>\n <Pressable\n style={[styles.button, isDisabled && styles.buttonDisabled]}\n onPress={handlePress}\n disabled={isDisabled}\n >\n {isLoading && (\n <ActivityIndicator\n size=\"small\"\n color=\"#FFFFFF\"\n style={styles.spinner}\n />\n )}\n <Text style={styles.buttonText}>{children || label}</Text>\n </Pressable>\n {showProgress && state.status !== \"idle\" && (\n <View style={styles.progressContainer}>\n <UploadProgress state={state} label=\"File upload\" />\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n button: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n paddingVertical: 12,\n paddingHorizontal: 16,\n backgroundColor: \"#FF9500\",\n borderRadius: 8,\n gap: 8,\n },\n buttonDisabled: {\n opacity: 0.6,\n },\n buttonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FFFFFF\",\n },\n spinner: {\n marginRight: 4,\n },\n progressContainer: {\n marginTop: 4,\n },\n});\n","import type {\n FlowUploadState,\n FlowUploadStatus,\n InputExecutionState,\n} from \"@uploadista/client-core\";\nimport type { TypedOutput } from \"@uploadista/core/flow\";\nimport { createContext, type ReactNode, useCallback, useContext } from \"react\";\nimport {\n type FlowInputMetadata,\n type UseFlowOptions,\n useFlow,\n} from \"../hooks/use-flow\";\nimport { useUploadistaContext } from \"../hooks/use-uploadista-context\";\nimport type { FilePickResult } from \"../types\";\n\n// Re-export types for convenience\nexport type {\n FlowUploadState,\n FlowUploadStatus,\n InputExecutionState,\n FlowInputMetadata,\n};\n\n// ============ FLOW CONTEXT ============\n\n/**\n * Context value provided by the Flow component root.\n * Contains all flow state and actions.\n */\nexport interface FlowContextValue {\n /** Current upload state */\n state: FlowUploadState;\n /** Discovered input nodes metadata (null until discovery completes) */\n inputMetadata: FlowInputMetadata[] | null;\n /** Current input values set via setInput() */\n inputs: Record<string, unknown>;\n /** Per-input execution state for multi-input flows */\n inputStates: ReadonlyMap<string, InputExecutionState>;\n\n /** Set an input value for a specific node */\n setInput: (nodeId: string, value: unknown) => void;\n /** Execute the flow with current inputs */\n execute: () => Promise<void>;\n /** Upload a single file through the flow */\n upload: (file: FilePickResult) => Promise<void>;\n /** Abort the current upload */\n abort: () => void;\n /** Reset the upload state and clear all inputs */\n reset: () => void;\n\n /** Whether an upload or flow execution is in progress */\n isActive: boolean;\n /** Whether the file is currently being uploaded */\n isUploadingFile: boolean;\n /** Whether the flow is currently processing */\n isProcessing: boolean;\n /** Whether the hook is discovering flow inputs */\n isDiscoveringInputs: boolean;\n\n /** Pick a file and set it for a specific input node */\n pickFileForInput: (nodeId: string) => Promise<void>;\n /** Pick a file and start upload immediately (single-file flows) */\n pickAndUpload: () => Promise<void>;\n}\n\nconst FlowContext = createContext<FlowContextValue | null>(null);\n\n/**\n * Hook to access flow context from within a Flow component.\n * @throws Error if used outside of a Flow component\n */\nexport function useFlowContext(): FlowContextValue {\n const context = useContext(FlowContext);\n if (!context) {\n throw new Error(\n \"useFlowContext must be used within a <Flow> component. \" +\n 'Wrap your component tree with <Flow flowId=\"...\" storageId=\"...\">',\n );\n }\n return context;\n}\n\n// ============ FLOW INPUT CONTEXT ============\n\n/**\n * Context value for a specific input node within a Flow.\n */\nexport interface FlowInputContextValue {\n /** Input node ID */\n nodeId: string;\n /** Input metadata from flow discovery */\n metadata: FlowInputMetadata;\n /** Current value for this input */\n value: unknown;\n /** Set the value for this input */\n setValue: (value: unknown) => void;\n /** Per-input execution state (if available) */\n state: InputExecutionState | undefined;\n /** Pick a file for this input */\n pickFile: () => Promise<void>;\n}\n\nconst FlowInputContext = createContext<FlowInputContextValue | null>(null);\n\n/**\n * Hook to access flow input context from within a Flow.Input component.\n * @throws Error if used outside of a Flow.Input component\n */\nexport function useFlowInputContext(): FlowInputContextValue {\n const context = useContext(FlowInputContext);\n if (!context) {\n throw new Error(\n \"useFlowInputContext must be used within a <Flow.Input> component. \" +\n 'Wrap your component with <Flow.Input nodeId=\"...\">',\n );\n }\n return context;\n}\n\n// ============ FLOW ROOT COMPONENT ============\n\n/**\n * Render props for the Flow root component.\n */\nexport interface FlowRenderProps extends FlowContextValue {\n /** Alias for execute() */\n submit: () => Promise<void>;\n /** Alias for abort() */\n cancel: () => void;\n}\n\n/**\n * Props for the Flow root component.\n */\nexport interface FlowProps {\n /** Flow ID to execute */\n flowId: string;\n /** Storage ID for file uploads */\n storageId: string;\n /** Optional output node ID to wait for */\n outputNodeId?: string;\n /** Optional metadata to include with the flow execution */\n metadata?: Record<string, string>;\n /** Called when flow completes successfully */\n onSuccess?: (outputs: TypedOutput[]) => void;\n /** Called when flow fails */\n onError?: (error: Error) => void;\n /** Called on upload progress */\n onProgress?: (\n progress: number,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n /** Called when flow completes with all outputs */\n onFlowComplete?: (outputs: TypedOutput[]) => void;\n /** Children to render (can be render function or ReactNode) */\n children: ReactNode | ((props: FlowRenderProps) => ReactNode);\n}\n\n/**\n * Root component for flow-based uploads on React Native.\n * Provides context for all Flow sub-components.\n *\n * @example\n * ```tsx\n * <Flow flowId=\"image-optimizer\" storageId=\"s3\" onSuccess={handleSuccess}>\n * <Flow.Inputs>\n * {({ inputs, isLoading }) => (\n * inputs.map(input => (\n * <Flow.Input key={input.nodeId} nodeId={input.nodeId}>\n * {({ metadata, pickFile }) => (\n * <Button onPress={pickFile} title={metadata.nodeName} />\n * )}\n * </Flow.Input>\n * ))\n * )}\n * </Flow.Inputs>\n * <Flow.Submit>\n * <Text>Process</Text>\n * </Flow.Submit>\n * </Flow>\n * ```\n */\nfunction FlowRoot({\n flowId,\n storageId,\n outputNodeId,\n metadata,\n onSuccess,\n onError,\n onProgress,\n onFlowComplete,\n children,\n}: FlowProps) {\n const { fileSystemProvider } = useUploadistaContext();\n\n const options: UseFlowOptions = {\n flowId,\n storageId,\n outputNodeId,\n metadata,\n onSuccess,\n onError,\n onProgress,\n onFlowComplete,\n };\n\n const flow = useFlow(options);\n\n // Pick a file for a specific input node\n const pickFileForInput = useCallback(\n async (nodeId: string) => {\n if (!fileSystemProvider?.pickDocument) {\n throw new Error(\"File picker not available\");\n }\n const result = await fileSystemProvider.pickDocument();\n if (result.status === \"success\") {\n flow.setInput(nodeId, result);\n }\n },\n [fileSystemProvider, flow],\n );\n\n // Pick a file and start upload immediately\n const pickAndUpload = useCallback(async () => {\n if (!fileSystemProvider?.pickDocument) {\n throw new Error(\"File picker not available\");\n }\n const result = await fileSystemProvider.pickDocument();\n if (result.status === \"success\") {\n await flow.upload(result);\n }\n }, [fileSystemProvider, flow]);\n\n const contextValue: FlowContextValue = {\n state: flow.state,\n inputMetadata: flow.inputMetadata,\n inputs: flow.inputs,\n inputStates: flow.inputStates,\n setInput: flow.setInput,\n execute: flow.execute,\n upload: flow.upload,\n abort: flow.abort,\n reset: flow.reset,\n isActive: flow.isActive,\n isUploadingFile: flow.isUploadingFile,\n isProcessing: flow.isProcessing,\n isDiscoveringInputs: flow.isDiscoveringInputs,\n pickFileForInput,\n pickAndUpload,\n };\n\n const renderProps: FlowRenderProps = {\n ...contextValue,\n submit: flow.execute,\n cancel: flow.abort,\n };\n\n return (\n <FlowContext.Provider value={contextValue}>\n {typeof children === \"function\" ? children(renderProps) : children}\n </FlowContext.Provider>\n );\n}\n\n// ============ INPUTS DISCOVERY PRIMITIVE ============\n\n/**\n * Render props for Flow.Inputs component.\n */\nexport interface FlowInputsRenderProps {\n /** Discovered input metadata */\n inputs: FlowInputMetadata[];\n /** Whether inputs are still being discovered */\n isLoading: boolean;\n}\n\n/**\n * Props for Flow.Inputs component.\n */\nexport interface FlowInputsProps {\n /** Render function receiving discovered inputs */\n children: (props: FlowInputsRenderProps) => ReactNode;\n}\n\n/**\n * Auto-discovers flow input nodes and provides them via render props.\n *\n * @example\n * ```tsx\n * <Flow.Inputs>\n * {({ inputs, isLoading }) => (\n * isLoading ? <ActivityIndicator /> : (\n * inputs.map(input => (\n * <Flow.Input key={input.nodeId} nodeId={input.nodeId}>\n * ...\n * </Flow.Input>\n * ))\n * )\n * )}\n * </Flow.Inputs>\n * ```\n */\nfunction FlowInputs({ children }: FlowInputsProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowInputsRenderProps = {\n inputs: flow.inputMetadata ?? [],\n isLoading: flow.isDiscoveringInputs,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ INPUT PRIMITIVE ============\n\n/**\n * Props for Flow.Input component.\n */\nexport interface FlowInputProps {\n /** Input node ID */\n nodeId: string;\n /** Children (can be render function or regular children) */\n children: ReactNode | ((props: FlowInputContextValue) => ReactNode);\n}\n\n/**\n * Scoped input context provider for a specific input node.\n * Children can access input-specific state via useFlowInputContext().\n *\n * @example\n * ```tsx\n * <Flow.Input nodeId=\"video-input\">\n * {({ metadata, value, pickFile }) => (\n * <View>\n * <Text>{metadata.nodeName}</Text>\n * <Button onPress={pickFile} title=\"Select File\" />\n * {value && <Text>Selected: {value.data?.name}</Text>}\n * </View>\n * )}\n * </Flow.Input>\n * ```\n */\nfunction FlowInput({ nodeId, children }: FlowInputProps) {\n const flow = useFlowContext();\n\n const metadata = flow.inputMetadata?.find((m) => m.nodeId === nodeId);\n\n if (!metadata) {\n // Input not yet discovered or doesn't exist\n return null;\n }\n\n const pickFile = async () => {\n await flow.pickFileForInput(nodeId);\n };\n\n const contextValue: FlowInputContextValue = {\n nodeId,\n metadata,\n value: flow.inputs[nodeId],\n setValue: (value) => flow.setInput(nodeId, value),\n state: flow.inputStates.get(nodeId),\n pickFile,\n };\n\n return (\n <FlowInputContext.Provider value={contextValue}>\n {typeof children === \"function\" ? children(contextValue) : children}\n </FlowInputContext.Provider>\n );\n}\n\n// ============ INPUT FILE PICKER PRIMITIVE ============\n\n/**\n * Render props for Flow.Input.FilePicker component.\n */\nexport interface FlowInputFilePickerRenderProps {\n /** Current value for this input */\n value: unknown;\n /** Whether a file is selected */\n hasFile: boolean;\n /** File name (if value is FilePickResult) */\n fileName: string | null;\n /** File size in bytes (if value is FilePickResult) */\n fileSize: number | null;\n /** Per-input progress (if available) */\n progress: number;\n /** Per-input status (if available) */\n status: string;\n /** Open file picker */\n pickFile: () => Promise<void>;\n /** Clear the input value */\n clear: () => void;\n}\n\n/**\n * Props for Flow.Input.FilePicker component.\n */\nexport interface FlowInputFilePickerProps {\n /** Render function receiving file picker state */\n children: (props: FlowInputFilePickerRenderProps) => ReactNode;\n}\n\n/**\n * File picker for a specific input within a Flow.Input.\n * Sets the input value but does NOT trigger upload until Flow.Submit is pressed.\n */\nfunction FlowInputFilePicker({ children }: FlowInputFilePickerProps) {\n const input = useFlowInputContext();\n\n // Check if value is a FilePickResult\n const fileResult = input.value as FilePickResult | undefined;\n const hasFile = fileResult?.status === \"success\";\n const fileName = hasFile ? (fileResult?.data?.name ?? null) : null;\n const fileSize = hasFile ? (fileResult?.data?.size ?? null) : null;\n\n const renderProps: FlowInputFilePickerRenderProps = {\n value: input.value,\n hasFile,\n fileName,\n fileSize,\n progress: input.state?.progress ?? 0,\n status: input.state?.status ?? \"idle\",\n pickFile: input.pickFile,\n clear: () => input.setValue(undefined),\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ INPUT PREVIEW PRIMITIVE ============\n\n/**\n * Render props for Flow.Input.Preview component.\n */\nexport interface FlowInputPreviewRenderProps {\n /** Current value */\n value: unknown;\n /** Whether a file is selected */\n hasFile: boolean;\n /** Whether value is a URL string */\n isUrl: boolean;\n /** File name (if value is FilePickResult) */\n fileName: string | null;\n /** File size in bytes (if value is FilePickResult) */\n fileSize: number | null;\n /** File URI (if value is FilePickResult) */\n fileUri: string | null;\n /** Clear the input value */\n clear: () => void;\n}\n\n/**\n * Props for Flow.Input.Preview component.\n */\nexport interface FlowInputPreviewProps {\n /** Render function receiving preview state */\n children: (props: FlowInputPreviewRenderProps) => ReactNode;\n}\n\n/**\n * Preview component for showing the selected value within a Flow.Input.\n */\nfunction FlowInputPreview({ children }: FlowInputPreviewProps) {\n const input = useFlowInputContext();\n\n // Check if value is a FilePickResult\n const fileResult = input.value as FilePickResult | undefined;\n const hasFile = fileResult?.status === \"success\";\n const isUrl =\n typeof input.value === \"string\" && (input.value as string).length > 0;\n\n const renderProps: FlowInputPreviewRenderProps = {\n value: input.value,\n hasFile,\n isUrl,\n fileName: hasFile ? (fileResult?.data?.name ?? null) : null,\n fileSize: hasFile ? (fileResult?.data?.size ?? null) : null,\n fileUri: hasFile ? (fileResult?.data?.uri ?? null) : null,\n clear: () => input.setValue(undefined),\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ PROGRESS PRIMITIVE ============\n\n/**\n * Render props for Flow.Progress component.\n */\nexport interface FlowProgressRenderProps {\n /** Progress percentage (0-100) */\n progress: number;\n /** Bytes uploaded so far */\n bytesUploaded: number;\n /** Total bytes to upload (null if unknown) */\n totalBytes: number | null;\n /** Current status */\n status: FlowUploadStatus;\n}\n\n/**\n * Props for Flow.Progress component.\n */\nexport interface FlowProgressProps {\n /** Render function receiving progress state */\n children: (props: FlowProgressRenderProps) => ReactNode;\n}\n\n/**\n * Progress display component within a Flow.\n */\nfunction FlowProgress({ children }: FlowProgressProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowProgressRenderProps = {\n progress: flow.state.progress,\n bytesUploaded: flow.state.bytesUploaded,\n totalBytes: flow.state.totalBytes,\n status: flow.state.status,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ STATUS PRIMITIVE ============\n\n/**\n * Render props for Flow.Status component.\n */\nexport interface FlowStatusRenderProps {\n /** Current status */\n status: FlowUploadStatus;\n /** Current node being processed (if any) */\n currentNodeName: string | null;\n /** Current node type (if any) */\n currentNodeType: string | null;\n /** Error (if status is error) */\n error: Error | null;\n /** Job ID (if started) */\n jobId: string | null;\n /** Whether flow has started */\n flowStarted: boolean;\n /** Flow outputs (if completed) */\n flowOutputs: TypedOutput[] | null;\n}\n\n/**\n * Props for Flow.Status component.\n */\nexport interface FlowStatusProps {\n /** Render function receiving status state */\n children: (props: FlowStatusRenderProps) => ReactNode;\n}\n\n/**\n * Status display component within a Flow.\n */\nfunction FlowStatus({ children }: FlowStatusProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowStatusRenderProps = {\n status: flow.state.status,\n currentNodeName: flow.state.currentNodeName,\n currentNodeType: flow.state.currentNodeType,\n error: flow.state.error,\n jobId: flow.state.jobId,\n flowStarted: flow.state.flowStarted,\n flowOutputs: flow.state.flowOutputs,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ERROR PRIMITIVE ============\n\n/**\n * Render props for Flow.Error component.\n */\nexport interface FlowErrorRenderProps {\n /** Error object (null if no error) */\n error: Error | null;\n /** Whether there is an error */\n hasError: boolean;\n /** Error message */\n message: string | null;\n /** Reset the flow */\n reset: () => void;\n}\n\n/**\n * Props for Flow.Error component.\n */\nexport interface FlowErrorProps {\n /** Render function receiving error state */\n children: (props: FlowErrorRenderProps) => ReactNode;\n}\n\n/**\n * Error display component within a Flow.\n */\nfunction FlowError({ children }: FlowErrorProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowErrorRenderProps = {\n error: flow.state.error,\n hasError: flow.state.status === \"error\",\n message: flow.state.error?.message ?? null,\n reset: flow.reset,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ACTION PRIMITIVES ============\n\n/**\n * Render props for Flow.Submit component.\n */\nexport interface FlowSubmitRenderProps {\n /** Execute the flow */\n submit: () => Promise<void>;\n /** Whether the button should be disabled */\n isDisabled: boolean;\n /** Whether currently submitting */\n isSubmitting: boolean;\n}\n\n/**\n * Props for Flow.Submit component.\n */\nexport interface FlowSubmitProps {\n /** Render function receiving submit state */\n children: ReactNode | ((props: FlowSubmitRenderProps) => ReactNode);\n /** Additional disabled state */\n disabled?: boolean;\n}\n\n/**\n * Submit primitive that executes the flow with current inputs.\n * Provides render props for building custom submit buttons.\n * Automatically disabled when uploading.\n */\nfunction FlowSubmit({ children, disabled }: FlowSubmitProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowSubmitRenderProps = {\n submit: flow.execute,\n isDisabled:\n disabled || flow.isActive || Object.keys(flow.inputs).length === 0,\n isSubmitting: flow.isActive,\n };\n\n return (\n <>{typeof children === \"function\" ? children(renderProps) : children}</>\n );\n}\n\n/**\n * Render props for Flow.Cancel component.\n */\nexport interface FlowCancelRenderProps {\n /** Cancel the flow */\n cancel: () => void;\n /** Whether the button should be disabled */\n isDisabled: boolean;\n}\n\n/**\n * Props for Flow.Cancel component.\n */\nexport interface FlowCancelProps {\n /** Render function receiving cancel state */\n children: ReactNode | ((props: FlowCancelRenderProps) => ReactNode);\n}\n\n/**\n * Cancel primitive that aborts the current upload.\n */\nfunction FlowCancel({ children }: FlowCancelProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowCancelRenderProps = {\n cancel: flow.abort,\n isDisabled: !flow.isActive,\n };\n\n return (\n <>{typeof children === \"function\" ? children(renderProps) : children}</>\n );\n}\n\n/**\n * Render props for Flow.Reset component.\n */\nexport interface FlowResetRenderProps {\n /** Reset the flow */\n reset: () => void;\n /** Whether the button should be disabled */\n isDisabled: boolean;\n}\n\n/**\n * Props for Flow.Reset component.\n */\nexport interface FlowResetProps {\n /** Render function receiving reset state */\n children: ReactNode | ((props: FlowResetRenderProps) => ReactNode);\n}\n\n/**\n * Reset primitive that clears all inputs and resets to idle state.\n */\nfunction FlowReset({ children }: FlowResetProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowResetRenderProps = {\n reset: flow.reset,\n isDisabled: flow.isActive,\n };\n\n return (\n <>{typeof children === \"function\" ? children(renderProps) : children}</>\n );\n}\n\n// ============ QUICK UPLOAD PRIMITIVE ============\n\n/**\n * Render props for Flow.QuickUpload component.\n */\nexport interface FlowQuickUploadRenderProps {\n /** Whether currently uploading */\n isUploading: boolean;\n /** Progress percentage (0-100) */\n progress: number;\n /** Current status */\n status: FlowUploadStatus;\n /** Pick a file and start upload immediately */\n pickAndUpload: () => Promise<void>;\n /** Abort the current upload */\n abort: () => void;\n}\n\n/**\n * Props for Flow.QuickUpload component.\n */\nexport interface FlowQuickUploadProps {\n /** Render function receiving quick upload state */\n children: (props: FlowQuickUploadRenderProps) => ReactNode;\n}\n\n/**\n * Quick upload component for single-file flows.\n * Picks a file and starts upload immediately.\n *\n * @example\n * ```tsx\n * <Flow.QuickUpload>\n * {({ isUploading, progress, pickAndUpload, abort }) => (\n * <Button\n * onPress={isUploading ? abort : pickAndUpload}\n * title={isUploading ? `Uploading ${progress}%` : 'Upload File'}\n * />\n * )}\n * </Flow.QuickUpload>\n * ```\n */\nfunction FlowQuickUpload({ children }: FlowQuickUploadProps) {\n const flow = useFlowContext();\n\n const renderProps: FlowQuickUploadRenderProps = {\n isUploading: flow.isActive,\n progress: flow.state.progress,\n status: flow.state.status,\n pickAndUpload: flow.pickAndUpload,\n abort: flow.abort,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ COMPOUND COMPONENT EXPORT ============\n\n/**\n * Flow compound component for flow-based file uploads on React Native.\n *\n * Provides a composable, headless API for building flow upload interfaces.\n * All sub-components use render props for complete UI control.\n *\n * @example Quick Upload (Single File)\n * ```tsx\n * <Flow flowId=\"image-optimizer\" storageId=\"s3\" onSuccess={handleSuccess}>\n * <Flow.QuickUpload>\n * {({ isUploading, progress, pickAndUpload, abort }) => (\n * <View>\n * <Button\n * onPress={isUploading ? abort : pickAndUpload}\n * title={isUploading ? 'Cancel' : 'Upload Image'}\n * />\n * {isUploading && <Text>{progress}%</Text>}\n * </View>\n * )}\n * </Flow.QuickUpload>\n * </Flow>\n * ```\n *\n * @example Multi-Input Flow\n * ```tsx\n * <Flow flowId=\"video-processor\" storageId=\"s3\">\n * <Flow.Inputs>\n * {({ inputs, isLoading }) => (\n * isLoading ? <ActivityIndicator /> : inputs.map(input => (\n * <Flow.Input key={input.nodeId} nodeId={input.nodeId}>\n * {({ metadata, pickFile, value }) => (\n * <View>\n * <Text>{metadata.nodeName}</Text>\n * <Button onPress={pickFile} title=\"Select File\" />\n * <Flow.Input.Preview>\n * {({ hasFile, fileName }) => hasFile && <Text>{fileName}</Text>}\n * </Flow.Input.Preview>\n * </View>\n * )}\n * </Flow.Input>\n * ))\n * )}\n * </Flow.Inputs>\n * <CustomSubmitButton />\n * </Flow>\n * ```\n */\nexport const Flow = Object.assign(FlowRoot, {\n Inputs: FlowInputs,\n Input: Object.assign(FlowInput, {\n FilePicker: FlowInputFilePicker,\n Preview: FlowInputPreview,\n }),\n Progress: FlowProgress,\n Status: FlowStatus,\n Error: FlowError,\n Submit: FlowSubmit,\n Cancel: FlowCancel,\n Reset: FlowReset,\n QuickUpload: FlowQuickUpload,\n});\n","import React, { type ReactNode } from \"react\";\nimport {\n ActivityIndicator,\n FlatList,\n Pressable,\n StyleSheet,\n Text,\n View,\n} from \"react-native\";\nimport { useGalleryUpload } from \"../hooks\";\nimport type { UseGalleryUploadOptions } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface GalleryUploadButtonProps {\n /** Options for gallery upload */\n options?: UseGalleryUploadOptions;\n /** Button label text */\n label?: string;\n /** Custom button content */\n children?: ReactNode;\n /** Callback when all uploads complete successfully */\n onSuccess?: (results: unknown[]) => void;\n /** Callback when any upload fails */\n onError?: (error: Error) => void;\n /** Callback when upload is cancelled */\n onCancel?: () => void;\n /** Whether to show individual progress for each file */\n showProgress?: boolean;\n}\n\n/**\n * Button component for gallery selection and batch upload\n * Triggers gallery picker on press and handles concurrent uploads\n */\nexport function GalleryUploadButton({\n options,\n label = \"Select from Gallery\",\n children,\n onSuccess,\n onError,\n onCancel,\n showProgress = true,\n}: GalleryUploadButtonProps) {\n const { state, selectAndUpload } = useGalleryUpload(options);\n\n const handlePress = async () => {\n try {\n await selectAndUpload();\n } catch (error) {\n if (error instanceof Error) {\n if (\n error.message.includes(\"cancelled\") ||\n error.message.includes(\"aborted\")\n ) {\n onCancel?.();\n } else {\n onError?.(error);\n }\n }\n }\n };\n\n const isLoading = state.items.some((item) => item.status === \"uploading\");\n const hasItems = state.items.length > 0;\n const allComplete =\n hasItems &&\n state.items.every(\n (item) => item.status !== \"uploading\" && item.status !== \"idle\",\n );\n\n React.useEffect(() => {\n if (allComplete) {\n const results = state.items\n .filter((item) => item.status === \"success\")\n .map((item) => item.result);\n if (results.length > 0) {\n onSuccess?.(results);\n }\n }\n }, [allComplete, state.items, onSuccess]);\n\n React.useEffect(() => {\n const errors = state.items.filter((item) => item.status === \"error\");\n const firstError = errors[0]?.error;\n if (firstError) {\n onError?.(firstError);\n }\n }, [state.items, onError]);\n\n const renderItem = ({ item }: { item: (typeof state.items)[0] }) => (\n <View key={item.id} style={styles.itemContainer}>\n <UploadProgress\n state={{\n status: item.status,\n progress: item.progress,\n bytesUploaded: item.bytesUploaded,\n totalBytes: item.totalBytes,\n error: item.error,\n result: item.result,\n }}\n label={item.file.data.name}\n />\n </View>\n );\n\n return (\n <View style={styles.container}>\n <Pressable\n style={[styles.button, isLoading && styles.buttonDisabled]}\n onPress={handlePress}\n disabled={isLoading}\n >\n {isLoading && (\n <ActivityIndicator\n size=\"small\"\n color=\"#FFFFFF\"\n style={styles.spinner}\n />\n )}\n <Text style={styles.buttonText}>\n {children || label}\n {hasItems && ` (${state.items.length})`}\n </Text>\n </Pressable>\n\n {hasItems && (\n <View style={styles.statsContainer}>\n <Text style={styles.statsText}>\n Progress: {state.items.filter((i) => i.status === \"success\").length}\n /{state.items.length} uploaded\n </Text>\n <Text style={styles.statsText}>Overall: {state.totalProgress}%</Text>\n </View>\n )}\n\n {showProgress && hasItems && (\n <FlatList\n scrollEnabled={false}\n data={state.items}\n renderItem={renderItem}\n keyExtractor={(item) => item.id}\n style={styles.listContainer}\n contentContainerStyle={styles.listContent}\n ItemSeparatorComponent={() => <View style={styles.separator} />}\n />\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n button: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"center\",\n paddingVertical: 12,\n paddingHorizontal: 16,\n backgroundColor: \"#34C759\",\n borderRadius: 8,\n gap: 8,\n },\n buttonDisabled: {\n opacity: 0.6,\n },\n buttonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FFFFFF\",\n },\n spinner: {\n marginRight: 4,\n },\n statsContainer: {\n paddingVertical: 8,\n paddingHorizontal: 12,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n gap: 4,\n },\n statsText: {\n fontSize: 12,\n color: \"#666666\",\n },\n listContainer: {\n maxHeight: 400,\n },\n listContent: {\n gap: 8,\n },\n itemContainer: {\n paddingHorizontal: 0,\n },\n separator: {\n height: 4,\n },\n});\n","import { FlatList, Pressable, StyleSheet, Text, View } from \"react-native\";\nimport type { UploadItem } from \"../types\";\nimport { UploadProgress } from \"./UploadProgress\";\n\nexport interface UploadListProps {\n /** List of upload items to display */\n items: UploadItem[];\n /** Callback when remove item is pressed */\n onRemove?: (id: string) => void;\n /** Callback when item is pressed */\n onItemPress?: (item: UploadItem) => void;\n /** Whether to show remove button */\n showRemoveButton?: boolean;\n}\n\n/**\n * Component to display a list of upload items with individual progress\n * Shows status indicators and allows removal of items\n */\nexport function UploadList({\n items,\n onRemove,\n onItemPress,\n showRemoveButton = true,\n}: UploadListProps) {\n const renderItem = ({ item }: { item: UploadItem }) => (\n <Pressable\n style={[\n styles.itemContainer,\n { borderLeftColor: getStatusColor(item.progress.state) },\n ]}\n onPress={() => onItemPress?.(item)}\n >\n <View style={styles.itemContent}>\n {item.file.status === \"success\" && (\n <View style={styles.itemHeader}>\n <Text style={styles.fileName} numberOfLines={1}>\n {item.file.data.name}\n </Text>\n <Text style={styles.fileSize}>\n {getFileSizeDisplay(item.file.data.size)}\n </Text>\n </View>\n )}\n {item.file.status === \"error\" && (\n <Text style={styles.errorText}>{item.progress.error?.message}</Text>\n )}\n <View style={styles.progressWrapper}>\n <UploadProgress\n state={{\n status:\n item.progress.state === \"pending\"\n ? \"idle\"\n : item.progress.state === \"cancelled\"\n ? \"aborted\"\n : item.progress.state,\n progress: item.progress.progress,\n bytesUploaded: item.progress.uploadedBytes,\n totalBytes: item.progress.totalBytes,\n error: item.progress.error || null,\n result: item.result ?? null,\n }}\n />\n </View>\n </View>\n {showRemoveButton &&\n item.progress.state !== \"uploading\" &&\n item.progress.state !== \"pending\" && (\n <Pressable\n style={styles.removeButton}\n onPress={() => onRemove?.(item.id)}\n hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}\n >\n <Text style={styles.removeButtonText}>✕</Text>\n </Pressable>\n )}\n </Pressable>\n );\n\n if (items.length === 0) {\n return (\n <View style={styles.emptyContainer}>\n <Text style={styles.emptyText}>No uploads</Text>\n </View>\n );\n }\n\n return (\n <View style={styles.container}>\n <View style={styles.headerRow}>\n <Text style={styles.headerText}>Uploads ({items.length})</Text>\n <Text style={styles.headerSubtext}>\n {items.filter((i) => i.progress.state === \"success\").length} complete\n </Text>\n </View>\n <FlatList\n scrollEnabled={false}\n data={items}\n renderItem={renderItem}\n keyExtractor={(item) => item.id}\n ItemSeparatorComponent={() => <View style={styles.separator} />}\n contentContainerStyle={styles.listContent}\n />\n </View>\n );\n}\n\n// Helper functions\nfunction getStatusColor(state: string): string {\n switch (state) {\n case \"success\":\n return \"#34C759\";\n case \"error\":\n case \"cancelled\":\n return \"#FF3B30\";\n case \"uploading\":\n case \"pending\":\n return \"#007AFF\";\n default:\n return \"#999999\";\n }\n}\n\nfunction getFileSizeDisplay(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${Math.round((bytes / k ** i) * 10) / 10} ${sizes[i]}`;\n}\n\nconst styles = StyleSheet.create({\n container: {\n gap: 8,\n },\n headerRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n paddingHorizontal: 12,\n paddingVertical: 8,\n backgroundColor: \"#f9f9f9\",\n borderRadius: 4,\n },\n headerText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#333333\",\n },\n errorText: {\n fontSize: 14,\n color: \"#FF3B30\",\n },\n headerSubtext: {\n fontSize: 14,\n color: \"#666666\",\n },\n listContent: {\n gap: 8,\n },\n itemContainer: {\n flexDirection: \"row\",\n alignItems: \"center\",\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderLeftWidth: 4,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n gap: 8,\n },\n itemContent: {\n flex: 1,\n gap: 6,\n },\n itemHeader: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n fileName: {\n fontSize: 14,\n fontWeight: \"500\",\n color: \"#333333\",\n flex: 1,\n },\n fileSize: {\n fontSize: 12,\n color: \"#999999\",\n marginLeft: 8,\n },\n progressWrapper: {\n marginTop: 2,\n },\n removeButton: {\n width: 32,\n height: 32,\n justifyContent: \"center\",\n alignItems: \"center\",\n borderRadius: 16,\n backgroundColor: \"#FFE5E5\",\n },\n removeButtonText: {\n fontSize: 16,\n fontWeight: \"600\",\n color: \"#FF3B30\",\n },\n separator: {\n height: 4,\n },\n emptyContainer: {\n paddingVertical: 24,\n paddingHorizontal: 12,\n backgroundColor: \"#f5f5f5\",\n borderRadius: 4,\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n emptyText: {\n fontSize: 14,\n color: \"#999999\",\n fontStyle: \"italic\",\n },\n});\n","import type { UploadFile } from \"@uploadista/core/types\";\nimport { createContext, type ReactNode, useCallback, useContext } from \"react\";\nimport {\n type MultiUploadState,\n type UploadItemState,\n useMultiUpload,\n} from \"../hooks/use-multi-upload\";\nimport { useUploadistaContext } from \"../hooks/use-uploadista-context\";\nimport type { FilePickResult } from \"../types\";\n\n// Re-export types for convenience\nexport type { MultiUploadState, UploadItemState };\n\n// ============ UPLOAD CONTEXT ============\n\n/**\n * Context value provided by the Upload component root.\n * Contains all upload state and actions.\n */\nexport interface UploadContextValue {\n /** Whether in multi-file mode */\n mode: \"single\" | \"multi\";\n /** Current multi-upload state (aggregate) */\n state: MultiUploadState;\n /** Whether auto-start is enabled */\n autoStart: boolean;\n\n /** Add files to the upload queue */\n addFiles: (files: FilePickResult[]) => string[];\n /** Remove an item from the queue */\n removeItem: (id: string) => void;\n /** Start all pending uploads */\n startAll: (itemIds?: string[]) => Promise<void>;\n /** Abort a specific upload by ID */\n abortItem: (id: string) => void;\n /** Retry a specific failed upload by ID */\n retryItem: (id: string) => Promise<void>;\n /** Clear all items and reset state */\n clear: () => void;\n\n /** Internal handler for files received from picker */\n handleFilesReceived: (files: FilePickResult[]) => void;\n /** Pick a file using the file system provider */\n pickFile: () => Promise<FilePickResult | null>;\n /** Pick an image using the file system provider */\n pickImage: () => Promise<FilePickResult | null>;\n /** Take a photo using the camera */\n takePhoto: () => Promise<FilePickResult | null>;\n}\n\nconst UploadContext = createContext<UploadContextValue | null>(null);\n\n/**\n * Hook to access upload context from within an Upload component.\n * @throws Error if used outside of an Upload component\n */\nexport function useUploadContext(): UploadContextValue {\n const context = useContext(UploadContext);\n if (!context) {\n throw new Error(\n \"useUploadContext must be used within an <Upload> component. \" +\n \"Wrap your component tree with <Upload>\",\n );\n }\n return context;\n}\n\n// ============ UPLOAD ITEM CONTEXT ============\n\n/**\n * Context value for a specific upload item within an Upload.\n */\nexport interface UploadItemContextValue {\n /** Item ID */\n id: string;\n /** The file being uploaded */\n file: Extract<FilePickResult, { status: \"success\" }>;\n /** Current upload state */\n state: {\n status: UploadItemState[\"status\"];\n progress: number;\n bytesUploaded: number;\n totalBytes: number;\n error: Error | null;\n result: UploadFile | null;\n };\n /** Abort this upload */\n abort: () => void;\n /** Retry this upload */\n retry: () => Promise<void>;\n /** Remove this item from the queue */\n remove: () => void;\n}\n\nconst UploadItemContext = createContext<UploadItemContextValue | null>(null);\n\n/**\n * Hook to access upload item context from within an Upload.Item component.\n * @throws Error if used outside of an Upload.Item component\n */\nexport function useUploadItemContext(): UploadItemContextValue {\n const context = useContext(UploadItemContext);\n if (!context) {\n throw new Error(\n \"useUploadItemContext must be used within an <Upload.Item> component. \" +\n 'Wrap your component with <Upload.Item id=\"...\">',\n );\n }\n return context;\n}\n\n// ============ UPLOAD ROOT COMPONENT ============\n\n/**\n * Render props for the Upload root component.\n */\nexport interface UploadRenderProps extends UploadContextValue {}\n\n/**\n * Props for the Upload root component.\n */\nexport interface UploadProps {\n /** Whether to allow multiple file uploads (default: false) */\n multiple?: boolean;\n /** Maximum concurrent uploads (default: 3, only used in multi mode) */\n maxConcurrent?: number;\n /** Whether to auto-start uploads when files are received (default: true) */\n autoStart?: boolean;\n /** Metadata to attach to uploads */\n metadata?: Record<string, string>;\n /** Called when a single file upload succeeds */\n onSuccess?: (result: UploadFile) => void;\n /** Called when an upload fails */\n onError?: (error: Error) => void;\n /** Called when all uploads complete (multi mode) */\n onComplete?: (results: {\n successful: number;\n failed: number;\n total: number;\n }) => void;\n /** Children to render (can be render function or ReactNode) */\n children: ReactNode | ((props: UploadRenderProps) => ReactNode);\n}\n\n/**\n * Root component for file uploads on React Native.\n * Provides context for all Upload sub-components.\n * Supports both single-file and multi-file modes via the `multiple` prop.\n *\n * @example Single file upload\n * ```tsx\n * <Upload onSuccess={handleSuccess}>\n * <Upload.FilePicker>\n * {({ pick, isLoading }) => (\n * <Pressable onPress={pick}>\n * <Text>Select File</Text>\n * </Pressable>\n * )}\n * </Upload.FilePicker>\n * <Upload.Progress>\n * {({ progress, isUploading }) => (\n * isUploading && <Text>{progress}%</Text>\n * )}\n * </Upload.Progress>\n * </Upload>\n * ```\n *\n * @example Multi-file upload\n * ```tsx\n * <Upload multiple maxConcurrent={3} onComplete={handleComplete}>\n * <Upload.GalleryPicker>\n * {({ pick }) => (\n * <Pressable onPress={pick}>\n * <Text>Select Photos</Text>\n * </Pressable>\n * )}\n * </Upload.GalleryPicker>\n * <Upload.Items>\n * {({ items }) => items.map(item => (\n * <Upload.Item key={item.id} id={item.id}>\n * {({ file, state, abort, remove }) => (\n * <View>\n * <Text>{file.data.name}: {state.progress}%</Text>\n * </View>\n * )}\n * </Upload.Item>\n * ))}\n * </Upload.Items>\n * <Upload.StartAll>\n * {({ start, disabled }) => (\n * <Pressable onPress={start} disabled={disabled}>\n * <Text>Upload All</Text>\n * </Pressable>\n * )}\n * </Upload.StartAll>\n * </Upload>\n * ```\n */\nfunction UploadRoot({\n multiple = false,\n maxConcurrent = 3,\n autoStart = true,\n metadata,\n onSuccess,\n onError,\n onComplete,\n children,\n}: UploadProps) {\n const { fileSystemProvider } = useUploadistaContext();\n\n const multiUpload = useMultiUpload({\n maxConcurrent,\n metadata,\n // Cast to unknown since the type definition uses unknown but implementation uses UploadFile\n onSuccess: onSuccess as ((result: unknown) => void) | undefined,\n onError,\n });\n\n // Track completion\n const checkComplete = useCallback(() => {\n const { items } = multiUpload.state;\n const allComplete =\n items.length > 0 &&\n items.every(\n (item) =>\n item.status === \"success\" ||\n item.status === \"error\" ||\n item.status === \"aborted\",\n );\n if (allComplete && onComplete) {\n const successful = items.filter(\n (item) => item.status === \"success\",\n ).length;\n const failed = items.filter(\n (item) => item.status === \"error\" || item.status === \"aborted\",\n ).length;\n onComplete({ successful, failed, total: items.length });\n }\n }, [multiUpload.state, onComplete]);\n\n const handleFilesReceived = useCallback(\n (files: FilePickResult[]) => {\n if (!multiple) {\n // Single mode: clear existing\n multiUpload.clear();\n }\n const ids = multiUpload.addFiles(files);\n if (autoStart && ids.length > 0) {\n multiUpload.startUploads(ids).then(checkComplete);\n }\n },\n [multiple, autoStart, multiUpload, checkComplete],\n );\n\n const pickFile = useCallback(async (): Promise<FilePickResult | null> => {\n if (!fileSystemProvider?.pickDocument) {\n throw new Error(\"File picker not available\");\n }\n const result = await fileSystemProvider.pickDocument();\n if (result.status === \"success\") {\n handleFilesReceived([result]);\n return result;\n }\n return null;\n }, [fileSystemProvider, handleFilesReceived]);\n\n const pickImage = useCallback(async (): Promise<FilePickResult | null> => {\n if (!fileSystemProvider?.pickImage) {\n throw new Error(\"Image picker not available\");\n }\n const result = await fileSystemProvider.pickImage({\n allowMultiple: multiple,\n });\n if (result.status === \"success\") {\n handleFilesReceived([result]);\n return result;\n }\n return null;\n }, [fileSystemProvider, handleFilesReceived, multiple]);\n\n const takePhoto = useCallback(async (): Promise<FilePickResult | null> => {\n if (!fileSystemProvider?.pickCamera) {\n throw new Error(\"Camera not available\");\n }\n const result = await fileSystemProvider.pickCamera();\n if (result.status === \"success\") {\n handleFilesReceived([result]);\n return result;\n }\n return null;\n }, [fileSystemProvider, handleFilesReceived]);\n\n const contextValue: UploadContextValue = {\n mode: multiple ? \"multi\" : \"single\",\n state: multiUpload.state,\n autoStart,\n addFiles: multiUpload.addFiles,\n removeItem: multiUpload.removeItem,\n startAll: async (ids) => {\n await multiUpload.startUploads(ids);\n checkComplete();\n },\n abortItem: multiUpload.abortItem,\n retryItem: multiUpload.retryItem,\n clear: multiUpload.clear,\n handleFilesReceived,\n pickFile,\n pickImage,\n takePhoto,\n };\n\n return (\n <UploadContext.Provider value={contextValue}>\n {typeof children === \"function\" ? children(contextValue) : children}\n </UploadContext.Provider>\n );\n}\n\n// ============ FILE PICKER PRIMITIVE ============\n\n/**\n * Render props for Upload.FilePicker component.\n */\nexport interface UploadFilePickerRenderProps {\n /** Pick a file */\n pick: () => Promise<void>;\n /** Whether a pick operation is in progress */\n isLoading: boolean;\n}\n\n/**\n * Props for Upload.FilePicker component.\n */\nexport interface UploadFilePickerProps {\n /** Render function receiving picker state */\n children: (props: UploadFilePickerRenderProps) => ReactNode;\n}\n\n/**\n * File picker component for document selection.\n *\n * @example\n * ```tsx\n * <Upload.FilePicker>\n * {({ pick }) => (\n * <Pressable onPress={pick}>\n * <Text>Select Document</Text>\n * </Pressable>\n * )}\n * </Upload.FilePicker>\n * ```\n */\nfunction UploadFilePicker({ children }: UploadFilePickerProps) {\n const upload = useUploadContext();\n\n const pick = useCallback(async () => {\n await upload.pickFile();\n }, [upload]);\n\n const renderProps: UploadFilePickerRenderProps = {\n pick,\n isLoading: upload.state.activeCount > 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ GALLERY PICKER PRIMITIVE ============\n\n/**\n * Render props for Upload.GalleryPicker component.\n */\nexport interface UploadGalleryPickerRenderProps {\n /** Pick from gallery */\n pick: () => Promise<void>;\n /** Whether a pick operation is in progress */\n isLoading: boolean;\n}\n\n/**\n * Props for Upload.GalleryPicker component.\n */\nexport interface UploadGalleryPickerProps {\n /** Render function receiving picker state */\n children: (props: UploadGalleryPickerRenderProps) => ReactNode;\n}\n\n/**\n * Gallery picker component for image selection.\n *\n * @example\n * ```tsx\n * <Upload.GalleryPicker>\n * {({ pick }) => (\n * <Pressable onPress={pick}>\n * <Text>Select Photos</Text>\n * </Pressable>\n * )}\n * </Upload.GalleryPicker>\n * ```\n */\nfunction UploadGalleryPicker({ children }: UploadGalleryPickerProps) {\n const upload = useUploadContext();\n\n const pick = useCallback(async () => {\n await upload.pickImage();\n }, [upload]);\n\n const renderProps: UploadGalleryPickerRenderProps = {\n pick,\n isLoading: upload.state.activeCount > 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ CAMERA PICKER PRIMITIVE ============\n\n/**\n * Render props for Upload.CameraPicker component.\n */\nexport interface UploadCameraPickerRenderProps {\n /** Take a photo */\n take: () => Promise<void>;\n /** Whether a capture is in progress */\n isLoading: boolean;\n}\n\n/**\n * Props for Upload.CameraPicker component.\n */\nexport interface UploadCameraPickerProps {\n /** Render function receiving picker state */\n children: (props: UploadCameraPickerRenderProps) => ReactNode;\n}\n\n/**\n * Camera picker component for photo capture.\n *\n * @example\n * ```tsx\n * <Upload.CameraPicker>\n * {({ take }) => (\n * <Pressable onPress={take}>\n * <Text>Take Photo</Text>\n * </Pressable>\n * )}\n * </Upload.CameraPicker>\n * ```\n */\nfunction UploadCameraPicker({ children }: UploadCameraPickerProps) {\n const upload = useUploadContext();\n\n const take = useCallback(async () => {\n await upload.takePhoto();\n }, [upload]);\n\n const renderProps: UploadCameraPickerRenderProps = {\n take,\n isLoading: upload.state.activeCount > 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ITEMS PRIMITIVE ============\n\n/**\n * Render props for Upload.Items component.\n */\nexport interface UploadItemsRenderProps {\n /** All upload items */\n items: UploadItemState[];\n /** Whether there are any items */\n hasItems: boolean;\n /** Whether items array is empty */\n isEmpty: boolean;\n}\n\n/**\n * Props for Upload.Items component.\n */\nexport interface UploadItemsProps {\n /** Render function receiving items */\n children: (props: UploadItemsRenderProps) => ReactNode;\n}\n\n/**\n * Renders the list of upload items via render props.\n *\n * @example\n * ```tsx\n * <Upload.Items>\n * {({ items, isEmpty }) => (\n * isEmpty ? <Text>No files</Text> : (\n * items.map(item => (\n * <Upload.Item key={item.id} id={item.id}>\n * {(props) => ...}\n * </Upload.Item>\n * ))\n * )\n * )}\n * </Upload.Items>\n * ```\n */\nfunction UploadItems({ children }: UploadItemsProps) {\n const upload = useUploadContext();\n\n const renderProps: UploadItemsRenderProps = {\n items: upload.state.items,\n hasItems: upload.state.items.length > 0,\n isEmpty: upload.state.items.length === 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ITEM PRIMITIVE ============\n\n/**\n * Props for Upload.Item component.\n */\nexport interface UploadItemProps {\n /** Item ID */\n id: string;\n /** Children (can be render function or regular children) */\n children: ReactNode | ((props: UploadItemContextValue) => ReactNode);\n}\n\n/**\n * Scoped context provider for a specific upload item.\n *\n * @example\n * ```tsx\n * <Upload.Item id={item.id}>\n * {({ file, state, abort, remove }) => (\n * <View>\n * <Text>{file.data.name}</Text>\n * <Text>{state.progress}%</Text>\n * <Pressable onPress={abort}><Text>Cancel</Text></Pressable>\n * <Pressable onPress={remove}><Text>Remove</Text></Pressable>\n * </View>\n * )}\n * </Upload.Item>\n * ```\n */\nfunction UploadItem({ id, children }: UploadItemProps) {\n const upload = useUploadContext();\n\n const item = upload.state.items.find((i) => i.id === id);\n\n if (!item) {\n // Item not found\n return null;\n }\n\n const contextValue: UploadItemContextValue = {\n id,\n file: item.file,\n state: {\n status: item.status,\n progress: item.progress,\n bytesUploaded: item.bytesUploaded,\n totalBytes: item.totalBytes,\n error: item.error,\n result: item.result,\n },\n abort: () => upload.abortItem(id),\n retry: () => upload.retryItem(id),\n remove: () => upload.removeItem(id),\n };\n\n return (\n <UploadItemContext.Provider value={contextValue}>\n {typeof children === \"function\" ? children(contextValue) : children}\n </UploadItemContext.Provider>\n );\n}\n\n// ============ PROGRESS PRIMITIVE ============\n\n/**\n * Render props for Upload.Progress component.\n */\nexport interface UploadProgressRenderProps {\n /** Progress percentage (0-100) */\n progress: number;\n /** Bytes uploaded so far */\n bytesUploaded: number;\n /** Total bytes to upload */\n totalBytes: number;\n /** Whether any uploads are active */\n isUploading: boolean;\n}\n\n/**\n * Props for Upload.Progress component.\n */\nexport interface UploadProgressProps {\n /** Render function receiving progress state */\n children: (props: UploadProgressRenderProps) => ReactNode;\n}\n\n/**\n * Progress display component within an Upload.\n *\n * @example\n * ```tsx\n * <Upload.Progress>\n * {({ progress, isUploading }) => (\n * isUploading && <Text>{progress}%</Text>\n * )}\n * </Upload.Progress>\n * ```\n */\nfunction UploadProgress({ children }: UploadProgressProps) {\n const upload = useUploadContext();\n\n const renderProps: UploadProgressRenderProps = {\n progress: upload.state.totalProgress,\n bytesUploaded: upload.state.totalUploaded,\n totalBytes: upload.state.totalBytes,\n isUploading: upload.state.activeCount > 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ STATUS PRIMITIVE ============\n\n/**\n * Render props for Upload.Status component.\n */\nexport interface UploadStatusRenderProps {\n /** Overall status */\n status: \"idle\" | \"uploading\" | \"success\" | \"error\";\n /** Whether idle (no uploads active or completed) */\n isIdle: boolean;\n /** Whether uploading */\n isUploading: boolean;\n /** Whether all uploads succeeded */\n isSuccess: boolean;\n /** Whether any upload failed */\n isError: boolean;\n /** Number of total items */\n total: number;\n /** Number of successful uploads */\n successful: number;\n /** Number of failed uploads */\n failed: number;\n /** Number of currently uploading */\n active: number;\n}\n\n/**\n * Props for Upload.Status component.\n */\nexport interface UploadStatusProps {\n /** Render function receiving status state */\n children: (props: UploadStatusRenderProps) => ReactNode;\n}\n\n/**\n * Status display component within an Upload.\n *\n * @example\n * ```tsx\n * <Upload.Status>\n * {({ status, successful, failed, total }) => (\n * <Text>\n * {status}: {successful}/{total} uploaded, {failed} failed\n * </Text>\n * )}\n * </Upload.Status>\n * ```\n */\nfunction UploadStatus({ children }: UploadStatusProps) {\n const upload = useUploadContext();\n const { state } = upload;\n\n // Derive overall status\n let status: \"idle\" | \"uploading\" | \"success\" | \"error\" = \"idle\";\n if (state.activeCount > 0) {\n status = \"uploading\";\n } else if (state.items.length > 0) {\n const allComplete = state.items.every(\n (item) =>\n item.status === \"success\" ||\n item.status === \"error\" ||\n item.status === \"aborted\",\n );\n if (allComplete) {\n status = state.failedCount > 0 ? \"error\" : \"success\";\n }\n }\n\n const renderProps: UploadStatusRenderProps = {\n status,\n isIdle: status === \"idle\",\n isUploading: state.activeCount > 0,\n isSuccess:\n state.completedCount > 0 &&\n state.failedCount === 0 &&\n state.activeCount === 0,\n isError: state.failedCount > 0,\n total: state.items.length,\n successful: state.completedCount,\n failed: state.failedCount,\n active: state.activeCount,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ERROR PRIMITIVE ============\n\n/**\n * Render props for Upload.Error component.\n */\nexport interface UploadErrorRenderProps {\n /** Whether there are any errors */\n hasError: boolean;\n /** Number of failed uploads */\n failedCount: number;\n /** Failed items */\n failedItems: UploadItemState[];\n /** Clear all items */\n clear: () => void;\n}\n\n/**\n * Props for Upload.Error component.\n */\nexport interface UploadErrorProps {\n /** Render function receiving error state */\n children: (props: UploadErrorRenderProps) => ReactNode;\n}\n\n/**\n * Error display component within an Upload.\n *\n * @example\n * ```tsx\n * <Upload.Error>\n * {({ hasError, failedItems, clear }) => (\n * hasError && (\n * <View>\n * {failedItems.map(item => (\n * <Text key={item.id}>{item.file.data.name}: {item.error?.message}</Text>\n * ))}\n * <Pressable onPress={clear}><Text>Clear</Text></Pressable>\n * </View>\n * )\n * )}\n * </Upload.Error>\n * ```\n */\nfunction UploadError({ children }: UploadErrorProps) {\n const upload = useUploadContext();\n\n const failedItems = upload.state.items.filter(\n (item) => item.status === \"error\" || item.status === \"aborted\",\n );\n\n const renderProps: UploadErrorRenderProps = {\n hasError: failedItems.length > 0,\n failedCount: failedItems.length,\n failedItems,\n clear: upload.clear,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ ACTION PRIMITIVES ============\n\n/**\n * Render props for Upload.Cancel component.\n */\nexport interface UploadCancelRenderProps {\n /** Cancel all uploads */\n cancel: () => void;\n /** Whether cancel is disabled */\n disabled: boolean;\n}\n\n/**\n * Props for Upload.Cancel component.\n */\nexport interface UploadCancelProps {\n /** Render function receiving cancel state */\n children: (props: UploadCancelRenderProps) => ReactNode;\n}\n\n/**\n * Cancel component that aborts all active uploads.\n */\nfunction UploadCancel({ children }: UploadCancelProps) {\n const upload = useUploadContext();\n\n const cancel = useCallback(() => {\n for (const item of upload.state.items.filter(\n (item) => item.status === \"uploading\",\n )) {\n upload.abortItem(item.id);\n }\n }, [upload]);\n\n const renderProps: UploadCancelRenderProps = {\n cancel,\n disabled: upload.state.activeCount === 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n/**\n * Render props for Upload.Retry component.\n */\nexport interface UploadRetryRenderProps {\n /** Retry all failed uploads */\n retry: () => Promise<void>;\n /** Whether retry is disabled */\n disabled: boolean;\n}\n\n/**\n * Props for Upload.Retry component.\n */\nexport interface UploadRetryProps {\n /** Render function receiving retry state */\n children: (props: UploadRetryRenderProps) => ReactNode;\n}\n\n/**\n * Retry component that retries all failed uploads.\n */\nfunction UploadRetry({ children }: UploadRetryProps) {\n const upload = useUploadContext();\n\n const retry = useCallback(async () => {\n const failedItems = upload.state.items.filter(\n (item) => item.status === \"error\" || item.status === \"aborted\",\n );\n for (const item of failedItems) {\n await upload.retryItem(item.id);\n }\n }, [upload]);\n\n const renderProps: UploadRetryRenderProps = {\n retry,\n disabled: upload.state.failedCount === 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n/**\n * Render props for Upload.Reset component.\n */\nexport interface UploadResetRenderProps {\n /** Reset all state */\n reset: () => void;\n}\n\n/**\n * Props for Upload.Reset component.\n */\nexport interface UploadResetProps {\n /** Render function receiving reset state */\n children: (props: UploadResetRenderProps) => ReactNode;\n}\n\n/**\n * Reset component that clears all items and state.\n */\nfunction UploadReset({ children }: UploadResetProps) {\n const upload = useUploadContext();\n\n const renderProps: UploadResetRenderProps = {\n reset: upload.clear,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n/**\n * Render props for Upload.StartAll component.\n */\nexport interface UploadStartAllRenderProps {\n /** Start all pending uploads */\n start: () => Promise<void>;\n /** Whether start is disabled */\n disabled: boolean;\n}\n\n/**\n * Props for Upload.StartAll component.\n */\nexport interface UploadStartAllProps {\n /** Render function receiving start state */\n children: (props: UploadStartAllRenderProps) => ReactNode;\n}\n\n/**\n * Start all component that begins all queued uploads.\n */\nfunction UploadStartAll({ children }: UploadStartAllProps) {\n const upload = useUploadContext();\n\n const idleCount = upload.state.items.filter(\n (item) => item.status === \"idle\",\n ).length;\n\n const start = useCallback(async () => {\n await upload.startAll();\n }, [upload]);\n\n const renderProps: UploadStartAllRenderProps = {\n start,\n disabled: upload.state.activeCount > 0 || idleCount === 0,\n };\n\n return <>{children(renderProps)}</>;\n}\n\n// ============ COMPOUND COMPONENT EXPORT ============\n\n/**\n * Upload compound component for React Native.\n *\n * Provides a composable, headless API for building upload interfaces on mobile.\n * Uses picker components instead of drag-and-drop (which isn't available on mobile).\n * All sub-components use render props for complete UI control.\n *\n * @example Single file upload\n * ```tsx\n * <Upload onSuccess={handleSuccess}>\n * <Upload.FilePicker>\n * {({ pick }) => (\n * <Pressable onPress={pick}>\n * <Text>Select File</Text>\n * </Pressable>\n * )}\n * </Upload.FilePicker>\n * <Upload.Progress>\n * {({ progress }) => <Text>{progress}%</Text>}\n * </Upload.Progress>\n * </Upload>\n * ```\n *\n * @example Multi-file upload\n * ```tsx\n * <Upload multiple maxConcurrent={3} onComplete={handleComplete}>\n * <Upload.GalleryPicker>\n * {({ pick }) => (\n * <Pressable onPress={pick}>\n * <Text>Select Photos</Text>\n * </Pressable>\n * )}\n * </Upload.GalleryPicker>\n * <Upload.Items>\n * {({ items }) => items.map(item => (\n * <Upload.Item key={item.id} id={item.id}>\n * {({ file, state }) => (\n * <Text>{file.data.name}: {state.progress}%</Text>\n * )}\n * </Upload.Item>\n * ))}\n * </Upload.Items>\n * <Upload.StartAll>\n * {({ start, disabled }) => (\n * <Pressable onPress={start} disabled={disabled}>\n * <Text>Upload All</Text>\n * </Pressable>\n * )}\n * </Upload.StartAll>\n * </Upload>\n * ```\n */\nexport const Upload = Object.assign(UploadRoot, {\n FilePicker: UploadFilePicker,\n GalleryPicker: UploadGalleryPicker,\n CameraPicker: UploadCameraPicker,\n Items: UploadItems,\n Item: UploadItem,\n Progress: UploadProgress,\n Status: UploadStatus,\n Error: UploadError,\n Cancel: UploadCancel,\n Retry: UploadRetry,\n Reset: UploadReset,\n StartAll: UploadStartAll,\n});\n"],"mappings":"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"}