@shipstatic/drop 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,26 @@
4
4
 
5
5
  A focused React hook for preparing files for deployment with [@shipstatic/ship](https://github.com/shipstatic/ship). Handles ZIP extraction, path normalization, and validation - everything needed before calling `ship.deploy()`.
6
6
 
7
- **v0.1.6 Update:** Now includes built-in drag & drop support with prop getters! No need to manually implement drag & drop handlers - just spread `{...drop.getDropzoneProps()}` on your container and `{...drop.getInputProps()}` on the input element. Folder structure preservation is handled automatically.
7
+ Built-in drag & drop support with prop getters! No need to manually implement drag & drop handlers - just spread `{...drop.getDropzoneProps()}` on your container and `{...drop.getInputProps()}` on the input element. Folder structure preservation is handled automatically.
8
8
 
9
9
  **Note:** MD5 calculation is handled by Ship SDK during deployment. Drop focuses on file processing and UI state management.
10
10
 
11
+ ## Table of Contents
12
+
13
+ - [Why Headless?](#why-headless)
14
+ - [Features](#features)
15
+ - [Installation](#installation)
16
+ - [Requirements](#requirements)
17
+ - [Quick Start](#quick-start)
18
+ - [Configuration Architecture](#️-configuration-architecture)
19
+ - [Advanced Usage](#advanced-programmatic-file-picker)
20
+ - [API Reference](#api)
21
+ - [State Machine](#state-machine)
22
+ - [Error Handling](#error-handling)
23
+ - [Types](#types)
24
+ - [Ship SDK Integration](#direct-ship-sdk-integration)
25
+ - [Architecture Decisions](#architecture-decisions)
26
+
11
27
  ## Why Headless?
12
28
 
13
29
  This package provides **zero UI components** - just a React hook with built-in drag & drop functionality. You bring your own styling.
@@ -16,8 +32,7 @@ This package provides **zero UI components** - just a React hook with built-in d
16
32
  1. **Built-in drag & drop** - Proper folder support with `webkitGetAsEntry` API, all handled internally
17
33
  2. **Prop getters API** - Similar to `react-dropzone`, just spread props on your elements
18
34
  3. **Full styling control** - No imposed CSS, design system, or theming
19
- 4. **Smaller bundle** - No UI components means less bloat
20
- 5. **Ship SDK integration** - Purpose-built for Ship deployments, not a generic file upload library
35
+ 4. **Ship SDK integration** - Purpose-built for Ship deployments, not a generic file upload library
21
36
 
22
37
  **What's different from other libraries:**
23
38
  - Generic dropzone libraries don't preserve folder structure properly
@@ -34,6 +49,7 @@ This package provides **zero UI components** - just a React hook with built-in d
34
49
  - 🔒 **Path Sanitization** - Defense-in-depth protection against directory traversal attacks
35
50
  - 📁 **Folder Structure Preservation** - Proper folder paths via `webkitRelativePath`
36
51
  - 🎨 **Headless UI** - No visual components, just logic and state management
52
+ - 📘 **Full TypeScript Support** - Complete type definitions with discriminated unions for state machine
37
53
  - 🚀 **Focused Scope** - File processing and UI state only. MD5 calculation and deployment handled by Ship SDK
38
54
 
39
55
  ## Installation
@@ -44,6 +60,17 @@ npm install @shipstatic/drop
44
60
  pnpm add @shipstatic/drop
45
61
  ```
46
62
 
63
+ ## Requirements
64
+
65
+ - **React**: ^18.0.0 or ^19.0.0
66
+ - **TypeScript**: Full TypeScript support with exported types
67
+ - **Browsers**: Modern browsers with support for:
68
+ - File API (universal support)
69
+ - DataTransfer API for drag & drop (universal support)
70
+ - `webkitGetAsEntry` for folder uploads (Chrome, Edge, Safari 11.1+, Firefox 50+)
71
+
72
+ **Note on folder uploads**: The folder drag & drop feature uses the `webkitGetAsEntry` API. While widely supported, older browsers may only support file-by-file selection. ZIP extraction works universally as a fallback.
73
+
47
74
  ## Quick Start
48
75
 
49
76
  ```tsx
@@ -231,7 +258,7 @@ interface DropReturn {
231
258
  type: 'file';
232
259
  style: { display: string };
233
260
  multiple: boolean;
234
- webkitdirectory: string;
261
+ webkitdirectory: string; // Note: React expects string ('') for boolean attributes
235
262
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
236
263
  };
237
264
 
package/dist/index.cjs CHANGED
@@ -37,9 +37,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
37
 
38
38
  // node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js
39
39
  var require_jszip_min = __commonJS({
40
- "node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js"(exports, module) {
40
+ "node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js"(exports$1, module) {
41
41
  !(function(e) {
42
- if ("object" == typeof exports && "undefined" != typeof module) module.exports = e();
42
+ if ("object" == typeof exports$1 && "undefined" != typeof module) module.exports = e();
43
43
  else if ("function" == typeof define && define.amd) define([], e);
44
44
  else {
45
45
  ("undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : this).JSZip = e();
@@ -2349,7 +2349,7 @@ var require_jszip_min = __commonJS({
2349
2349
 
2350
2350
  // node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/db.json
2351
2351
  var require_db = __commonJS({
2352
- "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/db.json"(exports, module) {
2352
+ "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/db.json"(exports$1, module) {
2353
2353
  module.exports = {
2354
2354
  "application/1d-interleaved-parityfec": {
2355
2355
  source: "iana"
@@ -11697,7 +11697,7 @@ var require_db = __commonJS({
11697
11697
 
11698
11698
  // node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/index.js
11699
11699
  var require_mime_db = __commonJS({
11700
- "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/index.js"(exports, module) {
11700
+ "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/index.js"(exports$1, module) {
11701
11701
  module.exports = require_db();
11702
11702
  }
11703
11703
  });
@@ -11835,35 +11835,39 @@ function stripCommonPrefix(files) {
11835
11835
  }));
11836
11836
  }
11837
11837
  async function traverseFileTree(entry, files, currentPath = "") {
11838
- if (entry.isFile) {
11839
- const file = await new Promise((resolve, reject) => {
11840
- entry.file(resolve, reject);
11841
- });
11842
- const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
11843
- Object.defineProperty(file, "webkitRelativePath", {
11844
- value: relativePath,
11845
- writable: false
11846
- });
11847
- files.push(file);
11848
- } else if (entry.isDirectory) {
11849
- const dirReader = entry.createReader();
11850
- let allEntries = [];
11851
- const readEntriesBatch = async () => {
11852
- const batch = await new Promise(
11853
- (resolve, reject) => {
11854
- dirReader.readEntries(resolve, reject);
11838
+ try {
11839
+ if (entry.isFile) {
11840
+ const file = await new Promise((resolve, reject) => {
11841
+ entry.file(resolve, reject);
11842
+ });
11843
+ const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
11844
+ Object.defineProperty(file, "webkitRelativePath", {
11845
+ value: relativePath,
11846
+ writable: false
11847
+ });
11848
+ files.push(file);
11849
+ } else if (entry.isDirectory) {
11850
+ const dirReader = entry.createReader();
11851
+ let allEntries = [];
11852
+ const readEntriesBatch = async () => {
11853
+ const batch = await new Promise(
11854
+ (resolve, reject) => {
11855
+ dirReader.readEntries(resolve, reject);
11856
+ }
11857
+ );
11858
+ if (batch.length > 0) {
11859
+ allEntries = allEntries.concat(batch);
11860
+ await readEntriesBatch();
11855
11861
  }
11856
- );
11857
- if (batch.length > 0) {
11858
- allEntries = allEntries.concat(batch);
11859
- await readEntriesBatch();
11862
+ };
11863
+ await readEntriesBatch();
11864
+ for (const childEntry of allEntries) {
11865
+ const entryPath = childEntry.isDirectory ? currentPath ? `${currentPath}/${childEntry.name}` : childEntry.name : currentPath;
11866
+ await traverseFileTree(childEntry, files, entryPath);
11860
11867
  }
11861
- };
11862
- await readEntriesBatch();
11863
- for (const childEntry of allEntries) {
11864
- const entryPath = childEntry.isDirectory ? currentPath ? `${currentPath}/${childEntry.name}` : childEntry.name : currentPath;
11865
- await traverseFileTree(childEntry, files, entryPath);
11866
11868
  }
11869
+ } catch (error) {
11870
+ console.warn(`Error traversing file tree for entry ${entry.name}:`, error);
11867
11871
  }
11868
11872
  }
11869
11873
  function useDrop(options) {
@@ -12029,14 +12033,27 @@ function useDrop(options) {
12029
12033
  let hasEntries = false;
12030
12034
  for (const item of items) {
12031
12035
  if (item.kind === "file") {
12032
- const entry = item.webkitGetAsEntry?.();
12033
- if (entry) {
12034
- hasEntries = true;
12035
- await traverseFileTree(
12036
- entry,
12037
- files,
12038
- entry.isDirectory ? entry.name : ""
12039
- );
12036
+ try {
12037
+ const entry = item.webkitGetAsEntry?.();
12038
+ if (entry) {
12039
+ hasEntries = true;
12040
+ await traverseFileTree(
12041
+ entry,
12042
+ files,
12043
+ entry.isDirectory ? entry.name : ""
12044
+ );
12045
+ } else {
12046
+ const file = item.getAsFile();
12047
+ if (file) {
12048
+ files.push(file);
12049
+ }
12050
+ }
12051
+ } catch (error) {
12052
+ console.warn("Error processing drop item:", error);
12053
+ const file = item.getAsFile();
12054
+ if (file) {
12055
+ files.push(file);
12056
+ }
12040
12057
  }
12041
12058
  }
12042
12059
  }