// DOM Utils type PhilomenaInputElements = HTMLTextAreaElement | HTMLInputElement | HTMLButtonElement; /** * Get the first matching element */ export function $<E extends Element = Element>(selector: string, context: Pick<Document, 'querySelector'> = document): E | null { return context.querySelector<E>(selector); } /** * Get every matching element as an array */ export function $$<E extends Element = Element>(selector: string, context: Pick<Document, 'querySelectorAll'> = document): E[] { const elements = context.querySelectorAll<E>(selector); return [...elements]; } export function showEl<E extends HTMLElement>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => el.classList.remove('hidden')); } export function hideEl<E extends HTMLElement>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => el.classList.add('hidden')); } export function toggleEl<E extends HTMLElement>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => el.classList.toggle('hidden')); } export function clearEl<E extends HTMLElement>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => { while (el.firstChild) el.removeChild(el.firstChild); }); } export function disableEl<E extends PhilomenaInputElements>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => { el.disabled = true; }); } export function enableEl<E extends PhilomenaInputElements>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => { el.disabled = false; }); } export function removeEl<E extends HTMLElement>(...elements: E[] | ConcatArray<E>[]) { ([] as E[]).concat(...elements).forEach(el => el.parentNode?.removeChild(el)); } export function makeEl<Tag extends keyof HTMLElementTagNameMap>(tag: Tag, attr?: Partial<HTMLElementTagNameMap[Tag]>): HTMLElementTagNameMap[Tag] { const el = document.createElement(tag); if (attr) { for (const prop in attr) { const newValue = attr[prop]; if (newValue) { el[prop] = newValue; } } } return el; } export function onLeftClick(callback: (e: MouseEvent) => boolean | void, context: Pick<GlobalEventHandlers, 'addEventListener' | 'removeEventListener'> = document): VoidFunction { const handler: typeof callback = event => { if (event.button === 0) callback(event); }; context.addEventListener('click', handler); return () => context.removeEventListener('click', handler); } /** * Execute a function when the DOM is ready */ export function whenReady(callback: VoidFunction): void { if (document.readyState !== 'loading') { callback(); } else { document.addEventListener('DOMContentLoaded', callback); } } export function escapeHtml(html: string): string { return html.replace(/&/g, '&') .replace(/>/g, '>') .replace(/</g, '<') .replace(/"/g, '"'); } export function escapeCss(css: string): string { return css.replace(/\\/g, '\\\\') .replace(/"/g, '\\"'); } export function findFirstTextNode<N extends Node>(of: Node): N { return Array.prototype.filter.call(of.childNodes, el => el.nodeType === Node.TEXT_NODE)[0]; }