import DOMPurify from "dompurify";
export interface FileConfig {
dragDropClassname: string;
inputSelector: string;
resultsSelector: string;
deleteButtonSelector: string;
deleteFileAttr: string;
fileTemplate: (name: string, i: number) => string;
}
export const defaultConfig: FileConfig = {
dragDropClassname: "is-dragdrop",
inputSelector: 'input[type="file"]',
resultsSelector: ".file-input__files",
deleteButtonSelector: "[data-file-delete]",
deleteFileAttr: "data-file-delete",
fileTemplate: (name: string, i: number) => {
return `
${name}
`;
},
};
export default class FileStatic {
element: HTMLElement;
config: FileConfig;
input: HTMLInputElement | null;
files: File[];
results: HTMLElement | null;
removeButtons: HTMLButtonElement[];
constructor(element: HTMLElement, config?: Partial) {
this.element = element;
this.config = { ...defaultConfig, ...config };
this.input = null;
this.files = [];
this.results = null;
this.removeButtons = [];
this.onInputChange = this.onInputChange.bind(this);
this.onRemoveButtonClick = this.onRemoveButtonClick.bind(this);
this.highlight = this.highlight.bind(this);
this.unhighlight = this.unhighlight.bind(this);
this.onDrop = this.onDrop.bind(this);
this.preventDefaults = this.preventDefaults.bind(this);
this.init();
return this;
}
init() {
this.input = this.element.querySelector(
this.config.inputSelector,
) as HTMLInputElement | null;
this.results = this.element.querySelector(
this.config.resultsSelector,
) as HTMLElement | null;
if (!this.input || !this.results) return;
this.input.addEventListener("change", this.onInputChange);
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
this.element.addEventListener(eventName, this.preventDefaults, false);
document.body.addEventListener(eventName, this.preventDefaults, false);
});
["dragenter", "dragover"].forEach((eventName) => {
this.element.addEventListener(eventName, this.highlight, false);
});
["dragleave", "drop"].forEach((eventName) => {
this.element.addEventListener(eventName, this.unhighlight, false);
});
this.element.addEventListener("drop", this.onDrop);
}
onInputChange() {
if (!this.input || !this.results) return false;
this.files = Array.from(this.input.files ?? []);
if (!this.files) return false;
this.results.innerHTML = "";
const listItems = this.files.map((file, i) =>
this.config.fileTemplate(
DOMPurify.sanitize(file.name, { ALLOWED_TAGS: [] }),
i,
),
);
listItems.forEach((listItem) => {
this.results!.appendChild(
DOMPurify.sanitize(listItem, {
ADD_TAGS: ["use"],
RETURN_DOM_FRAGMENT: true,
}) as DocumentFragment,
);
});
this.removeButtons = Array.from(
this.results.querySelectorAll(this.config.deleteButtonSelector),
) as HTMLButtonElement[];
this.removeButtons.forEach((button) =>
button.addEventListener("click", this.onRemoveButtonClick),
);
}
onDrop(e: DragEvent) {
if (!this.input) return;
this.input.files = e?.dataTransfer?.files || new DataTransfer().files;
this.onInputChange();
}
onRemoveButtonClick(e: MouseEvent) {
if (!this.input) return;
const dt = new DataTransfer();
const target = e.currentTarget as HTMLElement;
const deletedFileIndex = Number(
target.getAttribute(this.config.deleteFileAttr),
);
this.files.splice(deletedFileIndex, 1);
this.files.forEach((file) => dt.items.add(file));
this.input.files = dt.files;
this.onInputChange();
}
highlight() {
this.element.classList.add(this.config.dragDropClassname);
}
unhighlight() {
this.element.classList.remove(this.config.dragDropClassname);
}
preventDefaults(e: Event) {
e.preventDefault();
e.stopPropagation();
}
destroy() {
if (!this.input) return;
this.input.removeEventListener("change", this.onInputChange);
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
this.element.removeEventListener(eventName, this.preventDefaults, false);
document.body.removeEventListener(eventName, this.preventDefaults, false);
});
["dragenter", "dragover"].forEach((eventName) => {
this.element.removeEventListener(eventName, this.highlight, false);
});
["dragleave", "drop"].forEach((eventName) => {
this.element.removeEventListener(eventName, this.unhighlight, false);
});
this.element.removeEventListener("drop", this.onDrop);
}
update() {
this.destroy();
this.init();
}
}