@shipstatic/drop 0.1.5 → 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/README.md +227 -180
- package/dist/index.cjs +167 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -13
- package/dist/index.d.ts +59 -13
- package/dist/index.js +168 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,17 +84,33 @@ interface DropOptions {
|
|
|
64
84
|
stripPrefix?: boolean;
|
|
65
85
|
}
|
|
66
86
|
interface DropReturn {
|
|
67
|
-
/**
|
|
68
|
-
|
|
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
|
-
/**
|
|
76
|
-
|
|
77
|
-
/**
|
|
91
|
+
/** Whether user is currently dragging over the dropzone */
|
|
92
|
+
isDragging: boolean;
|
|
93
|
+
/** Get props to spread on dropzone element (handles drag & drop) */
|
|
94
|
+
getDropzoneProps: () => {
|
|
95
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
96
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
97
|
+
onDrop: (e: React.DragEvent) => void;
|
|
98
|
+
onClick: () => void;
|
|
99
|
+
};
|
|
100
|
+
/** Get props to spread on hidden file input element */
|
|
101
|
+
getInputProps: () => {
|
|
102
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
103
|
+
type: 'file';
|
|
104
|
+
style: {
|
|
105
|
+
display: string;
|
|
106
|
+
};
|
|
107
|
+
multiple: boolean;
|
|
108
|
+
webkitdirectory: string;
|
|
109
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
110
|
+
};
|
|
111
|
+
/** Programmatically trigger file picker */
|
|
112
|
+
open: () => void;
|
|
113
|
+
/** Manually process files (for advanced usage) */
|
|
78
114
|
processFiles: (files: File[]) => Promise<void>;
|
|
79
115
|
/** Clear all files and reset state */
|
|
80
116
|
clearAll: () => void;
|
|
@@ -88,9 +124,19 @@ interface DropReturn {
|
|
|
88
124
|
}) => void;
|
|
89
125
|
}
|
|
90
126
|
/**
|
|
91
|
-
* Headless drop hook
|
|
92
|
-
*
|
|
93
|
-
*
|
|
127
|
+
* Headless drop hook for file upload workflows
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* const drop = useDrop({ ship });
|
|
132
|
+
*
|
|
133
|
+
* return (
|
|
134
|
+
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
135
|
+
* <input {...drop.getInputProps()} />
|
|
136
|
+
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
137
|
+
* </div>
|
|
138
|
+
* );
|
|
139
|
+
* ```
|
|
94
140
|
*/
|
|
95
141
|
declare function useDrop(options: DropOptions): DropReturn;
|
|
96
142
|
|
|
@@ -157,4 +203,4 @@ declare function normalizePath(path: string): string;
|
|
|
157
203
|
*/
|
|
158
204
|
declare function isZipFile(file: File): boolean;
|
|
159
205
|
|
|
160
|
-
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,17 +84,33 @@ interface DropOptions {
|
|
|
64
84
|
stripPrefix?: boolean;
|
|
65
85
|
}
|
|
66
86
|
interface DropReturn {
|
|
67
|
-
/**
|
|
68
|
-
|
|
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
|
-
/**
|
|
76
|
-
|
|
77
|
-
/**
|
|
91
|
+
/** Whether user is currently dragging over the dropzone */
|
|
92
|
+
isDragging: boolean;
|
|
93
|
+
/** Get props to spread on dropzone element (handles drag & drop) */
|
|
94
|
+
getDropzoneProps: () => {
|
|
95
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
96
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
97
|
+
onDrop: (e: React.DragEvent) => void;
|
|
98
|
+
onClick: () => void;
|
|
99
|
+
};
|
|
100
|
+
/** Get props to spread on hidden file input element */
|
|
101
|
+
getInputProps: () => {
|
|
102
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
103
|
+
type: 'file';
|
|
104
|
+
style: {
|
|
105
|
+
display: string;
|
|
106
|
+
};
|
|
107
|
+
multiple: boolean;
|
|
108
|
+
webkitdirectory: string;
|
|
109
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
110
|
+
};
|
|
111
|
+
/** Programmatically trigger file picker */
|
|
112
|
+
open: () => void;
|
|
113
|
+
/** Manually process files (for advanced usage) */
|
|
78
114
|
processFiles: (files: File[]) => Promise<void>;
|
|
79
115
|
/** Clear all files and reset state */
|
|
80
116
|
clearAll: () => void;
|
|
@@ -88,9 +124,19 @@ interface DropReturn {
|
|
|
88
124
|
}) => void;
|
|
89
125
|
}
|
|
90
126
|
/**
|
|
91
|
-
* Headless drop hook
|
|
92
|
-
*
|
|
93
|
-
*
|
|
127
|
+
* Headless drop hook for file upload workflows
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* const drop = useDrop({ ship });
|
|
132
|
+
*
|
|
133
|
+
* return (
|
|
134
|
+
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
135
|
+
* <input {...drop.getInputProps()} />
|
|
136
|
+
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
137
|
+
* </div>
|
|
138
|
+
* );
|
|
139
|
+
* ```
|
|
94
140
|
*/
|
|
95
141
|
declare function useDrop(options: DropOptions): DropReturn;
|
|
96
142
|
|
|
@@ -157,4 +203,4 @@ declare function normalizePath(path: string): string;
|
|
|
157
203
|
*/
|
|
158
204
|
declare function isZipFile(file: File): boolean;
|
|
159
205
|
|
|
160
|
-
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;
|
|
@@ -11832,6 +11832,38 @@ function stripCommonPrefix(files) {
|
|
|
11832
11832
|
path: f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path
|
|
11833
11833
|
}));
|
|
11834
11834
|
}
|
|
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);
|
|
11853
|
+
}
|
|
11854
|
+
);
|
|
11855
|
+
if (batch.length > 0) {
|
|
11856
|
+
allEntries = allEntries.concat(batch);
|
|
11857
|
+
await readEntriesBatch();
|
|
11858
|
+
}
|
|
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
|
+
}
|
|
11865
|
+
}
|
|
11866
|
+
}
|
|
11835
11867
|
function useDrop(options) {
|
|
11836
11868
|
const {
|
|
11837
11869
|
ship,
|
|
@@ -11839,26 +11871,32 @@ function useDrop(options) {
|
|
|
11839
11871
|
onFilesReady,
|
|
11840
11872
|
stripPrefix = true
|
|
11841
11873
|
} = options;
|
|
11842
|
-
const
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11874
|
+
const initialState = {
|
|
11875
|
+
value: "idle",
|
|
11876
|
+
files: [],
|
|
11877
|
+
sourceName: "",
|
|
11878
|
+
status: null
|
|
11879
|
+
};
|
|
11880
|
+
const [state, setState] = useState(initialState);
|
|
11847
11881
|
const isProcessingRef = useRef(false);
|
|
11882
|
+
const inputRef = useRef(null);
|
|
11883
|
+
const isProcessing = useMemo(() => state.value === "processing", [state.value]);
|
|
11884
|
+
const isDragging = useMemo(() => state.value === "dragging", [state.value]);
|
|
11848
11885
|
const processFiles = useCallback(async (newFiles) => {
|
|
11849
11886
|
if (isProcessingRef.current) {
|
|
11850
11887
|
console.warn("File processing already in progress. Ignoring duplicate call.");
|
|
11851
11888
|
return;
|
|
11852
11889
|
}
|
|
11853
11890
|
if (!newFiles || newFiles.length === 0) {
|
|
11854
|
-
setStatusText("No files selected.");
|
|
11855
11891
|
return;
|
|
11856
11892
|
}
|
|
11857
11893
|
isProcessingRef.current = true;
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11894
|
+
setState({
|
|
11895
|
+
value: "processing",
|
|
11896
|
+
files: [],
|
|
11897
|
+
sourceName: "",
|
|
11898
|
+
status: { title: "Processing...", details: "Validating and preparing files." }
|
|
11899
|
+
});
|
|
11862
11900
|
try {
|
|
11863
11901
|
let detectedSourceName = "";
|
|
11864
11902
|
if (newFiles.length === 1 && isZipFile(newFiles[0])) {
|
|
@@ -11871,12 +11909,14 @@ function useDrop(options) {
|
|
|
11871
11909
|
detectedSourceName = newFiles[0].name;
|
|
11872
11910
|
}
|
|
11873
11911
|
}
|
|
11874
|
-
setSourceName(detectedSourceName);
|
|
11875
11912
|
const allFiles = [];
|
|
11876
11913
|
const shouldExtractZip = newFiles.length === 1 && isZipFile(newFiles[0]);
|
|
11877
11914
|
if (shouldExtractZip) {
|
|
11878
11915
|
const zipFile = newFiles[0];
|
|
11879
|
-
|
|
11916
|
+
setState((prev) => ({
|
|
11917
|
+
...prev,
|
|
11918
|
+
status: { title: "Extracting...", details: `Extracting ${zipFile.name}...` }
|
|
11919
|
+
}));
|
|
11880
11920
|
const { files: extractedFiles, errors } = await extractZipToFiles(zipFile);
|
|
11881
11921
|
if (errors.length > 0) {
|
|
11882
11922
|
console.warn("ZIP extraction errors:", errors);
|
|
@@ -11892,29 +11932,44 @@ function useDrop(options) {
|
|
|
11892
11932
|
const filePaths = allFiles.map(getFilePath);
|
|
11893
11933
|
const validPaths = new Set(filterJunk(filePaths));
|
|
11894
11934
|
const cleanFiles = allFiles.filter((f) => validPaths.has(getFilePath(f)));
|
|
11895
|
-
|
|
11935
|
+
setState((prev) => ({
|
|
11936
|
+
...prev,
|
|
11937
|
+
status: { title: "Processing...", details: "Processing files..." }
|
|
11938
|
+
}));
|
|
11896
11939
|
const processedFiles = await Promise.all(
|
|
11897
11940
|
cleanFiles.map((file) => createProcessedFile(file))
|
|
11898
11941
|
);
|
|
11899
11942
|
const finalFiles = stripPrefix ? stripCommonPrefix(processedFiles) : processedFiles;
|
|
11900
11943
|
const config = await ship.getConfig();
|
|
11901
11944
|
const validation = validateFiles(finalFiles, config);
|
|
11902
|
-
setFiles(validation.files);
|
|
11903
|
-
setValidationError(validation.error);
|
|
11904
11945
|
if (validation.error) {
|
|
11905
|
-
|
|
11946
|
+
setState({
|
|
11947
|
+
value: "error",
|
|
11948
|
+
files: validation.files,
|
|
11949
|
+
sourceName: detectedSourceName,
|
|
11950
|
+
status: { title: validation.error.error, details: validation.error.details }
|
|
11951
|
+
});
|
|
11906
11952
|
onValidationError?.(validation.error);
|
|
11907
11953
|
} else if (validation.validFiles.length > 0) {
|
|
11908
|
-
|
|
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
|
+
});
|
|
11909
11960
|
onFilesReady?.(validation.validFiles);
|
|
11910
11961
|
} else {
|
|
11911
11962
|
const noValidError = {
|
|
11912
11963
|
error: "No Valid Files",
|
|
11913
|
-
details: "
|
|
11964
|
+
details: "None of the provided files could be processed.",
|
|
11914
11965
|
isClientError: true
|
|
11915
11966
|
};
|
|
11916
|
-
|
|
11917
|
-
|
|
11967
|
+
setState({
|
|
11968
|
+
value: "error",
|
|
11969
|
+
files: validation.files,
|
|
11970
|
+
sourceName: detectedSourceName,
|
|
11971
|
+
status: { title: noValidError.error, details: noValidError.details }
|
|
11972
|
+
});
|
|
11918
11973
|
onValidationError?.(noValidError);
|
|
11919
11974
|
}
|
|
11920
11975
|
} catch (error) {
|
|
@@ -11923,38 +11978,112 @@ function useDrop(options) {
|
|
|
11923
11978
|
details: `Failed to process files: ${error instanceof Error ? error.message : String(error)}`,
|
|
11924
11979
|
isClientError: true
|
|
11925
11980
|
};
|
|
11926
|
-
|
|
11927
|
-
|
|
11981
|
+
setState((prev) => ({
|
|
11982
|
+
...prev,
|
|
11983
|
+
value: "error",
|
|
11984
|
+
status: { title: processingError.error, details: processingError.details }
|
|
11985
|
+
}));
|
|
11928
11986
|
onValidationError?.(processingError);
|
|
11929
11987
|
} finally {
|
|
11930
11988
|
isProcessingRef.current = false;
|
|
11931
|
-
setIsProcessing(false);
|
|
11932
11989
|
}
|
|
11933
11990
|
}, [ship, onValidationError, onFilesReady, stripPrefix]);
|
|
11934
11991
|
const clearAll = useCallback(() => {
|
|
11935
|
-
|
|
11936
|
-
setSourceName("");
|
|
11937
|
-
setStatusText("");
|
|
11938
|
-
setValidationError(null);
|
|
11992
|
+
setState(initialState);
|
|
11939
11993
|
isProcessingRef.current = false;
|
|
11940
|
-
setIsProcessing(false);
|
|
11941
11994
|
}, []);
|
|
11942
11995
|
const getValidFilesCallback = useCallback(() => {
|
|
11943
|
-
return getValidFiles(files);
|
|
11944
|
-
}, [files]);
|
|
11945
|
-
const updateFileStatus = useCallback((fileId,
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
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
|
+
}));
|
|
12005
|
+
}, []);
|
|
12006
|
+
const handleDragOver = useCallback((e) => {
|
|
12007
|
+
e.preventDefault();
|
|
12008
|
+
setState((prev) => {
|
|
12009
|
+
if (prev.value === "idle" || prev.value === "ready" || prev.value === "error") {
|
|
12010
|
+
return { ...prev, value: "dragging" };
|
|
12011
|
+
}
|
|
12012
|
+
return prev;
|
|
12013
|
+
});
|
|
12014
|
+
}, []);
|
|
12015
|
+
const handleDragLeave = useCallback((e) => {
|
|
12016
|
+
e.preventDefault();
|
|
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
|
+
});
|
|
12022
|
+
}, []);
|
|
12023
|
+
const handleDrop = useCallback(async (e) => {
|
|
12024
|
+
e.preventDefault();
|
|
12025
|
+
const items = Array.from(e.dataTransfer.items);
|
|
12026
|
+
const files = [];
|
|
12027
|
+
let hasEntries = false;
|
|
12028
|
+
for (const item of items) {
|
|
12029
|
+
if (item.kind === "file") {
|
|
12030
|
+
const entry = item.webkitGetAsEntry?.();
|
|
12031
|
+
if (entry) {
|
|
12032
|
+
hasEntries = true;
|
|
12033
|
+
await traverseFileTree(
|
|
12034
|
+
entry,
|
|
12035
|
+
files,
|
|
12036
|
+
entry.isDirectory ? entry.name : ""
|
|
12037
|
+
);
|
|
12038
|
+
}
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
if (!hasEntries && e.dataTransfer.files.length > 0) {
|
|
12042
|
+
files.push(...Array.from(e.dataTransfer.files));
|
|
12043
|
+
}
|
|
12044
|
+
if (files.length > 0) {
|
|
12045
|
+
await processFiles(files);
|
|
12046
|
+
} else if (state.value === "dragging") {
|
|
12047
|
+
setState((prev) => ({ ...prev, value: "idle" }));
|
|
12048
|
+
}
|
|
12049
|
+
}, [processFiles, state.value]);
|
|
12050
|
+
const handleInputChange = useCallback((e) => {
|
|
12051
|
+
const files = Array.from(e.target.files || []);
|
|
12052
|
+
if (files.length > 0) {
|
|
12053
|
+
processFiles(files);
|
|
12054
|
+
}
|
|
12055
|
+
}, [processFiles]);
|
|
12056
|
+
const open = useCallback(() => {
|
|
12057
|
+
inputRef.current?.click();
|
|
11949
12058
|
}, []);
|
|
12059
|
+
const getDropzoneProps = useCallback(() => ({
|
|
12060
|
+
onDragOver: handleDragOver,
|
|
12061
|
+
onDragLeave: handleDragLeave,
|
|
12062
|
+
onDrop: handleDrop,
|
|
12063
|
+
onClick: open
|
|
12064
|
+
}), [handleDragOver, handleDragLeave, handleDrop, open]);
|
|
12065
|
+
const getInputProps = useCallback(() => ({
|
|
12066
|
+
ref: inputRef,
|
|
12067
|
+
type: "file",
|
|
12068
|
+
style: { display: "none" },
|
|
12069
|
+
multiple: true,
|
|
12070
|
+
webkitdirectory: "",
|
|
12071
|
+
onChange: handleInputChange
|
|
12072
|
+
}), [handleInputChange]);
|
|
11950
12073
|
return {
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
12074
|
+
// State machine
|
|
12075
|
+
state,
|
|
12076
|
+
// Convenience getters (computed from state)
|
|
11954
12077
|
isProcessing,
|
|
11955
|
-
|
|
12078
|
+
isDragging,
|
|
12079
|
+
// Primary API: Prop getters
|
|
12080
|
+
getDropzoneProps,
|
|
12081
|
+
getInputProps,
|
|
12082
|
+
// Actions
|
|
12083
|
+
open,
|
|
11956
12084
|
processFiles,
|
|
11957
12085
|
clearAll,
|
|
12086
|
+
// Helpers
|
|
11958
12087
|
getValidFiles: getValidFilesCallback,
|
|
11959
12088
|
updateFileStatus
|
|
11960
12089
|
};
|