npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-viewdrop-ios

v0.2.4

Published

ViewDrop is a module for React Native that will allow View to use a native iOS feature to transfer pictures, videos, files, and more through a simple Drag & Drop action.

Readme

react-native-viewdrop-ios

ViewDrop is a React Native module that turns any View into a native iOS drag-and-drop target. Drop images, videos, audio, documents, or any other file — one at a time or in batches.

Work of library GIF


Requirements

  • React Native >= 0.71.0
  • iOS >= 13.0
  • Supports both Old Architecture and New Architecture (Fabric)

Installation

npm install react-native-viewdrop-ios
# or
yarn add react-native-viewdrop-ios

For New Architecture projects, no extra setup is needed — the library auto-detects Fabric.

For projects using use_modular_headers! in their Podfile (common with New Arch), ensure it's set globally or for this pod.


Supported platforms

| Platform | Status | |----------|--------| | iOS | ✅ Supported (Old Arch + Fabric) | | macOS | 🚧 In development | | Android / Web / tvOS / visionOS | ❌ Not planned |


Quick start

import { ViewDrop } from 'react-native-viewdrop-ios';

<ViewDrop
  style={{ flex: 1 }}
  onImageReceived={(base64) => console.log(base64)}
  onDropItemDetected={() => console.log('drag started')}
>
  <Text>Drop files here</Text>
</ViewDrop>

Props

Event callbacks

| Prop | Type | Description | |------|------|-------------| | onDropItemDetected | () => void | Fires when a drag session enters the view. Use it to animate the drop zone. | | onImageReceived | (image: string) => void | Fires when a single image is dropped. image is a base64 data-URI. Only active when isEnableMultiDropping is false. | | onVideoReceived | ({ fileName, fullUrl }) => void | Fires when a single video is dropped. fullUrl is a temporary file path. Only active when isEnableMultiDropping is false. | | onAudioReceived | ({ fileName, fullUrl }) => void | Fires when a single audio file is dropped. Only active when isEnableMultiDropping is false. | | onFileReceived | ({ fileName, fileUrl, typeIdentifier }) => void | Fires for any other file type (PDF, ZIP, etc.) dropped as a single item. Only active when isEnableMultiDropping is false. | | onFileItemsReceived | (data: Record<'image'\|'video'\|'audio'\|'file', FileInfo[]>) => void | Fires when isEnableMultiDropping is true. Contains all dropped files grouped by category. Works for single-file drops too. |

Filter props

| Prop | Type | Default | Description | |------|------|---------|-------------| | fileTypes | ('image' \| 'video' \| 'audio' \| 'file')[] | all | Accepted file categories. Uses Apple's UTType system under the hood. | | whiteListExtensions | string[] | — | Only files whose extension is in this list are accepted. | | blackListExtensions | string[] | — | Files whose extension is in this list are rejected. | | isEnableMultiDropping | boolean | false | Routes all drops (including single-file) through onFileItemsReceived. | | allowPartialDrop | boolean | false | Requires isEnableMultiDropping. Changes filter behaviour from session-level to per-file (see below). |

Image resize props

| Prop | Type | Default | Description | |------|------|---------|-------------| | imageResize | ImageResizeConfig | — | Downscale and/or recompress images before they are delivered to onImageReceived. Has no effect on videos, audio, or generic files. |

type ImageResizeConfig = {
  maxWidth?:  number;                     // max output width in pixels;  0 = unlimited (default: 0)
  maxHeight?: number;                     // max output height in pixels; 0 = unlimited (default: 0)
  quality?:   number;                     // JPEG compression 0.0–1.0 (default: 1.0)
  mode?:      'aspectFit' | 'aspectFill'; // scaling mode              (default: 'aspectFit')
};
  • Only fields you specify are applied; omitted fields use their defaults.
  • When both maxWidth and maxHeight are 0 (or imageResize is not set) images are delivered at their original size.
  • aspectFit (default) fits the image inside the bounding box, preserving aspect ratio. aspectFill fills the bounding box, cropping if necessary.
  • Output encoding: JPEG when quality < 1.0, PNG otherwise.

Filtering in depth

Session-level vs per-file filtering

By default (allowPartialDrop = false) filtering is session-level: the entire drop is either accepted or rejected as a unit. If any single dragged item fails a filter, the whole session is rejected and iOS shows a red "forbidden" indicator.

When allowPartialDrop = true filtering becomes per-file: the drop session is always accepted visually, but individual files that fail the filters are silently removed from the result. This is useful when users drag a mixed batch of files.

allowPartialDrop = false (default)          allowPartialDrop = true
─────────────────────────────────           ─────────────────────────────────
[a.pdf, b.exe, c.docx]                      [a.pdf, b.exe, c.docx]
     blackList = ['exe']                          blackList = ['exe']
            │                                            │
     ┌──────▼──────┐                          ┌──────────▼──────────┐
     │  any .exe?  │                          │  filter per file    │
     └──────┬──────┘                          │  a.pdf → ✅ pass    │
           yes                                │  b.exe → ❌ skip    │
            │                                 │  c.docx → ✅ pass   │
     ┌──────▼───────┐                         └──────────┬──────────┘
     │  FORBIDDEN   │                                    │
     │  (grey icon) │                onFileItemsReceived({ file: [a.pdf, c.docx] })
     └──────────────┘

fileTypes

Filters by Apple UTType category. If not specified, all types are accepted.

// Accept only images and generic documents
<ViewDrop fileTypes={['image', 'file']} ... />

whiteListExtensions — allow-list

Only files whose extension is in the list pass. All other extensions are blocked.

// Accept only PDF, DOCX, and TXT files
<ViewDrop whiteListExtensions={['pdf', 'docx', 'txt']} ... />

With allowPartialDrop: non-matching files are silently dropped from the result instead of rejecting the whole session.

blackListExtensions — block-list

Files whose extension matches the list are rejected. All other extensions pass.

// Block executables and shell scripts
<ViewDrop blackListExtensions={['exe', 'bat', 'sh', 'cmd']} ... />

With allowPartialDrop: matching files are removed from the result; the rest are delivered normally.

Combining filters

All active filters are applied together with AND logic. A file must satisfy every specified constraint to pass:

  1. File category must match fileTypes (if specified)
  2. AND file extension must be in whiteListExtensions (if specified)
  3. AND file extension must not be in blackListExtensions (if specified)

fileTypes + whiteListExtensions

fileTypes coarsely pre-filters by Apple UTType (image/video/audio/file). whiteListExtensions then narrows to specific extensions within that category.

fileTypes=['image'] + whiteListExtensions=['png','jpg']
────────────────────────────────────────────────────────
photo.png  → image ✅ → png  ✅ → PASS
photo.heic → image ✅ → heic ❌ → REJECT
doc.pdf    → file  ❌          → REJECT

Typical use-cases:

  • Accept only raster images, block HEIC/RAW: fileTypes=['image'] + whiteListExtensions=['png','jpg','jpeg']
  • Accept only specific document formats: fileTypes=['file'] + whiteListExtensions=['pdf','docx','xlsx']
  • Accept audio but only lossless: fileTypes=['audio'] + whiteListExtensions=['flac','wav','aiff']

fileTypes + blackListExtensions

fileTypes accepts the whole category; blackListExtensions carves out unwanted extensions inside it.

fileTypes=['image'] + blackListExtensions=['heic','heif']
──────────────────────────────────────────────────────────
photo.png  → image ✅ → not heic ✅ → PASS
photo.heic → image ✅ → heic     ❌ → REJECT
video.mp4  → video ❌             → REJECT

Typical use-cases:

  • Accept all images except HEIC: fileTypes=['image'] + blackListExtensions=['heic','heif']
  • Accept all documents except archives: fileTypes=['file'] + blackListExtensions=['zip','rar','7z']

All three together

fileTypes + whiteListExtensions + blackListExtensions can be combined, though whitelist and blacklist on the same extension set is unusual. A more realistic pattern is using fileTypes for category selection and one list for extension refinement.


Multi-file dropping

Enable with isEnableMultiDropping. All results — even a single-file drop — arrive in onFileItemsReceived grouped by category:

import { ViewDrop, MapKeysMultiItems, type FileInfo } from 'react-native-viewdrop-ios';

<ViewDrop
  isEnableMultiDropping
  onFileItemsReceived={(data) => {
    // data.image  → FileInfo[]  (PNG, JPEG, HEIC, …)
    // data.video  → FileInfo[]  (MP4, MOV, …)
    // data.audio  → FileInfo[]  (MP3, AAC, …)
    // data.file   → FileInfo[]  (PDF, ZIP, DOCX, …)
    console.log(data);
  }}
/>

FileInfo shape:

type FileInfo = {
  fileName: string;       // e.g. "photo.png"
  fileUrl: string;        // absolute path to a temporary copy on disk (no "file://" prefix)
  typeIdentifier: string; // UTType category: "image" | "video" | "audio" | "file"
};

fileUrl is a raw file-system path, not a URI — it does not include a file:// scheme. To display a file in a React Native <Image> or pass it to a media player, prepend the scheme manually:

<Image source={{ uri: `file://${fileUrl}` }} />

The file is a temporary copy managed by iOS and may be deleted after the drop session ends. Copy it to a permanent location (e.g. the app's Documents directory) if you need it beyond the current screen.


Examples

Accept any file — single drop

<ViewDrop
  onImageReceived={(base64) => setImage(base64)}
  onVideoReceived={({ fullUrl }) => setVideo(fullUrl)}
  onAudioReceived={({ fullUrl }) => setAudio(fullUrl)}
  onFileReceived={({ fileName, fileUrl }) => console.log(fileName, fileUrl)}
  onDropItemDetected={() => setHint('Drop!')}
/>

Accept any file — multi drop

<ViewDrop
  isEnableMultiDropping
  onFileItemsReceived={(data) => {
    data.image?.forEach((f) => console.log('image:', f.fileName));
    data.video?.forEach((f) => console.log('video:', f.fileName));
    data.file?.forEach((f)  => console.log('file:', f.fileName));
  }}
/>

Allow only images (whitelist by type)

<ViewDrop
  fileTypes={['image']}
  onImageReceived={(base64) => setImage(base64)}
/>

Allow only PNG and JPEG (whitelist by extension)

If any dragged file is not a PNG or JPEG the whole drop is rejected (red indicator).

<ViewDrop
  whiteListExtensions={['png', 'jpg', 'jpeg']}
  onImageReceived={(base64) => setImage(base64)}
/>

Block executables — reject whole batch

// If the user drags even one .exe, the entire drop is rejected.
<ViewDrop
  isEnableMultiDropping
  blackListExtensions={['exe', 'bat', 'sh']}
  onFileItemsReceived={(data) => console.log(data)}
/>

Block executables — filter silently (allowPartialDrop)

// .exe files are removed; the rest arrive normally.
<ViewDrop
  isEnableMultiDropping
  allowPartialDrop
  blackListExtensions={['exe', 'bat', 'sh']}
  onFileItemsReceived={(data) => console.log(data)}
/>

Accept only PDF from a mixed batch (allowPartialDrop + whitelist)

// Drop [a.pdf, b.png, c.txt] → only a.pdf arrives in the callback.
<ViewDrop
  isEnableMultiDropping
  allowPartialDrop
  whiteListExtensions={['pdf']}
  onFileItemsReceived={(data) => {
    // data.file = [{ fileName: 'a.pdf', ... }]
  }}
/>

fileTypes + whiteListExtensions — only PNG/JPEG images (strict)

The entire drop session is rejected if any file is not a PNG or JPEG image.

<ViewDrop
  isEnableMultiDropping
  fileTypes={['image']}
  whiteListExtensions={['png', 'jpg', 'jpeg']}
  onFileItemsReceived={(data) => {
    // data.image contains only PNG/JPEG files
  }}
/>

fileTypes + whiteListExtensions + allowPartialDrop — filter PNG/JPEG per-file

Drop session is always accepted. Non-PNG/JPEG files and non-image files are silently removed from the result.

// Drop [photo.png, photo.heic, doc.pdf]
// → onFileItemsReceived receives only photo.png
<ViewDrop
  isEnableMultiDropping
  allowPartialDrop
  fileTypes={['image']}
  whiteListExtensions={['png', 'jpg', 'jpeg']}
  onFileItemsReceived={(data) => {
    // data.image = [{ fileName: 'photo.png', ... }]
  }}
/>

fileTypes + blackListExtensions — images, but block HEIC (strict)

Drops containing HEIC files or non-image files are rejected at the session level.

<ViewDrop
  isEnableMultiDropping
  fileTypes={['image']}
  blackListExtensions={['heic', 'heif']}
  onFileItemsReceived={(data) => {
    // data.image contains any image format except HEIC/HEIF
  }}
/>

fileTypes + blackListExtensions + allowPartialDrop — remove HEIC per-file

Drop session is always accepted. HEIC/HEIF images are filtered out; all other image formats pass through.

// Drop [photo.png, photo.heic, shot.heif]
// → onFileItemsReceived receives only photo.png
<ViewDrop
  isEnableMultiDropping
  allowPartialDrop
  fileTypes={['image']}
  blackListExtensions={['heic', 'heif']}
  onFileItemsReceived={(data) => {
    // data.image = [{ fileName: 'photo.png', ... }]
  }}
/>

fileTypes + whiteListExtensions — documents only (PDF, DOCX, XLSX)

<ViewDrop
  isEnableMultiDropping
  fileTypes={['file']}
  whiteListExtensions={['pdf', 'docx', 'xlsx']}
  onFileItemsReceived={(data) => {
    data.file?.forEach((f) => console.log(f.fileName));
  }}
/>

Resize and compress images before delivery

<ViewDrop
  imageResize={{ maxWidth: 800, maxHeight: 800, quality: 0.8 }}
  onImageReceived={(base64) => setImage(base64)}
/>

Drop an image → it is scaled down to fit within 800×800 px (aspectFit) and JPEG-compressed at 80% quality before being base64-encoded and sent to the callback.

// Only limit width, keep quality lossless
<ViewDrop
  imageResize={{ maxWidth: 1200 }}
  onImageReceived={(base64) => setImage(base64)}
/>

// Lossless PNG, no size limit — same as not passing imageResize at all
<ViewDrop
  imageResize={{ quality: 1.0 }}
  onImageReceived={(base64) => setImage(base64)}
/>

// Crop to fill a square thumbnail
<ViewDrop
  imageResize={{ maxWidth: 400, maxHeight: 400, mode: 'aspectFill', quality: 0.9 }}
  onImageReceived={(base64) => setImage(base64)}
/>

Notes

  • onImageReceived, onVideoReceived, onAudioReceived, onFileReceived are only called when isEnableMultiDropping is false. When multi-dropping is enabled, use onFileItemsReceived for everything.
  • allowPartialDrop has no effect unless isEnableMultiDropping is also enabled.
  • fileUrl in FileInfo is a raw path without a file:// scheme (e.g. /private/var/.../photo.png). Use `file://${fileUrl}` when passing it to <Image>, video players, or any API that expects a URI. The file is a temporary copy managed by iOS — copy it to a permanent location if you need it to persist.
  • Extensions in whiteListExtensions / blackListExtensions are case-insensitive ('PDF' and 'pdf' are the same).
  • fileTypes and whiteListExtensions / blackListExtensions operate on different mechanisms and cannot substitute for each other. fileTypes uses Apple's UTType conformance system — formats not registered in iOS (e.g. .ogg) will never conform to kUTTypeAudio, so adding 'ogg' to whiteListExtensions will not help when fileTypes=['audio'] is set. For non-standard or niche formats, omit fileTypes entirely and rely solely on whiteListExtensions to control what is accepted.

Future plans

  • Drop preview / badge customisation
  • macOS support

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT


Made with create-react-native-library