59f4829fa9
It's been fun while it lasted!
199 lines
6.0 KiB
TypeScript
199 lines
6.0 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
// Fake jQuery because jQuery is for faggots
|
|
|
|
export class SelectorError extends Error {
|
|
constructor(...args: any[]) {
|
|
super(...args);
|
|
this.name = 'SelectorError';
|
|
}
|
|
}
|
|
|
|
export default class Q {
|
|
private document: Document;
|
|
|
|
constructor(document: Document) {
|
|
this.document = document;
|
|
}
|
|
|
|
public $<T = HTMLElement>(queryString: string): T {
|
|
const element = this.document.querySelector<HTMLElement>(queryString);
|
|
if (element === null) {
|
|
throw new SelectorError(`unable to find [${queryString}] in document`);
|
|
}
|
|
return element as unknown as T; // dangerous but convenient
|
|
}
|
|
|
|
public $$(queryString: string): HTMLElement[] {
|
|
return Array.from(this.document.querySelectorAll<HTMLElement>(queryString));
|
|
}
|
|
|
|
public $$$<T = HTMLElement>(baseElement: Element, queryString: string): T {
|
|
const element = baseElement.querySelector<HTMLElement>(queryString);
|
|
if (element === null) {
|
|
throw new SelectorError(`unable to find [${queryString}] in document`);
|
|
}
|
|
return element as unknown as T; // dangerous but convenient
|
|
}
|
|
|
|
public $$$$(baseElement: Element, queryString: string): HTMLElement[] {
|
|
return Array.from(baseElement.querySelectorAll<HTMLElement>(queryString));
|
|
}
|
|
|
|
public $_(queryString: string): HTMLElement | null {
|
|
try {
|
|
return this.$(queryString);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public $$$_(baseElement: Element, queryString: string): HTMLElement | null {
|
|
try {
|
|
return this.$$$(baseElement, queryString);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @function $.zip
|
|
* @description Zip arrays together
|
|
* $.zip([2, 3, 4], [5, 6]) -> [ [2, 5], [3, 6], [4, undefined] ]
|
|
* $.zip([3, 4], [5, 6, 7]) -> [ [3, 5], [4, 6], [undefined, 7] ]
|
|
* @param {...any} arrays Lists to zip together
|
|
* @return The arrays zipped together
|
|
*/
|
|
public static zip(...arrays: any[]): any[] {
|
|
const n = 0;
|
|
const result: any[] = [];
|
|
for (const array of arrays) {
|
|
if (!Array.isArray(array)) throw new Error('all parameters must be arrays');
|
|
for (let i = 0; i < array.length; ++i) {
|
|
if (i == result.length) {
|
|
result.push([]);
|
|
for (let j = 0; j < n; ++j) {
|
|
result[i].push(undefined);
|
|
}
|
|
}
|
|
result[i].push(array[i]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @description gets the previous Element in the DOM. Useful to skip any text nodes
|
|
* @param element Element to get the previous element of
|
|
* @returns the previous Element in the DOM or null if there are no Element before the specified element.
|
|
*/
|
|
public static previousElement(element: Element): Element | null {
|
|
let current: ChildNode | Element = element;
|
|
while (current.previousSibling) {
|
|
if (current.previousSibling instanceof Element) {
|
|
return current.previousSibling;
|
|
}
|
|
current = current.previousSibling;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @description gets the next Element in the DOM. Useful to skip any text nodes
|
|
* @param element Element to get the next element of
|
|
* @returns the next Element in the DOM or null if there are no Elements after the specified element.
|
|
*/
|
|
public static nextElement(element: Element): Element | null {
|
|
let current: ChildNode | Element = element;
|
|
while (current.nextSibling) {
|
|
if (current.nextSibling instanceof Element) {
|
|
return current.nextSibling;
|
|
}
|
|
current = current.nextSibling;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @function $.clearChildren
|
|
* @description removes all children from an element
|
|
* @param {Element} element The element to remove all children from
|
|
*/
|
|
public static clearChildren = function(element: Element): void {
|
|
while (element.firstChild) {
|
|
element.removeChild(element.firstChild);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description Adds to the internal content of an Element
|
|
* @param {Element} element The element to add content to
|
|
* @param content The content to be added to the element
|
|
*/
|
|
public addContent(element: Element, content: any) {
|
|
content = Array.isArray(content) ? content : [ content ];
|
|
for (const e of content) {
|
|
element.appendChild(this.create(e));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively creates an Element from the specified content
|
|
* @param obj An element initialization object { tag, id, class, content, ... }
|
|
* @var tag The HTML tag of the element (default: 'div')
|
|
* @var id The id of the elemnt
|
|
* @var class The class of the element (string or list of strings)
|
|
* @var content The internal content of the element. This is handled recursively.
|
|
* Can be a string, Element, obj, list of objs, or list of Elements
|
|
* @var ... All other attributes will be applied using element.setAttribute(key, value)
|
|
*/
|
|
public create(obj: Element | any | null): Node {
|
|
if (obj instanceof Element) {
|
|
return obj
|
|
} else if (typeof obj !== 'object') {
|
|
return this.document.createTextNode(obj.toString());
|
|
} else if (obj === null) {
|
|
return this.document.createTextNode('[null]');
|
|
} else if (obj === undefined) {
|
|
throw new Error('obj is undefined');
|
|
}
|
|
|
|
let element: Element;
|
|
if (obj.ns) {
|
|
element = document.createElementNS(obj.ns, obj.tag ?? 'div');
|
|
} else {
|
|
element = document.createElement(obj.tag ?? 'div');
|
|
}
|
|
|
|
if (obj.id) {
|
|
element.id = obj.id;
|
|
}
|
|
|
|
if (obj.class) {
|
|
if (Array.isArray(obj.class)) {
|
|
for (const c of obj.class) {
|
|
if (typeof c !== 'string') throw new Error(`Invalid obj.class type: ${typeof c}`);
|
|
element.classList.add(c);
|
|
}
|
|
} else if (typeof obj.class === 'string') {
|
|
element.className = obj.class;
|
|
} else {
|
|
throw new Error(`Invalid obj.class type: ${obj.class}`);
|
|
}
|
|
}
|
|
|
|
for (const key in obj) {
|
|
if (key !== 'tag' && key !== 'id' && key != 'class' && key != 'content') {
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
element.setAttribute(key.toString(), obj[key].toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj.content !== undefined) {
|
|
this.addContent(element, obj.content);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
}
|