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,203 lines 295 kB
import { diffArrayList } from '../basics/array.mjs'; import { diffStrings } from '../basics/text.mjs'; import * as TinyCollision from '../basics/collision.mjs'; import TinyElementObserver from './TinyElementObserver.mjs'; // TITLE: Introduction const { areElsColliding, areElsPerfColliding, areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, } = TinyCollision; /** * 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 = { ...TinyCollision }; /** @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 = 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 = 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], ['HT