Files
anouma/components/admin/UploadLimitGuard.tsx
T

69 lines
2.2 KiB
TypeScript

"use client";
import { useEffect } from "react";
const MAX_FILES_PER_UPLOAD = 100;
/** Keeps Payload's native multi-file picker from accepting an unbounded batch. */
export function UploadLimitGuard() {
useEffect(() => {
const attached = new WeakSet<HTMLInputElement>();
const limitedTransfer = (files: FileList) => {
const transfer = new DataTransfer();
Array.from(files)
.slice(0, MAX_FILES_PER_UPLOAD)
.forEach((file) => transfer.items.add(file));
return transfer;
};
const attachToFileInputs = () => {
document.querySelectorAll<HTMLInputElement>('input[type="file"][multiple]').forEach((input) => {
if (attached.has(input)) return;
attached.add(input);
input.addEventListener("change", () => {
if (!input.files || input.files.length <= MAX_FILES_PER_UPLOAD) return;
try {
input.files = limitedTransfer(input.files).files;
} catch {
// Older browsers can expose a read-only FileList. Payload still
// receives the original selection there and can process it.
}
});
});
};
const limitDroppedFiles = (event: DragEvent) => {
const files = event.dataTransfer?.files;
if (!files || files.length <= MAX_FILES_PER_UPLOAD || !(event.target instanceof Element)) return;
event.preventDefault();
event.stopPropagation();
try {
event.target.dispatchEvent(
new DragEvent("drop", {
bubbles: true,
cancelable: true,
dataTransfer: limitedTransfer(files),
}),
);
} catch {
// If a browser cannot construct a replacement drag event, the native
// picker still enforces the same limit for normal file selection.
}
};
attachToFileInputs();
document.addEventListener("drop", limitDroppedFiles, true);
const observer = new MutationObserver(attachToFileInputs);
observer.observe(document.body, { childList: true, subtree: true });
return () => {
document.removeEventListener("drop", limitDroppedFiles, true);
observer.disconnect();
};
}, []);
return null;
}