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
JavaScript
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