UNPKG

tiny-essentials

Version:

Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.

1,159 lines 203 kB
import * as TinyCollision from '../basics/collision.mjs'; const { areElsColliding, areElsPerfColliding, areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, } = TinyCollision; /** * Callback invoked on each animation frame with the current scroll position, * normalized animation time (`0` to `1`), and a completion flag. * * @typedef {(progress: { x: number, y: number, isComplete: boolean, time: number }) => void} OnScrollAnimation */ /** * A list of supported easing function names for smooth animations. * * @typedef {'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'} Easings */ /** * Represents a raw Node element or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {Node|TinyHtml|null} TinyNode */ /** * Represents a raw DOM element or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {Element|TinyHtml} TinyElement */ /** * Represents a raw DOM html element or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {HTMLElement|TinyHtml} TinyHtmlElement */ /** * Represents a raw DOM event target element or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {EventTarget|TinyHtml} TinyEventTarget */ /** * Represents a raw DOM input element or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {InputElement|TinyHtml} TinyInputElement */ /** * Represents a raw DOM element/window or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {ElementAndWindow|TinyHtml} TinyElementAndWindow */ /** * Represents a value that can be either a DOM Element or the global Window object. * Useful for functions that operate generically on scrollable or measurable targets. * * @typedef {Element|Window} ElementAndWindow */ /** * Represents a raw DOM element/window/document or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {ElementAndWinAndDoc|TinyHtml} TinyElementAndWinAndDoc */ /** * Represents a value that can be either a DOM Element, or the global Window object, or the document object. * Useful for functions that operate generically on scrollable or measurable targets. * * @typedef {Element|Window|Document} ElementAndWinAndDoc */ /** * Represents a raw DOM element with document or an instance of TinyHtml. * This type is used to abstract interactions with both plain elements * and wrapped elements via the TinyHtml class. * * @typedef {ElementWithDoc|TinyHtml} TinyElementWithDoc */ /** * Represents a value that can be either a DOM Element, or the document object. * Useful for functions that operate generically on measurable targets. * * @typedef {Element|Document} ElementWithDoc */ /** * A parameter type used for filtering or matching elements. * It can be: * - A string (CSS selector), * - A raw DOM element, * - An array of raw DOM elements, * - A filtering function that receives an index and element, * and returns true if it matches. * * @typedef {string|Element|Element[]|((index: number, el: Element) => boolean)} WinnowRequest */ /** * Elements accepted as constructor values for TinyHtml. * These include common DOM elements and root containers. * * @typedef {Window|Element|Document|Text} ConstructorElValues */ /** * Options passed to `addEventListener` or `removeEventListener`. * Can be a boolean or an object of type `AddEventListenerOptions`. * * @typedef {boolean|AddEventListenerOptions} EventRegistryOptions */ /** * Structure describing a registered event callback and its options. * * @typedef {Object} EventRegistryItem * @property {EventListenerOrEventListenerObject|null} handler - The function to be executed when the event is triggered. * @property {EventRegistryOptions} [options] - Optional configuration passed to the listener. */ /** * Maps event names (e.g., `"click"`, `"keydown"`) to a list of registered handlers and options. * * @typedef {Record<string, EventRegistryItem[]>} EventRegistryList */ /** * WeakMap storing all event listeners per element. * Each element has a registry mapping event names to their handler lists. * * @type {WeakMap<ConstructorElValues|EventTarget, EventRegistryList>} */ const __eventRegistry = new WeakMap(); /** * A key-value store associated with a specific DOM element. * Keys are strings, and values can be of any type. * * @typedef {Record<string, *>} ElementDataStore */ /** * WeakMap to hold private data for elements * * @type {WeakMap<ConstructorElValues, ElementDataStore>} */ const __elementDataMap = new WeakMap(); /** * Stores directional collision locks separately. * Each direction has its own WeakMap to allow independent locking. * * @type {{ * top: WeakMap<Element, true>, * bottom: WeakMap<Element, true>, * left: WeakMap<Element, true>, * right: WeakMap<Element, true> * }} */ const __elemCollision = { top: new WeakMap(), bottom: new WeakMap(), left: new WeakMap(), right: new WeakMap(), }; /** * Possible directions from which a collision was detected and locked. * * @typedef {'top'|'bottom'|'left'|'right'} CollisionDirLock */ /** * @typedef {Object} HtmlElBoxSides * @property {number} x - Total horizontal size (left + right) * @property {number} y - Total vertical size (top + bottom) * @property {number} left * @property {number} right * @property {number} top * @property {number} bottom */ /** * @typedef {string | number | Date | boolean | null} SetValueBase - Primitive types accepted as input values. */ /** * @typedef {'string' | 'date' | 'number'} GetValueTypes * Types of value extractors supported by TinyHtml._valTypes. */ /** * @typedef {SetValueBase|SetValueBase[]} SetValueList - A single value or an array of values to be assigned to the input element. */ /** * A list of HTML form elements that can have a `.value` property used by TinyHtml. * Includes common input types used in forms. * * @typedef {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLOptionElement} InputElement */ /** * Represents a parsed HTML element in JSON-like array form. * * @typedef {[ * tagName: string, // The tag name of the element (e.g., 'div', 'img') * attributes: Record<string, string>, // All element attributes as key-value pairs * ...children: (string | HtmlParsed)[] // Text or nested elements * ]} HtmlParsed */ /** * TinyHtml is a utility class that provides static and instance-level methods * for precise dimension and position computations on HTML elements. * It mimics some jQuery functionalities while using native browser APIs. * * Inspired by the jQuery project's open source implementations of element dimension * and offset utilities. This class serves as a lightweight alternative using modern DOM APIs. * * @class */ class TinyHtml { /** @typedef {import('../basics/collision.mjs').ObjRect} ObjRect */ static Utils = { ...TinyCollision }; /** * Fetches an HTML file from the given URL, parses it to JSON. * * @param {string | URL | globalThis.Request} url - The URL of the HTML file. * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc). * @returns {Promise<HtmlParsed[]>} A promise that resolves with the parsed JSON representation of the HTML structure. */ static async fetchHtmlFile(url, ops = { method: 'GET' }) { const res = await fetch(url, ops); const contentType = res.headers.get('Content-Type') || ''; if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`); // Only accept HTML responses if (!contentType.includes('text/html')) { throw new Error(`Invalid content type: ${contentType} (expected text/html)`); } const html = await res.text(); return TinyHtml.htmlToJson(html); } /** * Fetches an HTML file from the given URL, parses it to JSON, then converts it to DOM nodes. * * @param {string} url - The URL of the HTML file. * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc). * @returns {Promise<(HTMLElement|Text)[]>} A promise that resolves with the DOM nodes. */ static async fetchHtmlNodes(url, ops) { const json = await TinyHtml.fetchHtmlFile(url, ops); return TinyHtml.jsonToNodes(json); } /** * Fetches an HTML file from the given URL, parses it to JSON, then converts it to TinyHtml instances. * * @param {string} url - The URL of the HTML file. * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc). * @returns {Promise<TinyHtml[]>} A promise that resolves with the TinyHtml instances. */ static async fetchHtmlTinyElems(url, ops) { const nodes = await TinyHtml.fetchHtmlNodes(url, ops); return TinyHtml.toTinyElm(nodes); } /** * Converts the content of a <template> to an array of HtmlParsed. * * @param {HTMLTemplateElement} nodes * @returns {HtmlParsed[]} */ static templateToJson(nodes) { return TinyHtml.htmlToJson([...nodes.content.childNodes] .map((node) => node instanceof Element ? node.getHTML() : node instanceof Text ? node.textContent : '') .join('')); } /** * Converts the content of a <template> to real DOM nodes. * * @param {HTMLTemplateElement} nodes * @returns {(Element|Text)[]} */ static templateToNodes(nodes) { /** @type {(Element|Text)[]} */ const result = []; [...nodes.content.cloneNode(true).childNodes].map((node) => { if (!(node instanceof Element) && !(node instanceof Text) && !(node instanceof Comment)) throw new Error(`Expected only Element nodes in <template>, but found: ${node.constructor.name}`); if (!(node instanceof Comment)) result.push(node); }); return result; } /** * Converts the content of a <template> to an array of TinyHtml elements. * * @param {HTMLTemplateElement} nodes * @returns {TinyHtml[]} */ static templateToTinyElems(nodes) { return TinyHtml.toTinyElm(TinyHtml.templateToNodes(nodes)); } /** * Parses a full HTML string into a JSON-like structure. * * @param {string} htmlString - Full HTML markup as a string. * @returns {HtmlParsed[]} An array of parsed HTML elements in structured format. */ static htmlToJson(htmlString) { const container = document.createElement('div'); container.innerHTML = htmlString.trim(); const result = []; /** * @param {Node} el * @returns {*} */ const parseElement = (el) => { if (el instanceof Comment) return null; if (el instanceof Text) { const text = el.textContent?.trim(); return text ? text : null; } if (!(el instanceof Element)) return null; const tag = el.tagName.toLowerCase(); /** @type {Record<string, string>} */ const props = {}; for (const attr of el.attributes) { props[TinyHtml.getPropName(attr.name)] = attr.value; } const children = Array.from(el.childNodes).map(parseElement).filter(Boolean); return children.length > 0 ? [tag, props, ...children] : [tag, props]; }; for (const child of container.childNodes) { const parsed = parseElement(child); if (parsed) result.push(parsed); } container.innerHTML = ''; return result; } /** * Converts a JSON-like HTML structure back to DOM Elements. * * @param {HtmlParsed[]} jsonArray - Parsed JSON format of HTML. * @returns {(HTMLElement|Text)[]} List of DOM nodes. */ static jsonToNodes(jsonArray) { /** * @param {HtmlParsed|string} nodeData * @returns {HTMLElement|Text} */ const createElement = (nodeData) => { if (typeof nodeData === 'string') { return document.createTextNode(nodeData); } if (!Array.isArray(nodeData)) return document.createTextNode(''); const [tag, props, ...children] = nodeData; const el = document.createElement(tag); for (const [key, value] of Object.entries(props)) { el.setAttribute(TinyHtml.getAttrName(key), value); } for (const child of children) { const childEl = createElement(child); if (childEl instanceof Comment) continue; el.appendChild(childEl); } return el; }; return jsonArray.map(createElement).filter((node) => !(node instanceof Comment)); } /** * Converts a JSON-like HTML structure back to TinyHtml instances. * * @param {HtmlParsed[]} jsonArray - Parsed JSON format of HTML. * @returns {TinyHtml[]} List of TinyHtml instances. */ static jsonToTinyElems(jsonArray) { return TinyHtml.toTinyElm(TinyHtml.jsonToNodes(jsonArray)); } /** * Creates a new TinyHtml element from a tag name and optional attributes. * * You can alias this method for convenience: * ```js * const createElement = TinyHtml.createFrom; * const myDiv = createElement('div', { class: 'box' }); * ``` * * @param {string} tagName - The HTML tag name (e.g., 'div', 'span', 'button'). * @param {Record<string, string|null>} [attrs] - Optional key-value pairs representing HTML attributes. * If the value is `null`, the attribute will still be set with an empty value. * @returns {TinyHtml} - A new instance of TinyHtml representing the created element. * @throws {TypeError} - If `tagName` is not a string, or `attrs` is not a plain object when defined. */ static createFrom(tagName, attrs) { if (typeof tagName !== 'string') throw new TypeError('The "tagName" must be a string.'); if (typeof attrs !== 'undefined' && typeof attrs !== 'object') throw new TypeError('The "attrs" must be a object.'); const elem = TinyHtml.createElement(tagName); if (typeof attrs === 'object') { for (const attrName in attrs) { elem.setAttr(attrName, attrs[attrName]); } } return elem; } /** * Creates a new DOM element with the specified tag name and options, then wraps it in a TinyHtml instance. * * @param {string} tagName - The tag name of the element to create (e.g., 'div', 'span'). * @param {ElementCreationOptions} [ops] - Optional settings for creating the element. * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM element. * @throws {TypeError} If tagName is not a string or ops is not an object. */ static createElement(tagName, ops) { if (typeof tagName !== 'string') throw new TypeError(`[TinyHtml] createElement(): The tagName must be a string.`); if (typeof ops !== 'undefined' && typeof ops !== 'object') throw new TypeError(`[TinyHtml] createElement(): The ops must be a object.`); return new TinyHtml(document.createElement(tagName, ops)); } /** * Creates a new TinyHtml instance that wraps a DOM TextNode. * * This method is useful when you want to insert raw text content into the DOM * without it being interpreted as HTML. The returned instance behaves like any * other TinyHtml element and can be appended or manipulated as needed. * * @param {string} value - The plain text content to be wrapped in a TextNode. * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM TextNode. * @throws {TypeError} If the provided value is not a string. */ static createTextNode(value) { if (typeof value !== 'string') throw new TypeError(`[TinyHtml] createTextNode(): The value must be a string.`); return new TinyHtml(document.createTextNode(value)); } /** * Creates an HTMLElement or TextNode from an HTML string. * Supports both elements and plain text. * * @param {string} htmlString - The HTML string to convert. * @returns {TinyHtml} - A single HTMLElement or TextNode. */ static createElementFromHTML(htmlString) { const template = document.createElement('template'); htmlString = htmlString.trim(); // If it's only text (e.g., no "<" tag), return a TextNode if (!htmlString.startsWith('<')) { return TinyHtml.createTextNode(htmlString); } template.innerHTML = htmlString; if (!(template.content.firstChild instanceof Element)) throw new Error('The HTML string must contain a valid HTML element.'); return new TinyHtml(template.content.firstChild); } /** * Queries the document for the first element matching the CSS selector and wraps it in a TinyHtml instance. * * @param {string} selector - A valid CSS selector string. * @param {Document|Element} elem - Target element. * @returns {TinyHtml|null} A TinyHtml instance wrapping the matched element. */ static query(selector, elem = document) { const newEl = elem.querySelector(selector); if (!newEl) return null; return new TinyHtml(newEl); } /** * Queries the element for the first element matching the CSS selector and wraps it in a TinyHtml instance. * * @param {string} selector - A valid CSS selector string. * @returns {TinyHtml|null} A TinyHtml instance wrapping the matched element. */ querySelector(selector) { return TinyHtml.query(selector, TinyHtml._preElem(this, 'query')); } /** * Queries the document for all elements matching the CSS selector and wraps them in TinyHtml instances. * * @param {string} selector - A valid CSS selector string. * @param {Document|Element} elem - Target element. * @returns {TinyHtml} An array of TinyHtml instances wrapping the matched elements. */ static queryAll(selector, elem = document) { return new TinyHtml(elem.querySelectorAll(selector)); } /** * Queries the element for all elements matching the CSS selector and wraps them in TinyHtml instances. * * @param {string} selector - A valid CSS selector string. * @returns {TinyHtml} An array of TinyHtml instances wrapping the matched elements. */ querySelectorAll(selector) { return TinyHtml.queryAll(selector, TinyHtml._preElem(this, 'queryAll')); } /** * Retrieves an element by its ID and wraps it in a TinyHtml instance. * * @param {string} selector - The ID of the element to retrieve. * @returns {TinyHtml|null} A TinyHtml instance wrapping the found element. */ static getById(selector) { const newEl = document.getElementById(selector); if (!newEl) return null; return new TinyHtml(newEl); } /** * Retrieves all elements with the specified class name and wraps them in TinyHtml instances. * * @param {string} selector - The class name to search for. * @param {Document|Element} elem - Target element. * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements. */ static getByClassName(selector, elem = document) { return new TinyHtml(elem.getElementsByClassName(selector)); } /** * Retrieves all elements with the specified class name and wraps them in TinyHtml instances. * * @param {string} selector - The class name to search for. * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements. */ getElementsByClassName(selector) { return TinyHtml.getByClassName(selector, TinyHtml._preElem(this, 'getByClassName')); } /** * Retrieves all elements with the specified name attribute and wraps them in TinyHtml instances. * * @param {string} selector - The name attribute to search for. * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements. */ static getByName(selector) { return new TinyHtml(document.getElementsByName(selector)); } /** * Retrieves all elements with the specified local tag name within the given namespace URI, * and wraps them in TinyHtml instances. * * @param {string} localName - The local name (tag) of the elements to search for. * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within. * @param {Document|Element} elem - Target element. * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements. */ static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml', elem = document) { return new TinyHtml(elem.getElementsByTagNameNS(namespaceURI, localName)); } /** * Retrieves all elements with the specified local tag name within the given namespace URI, * and wraps them in TinyHtml instances. * * @param {string} localName - The local name (tag) of the elements to search for. * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within. * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements. */ getElementsByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') { return TinyHtml.getByTagNameNS(localName, namespaceURI, TinyHtml._preElem(this, 'getByTagNameNS')); } ////////////////////////////////////////////////////////////////// /** * Returns the current target held by this instance. * * @param {number} index - The index of the element to retrieve. * @returns {ConstructorElValues} - The instance's target element. */ get(index) { if (typeof index !== 'number') throw new TypeError('The index must be a number.'); if (!this.#el[index]) throw new Error(`No element found at index ${index}.`); return this.#el[index]; } /** * Extracts a single DOM element from the internal list at the specified index. * * @param {number} index - The index of the element to extract. * @returns {TinyHtml} A new TinyHtml instance wrapping the extracted element. */ extract(index) { if (typeof index !== 'number') throw new TypeError('The index must be a number.'); if (!this.#el[index]) throw new Error(`Cannot extract: no element exists at index ${index}.`); return new TinyHtml(this.#el[index]); } /** * Checks whether the element exists at the given index. * * @param {number} index - The index to check. * @returns {boolean} - True if the element exists; otherwise, false. */ exists(index) { if (typeof index !== 'number') throw new TypeError('The index must be a number.'); if (!this.#el[index]) return false; return true; } /** * Returns the current targets held by this instance. * * @returns {ConstructorElValues[]} - The instance's targets element. */ getAll() { return [...this.#el]; } /** * Returns the current Element held by this instance. * * @param {string} where - The method name or context calling this. * @param {number} index - The index of the element to retrieve. * @returns {ConstructorElValues} - The instance's element. * @readonly */ _getElement(where, index) { if (!(this.#el[index] instanceof Element) && !(this.#el[index] instanceof Window) && !(this.#el[index] instanceof Document) && !(this.#el[index] instanceof Text)) throw new Error(`[TinyHtml] Invalid Element in ${where}().`); return this.#el[index]; } /** * Returns the current Elements held by this instance. * * @param {string} where - The method name or context calling this. * @returns {ConstructorElValues[]} - The instance's elements. * @readonly */ _getElements(where) { if (!this.#el.every((el) => el instanceof Element || el instanceof Window || el instanceof Document || el instanceof Text)) throw new Error(`[TinyHtml] Invalid Element in ${where}().`); return [...this.#el]; } ////////////////////////////////////////////////////// /** * Prepares and validates multiple elements against allowed types. * * @param {TinyElement | EventTarget | null | (TinyElement | EventTarget | null)[]} elems - The input elements to validate. * @param {string} where - The method name or context calling this. * @param {any[]} TheTinyElements - The list of allowed constructors (e.g., Element, Document). * @param {string[]} elemName - The list of expected element names for error reporting. * @returns {any[]} - A flat array of validated elements. * @throws {Error} - If any element is not an instance of one of the allowed types. * @readonly */ static _preElemsTemplate(elems, where, TheTinyElements, elemName) { /** @param {(TinyElement|EventTarget|null)[]} item */ const checkElement = (item) => { /** @type {any[]} */ const results = []; item.map((elem) => (elem instanceof TinyHtml ? elem._getElements(where) : [elem]).map((result) => { let allowed = false; for (const TheTinyElement of TheTinyElements) { if (result instanceof TheTinyElement) { allowed = true; break; } } if (!allowed) throw new Error(`[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`); results.push(result); return result; })); return results; }; if (!Array.isArray(elems)) return checkElement([elems]); return checkElement(elems); } /** * Prepares and validates a single element against allowed types. * * @param {TinyElement | EventTarget | null | (TinyElement | EventTarget | null)[]} elems - The input element or list to validate. * @param {string} where - The method name or context calling this. * @param {any[]} TheTinyElements - The list of allowed constructors (e.g., Element, Document). * @param {string[]} elemName - The list of expected element names for error reporting. * @param {boolean} [canNull=false] - Whether `null` is allowed as a valid value. * @returns {any} - The validated element or `null` if allowed. * @throws {Error} - If the element is not valid or if multiple elements are provided. * @readonly */ static _preElemTemplate(elems, where, TheTinyElements, elemName, canNull = false) { /** @param {(TinyElement|EventTarget|null)[]} item */ const checkElement = (item) => { const elem = item[0]; const result = elem instanceof TinyHtml ? elem._getElements(where) : [elem]; if (result.length > 1) throw new Error(`[TinyHtml] Invalid element amount in ${where}() (Received ${result.length}/1).`); let allowed = false; for (const TheTinyElement of TheTinyElements) { if (result[0] instanceof TheTinyElement) { allowed = true; break; } } if (canNull && (result[0] === null || typeof result[0] === 'undefined')) { result[0] = null; allowed = true; } if (!allowed) throw new Error(`[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`); return result[0]; }; if (!Array.isArray(elems)) return checkElement([elems]); if (elems.length > 1) throw new Error(`[TinyHtml] Invalid element amount in ${where}() (Received ${elems.length}/1).`); return checkElement(elems); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single elements. * * @param {TinyElement|TinyElement[]} elems - A single element or array of elements. * @param {string} where - The method or context name where validation is being called. * @returns {Element[]} - Always returns an array of elements. * @readonly */ static _preElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [Element], ['Element']); } /** * Ensures the input is returned as an single element. * Useful to normalize operations across multiple or single elements. * * @param {TinyElement|TinyElement[]} elems - A single element or array of elements. * @param {string} where - The method or context name where validation is being called. * @returns {Element} - Always returns an single element. * @readonly */ static _preElem(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Element], ['Element']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single nodes. * * @param {TinyNode|TinyNode[]} elems - A single node or array of nodes. * @param {string} where - The method or context name where validation is being called. * @returns {Node[]} - Always returns an array of nodes. * @readonly */ static _preNodeElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [Node], ['Node']); } /** * Ensures the input is returned as an single node. * Useful to normalize operations across multiple or single nodes. * * @param {TinyNode|TinyNode[]} elems - A single node or array of nodes. * @param {string} where - The method or context name where validation is being called. * @returns {Node} - Always returns an single node. * @readonly */ static _preNodeElem(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Node], ['Node']); } /** * Ensures the input is returned as an single node. * Useful to normalize operations across multiple or single nodes. * * @param {TinyNode|TinyNode[]} elems - A single node or array of nodes. * @param {string} where - The method or context name where validation is being called. * @returns {Node|null} - Always returns an single node or null. * @readonly */ static _preNodeElemWithNull(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Node], ['Node'], true); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single html elements. * * @param {TinyElement|TinyElement[]} elems - A single html element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {HTMLElement[]} - Always returns an array of html elements. * @readonly */ static _preHtmlElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [HTMLElement], ['HTMLElement']); } /** * Ensures the input is returned as an single html element. * Useful to normalize operations across multiple or single html elements. * * @param {TinyElement|TinyElement[]} elems - A single html element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {HTMLElement} - Always returns an single html element. * @readonly */ static _preHtmlElem(elems, where) { return TinyHtml._preElemTemplate(elems, where, [HTMLElement], ['HTMLElement']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single event target elements. * * @param {TinyInputElement|TinyInputElement[]} elems - A single event target element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {InputElement[]} - Always returns an array of event target elements. * @readonly */ static _preInputElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLOptionElement], ['HTMLInputElement', 'HTMLSelectElement', 'HTMLTextAreaElement', 'HTMLOptionElement']); } /** * Ensures the input is returned as an single event target element. * Useful to normalize operations across multiple or single event target elements. * * @param {TinyInputElement|TinyInputElement[]} elems - A single event target element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {InputElement} - Always returns an single event target element. * @readonly */ static _preInputElem(elems, where) { return TinyHtml._preElemTemplate(elems, where, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLOptionElement], ['HTMLInputElement', 'HTMLSelectElement', 'HTMLTextAreaElement', 'HTMLOptionElement']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single event target elements. * * @param {TinyEventTarget|TinyEventTarget[]} elems - A single event target element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {EventTarget[]} - Always returns an array of event target elements. * @readonly */ static _preEventTargetElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [EventTarget], ['EventTarget']); } /** * Ensures the input is returned as an single event target element. * Useful to normalize operations across multiple or single event target elements. * * @param {TinyEventTarget|TinyEventTarget[]} elems - A single event target element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {EventTarget} - Always returns an single event target element. * @readonly */ static _preEventTargetElem(elems, where) { return TinyHtml._preElemTemplate(elems, where, [EventTarget], ['EventTarget']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single element/window elements. * * @param {TinyElementAndWindow|TinyElementAndWindow[]} elems - A single element/window element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {ElementAndWindow[]} - Always returns an array of element/window elements. * @readonly */ static _preElemsAndWindow(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [Element, Window], ['Element', 'Window']); } /** * Ensures the input is returned as an single element/window element. * Useful to normalize operations across multiple or single element/window elements. * * @param {TinyElementAndWindow|TinyElementAndWindow[]} elems - A single element/window element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {ElementAndWindow} - Always returns an single element/window element. * @readonly */ static _preElemAndWindow(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Element, Window], ['Element', 'Window']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single element/window/document elements. * * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/document/window element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {ElementAndWindow[]} - Always returns an array of element/document/window elements. * @readonly */ static _preElemsAndWinAndDoc(elems, where) { const result = TinyHtml._preElemsTemplate(elems, where, [Element, Window, Document], ['Element', 'Window', 'Document']); return result.map((elem) => (!(elem instanceof Document) ? elem : elem.documentElement)); } /** * Ensures the input is returned as an single element/window/document element. * Useful to normalize operations across multiple or single element/window/document elements. * * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/document/window element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {ElementAndWindow} - Always returns an single element/document/window element. * @readonly */ static _preElemAndWinAndDoc(elems, where) { const result = TinyHtml._preElemTemplate(elems, where, [Element, Window, Document], ['Element', 'Window', 'Document']); if (result instanceof Document) return result.documentElement; return result; } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single element with document elements. * * @param {TinyElementWithDoc|TinyElementWithDoc[]} elems - A single element with document element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {ElementWithDoc[]} - Always returns an array of element with document elements. * @readonly */ static _preElemsWithDoc(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [Element, Document], ['Element', 'Document']); } /** * Ensures the input is returned as an single element with document element. * Useful to normalize operations across multiple or single element with document elements. * * @param {TinyElementWithDoc|TinyElementWithDoc[]} elems - A single element/window element or array of html elements. * @param {string} where - The method or context name where validation is being called. * @returns {ElementWithDoc} - Always returns an single element/window element. * @readonly */ static _preElemWithDoc(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Element, Document], ['Element', 'Document']); } /** * Normalizes and converts one or more DOM elements (or TinyHtml instances) * into an array of `TinyHtml` instances. * * - If a plain DOM element is passed, it is wrapped into a `TinyHtml` instance. * - If a `TinyHtml` instance is already passed, it is preserved. * - If an array is passed, all elements inside are converted accordingly. * * This ensures consistent access to methods of the `TinyHtml` class regardless * of the input form. * * @param {TinyElement|Text|(TinyElement|Text)[]} elems - A single element or an array of elements (DOM or TinyHtml). * @returns {TinyHtml[]} An array of TinyHtml instances corresponding to the input elements. */ static toTinyElm(elems) { /** @param {(TinyElement|Text)[]} item */ const checkElement = (item) => item.map((elem) => (!(elem instanceof TinyHtml) ? new TinyHtml(elem) : elem)); if (!Array.isArray(elems)) return checkElement([elems]); return checkElement(elems); } /** * Extracts native `Element` instances from one or more elements, * which can be either raw DOM elements or wrapped in `TinyHtml`. * * - If a `TinyHtml` instance is passed, its internal DOM element is extracted. * - If a raw DOM element is passed, it is returned as-is. * - If an array is passed, each element is processed accordingly. * * This function guarantees that the return value is always an array of * raw `Element` objects, regardless of whether the input was * a mix of `TinyHtml` or native DOM elements. * * @param {TinyElement|TinyElement[]} elems - A single element or an array of elements (DOM or TinyHtml`). * @returns {Element[]} An array of Element instances extracted from the input. */ static fromTinyElm(elems) { /** @param {TinyElement[]} item */ const checkElement = (item) => { /** @type {Element[]} */ const result = []; item.map((elem) => /** @type {Element[]} */ (elem instanceof TinyHtml ? elem._getElements('fromTinyElm') : [elem]).map((elem) => result.push(elem))); return result; }; if (!Array.isArray(elems)) return checkElement([elems]); return checkElement(elems); } /** * Filters an array of elements based on a selector, function, element, or array of elements. * * @param {TinyElement|TinyElement[]} elems * @param {WinnowRequest} qualifier * @param {string} where - The context/method name using this validation. * @param {boolean} not Whether to invert the result (used for .not()) * @returns {Element[]} */ static winnow(elems, qualifier, where, not = false) { if (typeof not !== 'boolean') throw new TypeError('The "not" must be a boolean.'); if (typeof qualifier === 'function') { return TinyHtml._preElems(elems, where).filter((el, i) => !!qualifier.call(el, i, el) !== not); } if (qualifier instanceof Element) { return TinyHtml._preElems(elems, where).filter((el) => (el === qualifier) !== not); } if (Array.isArray(qualifier) || (typeof qualifier !== 'string' && // @ts-ignore qualifier.length != null)) { return TinyHtml._preElems(elems, where).filter((el) => qualifier.includes(el) !== not); } // Assume it's a selector string let selector = qualifier; if (not) selector = `:not(${selector})`; return TinyHtml._preElems(elems, where).filter((el) => el.nodeType === 1 && el.matches(selector)); } /** * Filters a set of elements by a CSS selector. * * @param {TinyElement|TinyElement[]} elems * @param {string} selector * @param {boolean} not * @returns {Element[]} */ static filter(elems, selector, not = false) { if (not) selector = `:not(${selector})`; return TinyHtml._preElems(elems, 'filter').filter((el) => el.nodeType === 1 && el.matches(selector)); } /** * Returns only the elements matching the given selector or function. * * @param {TinyElement|TinyElement[]} elems * @param {WinnowRequest} selector * @returns {Element[]} */ static filterOnly(elems, selector) { return TinyHtml.winnow(elems, selector, 'filterOnly', false); } /** * Returns only the elements **not** matching the given selector or function. * * @param {TinyElement|TinyElement[]} elems * @param {WinnowRequest} selector * @returns {Element[]} */ static not(elems, selector) { return TinyHtml.winnow(elems, selector, 'not', true); } /** * Returns only the elements **not** matching the given selector or function. * * @param {WinnowRequest} selector * @returns {Element[]} */ not(selector) { return TinyHtml.not(this, selector); } /** * Finds elements matching a selector within a context. * * @param {TinyElement|TinyElement[]} context * @param {string} selector * @returns {Element[]} */ static find(context, selector) { const result = []; for (const el of TinyHtml._preElems(context, 'find')) { result.push(...el.querySelectorAll(selector)); } return [...new Set(result)]; } /** * Finds elements in your element matching a selector within a context. * * @param {string} selector * @returns {Element[]} */ find(selector) { return TinyHtml.find(this, selector); } /** * Checks if at least one element matches the selector. * * @param {TinyElement|TinyElement[]} elems * @param {WinnowRequest} selector * @returns {boolean} */ static is(elems, selector) { return TinyHtml.winnow(elems, selector, 'is', false).length > 0; } /** * Checks if the element matches the selector. * * @param {WinnowRequest} selector * @returns {boolean} */ is(selector) { return TinyHtml.is(this, selector); } /** * Returns elements from the current list that contain the given target(s). * @param {TinyElement|TinyElement[]} roots - A single element or an array of elements (DOM or TinyHtml). * @param {string|TinyElement|TinyElement[]} target - Selector or DOM element(s). * @returns {Element[]} Elements that contain the target. */ static has(roots, target) { const targets = typeof target === 'string' ? [...document.querySelectorAll(target)] : TinyHtml._preElems(target, 'has'); return TinyHtml._preElems(roots, 'has').filter((root) => targets.some((t) => root && root.contains(t))); } /** * Return if the element has the target(s). * @param {string|TinyElement|TinyElement[]} target - Selector or DOM element(s). * @returns {boolean} Elements that contain the target. */ has(target) { return TinyHtml.has(this, target).length > 0; } /** * Finds the closest ancestor (including self) that matches the selector. * * @param {TinyElement|TinyElement[]} els - A single element or an array of elements (DOM or TinyHtml). * @param {string|Element} selector - A selector string or DOM element to match. * @param {Element|null} [context] - An optional context to stop searching. * @returns {Element[]} */ static closest(els, selector, context) { const matched = []; for (const el of TinyHtml._preElems(els, 'closest')) { /** @type {Element | null} */ let current = el; while (current && current !== context) { if (current.nodeType === 1 && (typeof selector === 'string' ? current.matches(selector) : current === selector)) { matched.push(current); break; } current = current.parentElement; } } return [...new Set(matched)]; } /** * Finds the closest ancestor (including self) that matches the selector. * * @param {string|Element} selector - A selector string or DOM element to match. * @param {Element|null} [context] - An optional context to stop searching. * @returns {Element[]} */ closest(selector, context) { return TinyHtml.closest(this, selector, context); } /** * Compares two DOM elements to determine if they refer to the same node in the document. * * This performs a strict equality check (`===`) between the two elements. * * @param {TinyNode} elem - The first DOM element to compare. * @param {TinyNode} otherElem - The second DOM element to compare. * @returns {boolean} `true` if both elements are the same DOM node; otherwise, `false`. */ static isSameDom(elem, otherElem) { return (TinyHtml._preNodeElem(elem, 'isSameDom') === TinyHtml._preNodeElem(otherElem, 'isSameDom')); } /** * Compares two DOM elements to determine if they refer to the same node in the document. * * This performs a strict equality check (`===`) between the two elements. * * @param {TinyNode} elem - The DOM element to compare. * @returns {boolean} `true` if both elements are the same DOM node; otherwise, `false`. */ isSameDom(elem) { return TinyHtml.isSameDom(this, elem); } ////////////////////////////////////////////////////////////////// /** @type {ElementDataStore} */ _data = {}; /** * Internal data selectors for accessing public