@shipstatic/drop 0.1.6 → 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/dist/index.d.cts CHANGED
@@ -52,6 +52,26 @@ interface ProcessedFile extends StaticFile {
52
52
  /** Upload progress (0-100) - only set during upload */
53
53
  progress?: number;
54
54
  }
55
+ /**
56
+ * State machine values for the drop hook
57
+ */
58
+ type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
59
+ /**
60
+ * Status information with title and details
61
+ */
62
+ interface DropStatus {
63
+ title: string;
64
+ details: string;
65
+ }
66
+ /**
67
+ * State machine state for the drop hook
68
+ */
69
+ interface DropState {
70
+ value: DropStateValue;
71
+ files: ProcessedFile[];
72
+ sourceName: string;
73
+ status: DropStatus | null;
74
+ }
55
75
 
56
76
  interface DropOptions {
57
77
  /** Ship SDK instance (required for validation) */
@@ -64,18 +84,12 @@ interface DropOptions {
64
84
  stripPrefix?: boolean;
65
85
  }
66
86
  interface DropReturn {
67
- /** All processed files with their status */
68
- files: ProcessedFile[];
69
- /** Name of the source (file/folder/ZIP) that was dropped/selected */
70
- sourceName: string;
71
- /** Current status text */
72
- statusText: string;
87
+ /** Current state of the drop hook */
88
+ state: DropState;
73
89
  /** Whether currently processing files (ZIP extraction, etc.) */
74
90
  isProcessing: boolean;
75
91
  /** Whether user is currently dragging over the dropzone */
76
92
  isDragging: boolean;
77
- /** Last validation error if any */
78
- validationError: ClientError | null;
79
93
  /** Get props to spread on dropzone element (handles drag & drop) */
80
94
  getDropzoneProps: () => {
81
95
  onDragOver: (e: React.DragEvent) => void;
@@ -189,4 +203,4 @@ declare function normalizePath(path: string): string;
189
203
  */
190
204
  declare function isZipFile(file: File): boolean;
191
205
 
192
- export { type ClientError, type DropOptions, type DropReturn, FILE_STATUSES, type FileStatus, type ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getValidFiles, isZipFile, normalizePath, stripCommonPrefix, useDrop };
206
+ export { type ClientError, type DropOptions, type DropReturn, type DropState, type DropStateValue, type DropStatus, FILE_STATUSES, type FileStatus, type ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getValidFiles, isZipFile, normalizePath, stripCommonPrefix, useDrop };
package/dist/index.d.ts CHANGED
@@ -52,6 +52,26 @@ interface ProcessedFile extends StaticFile {
52
52
  /** Upload progress (0-100) - only set during upload */
53
53
  progress?: number;
54
54
  }
55
+ /**
56
+ * State machine values for the drop hook
57
+ */
58
+ type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
59
+ /**
60
+ * Status information with title and details
61
+ */
62
+ interface DropStatus {
63
+ title: string;
64
+ details: string;
65
+ }
66
+ /**
67
+ * State machine state for the drop hook
68
+ */
69
+ interface DropState {
70
+ value: DropStateValue;
71
+ files: ProcessedFile[];
72
+ sourceName: string;
73
+ status: DropStatus | null;
74
+ }
55
75
 
56
76
  interface DropOptions {
57
77
  /** Ship SDK instance (required for validation) */
@@ -64,18 +84,12 @@ interface DropOptions {
64
84
  stripPrefix?: boolean;
65
85
  }
66
86
  interface DropReturn {
67
- /** All processed files with their status */
68
- files: ProcessedFile[];
69
- /** Name of the source (file/folder/ZIP) that was dropped/selected */
70
- sourceName: string;
71
- /** Current status text */
72
- statusText: string;
87
+ /** Current state of the drop hook */
88
+ state: DropState;
73
89
  /** Whether currently processing files (ZIP extraction, etc.) */
74
90
  isProcessing: boolean;
75
91
  /** Whether user is currently dragging over the dropzone */
76
92
  isDragging: boolean;
77
- /** Last validation error if any */
78
- validationError: ClientError | null;
79
93
  /** Get props to spread on dropzone element (handles drag & drop) */
80
94
  getDropzoneProps: () => {
81
95
  onDragOver: (e: React.DragEvent) => void;
@@ -189,4 +203,4 @@ declare function normalizePath(path: string): string;
189
203
  */
190
204
  declare function isZipFile(file: File): boolean;
191
205
 
192
- export { type ClientError, type DropOptions, type DropReturn, FILE_STATUSES, type FileStatus, type ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getValidFiles, isZipFile, normalizePath, stripCommonPrefix, useDrop };
206
+ export { type ClientError, type DropOptions, type DropReturn, type DropState, type DropStateValue, type DropStatus, FILE_STATUSES, type FileStatus, type ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getValidFiles, isZipFile, normalizePath, stripCommonPrefix, useDrop };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useCallback } from 'react';
1
+ import { useState, useRef, useMemo, useCallback } from 'react';
2
2
  import { formatFileSize as formatFileSize$1, getValidFiles as getValidFiles$1, filterJunk, validateFiles } from '@shipstatic/ship';
3
3
 
4
4
  var __create = Object.create;
@@ -35,9 +35,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
35
35
 
36
36
  // node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js
37
37
  var require_jszip_min = __commonJS({
38
- "node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js"(exports, module) {
38
+ "node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js"(exports$1, module) {
39
39
  !(function(e) {
40
- if ("object" == typeof exports && "undefined" != typeof module) module.exports = e();
40
+ if ("object" == typeof exports$1 && "undefined" != typeof module) module.exports = e();
41
41
  else if ("function" == typeof define && define.amd) define([], e);
42
42
  else {
43
43
  ("undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : this).JSZip = e();
@@ -2347,7 +2347,7 @@ var require_jszip_min = __commonJS({
2347
2347
 
2348
2348
  // node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/db.json
2349
2349
  var require_db = __commonJS({
2350
- "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/db.json"(exports, module) {
2350
+ "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/db.json"(exports$1, module) {
2351
2351
  module.exports = {
2352
2352
  "application/1d-interleaved-parityfec": {
2353
2353
  source: "iana"
@@ -11695,7 +11695,7 @@ var require_db = __commonJS({
11695
11695
 
11696
11696
  // node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/index.js
11697
11697
  var require_mime_db = __commonJS({
11698
- "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/index.js"(exports, module) {
11698
+ "node_modules/.pnpm/mime-db@1.54.0/node_modules/mime-db/index.js"(exports$1, module) {
11699
11699
  module.exports = require_db();
11700
11700
  }
11701
11701
  });
@@ -11833,35 +11833,39 @@ function stripCommonPrefix(files) {
11833
11833
  }));
11834
11834
  }
11835
11835
  async function traverseFileTree(entry, files, currentPath = "") {
11836
- if (entry.isFile) {
11837
- const file = await new Promise((resolve, reject) => {
11838
- entry.file(resolve, reject);
11839
- });
11840
- const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
11841
- Object.defineProperty(file, "webkitRelativePath", {
11842
- value: relativePath,
11843
- writable: false
11844
- });
11845
- files.push(file);
11846
- } else if (entry.isDirectory) {
11847
- const dirReader = entry.createReader();
11848
- let allEntries = [];
11849
- const readEntriesBatch = async () => {
11850
- const batch = await new Promise(
11851
- (resolve, reject) => {
11852
- dirReader.readEntries(resolve, reject);
11836
+ try {
11837
+ if (entry.isFile) {
11838
+ const file = await new Promise((resolve, reject) => {
11839
+ entry.file(resolve, reject);
11840
+ });
11841
+ const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
11842
+ Object.defineProperty(file, "webkitRelativePath", {
11843
+ value: relativePath,
11844
+ writable: false
11845
+ });
11846
+ files.push(file);
11847
+ } else if (entry.isDirectory) {
11848
+ const dirReader = entry.createReader();
11849
+ let allEntries = [];
11850
+ const readEntriesBatch = async () => {
11851
+ const batch = await new Promise(
11852
+ (resolve, reject) => {
11853
+ dirReader.readEntries(resolve, reject);
11854
+ }
11855
+ );
11856
+ if (batch.length > 0) {
11857
+ allEntries = allEntries.concat(batch);
11858
+ await readEntriesBatch();
11853
11859
  }
11854
- );
11855
- if (batch.length > 0) {
11856
- allEntries = allEntries.concat(batch);
11857
- await readEntriesBatch();
11860
+ };
11861
+ await readEntriesBatch();
11862
+ for (const childEntry of allEntries) {
11863
+ const entryPath = childEntry.isDirectory ? currentPath ? `${currentPath}/${childEntry.name}` : childEntry.name : currentPath;
11864
+ await traverseFileTree(childEntry, files, entryPath);
11858
11865
  }
11859
- };
11860
- await readEntriesBatch();
11861
- for (const childEntry of allEntries) {
11862
- const entryPath = childEntry.isDirectory ? currentPath ? `${currentPath}/${childEntry.name}` : childEntry.name : currentPath;
11863
- await traverseFileTree(childEntry, files, entryPath);
11864
11866
  }
11867
+ } catch (error) {
11868
+ console.warn(`Error traversing file tree for entry ${entry.name}:`, error);
11865
11869
  }
11866
11870
  }
11867
11871
  function useDrop(options) {
@@ -11871,28 +11875,32 @@ function useDrop(options) {
11871
11875
  onFilesReady,
11872
11876
  stripPrefix = true
11873
11877
  } = options;
11874
- const [files, setFiles] = useState([]);
11875
- const [sourceName, setSourceName] = useState("");
11876
- const [statusText, setStatusText] = useState("");
11877
- const [isProcessing, setIsProcessing] = useState(false);
11878
- const [isDragging, setIsDragging] = useState(false);
11879
- const [validationError, setValidationError] = useState(null);
11878
+ const initialState = {
11879
+ value: "idle",
11880
+ files: [],
11881
+ sourceName: "",
11882
+ status: null
11883
+ };
11884
+ const [state, setState] = useState(initialState);
11880
11885
  const isProcessingRef = useRef(false);
11881
11886
  const inputRef = useRef(null);
11887
+ const isProcessing = useMemo(() => state.value === "processing", [state.value]);
11888
+ const isDragging = useMemo(() => state.value === "dragging", [state.value]);
11882
11889
  const processFiles = useCallback(async (newFiles) => {
11883
11890
  if (isProcessingRef.current) {
11884
11891
  console.warn("File processing already in progress. Ignoring duplicate call.");
11885
11892
  return;
11886
11893
  }
11887
11894
  if (!newFiles || newFiles.length === 0) {
11888
- setStatusText("No files selected.");
11889
11895
  return;
11890
11896
  }
11891
11897
  isProcessingRef.current = true;
11892
- setIsProcessing(true);
11893
- setFiles([]);
11894
- setValidationError(null);
11895
- setStatusText("Processing files...");
11898
+ setState({
11899
+ value: "processing",
11900
+ files: [],
11901
+ sourceName: "",
11902
+ status: { title: "Processing...", details: "Validating and preparing files." }
11903
+ });
11896
11904
  try {
11897
11905
  let detectedSourceName = "";
11898
11906
  if (newFiles.length === 1 && isZipFile(newFiles[0])) {
@@ -11905,12 +11913,14 @@ function useDrop(options) {
11905
11913
  detectedSourceName = newFiles[0].name;
11906
11914
  }
11907
11915
  }
11908
- setSourceName(detectedSourceName);
11909
11916
  const allFiles = [];
11910
11917
  const shouldExtractZip = newFiles.length === 1 && isZipFile(newFiles[0]);
11911
11918
  if (shouldExtractZip) {
11912
11919
  const zipFile = newFiles[0];
11913
- setStatusText(`Extracting ${zipFile.name}...`);
11920
+ setState((prev) => ({
11921
+ ...prev,
11922
+ status: { title: "Extracting...", details: `Extracting ${zipFile.name}...` }
11923
+ }));
11914
11924
  const { files: extractedFiles, errors } = await extractZipToFiles(zipFile);
11915
11925
  if (errors.length > 0) {
11916
11926
  console.warn("ZIP extraction errors:", errors);
@@ -11926,29 +11936,44 @@ function useDrop(options) {
11926
11936
  const filePaths = allFiles.map(getFilePath);
11927
11937
  const validPaths = new Set(filterJunk(filePaths));
11928
11938
  const cleanFiles = allFiles.filter((f) => validPaths.has(getFilePath(f)));
11929
- setStatusText("Processing files...");
11939
+ setState((prev) => ({
11940
+ ...prev,
11941
+ status: { title: "Processing...", details: "Processing files..." }
11942
+ }));
11930
11943
  const processedFiles = await Promise.all(
11931
11944
  cleanFiles.map((file) => createProcessedFile(file))
11932
11945
  );
11933
11946
  const finalFiles = stripPrefix ? stripCommonPrefix(processedFiles) : processedFiles;
11934
11947
  const config = await ship.getConfig();
11935
11948
  const validation = validateFiles(finalFiles, config);
11936
- setFiles(validation.files);
11937
- setValidationError(validation.error);
11938
11949
  if (validation.error) {
11939
- setStatusText(validation.error.details);
11950
+ setState({
11951
+ value: "error",
11952
+ files: validation.files,
11953
+ sourceName: detectedSourceName,
11954
+ status: { title: validation.error.error, details: validation.error.details }
11955
+ });
11940
11956
  onValidationError?.(validation.error);
11941
11957
  } else if (validation.validFiles.length > 0) {
11942
- setStatusText(`${validation.validFiles.length} file(s) ready.`);
11958
+ setState({
11959
+ value: "ready",
11960
+ files: validation.files,
11961
+ sourceName: detectedSourceName,
11962
+ status: { title: "Ready", details: `${validation.validFiles.length} file(s) are ready.` }
11963
+ });
11943
11964
  onFilesReady?.(validation.validFiles);
11944
11965
  } else {
11945
11966
  const noValidError = {
11946
11967
  error: "No Valid Files",
11947
- details: "No files are valid for upload after processing.",
11968
+ details: "None of the provided files could be processed.",
11948
11969
  isClientError: true
11949
11970
  };
11950
- setStatusText(noValidError.details);
11951
- setValidationError(noValidError);
11971
+ setState({
11972
+ value: "error",
11973
+ files: validation.files,
11974
+ sourceName: detectedSourceName,
11975
+ status: { title: noValidError.error, details: noValidError.details }
11976
+ });
11952
11977
  onValidationError?.(noValidError);
11953
11978
  }
11954
11979
  } catch (error) {
@@ -11957,69 +11982,92 @@ function useDrop(options) {
11957
11982
  details: `Failed to process files: ${error instanceof Error ? error.message : String(error)}`,
11958
11983
  isClientError: true
11959
11984
  };
11960
- setStatusText(processingError.details);
11961
- setValidationError(processingError);
11985
+ setState((prev) => ({
11986
+ ...prev,
11987
+ value: "error",
11988
+ status: { title: processingError.error, details: processingError.details }
11989
+ }));
11962
11990
  onValidationError?.(processingError);
11963
11991
  } finally {
11964
11992
  isProcessingRef.current = false;
11965
- setIsProcessing(false);
11966
11993
  }
11967
11994
  }, [ship, onValidationError, onFilesReady, stripPrefix]);
11968
11995
  const clearAll = useCallback(() => {
11969
- setFiles([]);
11970
- setSourceName("");
11971
- setStatusText("");
11972
- setValidationError(null);
11973
- setIsDragging(false);
11996
+ setState(initialState);
11974
11997
  isProcessingRef.current = false;
11975
- setIsProcessing(false);
11976
11998
  }, []);
11977
11999
  const getValidFilesCallback = useCallback(() => {
11978
- return getValidFiles(files);
11979
- }, [files]);
11980
- const updateFileStatus = useCallback((fileId, state) => {
11981
- setFiles((prev) => prev.map(
11982
- (file) => file.id === fileId ? { ...file, ...state } : file
11983
- ));
12000
+ return getValidFiles(state.files);
12001
+ }, [state.files]);
12002
+ const updateFileStatus = useCallback((fileId, fileState) => {
12003
+ setState((prev) => ({
12004
+ ...prev,
12005
+ files: prev.files.map(
12006
+ (file) => file.id === fileId ? { ...file, ...fileState } : file
12007
+ )
12008
+ }));
11984
12009
  }, []);
11985
12010
  const handleDragOver = useCallback((e) => {
11986
12011
  e.preventDefault();
11987
- setIsDragging(true);
12012
+ setState((prev) => {
12013
+ if (prev.value === "idle" || prev.value === "ready" || prev.value === "error") {
12014
+ return { ...prev, value: "dragging" };
12015
+ }
12016
+ return prev;
12017
+ });
11988
12018
  }, []);
11989
12019
  const handleDragLeave = useCallback((e) => {
11990
12020
  e.preventDefault();
11991
- setIsDragging(false);
12021
+ setState((prev) => {
12022
+ if (prev.value !== "dragging") return prev;
12023
+ const nextValue = prev.files.length > 0 ? prev.status?.title === "Ready" ? "ready" : "error" : "idle";
12024
+ return { ...prev, value: nextValue };
12025
+ });
11992
12026
  }, []);
11993
12027
  const handleDrop = useCallback(async (e) => {
11994
12028
  e.preventDefault();
11995
- setIsDragging(false);
11996
12029
  const items = Array.from(e.dataTransfer.items);
11997
- const files2 = [];
12030
+ const files = [];
11998
12031
  let hasEntries = false;
11999
12032
  for (const item of items) {
12000
12033
  if (item.kind === "file") {
12001
- const entry = item.webkitGetAsEntry?.();
12002
- if (entry) {
12003
- hasEntries = true;
12004
- await traverseFileTree(
12005
- entry,
12006
- files2,
12007
- entry.isDirectory ? entry.name : ""
12008
- );
12034
+ try {
12035
+ const entry = item.webkitGetAsEntry?.();
12036
+ if (entry) {
12037
+ hasEntries = true;
12038
+ await traverseFileTree(
12039
+ entry,
12040
+ files,
12041
+ entry.isDirectory ? entry.name : ""
12042
+ );
12043
+ } else {
12044
+ const file = item.getAsFile();
12045
+ if (file) {
12046
+ files.push(file);
12047
+ }
12048
+ }
12049
+ } catch (error) {
12050
+ console.warn("Error processing drop item:", error);
12051
+ const file = item.getAsFile();
12052
+ if (file) {
12053
+ files.push(file);
12054
+ }
12009
12055
  }
12010
12056
  }
12011
12057
  }
12012
12058
  if (!hasEntries && e.dataTransfer.files.length > 0) {
12013
- files2.push(...Array.from(e.dataTransfer.files));
12059
+ files.push(...Array.from(e.dataTransfer.files));
12014
12060
  }
12015
- if (files2.length > 0) {
12016
- await processFiles(files2);
12061
+ if (files.length > 0) {
12062
+ await processFiles(files);
12063
+ } else if (state.value === "dragging") {
12064
+ setState((prev) => ({ ...prev, value: "idle" }));
12017
12065
  }
12018
- }, [processFiles]);
12066
+ }, [processFiles, state.value]);
12019
12067
  const handleInputChange = useCallback((e) => {
12020
- const files2 = Array.from(e.target.files || []);
12021
- if (files2.length > 0) {
12022
- processFiles(files2);
12068
+ const files = Array.from(e.target.files || []);
12069
+ if (files.length > 0) {
12070
+ processFiles(files);
12023
12071
  }
12024
12072
  }, [processFiles]);
12025
12073
  const open = useCallback(() => {
@@ -12040,13 +12088,11 @@ function useDrop(options) {
12040
12088
  onChange: handleInputChange
12041
12089
  }), [handleInputChange]);
12042
12090
  return {
12043
- // State
12044
- files,
12045
- sourceName,
12046
- statusText,
12091
+ // State machine
12092
+ state,
12093
+ // Convenience getters (computed from state)
12047
12094
  isProcessing,
12048
12095
  isDragging,
12049
- validationError,
12050
12096
  // Primary API: Prop getters
12051
12097
  getDropzoneProps,
12052
12098
  getInputProps,