@isik-kaplan/core 0.1.0
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/LICENSE +21 -0
- package/README.md +22 -0
- package/dist/hooks/index.cjs +341 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +50 -0
- package/dist/hooks/index.d.ts +50 -0
- package/dist/hooks/index.js +312 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.cjs +466 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +404 -0
- package/dist/index.js.map +1 -0
- package/dist/next/cookies/index.d.ts +5 -0
- package/dist/next/cookies/index.js +21 -0
- package/dist/next/cookies/index.js.map +1 -0
- package/dist/next/middleware/index.cjs +66 -0
- package/dist/next/middleware/index.cjs.map +1 -0
- package/dist/next/middleware/index.d.cts +7 -0
- package/dist/next/middleware/index.d.ts +7 -0
- package/dist/next/middleware/index.js +37 -0
- package/dist/next/middleware/index.js.map +1 -0
- package/dist/node/index.cjs +173 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +57 -0
- package/dist/node/index.d.ts +57 -0
- package/dist/node/index.js +125 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +123 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// src/hooks/index.ts
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
function useElementAttributes(attributeKeys) {
|
|
4
|
+
const ref = useRef(null);
|
|
5
|
+
const [attributeValues, setAttributeValue] = useState({});
|
|
6
|
+
const updateAttribute = useCallback(() => {
|
|
7
|
+
if (ref.current) {
|
|
8
|
+
const values = {};
|
|
9
|
+
for (const key of attributeKeys) {
|
|
10
|
+
Object.defineProperty(values, key, {
|
|
11
|
+
value: ref.current[key],
|
|
12
|
+
writable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
setAttributeValue((prevValues) => {
|
|
18
|
+
const hasChanged = attributeKeys.some((key) => prevValues[key] !== values[key]);
|
|
19
|
+
return hasChanged ? values : prevValues;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}, [attributeKeys]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
updateAttribute();
|
|
25
|
+
window.addEventListener("resize", updateAttribute);
|
|
26
|
+
if (ref.current) {
|
|
27
|
+
const observer = new MutationObserver(updateAttribute);
|
|
28
|
+
observer.observe(ref.current, { attributes: true });
|
|
29
|
+
return () => {
|
|
30
|
+
window.removeEventListener("resize", updateAttribute);
|
|
31
|
+
observer.disconnect();
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return () => {
|
|
35
|
+
window.removeEventListener("resize", updateAttribute);
|
|
36
|
+
};
|
|
37
|
+
}, [updateAttribute]);
|
|
38
|
+
return { ref, attributeValues };
|
|
39
|
+
}
|
|
40
|
+
function useFormState(initialState) {
|
|
41
|
+
const [formState, setFormState] = useState(initialState);
|
|
42
|
+
const [formErrors, setFormErrors] = useState();
|
|
43
|
+
function resetFormState() {
|
|
44
|
+
setFormState(initialState);
|
|
45
|
+
}
|
|
46
|
+
function handleFormState({
|
|
47
|
+
key,
|
|
48
|
+
inputType
|
|
49
|
+
}) {
|
|
50
|
+
return (event) => {
|
|
51
|
+
let value;
|
|
52
|
+
if (inputType === "event") {
|
|
53
|
+
const target = event.target;
|
|
54
|
+
if (target.type === "checkbox") {
|
|
55
|
+
value = target.checked;
|
|
56
|
+
} else if (target.type === "number" || target.type === "range") {
|
|
57
|
+
value = target.valueAsNumber;
|
|
58
|
+
} else {
|
|
59
|
+
value = target.value;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
value = event;
|
|
63
|
+
}
|
|
64
|
+
setFormState((prev) => ({ ...prev, [key]: value }));
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const handleFormStateValue = (key) => handleFormState({ key, inputType: "value" });
|
|
68
|
+
const handleFormStateEvent = (key) => handleFormState({ key, inputType: "event" });
|
|
69
|
+
const handleFormStateOnClick = (key, value) => () => {
|
|
70
|
+
setFormState((prev) => ({ ...prev, [key]: value }));
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
formState,
|
|
74
|
+
setFormState,
|
|
75
|
+
formErrors,
|
|
76
|
+
setFormErrors,
|
|
77
|
+
handleFormStateValue,
|
|
78
|
+
handleFormStateEvent,
|
|
79
|
+
handleFormStateOnClick,
|
|
80
|
+
resetFormState
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function useEffectAfterMount(effect, deps) {
|
|
84
|
+
const isFirstRender = useRef(true);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (isFirstRender.current) {
|
|
87
|
+
isFirstRender.current = false;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
return effect();
|
|
91
|
+
}, deps);
|
|
92
|
+
}
|
|
93
|
+
function useFilePaste({
|
|
94
|
+
acceptedTypes,
|
|
95
|
+
maxSize,
|
|
96
|
+
targetElement,
|
|
97
|
+
enabled = true
|
|
98
|
+
} = {}) {
|
|
99
|
+
const [files, setFiles] = useState([]);
|
|
100
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
101
|
+
const [error, setError] = useState(null);
|
|
102
|
+
const clearFiles = useCallback(() => {
|
|
103
|
+
setFiles([]);
|
|
104
|
+
setError(null);
|
|
105
|
+
}, []);
|
|
106
|
+
const validateFiles = useCallback(
|
|
107
|
+
(pastedFiles) => {
|
|
108
|
+
if (acceptedTypes && acceptedTypes.length > 0) {
|
|
109
|
+
const wildcardAcceptedTypes = acceptedTypes.filter((type) => type.endsWith("/*")).map((type) => type.slice(0, -1));
|
|
110
|
+
const normalizedAcceptedTypes = acceptedTypes.map((type) => type.endsWith("/*") ? type.slice(0, -2) : type);
|
|
111
|
+
const invalidFiles = pastedFiles.filter((file) => {
|
|
112
|
+
const fileType = file.type;
|
|
113
|
+
return !normalizedAcceptedTypes.includes(fileType) && !wildcardAcceptedTypes.some((wildcardType) => fileType.startsWith(wildcardType));
|
|
114
|
+
});
|
|
115
|
+
if (invalidFiles.length > 0) {
|
|
116
|
+
const invalidFileNames = invalidFiles.map((file) => file.name).join(", ");
|
|
117
|
+
return {
|
|
118
|
+
valid: false,
|
|
119
|
+
error: `Invalid file types: ${invalidFileNames}. Accepted types: ${acceptedTypes.join(", ")}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (maxSize) {
|
|
124
|
+
const oversizedFile = pastedFiles.find((file) => file.size > maxSize);
|
|
125
|
+
if (oversizedFile) {
|
|
126
|
+
const fileSizeMB = (oversizedFile.size / (1024 * 1024)).toFixed(2);
|
|
127
|
+
const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2);
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: `File "${oversizedFile.name}" (${fileSizeMB} MB) exceeds the maximum size of ${maxSizeMB} MB`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { valid: true };
|
|
135
|
+
},
|
|
136
|
+
[acceptedTypes, maxSize]
|
|
137
|
+
);
|
|
138
|
+
const handlePaste = useCallback(
|
|
139
|
+
(event) => {
|
|
140
|
+
const { clipboardData } = event;
|
|
141
|
+
if (!clipboardData) return;
|
|
142
|
+
const hasFiles = clipboardData.files && clipboardData.files.length > 0;
|
|
143
|
+
if (!hasFiles) return;
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
setIsLoading(true);
|
|
146
|
+
setError(null);
|
|
147
|
+
try {
|
|
148
|
+
const pastedFiles = Array.from(clipboardData.files);
|
|
149
|
+
const validation = validateFiles(pastedFiles);
|
|
150
|
+
if (!validation.valid) {
|
|
151
|
+
setError(validation.error);
|
|
152
|
+
setFiles([]);
|
|
153
|
+
} else {
|
|
154
|
+
setFiles(pastedFiles);
|
|
155
|
+
}
|
|
156
|
+
} catch (err) {
|
|
157
|
+
setError(err instanceof Error ? err.message : "Failed to process pasted files");
|
|
158
|
+
setFiles([]);
|
|
159
|
+
} finally {
|
|
160
|
+
setIsLoading(false);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
[validateFiles]
|
|
164
|
+
);
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!enabled) return;
|
|
167
|
+
const target = targetElement || document;
|
|
168
|
+
target.addEventListener("paste", handlePaste);
|
|
169
|
+
return () => {
|
|
170
|
+
target.removeEventListener("paste", handlePaste);
|
|
171
|
+
};
|
|
172
|
+
}, [enabled, handlePaste, targetElement]);
|
|
173
|
+
return { files, isLoading, error, clearFiles };
|
|
174
|
+
}
|
|
175
|
+
function useFileDragDrop(options = {}) {
|
|
176
|
+
const { onDrop, onDragOver, onDragLeave, acceptedFileTypes, maxFileSize, multiple = true } = options;
|
|
177
|
+
const [state, setState] = useState({
|
|
178
|
+
isDragging: false,
|
|
179
|
+
files: null
|
|
180
|
+
});
|
|
181
|
+
const ref = useRef(null);
|
|
182
|
+
const dragCounter = useRef(0);
|
|
183
|
+
const handleDragOver = useCallback(
|
|
184
|
+
(e) => {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
e.stopPropagation();
|
|
187
|
+
onDragOver?.(e);
|
|
188
|
+
},
|
|
189
|
+
[onDragOver]
|
|
190
|
+
);
|
|
191
|
+
const handleDragEnter = useCallback(
|
|
192
|
+
(e) => {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
e.stopPropagation();
|
|
195
|
+
dragCounter.current += 1;
|
|
196
|
+
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
|
|
197
|
+
const hasValidItems = Array.from(e.dataTransfer.items).some((item) => {
|
|
198
|
+
if (item.kind !== "file") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (acceptedFileTypes && acceptedFileTypes.length > 0) {
|
|
202
|
+
return acceptedFileTypes.some((type) => {
|
|
203
|
+
if (type.endsWith("/*")) {
|
|
204
|
+
const category = type.split("/")[0];
|
|
205
|
+
return item.type.startsWith(`${category}/`);
|
|
206
|
+
}
|
|
207
|
+
return item.type === type;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
});
|
|
212
|
+
if (hasValidItems) {
|
|
213
|
+
setState((prevState) => ({
|
|
214
|
+
...prevState,
|
|
215
|
+
isDragging: true
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[acceptedFileTypes]
|
|
221
|
+
);
|
|
222
|
+
const handleDragLeave = useCallback(
|
|
223
|
+
(e) => {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
e.stopPropagation();
|
|
226
|
+
dragCounter.current -= 1;
|
|
227
|
+
if (dragCounter.current === 0) {
|
|
228
|
+
setState((prevState) => ({
|
|
229
|
+
...prevState,
|
|
230
|
+
isDragging: false
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
onDragLeave?.(e);
|
|
234
|
+
},
|
|
235
|
+
[onDragLeave]
|
|
236
|
+
);
|
|
237
|
+
const handleDrop = useCallback(
|
|
238
|
+
(e) => {
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
e.stopPropagation();
|
|
241
|
+
dragCounter.current = 0;
|
|
242
|
+
setState((prevState) => ({
|
|
243
|
+
...prevState,
|
|
244
|
+
isDragging: false
|
|
245
|
+
}));
|
|
246
|
+
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
247
|
+
let validFiles = Array.from(e.dataTransfer.files);
|
|
248
|
+
if (acceptedFileTypes && acceptedFileTypes.length > 0) {
|
|
249
|
+
validFiles = validFiles.filter(
|
|
250
|
+
(file) => acceptedFileTypes.some((type) => {
|
|
251
|
+
if (type.endsWith("/*")) {
|
|
252
|
+
const category = type.split("/")[0];
|
|
253
|
+
return file.type.startsWith(`${category}/`);
|
|
254
|
+
}
|
|
255
|
+
return file.type === type;
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (maxFileSize) {
|
|
260
|
+
validFiles = validFiles.filter((file) => file.size <= maxFileSize);
|
|
261
|
+
}
|
|
262
|
+
if (!multiple && validFiles.length > 0) {
|
|
263
|
+
validFiles = [validFiles[0]];
|
|
264
|
+
}
|
|
265
|
+
if (validFiles.length > 0) {
|
|
266
|
+
setState((prevState) => ({
|
|
267
|
+
...prevState,
|
|
268
|
+
files: validFiles
|
|
269
|
+
}));
|
|
270
|
+
onDrop?.(validFiles);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
[onDrop, acceptedFileTypes, maxFileSize, multiple]
|
|
275
|
+
);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
const currentRef = ref.current;
|
|
278
|
+
if (currentRef) {
|
|
279
|
+
currentRef.addEventListener("dragover", handleDragOver);
|
|
280
|
+
currentRef.addEventListener("dragenter", handleDragEnter);
|
|
281
|
+
currentRef.addEventListener("dragleave", handleDragLeave);
|
|
282
|
+
currentRef.addEventListener("drop", handleDrop);
|
|
283
|
+
return () => {
|
|
284
|
+
currentRef.removeEventListener("dragover", handleDragOver);
|
|
285
|
+
currentRef.removeEventListener("dragenter", handleDragEnter);
|
|
286
|
+
currentRef.removeEventListener("dragleave", handleDragLeave);
|
|
287
|
+
currentRef.removeEventListener("drop", handleDrop);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return void 0;
|
|
291
|
+
}, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop]);
|
|
292
|
+
const reset = useCallback(() => {
|
|
293
|
+
setState({
|
|
294
|
+
isDragging: false,
|
|
295
|
+
files: null
|
|
296
|
+
});
|
|
297
|
+
}, []);
|
|
298
|
+
return {
|
|
299
|
+
ref,
|
|
300
|
+
isDragging: state.isDragging,
|
|
301
|
+
files: state.files,
|
|
302
|
+
reset
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export {
|
|
306
|
+
useEffectAfterMount,
|
|
307
|
+
useElementAttributes,
|
|
308
|
+
useFileDragDrop,
|
|
309
|
+
useFilePaste,
|
|
310
|
+
useFormState
|
|
311
|
+
};
|
|
312
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["import type { ChangeEvent, DependencyList, DragEvent, EffectCallback } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport function useElementAttributes<T extends HTMLElement, K extends keyof T>(attributeKeys: K[]) {\n const ref = useRef<T | null>(null)\n const [attributeValues, setAttributeValue] = useState<Partial<Pick<T, K>>>({})\n\n const updateAttribute = useCallback(() => {\n if (ref.current) {\n const values = {} as Partial<Pick<T, K>>\n for (const key of attributeKeys) {\n // Object.defineProperty (unlike a plain `values[key] = value` assignment) always creates/\n // overwrites an own property, even if a consumer's T somehow has a key like \"__proto__\" -\n // matches the same defensive pattern used everywhere else in this package that writes a\n // non-literal key onto an object.\n Object.defineProperty(values, key, {\n value: ref.current[key],\n writable: true,\n configurable: true,\n enumerable: true,\n })\n }\n\n setAttributeValue((prevValues) => {\n const hasChanged = attributeKeys.some((key) => prevValues[key] !== values[key])\n return hasChanged ? values : prevValues\n })\n }\n }, [attributeKeys])\n\n useEffect(() => {\n updateAttribute()\n window.addEventListener('resize', updateAttribute)\n\n if (ref.current) {\n const observer = new MutationObserver(updateAttribute)\n observer.observe(ref.current, { attributes: true })\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n observer.disconnect()\n }\n }\n\n return () => {\n window.removeEventListener('resize', updateAttribute)\n }\n }, [updateAttribute])\n\n return { ref, attributeValues }\n}\n\ntype InputEventType = ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>\n\nexport function useFormState<T>(initialState: T) {\n const [formState, setFormState] = useState<T>(initialState)\n const [formErrors, setFormErrors] = useState<Partial<Record<keyof T, string[]>> & { non_field_errors?: string[] }>()\n\n function resetFormState() {\n setFormState(initialState)\n }\n\n function handleFormState<K extends keyof T, HandlerInputType>({\n key,\n inputType,\n }: {\n key: K\n inputType: 'event' | 'value'\n }) {\n return (event: HandlerInputType) => {\n let value: T[K]\n if (inputType === 'event') {\n const target = (event as InputEventType).target\n if (target.type === 'checkbox') {\n value = (target as HTMLInputElement).checked as T[K]\n } else if (target.type === 'number' || target.type === 'range') {\n value = (target as HTMLInputElement).valueAsNumber as T[K]\n } else {\n value = target.value as T[K]\n }\n } else {\n value = event as T[K]\n }\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n }\n\n const handleFormStateValue = <K extends keyof T>(key: K) => handleFormState<K, T[K]>({ key, inputType: 'value' })\n const handleFormStateEvent = <K extends keyof T>(key: K) =>\n handleFormState<K, InputEventType>({ key, inputType: 'event' })\n const handleFormStateOnClick =\n <K extends keyof T>(key: K, value: T[K]) =>\n () => {\n setFormState((prev) => ({ ...prev, [key]: value }))\n }\n\n return {\n formState,\n setFormState,\n formErrors,\n setFormErrors,\n handleFormStateValue,\n handleFormStateEvent,\n handleFormStateOnClick,\n resetFormState,\n }\n}\n\nexport function useEffectAfterMount(effect: EffectCallback, deps: DependencyList) {\n const isFirstRender = useRef(true)\n\n // No manual \"did deps actually change\" recheck needed here: React's own useEffect already\n // only re-invokes this callback when a dependency's value changed (shallow-compared against\n // the last time *this exact effect* ran) - which is exactly the same comparison a hand-rolled\n // check against a \"previous deps\" ref would be making, just duplicated.\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n return effect()\n }, deps)\n}\n\nexport function useFilePaste({\n acceptedTypes,\n maxSize,\n targetElement,\n enabled = true,\n}: {\n acceptedTypes?: string[]\n maxSize?: number\n targetElement?: HTMLElement | null\n enabled?: boolean\n} = {}): {\n files: File[]\n isLoading: boolean\n error: string | null\n clearFiles: () => void\n} {\n const [files, setFiles] = useState<File[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const clearFiles = useCallback(() => {\n setFiles([])\n setError(null)\n }, [])\n\n const validateFiles = useCallback(\n (pastedFiles: File[]): { valid: true } | { valid: false; error: string } => {\n if (acceptedTypes && acceptedTypes.length > 0) {\n const wildcardAcceptedTypes = acceptedTypes\n .filter((type) => type.endsWith('/*'))\n .map((type) => type.slice(0, -1))\n const normalizedAcceptedTypes = acceptedTypes.map((type) => (type.endsWith('/*') ? type.slice(0, -2) : type))\n\n const invalidFiles = pastedFiles.filter((file) => {\n const fileType = file.type\n return (\n !normalizedAcceptedTypes.includes(fileType) &&\n !wildcardAcceptedTypes.some((wildcardType) => fileType.startsWith(wildcardType))\n )\n })\n if (invalidFiles.length > 0) {\n const invalidFileNames = invalidFiles.map((file) => file.name).join(', ')\n return {\n valid: false,\n error: `Invalid file types: ${invalidFileNames}. Accepted types: ${acceptedTypes.join(', ')}`,\n }\n }\n }\n\n if (maxSize) {\n const oversizedFile = pastedFiles.find((file) => file.size > maxSize)\n if (oversizedFile) {\n const fileSizeMB = (oversizedFile.size / (1024 * 1024)).toFixed(2)\n const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2)\n return {\n valid: false,\n error: `File \"${oversizedFile.name}\" (${fileSizeMB} MB) exceeds the maximum size of ${maxSizeMB} MB`,\n }\n }\n }\n\n return { valid: true }\n },\n [acceptedTypes, maxSize]\n )\n\n const handlePaste = useCallback(\n (event: ClipboardEvent) => {\n const { clipboardData } = event\n if (!clipboardData) return\n\n const hasFiles = clipboardData.files && clipboardData.files.length > 0\n if (!hasFiles) return\n\n event.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const pastedFiles = Array.from(clipboardData.files)\n const validation = validateFiles(pastedFiles)\n\n if (!validation.valid) {\n setError(validation.error)\n setFiles([])\n } else {\n setFiles(pastedFiles)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to process pasted files')\n setFiles([])\n } finally {\n setIsLoading(false)\n }\n },\n [validateFiles]\n )\n\n useEffect(() => {\n if (!enabled) return\n\n const target = targetElement || document\n\n target.addEventListener('paste', handlePaste as EventListener)\n return () => {\n target.removeEventListener('paste', handlePaste as EventListener)\n }\n }, [enabled, handlePaste, targetElement])\n\n return { files, isLoading, error, clearFiles }\n}\n\ntype FileDragDropState = {\n isDragging: boolean\n files: File[] | null\n}\n\ntype FileDragDropOptions = {\n onDrop?: (files: File[]) => void\n onDragOver?: (e: DragEvent<HTMLElement>) => void\n onDragLeave?: (e: DragEvent<HTMLElement>) => void\n acceptedFileTypes?: string[]\n maxFileSize?: number\n multiple?: boolean\n}\n\nexport function useFileDragDrop<T extends HTMLElement = HTMLDivElement>(options: FileDragDropOptions = {}) {\n const { onDrop, onDragOver, onDragLeave, acceptedFileTypes, maxFileSize, multiple = true } = options\n\n const [state, setState] = useState<FileDragDropState>({\n isDragging: false,\n files: null,\n })\n\n const ref = useRef<T | null>(null)\n const dragCounter = useRef(0)\n\n const handleDragOver = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n onDragOver?.(e)\n },\n [onDragOver]\n )\n\n const handleDragEnter = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current += 1\n\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n const hasValidItems = Array.from(e.dataTransfer.items).some((item) => {\n if (item.kind !== 'file') {\n return false\n }\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n return acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return item.type.startsWith(`${category}/`)\n }\n return item.type === type\n })\n }\n\n return true\n })\n\n if (hasValidItems) {\n setState((prevState) => ({\n ...prevState,\n isDragging: true,\n }))\n }\n }\n },\n [acceptedFileTypes]\n )\n\n const handleDragLeave = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current -= 1\n\n if (dragCounter.current === 0) {\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n }\n\n onDragLeave?.(e)\n },\n [onDragLeave]\n )\n\n const handleDrop = useCallback(\n (e: DragEvent<T>) => {\n e.preventDefault()\n e.stopPropagation()\n\n dragCounter.current = 0\n setState((prevState) => ({\n ...prevState,\n isDragging: false,\n }))\n\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n let validFiles = Array.from(e.dataTransfer.files)\n\n if (acceptedFileTypes && acceptedFileTypes.length > 0) {\n validFiles = validFiles.filter((file) =>\n acceptedFileTypes.some((type) => {\n if (type.endsWith('/*')) {\n const category = type.split('/')[0]\n return file.type.startsWith(`${category}/`)\n }\n return file.type === type\n })\n )\n }\n\n if (maxFileSize) {\n validFiles = validFiles.filter((file) => file.size <= maxFileSize)\n }\n\n if (!multiple && validFiles.length > 0) {\n validFiles = [validFiles[0]]\n }\n\n if (validFiles.length > 0) {\n setState((prevState) => ({\n ...prevState,\n files: validFiles,\n }))\n\n onDrop?.(validFiles)\n }\n }\n },\n [onDrop, acceptedFileTypes, maxFileSize, multiple]\n )\n\n useEffect(() => {\n const currentRef = ref.current\n if (currentRef) {\n currentRef.addEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.addEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.addEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.addEventListener('drop', handleDrop as unknown as EventListener)\n\n return () => {\n currentRef.removeEventListener('dragover', handleDragOver as unknown as EventListener)\n currentRef.removeEventListener('dragenter', handleDragEnter as unknown as EventListener)\n currentRef.removeEventListener('dragleave', handleDragLeave as unknown as EventListener)\n currentRef.removeEventListener('drop', handleDrop as unknown as EventListener)\n }\n }\n return undefined\n }, [handleDragOver, handleDragEnter, handleDragLeave, handleDrop])\n\n const reset = useCallback(() => {\n setState({\n isDragging: false,\n files: null,\n })\n }, [])\n\n return {\n ref,\n isDragging: state.isDragging,\n files: state.files,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|