@shipstatic/drop 0.1.5 → 0.1.6
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 +116 -161
- package/dist/index.cjs +100 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -4
- package/dist/index.d.ts +36 -4
- package/dist/index.js +100 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -72,9 +72,31 @@ interface DropReturn {
|
|
|
72
72
|
statusText: string;
|
|
73
73
|
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
74
74
|
isProcessing: boolean;
|
|
75
|
+
/** Whether user is currently dragging over the dropzone */
|
|
76
|
+
isDragging: boolean;
|
|
75
77
|
/** Last validation error if any */
|
|
76
78
|
validationError: ClientError | null;
|
|
77
|
-
/**
|
|
79
|
+
/** Get props to spread on dropzone element (handles drag & drop) */
|
|
80
|
+
getDropzoneProps: () => {
|
|
81
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
82
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
83
|
+
onDrop: (e: React.DragEvent) => void;
|
|
84
|
+
onClick: () => void;
|
|
85
|
+
};
|
|
86
|
+
/** Get props to spread on hidden file input element */
|
|
87
|
+
getInputProps: () => {
|
|
88
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
89
|
+
type: 'file';
|
|
90
|
+
style: {
|
|
91
|
+
display: string;
|
|
92
|
+
};
|
|
93
|
+
multiple: boolean;
|
|
94
|
+
webkitdirectory: string;
|
|
95
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
96
|
+
};
|
|
97
|
+
/** Programmatically trigger file picker */
|
|
98
|
+
open: () => void;
|
|
99
|
+
/** Manually process files (for advanced usage) */
|
|
78
100
|
processFiles: (files: File[]) => Promise<void>;
|
|
79
101
|
/** Clear all files and reset state */
|
|
80
102
|
clearAll: () => void;
|
|
@@ -88,9 +110,19 @@ interface DropReturn {
|
|
|
88
110
|
}) => void;
|
|
89
111
|
}
|
|
90
112
|
/**
|
|
91
|
-
* Headless drop hook
|
|
92
|
-
*
|
|
93
|
-
*
|
|
113
|
+
* Headless drop hook for file upload workflows
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```tsx
|
|
117
|
+
* const drop = useDrop({ ship });
|
|
118
|
+
*
|
|
119
|
+
* return (
|
|
120
|
+
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
121
|
+
* <input {...drop.getInputProps()} />
|
|
122
|
+
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
123
|
+
* </div>
|
|
124
|
+
* );
|
|
125
|
+
* ```
|
|
94
126
|
*/
|
|
95
127
|
declare function useDrop(options: DropOptions): DropReturn;
|
|
96
128
|
|
package/dist/index.d.ts
CHANGED
|
@@ -72,9 +72,31 @@ interface DropReturn {
|
|
|
72
72
|
statusText: string;
|
|
73
73
|
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
74
74
|
isProcessing: boolean;
|
|
75
|
+
/** Whether user is currently dragging over the dropzone */
|
|
76
|
+
isDragging: boolean;
|
|
75
77
|
/** Last validation error if any */
|
|
76
78
|
validationError: ClientError | null;
|
|
77
|
-
/**
|
|
79
|
+
/** Get props to spread on dropzone element (handles drag & drop) */
|
|
80
|
+
getDropzoneProps: () => {
|
|
81
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
82
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
83
|
+
onDrop: (e: React.DragEvent) => void;
|
|
84
|
+
onClick: () => void;
|
|
85
|
+
};
|
|
86
|
+
/** Get props to spread on hidden file input element */
|
|
87
|
+
getInputProps: () => {
|
|
88
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
89
|
+
type: 'file';
|
|
90
|
+
style: {
|
|
91
|
+
display: string;
|
|
92
|
+
};
|
|
93
|
+
multiple: boolean;
|
|
94
|
+
webkitdirectory: string;
|
|
95
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
96
|
+
};
|
|
97
|
+
/** Programmatically trigger file picker */
|
|
98
|
+
open: () => void;
|
|
99
|
+
/** Manually process files (for advanced usage) */
|
|
78
100
|
processFiles: (files: File[]) => Promise<void>;
|
|
79
101
|
/** Clear all files and reset state */
|
|
80
102
|
clearAll: () => void;
|
|
@@ -88,9 +110,19 @@ interface DropReturn {
|
|
|
88
110
|
}) => void;
|
|
89
111
|
}
|
|
90
112
|
/**
|
|
91
|
-
* Headless drop hook
|
|
92
|
-
*
|
|
93
|
-
*
|
|
113
|
+
* Headless drop hook for file upload workflows
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```tsx
|
|
117
|
+
* const drop = useDrop({ ship });
|
|
118
|
+
*
|
|
119
|
+
* return (
|
|
120
|
+
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
121
|
+
* <input {...drop.getInputProps()} />
|
|
122
|
+
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
123
|
+
* </div>
|
|
124
|
+
* );
|
|
125
|
+
* ```
|
|
94
126
|
*/
|
|
95
127
|
declare function useDrop(options: DropOptions): DropReturn;
|
|
96
128
|
|
package/dist/index.js
CHANGED
|
@@ -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,
|
|
@@ -11843,8 +11875,10 @@ function useDrop(options) {
|
|
|
11843
11875
|
const [sourceName, setSourceName] = useState("");
|
|
11844
11876
|
const [statusText, setStatusText] = useState("");
|
|
11845
11877
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
11878
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
11846
11879
|
const [validationError, setValidationError] = useState(null);
|
|
11847
11880
|
const isProcessingRef = useRef(false);
|
|
11881
|
+
const inputRef = useRef(null);
|
|
11848
11882
|
const processFiles = useCallback(async (newFiles) => {
|
|
11849
11883
|
if (isProcessingRef.current) {
|
|
11850
11884
|
console.warn("File processing already in progress. Ignoring duplicate call.");
|
|
@@ -11936,6 +11970,7 @@ function useDrop(options) {
|
|
|
11936
11970
|
setSourceName("");
|
|
11937
11971
|
setStatusText("");
|
|
11938
11972
|
setValidationError(null);
|
|
11973
|
+
setIsDragging(false);
|
|
11939
11974
|
isProcessingRef.current = false;
|
|
11940
11975
|
setIsProcessing(false);
|
|
11941
11976
|
}, []);
|
|
@@ -11947,14 +11982,79 @@ function useDrop(options) {
|
|
|
11947
11982
|
(file) => file.id === fileId ? { ...file, ...state } : file
|
|
11948
11983
|
));
|
|
11949
11984
|
}, []);
|
|
11985
|
+
const handleDragOver = useCallback((e) => {
|
|
11986
|
+
e.preventDefault();
|
|
11987
|
+
setIsDragging(true);
|
|
11988
|
+
}, []);
|
|
11989
|
+
const handleDragLeave = useCallback((e) => {
|
|
11990
|
+
e.preventDefault();
|
|
11991
|
+
setIsDragging(false);
|
|
11992
|
+
}, []);
|
|
11993
|
+
const handleDrop = useCallback(async (e) => {
|
|
11994
|
+
e.preventDefault();
|
|
11995
|
+
setIsDragging(false);
|
|
11996
|
+
const items = Array.from(e.dataTransfer.items);
|
|
11997
|
+
const files2 = [];
|
|
11998
|
+
let hasEntries = false;
|
|
11999
|
+
for (const item of items) {
|
|
12000
|
+
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
|
+
);
|
|
12009
|
+
}
|
|
12010
|
+
}
|
|
12011
|
+
}
|
|
12012
|
+
if (!hasEntries && e.dataTransfer.files.length > 0) {
|
|
12013
|
+
files2.push(...Array.from(e.dataTransfer.files));
|
|
12014
|
+
}
|
|
12015
|
+
if (files2.length > 0) {
|
|
12016
|
+
await processFiles(files2);
|
|
12017
|
+
}
|
|
12018
|
+
}, [processFiles]);
|
|
12019
|
+
const handleInputChange = useCallback((e) => {
|
|
12020
|
+
const files2 = Array.from(e.target.files || []);
|
|
12021
|
+
if (files2.length > 0) {
|
|
12022
|
+
processFiles(files2);
|
|
12023
|
+
}
|
|
12024
|
+
}, [processFiles]);
|
|
12025
|
+
const open = useCallback(() => {
|
|
12026
|
+
inputRef.current?.click();
|
|
12027
|
+
}, []);
|
|
12028
|
+
const getDropzoneProps = useCallback(() => ({
|
|
12029
|
+
onDragOver: handleDragOver,
|
|
12030
|
+
onDragLeave: handleDragLeave,
|
|
12031
|
+
onDrop: handleDrop,
|
|
12032
|
+
onClick: open
|
|
12033
|
+
}), [handleDragOver, handleDragLeave, handleDrop, open]);
|
|
12034
|
+
const getInputProps = useCallback(() => ({
|
|
12035
|
+
ref: inputRef,
|
|
12036
|
+
type: "file",
|
|
12037
|
+
style: { display: "none" },
|
|
12038
|
+
multiple: true,
|
|
12039
|
+
webkitdirectory: "",
|
|
12040
|
+
onChange: handleInputChange
|
|
12041
|
+
}), [handleInputChange]);
|
|
11950
12042
|
return {
|
|
12043
|
+
// State
|
|
11951
12044
|
files,
|
|
11952
12045
|
sourceName,
|
|
11953
12046
|
statusText,
|
|
11954
12047
|
isProcessing,
|
|
12048
|
+
isDragging,
|
|
11955
12049
|
validationError,
|
|
12050
|
+
// Primary API: Prop getters
|
|
12051
|
+
getDropzoneProps,
|
|
12052
|
+
getInputProps,
|
|
12053
|
+
// Actions
|
|
12054
|
+
open,
|
|
11956
12055
|
processFiles,
|
|
11957
12056
|
clearAll,
|
|
12057
|
+
// Helpers
|
|
11958
12058
|
getValidFiles: getValidFilesCallback,
|
|
11959
12059
|
updateFileStatus
|
|
11960
12060
|
};
|