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
@@ -225,6 +225,16 @@ const __elemCollision = {
225
225
  * @typedef {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLOptionElement} InputElement
226
226
  */
227
227
 
228
+ /**
229
+ * Represents a parsed HTML element in JSON-like array form.
230
+ *
231
+ * @typedef {[
232
+ * tagName: string, // The tag name of the element (e.g., 'div', 'img')
233
+ * attributes: Record<string, string>, // All element attributes as key-value pairs
234
+ * ...children: (string | HtmlParsed)[] // Text or nested elements
235
+ * ]} HtmlParsed
236
+ */
237
+
228
238
  /**
229
239
  * TinyHtml is a utility class that provides static and instance-level methods
230
240
  * for precise dimension and position computations on HTML elements.
@@ -240,11 +250,221 @@ class TinyHtml {
240
250
 
241
251
  static Utils = { ...collision };
242
252
 
253
+ /**
254
+ * Fetches an HTML file from the given URL, parses it to JSON.
255
+ *
256
+ * @param {string | URL | globalThis.Request} url - The URL of the HTML file.
257
+ * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc).
258
+ * @returns {Promise<HtmlParsed[]>} A promise that resolves with the parsed JSON representation of the HTML structure.
259
+ */
260
+ static async fetchHtmlFile(url, ops = { method: 'GET' }) {
261
+ const res = await fetch(url, ops);
262
+
263
+ const contentType = res.headers.get('Content-Type') || '';
264
+ if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
265
+
266
+ // Only accept HTML responses
267
+ if (!contentType.includes('text/html')) {
268
+ throw new Error(`Invalid content type: ${contentType} (expected text/html)`);
269
+ }
270
+
271
+ const html = await res.text();
272
+ return TinyHtml.htmlToJson(html);
273
+ }
274
+
275
+ /**
276
+ * Fetches an HTML file from the given URL, parses it to JSON, then converts it to DOM nodes.
277
+ *
278
+ * @param {string} url - The URL of the HTML file.
279
+ * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc).
280
+ * @returns {Promise<(HTMLElement|Text)[]>} A promise that resolves with the DOM nodes.
281
+ */
282
+ static async fetchHtmlNodes(url, ops) {
283
+ const json = await TinyHtml.fetchHtmlFile(url, ops);
284
+ return TinyHtml.jsonToNodes(json);
285
+ }
286
+
287
+ /**
288
+ * Fetches an HTML file from the given URL, parses it to JSON, then converts it to TinyHtml instances.
289
+ *
290
+ * @param {string} url - The URL of the HTML file.
291
+ * @param {RequestInit} [ops] - Optional fetch configuration (e.g., method, headers, cache, etc).
292
+ * @returns {Promise<TinyHtml[]>} A promise that resolves with the TinyHtml instances.
293
+ */
294
+ static async fetchHtmlTinyElems(url, ops) {
295
+ const nodes = await TinyHtml.fetchHtmlNodes(url, ops);
296
+ return TinyHtml.toTinyElm(nodes);
297
+ }
298
+
299
+ /**
300
+ * Converts the content of a <template> to an array of HtmlParsed.
301
+ *
302
+ * @param {HTMLTemplateElement} nodes
303
+ * @returns {HtmlParsed[]}
304
+ */
305
+ static templateToJson(nodes) {
306
+ return TinyHtml.htmlToJson(
307
+ [...nodes.content.childNodes]
308
+ .map((node) =>
309
+ node instanceof Element ? node.getHTML() : node instanceof Text ? node.textContent : '',
310
+ )
311
+ .join(''),
312
+ );
313
+ }
314
+
315
+ /**
316
+ * Converts the content of a <template> to real DOM nodes.
317
+ *
318
+ * @param {HTMLTemplateElement} nodes
319
+ * @returns {(Element|Text)[]}
320
+ */
321
+ static templateToNodes(nodes) {
322
+ /** @type {(Element|Text)[]} */
323
+ const result = [];
324
+ [...nodes.content.cloneNode(true).childNodes].map((node) => {
325
+ if (!(node instanceof Element) && !(node instanceof Text) && !(node instanceof Comment))
326
+ throw new Error(
327
+ `Expected only Element nodes in <template>, but found: ${node.constructor.name}`,
328
+ );
329
+ if (!(node instanceof Comment)) result.push(node);
330
+ });
331
+ return result;
332
+ }
333
+
334
+ /**
335
+ * Converts the content of a <template> to an array of TinyHtml elements.
336
+ *
337
+ * @param {HTMLTemplateElement} nodes
338
+ * @returns {TinyHtml[]}
339
+ */
340
+ static templateToTinyElems(nodes) {
341
+ return TinyHtml.toTinyElm(TinyHtml.templateToNodes(nodes));
342
+ }
343
+
344
+ /**
345
+ * Parses a full HTML string into a JSON-like structure.
346
+ *
347
+ * @param {string} htmlString - Full HTML markup as a string.
348
+ * @returns {HtmlParsed[]} An array of parsed HTML elements in structured format.
349
+ */
350
+ static htmlToJson(htmlString) {
351
+ const container = document.createElement('div');
352
+ container.innerHTML = htmlString.trim();
353
+
354
+ const result = [];
355
+
356
+ /**
357
+ * @param {Node} el
358
+ * @returns {*}
359
+ */
360
+ const parseElement = (el) => {
361
+ if (el instanceof Comment) return null;
362
+ if (el instanceof Text) {
363
+ const text = el.textContent?.trim();
364
+ return text ? text : null;
365
+ }
366
+
367
+ if (!(el instanceof Element)) return null;
368
+ const tag = el.tagName.toLowerCase();
369
+
370
+ /** @type {Record<string, string>} */
371
+ const props = {};
372
+ for (const attr of el.attributes) {
373
+ props[TinyHtml.getPropName(attr.name)] = attr.value;
374
+ }
375
+
376
+ const children = Array.from(el.childNodes).map(parseElement).filter(Boolean);
377
+ return children.length > 0 ? [tag, props, ...children] : [tag, props];
378
+ };
379
+
380
+ for (const child of container.childNodes) {
381
+ const parsed = parseElement(child);
382
+ if (parsed) result.push(parsed);
383
+ }
384
+
385
+ container.innerHTML = '';
386
+ return result;
387
+ }
388
+
389
+ /**
390
+ * Converts a JSON-like HTML structure back to DOM Elements.
391
+ *
392
+ * @param {HtmlParsed[]} jsonArray - Parsed JSON format of HTML.
393
+ * @returns {(HTMLElement|Text)[]} List of DOM nodes.
394
+ */
395
+ static jsonToNodes(jsonArray) {
396
+ /**
397
+ * @param {HtmlParsed|string} nodeData
398
+ * @returns {HTMLElement|Text}
399
+ */
400
+ const createElement = (nodeData) => {
401
+ if (typeof nodeData === 'string') {
402
+ return document.createTextNode(nodeData);
403
+ }
404
+
405
+ if (!Array.isArray(nodeData)) return document.createTextNode('');
406
+ const [tag, props, ...children] = nodeData;
407
+ const el = document.createElement(tag);
408
+
409
+ for (const [key, value] of Object.entries(props)) {
410
+ el.setAttribute(TinyHtml.getAttrName(key), value);
411
+ }
412
+
413
+ for (const child of children) {
414
+ const childEl = createElement(child);
415
+ if (childEl instanceof Comment) continue;
416
+ el.appendChild(childEl);
417
+ }
418
+
419
+ return el;
420
+ };
421
+
422
+ return jsonArray.map(createElement).filter((node) => !(node instanceof Comment));
423
+ }
424
+
425
+ /**
426
+ * Converts a JSON-like HTML structure back to TinyHtml instances.
427
+ *
428
+ * @param {HtmlParsed[]} jsonArray - Parsed JSON format of HTML.
429
+ * @returns {TinyHtml[]} List of TinyHtml instances.
430
+ */
431
+ static jsonToTinyElems(jsonArray) {
432
+ return TinyHtml.toTinyElm(TinyHtml.jsonToNodes(jsonArray));
433
+ }
434
+
435
+ /**
436
+ * Creates a new TinyHtml element from a tag name and optional attributes.
437
+ *
438
+ * You can alias this method for convenience:
439
+ * ```js
440
+ * const createElement = TinyHtml.createFrom;
441
+ * const myDiv = createElement('div', { class: 'box' });
442
+ * ```
443
+ *
444
+ * @param {string} tagName - The HTML tag name (e.g., 'div', 'span', 'button').
445
+ * @param {Record<string, string|null>} [attrs] - Optional key-value pairs representing HTML attributes.
446
+ * If the value is `null`, the attribute will still be set with an empty value.
447
+ * @returns {TinyHtml} - A new instance of TinyHtml representing the created element.
448
+ * @throws {TypeError} - If `tagName` is not a string, or `attrs` is not a plain object when defined.
449
+ */
450
+ static createFrom(tagName, attrs) {
451
+ if (typeof tagName !== 'string') throw new TypeError('The "tagName" must be a string.');
452
+ if (typeof attrs !== 'undefined' && typeof attrs !== 'object')
453
+ throw new TypeError('The "attrs" must be a object.');
454
+ const elem = TinyHtml.createElement(tagName);
455
+ if (typeof attrs === 'object') {
456
+ for (const attrName in attrs) {
457
+ elem.setAttr(attrName, attrs[attrName]);
458
+ }
459
+ }
460
+ return elem;
461
+ }
462
+
243
463
  /**
244
464
  * Creates a new DOM element with the specified tag name and options, then wraps it in a TinyHtml instance.
245
465
  *
246
466
  * @param {string} tagName - The tag name of the element to create (e.g., 'div', 'span').
247
- * @param {ElementCreationOptions} ops - Optional settings for creating the element.
467
+ * @param {ElementCreationOptions} [ops] - Optional settings for creating the element.
248
468
  * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM element.
249
469
  * @throws {TypeError} If tagName is not a string or ops is not an object.
250
470
  */
@@ -290,7 +510,8 @@ class TinyHtml {
290
510
  }
291
511
 
292
512
  template.innerHTML = htmlString;
293
- if (!(template.content.firstChild instanceof Element)) throw new Error('');
513
+ if (!(template.content.firstChild instanceof Element))
514
+ throw new Error('The HTML string must contain a valid HTML element.');
294
515
  return new TinyHtml(template.content.firstChild);
295
516
  }
296
517
 
@@ -322,18 +543,17 @@ class TinyHtml {
322
543
  *
323
544
  * @param {string} selector - A valid CSS selector string.
324
545
  * @param {Document|Element} elem - Target element.
325
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
546
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the matched elements.
326
547
  */
327
548
  static queryAll(selector, elem = document) {
328
- const newEls = elem.querySelectorAll(selector);
329
- return TinyHtml.toTinyElm([...newEls]);
549
+ return new TinyHtml(elem.querySelectorAll(selector));
330
550
  }
331
551
 
332
552
  /**
333
553
  * Queries the element for all elements matching the CSS selector and wraps them in TinyHtml instances.
334
554
  *
335
555
  * @param {string} selector - A valid CSS selector string.
336
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
556
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the matched elements.
337
557
  */
338
558
  querySelectorAll(selector) {
339
559
  return TinyHtml.queryAll(selector, TinyHtml._preElem(this, 'queryAll'));
@@ -356,18 +576,17 @@ class TinyHtml {
356
576
  *
357
577
  * @param {string} selector - The class name to search for.
358
578
  * @param {Document|Element} elem - Target element.
359
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
579
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
360
580
  */
361
581
  static getByClassName(selector, elem = document) {
362
- const newEls = elem.getElementsByClassName(selector);
363
- return TinyHtml.toTinyElm([...newEls]);
582
+ return new TinyHtml(elem.getElementsByClassName(selector));
364
583
  }
365
584
 
366
585
  /**
367
586
  * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
368
587
  *
369
588
  * @param {string} selector - The class name to search for.
370
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
589
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
371
590
  */
372
591
  getElementsByClassName(selector) {
373
592
  return TinyHtml.getByClassName(selector, TinyHtml._preElem(this, 'getByClassName'));
@@ -377,11 +596,10 @@ class TinyHtml {
377
596
  * Retrieves all elements with the specified name attribute and wraps them in TinyHtml instances.
378
597
  *
379
598
  * @param {string} selector - The name attribute to search for.
380
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
599
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
381
600
  */
382
601
  static getByName(selector) {
383
- const newEls = document.getElementsByName(selector);
384
- return TinyHtml.toTinyElm([...newEls]);
602
+ return new TinyHtml(document.getElementsByName(selector));
385
603
  }
386
604
 
387
605
  /**
@@ -391,11 +609,10 @@ class TinyHtml {
391
609
  * @param {string} localName - The local name (tag) of the elements to search for.
392
610
  * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
393
611
  * @param {Document|Element} elem - Target element.
394
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
612
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
395
613
  */
396
614
  static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml', elem = document) {
397
- const newEls = elem.getElementsByTagNameNS(namespaceURI, localName);
398
- return TinyHtml.toTinyElm([...newEls]);
615
+ return new TinyHtml(elem.getElementsByTagNameNS(namespaceURI, localName));
399
616
  }
400
617
 
401
618
  /**
@@ -404,7 +621,7 @@ class TinyHtml {
404
621
  *
405
622
  * @param {string} localName - The local name (tag) of the elements to search for.
406
623
  * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
407
- * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
624
+ * @returns {TinyHtml} An array of TinyHtml instances wrapping the found elements.
408
625
  */
409
626
  getElementsByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
410
627
  return TinyHtml.getByTagNameNS(
@@ -419,85 +636,161 @@ class TinyHtml {
419
636
  /**
420
637
  * Returns the current target held by this instance.
421
638
  *
639
+ * @param {number} index - The index of the element to retrieve.
422
640
  * @returns {ConstructorElValues} - The instance's target element.
423
641
  */
424
- get() {
425
- return this.#el;
642
+ get(index) {
643
+ if (typeof index !== 'number') throw new TypeError('The index must be a number.');
644
+ if (!this.#el[index]) throw new Error(`No element found at index ${index}.`);
645
+ return this.#el[index];
646
+ }
647
+
648
+ /**
649
+ * Extracts a single DOM element from the internal list at the specified index.
650
+ *
651
+ * @param {number} index - The index of the element to extract.
652
+ * @returns {TinyHtml} A new TinyHtml instance wrapping the extracted element.
653
+ */
654
+ extract(index) {
655
+ if (typeof index !== 'number') throw new TypeError('The index must be a number.');
656
+ if (!this.#el[index]) throw new Error(`Cannot extract: no element exists at index ${index}.`);
657
+ return new TinyHtml(this.#el[index]);
658
+ }
659
+
660
+ /**
661
+ * Checks whether the element exists at the given index.
662
+ *
663
+ * @param {number} index - The index to check.
664
+ * @returns {boolean} - True if the element exists; otherwise, false.
665
+ */
666
+ exists(index) {
667
+ if (typeof index !== 'number') throw new TypeError('The index must be a number.');
668
+ if (!this.#el[index]) return false;
669
+ return true;
670
+ }
671
+
672
+ /**
673
+ * Returns the current targets held by this instance.
674
+ *
675
+ * @returns {ConstructorElValues[]} - The instance's targets element.
676
+ */
677
+ getAll() {
678
+ return [...this.#el];
426
679
  }
427
680
 
428
681
  /**
429
682
  * Returns the current Element held by this instance.
430
683
  *
431
684
  * @param {string} where - The method name or context calling this.
685
+ * @param {number} index - The index of the element to retrieve.
432
686
  * @returns {ConstructorElValues} - The instance's element.
433
687
  * @readonly
434
688
  */
435
- _getElement(where) {
689
+ _getElement(where, index) {
690
+ if (
691
+ !(this.#el[index] instanceof Element) &&
692
+ !(this.#el[index] instanceof Window) &&
693
+ !(this.#el[index] instanceof Document) &&
694
+ !(this.#el[index] instanceof Text)
695
+ )
696
+ throw new Error(`[TinyHtml] Invalid Element in ${where}().`);
697
+ return this.#el[index];
698
+ }
699
+
700
+ /**
701
+ * Returns the current Elements held by this instance.
702
+ *
703
+ * @param {string} where - The method name or context calling this.
704
+ * @returns {ConstructorElValues[]} - The instance's elements.
705
+ * @readonly
706
+ */
707
+ _getElements(where) {
436
708
  if (
437
- !(this.#el instanceof Element) &&
438
- !(this.#el instanceof Window) &&
439
- !(this.#el instanceof Document)
709
+ !this.#el.every(
710
+ (el) =>
711
+ el instanceof Element ||
712
+ el instanceof Window ||
713
+ el instanceof Document ||
714
+ el instanceof Text,
715
+ )
440
716
  )
441
717
  throw new Error(`[TinyHtml] Invalid Element in ${where}().`);
442
- return this.#el;
718
+ return [...this.#el];
443
719
  }
444
720
 
445
721
  //////////////////////////////////////////////////////
446
722
 
447
723
  /**
448
- * @param {TinyElement|EventTarget|null|(TinyElement|EventTarget|null)[]} elems
449
- * @param {string} where
450
- * @param {any[]} TheTinyElements
451
- * @param {string[]} elemName
452
- * @returns {any[]}
724
+ * Prepares and validates multiple elements against allowed types.
725
+ *
726
+ * @param {TinyElement | EventTarget | null | (TinyElement | EventTarget | null)[]} elems - The input elements to validate.
727
+ * @param {string} where - The method name or context calling this.
728
+ * @param {any[]} TheTinyElements - The list of allowed constructors (e.g., Element, Document).
729
+ * @param {string[]} elemName - The list of expected element names for error reporting.
730
+ * @returns {any[]} - A flat array of validated elements.
731
+ * @throws {Error} - If any element is not an instance of one of the allowed types.
453
732
  * @readonly
454
733
  */
455
734
  static _preElemsTemplate(elems, where, TheTinyElements, elemName) {
456
735
  /** @param {(TinyElement|EventTarget|null)[]} item */
457
- const checkElement = (item) =>
458
- item.map((elem) => {
459
- const result = elem instanceof TinyHtml ? elem._getElement(where) : elem;
460
- let allowed = false;
461
- for (const TheTinyElement of TheTinyElements) {
462
- if (result instanceof TheTinyElement) {
463
- allowed = true;
464
- break;
736
+ const checkElement = (item) => {
737
+ /** @type {any[]} */
738
+ const results = [];
739
+ item.map((elem) =>
740
+ (elem instanceof TinyHtml ? elem._getElements(where) : [elem]).map((result) => {
741
+ let allowed = false;
742
+ for (const TheTinyElement of TheTinyElements) {
743
+ if (result instanceof TheTinyElement) {
744
+ allowed = true;
745
+ break;
746
+ }
465
747
  }
466
- }
467
- if (!allowed)
468
- throw new Error(
469
- `[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`,
470
- );
471
- return result;
472
- });
748
+ if (!allowed)
749
+ throw new Error(
750
+ `[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`,
751
+ );
752
+ results.push(result);
753
+ return result;
754
+ }),
755
+ );
756
+ return results;
757
+ };
473
758
  if (!Array.isArray(elems)) return checkElement([elems]);
474
759
  return checkElement(elems);
475
760
  }
476
761
 
477
762
  /**
478
- * @param {TinyElement|EventTarget|null|(TinyElement|EventTarget|null)[]} elems
479
- * @param {string} where
480
- * @param {any[]} TheTinyElements
481
- * @param {string[]} elemName
482
- * @param {boolean} [canNull=false]
483
- * @returns {any}
763
+ * Prepares and validates a single element against allowed types.
764
+ *
765
+ * @param {TinyElement | EventTarget | null | (TinyElement | EventTarget | null)[]} elems - The input element or list to validate.
766
+ * @param {string} where - The method name or context calling this.
767
+ * @param {any[]} TheTinyElements - The list of allowed constructors (e.g., Element, Document).
768
+ * @param {string[]} elemName - The list of expected element names for error reporting.
769
+ * @param {boolean} [canNull=false] - Whether `null` is allowed as a valid value.
770
+ * @returns {any} - The validated element or `null` if allowed.
771
+ * @throws {Error} - If the element is not valid or if multiple elements are provided.
484
772
  * @readonly
485
773
  */
486
774
  static _preElemTemplate(elems, where, TheTinyElements, elemName, canNull = false) {
487
775
  /** @param {(TinyElement|EventTarget|null)[]} item */
488
776
  const checkElement = (item) => {
489
777
  const elem = item[0];
490
- let result = elem instanceof TinyHtml ? elem._getElement(where) : elem;
778
+ const result = elem instanceof TinyHtml ? elem._getElements(where) : [elem];
779
+ if (result.length > 1)
780
+ throw new Error(
781
+ `[TinyHtml] Invalid element amount in ${where}() (Received ${result.length}/1).`,
782
+ );
783
+
491
784
  let allowed = false;
492
785
  for (const TheTinyElement of TheTinyElements) {
493
- if (result instanceof TheTinyElement) {
786
+ if (result[0] instanceof TheTinyElement) {
494
787
  allowed = true;
495
788
  break;
496
789
  }
497
790
  }
498
791
 
499
- if (canNull && (result === null || typeof result === 'undefined')) {
500
- result = null;
792
+ if (canNull && (result[0] === null || typeof result[0] === 'undefined')) {
793
+ result[0] = null;
501
794
  allowed = true;
502
795
  }
503
796
 
@@ -505,7 +798,7 @@ class TinyHtml {
505
798
  throw new Error(
506
799
  `[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`,
507
800
  );
508
- return result;
801
+ return result[0];
509
802
  };
510
803
  if (!Array.isArray(elems)) return checkElement([elems]);
511
804
  if (elems.length > 1)
@@ -770,11 +1063,11 @@ class TinyHtml {
770
1063
  * This ensures consistent access to methods of the `TinyHtml` class regardless
771
1064
  * of the input form.
772
1065
  *
773
- * @param {TinyElement|TinyElement[]} elems - A single element or an array of elements (DOM or TinyHtml).
1066
+ * @param {TinyElement|Text|(TinyElement|Text)[]} elems - A single element or an array of elements (DOM or TinyHtml).
774
1067
  * @returns {TinyHtml[]} An array of TinyHtml instances corresponding to the input elements.
775
1068
  */
776
1069
  static toTinyElm(elems) {
777
- /** @param {TinyElement[]} item */
1070
+ /** @param {(TinyElement|Text)[]} item */
778
1071
  const checkElement = (item) =>
779
1072
  item.map((elem) => (!(elem instanceof TinyHtml) ? new TinyHtml(elem) : elem));
780
1073
  if (!Array.isArray(elems)) return checkElement([elems]);
@@ -798,13 +1091,16 @@ class TinyHtml {
798
1091
  */
799
1092
  static fromTinyElm(elems) {
800
1093
  /** @param {TinyElement[]} item */
801
- const checkElement = (item) =>
802
- item.map(
803
- (elem) =>
804
- /** @type {Element} */ (
805
- elem instanceof TinyHtml ? elem._getElement('fromTinyElm') : elem
806
- ),
1094
+ const checkElement = (item) => {
1095
+ /** @type {Element[]} */
1096
+ const result = [];
1097
+ item.map((elem) =>
1098
+ /** @type {Element[]} */ (
1099
+ elem instanceof TinyHtml ? elem._getElements('fromTinyElm') : [elem]
1100
+ ).map((elem) => result.push(elem)),
807
1101
  );
1102
+ return result;
1103
+ };
808
1104
  if (!Array.isArray(elems)) return checkElement([elems]);
809
1105
  return checkElement(elems);
810
1106
  }
@@ -1648,7 +1944,8 @@ class TinyHtml {
1648
1944
  const elem = TinyHtml._preNodeElem(el, 'insertBefore');
1649
1945
  const targ = TinyHtml._preNodeElem(target, 'insertBefore');
1650
1946
  const childNode = TinyHtml._preNodeElemWithNull(child, 'insertBefore');
1651
- if (!targ.parentNode) throw new Error('');
1947
+ if (!targ.parentNode)
1948
+ throw new Error('The target element has no parent node to insert before.');
1652
1949
  targ.parentNode.insertBefore(elem, childNode || targ);
1653
1950
  return el;
1654
1951
  }
@@ -1676,7 +1973,7 @@ class TinyHtml {
1676
1973
  const elem = TinyHtml._preNodeElem(el, 'insertAfter');
1677
1974
  const targ = TinyHtml._preNodeElem(target, 'insertBefore');
1678
1975
  const childNode = TinyHtml._preNodeElemWithNull(child, 'insertBefore');
1679
- if (!targ.parentNode) throw new Error('');
1976
+ if (!targ.parentNode) throw new Error('The target element has no parent node to insert after.');
1680
1977
  targ.parentNode.insertBefore(elem, childNode || targ.nextSibling);
1681
1978
  return el;
1682
1979
  }
@@ -1728,28 +2025,57 @@ class TinyHtml {
1728
2025
 
1729
2026
  /**
1730
2027
  * The target HTML element for instance-level operations.
1731
- * @type {ConstructorElValues}
2028
+ * @type {ConstructorElValues[]}
1732
2029
  */
1733
2030
  #el;
1734
2031
 
2032
+ /**
2033
+ * Returns the number of elements currently stored in the internal element list.
2034
+ *
2035
+ * @returns {number} The total count of elements.
2036
+ */
2037
+ get size() {
2038
+ return this.#el.length;
2039
+ }
2040
+
1735
2041
  /**
1736
2042
  * Creates an instance of TinyHtml for a specific Element.
1737
2043
  * Useful when you want to operate repeatedly on the same element using instance methods.
1738
- * @param {ConstructorElValues} el - The element to wrap and manipulate.
2044
+ * @param {ConstructorElValues|ConstructorElValues[]|NodeListOf<Element>|HTMLCollectionOf<Element>|NodeListOf<HTMLElement>} el - The element to wrap and manipulate.
1739
2045
  */
1740
2046
  constructor(el) {
1741
2047
  if (el instanceof TinyHtml)
1742
2048
  throw new Error(
1743
2049
  `[TinyHtml] You are trying to put a TinyHtml inside another TinyHtml in constructor.`,
1744
2050
  );
1745
- if (
1746
- !(el instanceof Element) &&
1747
- !(el instanceof Window) &&
1748
- !(el instanceof Document) &&
1749
- !(el instanceof Text)
1750
- )
1751
- throw new Error(`[TinyHtml] Invalid Target in constructor.`);
1752
- this.#el = el;
2051
+
2052
+ /** @param {any[]} els */
2053
+ const elCheck = (els) => {
2054
+ if (
2055
+ !els.every(
2056
+ (el) =>
2057
+ el instanceof Element ||
2058
+ el instanceof Window ||
2059
+ el instanceof Document ||
2060
+ el instanceof Text,
2061
+ )
2062
+ )
2063
+ throw new Error(`[TinyHtml] Invalid Target in constructor.`);
2064
+ };
2065
+
2066
+ if (Array.isArray(el)) {
2067
+ elCheck(el);
2068
+ this.#el = el;
2069
+ } else if (el instanceof NodeList || el instanceof HTMLCollection) {
2070
+ const els = [...el];
2071
+ elCheck(els);
2072
+ this.#el = els;
2073
+ } else {
2074
+ const els = [el];
2075
+ elCheck(els);
2076
+ // @ts-ignore
2077
+ this.#el = els;
2078
+ }
1753
2079
  }
1754
2080
 
1755
2081
  /**
@@ -2153,7 +2479,13 @@ class TinyHtml {
2153
2479
  msTransition: '-ms-transition',
2154
2480
  };
2155
2481
 
2156
- /** @type {Record<string | symbol, string>} */
2482
+ /**
2483
+ * Public proxy to manage camelCase ➝ kebab-case CSS property aliasing.
2484
+ *
2485
+ * Modifying this object ensures that the reverse mapping in `cssPropRevAliases` is updated accordingly.
2486
+ *
2487
+ * @type {Record<string | symbol, string>}
2488
+ */
2157
2489
  static cssPropAliases = new Proxy(TinyHtml.#cssPropAliases, {
2158
2490
  set(target, camelCaseKey, kebabValue) {
2159
2491
  target[camelCaseKey] = kebabValue;
@@ -2163,7 +2495,13 @@ class TinyHtml {
2163
2495
  },
2164
2496
  });
2165
2497
 
2166
- /** @type {Record<string | symbol, string>} */
2498
+ /**
2499
+ * Reverse map of `cssPropAliases`, mapping kebab-case back to camelCase CSS property names.
2500
+ *
2501
+ * This enables consistent bidirectional lookups of style properties.
2502
+ *
2503
+ * @type {Record<string | symbol, string>}
2504
+ */
2167
2505
  static cssPropRevAliases = Object.fromEntries(
2168
2506
  Object.entries(TinyHtml.#cssPropAliases).map(([camel, kebab]) => [kebab, camel]),
2169
2507
  );
@@ -3804,7 +4142,7 @@ class TinyHtml {
3804
4142
  static _getValByType(elem, type, where) {
3805
4143
  if (typeof type !== 'string') throw new TypeError('The "type" must be a string.');
3806
4144
  if (typeof where !== 'string') throw new TypeError('The "where" must be a string.');
3807
- if (!(elem instanceof HTMLInputElement))
4145
+ if (!(elem instanceof HTMLInputElement) && !(elem instanceof HTMLTextAreaElement))
3808
4146
  throw new Error(`Provided element is not an HTMLInputElement in ${where}().`);
3809
4147
  if (typeof TinyHtml._valTypes[type] !== 'function')
3810
4148
  throw new Error(`No handler found for type "${type}" in ${where}().`);
@@ -4345,14 +4683,77 @@ class TinyHtml {
4345
4683
  ///////////////////////////////////////////////////////////////
4346
4684
 
4347
4685
  /**
4348
- * Property name normalization similar to jQuery's propFix.
4349
- * @readonly
4686
+ * Internal property name normalization map (similar to jQuery's `propFix`).
4687
+ * Maps attribute-like names to their JavaScript DOM property equivalents.
4688
+ *
4689
+ * Example: `'for'` ➝ `'htmlFor'`, `'class'` ➝ `'className'`.
4690
+ *
4691
+ * ⚠️ Do not modify this object directly. Use `TinyHtml.propFix` to ensure reverse mapping (`attrFix`) remains in sync.
4692
+ *
4693
+ * @type {Record<string | symbol, string>}
4350
4694
  */
4351
- static _propFix = {
4695
+ static #propFix = {
4352
4696
  for: 'htmlFor',
4353
4697
  class: 'className',
4354
4698
  };
4355
4699
 
4700
+ /**
4701
+ * Public proxy for normalized DOM property names.
4702
+ *
4703
+ * Setting a new entry here will also automatically update the reverse map in `TinyHtml.attrFix`.
4704
+ *
4705
+ * @type {Record<string | symbol, string>}
4706
+ */
4707
+ static propFix = new Proxy(TinyHtml.#propFix, {
4708
+ set(target, val1, val2) {
4709
+ target[val1] = val2;
4710
+ // @ts-ignore
4711
+ TinyHtml.attrFix[val2] = val1;
4712
+ return true;
4713
+ },
4714
+ });
4715
+
4716
+ /**
4717
+ * Reverse map of `propFix`, mapping property names back to their attribute equivalents.
4718
+ *
4719
+ * Used when converting DOM property names into HTML attribute names.
4720
+ *
4721
+ * @type {Record<string | symbol, string>}
4722
+ */
4723
+ static attrFix = Object.fromEntries(
4724
+ Object.entries(TinyHtml.#propFix).map(([val1, val2]) => [val2, val1]),
4725
+ );
4726
+
4727
+ /**
4728
+ * Normalizes an attribute name to its corresponding DOM property name.
4729
+ *
4730
+ * Example: `'class'` ➝ `'className'`, `'for'` ➝ `'htmlFor'`.
4731
+ * If the name is not mapped, it returns the original name.
4732
+ *
4733
+ * @param {string} name - The attribute name to normalize.
4734
+ * @returns {string} - The corresponding property name.
4735
+ */
4736
+ static getPropName(name) {
4737
+ // @ts-ignore
4738
+ const propName = typeof TinyHtml.propFix[name] === 'string' ? TinyHtml.propFix[name] : name;
4739
+ return propName;
4740
+ }
4741
+
4742
+ /**
4743
+ * Converts a DOM property name back to its corresponding attribute name.
4744
+ *
4745
+ * Example: `'className'` ➝ `'class'`, `'htmlFor'` ➝ `'for'`.
4746
+ * If the name is not mapped, it returns the original name.
4747
+ *
4748
+ * @param {string} name - The property name to convert.
4749
+ * @returns {string} - The corresponding attribute name.
4750
+ */
4751
+ static getAttrName(name) {
4752
+ // @ts-ignore
4753
+ const propName = typeof TinyHtml.attrFix[name] === 'string' ? TinyHtml.attrFix[name] : name;
4754
+ return propName;
4755
+ }
4756
+
4356
4757
  /**
4357
4758
  * Get an attribute on an element.
4358
4759
  * @param {TinyElement} el
@@ -4362,7 +4763,7 @@ class TinyHtml {
4362
4763
  static attr(el, name) {
4363
4764
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4364
4765
  const elem = TinyHtml._preElem(el, 'attr');
4365
- return elem.getAttribute(name);
4766
+ return elem.getAttribute(TinyHtml.getAttrName(name));
4366
4767
  }
4367
4768
 
4368
4769
  /**
@@ -4386,8 +4787,8 @@ class TinyHtml {
4386
4787
  if (value !== null && typeof value !== 'string')
4387
4788
  throw new TypeError('The "value" must be a string.');
4388
4789
  TinyHtml._preElems(el, 'setAttr').forEach((elem) => {
4389
- if (value === null) elem.removeAttribute(name);
4390
- else elem.setAttribute(name, value);
4790
+ if (value === null) elem.removeAttribute(TinyHtml.getAttrName(name));
4791
+ else elem.setAttribute(TinyHtml.getAttrName(name), value);
4391
4792
  });
4392
4793
  return el;
4393
4794
  }
@@ -4410,7 +4811,9 @@ class TinyHtml {
4410
4811
  */
4411
4812
  static removeAttr(el, name) {
4412
4813
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4413
- TinyHtml._preElems(el, 'removeAttr').forEach((elem) => elem.removeAttribute(name));
4814
+ TinyHtml._preElems(el, 'removeAttr').forEach((elem) =>
4815
+ elem.removeAttribute(TinyHtml.getAttrName(name)),
4816
+ );
4414
4817
  return el;
4415
4818
  }
4416
4819
 
@@ -4432,7 +4835,7 @@ class TinyHtml {
4432
4835
  static hasAttr(el, name) {
4433
4836
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4434
4837
  const elem = TinyHtml._preElem(el, 'hasAttr');
4435
- return elem.hasAttribute(name);
4838
+ return elem.hasAttribute(TinyHtml.getAttrName(name));
4436
4839
  }
4437
4840
 
4438
4841
  /**
@@ -4454,9 +4857,7 @@ class TinyHtml {
4454
4857
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4455
4858
  const elem = TinyHtml._preElem(el, 'hasProp');
4456
4859
  // @ts-ignore
4457
- const propName = TinyHtml._propFix[name] || name;
4458
- // @ts-ignore
4459
- return !!elem[propName];
4860
+ return !!elem[TinyHtml.getPropName(name)];
4460
4861
  }
4461
4862
 
4462
4863
  /**
@@ -4478,9 +4879,7 @@ class TinyHtml {
4478
4879
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4479
4880
  TinyHtml._preElems(el, 'addProp').forEach((elem) => {
4480
4881
  // @ts-ignore
4481
- name = TinyHtml._propFix[name] || name;
4482
- // @ts-ignore
4483
- elem[name] = true;
4882
+ elem[TinyHtml.getPropName(name)] = true;
4484
4883
  });
4485
4884
  return el;
4486
4885
  }
@@ -4504,9 +4903,7 @@ class TinyHtml {
4504
4903
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4505
4904
  TinyHtml._preElems(el, 'removeProp').forEach((elem) => {
4506
4905
  // @ts-ignore
4507
- name = TinyHtml._propFix[name] || name;
4508
- // @ts-ignore
4509
- elem[name] = false;
4906
+ elem[TinyHtml.getPropName(name)] = false;
4510
4907
  });
4511
4908
  return el;
4512
4909
  }
@@ -4532,9 +4929,7 @@ class TinyHtml {
4532
4929
  throw new TypeError('The "force" must be a boolean.');
4533
4930
  TinyHtml._preElems(el, 'toggleProp').forEach((elem) => {
4534
4931
  // @ts-ignore
4535
- const propName = TinyHtml._propFix[name] || name;
4536
- // @ts-ignore
4537
- const shouldEnable = force === undefined ? !elem[propName] : force;
4932
+ const shouldEnable = force === undefined ? !elem[TinyHtml.getPropName(name)] : force;
4538
4933
  // @ts-ignore
4539
4934
  if (shouldEnable) TinyHtml.addProp(elem, name);
4540
4935
  else TinyHtml.removeProp(elem, name);
@@ -4637,14 +5032,14 @@ class TinyHtml {
4637
5032
  }
4638
5033
 
4639
5034
  if (typeof extraRect !== 'object' || extraRect === null || Array.isArray(extraRect))
4640
- throw new Error('');
5035
+ throw new Error('extraRect must be a non-null object.');
4641
5036
  const { height = 0, width = 0, top = 0, bottom = 0, left = 0, right = 0 } = extraRect;
4642
- if (typeof height !== 'number') throw new Error('');
4643
- if (typeof width !== 'number') throw new Error('');
4644
- if (typeof top !== 'number') throw new Error('');
4645
- if (typeof bottom !== 'number') throw new Error('');
4646
- if (typeof left !== 'number') throw new Error('');
4647
- if (typeof right !== 'number') throw new Error('');
5037
+ if (typeof height !== 'number') throw new Error('extraRect.height must be a number.');
5038
+ if (typeof width !== 'number') throw new Error('extraRect.width must be a number.');
5039
+ if (typeof top !== 'number') throw new Error('extraRect.top must be a number.');
5040
+ if (typeof bottom !== 'number') throw new Error('extraRect.bottom must be a number.');
5041
+ if (typeof left !== 'number') throw new Error('extraRect.left must be a number.');
5042
+ if (typeof right !== 'number') throw new Error('extraRect.right must be a number.');
4648
5043
 
4649
5044
  // @ts-ignore
4650
5045
  result.height += height;