export const targetFromEvent = (evt: Event, selector: string): Element | undefined => { const targets = evt.composedPath() as Element[]; return targets.find((target) => target.matches?.(selector)); }; export const editableActiveElement = (): boolean => !!document.activeElement && /^(input)|(textarea)$/.test(document.activeElement.nodeName.toLowerCase()); export const inVisibleScrollArea = ( scrollElement: HTMLElement, childElement: HTMLElement ): boolean => { const scrollTop = scrollElement.offsetTop + scrollElement.scrollTop; const scrollBottom = scrollTop + scrollElement.offsetHeight; const childTop = childElement.offsetTop; const childBottom = childTop + childElement.clientHeight; if (childTop >= scrollTop && childTop < scrollBottom) return true; if (childTop < scrollTop && childBottom > scrollTop) return true; return false; }; export type FilesOrFile = T extends true ? File[] : File; export const selectFile = ( accept: string, multiple?: M ): Promise | undefined> => new Promise((resolve) => { const input = document.createElement('input'); input.type = 'file'; if (accept) input.accept = accept; if (multiple) input.multiple = true; const changeHandler = () => { const fileList = input.files; if (!fileList) { resolve(undefined); } else { const files: File[] = [...fileList].filter((file) => file); resolve((multiple ? files : files[0]) as FilesOrFile); } input.removeEventListener('change', changeHandler); }; input.addEventListener('change', changeHandler); input.click(); }); export const getDataTransferFiles = (dataTransfer: DataTransfer): File[] | undefined => { const fileList = dataTransfer.files; const files = [...fileList].filter((file) => file); if (files.length === 0) return undefined; return files; }; export const getImageUrlBlob = async (url: string) => { const res = await fetch(url); const blob = await res.blob(); return blob; }; export const getImageFileUrl = (fileOrBlob: File | Blob) => URL.createObjectURL(fileOrBlob); export const getVideoFileUrl = (fileOrBlob: File | Blob) => URL.createObjectURL(fileOrBlob); export const loadImageElement = (url: string): Promise => new Promise((resolve, reject) => { const img = document.createElement('img'); img.onload = () => resolve(img); img.onerror = (err) => reject(err); img.src = url; }); export const loadVideoElement = (url: string): Promise => new Promise((resolve, reject) => { const video = document.createElement('video'); video.preload = 'metadata'; video.playsInline = true; video.muted = true; video.onloadeddata = () => { resolve(video); video.pause(); }; video.onerror = (e) => { reject(e); }; video.src = url; video.load(); video.play(); }); export const getThumbnailDimensions = (width: number, height: number): [number, number] => { const MAX_WIDTH = 400; const MAX_HEIGHT = 300; let targetWidth = width; let targetHeight = height; if (targetHeight > MAX_HEIGHT) { targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight)); targetHeight = MAX_HEIGHT; } if (targetWidth > MAX_WIDTH) { targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth)); targetWidth = MAX_WIDTH; } return [targetWidth, targetHeight]; }; export const getThumbnail = ( img: HTMLImageElement | SVGImageElement | HTMLVideoElement, width: number, height: number, thumbnailMimeType?: string ): Promise => new Promise((resolve) => { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const context = canvas.getContext('2d'); if (!context) { resolve(undefined); return; } context.drawImage(img, 0, 0, width, height); canvas.toBlob((thumbnail) => { resolve(thumbnail ?? undefined); }, thumbnailMimeType ?? 'image/jpeg'); });