tiny-essentials 1.20.0 → 1.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +5 -0
  2. package/dist/v1/TinyBasicsEs.min.js +1 -1
  3. package/dist/v1/TinyDragger.min.js +1 -1
  4. package/dist/v1/TinyEssentials.min.js +1 -1
  5. package/dist/v1/TinyHtml.min.js +1 -1
  6. package/dist/v1/TinySmartScroller.min.js +1 -1
  7. package/dist/v1/TinyUploadClicker.min.js +1 -1
  8. package/dist/v1/basics/html.cjs +33 -2
  9. package/dist/v1/basics/html.d.mts +14 -4
  10. package/dist/v1/basics/html.mjs +27 -2
  11. package/dist/v1/basics/index.cjs +2 -0
  12. package/dist/v1/basics/index.d.mts +3 -1
  13. package/dist/v1/basics/index.mjs +3 -3
  14. package/dist/v1/basics/simpleMath.cjs +23 -4
  15. package/dist/v1/basics/simpleMath.d.mts +18 -4
  16. package/dist/v1/basics/simpleMath.mjs +22 -4
  17. package/dist/v1/index.cjs +2 -0
  18. package/dist/v1/index.d.mts +3 -1
  19. package/dist/v1/index.mjs +3 -3
  20. package/dist/v1/libs/TinyDragger.cjs +1 -1
  21. package/dist/v1/libs/TinyDragger.mjs +1 -1
  22. package/dist/v1/libs/TinyHtml.cjs +500 -105
  23. package/dist/v1/libs/TinyHtml.d.mts +251 -42
  24. package/dist/v1/libs/TinyHtml.mjs +448 -96
  25. package/dist/v1/libs/TinyIframeEvents.cjs +2 -1
  26. package/dist/v1/libs/TinyNewWinEvents.cjs +4 -2
  27. package/docs/v1/basics/html.md +78 -22
  28. package/docs/v1/basics/simpleMath.md +22 -4
  29. package/docs/v1/libs/TinyHtml.md +268 -6
  30. package/package.json +1 -1
  31. package/dist/v1/ColorSafeStringify.js +0 -235
  32. package/dist/v1/TinyAfterScrollWatcher.js +0 -219
  33. package/dist/v1/TinyBasicsEs.js +0 -9334
  34. package/dist/v1/TinyClipboard.js +0 -459
  35. package/dist/v1/TinyColorConverter.js +0 -617
  36. package/dist/v1/TinyDomReadyManager.js +0 -213
  37. package/dist/v1/TinyDragDropDetector.js +0 -307
  38. package/dist/v1/TinyDragger.js +0 -6569
  39. package/dist/v1/TinyEssentials.js +0 -20792
  40. package/dist/v1/TinyEvents.js +0 -402
  41. package/dist/v1/TinyHtml.js +0 -5545
  42. package/dist/v1/TinyIframeEvents.js +0 -854
  43. package/dist/v1/TinyLevelUp.js +0 -291
  44. package/dist/v1/TinyLocalStorage.js +0 -1440
  45. package/dist/v1/TinyNewWinEvents.js +0 -888
  46. package/dist/v1/TinyNotifications.js +0 -408
  47. package/dist/v1/TinyNotifyCenter.js +0 -493
  48. package/dist/v1/TinyPromiseQueue.js +0 -299
  49. package/dist/v1/TinyRateLimiter.js +0 -611
  50. package/dist/v1/TinySmartScroller.js +0 -7039
  51. package/dist/v1/TinyTextRangeEditor.js +0 -497
  52. package/dist/v1/TinyTimeout.js +0 -233
  53. package/dist/v1/TinyToastNotify.js +0 -441
  54. package/dist/v1/TinyUploadClicker.js +0 -14353
  55. package/dist/v1/UltraRandomMsgGen.js +0 -995
@@ -186,6 +186,15 @@ const __elemCollision = {
186
186
  *
187
187
  * @typedef {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLOptionElement} InputElement
188
188
  */
189
+ /**
190
+ * Represents a parsed HTML element in JSON-like array form.
191
+ *
192
+ * @typedef {[
193
+ * tagName: string, // The tag name of the element (e.g., 'div', 'img')
194
+ * attributes: Record<string, string>, // All element attributes as key-value pairs
195
+ * ...children: (string | HtmlParsed)[] // Text or nested elements
196
+ * ]} HtmlParsed
197
+ */
189
198
  /**
190
199
  * TinyHtml is a utility class that provides static and instance-level methods
191
200
  * for precise dimension and position computations on HTML elements.
@@ -199,11 +208,198 @@ const __elemCollision = {
199
208
  class TinyHtml {
200
209
  /** @typedef {import('../basics/collision.mjs').ObjRect} ObjRect */
201
210
  static Utils = { ...TinyCollision };
211
+ /**
212
+ * Fetches an HTML file from the given URL, parses it to JSON.
213
+ *
214
+ * @param {string | URL | globalThis.Request} url - The URL of the HTML file.
215
+ * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc).
216
+ * @returns {Promise<HtmlParsed[]>} A promise that resolves with the parsed JSON representation of the HTML structure.
217
+ */
218
+ static async fetchHtmlFile(url, ops = { method: 'GET' }) {
219
+ const res = await fetch(url, ops);
220
+ const contentType = res.headers.get('Content-Type') || '';
221
+ if (!res.ok)
222
+ throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
223
+ // Only accept HTML responses
224
+ if (!contentType.includes('text/html')) {
225
+ throw new Error(`Invalid content type: ${contentType} (expected text/html)`);
226
+ }
227
+ const html = await res.text();
228
+ return TinyHtml.htmlToJson(html);
229
+ }
230
+ /**
231
+ * Fetches an HTML file from the given URL, parses it to JSON, then converts it to DOM nodes.
232
+ *
233
+ * @param {string} url - The URL of the HTML file.
234
+ * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc).
235
+ * @returns {Promise<(HTMLElement|Text)[]>} A promise that resolves with the DOM nodes.
236
+ */
237
+ static async fetchHtmlNodes(url, ops) {
238
+ const json = await TinyHtml.fetchHtmlFile(url, ops);
239
+ return TinyHtml.jsonToNodes(json);
240
+ }
241
+ /**
242
+ * Fetches an HTML file from the given URL, parses it to JSON, then converts it to TinyHtml instances.
243
+ *
244
+ * @param {string} url - The URL of the HTML file.
245
+ * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc).
246
+ * @returns {Promise<TinyHtml[]>} A promise that resolves with the TinyHtml instances.
247
+ */
248
+ static async fetchHtmlTinyElems(url, ops) {
249
+ const nodes = await TinyHtml.fetchHtmlNodes(url, ops);
250
+ return TinyHtml.toTinyElm(nodes);
251
+ }
252
+ /**
253
+ * Converts the content of a <template> to an array of HtmlParsed.
254
+ *
255
+ * @param {HTMLTemplateElement} nodes
256
+ * @returns {HtmlParsed[]}
257
+ */
258
+ static templateToJson(nodes) {
259
+ return TinyHtml.htmlToJson([...nodes.content.childNodes]
260
+ .map((node) => node instanceof Element ? node.getHTML() : node instanceof Text ? node.textContent : '')
261
+ .join(''));
262
+ }
263
+ /**
264
+ * Converts the content of a <template> to real DOM nodes.
265
+ *
266
+ * @param {HTMLTemplateElement} nodes
267
+ * @returns {(Element|Text)[]}
268
+ */
269
+ static templateToNodes(nodes) {
270
+ /** @type {(Element|Text)[]} */
271
+ const result = [];
272
+ [...nodes.content.cloneNode(true).childNodes].map((node) => {
273
+ if (!(node instanceof Element) && !(node instanceof Text) && !(node instanceof Comment))
274
+ throw new Error(`Expected only Element nodes in <template>, but found: ${node.constructor.name}`);
275
+ if (!(node instanceof Comment))
276
+ result.push(node);
277
+ });
278
+ return result;
279
+ }
280
+ /**
281
+ * Converts the content of a <template> to an array of TinyHtml elements.
282
+ *
283
+ * @param {HTMLTemplateElement} nodes
284
+ * @returns {TinyHtml[]}
285
+ */
286
+ static templateToTinyElems(nodes) {
287
+ return TinyHtml.toTinyElm(TinyHtml.templateToNodes(nodes));
288
+ }
289
+ /**
290
+ * Parses a full HTML string into a JSON-like structure.
291
+ *
292
+ * @param {string} htmlString - Full HTML markup as a string.
293
+ * @returns {HtmlParsed[]} An array of parsed HTML elements in structured format.
294
+ */
295
+ static htmlToJson(htmlString) {
296
+ const container = document.createElement('div');
297
+ container.innerHTML = htmlString.trim();
298
+ const result = [];
299
+ /**
300
+ * @param {Node} el
301
+ * @returns {*}
302
+ */
303
+ const parseElement = (el) => {
304
+ if (el instanceof Comment)
305
+ return null;
306
+ if (el instanceof Text) {
307
+ const text = el.textContent?.trim();
308
+ return text ? text : null;
309
+ }
310
+ if (!(el instanceof Element))
311
+ return null;
312
+ const tag = el.tagName.toLowerCase();
313
+ /** @type {Record<string, string>} */
314
+ const props = {};
315
+ for (const attr of el.attributes) {
316
+ props[TinyHtml.getPropName(attr.name)] = attr.value;
317
+ }
318
+ const children = Array.from(el.childNodes).map(parseElement).filter(Boolean);
319
+ return children.length > 0 ? [tag, props, ...children] : [tag, props];
320
+ };
321
+ for (const child of container.childNodes) {
322
+ const parsed = parseElement(child);
323
+ if (parsed)
324
+ result.push(parsed);
325
+ }
326
+ container.innerHTML = '';
327
+ return result;
328
+ }
329
+ /**
330
+ * Converts a JSON-like HTML structure back to DOM Elements.
331
+ *
332
+ * @param {HtmlParsed[]} jsonArray - Parsed JSON format of HTML.
333
+ * @returns {(HTMLElement|Text)[]} List of DOM nodes.
334
+ */
335
+ static jsonToNodes(jsonArray) {
336
+ /**
337
+ * @param {HtmlParsed|string} nodeData
338
+ * @returns {HTMLElement|Text}
339
+ */
340
+ const createElement = (nodeData) => {
341
+ if (typeof nodeData === 'string') {
342
+ return document.createTextNode(nodeData);
343
+ }
344
+ if (!Array.isArray(nodeData))
345
+ return document.createTextNode('');
346
+ const [tag, props, ...children] = nodeData;
347
+ const el = document.createElement(tag);
348
+ for (const [key, value] of Object.entries(props)) {
349
+ el.setAttribute(TinyHtml.getAttrName(key), value);
350
+ }
351
+ for (const child of children) {
352
+ const childEl = createElement(child);
353
+ if (childEl instanceof Comment)
354
+ continue;
355
+ el.appendChild(childEl);
356
+ }
357
+ return el;
358
+ };
359
+ return jsonArray.map(createElement).filter((node) => !(node instanceof Comment));
360
+ }
361
+ /**
362
+ * Converts a JSON-like HTML structure back to TinyHtml instances.
363
+ *
364
+ * @param {HtmlParsed[]} jsonArray - Parsed JSON format of HTML.
365
+ * @returns {TinyHtml[]} List of TinyHtml instances.
366
+ */
367
+ static jsonToTinyElems(jsonArray) {
368
+ return TinyHtml.toTinyElm(TinyHtml.jsonToNodes(jsonArray));
369
+ }
370
+ /**
371
+ * Creates a new TinyHtml element from a tag name and optional attributes.
372
+ *
373
+ * You can alias this method for convenience:
374
+ * ```js
375
+ * const createElement = TinyHtml.createFrom;
376
+ * const myDiv = createElement('div', { class: 'box' });
377
+ * ```
378
+ *
379
+ * @param {string} tagName - The HTML tag name (e.g., 'div', 'span', 'button').
380
+ * @param {Record<string, string|null>} [attrs] - Optional key-value pairs representing HTML attributes.
381
+ * If the value is `null`, the attribute will still be set with an empty value.
382
+ * @returns {TinyHtml} - A new instance of TinyHtml representing the created element.
383
+ * @throws {TypeError} - If `tagName` is not a string, or `attrs` is not a plain object when defined.
384
+ */
385
+ static createFrom(tagName, attrs) {
386
+ if (typeof tagName !== 'string')
387
+ throw new TypeError('The "tagName" must be a string.');
388
+ if (typeof attrs !== 'undefined' && typeof attrs !== 'object')
389
+ throw new TypeError('The "attrs" must be a object.');
390
+ const elem = TinyHtml.createElement(tagName);
391
+ if (typeof attrs === 'object') {
392
+ for (const attrName in attrs) {
393
+ elem.setAttr(attrName, attrs[attrName]);
394
+ }
395
+ }
396
+ return elem;
397
+ }
202
398
  /**
203
399
  * Creates a new DOM element with the specified tag name and options, then wraps it in a TinyHtml instance.
204
400
  *
205
401
  * @param {string} tagName - The tag name of the element to create (e.g., 'div', 'span').
206
- * @param {ElementCreationOptions} ops - Optional settings for creating the element.
402
+ * @param {ElementCreationOptions} [ops] - Optional settings for creating the element.
207
403
  * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM element.
208
404
  * @throws {TypeError} If tagName is not a string or ops is not an object.
209
405
  */
@@ -246,7 +442,7 @@ class TinyHtml {
246
442
  }
247
443
  template.innerHTML = htmlString;
248
444
  if (!(template.content.firstChild instanceof Element))
249
- throw new Error('');
445
+ throw new Error('The HTML string must contain a valid HTML element.');
250
446
  return new TinyHtml(template.content.firstChild);
251
447
  }
252
448
  /**
@@ -276,17 +472,16 @@ class TinyHtml {
276
472
  *
277
473
  * @param {string} selector - A valid CSS selector string.
278
474
  * @param {Document|Element} elem - Target element.
279
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
475
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the matched elements.
280
476
  */
281
477
  static queryAll(selector, elem = document) {
282
- const newEls = elem.querySelectorAll(selector);
283
- return TinyHtml.toTinyElm([...newEls]);
478
+ return new TinyHtml(elem.querySelectorAll(selector));
284
479
  }
285
480
  /**
286
481
  * Queries the element for all elements matching the CSS selector and wraps them in TinyHtml instances.
287
482
  *
288
483
  * @param {string} selector - A valid CSS selector string.
289
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
484
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the matched elements.
290
485
  */
291
486
  querySelectorAll(selector) {
292
487
  return TinyHtml.queryAll(selector, TinyHtml._preElem(this, 'queryAll'));
@@ -308,17 +503,16 @@ class TinyHtml {
308
503
  *
309
504
  * @param {string} selector - The class name to search for.
310
505
  * @param {Document|Element} elem - Target element.
311
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
506
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
312
507
  */
313
508
  static getByClassName(selector, elem = document) {
314
- const newEls = elem.getElementsByClassName(selector);
315
- return TinyHtml.toTinyElm([...newEls]);
509
+ return new TinyHtml(elem.getElementsByClassName(selector));
316
510
  }
317
511
  /**
318
512
  * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
319
513
  *
320
514
  * @param {string} selector - The class name to search for.
321
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
515
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
322
516
  */
323
517
  getElementsByClassName(selector) {
324
518
  return TinyHtml.getByClassName(selector, TinyHtml._preElem(this, 'getByClassName'));
@@ -327,11 +521,10 @@ class TinyHtml {
327
521
  * Retrieves all elements with the specified name attribute and wraps them in TinyHtml instances.
328
522
  *
329
523
  * @param {string} selector - The name attribute to search for.
330
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
524
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
331
525
  */
332
526
  static getByName(selector) {
333
- const newEls = document.getElementsByName(selector);
334
- return TinyHtml.toTinyElm([...newEls]);
527
+ return new TinyHtml(document.getElementsByName(selector));
335
528
  }
336
529
  /**
337
530
  * Retrieves all elements with the specified local tag name within the given namespace URI,
@@ -340,11 +533,10 @@ class TinyHtml {
340
533
  * @param {string} localName - The local name (tag) of the elements to search for.
341
534
  * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
342
535
  * @param {Document|Element} elem - Target element.
343
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
536
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
344
537
  */
345
538
  static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml', elem = document) {
346
- const newEls = elem.getElementsByTagNameNS(namespaceURI, localName);
347
- return TinyHtml.toTinyElm([...newEls]);
539
+ return new TinyHtml(elem.getElementsByTagNameNS(namespaceURI, localName));
348
540
  }
349
541
  /**
350
542
  * Retrieves all elements with the specified local tag name within the given namespace URI,
@@ -352,7 +544,7 @@ class TinyHtml {
352
544
  *
353
545
  * @param {string} localName - The local name (tag) of the elements to search for.
354
546
  * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
355
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
547
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
356
548
  */
357
549
  getElementsByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
358
550
  return TinyHtml.getByTagNameNS(localName, namespaceURI, TinyHtml._preElem(this, 'getByTagNameNS'));
@@ -361,81 +553,150 @@ class TinyHtml {
361
553
  /**
362
554
  * Returns the current target held by this instance.
363
555
  *
556
+ * @param {number} index - The index of the element to retrieve.
364
557
  * @returns {ConstructorElValues} - The instance's target element.
365
558
  */
366
- get() {
367
- return this.#el;
559
+ get(index) {
560
+ if (typeof index !== 'number')
561
+ throw new TypeError('The index must be a number.');
562
+ if (!this.#el[index])
563
+ throw new Error(`No element found at index ${index}.`);
564
+ return this.#el[index];
565
+ }
566
+ /**
567
+ * Extracts a single DOM element from the internal list at the specified index.
568
+ *
569
+ * @param {number} index - The index of the element to extract.
570
+ * @returns {TinyHtml} A new TinyHtml instance wrapping the extracted element.
571
+ */
572
+ extract(index) {
573
+ if (typeof index !== 'number')
574
+ throw new TypeError('The index must be a number.');
575
+ if (!this.#el[index])
576
+ throw new Error(`Cannot extract: no element exists at index ${index}.`);
577
+ return new TinyHtml(this.#el[index]);
578
+ }
579
+ /**
580
+ * Checks whether the element exists at the given index.
581
+ *
582
+ * @param {number} index - The index to check.
583
+ * @returns {boolean} - True if the element exists; otherwise, false.
584
+ */
585
+ exists(index) {
586
+ if (typeof index !== 'number')
587
+ throw new TypeError('The index must be a number.');
588
+ if (!this.#el[index])
589
+ return false;
590
+ return true;
591
+ }
592
+ /**
593
+ * Returns the current targets held by this instance.
594
+ *
595
+ * @returns {ConstructorElValues[]} - The instance's targets element.
596
+ */
597
+ getAll() {
598
+ return [...this.#el];
368
599
  }
369
600
  /**
370
601
  * Returns the current Element held by this instance.
371
602
  *
372
603
  * @param {string} where - The method name or context calling this.
604
+ * @param {number} index - The index of the element to retrieve.
373
605
  * @returns {ConstructorElValues} - The instance's element.
374
606
  * @readonly
375
607
  */
376
- _getElement(where) {
377
- if (!(this.#el instanceof Element) &&
378
- !(this.#el instanceof Window) &&
379
- !(this.#el instanceof Document))
608
+ _getElement(where, index) {
609
+ if (!(this.#el[index] instanceof Element) &&
610
+ !(this.#el[index] instanceof Window) &&
611
+ !(this.#el[index] instanceof Document) &&
612
+ !(this.#el[index] instanceof Text))
380
613
  throw new Error(`[TinyHtml] Invalid Element in ${where}().`);
381
- return this.#el;
614
+ return this.#el[index];
615
+ }
616
+ /**
617
+ * Returns the current Elements held by this instance.
618
+ *
619
+ * @param {string} where - The method name or context calling this.
620
+ * @returns {ConstructorElValues[]} - The instance's elements.
621
+ * @readonly
622
+ */
623
+ _getElements(where) {
624
+ if (!this.#el.every((el) => el instanceof Element ||
625
+ el instanceof Window ||
626
+ el instanceof Document ||
627
+ el instanceof Text))
628
+ throw new Error(`[TinyHtml] Invalid Element in ${where}().`);
629
+ return [...this.#el];
382
630
  }
383
631
  //////////////////////////////////////////////////////
384
632
  /**
385
- * @param {TinyElement|EventTarget|null|(TinyElement|EventTarget|null)[]} elems
386
- * @param {string} where
387
- * @param {any[]} TheTinyElements
388
- * @param {string[]} elemName
389
- * @returns {any[]}
633
+ * Prepares and validates multiple elements against allowed types.
634
+ *
635
+ * @param {TinyElement | EventTarget | null | (TinyElement | EventTarget | null)[]} elems - The input elements to validate.
636
+ * @param {string} where - The method name or context calling this.
637
+ * @param {any[]} TheTinyElements - The list of allowed constructors (e.g., Element, Document).
638
+ * @param {string[]} elemName - The list of expected element names for error reporting.
639
+ * @returns {any[]} - A flat array of validated elements.
640
+ * @throws {Error} - If any element is not an instance of one of the allowed types.
390
641
  * @readonly
391
642
  */
392
643
  static _preElemsTemplate(elems, where, TheTinyElements, elemName) {
393
644
  /** @param {(TinyElement|EventTarget|null)[]} item */
394
- const checkElement = (item) => item.map((elem) => {
395
- const result = elem instanceof TinyHtml ? elem._getElement(where) : elem;
396
- let allowed = false;
397
- for (const TheTinyElement of TheTinyElements) {
398
- if (result instanceof TheTinyElement) {
399
- allowed = true;
400
- break;
645
+ const checkElement = (item) => {
646
+ /** @type {any[]} */
647
+ const results = [];
648
+ item.map((elem) => (elem instanceof TinyHtml ? elem._getElements(where) : [elem]).map((result) => {
649
+ let allowed = false;
650
+ for (const TheTinyElement of TheTinyElements) {
651
+ if (result instanceof TheTinyElement) {
652
+ allowed = true;
653
+ break;
654
+ }
401
655
  }
402
- }
403
- if (!allowed)
404
- throw new Error(`[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`);
405
- return result;
406
- });
656
+ if (!allowed)
657
+ throw new Error(`[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`);
658
+ results.push(result);
659
+ return result;
660
+ }));
661
+ return results;
662
+ };
407
663
  if (!Array.isArray(elems))
408
664
  return checkElement([elems]);
409
665
  return checkElement(elems);
410
666
  }
411
667
  /**
412
- * @param {TinyElement|EventTarget|null|(TinyElement|EventTarget|null)[]} elems
413
- * @param {string} where
414
- * @param {any[]} TheTinyElements
415
- * @param {string[]} elemName
416
- * @param {boolean} [canNull=false]
417
- * @returns {any}
668
+ * Prepares and validates a single element against allowed types.
669
+ *
670
+ * @param {TinyElement | EventTarget | null | (TinyElement | EventTarget | null)[]} elems - The input element or list to validate.
671
+ * @param {string} where - The method name or context calling this.
672
+ * @param {any[]} TheTinyElements - The list of allowed constructors (e.g., Element, Document).
673
+ * @param {string[]} elemName - The list of expected element names for error reporting.
674
+ * @param {boolean} [canNull=false] - Whether `null` is allowed as a valid value.
675
+ * @returns {any} - The validated element or `null` if allowed.
676
+ * @throws {Error} - If the element is not valid or if multiple elements are provided.
418
677
  * @readonly
419
678
  */
420
679
  static _preElemTemplate(elems, where, TheTinyElements, elemName, canNull = false) {
421
680
  /** @param {(TinyElement|EventTarget|null)[]} item */
422
681
  const checkElement = (item) => {
423
682
  const elem = item[0];
424
- let result = elem instanceof TinyHtml ? elem._getElement(where) : elem;
683
+ const result = elem instanceof TinyHtml ? elem._getElements(where) : [elem];
684
+ if (result.length > 1)
685
+ throw new Error(`[TinyHtml] Invalid element amount in ${where}() (Received ${result.length}/1).`);
425
686
  let allowed = false;
426
687
  for (const TheTinyElement of TheTinyElements) {
427
- if (result instanceof TheTinyElement) {
688
+ if (result[0] instanceof TheTinyElement) {
428
689
  allowed = true;
429
690
  break;
430
691
  }
431
692
  }
432
- if (canNull && (result === null || typeof result === 'undefined')) {
433
- result = null;
693
+ if (canNull && (result[0] === null || typeof result[0] === 'undefined')) {
694
+ result[0] = null;
434
695
  allowed = true;
435
696
  }
436
697
  if (!allowed)
437
698
  throw new Error(`[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`);
438
- return result;
699
+ return result[0];
439
700
  };
440
701
  if (!Array.isArray(elems))
441
702
  return checkElement([elems]);
@@ -662,11 +923,11 @@ class TinyHtml {
662
923
  * This ensures consistent access to methods of the `TinyHtml` class regardless
663
924
  * of the input form.
664
925
  *
665
- * @param {TinyElement|TinyElement[]} elems - A single element or an array of elements (DOM or TinyHtml).
926
+ * @param {TinyElement|Text|(TinyElement|Text)[]} elems - A single element or an array of elements (DOM or TinyHtml).
666
927
  * @returns {TinyHtml[]} An array of TinyHtml instances corresponding to the input elements.
667
928
  */
668
929
  static toTinyElm(elems) {
669
- /** @param {TinyElement[]} item */
930
+ /** @param {(TinyElement|Text)[]} item */
670
931
  const checkElement = (item) => item.map((elem) => (!(elem instanceof TinyHtml) ? new TinyHtml(elem) : elem));
671
932
  if (!Array.isArray(elems))
672
933
  return checkElement([elems]);
@@ -689,8 +950,13 @@ class TinyHtml {
689
950
  */
690
951
  static fromTinyElm(elems) {
691
952
  /** @param {TinyElement[]} item */
692
- const checkElement = (item) => item.map((elem) =>
693
- /** @type {Element} */ (elem instanceof TinyHtml ? elem._getElement('fromTinyElm') : elem));
953
+ const checkElement = (item) => {
954
+ /** @type {Element[]} */
955
+ const result = [];
956
+ item.map((elem) =>
957
+ /** @type {Element[]} */ (elem instanceof TinyHtml ? elem._getElements('fromTinyElm') : [elem]).map((elem) => result.push(elem)));
958
+ return result;
959
+ };
694
960
  if (!Array.isArray(elems))
695
961
  return checkElement([elems]);
696
962
  return checkElement(elems);
@@ -1452,7 +1718,7 @@ class TinyHtml {
1452
1718
  const targ = TinyHtml._preNodeElem(target, 'insertBefore');
1453
1719
  const childNode = TinyHtml._preNodeElemWithNull(child, 'insertBefore');
1454
1720
  if (!targ.parentNode)
1455
- throw new Error('');
1721
+ throw new Error('The target element has no parent node to insert before.');
1456
1722
  targ.parentNode.insertBefore(elem, childNode || targ);
1457
1723
  return el;
1458
1724
  }
@@ -1479,7 +1745,7 @@ class TinyHtml {
1479
1745
  const targ = TinyHtml._preNodeElem(target, 'insertBefore');
1480
1746
  const childNode = TinyHtml._preNodeElemWithNull(child, 'insertBefore');
1481
1747
  if (!targ.parentNode)
1482
- throw new Error('');
1748
+ throw new Error('The target element has no parent node to insert after.');
1483
1749
  targ.parentNode.insertBefore(elem, childNode || targ.nextSibling);
1484
1750
  return el;
1485
1751
  }
@@ -1526,23 +1792,48 @@ class TinyHtml {
1526
1792
  //////////////////////////////////////////////////////
1527
1793
  /**
1528
1794
  * The target HTML element for instance-level operations.
1529
- * @type {ConstructorElValues}
1795
+ * @type {ConstructorElValues[]}
1530
1796
  */
1531
1797
  #el;
1798
+ /**
1799
+ * Returns the number of elements currently stored in the internal element list.
1800
+ *
1801
+ * @returns {number} The total count of elements.
1802
+ */
1803
+ get size() {
1804
+ return this.#el.length;
1805
+ }
1532
1806
  /**
1533
1807
  * Creates an instance of TinyHtml for a specific Element.
1534
1808
  * Useful when you want to operate repeatedly on the same element using instance methods.
1535
- * @param {ConstructorElValues} el - The element to wrap and manipulate.
1809
+ * @param {ConstructorElValues|ConstructorElValues[]|NodeListOf<Element>|HTMLCollectionOf<Element>|NodeListOf<HTMLElement>} el - The element to wrap and manipulate.
1536
1810
  */
1537
1811
  constructor(el) {
1538
1812
  if (el instanceof TinyHtml)
1539
1813
  throw new Error(`[TinyHtml] You are trying to put a TinyHtml inside another TinyHtml in constructor.`);
1540
- if (!(el instanceof Element) &&
1541
- !(el instanceof Window) &&
1542
- !(el instanceof Document) &&
1543
- !(el instanceof Text))
1544
- throw new Error(`[TinyHtml] Invalid Target in constructor.`);
1545
- this.#el = el;
1814
+ /** @param {any[]} els */
1815
+ const elCheck = (els) => {
1816
+ if (!els.every((el) => el instanceof Element ||
1817
+ el instanceof Window ||
1818
+ el instanceof Document ||
1819
+ el instanceof Text))
1820
+ throw new Error(`[TinyHtml] Invalid Target in constructor.`);
1821
+ };
1822
+ if (Array.isArray(el)) {
1823
+ elCheck(el);
1824
+ this.#el = el;
1825
+ }
1826
+ else if (el instanceof NodeList || el instanceof HTMLCollection) {
1827
+ const els = [...el];
1828
+ elCheck(els);
1829
+ this.#el = els;
1830
+ }
1831
+ else {
1832
+ const els = [el];
1833
+ elCheck(els);
1834
+ // @ts-ignore
1835
+ this.#el = els;
1836
+ }
1546
1837
  }
1547
1838
  /**
1548
1839
  * Checks whether the given object is a window.
@@ -1932,7 +2223,13 @@ class TinyHtml {
1932
2223
  msTransform: '-ms-transform',
1933
2224
  msTransition: '-ms-transition',
1934
2225
  };
1935
- /** @type {Record<string | symbol, string>} */
2226
+ /**
2227
+ * Public proxy to manage camelCase ➝ kebab-case CSS property aliasing.
2228
+ *
2229
+ * Modifying this object ensures that the reverse mapping in `cssPropRevAliases` is updated accordingly.
2230
+ *
2231
+ * @type {Record<string | symbol, string>}
2232
+ */
1936
2233
  static cssPropAliases = new Proxy(TinyHtml.#cssPropAliases, {
1937
2234
  set(target, camelCaseKey, kebabValue) {
1938
2235
  target[camelCaseKey] = kebabValue;
@@ -1941,7 +2238,13 @@ class TinyHtml {
1941
2238
  return true;
1942
2239
  },
1943
2240
  });
1944
- /** @type {Record<string | symbol, string>} */
2241
+ /**
2242
+ * Reverse map of `cssPropAliases`, mapping kebab-case back to camelCase CSS property names.
2243
+ *
2244
+ * This enables consistent bidirectional lookups of style properties.
2245
+ *
2246
+ * @type {Record<string | symbol, string>}
2247
+ */
1945
2248
  static cssPropRevAliases = Object.fromEntries(Object.entries(TinyHtml.#cssPropAliases).map(([camel, kebab]) => [kebab, camel]));
1946
2249
  /**
1947
2250
  * Converts a camelCase string to kebab-case
@@ -3410,7 +3713,7 @@ class TinyHtml {
3410
3713
  throw new TypeError('The "type" must be a string.');
3411
3714
  if (typeof where !== 'string')
3412
3715
  throw new TypeError('The "where" must be a string.');
3413
- if (!(elem instanceof HTMLInputElement))
3716
+ if (!(elem instanceof HTMLInputElement) && !(elem instanceof HTMLTextAreaElement))
3414
3717
  throw new Error(`Provided element is not an HTMLInputElement in ${where}().`);
3415
3718
  if (typeof TinyHtml._valTypes[type] !== 'function')
3416
3719
  throw new Error(`No handler found for type "${type}" in ${where}().`);
@@ -3923,13 +4226,70 @@ class TinyHtml {
3923
4226
  }
3924
4227
  ///////////////////////////////////////////////////////////////
3925
4228
  /**
3926
- * Property name normalization similar to jQuery's propFix.
3927
- * @readonly
4229
+ * Internal property name normalization map (similar to jQuery's `propFix`).
4230
+ * Maps attribute-like names to their JavaScript DOM property equivalents.
4231
+ *
4232
+ * Example: `'for'` ➝ `'htmlFor'`, `'class'` ➝ `'className'`.
4233
+ *
4234
+ * ⚠️ Do not modify this object directly. Use `TinyHtml.propFix` to ensure reverse mapping (`attrFix`) remains in sync.
4235
+ *
4236
+ * @type {Record<string | symbol, string>}
3928
4237
  */
3929
- static _propFix = {
4238
+ static #propFix = {
3930
4239
  for: 'htmlFor',
3931
4240
  class: 'className',
3932
4241
  };
4242
+ /**
4243
+ * Public proxy for normalized DOM property names.
4244
+ *
4245
+ * Setting a new entry here will also automatically update the reverse map in `TinyHtml.attrFix`.
4246
+ *
4247
+ * @type {Record<string | symbol, string>}
4248
+ */
4249
+ static propFix = new Proxy(TinyHtml.#propFix, {
4250
+ set(target, val1, val2) {
4251
+ target[val1] = val2;
4252
+ // @ts-ignore
4253
+ TinyHtml.attrFix[val2] = val1;
4254
+ return true;
4255
+ },
4256
+ });
4257
+ /**
4258
+ * Reverse map of `propFix`, mapping property names back to their attribute equivalents.
4259
+ *
4260
+ * Used when converting DOM property names into HTML attribute names.
4261
+ *
4262
+ * @type {Record<string | symbol, string>}
4263
+ */
4264
+ static attrFix = Object.fromEntries(Object.entries(TinyHtml.#propFix).map(([val1, val2]) => [val2, val1]));
4265
+ /**
4266
+ * Normalizes an attribute name to its corresponding DOM property name.
4267
+ *
4268
+ * Example: `'class'` ➝ `'className'`, `'for'` ➝ `'htmlFor'`.
4269
+ * If the name is not mapped, it returns the original name.
4270
+ *
4271
+ * @param {string} name - The attribute name to normalize.
4272
+ * @returns {string} - The corresponding property name.
4273
+ */
4274
+ static getPropName(name) {
4275
+ // @ts-ignore
4276
+ const propName = typeof TinyHtml.propFix[name] === 'string' ? TinyHtml.propFix[name] : name;
4277
+ return propName;
4278
+ }
4279
+ /**
4280
+ * Converts a DOM property name back to its corresponding attribute name.
4281
+ *
4282
+ * Example: `'className'` ➝ `'class'`, `'htmlFor'` ➝ `'for'`.
4283
+ * If the name is not mapped, it returns the original name.
4284
+ *
4285
+ * @param {string} name - The property name to convert.
4286
+ * @returns {string} - The corresponding attribute name.
4287
+ */
4288
+ static getAttrName(name) {
4289
+ // @ts-ignore
4290
+ const propName = typeof TinyHtml.attrFix[name] === 'string' ? TinyHtml.attrFix[name] : name;
4291
+ return propName;
4292
+ }
3933
4293
  /**
3934
4294
  * Get an attribute on an element.
3935
4295
  * @param {TinyElement} el
@@ -3940,7 +4300,7 @@ class TinyHtml {
3940
4300
  if (typeof name !== 'string')
3941
4301
  throw new TypeError('The "name" must be a string.');
3942
4302
  const elem = TinyHtml._preElem(el, 'attr');
3943
- return elem.getAttribute(name);
4303
+ return elem.getAttribute(TinyHtml.getAttrName(name));
3944
4304
  }
3945
4305
  /**
3946
4306
  * Get an attribute on an element.
@@ -3964,9 +4324,9 @@ class TinyHtml {
3964
4324
  throw new TypeError('The "value" must be a string.');
3965
4325
  TinyHtml._preElems(el, 'setAttr').forEach((elem) => {
3966
4326
  if (value === null)
3967
- elem.removeAttribute(name);
4327
+ elem.removeAttribute(TinyHtml.getAttrName(name));
3968
4328
  else
3969
- elem.setAttribute(name, value);
4329
+ elem.setAttribute(TinyHtml.getAttrName(name), value);
3970
4330
  });
3971
4331
  return el;
3972
4332
  }
@@ -3988,7 +4348,7 @@ class TinyHtml {
3988
4348
  static removeAttr(el, name) {
3989
4349
  if (typeof name !== 'string')
3990
4350
  throw new TypeError('The "name" must be a string.');
3991
- TinyHtml._preElems(el, 'removeAttr').forEach((elem) => elem.removeAttribute(name));
4351
+ TinyHtml._preElems(el, 'removeAttr').forEach((elem) => elem.removeAttribute(TinyHtml.getAttrName(name)));
3992
4352
  return el;
3993
4353
  }
3994
4354
  /**
@@ -4009,7 +4369,7 @@ class TinyHtml {
4009
4369
  if (typeof name !== 'string')
4010
4370
  throw new TypeError('The "name" must be a string.');
4011
4371
  const elem = TinyHtml._preElem(el, 'hasAttr');
4012
- return elem.hasAttribute(name);
4372
+ return elem.hasAttribute(TinyHtml.getAttrName(name));
4013
4373
  }
4014
4374
  /**
4015
4375
  * Check if an attribute exists on an element.
@@ -4030,9 +4390,7 @@ class TinyHtml {
4030
4390
  throw new TypeError('The "name" must be a string.');
4031
4391
  const elem = TinyHtml._preElem(el, 'hasProp');
4032
4392
  // @ts-ignore
4033
- const propName = TinyHtml._propFix[name] || name;
4034
- // @ts-ignore
4035
- return !!elem[propName];
4393
+ return !!elem[TinyHtml.getPropName(name)];
4036
4394
  }
4037
4395
  /**
4038
4396
  * Check if a property exists.
@@ -4053,9 +4411,7 @@ class TinyHtml {
4053
4411
  throw new TypeError('The "name" must be a string.');
4054
4412
  TinyHtml._preElems(el, 'addProp').forEach((elem) => {
4055
4413
  // @ts-ignore
4056
- name = TinyHtml._propFix[name] || name;
4057
- // @ts-ignore
4058
- elem[name] = true;
4414
+ elem[TinyHtml.getPropName(name)] = true;
4059
4415
  });
4060
4416
  return el;
4061
4417
  }
@@ -4078,9 +4434,7 @@ class TinyHtml {
4078
4434
  throw new TypeError('The "name" must be a string.');
4079
4435
  TinyHtml._preElems(el, 'removeProp').forEach((elem) => {
4080
4436
  // @ts-ignore
4081
- name = TinyHtml._propFix[name] || name;
4082
- // @ts-ignore
4083
- elem[name] = false;
4437
+ elem[TinyHtml.getPropName(name)] = false;
4084
4438
  });
4085
4439
  return el;
4086
4440
  }
@@ -4105,9 +4459,7 @@ class TinyHtml {
4105
4459
  throw new TypeError('The "force" must be a boolean.');
4106
4460
  TinyHtml._preElems(el, 'toggleProp').forEach((elem) => {
4107
4461
  // @ts-ignore
4108
- const propName = TinyHtml._propFix[name] || name;
4109
- // @ts-ignore
4110
- const shouldEnable = force === undefined ? !elem[propName] : force;
4462
+ const shouldEnable = force === undefined ? !elem[TinyHtml.getPropName(name)] : force;
4111
4463
  // @ts-ignore
4112
4464
  if (shouldEnable)
4113
4465
  TinyHtml.addProp(elem, name);
@@ -4200,20 +4552,20 @@ class TinyHtml {
4200
4552
  result[name] = rect[name];
4201
4553
  }
4202
4554
  if (typeof extraRect !== 'object' || extraRect === null || Array.isArray(extraRect))
4203
- throw new Error('');
4555
+ throw new Error('extraRect must be a non-null object.');
4204
4556
  const { height = 0, width = 0, top = 0, bottom = 0, left = 0, right = 0 } = extraRect;
4205
4557
  if (typeof height !== 'number')
4206
- throw new Error('');
4558
+ throw new Error('extraRect.height must be a number.');
4207
4559
  if (typeof width !== 'number')
4208
- throw new Error('');
4560
+ throw new Error('extraRect.width must be a number.');
4209
4561
  if (typeof top !== 'number')
4210
- throw new Error('');
4562
+ throw new Error('extraRect.top must be a number.');
4211
4563
  if (typeof bottom !== 'number')
4212
- throw new Error('');
4564
+ throw new Error('extraRect.bottom must be a number.');
4213
4565
  if (typeof left !== 'number')
4214
- throw new Error('');
4566
+ throw new Error('extraRect.left must be a number.');
4215
4567
  if (typeof right !== 'number')
4216
- throw new Error('');
4568
+ throw new Error('extraRect.right must be a number.');
4217
4569
  // @ts-ignore
4218
4570
  result.height += height;
4219
4571
  // @ts-ignore