philomena/assets/js/utils/dom.ts
2024-07-05 21:48:17 -04:00

110 lines
3.2 KiB
TypeScript

// 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, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
}
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];
}