@shipstatic/drop 0.1.6 → 0.1.7

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;
@@ -11871,28 +11871,32 @@ function useDrop(options) {
11871
11871
  onFilesReady,
11872
11872
  stripPrefix = true
11873
11873
  } = 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);
11874
+ const initialState = {
11875
+ value: "idle",
11876
+ files: [],
11877
+ sourceName: "",
11878
+ status: null
11879
+ };
11880
+ const [state, setState] = useState(initialState);
11880
11881
  const isProcessingRef = useRef(false);
11881
11882
  const inputRef = useRef(null);
11883
+ const isProcessing = useMemo(() => state.value === "processing", [state.value]);
11884
+ const isDragging = useMemo(() => state.value === "dragging", [state.value]);
11882
11885
  const processFiles = useCallback(async (newFiles) => {
11883
11886
  if (isProcessingRef.current) {
11884
11887
  console.warn("File processing already in progress. Ignoring duplicate call.");
11885
11888
  return;
11886
11889
  }
11887
11890
  if (!newFiles || newFiles.length === 0) {
11888
- setStatusText("No files selected.");
11889
11891
  return;
11890
11892
  }
11891
11893
  isProcessingRef.current = true;
11892
- setIsProcessing(true);
11893
- setFiles([]);
11894
- setValidationError(null);
11895
- setStatusText("Processing files...");
11894
+ setState({
11895
+ value: "processing",
11896
+ files: [],
11897
+ sourceName: "",
11898
+ status: { title: "Processing...", details: "Validating and preparing files." }
11899
+ });
11896
11900
  try {
11897
11901
  let detectedSourceName = "";
11898
11902
  if (newFiles.length === 1 && isZipFile(newFiles[0])) {
@@ -11905,12 +11909,14 @@ function useDrop(options) {
11905
11909
  detectedSourceName = newFiles[0].name;
11906
11910
  }
11907
11911
  }
11908
- setSourceName(detectedSourceName);
11909
11912
  const allFiles = [];
11910
11913
  const shouldExtractZip = newFiles.length === 1 && isZipFile(newFiles[0]);
11911
11914
  if (shouldExtractZip) {
11912
11915
  const zipFile = newFiles[0];
11913
- setStatusText(`Extracting ${zipFile.name}...`);
11916
+ setState((prev) => ({
11917
+ ...prev,
11918
+ status: { title: "Extracting...", details: `Extracting ${zipFile.name}...` }
11919
+ }));
11914
11920
  const { files: extractedFiles, errors } = await extractZipToFiles(zipFile);
11915
11921
  if (errors.length > 0) {
11916
11922
  console.warn("ZIP extraction errors:", errors);
@@ -11926,29 +11932,44 @@ function useDrop(options) {
11926
11932
  const filePaths = allFiles.map(getFilePath);
11927
11933
  const validPaths = new Set(filterJunk(filePaths));
11928
11934
  const cleanFiles = allFiles.filter((f) => validPaths.has(getFilePath(f)));
11929
- setStatusText("Processing files...");
11935
+ setState((prev) => ({
11936
+ ...prev,
11937
+ status: { title: "Processing...", details: "Processing files..." }
11938
+ }));
11930
11939
  const processedFiles = await Promise.all(
11931
11940
  cleanFiles.map((file) => createProcessedFile(file))
11932
11941
  );
11933
11942
  const finalFiles = stripPrefix ? stripCommonPrefix(processedFiles) : processedFiles;
11934
11943
  const config = await ship.getConfig();
11935
11944
  const validation = validateFiles(finalFiles, config);
11936
- setFiles(validation.files);
11937
- setValidationError(validation.error);
11938
11945
  if (validation.error) {
11939
- setStatusText(validation.error.details);
11946
+ setState({
11947
+ value: "error",
11948
+ files: validation.files,
11949
+ sourceName: detectedSourceName,
11950
+ status: { title: validation.error.error, details: validation.error.details }
11951
+ });
11940
11952
  onValidationError?.(validation.error);
11941
11953
  } else if (validation.validFiles.length > 0) {
11942
- setStatusText(`${validation.validFiles.length} file(s) ready.`);
11954
+ setState({
11955
+ value: "ready",
11956
+ files: validation.files,
11957
+ sourceName: detectedSourceName,
11958
+ status: { title: "Ready", details: `${validation.validFiles.length} file(s) are ready.` }
11959
+ });
11943
11960
  onFilesReady?.(validation.validFiles);
11944
11961
  } else {
11945
11962
  const noValidError = {
11946
11963
  error: "No Valid Files",
11947
- details: "No files are valid for upload after processing.",
11964
+ details: "None of the provided files could be processed.",
11948
11965
  isClientError: true
11949
11966
  };
11950
- setStatusText(noValidError.details);
11951
- setValidationError(noValidError);
11967
+ setState({
11968
+ value: "error",
11969
+ files: validation.files,
11970
+ sourceName: detectedSourceName,
11971
+ status: { title: noValidError.error, details: noValidError.details }
11972
+ });
11952
11973
  onValidationError?.(noValidError);
11953
11974
  }
11954
11975
  } catch (error) {
@@ -11957,44 +11978,52 @@ function useDrop(options) {
11957
11978
  details: `Failed to process files: ${error instanceof Error ? error.message : String(error)}`,
11958
11979
  isClientError: true
11959
11980
  };
11960
- setStatusText(processingError.details);
11961
- setValidationError(processingError);
11981
+ setState((prev) => ({
11982
+ ...prev,
11983
+ value: "error",
11984
+ status: { title: processingError.error, details: processingError.details }
11985
+ }));
11962
11986
  onValidationError?.(processingError);
11963
11987
  } finally {
11964
11988
  isProcessingRef.current = false;
11965
- setIsProcessing(false);
11966
11989
  }
11967
11990
  }, [ship, onValidationError, onFilesReady, stripPrefix]);
11968
11991
  const clearAll = useCallback(() => {
11969
- setFiles([]);
11970
- setSourceName("");
11971
- setStatusText("");
11972
- setValidationError(null);
11973
- setIsDragging(false);
11992
+ setState(initialState);
11974
11993
  isProcessingRef.current = false;
11975
- setIsProcessing(false);
11976
11994
  }, []);
11977
11995
  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
- ));
11996
+ return getValidFiles(state.files);
11997
+ }, [state.files]);
11998
+ const updateFileStatus = useCallback((fileId, fileState) => {
11999
+ setState((prev) => ({
12000
+ ...prev,
12001
+ files: prev.files.map(
12002
+ (file) => file.id === fileId ? { ...file, ...fileState } : file
12003
+ )
12004
+ }));
11984
12005
  }, []);
11985
12006
  const handleDragOver = useCallback((e) => {
11986
12007
  e.preventDefault();
11987
- setIsDragging(true);
12008
+ setState((prev) => {
12009
+ if (prev.value === "idle" || prev.value === "ready" || prev.value === "error") {
12010
+ return { ...prev, value: "dragging" };
12011
+ }
12012
+ return prev;
12013
+ });
11988
12014
  }, []);
11989
12015
  const handleDragLeave = useCallback((e) => {
11990
12016
  e.preventDefault();
11991
- setIsDragging(false);
12017
+ setState((prev) => {
12018
+ if (prev.value !== "dragging") return prev;
12019
+ const nextValue = prev.files.length > 0 ? prev.status?.title === "Ready" ? "ready" : "error" : "idle";
12020
+ return { ...prev, value: nextValue };
12021
+ });
11992
12022
  }, []);
11993
12023
  const handleDrop = useCallback(async (e) => {
11994
12024
  e.preventDefault();
11995
- setIsDragging(false);
11996
12025
  const items = Array.from(e.dataTransfer.items);
11997
- const files2 = [];
12026
+ const files = [];
11998
12027
  let hasEntries = false;
11999
12028
  for (const item of items) {
12000
12029
  if (item.kind === "file") {
@@ -12003,23 +12032,25 @@ function useDrop(options) {
12003
12032
  hasEntries = true;
12004
12033
  await traverseFileTree(
12005
12034
  entry,
12006
- files2,
12035
+ files,
12007
12036
  entry.isDirectory ? entry.name : ""
12008
12037
  );
12009
12038
  }
12010
12039
  }
12011
12040
  }
12012
12041
  if (!hasEntries && e.dataTransfer.files.length > 0) {
12013
- files2.push(...Array.from(e.dataTransfer.files));
12042
+ files.push(...Array.from(e.dataTransfer.files));
12014
12043
  }
12015
- if (files2.length > 0) {
12016
- await processFiles(files2);
12044
+ if (files.length > 0) {
12045
+ await processFiles(files);
12046
+ } else if (state.value === "dragging") {
12047
+ setState((prev) => ({ ...prev, value: "idle" }));
12017
12048
  }
12018
- }, [processFiles]);
12049
+ }, [processFiles, state.value]);
12019
12050
  const handleInputChange = useCallback((e) => {
12020
- const files2 = Array.from(e.target.files || []);
12021
- if (files2.length > 0) {
12022
- processFiles(files2);
12051
+ const files = Array.from(e.target.files || []);
12052
+ if (files.length > 0) {
12053
+ processFiles(files);
12023
12054
  }
12024
12055
  }, [processFiles]);
12025
12056
  const open = useCallback(() => {
@@ -12040,13 +12071,11 @@ function useDrop(options) {
12040
12071
  onChange: handleInputChange
12041
12072
  }), [handleInputChange]);
12042
12073
  return {
12043
- // State
12044
- files,
12045
- sourceName,
12046
- statusText,
12074
+ // State machine
12075
+ state,
12076
+ // Convenience getters (computed from state)
12047
12077
  isProcessing,
12048
12078
  isDragging,
12049
- validationError,
12050
12079
  // Primary API: Prop getters
12051
12080
  getDropzoneProps,
12052
12081
  getInputProps,