@shipstatic/drop 0.1.8 → 0.1.10

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
@@ -85,9 +85,8 @@ function MyUploader() {
85
85
  });
86
86
 
87
87
  const handleUpload = async () => {
88
- const validFiles = drop.getValidFiles();
89
88
  // ProcessedFile extends StaticFile - no conversion needed!
90
- await ship.deployments.create(validFiles.map(f => f.file));
89
+ await ship.deployments.create(drop.validFiles.map(f => f.file));
91
90
  };
92
91
 
93
92
  return (
@@ -107,15 +106,15 @@ function MyUploader() {
107
106
  {drop.isDragging ? '📂 Drop here' : '📁 Click or drag files/folders'}
108
107
  </div>
109
108
 
110
- {/* Status - using state machine */}
111
- {drop.state.status && (
109
+ {/* Status - using state machine phase */}
110
+ {drop.status && (
112
111
  <p>
113
- <strong>{drop.state.status.title}:</strong> {drop.state.status.details}
112
+ <strong>{drop.status.title}:</strong> {drop.status.details}
114
113
  </p>
115
114
  )}
116
115
 
117
116
  {/* File list */}
118
- {drop.state.files.map(file => (
117
+ {drop.files.map(file => (
119
118
  <div key={file.id}>
120
119
  {file.name} - {file.status}
121
120
  </div>
@@ -124,9 +123,9 @@ function MyUploader() {
124
123
  {/* Upload button */}
125
124
  <button
126
125
  onClick={handleUpload}
127
- disabled={drop.state.value !== 'ready'}
126
+ disabled={drop.phase !== 'ready'}
128
127
  >
129
- Upload {drop.getValidFiles().length} files
128
+ Upload {drop.validFiles.length} files
130
129
  </button>
131
130
  </div>
132
131
  );
@@ -232,17 +231,22 @@ interface DropOptions {
232
231
 
233
232
  **Returns:**
234
233
 
234
+ ```typescript
235
235
  ```typescript
236
236
  interface DropReturn {
237
- // State machine
238
- /** Current state of the drop hook */
239
- state: DropState;
240
-
241
237
  // Convenience getters (computed from state)
238
+ /** Current phase of the state machine */
239
+ phase: DropStateValue;
242
240
  /** Whether currently processing files (ZIP extraction, etc.) */
243
241
  isProcessing: boolean;
244
242
  /** Whether user is currently dragging over the dropzone */
245
243
  isDragging: boolean;
244
+ /** Flattened access to files */
245
+ files: ProcessedFile[];
246
+ /** Flattened access to source name */
247
+ sourceName: string;
248
+ /** Flattened access to status */
249
+ status: DropStatus | null;
246
250
 
247
251
  // Primary API: Prop getters for easy integration
248
252
  /** Get props to spread on dropzone element (handles drag & drop) */
@@ -272,7 +276,7 @@ interface DropReturn {
272
276
 
273
277
  // Helpers
274
278
  /** Get only valid files ready for upload */
275
- getValidFiles: () => ProcessedFile[];
279
+ validFiles: ProcessedFile[];
276
280
  /** Update upload state for a specific file (status, progress, message) */
277
281
  updateFileStatus: (fileId: string, state: {
278
282
  status: FileStatus;
@@ -289,22 +293,16 @@ type DropStateValue =
289
293
  | 'ready' // Files are valid and ready for deployment
290
294
  | 'error'; // An error occurred during processing
291
295
 
292
- interface DropState {
293
- value: DropStateValue;
294
- files: ProcessedFile[];
295
- sourceName: string;
296
- status: DropStatus | null;
297
- }
298
-
299
296
  interface DropStatus {
300
297
  title: string;
301
298
  details: string;
299
+ errors?: string[];
302
300
  }
303
301
  ```
304
302
 
305
303
  ## State Machine
306
304
 
307
- The drop hook uses a state machine for predictable, clear state management. Instead of multiple boolean flags, you have a single `state.value` that represents exactly what's happening.
305
+ The drop hook uses a state machine for predictable, clear state management. Instead of multiple boolean flags, you have a single `drop.phase` that represents exactly what's happening.
308
306
 
309
307
  ### State Flow
310
308
 
@@ -320,9 +318,8 @@ error → dragging → processing → ... (retry)
320
318
 
321
319
  ```tsx
322
320
  function StatusIndicator({ drop }) {
323
- const { state } = drop;
324
-
325
- switch (state.value) {
321
+ // Use drop.phase to switch-case on the state
322
+ switch (drop.phase) {
326
323
  case 'idle':
327
324
  return <p>Drop files here or click to select</p>;
328
325
 
@@ -330,12 +327,12 @@ function StatusIndicator({ drop }) {
330
327
  return <p>Drop your files now!</p>;
331
328
 
332
329
  case 'processing':
333
- return <p>{state.status?.details || 'Processing...'}</p>;
330
+ return <p>{drop.status?.details || 'Processing...'}</p>;
334
331
 
335
332
  case 'ready':
336
333
  return (
337
334
  <div>
338
- <p>✓ {state.files.length} files ready</p>
335
+ <p>✓ {drop.files.length} files ready</p>
339
336
  <button>Upload to Ship</button>
340
337
  </div>
341
338
  );
@@ -343,8 +340,8 @@ function StatusIndicator({ drop }) {
343
340
  case 'error':
344
341
  return (
345
342
  <div>
346
- <p>✗ {state.status?.title}</p>
347
- <p>{state.status?.details}</p>
343
+ <p>✗ {drop.status?.title}</p>
344
+ <p>{drop.status?.details}</p>
348
345
  <button onClick={drop.clearAll}>Try Again</button>
349
346
  </div>
350
347
  );
@@ -357,14 +354,14 @@ function StatusIndicator({ drop }) {
357
354
  For simpler use cases, boolean convenience getters are provided:
358
355
 
359
356
  ```tsx
360
- // These are computed from state.value (read-only projections)
361
- drop.isProcessing // true when state.value === 'processing'
362
- drop.isDragging // true when state.value === 'dragging'
363
-
364
- // For error information, use the state object
365
- drop.state.value === 'error' // Check if in error state
366
- drop.state.status?.title // Error title
367
- drop.state.status?.details // Error details
357
+ // These are computed from drop.phase (read-only projections)
358
+ drop.isProcessing // true when phase === 'processing'
359
+ drop.isDragging // true when phase === 'dragging'
360
+
361
+ // For error information, use the flattened status object
362
+ drop.phase === 'error' // Check if in error state
363
+ drop.status?.title // Error title
364
+ drop.status?.details // Error details
368
365
  ```
369
366
 
370
367
  ### Benefits
@@ -384,7 +381,8 @@ Each file in the `state.files` array contains its own `status` and `statusMessag
384
381
  function FileList({ drop }) {
385
382
  return (
386
383
  <div>
387
- {drop.state.files.map(file => (
384
+ {/* Flattened access to files array */}
385
+ {drop.files.map(file => (
388
386
  <div key={file.id}>
389
387
  <span>{file.path}</span>
390
388
 
@@ -401,7 +399,7 @@ function FileList({ drop }) {
401
399
  ))}
402
400
 
403
401
  {/* If validation fails, allow user to clear all and try again */}
404
- {drop.state.value === 'error' && (
402
+ {drop.phase === 'error' && (
405
403
  <button onClick={drop.clearAll}>
406
404
  Clear All & Try Again
407
405
  </button>
@@ -422,10 +420,10 @@ function FileList({ drop }) {
422
420
  When files fail validation or processing, check the error state:
423
421
 
424
422
  ```tsx
425
- {drop.state.value === 'error' && drop.state.status && (
423
+ {drop.phase === 'error' && drop.status && (
426
424
  <div>
427
- <p>{drop.state.status.title}</p>
428
- <p>{drop.state.status.details}</p>
425
+ <p>{drop.status.title}</p>
426
+ <p>{drop.status.details}</p>
429
427
  </div>
430
428
  )}
431
429
  ```
@@ -455,10 +453,10 @@ Use `clearAll()` to reset and try again:
455
453
 
456
454
  ```tsx
457
455
  // If validation fails, show user which files failed
458
- {drop.state.value === 'error' && (
456
+ {drop.phase === 'error' && (
459
457
  <div>
460
458
  <p>Validation failed. Please fix the issues and try again:</p>
461
- {drop.state.files.map(file => (
459
+ {drop.files.map(file => (
462
460
  <div key={file.id}>
463
461
  {file.path}: {file.statusMessage}
464
462
  </div>
@@ -629,7 +627,7 @@ All errors are surfaced at the per-file level:
629
627
  - Processing errors (e.g., ZIP extraction failures) are marked with `status: 'processing_error'`
630
628
  - Validation failures are marked with `status: 'validation_failed'`
631
629
  - The `statusMessage` always contains specific error details
632
- - Failed files are excluded from `getValidFiles()` and cannot be deployed
630
+ - Failed files are excluded from `validFiles` and cannot be deployed
633
631
  - No silent failures - all errors are visible to users
634
632
 
635
633
  See the [Error Handling](#error-handling) section for examples of displaying per-file errors in your UI.
package/dist/index.cjs CHANGED
@@ -11813,7 +11813,6 @@ async function createProcessedFile(file, options) {
11813
11813
  status: FILE_STATUSES.PENDING
11814
11814
  };
11815
11815
  }
11816
- var getValidFiles = ship.getValidFiles;
11817
11816
  function stripCommonPrefix(files) {
11818
11817
  if (files.length === 0) return files;
11819
11818
  const paths = files.map((f) => f.path);
@@ -11829,10 +11828,20 @@ function stripCommonPrefix(files) {
11829
11828
  }
11830
11829
  if (commonDepth === 0) return files;
11831
11830
  const prefix = segments.slice(0, commonDepth).join("/") + "/";
11832
- return files.map((f) => ({
11833
- ...f,
11834
- path: f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path
11835
- }));
11831
+ return files.map((f) => {
11832
+ const newPath = f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path;
11833
+ if (f.file) {
11834
+ Object.defineProperty(f.file, "webkitRelativePath", {
11835
+ value: newPath,
11836
+ writable: false,
11837
+ configurable: true
11838
+ });
11839
+ }
11840
+ return {
11841
+ ...f,
11842
+ path: newPath
11843
+ };
11844
+ });
11836
11845
  }
11837
11846
  async function traverseFileTree(entry, files, currentPath = "") {
11838
11847
  try {
@@ -11843,7 +11852,8 @@ async function traverseFileTree(entry, files, currentPath = "") {
11843
11852
  const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
11844
11853
  Object.defineProperty(file, "webkitRelativePath", {
11845
11854
  value: relativePath,
11846
- writable: false
11855
+ writable: false,
11856
+ configurable: true
11847
11857
  });
11848
11858
  files.push(file);
11849
11859
  } else if (entry.isDirectory) {
@@ -11888,6 +11898,7 @@ function useDrop(options) {
11888
11898
  const inputRef = react.useRef(null);
11889
11899
  const isProcessing = react.useMemo(() => state.value === "processing", [state.value]);
11890
11900
  const isDragging = react.useMemo(() => state.value === "dragging", [state.value]);
11901
+ const validFiles = react.useMemo(() => ship.getValidFiles(state.files), [state.files]);
11891
11902
  const processFiles = react.useCallback(async (newFiles) => {
11892
11903
  if (isProcessingRef.current) {
11893
11904
  console.warn("File processing already in progress. Ignoring duplicate call.");
@@ -11953,7 +11964,11 @@ function useDrop(options) {
11953
11964
  value: "error",
11954
11965
  files: validation.files,
11955
11966
  sourceName: detectedSourceName,
11956
- status: { title: validation.error.error, details: validation.error.details }
11967
+ status: {
11968
+ title: validation.error.error,
11969
+ details: validation.error.details,
11970
+ errors: validation.error.errors
11971
+ }
11957
11972
  });
11958
11973
  onValidationError?.(validation.error);
11959
11974
  } else if (validation.validFiles.length > 0) {
@@ -11998,9 +12013,6 @@ function useDrop(options) {
11998
12013
  setState(initialState);
11999
12014
  isProcessingRef.current = false;
12000
12015
  }, []);
12001
- const getValidFilesCallback = react.useCallback(() => {
12002
- return getValidFiles(state.files);
12003
- }, [state.files]);
12004
12016
  const updateFileStatus = react.useCallback((fileId, fileState) => {
12005
12017
  setState((prev) => ({
12006
12018
  ...prev,
@@ -12030,21 +12042,21 @@ function useDrop(options) {
12030
12042
  e.preventDefault();
12031
12043
  const items = Array.from(e.dataTransfer.items);
12032
12044
  const files = [];
12033
- let hasEntries = false;
12045
+ const entriesToTraverse = [];
12034
12046
  for (const item of items) {
12035
12047
  if (item.kind === "file") {
12036
12048
  try {
12037
12049
  const entry = item.webkitGetAsEntry?.();
12038
- if (entry) {
12039
- hasEntries = true;
12040
- await traverseFileTree(
12041
- entry,
12042
- files,
12043
- entry.isDirectory ? entry.name : ""
12044
- );
12050
+ if (entry && entry.isDirectory) {
12051
+ entriesToTraverse.push({ entry, path: entry.name });
12045
12052
  } else {
12046
12053
  const file = item.getAsFile();
12047
12054
  if (file) {
12055
+ Object.defineProperty(file, "webkitRelativePath", {
12056
+ value: file.name,
12057
+ writable: false,
12058
+ configurable: true
12059
+ });
12048
12060
  files.push(file);
12049
12061
  }
12050
12062
  }
@@ -12057,7 +12069,12 @@ function useDrop(options) {
12057
12069
  }
12058
12070
  }
12059
12071
  }
12060
- if (!hasEntries && e.dataTransfer.files.length > 0) {
12072
+ if (entriesToTraverse.length > 0) {
12073
+ await Promise.all(entriesToTraverse.map(
12074
+ (item) => traverseFileTree(item.entry, files, item.path)
12075
+ ));
12076
+ }
12077
+ if (files.length === 0 && e.dataTransfer.files.length > 0) {
12061
12078
  files.push(...Array.from(e.dataTransfer.files));
12062
12079
  }
12063
12080
  if (files.length > 0) {
@@ -12091,10 +12108,13 @@ function useDrop(options) {
12091
12108
  }), [handleInputChange]);
12092
12109
  return {
12093
12110
  // State machine
12094
- state,
12095
12111
  // Convenience getters (computed from state)
12112
+ phase: state.value,
12096
12113
  isProcessing,
12097
12114
  isDragging,
12115
+ files: state.files,
12116
+ sourceName: state.sourceName,
12117
+ status: state.status,
12098
12118
  // Primary API: Prop getters
12099
12119
  getDropzoneProps,
12100
12120
  getInputProps,
@@ -12103,7 +12123,7 @@ function useDrop(options) {
12103
12123
  processFiles,
12104
12124
  clearAll,
12105
12125
  // Helpers
12106
- getValidFiles: getValidFilesCallback,
12126
+ validFiles,
12107
12127
  updateFileStatus
12108
12128
  };
12109
12129
  }
@@ -12135,10 +12155,10 @@ exports.FILE_STATUSES = FILE_STATUSES;
12135
12155
  exports.createProcessedFile = createProcessedFile;
12136
12156
  exports.extractZipToFiles = extractZipToFiles;
12137
12157
  exports.formatFileSize = formatFileSize;
12138
- exports.getValidFiles = getValidFiles;
12139
12158
  exports.isZipFile = isZipFile;
12140
12159
  exports.normalizePath = normalizePath;
12141
12160
  exports.stripCommonPrefix = stripCommonPrefix;
12161
+ exports.traverseFileTree = traverseFileTree;
12142
12162
  exports.useDrop = useDrop;
12143
12163
  //# sourceMappingURL=index.cjs.map
12144
12164
  //# sourceMappingURL=index.cjs.map