const initializedRoots = new WeakSet();
const fileUploadState = new WeakMap();
function getFileKey(file) {
return `${file.name}:${file.size}:${file.lastModified}`;
}
function clearFilePreviews(previewNode) {
if(!previewNode) {
return;
}
previewNode.querySelectorAll("img[data-object-url]").forEach(image => {
URL.revokeObjectURL(image.dataset.objectUrl);
});
previewNode.innerHTML = "";
previewNode.hidden = true;
}
function getStoredFiles(input) {
return fileUploadState.get(input) || [];
}
function setStoredFiles(input, files) {
fileUploadState.set(input, files);
const transfer = new DataTransfer();
files.forEach(file => transfer.items.add(file));
input.files = transfer.files;
}
function addStoredFiles(input, files) {
const storedFiles = getStoredFiles(input);
const knownKeys = new Set(storedFiles.map(getFileKey));
const nextFiles = [...storedFiles];
files.forEach(file => {
const key = getFileKey(file);
if(!knownKeys.has(key)) {
knownKeys.add(key);
nextFiles.push(file);
}
});
setStoredFiles(input, nextFiles);
return nextFiles;
}
function removeStoredFile(input, index) {
const nextFiles = getStoredFiles(input).filter((file, fileIndex) => fileIndex !== index);
setStoredFiles(input, nextFiles);
return nextFiles;
}
function getFileType(file) {
const nameParts = file.name.split(".");
const extension = nameParts.length > 1 ? nameParts.pop().trim() : "";
if(extension) {
return extension.slice(0, 6).toUpperCase();
}
if(file.type) {
return file.type.split("/").pop().slice(0, 6).toUpperCase();
}
return "FILE";
}
function formatBytes(bytes) {
if(!Number.isFinite(bytes)) {
return "";
}
if(bytes === 0) {
return "0 B";
}
const units = ["B", "KB", "MB", "GB"];
const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
const value = bytes / Math.pow(1024, index);
return `${value.toFixed(value >= 10 || index === 0 ? 0 : 1)} ${units[index]}`;
}
function updateFileUpload(input) {
const container = input.closest(".file-upload-panel, .file-upload");
const previewNode = container?.querySelector("[data-file-upload-preview]");
if(!container || !previewNode) {
return;
}
const files = getStoredFiles(input);
if(!files.length) {
clearFilePreviews(previewNode);
return;
}
updateFilePreviews(previewNode, files);
}
function updateFilePreviews(previewNode, files) {
if(!previewNode) {
return;
}
clearFilePreviews(previewNode);
files.forEach((file, index) => {
const figure = document.createElement("figure");
figure.className = "file-upload-preview-item";
figure.dataset.fileUploadIndex = String(index);
const preview = document.createElement("div");
preview.className = "file-upload-preview-visual";
if(file.type.startsWith("image/")) {
const image = document.createElement("img");
const objectUrl = URL.createObjectURL(file);
image.src = objectUrl;
image.dataset.objectUrl = objectUrl;
image.alt = "";
image.loading = "lazy";
preview.append(image);
} else {
const type = document.createElement("span");
type.className = "file-upload-preview-type";
type.textContent = getFileType(file);
preview.append(type);
}
const caption = document.createElement("figcaption");
const name = document.createElement("span");
name.className = "file-upload-preview-name";
name.textContent = file.name;
const meta = document.createElement("span");
meta.className = "file-upload-preview-meta";
meta.textContent = `${getFileType(file)} / ${formatBytes(file.size)}`;
const remove = document.createElement("button");
remove.className = "file-upload-preview-remove";
remove.type = "button";
remove.dataset.fileUploadRemove = String(index);
remove.setAttribute("aria-label", `Remove ${file.name}`);
remove.innerHTML = `<i class="ph ph-x"></i>`;
caption.append(name, meta);
figure.append(remove, preview, caption);
previewNode.append(figure);
});
previewNode.hidden = false;
}
function init(root = document) {
if(initializedRoots.has(root)) {
return;
}
root.addEventListener("click", event => {
const clearButton = event.target.closest("[data-input-clear]");
if(!clearButton) {
return;
}
const group = clearButton.closest(".input-group");
const input = group?.querySelector("input, textarea");
if(!input) {
return;
}
input.value = "";
input.dispatchEvent(new Event("input", { bubbles: true }));
input.focus();
});
root.addEventListener("click", event => {
const removeButton = event.target.closest("[data-file-upload-remove]");
if(!removeButton) {
return;
}
const container = removeButton.closest(".file-upload-panel, .file-upload");
const input = container?.querySelector("[data-file-upload-input]");
if(!input) {
return;
}
removeStoredFile(input, Number(removeButton.dataset.fileUploadRemove));
updateFileUpload(input);
input.dispatchEvent(new Event("change", { bubbles: true }));
});
root.addEventListener("click", event => {
const input = event.target.closest("[data-date-picker]");
if(!input) {
return;
}
input.focus();
if(typeof input.showPicker === "function") {
try {
input.showPicker();
} catch(error) {
// Some browsers restrict showPicker() to direct user gestures or supported input types.
}
}
});
root.addEventListener("change", event => {
const input = event.target.closest("[data-file-upload-input]");
if(!input) {
return;
}
addStoredFiles(input, Array.from(input.files || []));
updateFileUpload(input);
});
root.addEventListener("reset", event => {
const form = event.target.closest("form");
if(!form) {
return;
}
setTimeout(() => {
form.querySelectorAll("[data-file-upload-input]").forEach(input => {
setStoredFiles(input, []);
updateFileUpload(input);
});
}, 0);
});
initializedRoots.add(root);
}
export default {
init,
updateFileUpload
};