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(); } }