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,447 lines (1,288 loc) 273 kB
'use strict'; var array = require('../basics/array.cjs'); var text = require('../basics/text.cjs'); var collision = require('../basics/collision.cjs'); var TinyElementObserver = require('./TinyElementObserver.cjs'); // TITLE: Introduction const { areElsColliding, areElsPerfColliding, areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, } = collision; /** * Represents a TinyHtml instance with any constructor element values. * @typedef {TinyHtml<ConstructorElValues>} TinyHtmlAny */ /** * Represents the valid values that can be appended to a TinyNode. * * @typedef {TinyNode | TinyNode[] | string | false | null | undefined} AppendCheckerTemplate */ /** * Represents the the collection of values that can be appended to a TinyNode. * * @typedef {AppendCheckerTemplate|AppendCheckerTemplate[]} AppendCheckerValues */ /** * Callback function used for hover events. * @callback HoverEventCallback * @param {MouseEvent} ev - The mouse event triggered on enter or leave. * @returns {void} Returns nothing. */ /** * Represents a collection of active style-based animations. * * Each HTMLElement is associated with an array of its currently running * `Animation` objects (from the Web Animations API). * * @typedef {Map<HTMLElement, Animation|null>} StyleFxResult */ /** * Represents a collection of animation keyframe data mapped by CSS property. * * - The **key** is the CSS property name (e.g. `"height"`, `"opacity"`). * - The **value** is an array of values representing the start and end * states of the property during the animation. * * @typedef {Record<string, (string|number)[]>} AnimationSfxData */ /** * Function signature for style effects repeat detectors. * * @typedef {(effects: AnimationSfxData) => boolean} StyleEffectsRdFn */ /** * Function signature for style effect property handlers. * * @typedef {( * el: HTMLElement, * keyframes: AnimationSfxData, * prop: string, * style: CSSStyleDeclaration * ) => void} StyleEffectsFn */ /** * A collection of style effect property handlers. * * @typedef {Record<string, StyleEffectsFn>} StyleEffectsProps */ /** * 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|TinyHtmlAny|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|TinyHtmlAny} 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|TinyHtmlAny} 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|TinyHtmlAny} 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|TinyHtmlAny} 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|TinyHtmlAny} 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|TinyHtmlAny} 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|TinyHtmlAny} 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(); /** * Internal storage for animation-related data, associated with elements. * Used to remember original dimensions (height/width) and other properties * so that animations like `slideUp` and `slideDown` can restore or continue * smoothly, mimicking jQuery's behavior. * * Each element is mapped to a plain object with keys such as `origHeight`, * `origWidth`, etc. * * @type {WeakMap<HTMLElement, Record<string, string|number>>} */ const __elementAnimateData = new WeakMap(); /** * Stores the currently active animation for each element, * allowing cancellation or replacement of ongoing animations. * * @type {WeakMap<HTMLElement, { animation: Animation, id: string }>} */ const __elementCurrentAnimation = new WeakMap(); /** * Mapping of animation shortcuts to their effect definitions. * Similar to jQuery's predefined effects (slideDown, fadeIn, etc.). * * @typedef {Record<string, string|(string|number)[]>} StyleEffects */ /** * 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 */ // TITLE: Class Intro /** * Represents all possible inputs accepted by the TinyHtml constructor. * Can be a single element, an array of elements, array-like DOM collections, * or a CSS selector string. * * @typedef {( * ConstructorElValues | * ConstructorElValues[] | * NodeListOf<Element> | * HTMLCollectionOf<Element> | * NodeListOf<HTMLElement> | * string * )} TinyHtmlConstructor */ /** * 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. * * @template {TinyHtmlConstructor} TinyHtmlT * @class */ class TinyHtml { /** @typedef {import('../basics/collision.mjs').ObjRect} ObjRect */ static Utils = { ...collision }; /** @type {number} */ static #version = 1; /** @returns {number} */ static get version() { return this.#version; } /** * Controls whether TinyHtml emits detailed debug output to the console. * When enabled, helper methods print structured diagnostics for easier troubleshooting. * @type {boolean} */ static #elemDebug = false; /** * Gets whether debug output is enabled. * @returns {boolean} True if debug output is enabled; otherwise, false. */ static get elemDebug() { return TinyHtml.#elemDebug; } /** * Enables or disables debug output. * @param {boolean} value True to enable debug output; false to disable. * @throws {TypeError} Thrown if the provided value is not a boolean. * @returns {void} */ static set elemDebug(value) { if (typeof value !== 'boolean') throw new TypeError('Expected a boolean value for elemDebug'); TinyHtml.#elemDebug = value; } /** * Logs a standardized debug error for element validation, including a console.table * with the involved elements. Use this when an element argument is invalid, unexpected, * or fails a guard/validation. * * The output includes: * - A concise error header * - A stack trace (when supported) * - A table of elements (index, typeof, constructor, summary, value) * - The specific problematic element, if provided * * @param {(ConstructorElValues | EventTarget | TinyElement | null)[]} elems * A list of elements participating in the operation (may include nulls). * @param {ConstructorElValues | EventTarget | TinyElement | null} [elem] * The specific element that triggered the error, if available. * @returns {void} */ static _debugElemError(elems, elem) { if (!TinyHtml.#elemDebug) return; const header = '[TinyHtml Debug] Element validation error'; console.groupCollapsed(`${header}${elem ? ' — details below' : ''}`); console.error(header); // Stack trace for call-site visibility if (typeof Error !== 'undefined' && typeof Error.captureStackTrace === 'function') { const err = new Error(header); Error.captureStackTrace(err, TinyHtml._debugElemError); console.error(err.stack); } else { console.trace(header); } // Tabular overview of provided elements if (Array.isArray(elems)) { const rows = elems.map((el, index) => { const typeOf = el === null ? 'null' : typeof el; const ctor = el?.constructor?.name ?? (el === null ? 'null' : 'primitive'); const isDomEl = typeof Element !== 'undefined' && el instanceof Element; const summary = isDomEl ? `${el.tagName?.toLowerCase?.() ?? 'element'}#${el.id || ''}.${String(el.className || '') .trim() .replace(/\s+/g, '.')}` : el && typeof el === 'object' && 'nodeType' in el ? `nodeType:${/** @type {any} */ (el).nodeType}` : ''; return { index, typeOf, constructor: ctor, summary, value: el }; }); console.table(rows); } else { console.warn('[TinyHtml Debug] "elems" is not an array:', elems); } // Highlight the specific problematic element, if any if (arguments.length > 1) { console.error('[TinyHtml Debug] Problematic element:', elem); if (elem && typeof elem === 'object') console.dir(elem); } console.groupEnd(); } /** * Parse inline styles into an object. * @param {string} styleText * @returns {Record<string,string>} */ static parseStyle(styleText) { /** @type {Record<string,string>}} */ const styles = {}; styleText.split(';').forEach((rule) => { const [prop, value] = rule.split(':').map((s) => s && s.trim()); if (prop && value) { styles[prop] = value; } }); return styles; } ///////////////////////////////////////////////////////////////////// /** * Flag to determine if element observer should start automatically. * @type {boolean} */ static #autoStartElemObserver = true; /** * Get the auto-start flag for the observer. * @returns {boolean} */ static get autoStartElemObserver() { return TinyHtml.#autoStartElemObserver; } /** * Set the auto-start flag for the observer. * @param {boolean} value */ static set autoStartElemObserver(value) { if (typeof value !== 'boolean') throw new TypeError('autoStartElemObserver must be a boolean.'); TinyHtml.#autoStartElemObserver = value; } /** @type {TinyElementObserver} */ static #tinyObserver = new TinyElementObserver({ el: typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document.documentElement : undefined, initDetectors: [ // Style Detector [ 'tinyStyleEvent', (mutation) => { if ( mutation.type !== 'attributes' || mutation.attributeName !== 'style' || !(mutation.target instanceof HTMLElement) ) return; const oldVal = mutation.oldValue || ''; const newVal = mutation.target.getAttribute('style') || ''; const oldStyles = TinyHtml.parseStyle(oldVal); const newStyles = TinyHtml.parseStyle(newVal); const changes = text.diffStrings(oldStyles, newStyles); if ( Object.keys(changes.added).length || Object.keys(changes.removed).length || Object.keys(changes.modified).length ) { mutation.target.dispatchEvent( new CustomEvent('tinyhtml.stylechanged', { detail: changes, }), ); } }, ], // Class Detector [ 'tinyClassEvent', (mutation) => { if ( mutation.type !== 'attributes' || mutation.attributeName !== 'class' || !(mutation.target instanceof HTMLElement) ) return; const oldVal = mutation.oldValue || ''; const newVal = mutation.target.className || ''; const oldClasses = oldVal.split(/\s+/).filter(Boolean); const newClasses = newVal.split(/\s+/).filter(Boolean); const changes = array.diffArrayList(oldClasses, newClasses); if (changes.added.length || changes.removed.length) { mutation.target.dispatchEvent( new CustomEvent('tinyhtml.classchanged', { detail: changes, }), ); } }, ], ], initCfg: { attributeOldValue: true, attributes: true, subtree: true, attributeFilter: ['style', 'class'], }, }); /** @returns {TinyElementObserver} */ static get tinyObserver() { return TinyHtml.#tinyObserver; } /////////////////////////////////////////////////////////////////// // TITLE: Fetch Html List /** * 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<TinyElement|Text>[]>} A promise that resolves with the TinyHtml instances. */ static async fetchHtmlTinyElems(url, ops) { const nodes = await TinyHtml.fetchHtmlNodes(url, ops); return TinyHtml.toTinyElm(nodes); } // TITLE: Fetch Template List /** * 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<Element|Text>[]} */ static templateToTinyElems(nodes) { return TinyHtml.toTinyElm(TinyHtml.templateToNodes(nodes)); } // TITLE: Fetch Json List /** * 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<HTMLElement | Text>[]} List of TinyHtml instances. */ static jsonToTinyElems(jsonArray) { return TinyHtml.toTinyElm(TinyHtml.jsonToNodes(jsonArray)); } // TITLE: Element Creator /** * 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|number|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<HTMLElement>} - 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<HTMLElement>} 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<Text>} 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)); } /** * @deprecated Use the {@link createFromHTML} instead. * * 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<Element>} - 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); } /** * 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<Element>} - A single HTMLElement or TextNode. */ static createFromHTML(htmlString) { const template = document.createElement('template'); htmlString = htmlString.trim(); template.innerHTML = htmlString; const elems = Array.from(template.content.childNodes); if (!elems.every((item) => item instanceof Element || item instanceof Text)) throw new Error('The HTML string must contain a valid HTML element.'); return new TinyHtml(elems); } /** * 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<Element>} - A single HTMLElement or TextNode. */ static createFromHtml(htmlString) { return TinyHtml.createFromHTML(htmlString); } /////////////////////////////////////////////////// // TITLE: Query Script /** * 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<Element>|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<Element>|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<NodeListOf<Element>>} 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<NodeListOf<Element>>} 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<HTMLElement>|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<HTMLCollectionOf<Element>>} 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<HTMLCollectionOf<Element>>} 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<NodeListOf<HTMLElement>>} 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<HTMLCollectionOf<Element>>} 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<HTMLCollectionOf<Element>>} 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'), ); } ////////////////////////////////////////////////////////////////// // TITLE: Element getter /** * Returns the current targets held by this instance. * * @returns {ConstructorElValues[]} - The instance's targets element. */ get elements() { return [...this.#el]; } /** * Iterates over all elements, executing the provided callback on each. * @param {(element: TinyHtmlAny, index: number, items: TinyHtmlAny[]) => void} callback - Function invoked for each element. * @returns {this} The current instance for chaining. */ forEach(callback) { const elems = this.elements.map((el, index) => this.extract(index)); for (const index in elems) callback(elems[index], Number(index), elems); return this; } /** * 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 {TinyHtmlAny} 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; } /** * @deprecated Use the getter {@link elements} instead. * Returns the current targets held by this instance. * * @returns {ConstructorElValues[]} - The instance's targets element. */ getAll() { return [...this.#el]; } //////////////////////////////////////////////// // TITLE: Element Getter (Private) (Pt1) /** * 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. */ _getElement(where, index) { if ( !(this.#el[index] instanceof Element) && !(this.#el[index] instanceof Window) && !(this.#el[index] instanceof Document) && !(this.#el[index] instanceof Text) ) { TinyHtml._debugElemError([...this.#el], this.#el[index]); 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. */ _getElements(where) { if ( !this.#el.every( (el) => el instanceof Element || el instanceof Window || el instanceof Document || el instanceof Text, ) ) { TinyHtml._debugElemError([...this.#el]); throw new Error(`[TinyHtml] Invalid Element in ${where}().`); } return [...this.#el]; } ////////////////////////////////////////////////////// // TITLE: Element Getter (Private) (Pt2) /** * 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. */ 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) { TinyHtml._debugElemError([...item], result); 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 multiple elements against allowed types. * * @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. */ _preElemsTemplate(where, TheTinyElements, elemName) { return TinyHtml._preElemsTemplate(this, where, TheTinyElements, elemName); } /** * 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. */ 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) { TinyHtml._debugElemError([...item]); 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) { TinyHtml._debugElemError([...item], result[0]); 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) { TinyHtml._debugElemError([...elems]); throw new Error( `[TinyHtml] Invalid element amount in ${where}() (Received ${elems.length}/1).`, ); } return checkElement(elems); } /** * Prepares and validates a single element against allowed types. * * @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. */ _preElemTemplate(where, TheTinyElements, elemName, canNull = false) { return TinyHtml._preElemTemplate(this, where, TheTinyElements, elemName, canNull); } /** * 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. */ static _preElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [Element], ['Element']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single elements. * * @param {string} where - The method or context name where validation is being called. * @returns {Element[]} - Always returns an array of elements. */ _preElems(where) { return TinyHtml._preElems(this, where); } /** * 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. */ static _preElem(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Element], ['Element']); } /** * Ensures the input is returned as an single element. * Useful to normalize operations across multiple or single elements. * * @param {string} where - The method or context name where validation is being called. * @returns {Element} - Always returns an single element. */ _preElem(where) { return TinyHtml._preElem(this, where); } /** * 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. */ static _preNodeElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [Node], ['Node']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single nodes. * * @param {string} where - The method or context name where validation is being called. * @returns {Node[]} - Always returns an array of nodes. */ _preNodeElems(where) { return TinyHtml._preNodeElems(this, where); } /** * 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. */ 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 {string} where - The method or context name where validation is being called. * @returns {Node} - Always returns an single node. */ _preNodeElem(where) { return TinyHtml._preNodeElem(this, where); } /** * 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. */ static _preNodeElemWithNull(elems, where) { return TinyHtml._preElemTemplate(elems, where, [Node], ['Node'], true); } /** * Ensures the input is returned as an single node. * Useful to normalize operations across multiple or single nodes. * * @param {string} where - The method or context name where validation is being called. * @returns {Node|null} - Always returns an single node or null. */ _preNodeElemWithNull(where) { return TinyHtml._preNodeElemWithNull(this, where); } /** * 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. */ static _preHtmlElems(elems, where) { return TinyHtml._preElemsTemplate(elems, where, [HTMLElement], ['HTMLElement']); } /** * Ensures the input is returned as an array. * Useful to normalize operations across multiple or single html elements. * * @param {string} where - The method or context name where validation is being called. * @returns {HTMLElement[]} - Always returns an array of html elements. */ _preHtmlElems(where) { return TinyHtml._preHtmlElems(this, where); } /** * 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. */ static _preHtmlElem(elems, where) { return TinyHtml._preElemTemplate(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 {string} where - The method or context name where validation is being called. * @returns {HTMLElement} - Always returns an single html element. */ _preHtmlElem(where) { return TinyHtml._preHtmlElem(this, where); } /** * 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. */ static _preInputElems(elems, where) { return TinyHtml._preElemsTemplate( 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 {string} where - The method or context name where validation is being called. * @returns {InputElement[]} - Always returns an array of event target elements. */ _preInputElems(where) { return TinyHtml._preInputElems(this, where); } /** * 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