tiny-essentials 1.16.1 → 1.17.0

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 (139) hide show
  1. package/dist/legacy/crypto/decrypt.cjs +1 -0
  2. package/dist/legacy/crypto/decrypt.d.mts +1 -0
  3. package/dist/legacy/crypto/decrypt.mjs +1 -0
  4. package/dist/legacy/crypto/encrypt.cjs +1 -0
  5. package/dist/legacy/crypto/encrypt.d.mts +1 -0
  6. package/dist/legacy/crypto/encrypt.mjs +1 -0
  7. package/dist/legacy/get/decimalColor.cjs +1 -0
  8. package/dist/legacy/get/decimalColor.d.mts +1 -0
  9. package/dist/legacy/get/decimalColor.mjs +1 -0
  10. package/dist/legacy/get/pagination.cjs +1 -0
  11. package/dist/legacy/get/pagination.d.mts +1 -0
  12. package/dist/legacy/get/pagination.mjs +1 -0
  13. package/dist/legacy/get/queryUrlByName.cjs +1 -0
  14. package/dist/legacy/get/queryUrlByName.d.mts +1 -0
  15. package/dist/legacy/get/queryUrlByName.mjs +1 -0
  16. package/dist/legacy/get/queryUrlJSON.cjs +1 -0
  17. package/dist/legacy/get/queryUrlJSON.d.mts +1 -0
  18. package/dist/legacy/get/queryUrlJSON.mjs +1 -0
  19. package/dist/legacy/get/super_string_filter.cjs +1 -0
  20. package/dist/legacy/get/super_string_filter.d.mts +1 -0
  21. package/dist/legacy/get/super_string_filter.mjs +1 -0
  22. package/dist/legacy/get/versionCheck.cjs +1 -0
  23. package/dist/legacy/get/versionCheck.d.mts +1 -0
  24. package/dist/legacy/get/versionCheck.mjs +1 -0
  25. package/dist/legacy/http/HTTP-1.0.cjs +2 -0
  26. package/dist/legacy/http/HTTP-1.0.mjs +2 -0
  27. package/dist/legacy/http/auth.cjs +1 -0
  28. package/dist/legacy/http/auth.d.mts +1 -0
  29. package/dist/legacy/http/auth.mjs +1 -0
  30. package/dist/legacy/http/check_domain.cjs +11 -0
  31. package/dist/legacy/http/check_domain.d.mts +3 -0
  32. package/dist/legacy/http/check_domain.mjs +11 -0
  33. package/dist/legacy/http/csrfTokenAnalyze.cjs +1 -0
  34. package/dist/legacy/http/csrfTokenAnalyze.d.mts +1 -0
  35. package/dist/legacy/http/csrfTokenAnalyze.mjs +1 -0
  36. package/dist/legacy/http/domainValidator.cjs +1 -0
  37. package/dist/legacy/http/domainValidator.d.mts +1 -0
  38. package/dist/legacy/http/domainValidator.mjs +1 -0
  39. package/dist/legacy/http/errorsCallback.cjs +1 -0
  40. package/dist/legacy/http/errorsCallback.d.mts +1 -0
  41. package/dist/legacy/http/errorsCallback.mjs +1 -0
  42. package/dist/legacy/http/fetch/json.cjs +1 -0
  43. package/dist/legacy/http/fetch/json.d.mts +1 -0
  44. package/dist/legacy/http/fetch/json.mjs +1 -0
  45. package/dist/legacy/http/fetch/text.cjs +1 -0
  46. package/dist/legacy/http/fetch/text.d.mts +1 -0
  47. package/dist/legacy/http/fetch/text.mjs +1 -0
  48. package/dist/legacy/http/fileCache.cjs +1 -0
  49. package/dist/legacy/http/fileCache.d.mts +1 -0
  50. package/dist/legacy/http/fileCache.mjs +1 -0
  51. package/dist/legacy/http/getDomainURL.cjs +1 -0
  52. package/dist/legacy/http/getDomainURL.d.mts +1 -0
  53. package/dist/legacy/http/getDomainURL.mjs +1 -0
  54. package/dist/legacy/http/userIP.cjs +1 -0
  55. package/dist/legacy/http/userIP.d.mts +1 -0
  56. package/dist/legacy/http/userIP.mjs +1 -0
  57. package/dist/legacy/libs/capitalize.cjs +1 -0
  58. package/dist/legacy/libs/capitalize.d.mts +1 -0
  59. package/dist/legacy/libs/capitalize.mjs +1 -0
  60. package/dist/legacy/libs/convertBytes.cjs +2 -0
  61. package/dist/legacy/libs/convertBytes.mjs +2 -0
  62. package/dist/legacy/libs/custom_module_loader.cjs +2 -0
  63. package/dist/legacy/libs/custom_module_loader.mjs +2 -0
  64. package/dist/legacy/libs/dice.cjs +2 -0
  65. package/dist/legacy/libs/dice.mjs +2 -0
  66. package/dist/legacy/libs/markdown.cjs +1 -0
  67. package/dist/legacy/libs/markdown.mjs +1 -0
  68. package/dist/legacy/libs/percentage.cjs +1 -0
  69. package/dist/legacy/libs/percentage.mjs +1 -0
  70. package/dist/legacy/libs/regex/getLetter.cjs +2 -0
  71. package/dist/legacy/libs/regex/getLetter.d.mts +2 -0
  72. package/dist/legacy/libs/regex/getLetter.mjs +2 -0
  73. package/dist/legacy/libs/rule3.cjs +1 -0
  74. package/dist/legacy/libs/rule3.mjs +1 -0
  75. package/dist/legacy/momentjs/getAge.cjs +1 -0
  76. package/dist/legacy/momentjs/getAge.d.mts +1 -0
  77. package/dist/legacy/momentjs/getAge.mjs +1 -0
  78. package/dist/legacy/momentjs/timeDuration.cjs +1 -0
  79. package/dist/legacy/momentjs/timeDuration.d.mts +1 -0
  80. package/dist/legacy/momentjs/timeDuration.mjs +1 -0
  81. package/dist/legacy/socket.io/antiFlood/install.cjs +1 -0
  82. package/dist/legacy/socket.io/antiFlood/install.d.mts +1 -0
  83. package/dist/legacy/socket.io/antiFlood/install.mjs +1 -0
  84. package/dist/legacy/socket.io/antiFlood/verify.cjs +1 -0
  85. package/dist/legacy/socket.io/antiFlood/verify.d.mts +1 -0
  86. package/dist/legacy/socket.io/antiFlood/verify.mjs +1 -0
  87. package/dist/legacy/socket.io/cookie-session.cjs +1 -0
  88. package/dist/legacy/socket.io/cookie-session.d.mts +1 -0
  89. package/dist/legacy/socket.io/cookie-session.mjs +1 -0
  90. package/dist/legacy/socket.io/discord.cjs +1 -0
  91. package/dist/legacy/socket.io/discord.d.mts +1 -0
  92. package/dist/legacy/socket.io/discord.mjs +1 -0
  93. package/dist/v1/TinyAfterScrollWatcher.js +49 -26
  94. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
  95. package/dist/v1/TinyBasicsEs.js +559 -71
  96. package/dist/v1/TinyBasicsEs.min.js +1 -1
  97. package/dist/v1/TinyDragger.js +486 -47
  98. package/dist/v1/TinyDragger.min.js +1 -1
  99. package/dist/v1/TinyEssentials.js +2545 -98
  100. package/dist/v1/TinyEssentials.min.js +1 -1
  101. package/dist/v1/TinyHtml.js +486 -47
  102. package/dist/v1/TinyHtml.min.js +1 -1
  103. package/dist/v1/TinySmartScroller.js +6230 -0
  104. package/dist/v1/TinySmartScroller.min.js +1 -0
  105. package/dist/v1/TinyUploadClicker.js +1538 -72
  106. package/dist/v1/TinyUploadClicker.min.js +1 -1
  107. package/dist/v1/UltraRandomMsgGen.js +995 -0
  108. package/dist/v1/UltraRandomMsgGen.min.js +1 -0
  109. package/dist/v1/basics/html.cjs +74 -24
  110. package/dist/v1/basics/html.d.mts +38 -15
  111. package/dist/v1/basics/html.mjs +63 -20
  112. package/dist/v1/build/TinySmartScroller.cjs +7 -0
  113. package/dist/v1/build/TinySmartScroller.d.mts +3 -0
  114. package/dist/v1/build/TinySmartScroller.mjs +2 -0
  115. package/dist/v1/build/UltraRandomMsgGen.cjs +7 -0
  116. package/dist/v1/build/UltraRandomMsgGen.d.mts +3 -0
  117. package/dist/v1/build/UltraRandomMsgGen.mjs +2 -0
  118. package/dist/v1/index.cjs +4 -0
  119. package/dist/v1/index.d.mts +3 -1
  120. package/dist/v1/index.mjs +3 -1
  121. package/dist/v1/libs/TinyAfterScrollWatcher.cjs +49 -26
  122. package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +17 -14
  123. package/dist/v1/libs/TinyAfterScrollWatcher.mjs +50 -24
  124. package/dist/v1/libs/TinyHtml.cjs +486 -47
  125. package/dist/v1/libs/TinyHtml.d.mts +352 -139
  126. package/dist/v1/libs/TinyHtml.mjs +431 -47
  127. package/dist/v1/libs/TinySmartScroller.cjs +976 -0
  128. package/dist/v1/libs/TinySmartScroller.d.mts +458 -0
  129. package/dist/v1/libs/TinySmartScroller.mjs +856 -0
  130. package/dist/v1/libs/UltraRandomMsgGen.cjs +956 -0
  131. package/dist/v1/libs/UltraRandomMsgGen.d.mts +390 -0
  132. package/dist/v1/libs/UltraRandomMsgGen.mjs +825 -0
  133. package/docs/v1/README.md +2 -0
  134. package/docs/v1/basics/html.md +17 -2
  135. package/docs/v1/libs/TinyAfterScrollWatcher.md +54 -4
  136. package/docs/v1/libs/TinyHtml.md +197 -8
  137. package/docs/v1/libs/TinySmartScroller.md +523 -0
  138. package/docs/v1/libs/UltraRandomMsgGen.md +289 -0
  139. package/package.json +1 -1
@@ -2896,7 +2896,9 @@ __webpack_require__.d(__webpack_exports__, {
2896
2896
  TinyNotifyCenter: () => (/* reexport */ libs_TinyNotifyCenter),
2897
2897
  TinyPromiseQueue: () => (/* reexport */ libs_TinyPromiseQueue),
2898
2898
  TinyRateLimiter: () => (/* reexport */ libs_TinyRateLimiter),
2899
+ TinySmartScroller: () => (/* reexport */ libs_TinySmartScroller),
2899
2900
  TinyToastNotify: () => (/* reexport */ libs_TinyToastNotify),
2901
+ UltraRandomMsgGen: () => (/* reexport */ libs_UltraRandomMsgGen),
2900
2902
  addAiMarkerShortcut: () => (/* reexport */ addAiMarkerShortcut),
2901
2903
  areElsCollBottom: () => (/* reexport */ areElsCollBottom),
2902
2904
  areElsCollLeft: () => (/* reexport */ areElsCollLeft),
@@ -6363,20 +6365,22 @@ function saveJsonFile(filename, data, spaces = 2) {
6363
6365
  }
6364
6366
 
6365
6367
  /**
6366
- * Loads and parses a JSON from a remote URL using Fetch API.
6367
- *
6368
- * @param {string} url - The full URL to fetch JSON from.
6369
- * @param {Object} [options] - Optional settings.
6370
- * @param {string} [options.method="GET"] - HTTP method to use (GET, POST, etc.).
6371
- * @param {any} [options.body] - Request body (only for methods like POST, PUT).
6372
- * @param {number} [options.timeout=0] - Timeout in milliseconds (ignored if signal is provided).
6373
- * @param {number} [options.retries=0] - Number of retry attempts (ignored if signal is provided).
6374
- * @param {Headers|Record<string, *>} [options.headers={}] - Additional headers.
6375
- * @param {AbortSignal|null} [options.signal] - External AbortSignal; disables timeout and retries.
6376
- * @returns {Promise<*>} Parsed JSON object.
6377
- * @throws {Error} Throws if fetch fails, times out, or returns invalid JSON.
6368
+ * @typedef {Object} FetchTemplateOptions
6369
+ * @property {string} [method="GET"] - HTTP method to use (GET, POST, etc.).
6370
+ * @property {any} [body] - Request body (only for methods like POST, PUT).
6371
+ * @property {number} [timeout=0] - Timeout in milliseconds (ignored if signal is provided).
6372
+ * @property {number} [retries=0] - Number of retry attempts (ignored if signal is provided).
6373
+ * @property {Headers|Record<string, *>} [headers={}] - Additional headers.
6374
+ * @property {AbortSignal|null} [signal] - External AbortSignal; disables timeout and retries.
6375
+ */
6376
+
6377
+ /**
6378
+ * @param {string} url - The full URL to fetch data from.
6379
+ * @param {FetchTemplateOptions} [options] - Optional settings.
6380
+ * @returns {Promise<Response>} Result data.
6381
+ * @throws {Error} Throws if fetch fails, times out.
6378
6382
  */
6379
- async function fetchJson(
6383
+ async function fetchTemplate(
6380
6384
  url,
6381
6385
  { method = 'GET', body, timeout = 0, retries = 0, headers = {}, signal = null } = {},
6382
6386
  ) {
@@ -6435,17 +6439,7 @@ async function fetchJson(
6435
6439
  if (timer) clearTimeout(timer);
6436
6440
 
6437
6441
  if (!response.ok) throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
6438
-
6439
- const contentType = response.headers.get('content-type') || '';
6440
- if (!contentType.includes('application/json'))
6441
- throw new Error(`Unexpected content-type: ${contentType}`);
6442
-
6443
- const data = await response.json();
6444
-
6445
- if (!Array.isArray(data) && !isJsonObject(data))
6446
- throw new Error('Received invalid data instead of valid JSON.');
6447
-
6448
- return data;
6442
+ return response;
6449
6443
  } catch (err) {
6450
6444
  lastError = /** @type {Error} */ (err);
6451
6445
  if (signal) break; // if an external signal came, it does not retry
@@ -6459,6 +6453,63 @@ async function fetchJson(
6459
6453
  );
6460
6454
  }
6461
6455
 
6456
+ /**
6457
+ * Loads and parses a JSON from a remote URL using Fetch API.
6458
+ *
6459
+ * @param {string} url - The full URL to fetch JSON from.
6460
+ * @param {Object} [options] - Optional settings.
6461
+ * @returns {Promise<any[] | Record<string | number | symbol, unknown>>} Parsed JSON object.
6462
+ * @throws {Error} Throws if fetch fails, times out, or returns invalid JSON.
6463
+ */
6464
+ async function fetchJson(url, options) {
6465
+ return new Promise((resolve, reject) => {
6466
+ fetchTemplate(url, options)
6467
+ .then(async (res) => {
6468
+ const contentType = res.headers.get('content-type') || '';
6469
+ if (!contentType.includes('application/json'))
6470
+ throw new Error(`Unexpected content-type: ${contentType}`);
6471
+
6472
+ const data = await res.json();
6473
+
6474
+ if (!Array.isArray(data) && !isJsonObject(data))
6475
+ throw new Error('Received invalid data instead of valid JSON.');
6476
+
6477
+ return resolve(data);
6478
+ })
6479
+ .catch(reject);
6480
+ });
6481
+ }
6482
+
6483
+ /**
6484
+ * Loads a remote file as a Blob using Fetch API.
6485
+ *
6486
+ * @param {string} url - The full URL to fetch the file from.
6487
+ * @param {Object} [options] - Optional fetch options.
6488
+ * @param {string[]} [allowedMimeTypes] - Optional list of accepted MIME types (e.g., ['image/jpeg']).
6489
+ * @returns {Promise<Blob>} - The fetched file as a Blob.
6490
+ * @throws {Error} Throws if fetch fails, response is not ok, or MIME type is not allowed.
6491
+ */
6492
+ async function fetchBlob(url, allowedMimeTypes, options) {
6493
+ return new Promise((resolve, reject) => {
6494
+ fetchTemplate(url, options)
6495
+ .then(async (res) => {
6496
+ const contentType = res.headers.get('content-type') || '';
6497
+
6498
+ if (
6499
+ Array.isArray(allowedMimeTypes) &&
6500
+ allowedMimeTypes.length > 0 &&
6501
+ !allowedMimeTypes.some((type) => contentType.includes(type))
6502
+ ) {
6503
+ throw new Error(`Blocked MIME type: ${contentType}`);
6504
+ }
6505
+
6506
+ const data = await res.blob();
6507
+ return resolve(data);
6508
+ })
6509
+ .catch(reject);
6510
+ });
6511
+ }
6512
+
6462
6513
  ///////////////////////////////////////////////////////////////////////////////
6463
6514
 
6464
6515
  /**
@@ -7045,6 +7096,21 @@ const {
7045
7096
  * @typedef {Element|Window} ElementAndWindow
7046
7097
  */
7047
7098
 
7099
+ /**
7100
+ * Represents a raw DOM element/window/document or an instance of TinyHtml.
7101
+ * This type is used to abstract interactions with both plain elements
7102
+ * and wrapped elements via the TinyHtml class.
7103
+ *
7104
+ * @typedef {ElementAndWinAndDoc|TinyHtml} TinyElementAndWinAndDoc
7105
+ */
7106
+
7107
+ /**
7108
+ * Represents a value that can be either a DOM Element, or the global Window object, or the document object.
7109
+ * Useful for functions that operate generically on scrollable or measurable targets.
7110
+ *
7111
+ * @typedef {Element|Window|Document} ElementAndWinAndDoc
7112
+ */
7113
+
7048
7114
  /**
7049
7115
  * A parameter type used for filtering or matching elements.
7050
7116
  * It can be:
@@ -7061,7 +7127,7 @@ const {
7061
7127
  * Elements accepted as constructor values for TinyHtml.
7062
7128
  * These include common DOM elements and root containers.
7063
7129
  *
7064
- * @typedef {Window|Element|Document} ConstructorElValues
7130
+ * @typedef {Window|Element|Document|Text} ConstructorElValues
7065
7131
  */
7066
7132
 
7067
7133
  /**
@@ -7182,40 +7248,114 @@ class TinyHtml_TinyHtml {
7182
7248
 
7183
7249
  static Utils = { ...collision_namespaceObject };
7184
7250
 
7251
+ /**
7252
+ * Creates a new DOM element with the specified tag name and options, then wraps it in a TinyHtml instance.
7253
+ *
7254
+ * @param {string} tagName - The tag name of the element to create (e.g., 'div', 'span').
7255
+ * @param {ElementCreationOptions} ops - Optional settings for creating the element.
7256
+ * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM element.
7257
+ * @throws {TypeError} If tagName is not a string or ops is not an object.
7258
+ */
7259
+ static createElement(tagName, ops) {
7260
+ if (typeof tagName !== 'string')
7261
+ throw new TypeError(`[TinyHtml] createElement(): The tagName must be a string.`);
7262
+ if (typeof ops !== 'undefined' && typeof ops !== 'object')
7263
+ throw new TypeError(`[TinyHtml] createElement(): The ops must be a object.`);
7264
+ return new TinyHtml_TinyHtml(document.createElement(tagName, ops));
7265
+ }
7266
+
7267
+ /**
7268
+ * Creates a new TinyHtml instance that wraps a DOM TextNode.
7269
+ *
7270
+ * This method is useful when you want to insert raw text content into the DOM
7271
+ * without it being interpreted as HTML. The returned instance behaves like any
7272
+ * other TinyHtml element and can be appended or manipulated as needed.
7273
+ *
7274
+ * @param {string} value - The plain text content to be wrapped in a TextNode.
7275
+ * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM TextNode.
7276
+ * @throws {TypeError} If the provided value is not a string.
7277
+ */
7278
+ static createTextNode(value) {
7279
+ if (typeof value !== 'string')
7280
+ throw new TypeError(`[TinyHtml] createTextNode(): The value must be a string.`);
7281
+ return new TinyHtml_TinyHtml(document.createTextNode(value));
7282
+ }
7283
+
7284
+ /**
7285
+ * Creates an HTMLElement or TextNode from an HTML string.
7286
+ * Supports both elements and plain text.
7287
+ *
7288
+ * @param {string} htmlString - The HTML string to convert.
7289
+ * @returns {TinyHtml} - A single HTMLElement or TextNode.
7290
+ */
7291
+ static createElementFromHTML(htmlString) {
7292
+ const template = document.createElement('template');
7293
+ htmlString = htmlString.trim();
7294
+
7295
+ // If it's only text (e.g., no "<" tag), return a TextNode
7296
+ if (!htmlString.startsWith('<')) {
7297
+ return TinyHtml_TinyHtml.createTextNode(htmlString);
7298
+ }
7299
+
7300
+ template.innerHTML = htmlString;
7301
+ if (!(template.content.firstChild instanceof Element)) throw new Error('');
7302
+ return new TinyHtml_TinyHtml(template.content.firstChild);
7303
+ }
7304
+
7185
7305
  /**
7186
7306
  * Queries the document for the first element matching the CSS selector and wraps it in a TinyHtml instance.
7187
7307
  *
7188
7308
  * @param {string} selector - A valid CSS selector string.
7189
- * @returns {TinyHtml} A TinyHtml instance wrapping the matched element.
7190
- * @throws {Error} If no element matches the selector.
7309
+ * @param {Document|Element} elem - Target element.
7310
+ * @returns {TinyHtml|null} A TinyHtml instance wrapping the matched element.
7191
7311
  */
7192
- static query(selector) {
7193
- const newEl = document.querySelector(selector);
7194
- if (!newEl) throw new Error(`[TinyHtml] query(): No element found for selector "${selector}".`);
7312
+ static query(selector, elem = document) {
7313
+ const newEl = elem.querySelector(selector);
7314
+ if (!newEl) return null;
7195
7315
  return new TinyHtml_TinyHtml(newEl);
7196
7316
  }
7197
7317
 
7318
+ /**
7319
+ * Queries the element for the first element matching the CSS selector and wraps it in a TinyHtml instance.
7320
+ *
7321
+ * @param {string} selector - A valid CSS selector string.
7322
+ * @returns {TinyHtml|null} A TinyHtml instance wrapping the matched element.
7323
+ */
7324
+ querySelector(selector) {
7325
+ return TinyHtml_TinyHtml.query(selector, TinyHtml_TinyHtml._preElem(this, 'query'));
7326
+ }
7327
+
7198
7328
  /**
7199
7329
  * Queries the document for all elements matching the CSS selector and wraps them in TinyHtml instances.
7200
7330
  *
7201
7331
  * @param {string} selector - A valid CSS selector string.
7332
+ * @param {Document|Element} elem - Target element.
7202
7333
  * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
7203
7334
  */
7204
- static queryAll(selector) {
7205
- const newEls = document.querySelectorAll(selector);
7335
+ static queryAll(selector, elem = document) {
7336
+ const newEls = elem.querySelectorAll(selector);
7206
7337
  return TinyHtml_TinyHtml.toTinyElm([...newEls]);
7207
7338
  }
7208
7339
 
7340
+ /**
7341
+ * Queries the element for all elements matching the CSS selector and wraps them in TinyHtml instances.
7342
+ *
7343
+ * @param {string} selector - A valid CSS selector string.
7344
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
7345
+ */
7346
+ querySelectorAll(selector) {
7347
+ return TinyHtml_TinyHtml.queryAll(selector, TinyHtml_TinyHtml._preElem(this, 'queryAll'));
7348
+ }
7349
+
7209
7350
  /**
7210
7351
  * Retrieves an element by its ID and wraps it in a TinyHtml instance.
7211
7352
  *
7212
7353
  * @param {string} selector - The ID of the element to retrieve.
7213
- * @returns {TinyHtml} A TinyHtml instance wrapping the found element.
7214
- * @throws {Error} If no element is found with the specified ID.
7354
+ * @returns {TinyHtml|null} A TinyHtml instance wrapping the found element.
7215
7355
  */
7216
7356
  static getById(selector) {
7217
7357
  const newEl = document.getElementById(selector);
7218
- if (!newEl) throw new Error(`[TinyHtml] getById(): No element found with ID "${selector}".`);
7358
+ if (!newEl) return null;
7219
7359
  return new TinyHtml_TinyHtml(newEl);
7220
7360
  }
7221
7361
 
@@ -7223,13 +7363,24 @@ class TinyHtml_TinyHtml {
7223
7363
  * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
7224
7364
  *
7225
7365
  * @param {string} selector - The class name to search for.
7366
+ * @param {Document|Element} elem - Target element.
7226
7367
  * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7227
7368
  */
7228
- static getByClassName(selector) {
7229
- const newEls = document.getElementsByClassName(selector);
7369
+ static getByClassName(selector, elem = document) {
7370
+ const newEls = elem.getElementsByClassName(selector);
7230
7371
  return TinyHtml_TinyHtml.toTinyElm([...newEls]);
7231
7372
  }
7232
7373
 
7374
+ /**
7375
+ * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
7376
+ *
7377
+ * @param {string} selector - The class name to search for.
7378
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7379
+ */
7380
+ getElementsByClassName(selector) {
7381
+ return TinyHtml_TinyHtml.getByClassName(selector, TinyHtml_TinyHtml._preElem(this, 'getByClassName'));
7382
+ }
7383
+
7233
7384
  /**
7234
7385
  * Retrieves all elements with the specified name attribute and wraps them in TinyHtml instances.
7235
7386
  *
@@ -7247,13 +7398,30 @@ class TinyHtml_TinyHtml {
7247
7398
  *
7248
7399
  * @param {string} localName - The local name (tag) of the elements to search for.
7249
7400
  * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
7401
+ * @param {Document|Element} elem - Target element.
7250
7402
  * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7251
7403
  */
7252
- static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
7253
- const newEls = document.getElementsByTagNameNS(namespaceURI, localName);
7404
+ static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml', elem = document) {
7405
+ const newEls = elem.getElementsByTagNameNS(namespaceURI, localName);
7254
7406
  return TinyHtml_TinyHtml.toTinyElm([...newEls]);
7255
7407
  }
7256
7408
 
7409
+ /**
7410
+ * Retrieves all elements with the specified local tag name within the given namespace URI,
7411
+ * and wraps them in TinyHtml instances.
7412
+ *
7413
+ * @param {string} localName - The local name (tag) of the elements to search for.
7414
+ * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
7415
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7416
+ */
7417
+ getElementsByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
7418
+ return TinyHtml_TinyHtml.getByTagNameNS(
7419
+ localName,
7420
+ namespaceURI,
7421
+ TinyHtml_TinyHtml._preElem(this, 'getByTagNameNS'),
7422
+ );
7423
+ }
7424
+
7257
7425
  //////////////////////////////////////////////////////////////////
7258
7426
 
7259
7427
  /**
@@ -7534,6 +7702,45 @@ class TinyHtml_TinyHtml {
7534
7702
  return TinyHtml_TinyHtml._preElemTemplate(elems, where, [Element, Window], ['Element', 'Window']);
7535
7703
  }
7536
7704
 
7705
+ /**
7706
+ * Ensures the input is returned as an array.
7707
+ * Useful to normalize operations across multiple or single element/window/document elements.
7708
+ *
7709
+ * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/window element or array of html elements.
7710
+ * @param {string} where - The method or context name where validation is being called.
7711
+ * @returns {ElementAndWindow[]} - Always returns an array of element/window elements.
7712
+ * @readonly
7713
+ */
7714
+ static _preElemsAndWinAndDoc(elems, where) {
7715
+ const result = TinyHtml_TinyHtml._preElemsTemplate(
7716
+ elems,
7717
+ where,
7718
+ [Element, Window, Document],
7719
+ ['Element', 'Window', 'Document'],
7720
+ );
7721
+ return result.map((elem) => (!(elem instanceof Document) ? elem : elem.documentElement));
7722
+ }
7723
+
7724
+ /**
7725
+ * Ensures the input is returned as an single element/window/document element.
7726
+ * Useful to normalize operations across multiple or single element/window/document elements.
7727
+ *
7728
+ * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/window element or array of html elements.
7729
+ * @param {string} where - The method or context name where validation is being called.
7730
+ * @returns {ElementAndWindow} - Always returns an single element/window element.
7731
+ * @readonly
7732
+ */
7733
+ static _preElemAndWinAndDoc(elems, where) {
7734
+ const result = TinyHtml_TinyHtml._preElemTemplate(
7735
+ elems,
7736
+ where,
7737
+ [Element, Window, Document],
7738
+ ['Element', 'Window', 'Document'],
7739
+ );
7740
+ if (result instanceof Document) return result.documentElement;
7741
+ return result;
7742
+ }
7743
+
7537
7744
  /**
7538
7745
  * Normalizes and converts one or more DOM elements (or TinyHtml instances)
7539
7746
  * into an array of `TinyHtml` instances.
@@ -7880,12 +8087,13 @@ class TinyHtml_TinyHtml {
7880
8087
  * @param {string} key - The key under which the data will be stored.
7881
8088
  * @param {any} value - The value to store.
7882
8089
  * @param {boolean} [isPrivate=false] - Whether to store the data in the private store.
7883
- * @returns {void}
8090
+ * @returns {TinyElement}
7884
8091
  */
7885
8092
  static setData(el, key, value, isPrivate = false) {
7886
8093
  const data = TinyHtml_TinyHtml._dataSelector[!isPrivate ? 'public' : 'private']('setData', el);
7887
8094
  if (typeof key !== 'string') throw new TypeError('The key must be a string.');
7888
8095
  data[key] = value;
8096
+ return el;
7889
8097
  }
7890
8098
 
7891
8099
  /**
@@ -7894,7 +8102,7 @@ class TinyHtml_TinyHtml {
7894
8102
  * @param {string} key - The key under which the data will be stored.
7895
8103
  * @param {any} value - The value to store.
7896
8104
  * @param {boolean} [isPrivate=false] - Whether to store the data in the private store.
7897
- * @returns {void}
8105
+ * @returns {TinyElement}
7898
8106
  */
7899
8107
  setData(key, value, isPrivate = false) {
7900
8108
  return TinyHtml_TinyHtml.setData(this, key, value, isPrivate);
@@ -8241,18 +8449,21 @@ class TinyHtml_TinyHtml {
8241
8449
  /**
8242
8450
  * Appends child elements or strings to the end of the target element(s).
8243
8451
  *
8244
- * @param {TinyElement | TinyElement[]} el - The target element(s) to receive children.
8452
+ * @param {TinyElement} el - The target element(s) to receive children.
8245
8453
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to append.
8454
+ * @returns {TinyElement}
8246
8455
  */
8247
8456
  static append(el, ...children) {
8248
8457
  const elem = TinyHtml_TinyHtml._preElem(el, 'append');
8249
8458
  elem.append(...TinyHtml_TinyHtml._appendChecker('append', ...children));
8459
+ return el;
8250
8460
  }
8251
8461
 
8252
8462
  /**
8253
8463
  * Appends child elements or strings to the end of the target element(s).
8254
8464
  *
8255
8465
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to append.
8466
+ * @returns {TinyElement}
8256
8467
  */
8257
8468
  append(...children) {
8258
8469
  return TinyHtml_TinyHtml.append(this, ...children);
@@ -8261,18 +8472,21 @@ class TinyHtml_TinyHtml {
8261
8472
  /**
8262
8473
  * Prepends child elements or strings to the beginning of the target element(s).
8263
8474
  *
8264
- * @param {TinyElement | TinyElement[]} el - The target element(s) to receive children.
8475
+ * @param {TinyElement} el - The target element(s) to receive children.
8265
8476
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to prepend.
8477
+ * @returns {TinyElement}
8266
8478
  */
8267
8479
  static prepend(el, ...children) {
8268
8480
  const elem = TinyHtml_TinyHtml._preElem(el, 'prepend');
8269
8481
  elem.prepend(...TinyHtml_TinyHtml._appendChecker('prepend', ...children));
8482
+ return el;
8270
8483
  }
8271
8484
 
8272
8485
  /**
8273
8486
  * Prepends child elements or strings to the beginning of the target element(s).
8274
8487
  *
8275
8488
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to prepend.
8489
+ * @returns {TinyElement}
8276
8490
  */
8277
8491
  prepend(...children) {
8278
8492
  return TinyHtml_TinyHtml.prepend(this, ...children);
@@ -8281,18 +8495,21 @@ class TinyHtml_TinyHtml {
8281
8495
  /**
8282
8496
  * Inserts elements or strings immediately before the target element(s) in the DOM.
8283
8497
  *
8284
- * @param {TinyElement | TinyElement[]} el - The target element(s) before which new content is inserted.
8498
+ * @param {TinyElement} el - The target element(s) before which new content is inserted.
8285
8499
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert before the target.
8500
+ * @returns {TinyElement}
8286
8501
  */
8287
8502
  static before(el, ...children) {
8288
8503
  const elem = TinyHtml_TinyHtml._preElem(el, 'before');
8289
8504
  elem.before(...TinyHtml_TinyHtml._appendChecker('before', ...children));
8505
+ return el;
8290
8506
  }
8291
8507
 
8292
8508
  /**
8293
8509
  * Inserts elements or strings immediately before the target element(s) in the DOM.
8294
8510
  *
8295
8511
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert before the target.
8512
+ * @returns {TinyElement}
8296
8513
  */
8297
8514
  before(...children) {
8298
8515
  return TinyHtml_TinyHtml.before(this, ...children);
@@ -8301,18 +8518,21 @@ class TinyHtml_TinyHtml {
8301
8518
  /**
8302
8519
  * Inserts elements or strings immediately after the target element(s) in the DOM.
8303
8520
  *
8304
- * @param {TinyElement | TinyElement[]} el - The target element(s) after which new content is inserted.
8521
+ * @param {TinyElement} el - The target element(s) after which new content is inserted.
8305
8522
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert after the target.
8523
+ * @returns {TinyElement}
8306
8524
  */
8307
8525
  static after(el, ...children) {
8308
8526
  const elem = TinyHtml_TinyHtml._preElem(el, 'after');
8309
8527
  elem.after(...TinyHtml_TinyHtml._appendChecker('after', ...children));
8528
+ return el;
8310
8529
  }
8311
8530
 
8312
8531
  /**
8313
8532
  * Inserts elements or strings immediately after the target element(s) in the DOM.
8314
8533
  *
8315
8534
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert after the target.
8535
+ * @returns {TinyElement}
8316
8536
  */
8317
8537
  after(...children) {
8318
8538
  return TinyHtml_TinyHtml.after(this, ...children);
@@ -8321,18 +8541,21 @@ class TinyHtml_TinyHtml {
8321
8541
  /**
8322
8542
  * Replaces the target element(s) in the DOM with new elements or text.
8323
8543
  *
8324
- * @param {TinyElement | TinyElement[]} el - The element(s) to be replaced.
8544
+ * @param {TinyElement} el - The element(s) to be replaced.
8325
8545
  * @param {...(TinyNode | TinyNode[] | string)} newNodes - New elements or text to replace the target.
8546
+ * @returns {TinyElement}
8326
8547
  */
8327
8548
  static replaceWith(el, ...newNodes) {
8328
8549
  const elem = TinyHtml_TinyHtml._preElem(el, 'replaceWith');
8329
8550
  elem.replaceWith(...TinyHtml_TinyHtml._appendChecker('replaceWith', ...newNodes));
8551
+ return el;
8330
8552
  }
8331
8553
 
8332
8554
  /**
8333
8555
  * Replaces the target element(s) in the DOM with new elements or text.
8334
8556
  *
8335
8557
  * @param {...(TinyNode | TinyNode[] | string)} newNodes - New elements or text to replace the target.
8558
+ * @returns {TinyElement}
8336
8559
  */
8337
8560
  replaceWith(...newNodes) {
8338
8561
  return TinyHtml_TinyHtml.replaceWith(this, ...newNodes);
@@ -8343,6 +8566,7 @@ class TinyHtml_TinyHtml {
8343
8566
  *
8344
8567
  * @param {TinyNode | TinyNode[]} el - The element(s) to append.
8345
8568
  * @param {TinyNode | TinyNode[]} targets - Target element(s) where content will be appended.
8569
+ * @returns {TinyNode|TinyNode[]}
8346
8570
  */
8347
8571
  static appendTo(el, targets) {
8348
8572
  const elems = TinyHtml_TinyHtml._preNodeElems(el, 'appendTo');
@@ -8352,12 +8576,14 @@ class TinyHtml_TinyHtml {
8352
8576
  target.appendChild(i === tars.length - 1 ? elem : elem.cloneNode(true)),
8353
8577
  );
8354
8578
  });
8579
+ return el;
8355
8580
  }
8356
8581
 
8357
8582
  /**
8358
8583
  * Appends the given element(s) to each target element in sequence.
8359
8584
  *
8360
8585
  * @param {TinyNode | TinyNode[]} targets - Target element(s) where content will be appended.
8586
+ * @returns {TinyNode|TinyNode[]}
8361
8587
  */
8362
8588
  appendTo(targets) {
8363
8589
  return TinyHtml_TinyHtml.appendTo(this, targets);
@@ -8368,6 +8594,7 @@ class TinyHtml_TinyHtml {
8368
8594
  *
8369
8595
  * @param {TinyElement | TinyElement[]} el - The element(s) to prepend.
8370
8596
  * @param {TinyElement | TinyElement[]} targets - Target element(s) where content will be prepended.
8597
+ * @returns {TinyElement|TinyElement[]}
8371
8598
  */
8372
8599
  static prependTo(el, targets) {
8373
8600
  const elems = TinyHtml_TinyHtml._preElems(el, 'prependTo');
@@ -8378,12 +8605,14 @@ class TinyHtml_TinyHtml {
8378
8605
  .reverse()
8379
8606
  .forEach((elem) => target.prepend(i === tars.length - 1 ? elem : elem.cloneNode(true)));
8380
8607
  });
8608
+ return el;
8381
8609
  }
8382
8610
 
8383
8611
  /**
8384
8612
  * Prepends the given element(s) to each target element in sequence.
8385
8613
  *
8386
8614
  * @param {TinyElement | TinyElement[]} targets - Target element(s) where content will be prepended.
8615
+ * @returns {TinyElement|TinyElement[]}
8387
8616
  */
8388
8617
  prependTo(targets) {
8389
8618
  return TinyHtml_TinyHtml.prependTo(this, targets);
@@ -8395,6 +8624,7 @@ class TinyHtml_TinyHtml {
8395
8624
  * @param {TinyNode | TinyNode[]} el - The element(s) to insert.
8396
8625
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8397
8626
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert before, defaults to target.
8627
+ * @returns {TinyNode|TinyNode[]}
8398
8628
  */
8399
8629
  static insertBefore(el, target, child = null) {
8400
8630
  const elem = TinyHtml_TinyHtml._preNodeElem(el, 'insertBefore');
@@ -8402,6 +8632,7 @@ class TinyHtml_TinyHtml {
8402
8632
  const childNode = TinyHtml_TinyHtml._preNodeElemWithNull(child, 'insertBefore');
8403
8633
  if (!targ.parentNode) throw new Error('');
8404
8634
  targ.parentNode.insertBefore(elem, childNode || targ);
8635
+ return el;
8405
8636
  }
8406
8637
 
8407
8638
  /**
@@ -8409,6 +8640,7 @@ class TinyHtml_TinyHtml {
8409
8640
  *
8410
8641
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8411
8642
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert before, defaults to target.
8643
+ * @returns {TinyNode|TinyNode[]}
8412
8644
  */
8413
8645
  insertBefore(target, child) {
8414
8646
  return TinyHtml_TinyHtml.insertBefore(this, target, child);
@@ -8420,6 +8652,7 @@ class TinyHtml_TinyHtml {
8420
8652
  * @param {TinyNode | TinyNode[]} el - The element(s) to insert.
8421
8653
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8422
8654
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert after, defaults to target.
8655
+ * @returns {TinyNode|TinyNode[]}
8423
8656
  */
8424
8657
  static insertAfter(el, target, child = null) {
8425
8658
  const elem = TinyHtml_TinyHtml._preNodeElem(el, 'insertAfter');
@@ -8427,6 +8660,7 @@ class TinyHtml_TinyHtml {
8427
8660
  const childNode = TinyHtml_TinyHtml._preNodeElemWithNull(child, 'insertBefore');
8428
8661
  if (!targ.parentNode) throw new Error('');
8429
8662
  targ.parentNode.insertBefore(elem, childNode || targ.nextSibling);
8663
+ return el;
8430
8664
  }
8431
8665
 
8432
8666
  /**
@@ -8434,6 +8668,7 @@ class TinyHtml_TinyHtml {
8434
8668
  *
8435
8669
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8436
8670
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert after, defaults to target.
8671
+ * @returns {TinyNode|TinyNode[]}
8437
8672
  */
8438
8673
  insertAfter(target, child) {
8439
8674
  return TinyHtml_TinyHtml.insertAfter(this, target, child);
@@ -8445,6 +8680,7 @@ class TinyHtml_TinyHtml {
8445
8680
  *
8446
8681
  * @param {TinyNode | TinyNode[]} el - The new element(s) to insert.
8447
8682
  * @param {TinyNode | TinyNode[]} targets - The elements to be replaced.
8683
+ * @returns {TinyNode|TinyNode[]}
8448
8684
  */
8449
8685
  static replaceAll(el, targets) {
8450
8686
  const elems = TinyHtml_TinyHtml._preNodeElems(el, 'replaceAll');
@@ -8456,6 +8692,7 @@ class TinyHtml_TinyHtml {
8456
8692
  parent.replaceChild(i === tars.length - 1 ? elem : elem.cloneNode(true), target);
8457
8693
  });
8458
8694
  });
8695
+ return el;
8459
8696
  }
8460
8697
 
8461
8698
  /**
@@ -8463,6 +8700,7 @@ class TinyHtml_TinyHtml {
8463
8700
  * If multiple targets exist, the inserted elements are cloned accordingly.
8464
8701
  *
8465
8702
  * @param {TinyNode | TinyNode[]} targets - The elements to be replaced.
8703
+ * @returns {TinyNode|TinyNode[]}
8466
8704
  */
8467
8705
  replaceAll(targets) {
8468
8706
  return TinyHtml_TinyHtml.replaceAll(this, targets);
@@ -8486,7 +8724,12 @@ class TinyHtml_TinyHtml {
8486
8724
  throw new Error(
8487
8725
  `[TinyHtml] You are trying to put a TinyHtml inside another TinyHtml in constructor.`,
8488
8726
  );
8489
- if (!(el instanceof Element) && !(el instanceof Window) && !(el instanceof Document))
8727
+ if (
8728
+ !(el instanceof Element) &&
8729
+ !(el instanceof Window) &&
8730
+ !(el instanceof Document) &&
8731
+ !(el instanceof Text)
8732
+ )
8490
8733
  throw new Error(`[TinyHtml] Invalid Target in constructor.`);
8491
8734
  this.#el = el;
8492
8735
  }
@@ -8936,6 +9179,7 @@ class TinyHtml_TinyHtml {
8936
9179
  * @param {TinyHtmlElement|TinyHtmlElement[]} el - The element to inspect.
8937
9180
  * @param {string|Object} prop - The property name or an object with key-value pairs
8938
9181
  * @param {string|null} [value=null] - The value to set (if `prop` is a string)
9182
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
8939
9183
  */
8940
9184
  static setStyle(el, prop, value = null) {
8941
9185
  TinyHtml_TinyHtml._preHtmlElems(el, 'setStyle').forEach((elem) => {
@@ -8948,6 +9192,7 @@ class TinyHtml_TinyHtml {
8948
9192
  }
8949
9193
  } else elem.style.setProperty(TinyHtml_TinyHtml.toStyleKc(prop), value);
8950
9194
  });
9195
+ return el;
8951
9196
  }
8952
9197
 
8953
9198
  /**
@@ -8958,6 +9203,7 @@ class TinyHtml_TinyHtml {
8958
9203
  *
8959
9204
  * @param {string|Object} prop - The property name or an object with key-value pairs
8960
9205
  * @param {string|null} [value=null] - The value to set (if `prop` is a string)
9206
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
8961
9207
  */
8962
9208
  setStyle(prop, value) {
8963
9209
  return TinyHtml_TinyHtml.setStyle(this, prop, value);
@@ -9053,6 +9299,7 @@ class TinyHtml_TinyHtml {
9053
9299
  *
9054
9300
  * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element or an array of elements.
9055
9301
  * @param {string|string[]} prop - A property name or an array of property names to remove.
9302
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9056
9303
  */
9057
9304
  static removeStyle(el, prop) {
9058
9305
  TinyHtml_TinyHtml._preHtmlElems(el, 'removeStyle').forEach((elem) => {
@@ -9062,12 +9309,14 @@ class TinyHtml_TinyHtml {
9062
9309
  }
9063
9310
  } else elem.style.removeProperty(TinyHtml_TinyHtml.toStyleKc(prop));
9064
9311
  });
9312
+ return el;
9065
9313
  }
9066
9314
 
9067
9315
  /**
9068
9316
  * Removes one or more inline CSS properties from the given element(s).
9069
9317
  *
9070
9318
  * @param {string|string[]} prop - A property name or an array of property names to remove.
9319
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9071
9320
  */
9072
9321
  removeStyle(prop) {
9073
9322
  return TinyHtml_TinyHtml.removeStyle(this, prop);
@@ -9082,6 +9331,7 @@ class TinyHtml_TinyHtml {
9082
9331
  * @param {string} prop - The CSS property to toggle.
9083
9332
  * @param {string} val1 - The first value (used as "current" check).
9084
9333
  * @param {string} val2 - The second value (used as the "alternative").
9334
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9085
9335
  */
9086
9336
  static toggleStyle(el, prop, val1, val2) {
9087
9337
  TinyHtml_TinyHtml._preHtmlElems(el, 'toggleStyle').forEach((elem) => {
@@ -9089,6 +9339,7 @@ class TinyHtml_TinyHtml {
9089
9339
  const newVal = current === TinyHtml_TinyHtml.toStyleKc(val1) ? val2 : val1;
9090
9340
  TinyHtml_TinyHtml.setStyle(elem, prop, newVal);
9091
9341
  });
9342
+ return el;
9092
9343
  }
9093
9344
 
9094
9345
  /**
@@ -9099,6 +9350,7 @@ class TinyHtml_TinyHtml {
9099
9350
  * @param {string} prop - The CSS property to toggle.
9100
9351
  * @param {string} val1 - The first value (used as "current" check).
9101
9352
  * @param {string} val2 - The second value (used as the "alternative").
9353
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9102
9354
  */
9103
9355
  toggleStyle(prop, val1, val2) {
9104
9356
  return TinyHtml_TinyHtml.toggleStyle(this, prop, val1, val2);
@@ -9108,14 +9360,16 @@ class TinyHtml_TinyHtml {
9108
9360
  * Removes all inline styles (`style` attribute) from the given element(s).
9109
9361
  *
9110
9362
  * @param {TinyElement|TinyElement[]} el - A single element or an array of elements.
9363
+ * @returns {TinyElement|TinyElement[]}
9111
9364
  */
9112
9365
  static clearStyle(el) {
9113
9366
  TinyHtml_TinyHtml._preElems(el, 'clearStyle').forEach((elem) => elem.removeAttribute('style'));
9367
+ return el;
9114
9368
  }
9115
9369
 
9116
9370
  /**
9117
9371
  * Removes all inline styles (`style` attribute) from the given element(s).
9118
- *
9372
+ * @returns {TinyElement|TinyElement[]}
9119
9373
  */
9120
9374
  clearStyle() {
9121
9375
  return TinyHtml_TinyHtml.clearStyle(this);
@@ -9127,14 +9381,17 @@ class TinyHtml_TinyHtml {
9127
9381
  * Focus the element.
9128
9382
  *
9129
9383
  * @param {TinyHtmlElement} el - The element or a selector string.
9384
+ * @returns {TinyHtmlElement}
9130
9385
  */
9131
9386
  static focus(el) {
9132
9387
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'focus');
9133
9388
  elem.focus();
9389
+ return el;
9134
9390
  }
9135
9391
 
9136
9392
  /**
9137
9393
  * Focus the element.
9394
+ * @returns {TinyHtmlElement}
9138
9395
  */
9139
9396
  focus() {
9140
9397
  return TinyHtml_TinyHtml.focus(this);
@@ -9144,19 +9401,46 @@ class TinyHtml_TinyHtml {
9144
9401
  * Blur the element.
9145
9402
  *
9146
9403
  * @param {TinyHtmlElement} el - The element or a selector string.
9404
+ * @returns {TinyHtmlElement}
9147
9405
  */
9148
9406
  static blur(el) {
9149
9407
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'blur');
9150
9408
  elem.blur();
9409
+ return el;
9151
9410
  }
9152
9411
 
9153
9412
  /**
9154
9413
  * Blur the element.
9414
+ * @returns {TinyHtmlElement}
9155
9415
  */
9156
9416
  blur() {
9157
9417
  return TinyHtml_TinyHtml.blur(this);
9158
9418
  }
9159
9419
 
9420
+ /**
9421
+ * Interprets a value as a boolean `true` if it matches a common truthy representation.
9422
+ *
9423
+ * This method checks if the input is any of the common forms used to represent `true`,
9424
+ * such as the string `'true'`, `'1'`, `'on'`, the boolean `true`, or the number `1`.
9425
+ *
9426
+ * @param {string|boolean|number} [value] - The value to interpret as boolean.
9427
+ * @returns {boolean} `true` if the value represents a truthy state, otherwise `false`.
9428
+ */
9429
+ static boolCheck(value) {
9430
+ if (
9431
+ typeof value !== 'undefined' &&
9432
+ (value === 'true' ||
9433
+ value === '1' ||
9434
+ value === true ||
9435
+ value === 'on' ||
9436
+ (typeof value === 'number' && value > 0))
9437
+ ) {
9438
+ return true;
9439
+ } else {
9440
+ return false;
9441
+ }
9442
+ }
9443
+
9160
9444
  //////////////////////////////////////////////////////////////////////
9161
9445
 
9162
9446
  /**
@@ -9209,16 +9493,40 @@ class TinyHtml_TinyHtml {
9209
9493
  return window.innerWidth || document.documentElement.clientWidth;
9210
9494
  }
9211
9495
 
9496
+ /**
9497
+ * Checks if the page is currently scrolled to the very top.
9498
+ *
9499
+ * This method compares the current vertical scroll position with the total document height.
9500
+ * It's useful for detecting if the user has reached the top of the page.
9501
+ *
9502
+ * @returns {boolean} `true` if the page is scrolled to the top, otherwise `false`.
9503
+ */
9504
+ static isPageTop() {
9505
+ return window.scrollY === 0;
9506
+ }
9507
+
9508
+ /**
9509
+ * Checks if the page is currently scrolled to the very bottom.
9510
+ *
9511
+ * This method uses the `scrollY` and `innerHeight` properties to determine if the
9512
+ * user has reached the end of the document.
9513
+ *
9514
+ * @returns {boolean} `true` if the page is scrolled to the bottom, otherwise `false`.
9515
+ */
9516
+ static isPageBottom() {
9517
+ return window.innerHeight + window.scrollY >= document.body.offsetHeight;
9518
+ }
9519
+
9212
9520
  /**
9213
9521
  * Gets the width or height of an element based on the box model.
9214
- * @param {TinyElementAndWindow} el - The element or window.
9522
+ * @param {TinyElementAndWinAndDoc} el - The element or window.
9215
9523
  * @param {"width"|"height"} type - Dimension type.
9216
9524
  * @param {"content"|"padding"|"border"|"margin"} [extra='content'] - Box model context.
9217
9525
  * @returns {number} - Computed dimension.
9218
9526
  * @throws {TypeError} If `type` or `extra` is not a string.
9219
9527
  */
9220
9528
  static getDimension(el, type, extra = 'content') {
9221
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'getDimension');
9529
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'getDimension');
9222
9530
  if (typeof type !== 'string') throw new TypeError('The type must be a string.');
9223
9531
  if (typeof extra !== 'string') throw new TypeError('The extra must be a string.');
9224
9532
 
@@ -9306,17 +9614,20 @@ class TinyHtml_TinyHtml {
9306
9614
  * @param {TinyHtmlElement} el - Target element.
9307
9615
  * @param {string|number} value - Height value.
9308
9616
  * @throws {TypeError} If `value` is neither a string nor number.
9617
+ * @returns {TinyHtmlElement}
9309
9618
  */
9310
9619
  static setHeight(el, value) {
9311
9620
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'setHeight');
9312
9621
  if (typeof value !== 'number' && typeof value !== 'string')
9313
9622
  throw new TypeError('The value must be a string or number.');
9314
9623
  elem.style.height = typeof value === 'number' ? `${value}px` : value;
9624
+ return el;
9315
9625
  }
9316
9626
 
9317
9627
  /**
9318
9628
  * Sets the height of the element.
9319
9629
  * @param {string|number} value - Height value.
9630
+ * @returns {TinyHtmlElement}
9320
9631
  */
9321
9632
  setHeight(value) {
9322
9633
  return TinyHtml_TinyHtml.setHeight(this, value);
@@ -9327,17 +9638,20 @@ class TinyHtml_TinyHtml {
9327
9638
  * @param {TinyHtmlElement} el - Target element.
9328
9639
  * @param {string|number} value - Width value.
9329
9640
  * @throws {TypeError} If `value` is neither a string nor number.
9641
+ * @returns {TinyHtmlElement}
9330
9642
  */
9331
9643
  static setWidth(el, value) {
9332
9644
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'setWidth');
9333
9645
  if (typeof value !== 'number' && typeof value !== 'string')
9334
9646
  throw new TypeError('The value must be a string or number.');
9335
9647
  elem.style.width = typeof value === 'number' ? `${value}px` : value;
9648
+ return el;
9336
9649
  }
9337
9650
 
9338
9651
  /**
9339
9652
  * Sets the width of the element.
9340
9653
  * @param {string|number} value - Width value.
9654
+ * @returns {TinyHtmlElement}
9341
9655
  */
9342
9656
  setWidth(value) {
9343
9657
  return TinyHtml_TinyHtml.setWidth(this, value);
@@ -9345,11 +9659,11 @@ class TinyHtml_TinyHtml {
9345
9659
 
9346
9660
  /**
9347
9661
  * Returns content box height.
9348
- * @param {TinyElementAndWindow} el - Target element.
9662
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9349
9663
  * @returns {number}
9350
9664
  */
9351
9665
  static height(el) {
9352
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'height');
9666
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'height');
9353
9667
  return TinyHtml_TinyHtml.getDimension(elem, 'height', 'content');
9354
9668
  }
9355
9669
 
@@ -9363,11 +9677,11 @@ class TinyHtml_TinyHtml {
9363
9677
 
9364
9678
  /**
9365
9679
  * Returns content box width.
9366
- * @param {TinyElementAndWindow} el - Target element.
9680
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9367
9681
  * @returns {number}
9368
9682
  */
9369
9683
  static width(el) {
9370
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'width');
9684
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'width');
9371
9685
  return TinyHtml_TinyHtml.getDimension(elem, 'width', 'content');
9372
9686
  }
9373
9687
 
@@ -9381,11 +9695,11 @@ class TinyHtml_TinyHtml {
9381
9695
 
9382
9696
  /**
9383
9697
  * Returns padding box height.
9384
- * @param {TinyElementAndWindow} el - Target element.
9698
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9385
9699
  * @returns {number}
9386
9700
  */
9387
9701
  static innerHeight(el) {
9388
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'innerHeight');
9702
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'innerHeight');
9389
9703
  return TinyHtml_TinyHtml.getDimension(elem, 'height', 'padding');
9390
9704
  }
9391
9705
 
@@ -9399,11 +9713,11 @@ class TinyHtml_TinyHtml {
9399
9713
 
9400
9714
  /**
9401
9715
  * Returns padding box width.
9402
- * @param {TinyElementAndWindow} el - Target element.
9716
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9403
9717
  * @returns {number}
9404
9718
  */
9405
9719
  static innerWidth(el) {
9406
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'innerWidth');
9720
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'innerWidth');
9407
9721
  return TinyHtml_TinyHtml.getDimension(elem, 'width', 'padding');
9408
9722
  }
9409
9723
 
@@ -9417,14 +9731,14 @@ class TinyHtml_TinyHtml {
9417
9731
 
9418
9732
  /**
9419
9733
  * Returns outer height of the element, optionally including margin.
9420
- * @param {TinyElementAndWindow} el - Target element.
9734
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9421
9735
  * @param {boolean} [includeMargin=false] - Whether to include margin.
9422
9736
  * @returns {number}
9423
9737
  */
9424
9738
  static outerHeight(el, includeMargin = false) {
9425
9739
  if (typeof includeMargin !== 'boolean')
9426
9740
  throw new TypeError('The "includeMargin" must be a boolean.');
9427
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'outerHeight');
9741
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'outerHeight');
9428
9742
  return TinyHtml_TinyHtml.getDimension(elem, 'height', includeMargin ? 'margin' : 'border');
9429
9743
  }
9430
9744
 
@@ -9439,14 +9753,14 @@ class TinyHtml_TinyHtml {
9439
9753
 
9440
9754
  /**
9441
9755
  * Returns outer width of the element, optionally including margin.
9442
- * @param {TinyElementAndWindow} el - Target element.
9756
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9443
9757
  * @param {boolean} [includeMargin=false] - Whether to include margin.
9444
9758
  * @returns {number}
9445
9759
  */
9446
9760
  static outerWidth(el, includeMargin = false) {
9447
9761
  if (typeof includeMargin !== 'boolean')
9448
9762
  throw new TypeError('The "includeMargin" must be a boolean.');
9449
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'outerWidth');
9763
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'outerWidth');
9450
9764
  return TinyHtml_TinyHtml.getDimension(elem, 'width', includeMargin ? 'margin' : 'border');
9451
9765
  }
9452
9766
 
@@ -9619,6 +9933,7 @@ class TinyHtml_TinyHtml {
9619
9933
  * Sets the vertical scroll position.
9620
9934
  * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
9621
9935
  * @param {number} value - Scroll top value.
9936
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9622
9937
  */
9623
9938
  static setScrollTop(el, value) {
9624
9939
  if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
@@ -9632,11 +9947,13 @@ class TinyHtml_TinyHtml {
9632
9947
  elem.scrollTop = value;
9633
9948
  }
9634
9949
  });
9950
+ return el;
9635
9951
  }
9636
9952
 
9637
9953
  /**
9638
9954
  * Sets the vertical scroll position.
9639
9955
  * @param {number} value - Scroll top value.
9956
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9640
9957
  */
9641
9958
  setScrollTop(value) {
9642
9959
  return TinyHtml_TinyHtml.setScrollTop(this, value);
@@ -9646,6 +9963,7 @@ class TinyHtml_TinyHtml {
9646
9963
  * Sets the horizontal scroll position.
9647
9964
  * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
9648
9965
  * @param {number} value - Scroll left value.
9966
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9649
9967
  */
9650
9968
  static setScrollLeft(el, value) {
9651
9969
  if (typeof value !== 'number') throw new TypeError('ScrollLeft value must be a number.');
@@ -9659,11 +9977,13 @@ class TinyHtml_TinyHtml {
9659
9977
  elem.scrollLeft = value;
9660
9978
  }
9661
9979
  });
9980
+ return el;
9662
9981
  }
9663
9982
 
9664
9983
  /**
9665
9984
  * Sets the horizontal scroll position.
9666
9985
  * @param {number} value - Scroll left value.
9986
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9667
9987
  */
9668
9988
  setScrollLeft(value) {
9669
9989
  return TinyHtml_TinyHtml.setScrollLeft(this, value);
@@ -9794,15 +10114,16 @@ class TinyHtml_TinyHtml {
9794
10114
 
9795
10115
  /**
9796
10116
  * Adds one or more CSS class names to the element.
9797
- * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => void} - One or more class names to add.
10117
+ * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to add.
9798
10118
  */
9799
10119
  static addClass(el, ...args) {
9800
10120
  TinyHtml_TinyHtml._preElems(el, 'addClass').forEach((elem) => elem.classList.add(...args));
10121
+ return el;
9801
10122
  }
9802
10123
 
9803
10124
  /**
9804
10125
  * Adds one or more CSS class names to the element.
9805
- * @type {(...tokens: string[]) => void} - One or more class names to add.
10126
+ * @type {(...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to add.
9806
10127
  */
9807
10128
  addClass(...args) {
9808
10129
  return TinyHtml_TinyHtml.addClass(this, ...args);
@@ -9810,15 +10131,16 @@ class TinyHtml_TinyHtml {
9810
10131
 
9811
10132
  /**
9812
10133
  * Removes one or more CSS class names from the element.
9813
- * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => void} - One or more class names to remove.
10134
+ * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to remove.
9814
10135
  */
9815
10136
  static removeClass(el, ...args) {
9816
10137
  TinyHtml_TinyHtml._preElems(el, 'removeClass').forEach((elem) => elem.classList.remove(...args));
10138
+ return el;
9817
10139
  }
9818
10140
 
9819
10141
  /**
9820
10142
  * Removes one or more CSS class names from the element.
9821
- * @type {(...tokens: string[]) => void} - One or more class names to remove.
10143
+ * @type {(...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to remove.
9822
10144
  */
9823
10145
  removeClass(...args) {
9824
10146
  return TinyHtml_TinyHtml.removeClass(this, ...args);
@@ -10028,15 +10350,18 @@ class TinyHtml_TinyHtml {
10028
10350
  * Set text content of elements.
10029
10351
  * @param {TinyElement|TinyElement[]} el
10030
10352
  * @param {string} value
10353
+ * @returns {TinyElement|TinyElement[]}
10031
10354
  */
10032
10355
  static setText(el, value) {
10033
10356
  if (typeof value !== 'string') throw new Error('Value is not a valid string.');
10034
10357
  TinyHtml_TinyHtml._preElems(el, 'setText').forEach((el) => (el.textContent = value));
10358
+ return el;
10035
10359
  }
10036
10360
 
10037
10361
  /**
10038
10362
  * Set text content of the element.
10039
10363
  * @param {string} value
10364
+ * @returns {TinyElement|TinyElement[]}
10040
10365
  */
10041
10366
  setText(value) {
10042
10367
  return TinyHtml_TinyHtml.setText(this, value);
@@ -10045,13 +10370,16 @@ class TinyHtml_TinyHtml {
10045
10370
  /**
10046
10371
  * Remove all child nodes from each element.
10047
10372
  * @param {TinyElement|TinyElement[]} el
10373
+ * @returns {TinyElement|TinyElement[]}
10048
10374
  */
10049
10375
  static empty(el) {
10050
10376
  TinyHtml_TinyHtml._preElems(el, 'empty').forEach((el) => (el.textContent = ''));
10377
+ return el;
10051
10378
  }
10052
10379
 
10053
10380
  /**
10054
10381
  * Remove all child nodes of the element.
10382
+ * @returns {TinyElement|TinyElement[]}
10055
10383
  */
10056
10384
  empty() {
10057
10385
  return TinyHtml_TinyHtml.empty(this);
@@ -10059,35 +10387,40 @@ class TinyHtml_TinyHtml {
10059
10387
 
10060
10388
  /**
10061
10389
  * Get the innerHTML of the element.
10062
- * @param {TinyElement|TinyElement[]} el
10390
+ * @param {TinyElement} el
10391
+ * @param {GetHTMLOptions} [ops]
10063
10392
  * @returns {string}
10064
10393
  */
10065
- static html(el) {
10394
+ static html(el, ops) {
10066
10395
  const elem = TinyHtml_TinyHtml._preElem(el, 'html');
10067
- return elem.innerHTML;
10396
+ return elem.getHTML(ops);
10068
10397
  }
10069
10398
 
10070
10399
  /**
10071
10400
  * Get the innerHTML of the element.
10401
+ * @param {GetHTMLOptions} [ops]
10072
10402
  * @returns {string}
10073
10403
  */
10074
- html() {
10075
- return TinyHtml_TinyHtml.html(this);
10404
+ html(ops) {
10405
+ return TinyHtml_TinyHtml.html(this, ops);
10076
10406
  }
10077
10407
 
10078
10408
  /**
10079
10409
  * Set the innerHTML of each element.
10080
10410
  * @param {TinyElement|TinyElement[]} el
10081
10411
  * @param {string} value
10412
+ * @returns {TinyElement|TinyElement[]}
10082
10413
  */
10083
10414
  static setHtml(el, value) {
10084
10415
  if (typeof value !== 'string') throw new Error('Value is not a valid string.');
10085
10416
  TinyHtml_TinyHtml._preElems(el, 'setHtml').forEach((el) => (el.innerHTML = value));
10417
+ return el;
10086
10418
  }
10087
10419
 
10088
10420
  /**
10089
10421
  * Set the innerHTML of the element.
10090
10422
  * @param {string} value
10423
+ * @returns {TinyElement|TinyElement[]}
10091
10424
  */
10092
10425
  setHtml(value) {
10093
10426
  return TinyHtml_TinyHtml.setHtml(this, value);
@@ -10221,6 +10554,7 @@ class TinyHtml_TinyHtml {
10221
10554
  * @param {TinyInputElement|TinyInputElement[]} el - Target element.
10222
10555
  * @param {SetValueList|((el: InputElement, val: SetValueList) => SetValueList)} value - The value to assign or a function that returns it.
10223
10556
  * @throws {Error} If the computed value is not a valid string or boolean.
10557
+ * @returns {TinyInputElement|TinyInputElement[]}
10224
10558
  */
10225
10559
  static setVal(el, value) {
10226
10560
  TinyHtml_TinyHtml._preInputElems(el, 'setVal').forEach((elem) => {
@@ -10260,6 +10594,7 @@ class TinyHtml_TinyHtml {
10260
10594
  if (typeof valToSet === 'string') elem.value = valToSet;
10261
10595
  }
10262
10596
  });
10597
+ return el;
10263
10598
  }
10264
10599
 
10265
10600
  /**
@@ -10267,6 +10602,7 @@ class TinyHtml_TinyHtml {
10267
10602
  * Accepts strings, numbers, booleans or arrays of these values, or a callback function that computes them.
10268
10603
  *
10269
10604
  * @param {SetValueList|((el: InputElement, val: SetValueList) => SetValueList)} value - The value to assign or a function that returns it.
10605
+ * @returns {TinyInputElement|TinyInputElement[]}
10270
10606
  * @throws {Error} If the computed value is not a valid string or boolean.
10271
10607
  */
10272
10608
  setVal(value) {
@@ -10536,6 +10872,7 @@ class TinyHtml_TinyHtml {
10536
10872
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10537
10873
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10538
10874
  * @param {EventRegistryOptions} [options] - Optional event listener options.
10875
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10539
10876
  */
10540
10877
  static on(el, event, handler, options) {
10541
10878
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10548,6 +10885,7 @@ class TinyHtml_TinyHtml {
10548
10885
  if (!Array.isArray(events[event])) events[event] = [];
10549
10886
  events[event].push({ handler, options });
10550
10887
  });
10888
+ return el;
10551
10889
  }
10552
10890
 
10553
10891
  /**
@@ -10556,6 +10894,7 @@ class TinyHtml_TinyHtml {
10556
10894
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10557
10895
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10558
10896
  * @param {EventRegistryOptions} [options] - Optional event listener options.
10897
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10559
10898
  */
10560
10899
  on(event, handler, options) {
10561
10900
  return TinyHtml_TinyHtml.on(this, event, handler, options);
@@ -10568,6 +10907,7 @@ class TinyHtml_TinyHtml {
10568
10907
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10569
10908
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10570
10909
  * @param {EventRegistryOptions} [options={}] - Optional event listener options.
10910
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10571
10911
  */
10572
10912
  static once(el, event, handler, options = {}) {
10573
10913
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10585,6 +10925,7 @@ class TinyHtml_TinyHtml {
10585
10925
  typeof options === 'boolean' ? options : { ...options, once: true },
10586
10926
  );
10587
10927
  });
10928
+ return el;
10588
10929
  }
10589
10930
 
10590
10931
  /**
@@ -10593,6 +10934,7 @@ class TinyHtml_TinyHtml {
10593
10934
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10594
10935
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10595
10936
  * @param {EventRegistryOptions} [options={}] - Optional event listener options.
10937
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10596
10938
  */
10597
10939
  once(event, handler, options = {}) {
10598
10940
  return TinyHtml_TinyHtml.once(this, event, handler, options);
@@ -10605,6 +10947,7 @@ class TinyHtml_TinyHtml {
10605
10947
  * @param {string} event - The event type.
10606
10948
  * @param {EventRegistryHandle} handler - The function originally bound to the event.
10607
10949
  * @param {boolean|EventListenerOptions} [options] - Optional listener options.
10950
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10608
10951
  */
10609
10952
  static off(el, event, handler, options) {
10610
10953
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10617,6 +10960,7 @@ class TinyHtml_TinyHtml {
10617
10960
  if (events[event].length === 0) delete events[event];
10618
10961
  }
10619
10962
  });
10963
+ return el;
10620
10964
  }
10621
10965
 
10622
10966
  /**
@@ -10625,6 +10969,7 @@ class TinyHtml_TinyHtml {
10625
10969
  * @param {string} event - The event type.
10626
10970
  * @param {EventRegistryHandle} handler - The function originally bound to the event.
10627
10971
  * @param {boolean|EventListenerOptions} [options] - Optional listener options.
10972
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10628
10973
  */
10629
10974
  off(event, handler, options) {
10630
10975
  return TinyHtml_TinyHtml.off(this, event, handler, options);
@@ -10635,6 +10980,7 @@ class TinyHtml_TinyHtml {
10635
10980
  *
10636
10981
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
10637
10982
  * @param {string} event - The event type to remove (e.g. 'click').
10983
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10638
10984
  */
10639
10985
  static offAll(el, event) {
10640
10986
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10647,12 +10993,14 @@ class TinyHtml_TinyHtml {
10647
10993
  delete events[event];
10648
10994
  }
10649
10995
  });
10996
+ return el;
10650
10997
  }
10651
10998
 
10652
10999
  /**
10653
11000
  * Removes all event listeners of a specific type from the element.
10654
11001
  *
10655
11002
  * @param {string} event - The event type to remove (e.g. 'click').
11003
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10656
11004
  */
10657
11005
  offAll(event) {
10658
11006
  return TinyHtml_TinyHtml.offAll(this, event);
@@ -10664,6 +11012,7 @@ class TinyHtml_TinyHtml {
10664
11012
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
10665
11013
  * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
10666
11014
  * Optional filter function to selectively remove specific handlers.
11015
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10667
11016
  */
10668
11017
  static offAllTypes(el, filterFn = null) {
10669
11018
  if (filterFn !== null && typeof filterFn !== 'function')
@@ -10682,6 +11031,7 @@ class TinyHtml_TinyHtml {
10682
11031
 
10683
11032
  __eventRegistry.delete(elem);
10684
11033
  });
11034
+ return el;
10685
11035
  }
10686
11036
 
10687
11037
  /**
@@ -10689,6 +11039,7 @@ class TinyHtml_TinyHtml {
10689
11039
  *
10690
11040
  * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
10691
11041
  * Optional filter function to selectively remove specific handlers.
11042
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10692
11043
  */
10693
11044
  offAllTypes(filterFn = null) {
10694
11045
  return TinyHtml_TinyHtml.offAllTypes(this, filterFn);
@@ -10700,6 +11051,7 @@ class TinyHtml_TinyHtml {
10700
11051
  * @param {TinyEventTarget|TinyEventTarget[]} el - Target element where the event should be triggered.
10701
11052
  * @param {string} event - Name of the event to trigger.
10702
11053
  * @param {Event|CustomEvent|CustomEventInit} [payload] - Optional event object or data to pass.
11054
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10703
11055
  */
10704
11056
  static trigger(el, event, payload = {}) {
10705
11057
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10715,6 +11067,7 @@ class TinyHtml_TinyHtml {
10715
11067
 
10716
11068
  elem.dispatchEvent(evt);
10717
11069
  });
11070
+ return el;
10718
11071
  }
10719
11072
 
10720
11073
  /**
@@ -10722,6 +11075,7 @@ class TinyHtml_TinyHtml {
10722
11075
  *
10723
11076
  * @param {string} event - Name of the event to trigger.
10724
11077
  * @param {Event|CustomEvent|CustomEventInit} [payload] - Optional event object or data to pass.
11078
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10725
11079
  */
10726
11080
  trigger(event, payload = {}) {
10727
11081
  return TinyHtml_TinyHtml.trigger(this, event, payload);
@@ -10764,6 +11118,7 @@ class TinyHtml_TinyHtml {
10764
11118
  * @param {TinyElement|TinyElement[]} el
10765
11119
  * @param {string} name
10766
11120
  * @param {string|null} [value=null]
11121
+ * @returns {TinyElement|TinyElement[]}
10767
11122
  */
10768
11123
  static setAttr(el, name, value = null) {
10769
11124
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
@@ -10773,12 +11128,14 @@ class TinyHtml_TinyHtml {
10773
11128
  if (value === null) elem.removeAttribute(name);
10774
11129
  else elem.setAttribute(name, value);
10775
11130
  });
11131
+ return el;
10776
11132
  }
10777
11133
 
10778
11134
  /**
10779
11135
  * Set an attribute on an element.
10780
11136
  * @param {string} name
10781
11137
  * @param {string|null} [value=null]
11138
+ * @returns {TinyElement|TinyElement[]}
10782
11139
  */
10783
11140
  setAttr(name, value) {
10784
11141
  return TinyHtml_TinyHtml.setAttr(this, name, value);
@@ -10788,15 +11145,18 @@ class TinyHtml_TinyHtml {
10788
11145
  * Remove attribute(s) from an element.
10789
11146
  * @param {TinyElement|TinyElement[]} el
10790
11147
  * @param {string} name Space-separated list of attributes.
11148
+ * @returns {TinyElement|TinyElement[]}
10791
11149
  */
10792
11150
  static removeAttr(el, name) {
10793
11151
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
10794
11152
  TinyHtml_TinyHtml._preElems(el, 'removeAttr').forEach((elem) => elem.removeAttribute(name));
11153
+ return el;
10795
11154
  }
10796
11155
 
10797
11156
  /**
10798
11157
  * Remove attribute(s) from an element.
10799
11158
  * @param {string} name Space-separated list of attributes.
11159
+ * @returns {TinyElement|TinyElement[]}
10800
11160
  */
10801
11161
  removeAttr(name) {
10802
11162
  return TinyHtml_TinyHtml.removeAttr(this, name);
@@ -10851,6 +11211,7 @@ class TinyHtml_TinyHtml {
10851
11211
  * Set a property on an element.
10852
11212
  * @param {TinyElement|TinyElement[]} el
10853
11213
  * @param {string} name
11214
+ * @returns {TinyElement|TinyElement[]}
10854
11215
  */
10855
11216
  static addProp(el, name) {
10856
11217
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
@@ -10860,11 +11221,13 @@ class TinyHtml_TinyHtml {
10860
11221
  // @ts-ignore
10861
11222
  elem[name] = true;
10862
11223
  });
11224
+ return el;
10863
11225
  }
10864
11226
 
10865
11227
  /**
10866
11228
  * Set a property on an element.
10867
11229
  * @param {string} name
11230
+ * @returns {TinyElement|TinyElement[]}
10868
11231
  */
10869
11232
  addProp(name) {
10870
11233
  return TinyHtml_TinyHtml.addProp(this, name);
@@ -10874,6 +11237,7 @@ class TinyHtml_TinyHtml {
10874
11237
  * Remove a property from an element.
10875
11238
  * @param {TinyElement|TinyElement[]} el
10876
11239
  * @param {string} name
11240
+ * @returns {TinyElement|TinyElement[]}
10877
11241
  */
10878
11242
  static removeProp(el, name) {
10879
11243
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
@@ -10883,11 +11247,13 @@ class TinyHtml_TinyHtml {
10883
11247
  // @ts-ignore
10884
11248
  elem[name] = false;
10885
11249
  });
11250
+ return el;
10886
11251
  }
10887
11252
 
10888
11253
  /**
10889
11254
  * Remove a property from an element.
10890
11255
  * @param {string} name
11256
+ * @returns {TinyElement|TinyElement[]}
10891
11257
  */
10892
11258
  removeProp(name) {
10893
11259
  return TinyHtml_TinyHtml.removeProp(this, name);
@@ -10928,13 +11294,16 @@ class TinyHtml_TinyHtml {
10928
11294
  /**
10929
11295
  * Removes an element from the DOM.
10930
11296
  * @param {TinyElement|TinyElement[]} el - The DOM element or selector to remove.
11297
+ * @returns {TinyElement|TinyElement[]}
10931
11298
  */
10932
11299
  static remove(el) {
10933
11300
  TinyHtml_TinyHtml._preElems(el, 'remove').forEach((elem) => elem.remove());
11301
+ return el;
10934
11302
  }
10935
11303
 
10936
11304
  /**
10937
11305
  * Removes the element from the DOM.
11306
+ * @returns {TinyElement|TinyElement[]}
10938
11307
  */
10939
11308
  remove() {
10940
11309
  return TinyHtml_TinyHtml.remove(this);
@@ -11278,6 +11647,14 @@ class TinyHtml_TinyHtml {
11278
11647
  */
11279
11648
  static isInViewport(el) {
11280
11649
  const elem = TinyHtml_TinyHtml._preElem(el, 'isInViewport');
11650
+ if (
11651
+ !elem.checkVisibility({
11652
+ contentVisibilityAuto: false,
11653
+ opacityProperty: false,
11654
+ visibilityProperty: false,
11655
+ })
11656
+ )
11657
+ return false;
11281
11658
  const elementTop = TinyHtml_TinyHtml.offset(elem).top;
11282
11659
  const elementBottom = elementTop + TinyHtml_TinyHtml.outerHeight(elem);
11283
11660
 
@@ -11304,6 +11681,14 @@ class TinyHtml_TinyHtml {
11304
11681
  */
11305
11682
  static isScrolledIntoView(el) {
11306
11683
  const elem = TinyHtml_TinyHtml._preElem(el, 'isScrolledIntoView');
11684
+ if (
11685
+ !elem.checkVisibility({
11686
+ contentVisibilityAuto: false,
11687
+ opacityProperty: false,
11688
+ visibilityProperty: false,
11689
+ })
11690
+ )
11691
+ return false;
11307
11692
  const docViewTop = TinyHtml_TinyHtml.scrollTop(window);
11308
11693
  const docViewBottom = docViewTop + TinyHtml_TinyHtml.height(window);
11309
11694
 
@@ -11321,6 +11706,111 @@ class TinyHtml_TinyHtml {
11321
11706
  isScrolledIntoView() {
11322
11707
  return TinyHtml_TinyHtml.isScrolledIntoView(this);
11323
11708
  }
11709
+
11710
+ /**
11711
+ * Checks if the given element is at least partially visible within the boundaries of a container.
11712
+ *
11713
+ * @param {TinyElement} el - The DOM element to check.
11714
+ * @param {TinyElement} cont - The container element acting as the viewport.
11715
+ * @returns {boolean} True if the element is partially visible within the container, false otherwise.
11716
+ */
11717
+ static isInContainer(el, cont) {
11718
+ const elem = TinyHtml_TinyHtml._preElem(el, 'isInContainer');
11719
+ const container = TinyHtml_TinyHtml._preElem(cont, 'isInContainer');
11720
+
11721
+ if (
11722
+ !elem.checkVisibility({
11723
+ contentVisibilityAuto: false,
11724
+ opacityProperty: false,
11725
+ visibilityProperty: false,
11726
+ })
11727
+ )
11728
+ return false;
11729
+ const elemRect = elem.getBoundingClientRect();
11730
+ const containerRect = container.getBoundingClientRect();
11731
+
11732
+ const verticallyVisible =
11733
+ elemRect.bottom > containerRect.top && elemRect.top < containerRect.bottom;
11734
+
11735
+ const horizontallyVisible =
11736
+ elemRect.right > containerRect.left && elemRect.left < containerRect.right;
11737
+
11738
+ return verticallyVisible && horizontallyVisible;
11739
+ }
11740
+
11741
+ /**
11742
+ * Checks if the given element is at least partially visible within the boundaries of a container.
11743
+ *
11744
+ * @param {TinyElement} cont - The container element acting as the viewport.
11745
+ * @returns {boolean} True if the element is partially visible within the container, false otherwise.
11746
+ */
11747
+ isInContainer(cont) {
11748
+ return TinyHtml_TinyHtml.isInContainer(this, cont);
11749
+ }
11750
+
11751
+ /**
11752
+ * Checks if the given element is fully visible within the boundaries of a container (top and bottom).
11753
+ *
11754
+ * @param {TinyElement} el - The DOM element to check.
11755
+ * @param {TinyElement} cont - The container element acting as the viewport.
11756
+ * @returns {boolean} True if the element is fully visible within the container, false otherwise.
11757
+ */
11758
+ static isFullyInContainer(el, cont) {
11759
+ const elem = TinyHtml_TinyHtml._preElem(el, 'isScrolledIntoView');
11760
+ const container = TinyHtml_TinyHtml._preElem(cont, 'isInContainer');
11761
+
11762
+ if (
11763
+ !elem.checkVisibility({
11764
+ contentVisibilityAuto: false,
11765
+ opacityProperty: false,
11766
+ visibilityProperty: false,
11767
+ })
11768
+ )
11769
+ return false;
11770
+ const elemRect = elem.getBoundingClientRect();
11771
+ const containerRect = container.getBoundingClientRect();
11772
+
11773
+ const isFullyVisible =
11774
+ elemRect.top >= containerRect.top &&
11775
+ elemRect.bottom <= containerRect.bottom &&
11776
+ elemRect.left >= containerRect.left &&
11777
+ elemRect.right <= containerRect.right;
11778
+
11779
+ return isFullyVisible;
11780
+ }
11781
+
11782
+ /**
11783
+ * Checks if the given element is fully visible within the boundaries of a container (top and bottom).
11784
+ *
11785
+ * @param {TinyElement} cont - The container element acting as the viewport.
11786
+ * @returns {boolean} True if the element is fully visible within the container, false otherwise.
11787
+ */
11788
+ isFullyInContainer(cont) {
11789
+ return TinyHtml_TinyHtml.isFullyInContainer(this, cont);
11790
+ }
11791
+
11792
+ /**
11793
+ * Checks if an element has scrollable content.
11794
+ *
11795
+ * @param {TinyElement} el - The DOM element to check.
11796
+ * @returns {{ v: boolean, h: boolean }} - True if scroll is needed in that direction.
11797
+ */
11798
+ static hasScroll(el) {
11799
+ const elem = TinyHtml_TinyHtml._preElem(el, 'hasScroll');
11800
+ return {
11801
+ v: elem.scrollHeight > elem.clientHeight,
11802
+ h: elem.scrollWidth > elem.clientWidth,
11803
+ };
11804
+ }
11805
+
11806
+ /**
11807
+ * Checks if an element has scrollable content.
11808
+ *
11809
+ * @returns {{ v: boolean, h: boolean }} - True if scroll is needed in that direction.
11810
+ */
11811
+ hasScroll() {
11812
+ return TinyHtml_TinyHtml.hasScroll(this);
11813
+ }
11324
11814
  }
11325
11815
 
11326
11816
  /* harmony default export */ const libs_TinyHtml = (TinyHtml_TinyHtml);
@@ -13791,8 +14281,8 @@ class TinyAfterScrollWatcher {
13791
14281
  /** @type {Element|Window} */
13792
14282
  #scrollTarget;
13793
14283
 
13794
- /** @type {number} */
13795
- lastScrollTime = 0;
14284
+ /** @type {null|NodeJS.Timeout} */
14285
+ #lastScrollTime = null;
13796
14286
 
13797
14287
  /** @type {FnData[]} */
13798
14288
  #afterScrollQueue = [];
@@ -13803,6 +14293,9 @@ class TinyAfterScrollWatcher {
13803
14293
  /** @type {Set<OnScrollFunc>} */
13804
14294
  #externalScrollListeners = new Set();
13805
14295
 
14296
+ /** @type {Set<FnData>} */
14297
+ #onStopListeners = new Set();
14298
+
13806
14299
  /** @type {boolean} */
13807
14300
  #destroyed = false;
13808
14301
 
@@ -13816,16 +14309,20 @@ class TinyAfterScrollWatcher {
13816
14309
  if (!(scrollTarget instanceof Element) && !(scrollTarget instanceof Window))
13817
14310
  throw new TypeError('scrollTarget must be an Element or the Window object.');
13818
14311
  this.#scrollTarget = scrollTarget;
14312
+ this._checkTimer = this._checkTimer.bind(this);
13819
14313
 
13820
- this._onScroll = this._onScroll.bind(this);
13821
- this._checkQueue = this._checkQueue.bind(this);
13822
-
13823
- this.#scrollTarget.addEventListener('scroll', this._onScroll);
14314
+ this.#scrollTarget.addEventListener('scroll', this._checkTimer);
13824
14315
  this.#inactivityTime = inactivityTime;
13825
-
13826
- requestAnimationFrame(this._checkQueue);
13827
14316
  }
13828
14317
 
14318
+ _checkTimer = () => {
14319
+ if (this.#lastScrollTime) clearTimeout(this.#lastScrollTime);
14320
+ this.#lastScrollTime = setTimeout(() => {
14321
+ this.#lastScrollTime = null;
14322
+ this.#checkQueue();
14323
+ }, this.#inactivityTime);
14324
+ };
14325
+
13829
14326
  /**
13830
14327
  * Gets the current inactivity time in milliseconds.
13831
14328
  * @returns {number}
@@ -13846,29 +14343,21 @@ class TinyAfterScrollWatcher {
13846
14343
  this.#inactivityTime = value;
13847
14344
  }
13848
14345
 
13849
- /**
13850
- * Internal handler for scroll events.
13851
- * Updates the last scroll timestamp.
13852
- * @private
13853
- */
13854
- _onScroll() {
13855
- this.lastScrollTime = Date.now();
13856
- }
13857
-
13858
14346
  /**
13859
14347
  * Continuously checks whether the user has stopped scrolling,
13860
14348
  * and if so, runs all queued functions.
13861
- * @private
13862
14349
  */
13863
- _checkQueue() {
14350
+ #checkQueue() {
13864
14351
  if (this.#destroyed) return;
13865
- requestAnimationFrame(this._checkQueue);
14352
+ // Runs all onStop first listeners
14353
+ for (const fn of this.#onStopListeners) {
14354
+ if (typeof fn === 'function') fn();
14355
+ }
13866
14356
 
13867
- if (Date.now() - this.lastScrollTime > this.#inactivityTime) {
13868
- while (this.#afterScrollQueue.length) {
13869
- const fn = this.#afterScrollQueue.pop();
13870
- if (typeof fn === 'function') fn();
13871
- }
14357
+ // Then execute the queue afterScrollQueue
14358
+ while (this.#afterScrollQueue.length) {
14359
+ const fn = this.#afterScrollQueue.pop();
14360
+ if (typeof fn === 'function') fn();
13872
14361
  }
13873
14362
  }
13874
14363
 
@@ -13885,6 +14374,29 @@ class TinyAfterScrollWatcher {
13885
14374
  this.#afterScrollQueue.push(fn);
13886
14375
  }
13887
14376
 
14377
+ /**
14378
+ * Registers a function to run once after scrolling has stopped,
14379
+ * before any afterScrollQueue functions.
14380
+ *
14381
+ * @param {FnData} fn - A function to execute after scroll stop.
14382
+ * @throws {TypeError} If the argument is not a function.
14383
+ */
14384
+ onStop(fn) {
14385
+ if (typeof fn !== 'function') throw new TypeError('Argument must be a function.');
14386
+ this.#onStopListeners.add(fn);
14387
+ }
14388
+
14389
+ /**
14390
+ * Removes a previously registered onStop function.
14391
+ *
14392
+ * @param {FnData} fn - The function to remove.
14393
+ * @throws {TypeError} If the argument is not a function.
14394
+ */
14395
+ offStop(fn) {
14396
+ if (typeof fn !== 'function') throw new TypeError('Argument must be a function.');
14397
+ this.#onStopListeners.delete(fn);
14398
+ }
14399
+
13888
14400
  /**
13889
14401
  * Registers an external scroll listener on the tracked element.
13890
14402
  *
@@ -13918,17 +14430,1952 @@ class TinyAfterScrollWatcher {
13918
14430
  if (this.#destroyed) return;
13919
14431
  this.#destroyed = true;
13920
14432
 
13921
- this.#scrollTarget.removeEventListener('scroll', this._onScroll);
14433
+ this.#scrollTarget.removeEventListener('scroll', this._checkTimer);
13922
14434
  for (const fn of this.#externalScrollListeners)
13923
14435
  this.#scrollTarget.removeEventListener('scroll', fn);
13924
14436
 
13925
14437
  this.#externalScrollListeners.clear();
14438
+ this.#onStopListeners.clear();
13926
14439
  }
13927
14440
  }
13928
14441
 
13929
14442
  /* harmony default export */ const libs_TinyAfterScrollWatcher = (TinyAfterScrollWatcher);
13930
14443
 
13931
- ;// ./src/v1/index.mjs
14444
+ ;// ./src/v1/libs/UltraRandomMsgGen.mjs
14445
+ const defaultWords = [
14446
+ 'lorem',
14447
+ 'ipsum',
14448
+ 'dolor',
14449
+ 'sit',
14450
+ 'amet',
14451
+ 'consectetur',
14452
+ 'adipiscing',
14453
+ 'elit',
14454
+ 'sed',
14455
+ 'do',
14456
+ 'eiusmod',
14457
+ 'tempor',
14458
+ 'incididunt',
14459
+ 'ut',
14460
+ 'labore',
14461
+ 'et',
14462
+ 'dolore',
14463
+ 'magna',
14464
+ 'aliqua',
14465
+ ];
14466
+ const defaultEmojis = [
14467
+ '😂',
14468
+ '🌈',
14469
+ '🤖',
14470
+ '💥',
14471
+ '🐸',
14472
+ '🍕',
14473
+ '🦄',
14474
+ '🧠',
14475
+ '🧬',
14476
+ '🚀',
14477
+ '🫠',
14478
+ '💫',
14479
+ '🍩',
14480
+ '👾',
14481
+ '🎉',
14482
+ '🥴',
14483
+ '🐙',
14484
+ '🧃',
14485
+ '🪐',
14486
+ '🎈',
14487
+ '🧸',
14488
+ '👻',
14489
+ '🥳',
14490
+ '🍭',
14491
+ '💖',
14492
+ '😺',
14493
+ '🌮',
14494
+ '🪅',
14495
+ '🎮',
14496
+ '🥓',
14497
+ '🍮',
14498
+ ];
14499
+
14500
+ const defaultNouns = [
14501
+ 'pudding',
14502
+ 'bean',
14503
+ 'snuggle',
14504
+ 'bloop',
14505
+ 'jelly',
14506
+ 'unicorn',
14507
+ 'floof',
14508
+ 'giggle',
14509
+ 'bubble',
14510
+ 'muffin',
14511
+ 'puff',
14512
+ 'pickle',
14513
+ 'goblin',
14514
+ 'waffle',
14515
+ 'sprinkle',
14516
+ 'cupcake',
14517
+ 'fizzle',
14518
+ 'marshmallow',
14519
+ 'duckling',
14520
+ 'sock',
14521
+ 'cloud',
14522
+ 'teacup',
14523
+ 'nugget',
14524
+ 'gnome',
14525
+ 'hug',
14526
+ 'moonbean',
14527
+ 'crayon',
14528
+ 'jiggle',
14529
+ 'glitter',
14530
+ 'carrot',
14531
+ 'goober',
14532
+ ];
14533
+
14534
+ const defaultVerbs = [
14535
+ 'wiggles',
14536
+ 'bounces',
14537
+ 'flies',
14538
+ 'splats',
14539
+ 'scoots',
14540
+ 'giggles',
14541
+ 'wigglesnacks',
14542
+ 'twirls',
14543
+ 'boops',
14544
+ 'pops',
14545
+ 'sings',
14546
+ 'hugs',
14547
+ 'floats',
14548
+ 'glows',
14549
+ 'flaps',
14550
+ 'mlems',
14551
+ 'dances',
14552
+ 'puddles',
14553
+ 'nomnoms',
14554
+ 'wigglejumps',
14555
+ 'sniffs',
14556
+ 'tumbles',
14557
+ 'slides',
14558
+ 'chirps',
14559
+ 'burps',
14560
+ 'sparkles',
14561
+ 'waddles',
14562
+ 'rambles',
14563
+ 'blinks',
14564
+ 'mews',
14565
+ ];
14566
+
14567
+ const defaultAdjectives = [
14568
+ 'fluffy',
14569
+ 'silly',
14570
+ 'yummy',
14571
+ 'squishy',
14572
+ 'wobbly',
14573
+ 'magical',
14574
+ 'tiny',
14575
+ 'sleepy',
14576
+ 'wiggly',
14577
+ 'bubbly',
14578
+ 'glittery',
14579
+ 'fuzzy',
14580
+ 'jiggly',
14581
+ 'sparkly',
14582
+ 'giggly',
14583
+ 'crunchy',
14584
+ 'goofy',
14585
+ 'soft',
14586
+ 'mushy',
14587
+ 'sweet',
14588
+ 'loopy',
14589
+ 'floaty',
14590
+ 'bonkers',
14591
+ 'chewy',
14592
+ 'ticklish',
14593
+ 'dreamy',
14594
+ 'pastel',
14595
+ 'cozy',
14596
+ 'teensy',
14597
+ 'grumbly',
14598
+ ];
14599
+
14600
+ const defaultTemplates = [
14601
+ // 🧁 Tiny
14602
+ 'Boop!',
14603
+ 'Wiggle achieved.',
14604
+ 'Oops, {noun} everywhere!',
14605
+ 'Tiny {adj} {noun}, big {adj} {noun}.',
14606
+ 'Snuggle initiated with a {adj} {noun}.',
14607
+ '{adj} vibes only, powered by {noun}.',
14608
+ 'No {noun}, no {noun}.',
14609
+ 'Bounce first, {verb} later.',
14610
+ 'Mlem. {verb}. {noun}.',
14611
+ '{noun} go brrr and {verb}.',
14612
+ 'Why not? {noun} did. Then {noun} followed.',
14613
+
14614
+ // 🍬 Simple
14615
+ 'A {adj} {noun} just {verb} near the {adj} window.',
14616
+ 'That {adj} {noun} stole my {noun}!',
14617
+ 'Look, a {adj} {noun} trying to {verb} again!',
14618
+ 'Every {noun} deserves a {adj} nap.',
14619
+ 'The {adj} {noun} is my {adj} spirit animal.',
14620
+ 'Someone call the {noun}, it’s {verb}ing and {verb}ing again!',
14621
+ 'Don’t touch the {adj} {noun}. It {verb}s loudly.',
14622
+ '{noun} forgot how {noun}s work and just {verb}ed.',
14623
+
14624
+ // 🍩 Averages
14625
+ 'There once was a {adj} {noun} who loved to {verb} with a {adj} {noun} all day.',
14626
+ 'Apparently, {noun}s are banned from {verb}ing and {verb}ing in the {noun} library.',
14627
+ 'That {adj} noise? Just a {noun} learning to {verb} on a {adj} {noun}.',
14628
+ 'I saw a {adj} {noun} {verb} so hard, it became a {adj} muffin.',
14629
+ 'The {adj} {noun} met a {adj} pickle and everything {verb}ed.',
14630
+ '{noun}s are like {noun}: {adj}, unpredictable, and wiggly.',
14631
+ 'Don’t judge a {adj} {noun} by its ability to {verb}, or by its {adj} hat.',
14632
+ 'All I wanted was peace, but a {adj} {noun} with a {adj} {noun} had other plans.',
14633
+
14634
+ // 🍓 Giants
14635
+ 'One {adj} morning, a {adj} {noun} decided it was finally time to {verb}, but halfway through, it tripped on a {noun} and turned into a {adj} marshmallow.',
14636
+ 'I tried to be {adj} today, but then a {noun} {verb}ed across my {noun} and typed “asdfgh{noun}” repeatedly.',
14637
+ 'If you think you’ve seen everything, wait until a {adj} {noun} {verb}s on a {noun} wearing {adj} socks with tiny {noun}s.',
14638
+ 'No one ever believed the stories about the ancient {adj} {noun} who could {verb} with such {adj} grace that entire {noun}s turned pink out of embarrassment.',
14639
+ 'A curious {adj} {noun} wandered into a {noun} shop, not knowing that its destiny involved twelve {adj} {noun}s, one rubber {noun}, and a suspiciously quiet {adj} llama.',
14640
+
14641
+ // 🍒 Giants and narratives
14642
+ 'In a land where every {adj} {noun} was made of pudding and the sky was {adj} whipped cream, one brave {adj} {noun} set out to discover the legendary Spoon of {noun}s, facing obstacles like bouncing {noun}s, sassy {noun}s, and the Great Wobble of the {noun}.',
14643
+ 'Once upon a {adj} time, long before the {noun}s learned to {verb}, there existed a tiny {adj} {noun} who dreamed of swimming in a pool made entirely of {adj} glitter pudding while {verb}ing off-key lullabies to passing {noun}s with monocles.',
14644
+ 'The Council of {noun}s gathered in {adj} secret when the sacred {adj} {noun} started to {verb} uncontrollably, threatening the entire {adj} dessert-based civilization with spontaneous {noun} jiggles and uncontainable {noun} fits across the {noun}.',
14645
+
14646
+ // ☎️ Chats and chats
14647
+ 'Hey... do you ever wonder if {noun}s dream of {noun}s?',
14648
+ 'Okay but hear me out: what if the {adj} {noun} could actually {verb} backwards?',
14649
+ 'Can we talk about the {noun} that just {verb}ed and vanished?',
14650
+ 'So I was talking to a {noun}, and it told me to stop being {adj}. Rude.',
14651
+ 'Not to alarm you, but there’s a {adj} {noun} behind you doing the {verb}.',
14652
+ 'Why does this {noun} keep staring at me like I owe it pudding?',
14653
+ 'Is this a safe space to admit I accidentally {verb}ed a {noun}?',
14654
+ 'I just walked into the room and someone shouted “{adj} {noun}!” — what did I miss?',
14655
+ 'Wait... we were supposed to bring a {noun}? No one told me!',
14656
+ 'Okay but what if the {adj} {noun} has feelings too?',
14657
+ 'Be honest, do I look like a {noun} that forgot how to {verb}?',
14658
+ 'If I say "oops", do I still have to explain why the {noun} is glowing?',
14659
+ 'I don’t know what’s going on but I brought snacks and a confused {noun}.',
14660
+ 'Do you think pudding knows it’s pudding? Just a thought.',
14661
+ 'Is this about the time I {verb}ed the {adj} {noun} by accident? Because I can explain.',
14662
+
14663
+ // 🌀 Confusions, unexpected entrances, lost people
14664
+ 'Wait, are we talking about {noun}s or am I on the wrong chat again?',
14665
+ 'What is this group even about? I just saw "pudding" and joined.',
14666
+ 'Hi! I have no idea what’s happening but I’m here and I brought a {noun}.',
14667
+ 'I blinked and now there’s a {adj} {noun} in charge of everything.',
14668
+ 'Can someone please explain why the {noun} is floating and chanting?',
14669
+ 'Okay who gave the {noun} access to glitter and emotional support pickles?',
14670
+ 'I came for a calm discussion and now there’s a {adj} battle between {noun}s.',
14671
+ 'Not sure if I’m early, late, or inside a {noun} dream.',
14672
+ 'I was gone for 3 minutes and now someone’s riding a {noun} into the pudding realm.',
14673
+ 'This isn’t the pudding appreciation group, is it? ...oh no.',
14674
+ 'So... who summoned the {adj} {noun} this time? Be honest.',
14675
+ 'I came here for vibes and stayed for the {noun}s.',
14676
+ 'Does anyone else hear faint giggling or is that just the {adj} {noun} again?',
14677
+ 'I feel like I missed step one, two, and also three of this conversation.',
14678
+ 'All I asked was “what’s up?” and now I’m emotionally attached to a {noun}.',
14679
+
14680
+ // 💬 More interactive chat style
14681
+ 'Me: "I’ll be normal today." Also me: *{verb}s into a meeting riding a {adj} {noun}*',
14682
+ '"It’s just pudding," they said. "It can’t hurt you," they said. They were wrong.',
14683
+ 'I said one nice thing to a {noun} and now it follows me everywhere.',
14684
+ 'Can we take a moment to appreciate how the {adj} {noun} is just vibing?',
14685
+ 'Who put a tiny hat on the {noun}? Because that’s adorable.',
14686
+ 'My life has been different ever since the {noun} started {verb}ing.',
14687
+ 'Raise your hand if you’ve ever been personally attacked by a {adj} {noun}.',
14688
+ '"Don’t be weird," they said. So I became a {noun} instead.',
14689
+ 'Do {noun}s have feelings? Asking for a {adj} friend.',
14690
+ 'I trusted the {noun} and all I got was a glitter-covered sock.',
14691
+ 'Plot twist: the {adj} {noun} was inside us all along.',
14692
+
14693
+ // Mini cute explosions and chaos
14694
+ 'The {adj} {noun} {verb} and {verb} all over the {adj} {noun}!',
14695
+ 'Oops! {noun}s {verb}ed into the {adj} {noun} while {verb}ing crazily.',
14696
+ '{adj}, {adj}, and more {adj} {noun}s just {verb}ed by!',
14697
+ 'Why does the {adj} {noun} keep {verb}ing and {verb}ing without stopping?',
14698
+ 'Look! A {adj} {noun}, a {adj} {noun}, and a {noun} all {verb}ing together!',
14699
+ 'Sometimes, the {noun} just {verb}s, then {verb}s again, and never stops being {adj}.',
14700
+
14701
+ // Phrases with repetition and multiple spaces for chaos
14702
+ '{noun}, {noun}, and {noun} — all {verb}ing and {verb}ing in a {adj} {noun}.',
14703
+ 'I saw a {adj} {noun} {verb}, then another {noun} {verb}ing with a {adj} {noun}.',
14704
+ 'The {noun} {verb}s {verb} while the {adj} {noun} {verb}s and the {noun} just {verb}s.',
14705
+ 'Can a {adj} {noun} {verb} {verb} without a {adj} {noun} watching?',
14706
+
14707
+ // Phrases with interactions and breaks for fun
14708
+ 'Whoa! The {adj} {noun} just {verb}ed... and then {verb}ed again!',
14709
+ 'Hey... did you see that {adj} {noun} {verb} over there?',
14710
+ 'Umm, the {noun} is {verb}ing but also {verb}ing and {verb}ing!',
14711
+ 'Lol! {noun}s {verb} so {adj}ly, it’s impossible not to giggle.',
14712
+
14713
+ // Mini exaggerated and cute dialogues
14714
+ '"Hey! What’s up?" asked the {adj} {noun}, who {verb}ed loudly.',
14715
+ '"I’m just a {adj} {noun} trying to {verb}, you know?"',
14716
+ '"Did you see the {adj} {noun} that just {verb}ed in the pudding?"',
14717
+ '"No way! The {noun} {verb}s better than me!"',
14718
+ '"Wait, the {adj} {noun} said it would {verb}, but it {verb}ed instead!"',
14719
+
14720
+ // Extensive, crazy and fun narrative phrases
14721
+ 'Once upon a {adj} {noun}, a group of {adj} {noun}s {verb}ed through the {adj} forest, singing {adj} songs and eating {noun}s made of pudding.',
14722
+ 'The legend tells of a {adj} {noun} who could {verb} faster than any {noun} ever seen, leaving trails of {adj} sparkles behind.',
14723
+ 'Every day, the {adj} {noun} would {verb} around the {adj} meadow, trying to convince the {noun}s to join the grand pudding party.',
14724
+ 'No one knows why the {adj} {noun} suddenly {verb}ed and then {verb}ed again, but everyone agrees it was the cutest thing they ever saw.',
14725
+ 'In the kingdom of {adj} {noun}s, only the bravest {noun} dared to {verb} the giant pudding monster, armed with nothing but {adj} smiles and jellybeans.',
14726
+
14727
+ // Total cases with many placeholders
14728
+ '{adj} {noun} {verb} {verb} {verb} {noun} {verb} {adj} {noun} {verb} {noun}!',
14729
+ '{noun} {noun} {verb} {adj} {noun} and then {verb} {verb} the {adj} {noun}.',
14730
+ '{verb} the {adj} {noun}, then {verb} the {noun} while {verb}ing {adj}ly.',
14731
+ 'Can the {adj} {noun} {verb} and {verb} the {adj} {noun} at the same time?',
14732
+ '{noun} {verb} {noun} {verb}, but the {adj} {noun} {verb}s better.',
14733
+
14734
+ // Frases with cute interjections and silly sounds
14735
+ 'Boop! The {adj} {noun} just {verb}ed in the pudding.',
14736
+ 'Bloop bloop, the {noun} is {verb}ing all over again!',
14737
+ 'Mlem mlem, a {adj} {noun} {verb}s quietly in the corner.',
14738
+ 'Splat! {noun} {verb}ed right on the {adj} {noun}.',
14739
+ 'Yum! A {adj} {noun} just {verb}ed in my mouth.',
14740
+
14741
+ // Phrases with silly questions
14742
+ 'Do {noun}s really {verb} when nobody’s watching?',
14743
+ 'Why does the {adj} {noun} always {verb} at midnight?',
14744
+ 'Can a {noun} be too {adj} to {verb} properly?',
14745
+ 'Is it normal for a {adj} {noun} to {verb} three times in a row?',
14746
+ 'Who taught the {noun} to {verb} like that?',
14747
+
14748
+ // Frases of confusion and fun surprises
14749
+ 'Wait, did the {adj} {noun} just {verb} itself?',
14750
+ 'I can’t believe the {noun} {verb}ed into a {adj} {noun}!',
14751
+ 'Suddenly, a {adj} {noun} appeared and started to {verb} wildly.',
14752
+ 'That {noun} was {verb}ing so {adj}ly it broke the pudding meter.',
14753
+ 'Everyone’s talking about the {adj} {noun} that {verb}ed out of nowhere.',
14754
+ ];
14755
+
14756
+ /**
14757
+ * @typedef {'mixed'|'readable'|'chaotic'|'natural'} RandomMsgModes
14758
+ * Defines how the message content is generated:
14759
+ * - `mixed`: Combines readable words, gibberish, symbols, emojis, etc.
14760
+ * - `readable`: Focuses on human-readable words only.
14761
+ * - `chaotic`: Pure chaos, mostly gibberish and symbols.
14762
+ * - `natural`: Uses structured grammar templates to form silly but proper sentences.
14763
+ */
14764
+
14765
+ /**
14766
+ * @typedef {'inline'|'end'|'none'} EmojiPlacement
14767
+ * Controls where emojis are placed:
14768
+ * - `inline`: Emojis may appear throughout the text.
14769
+ * - `end`: Emojis appear at the end of lines.
14770
+ * - `none`: No emojis will be used.
14771
+ */
14772
+
14773
+ /**
14774
+ * @typedef {Object} MsgGenConfig
14775
+ * Configuration object for customizing message generation.
14776
+ *
14777
+ * @property {number} minLength - Minimum total length (in characters) of the final generated text.
14778
+ * @property {number} maxLength - Maximum total length (in characters) of the final generated text.
14779
+ * @property {boolean} readable - Whether to favor readable word-like strings.
14780
+ * @property {boolean} useEmojis - Whether emojis are allowed in the generated content.
14781
+ * @property {boolean} includeNumbers - Whether random numbers can appear in the text.
14782
+ * @property {boolean} includeSymbols - Whether random symbols (e.g., !@#) are included.
14783
+ * @property {boolean} allowWeirdSpacing - Enables fun spacing effects (e.g., extra spaces, newlines, uppercase words).
14784
+ *
14785
+ * @property {string[]} emojiSet - List of emojis to choose from. Overrides default set if provided.
14786
+ * @property {string[]} wordSet - List of base words used in readable and mixed modes.
14787
+ *
14788
+ * @property {RandomMsgModes} mode - Determines the overall generation strategy (`mixed`, `readable`, `chaotic`, or `natural`).
14789
+ *
14790
+ * @property {Object} grammar - Grammar configuration used when `mode` is set to `'natural'`.
14791
+ * @property {string[]} grammar.templates - Sentence templates using placeholders (`{noun}`, `{verb}`, `{adj}`).
14792
+ * @property {string[]} grammar.nouns - List of nouns to insert into `{noun}` placeholders.
14793
+ * @property {string[]} grammar.verbs - List of verbs to insert into `{verb}` placeholders.
14794
+ * @property {string[]} grammar.adjectives - List of adjectives to insert into `{adj}` placeholders.
14795
+ *
14796
+ * @property {boolean} repeatWords - If false, avoids repeating the same word within a single generation.
14797
+ * @property {EmojiPlacement} emojiPlacement - Controls how emojis are placed in the text.
14798
+ *
14799
+ * @property {Object} [paragraphs] - Optional paragraph configuration.
14800
+ * @property {number} paragraphs.min - Minimum number of paragraphs to generate.
14801
+ * @property {number} paragraphs.max - Maximum number of paragraphs to generate.
14802
+ *
14803
+ * @property {Object} line - Configuration for line-based generation.
14804
+ * @property {number} line.minLength - Minimum number of characters per line.
14805
+ * @property {number} line.maxLength - Maximum number of characters per line.
14806
+ * @property {number} line.emojiChance - Probability (0 to 1) that a line will end with an emoji when allowed.
14807
+ */
14808
+
14809
+ /**
14810
+ * UltraRandomMsgGen - Phrase templates and word lists
14811
+ *
14812
+ * Portions of the templates, word sets, and phrase structures
14813
+ * were generated and expanded by ChatGPT (OpenAI).
14814
+ *
14815
+ * This content was crafted to produce innocent, playful, and diverse
14816
+ * random messages focused on a pudding-themed, whimsical style.
14817
+ *
14818
+ * @class
14819
+ */
14820
+ class UltraRandomMsgGen {
14821
+ /** @type {string[]} */
14822
+ static defaultWords = defaultWords;
14823
+ /** @type {string[]} */
14824
+ static defaultEmojis = defaultEmojis;
14825
+ /** @type {string[]} */
14826
+ static defaultNouns = defaultNouns;
14827
+ /** @type {string[]} */
14828
+ static defaultVerbs = defaultVerbs;
14829
+ /** @type {string[]} */
14830
+ static defaultAdjectives = defaultAdjectives;
14831
+ /** @type {string[]} */
14832
+ static defaultTemplates = defaultTemplates;
14833
+
14834
+ /** @type {MsgGenConfig} */
14835
+ config = {
14836
+ minLength: 10,
14837
+ maxLength: 300,
14838
+ readable: true,
14839
+ useEmojis: true,
14840
+ includeNumbers: true,
14841
+ includeSymbols: true,
14842
+ allowWeirdSpacing: false,
14843
+ emojiSet: [],
14844
+ wordSet: [],
14845
+ mode: 'mixed', // 'mixed', 'readable', 'chaotic'
14846
+ grammar: {
14847
+ templates: [],
14848
+ nouns: [],
14849
+ verbs: [],
14850
+ adjectives: [],
14851
+ },
14852
+ repeatWords: true,
14853
+ emojiPlacement: 'inline', // 'inline' | 'end' | 'none'
14854
+ line: {
14855
+ minLength: 20,
14856
+ maxLength: 120,
14857
+ emojiChance: 0.3, // 30% chance per line
14858
+ },
14859
+ };
14860
+
14861
+ /**
14862
+ * Creates an instance of UltraRandomMsgGen.
14863
+ *
14864
+ * @param {Object} [config={}] - Configuration object to customize the generator.
14865
+ * @param {number} [config.minLength=10] - Minimum total length (in characters) of generated text.
14866
+ * @param {number} [config.maxLength=300] - Maximum total length (in characters) of generated text.
14867
+ * @param {boolean} [config.readable=true] - Whether to generate readable words or random strings.
14868
+ * @param {boolean} [config.useEmojis=true] - Whether to include emojis in the generated text.
14869
+ * @param {boolean} [config.includeNumbers=true] - Whether to include numbers randomly.
14870
+ * @param {boolean} [config.includeSymbols=true] - Whether to include symbols randomly.
14871
+ * @param {boolean} [config.allowWeirdSpacing=false] - Whether to allow weird spacing (newlines, extra spaces, uppercase).
14872
+ * @param {string[]} [config.emojiSet] - Array of emojis to use (defaults to internal emoji set).
14873
+ * @param {string[]} [config.wordSet] - Array of words to use (defaults to internal word set).
14874
+ * @param {RandomMsgModes} [config.mode='mixed'] - Mode of text generation.
14875
+ * @param {Object} [config.grammar] - Grammar configuration with templates and word categories.
14876
+ * @param {string[]} [config.grammar.templates] - Array of string templates with placeholders for generating sentences.
14877
+ * @param {string[]} [config.grammar.nouns] - Array of noun strings for template substitution.
14878
+ * @param {string[]} [config.grammar.verbs] - Array of verb strings for template substitution.
14879
+ * @param {string[]} [config.grammar.adjectives] - Array of adjective strings for template substitution.
14880
+ * @param {boolean} [config.repeatWords=true] - Whether to allow repeating words in the output.
14881
+ * @param {EmojiPlacement} [config.emojiPlacement='inline'] - Placement mode for emojis in generated text.
14882
+ * @param {Object} [config.paragraphs] - Paragraph configuration object or null for single block text.
14883
+ * @param {number} [config.paragraphs.min] - Minimum number of paragraphs to generate.
14884
+ * @param {number} [config.paragraphs.max] - Maximum number of paragraphs to generate.
14885
+ * @param {Object} [config.line] - Line configuration for generated text.
14886
+ * @param {number} [config.line.minLength=20] - Minimum length (characters) per line.
14887
+ * @param {number} [config.line.maxLength=120] - Maximum length (characters) per line.
14888
+ * @param {number} [config.line.emojiChance=0.3] - Probability (0–1) of placing emoji per line.
14889
+ */
14890
+ constructor(config = {}) {
14891
+ this.symbols = '!@#$%^&*()-_=+[]{}|;:,.<>?/\\~'.split('');
14892
+
14893
+ const {
14894
+ minLength,
14895
+ maxLength,
14896
+ readable,
14897
+ useEmojis,
14898
+ includeNumbers,
14899
+ includeSymbols,
14900
+ allowWeirdSpacing,
14901
+ emojiSet,
14902
+ wordSet,
14903
+ mode,
14904
+ grammar,
14905
+ repeatWords,
14906
+ emojiPlacement,
14907
+ paragraphs,
14908
+ line,
14909
+ } = config;
14910
+
14911
+ // Validations
14912
+ if (minLength !== undefined && (!Number.isInteger(minLength) || minLength < 1)) {
14913
+ throw new TypeError('config.minLength must be an integer >= 1');
14914
+ }
14915
+
14916
+ if (maxLength !== undefined && (!Number.isInteger(maxLength) || maxLength < 1)) {
14917
+ throw new TypeError('config.maxLength must be an integer >= 1');
14918
+ }
14919
+
14920
+ if (minLength !== undefined && maxLength !== undefined && minLength > maxLength) {
14921
+ throw new RangeError('config.minLength cannot be greater than config.maxLength');
14922
+ }
14923
+
14924
+ if (readable !== undefined && typeof readable !== 'boolean') {
14925
+ throw new TypeError('config.readable must be a boolean');
14926
+ }
14927
+
14928
+ if (useEmojis !== undefined && typeof useEmojis !== 'boolean') {
14929
+ throw new TypeError('config.useEmojis must be a boolean');
14930
+ }
14931
+
14932
+ if (includeNumbers !== undefined && typeof includeNumbers !== 'boolean') {
14933
+ throw new TypeError('config.includeNumbers must be a boolean');
14934
+ }
14935
+
14936
+ if (includeSymbols !== undefined && typeof includeSymbols !== 'boolean') {
14937
+ throw new TypeError('config.includeSymbols must be a boolean');
14938
+ }
14939
+
14940
+ if (allowWeirdSpacing !== undefined && typeof allowWeirdSpacing !== 'boolean') {
14941
+ throw new TypeError('config.allowWeirdSpacing must be a boolean');
14942
+ }
14943
+
14944
+ if (emojiSet !== undefined && !Array.isArray(emojiSet)) {
14945
+ throw new TypeError('config.emojiSet must be an array of strings');
14946
+ }
14947
+
14948
+ if (wordSet !== undefined && !Array.isArray(wordSet)) {
14949
+ throw new TypeError('config.wordSet must be an array of strings');
14950
+ }
14951
+
14952
+ if (mode !== undefined && !['mixed', 'readable', 'chaotic', 'natural'].includes(mode)) {
14953
+ throw new RangeError('config.mode must be one of: "mixed", "readable", "chaotic", "natural');
14954
+ }
14955
+
14956
+ if (grammar !== undefined) {
14957
+ if (typeof grammar !== 'object' || grammar === null) {
14958
+ throw new TypeError('config.grammar must be an object');
14959
+ }
14960
+ const { templates, nouns, verbs, adjectives } = grammar;
14961
+ if (templates !== undefined && !Array.isArray(templates)) {
14962
+ throw new TypeError('config.grammar.templates must be an array of strings');
14963
+ }
14964
+ if (nouns !== undefined && !Array.isArray(nouns)) {
14965
+ throw new TypeError('config.grammar.nouns must be an array of strings');
14966
+ }
14967
+ if (verbs !== undefined && !Array.isArray(verbs)) {
14968
+ throw new TypeError('config.grammar.verbs must be an array of strings');
14969
+ }
14970
+ if (adjectives !== undefined && !Array.isArray(adjectives)) {
14971
+ throw new TypeError('config.grammar.adjectives must be an array of strings');
14972
+ }
14973
+ }
14974
+
14975
+ if (repeatWords !== undefined && typeof repeatWords !== 'boolean') {
14976
+ throw new TypeError('config.repeatWords must be a boolean');
14977
+ }
14978
+
14979
+ if (emojiPlacement !== undefined && !['inline', 'end', 'none'].includes(emojiPlacement)) {
14980
+ throw new RangeError('config.emojiPlacement must be one of: "inline", "end", "none"');
14981
+ }
14982
+
14983
+ if (paragraphs !== undefined) {
14984
+ if (typeof paragraphs !== 'object') {
14985
+ throw new TypeError('config.paragraphs must be an object or null');
14986
+ }
14987
+ const { min, max } = paragraphs;
14988
+ if (min !== undefined && (!Number.isInteger(min) || min < 1)) {
14989
+ throw new TypeError('config.paragraphs.min must be an integer >= 1');
14990
+ }
14991
+ if (max !== undefined && (!Number.isInteger(max) || max < 1)) {
14992
+ throw new TypeError('config.paragraphs.max must be an integer >= 1');
14993
+ }
14994
+ if (min !== undefined && max !== undefined && min > max) {
14995
+ throw new RangeError('config.paragraphs.min cannot be greater than config.paragraphs.max');
14996
+ }
14997
+ }
14998
+
14999
+ if (line !== undefined) {
15000
+ if (typeof line !== 'object' || line === null) {
15001
+ throw new TypeError('config.line must be an object');
15002
+ }
15003
+ const { minLength: lineMin, maxLength: lineMax, emojiChance } = line;
15004
+ if (lineMin !== undefined && (!Number.isInteger(lineMin) || lineMin < 1)) {
15005
+ throw new TypeError('config.line.minLength must be an integer >= 1');
15006
+ }
15007
+ if (lineMax !== undefined && (!Number.isInteger(lineMax) || lineMax < 1)) {
15008
+ throw new TypeError('config.line.maxLength must be an integer >= 1');
15009
+ }
15010
+ if (lineMin !== undefined && lineMax !== undefined && lineMin > lineMax) {
15011
+ throw new RangeError('config.line.minLength cannot be greater than config.line.maxLength');
15012
+ }
15013
+ if (
15014
+ emojiChance !== undefined &&
15015
+ (typeof emojiChance !== 'number' || emojiChance < 0 || emojiChance > 1)
15016
+ ) {
15017
+ throw new RangeError('config.line.emojiChance must be a number between 0 and 1');
15018
+ }
15019
+ }
15020
+
15021
+ this.config.emojiSet = [...defaultEmojis];
15022
+ this.config.wordSet = [...defaultWords];
15023
+
15024
+ this.config.grammar.templates = [...defaultTemplates];
15025
+ this.config.grammar.nouns = [...defaultNouns];
15026
+ this.config.grammar.verbs = [...defaultVerbs];
15027
+ this.config.grammar.adjectives = [...defaultAdjectives];
15028
+ Object.assign(this.config, config);
15029
+ }
15030
+
15031
+ /**
15032
+ * Merges new configuration values into the current instance.
15033
+ * @param {Object} newConfig - Object with one or more configuration overrides.
15034
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15035
+ */
15036
+ configure(newConfig = {}) {
15037
+ Object.assign(this.config, newConfig);
15038
+ return this;
15039
+ }
15040
+
15041
+ /**
15042
+ * Replaces the entire list of grammar templates.
15043
+ * @param {...string[]} templates - One or more arrays or strings containing sentence templates.
15044
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15045
+ */
15046
+ setGrammarTemplates(...templates) {
15047
+ this.config.grammar.templates = templates.flat();
15048
+ return this;
15049
+ }
15050
+
15051
+ /**
15052
+ * Adds new grammar templates to the existing list.
15053
+ * @param {...string[]} templates - One or more arrays or strings containing sentence templates.
15054
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15055
+ */
15056
+ addGrammarTemplates(...templates) {
15057
+ this.config.grammar.templates.push(...templates.flat());
15058
+ return this;
15059
+ }
15060
+
15061
+ /**
15062
+ * Replaces the list of noun words used in grammar templates.
15063
+ * @param {...string[]} nouns - One or more arrays or strings of nouns.
15064
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15065
+ */
15066
+ setGrammarNouns(...nouns) {
15067
+ this.config.grammar.nouns = nouns.flat();
15068
+ return this;
15069
+ }
15070
+
15071
+ /**
15072
+ * Adds noun words to the existing list used in grammar templates.
15073
+ * @param {...string[]} nouns - One or more arrays or strings of nouns.
15074
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15075
+ */
15076
+ addGrammarNouns(...nouns) {
15077
+ this.config.grammar.nouns.push(...nouns.flat());
15078
+ return this;
15079
+ }
15080
+
15081
+ /**
15082
+ * Replaces the list of verb words used in grammar templates.
15083
+ * @param {...string[]} verbs - One or more arrays or strings of verbs.
15084
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15085
+ */
15086
+ setGrammarVerbs(...verbs) {
15087
+ this.config.grammar.verbs = verbs.flat();
15088
+ return this;
15089
+ }
15090
+
15091
+ /**
15092
+ * Adds verb words to the existing list used in grammar templates.
15093
+ * @param {...string[]} verbs - One or more arrays or strings of verbs.
15094
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15095
+ */
15096
+ addGrammarVerbs(...verbs) {
15097
+ this.config.grammar.verbs.push(...verbs.flat());
15098
+ return this;
15099
+ }
15100
+
15101
+ /**
15102
+ * Replaces the list of adjective words used in grammar templates.
15103
+ * @param {...string[]} adjectives - One or more arrays or strings of adjectives.
15104
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15105
+ */
15106
+ setGrammarAdjectives(...adjectives) {
15107
+ this.config.grammar.adjectives = adjectives.flat();
15108
+ return this;
15109
+ }
15110
+
15111
+ /**
15112
+ * Adds adjective words to the existing list used in grammar templates.
15113
+ * @param {...string[]} adjectives - One or more arrays or strings of adjectives.
15114
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15115
+ */
15116
+ addGrammarAdjectives(...adjectives) {
15117
+ this.config.grammar.adjectives.push(...adjectives.flat());
15118
+ return this;
15119
+ }
15120
+
15121
+ /**
15122
+ * Removes specific grammar templates from the current list.
15123
+ * @param {...string[]} templates - One or more arrays or strings of templates to remove.
15124
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15125
+ */
15126
+ removeGrammarTemplates(...templates) {
15127
+ const removeSet = new Set(templates.flat());
15128
+ this.config.grammar.templates = this.config.grammar.templates.filter((t) => !removeSet.has(t));
15129
+ return this;
15130
+ }
15131
+
15132
+ /**
15133
+ * Removes specific noun words from the grammar noun list.
15134
+ * @param {...string[]} nouns - One or more arrays or strings of nouns to remove.
15135
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15136
+ */
15137
+ removeGrammarNouns(...nouns) {
15138
+ const removeSet = new Set(nouns.flat());
15139
+ this.config.grammar.nouns = this.config.grammar.nouns.filter((n) => !removeSet.has(n));
15140
+ return this;
15141
+ }
15142
+
15143
+ /**
15144
+ * Removes specific verb words from the grammar verb list.
15145
+ * @param {...string[]} verbs - One or more arrays or strings of verbs to remove.
15146
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15147
+ */
15148
+ removeGrammarVerbs(...verbs) {
15149
+ const removeSet = new Set(verbs.flat());
15150
+ this.config.grammar.verbs = this.config.grammar.verbs.filter((v) => !removeSet.has(v));
15151
+ return this;
15152
+ }
15153
+
15154
+ /**
15155
+ * Removes specific adjective words from the grammar adjective list.
15156
+ * @param {...string[]} adjectives - One or more arrays or strings of adjectives to remove.
15157
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15158
+ */
15159
+ removeGrammarAdjectives(...adjectives) {
15160
+ const removeSet = new Set(adjectives.flat());
15161
+ this.config.grammar.adjectives = this.config.grammar.adjectives.filter(
15162
+ (a) => !removeSet.has(a),
15163
+ );
15164
+ return this;
15165
+ }
15166
+
15167
+ /**
15168
+ * Replaces the entire word set used in readable/mixed modes.
15169
+ * @param {...string[]} words - One or more arrays or strings of words.
15170
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15171
+ */
15172
+ setWords(...words) {
15173
+ this.config.wordSet = words.flat();
15174
+ return this;
15175
+ }
15176
+
15177
+ /**
15178
+ * Adds new words to the word set used in readable/mixed modes.
15179
+ * @param {...string[]} words - One or more arrays or strings of words.
15180
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15181
+ */
15182
+ addWords(...words) {
15183
+ this.config.wordSet.push(...words.flat());
15184
+ return this;
15185
+ }
15186
+
15187
+ /**
15188
+ * Removes specific words from the word set.
15189
+ * @param {...string[]} words - Words to be removed.
15190
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15191
+ */
15192
+ removeWords(...words) {
15193
+ const removeSet = new Set(words.flat());
15194
+ this.config.wordSet = this.config.wordSet.filter((word) => !removeSet.has(word));
15195
+ return this;
15196
+ }
15197
+
15198
+ /**
15199
+ * Replaces the emoji set used in generated output.
15200
+ * @param {...string[]} emojis - One or more arrays or strings of emojis.
15201
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15202
+ */
15203
+ setEmojis(...emojis) {
15204
+ this.config.emojiSet = emojis.flat();
15205
+ return this;
15206
+ }
15207
+
15208
+ /**
15209
+ * Adds new emojis to the emoji set.
15210
+ * @param {...string[]} emojis - One or more arrays or strings of emojis.
15211
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15212
+ */
15213
+ addEmojis(...emojis) {
15214
+ this.config.emojiSet.push(...emojis.flat());
15215
+ return this;
15216
+ }
15217
+
15218
+ /**
15219
+ * Removes specific emojis from the emoji set.
15220
+ * @param {...string[]} emojis - Emojis to be removed.
15221
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15222
+ */
15223
+ removeEmojis(...emojis) {
15224
+ const removeSet = new Set(emojis.flat());
15225
+ this.config.emojiSet = this.config.emojiSet.filter((emoji) => !removeSet.has(emoji));
15226
+ return this;
15227
+ }
15228
+
15229
+ /**
15230
+ * Returns a random item from an array.
15231
+ * @private
15232
+ * @param {string[]} array - Array to pick from.
15233
+ * @returns {string} - Random item from array.
15234
+ */
15235
+ _getRandomItem(array) {
15236
+ return array[Math.floor(Math.random() * array.length)];
15237
+ }
15238
+
15239
+ /**
15240
+ * Generates a single random content chunk based on the mode and settings.
15241
+ * @private
15242
+ * @returns {string} - A chunk (word, emoji, symbol, number, etc.).
15243
+ */
15244
+ _generateChunk() {
15245
+ const {
15246
+ wordSet,
15247
+ emojiSet,
15248
+ includeNumbers,
15249
+ includeSymbols,
15250
+ useEmojis,
15251
+ readable,
15252
+ mode,
15253
+ emojiPlacement,
15254
+ } = this.config;
15255
+
15256
+ if (mode === 'natural') {
15257
+ return this._generateNaturalSentence();
15258
+ }
15259
+
15260
+ const pools = [];
15261
+
15262
+ if (readable || mode === 'readable' || mode === 'mixed') {
15263
+ pools.push(this._getRandomItem(wordSet));
15264
+ }
15265
+
15266
+ if (mode === 'chaotic' || mode === 'mixed') {
15267
+ pools.push(Math.random().toString(36).slice(2));
15268
+ }
15269
+
15270
+ if (includeNumbers) {
15271
+ pools.push(Math.floor(Math.random() * 99999).toString());
15272
+ }
15273
+
15274
+ if (includeSymbols) {
15275
+ pools.push(this._getRandomItem(this.symbols));
15276
+ }
15277
+
15278
+ if (emojiPlacement === 'inline' && useEmojis && emojiSet.length) {
15279
+ pools.push(this._getRandomItem(emojiSet));
15280
+ }
15281
+
15282
+ return this._getRandomItem(pools);
15283
+ }
15284
+
15285
+ /**
15286
+ * Generates a natural sentence by replacing placeholders in a template.
15287
+ * @private
15288
+ * @returns {string} - A generated sentence.
15289
+ */
15290
+ _generateNaturalSentence() {
15291
+ const { templates, nouns, verbs, adjectives } = this.config.grammar;
15292
+
15293
+ let template = this._getRandomItem(templates);
15294
+
15295
+ return template
15296
+ .replace(/{noun}/g, () => this._getRandomItem(nouns))
15297
+ .replace(/{verb}/g, () => this._getRandomItem(verbs))
15298
+ .replace(/{adj}/g, () => this._getRandomItem(adjectives));
15299
+ }
15300
+
15301
+ /**
15302
+ * Generates a single line of text with target length and rules.
15303
+ * @private
15304
+ * @param {number} targetLength - Target character length for the line.
15305
+ * @param {Set<string>} [seenWords] - Set of already used words (for avoiding repeats).
15306
+ * @returns {string} - A generated line.
15307
+ */
15308
+ _generateLine(targetLength, seenWords) {
15309
+ const { allowWeirdSpacing, repeatWords, readable, emojiSet, useEmojis, emojiPlacement, line } =
15310
+ this.config;
15311
+
15312
+ const parts = [];
15313
+ seenWords ??= new Set();
15314
+
15315
+ while (parts.join(' ').length < targetLength) {
15316
+ let chunk = this._generateChunk();
15317
+
15318
+ if (!repeatWords && seenWords.has(chunk)) continue;
15319
+ seenWords.add(chunk);
15320
+
15321
+ if (allowWeirdSpacing) {
15322
+ if (Math.random() < 0.1) chunk = '\n' + chunk;
15323
+ if (Math.random() < 0.1) chunk = ' ' + chunk;
15324
+ if (Math.random() < 0.05) chunk = chunk.toUpperCase();
15325
+ }
15326
+
15327
+ parts.push(chunk);
15328
+ }
15329
+
15330
+ let lineText = parts.join(readable ? ' ' : '');
15331
+
15332
+ if (
15333
+ emojiPlacement === 'end' &&
15334
+ useEmojis &&
15335
+ emojiSet.length &&
15336
+ Math.random() < (line?.emojiChance || 0)
15337
+ ) {
15338
+ lineText += ' ' + this._getRandomItem(emojiSet);
15339
+ }
15340
+
15341
+ return lineText;
15342
+ }
15343
+
15344
+ /**
15345
+ * Generates lines to form a paragraph based on total length.
15346
+ * @private
15347
+ * @param {number} totalLength - Total target character count for the paragraph.
15348
+ * @returns {string[]} - Array of lines that form the paragraph.
15349
+ */
15350
+ _generateParagraphLines(totalLength) {
15351
+ const { line } = this.config;
15352
+ const lines = [];
15353
+ const seenWords = new Set();
15354
+ let currentTotal = 0;
15355
+
15356
+ while (currentTotal < totalLength) {
15357
+ const lineLength = Math.floor(
15358
+ Math.random() * (line.maxLength - line.minLength + 1) + line.minLength,
15359
+ );
15360
+
15361
+ const adjustedLength = Math.min(lineLength, totalLength - currentTotal);
15362
+
15363
+ lines.push(this._generateLine(adjustedLength, seenWords));
15364
+ currentTotal += adjustedLength;
15365
+ }
15366
+
15367
+ return lines;
15368
+ }
15369
+
15370
+ /**
15371
+ * Generates the final random message, which can be a paragraph or block of lines.
15372
+ * Uses full configuration rules (grammar, symbols, emojis, etc).
15373
+ * @returns {string} - A full generated message.
15374
+ */
15375
+ generate() {
15376
+ const { minLength, maxLength, paragraphs } = this.config;
15377
+
15378
+ const totalLength = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
15379
+
15380
+ if (paragraphs && typeof paragraphs === 'object') {
15381
+ const paraCount =
15382
+ Math.floor(Math.random() * (paragraphs.max - paragraphs.min + 1)) + paragraphs.min;
15383
+ const approxLength = Math.floor(totalLength / paraCount);
15384
+ const paragraphsArray = [];
15385
+
15386
+ for (let i = 0; i < paraCount; i++) {
15387
+ const lines = this._generateParagraphLines(approxLength);
15388
+ paragraphsArray.push(lines.join('\n'));
15389
+ }
15390
+
15391
+ return paragraphsArray.join('\n\n');
15392
+ }
15393
+
15394
+ return this._generateParagraphLines(totalLength).join('\n');
15395
+ }
15396
+ }
15397
+
15398
+ /* harmony default export */ const libs_UltraRandomMsgGen = (UltraRandomMsgGen);
15399
+
15400
+ ;// ./src/v1/libs/TinySmartScroller.mjs
15401
+
15402
+
15403
+
15404
+ /**
15405
+ * Represents the dimensions of a DOM element.
15406
+ *
15407
+ * @typedef {Object} NodeSizes
15408
+ * @property {number} height - The height of the element in pixels.
15409
+ * @property {number} width - The width of the element in pixels.
15410
+ */
15411
+
15412
+ /**
15413
+ * A callback function that receives node size change data and optionally returns a modified NodeSizes object.
15414
+ *
15415
+ * @callback NodeSizesEvent
15416
+ * @param {Element} elem - The DOM element whose size is being tracked.
15417
+ * @param {{ old: NodeSizes, now: NodeSizes }} sizes - The old and new size measurements of the element.
15418
+ * @param {{ old: number, now: number }} elemAmount - The number of matching elements before and after the update.
15419
+ * @returns {NodeSizes|undefined} A modified NodeSizes object to override the default measurement, or undefined to use the original.
15420
+ */
15421
+
15422
+ /**
15423
+ * A generic scroll-related event listener callback function.
15424
+ *
15425
+ * @callback ScrollListenersFunc
15426
+ * @param {any} payload - The data payload passed when the scroll event is triggered. The type may vary depending on the event.
15427
+ * @returns {void}
15428
+ */
15429
+
15430
+ /**
15431
+ * TinySmartScroller is a utility class designed to enhance and manage scroll behaviors within containers or the window.
15432
+ *
15433
+ * It enables advanced scroll monitoring, auto-scrolling to bottom, preserving scroll position during DOM changes,
15434
+ * and detecting visibility changes of elements. This is particularly useful for dynamic UIs like chat applications,
15435
+ * feed viewers, or live content containers.
15436
+ *
15437
+ * Features:
15438
+ * - Detects when the scroll reaches the top, bottom, or custom boundaries
15439
+ * - Supports automatic scrolling to the bottom unless the user scrolls away
15440
+ * - Observes DOM mutations and resizes, and adapts scroll position accordingly
15441
+ * - Emits scroll-related events such as 'onScrollBoundary', 'onAutoScroll', and 'onScrollPause'
15442
+ * - Includes customizable scroll correction filters for layout shift mitigation
15443
+ * - Handles media element load events (e.g. `<img>`, `<iframe>`, `<video>`) to prevent sudden scroll jumps
15444
+ *
15445
+ * This class is **not framework-dependent** and works with vanilla DOM elements and the window object.
15446
+ */
15447
+ class TinySmartScroller {
15448
+ static Utils = { ...collision_namespaceObject, TinyHtml: libs_TinyHtml };
15449
+
15450
+ /** @type {WeakMap<Element, NodeSizes>} */
15451
+ #oldSizes = new WeakMap();
15452
+ /** @type {WeakMap<Element, NodeSizes>} */
15453
+ #newSizes = new WeakMap();
15454
+
15455
+ /** @type {WeakMap<Element, boolean>} */
15456
+ #newVisibles = new WeakMap();
15457
+ /** @type {WeakMap<Element, boolean>} */
15458
+ #oldVisibles = new WeakMap();
15459
+
15460
+ /** @type {WeakMap<Element, boolean>} */
15461
+ #newVisiblesByTime = new WeakMap();
15462
+ /** @type {WeakMap<Element, boolean>} */
15463
+ #oldVisiblesByTime = new WeakMap();
15464
+
15465
+ /** @type {Record<string, ScrollListenersFunc[]>} */
15466
+ #scrollListeners = {};
15467
+
15468
+ /** @type {ResizeObserver|null} */
15469
+ #resizeObserver = null;
15470
+ /** @type {MutationObserver|null} */
15471
+ #mutationObserver = null;
15472
+
15473
+ /** @type {Set<string>} */
15474
+ #loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
15475
+
15476
+ /** @type {null|EventListenerOrEventListenerObject} */
15477
+ #handler = null;
15478
+
15479
+ #isAtBottom = false;
15480
+ #isAtTop = false;
15481
+ #isAtCustomTop = false;
15482
+ #isAtCustomBottom = false;
15483
+
15484
+ #querySelector = '';
15485
+ #useWindow = false;
15486
+ #destroyed = false;
15487
+ #scrollPaused = false;
15488
+ #autoScrollBottom = false;
15489
+ #observeMutations = false;
15490
+ #preserveScrollOnLayoutShift = false;
15491
+ #debounceTime = 0;
15492
+ #elemAmount = 0;
15493
+ #elemOldAmount = 0;
15494
+ #lastKnownScrollBottomOffset = 0;
15495
+ #extraScrollBoundary = 0;
15496
+
15497
+ /** @type {Set<string>} */
15498
+ #attributeFilter;
15499
+
15500
+ /** @type {Element} */
15501
+ #target;
15502
+
15503
+ /** @type {Set<NodeSizesEvent>} */
15504
+ #sizeFilter = new Set();
15505
+
15506
+ /**
15507
+ * Creates a new instance of TinySmartScroller, attaching scroll and resize observers to manage
15508
+ * automatic scroll behaviors, layout shift correction, and visibility tracking.
15509
+ *
15510
+ * @param {Element|Window} target - The scroll container to monitor. Can be an element or `window`.
15511
+ * @param {Object} [options={}] - Optional settings to configure scroll behavior.
15512
+ * @param {number} [options.extraScrollBoundary=0] - Extra margin in pixels to extend scroll boundary detection.
15513
+ * @param {boolean} [options.autoScrollBottom=true] - Whether to auto-scroll to bottom on layout updates.
15514
+ * @param {boolean} [options.observeMutations=true] - Enables MutationObserver to detect DOM changes.
15515
+ * @param {boolean} [options.preserveScrollOnLayoutShift=true] - Prevents scroll jumps when layout changes.
15516
+ * @param {number} [options.debounceTime=100] - Debounce time in milliseconds for scroll events.
15517
+ * @param {string|null} [options.querySelector=null] - Optional CSS selector to filter observed child nodes.
15518
+ * @param {string[]|Set<string>|null} [options.attributeFilter=['class', 'style', 'src', 'data-*', 'height', 'width']]
15519
+ * - Which attributes to observe for changes.
15520
+ */
15521
+ constructor(
15522
+ target,
15523
+ {
15524
+ extraScrollBoundary = 0,
15525
+ autoScrollBottom = true,
15526
+ observeMutations = true,
15527
+ preserveScrollOnLayoutShift = true,
15528
+ debounceTime = 100,
15529
+ querySelector = null,
15530
+ attributeFilter = ['class', 'style', 'src', 'data-*', 'height', 'width'],
15531
+ } = {},
15532
+ ) {
15533
+ // === target ===
15534
+ if (!(target instanceof Element || target === window))
15535
+ throw new TypeError(
15536
+ `TinySmartScroller: 'target' must be a DOM Element or 'window', but got ${typeof target}`,
15537
+ );
15538
+ // === extraScrollBoundary ===
15539
+ if (typeof extraScrollBoundary !== 'number' || Number.isNaN(extraScrollBoundary))
15540
+ throw new TypeError(
15541
+ `TinySmartScroller: 'extraScrollBoundary' must be a valid number, received ${extraScrollBoundary}`,
15542
+ );
15543
+ // === autoScrollBottom ===
15544
+ if (typeof autoScrollBottom !== 'boolean')
15545
+ throw new TypeError(
15546
+ `TinySmartScroller: 'autoScrollBottom' must be a boolean, received ${typeof autoScrollBottom}`,
15547
+ );
15548
+ // === observeMutations ===
15549
+ if (typeof observeMutations !== 'boolean')
15550
+ throw new TypeError(
15551
+ `TinySmartScroller: 'observeMutations' must be a boolean, received ${typeof observeMutations}`,
15552
+ );
15553
+ // === preserveScrollOnLayoutShift ===
15554
+ if (typeof preserveScrollOnLayoutShift !== 'boolean')
15555
+ throw new TypeError(
15556
+ `TinySmartScroller: 'preserveScrollOnLayoutShift' must be a boolean, received ${typeof preserveScrollOnLayoutShift}`,
15557
+ );
15558
+ // === debounceTime ===
15559
+ if (typeof debounceTime !== 'number' || debounceTime < 0 || Number.isNaN(debounceTime))
15560
+ throw new TypeError(
15561
+ `TinySmartScroller: 'debounceTime' must be a non-negative number, received ${debounceTime}`,
15562
+ );
15563
+ // === querySelector ===
15564
+ if (querySelector !== null && typeof querySelector !== 'string')
15565
+ throw new TypeError(
15566
+ `TinySmartScroller: 'querySelector' must be a string or null, received ${typeof querySelector}`,
15567
+ );
15568
+ // === attributeFilter ===
15569
+ const isValidAttrList =
15570
+ attributeFilter === null || Array.isArray(attributeFilter) || attributeFilter instanceof Set;
15571
+ if (!isValidAttrList)
15572
+ throw new TypeError(
15573
+ `TinySmartScroller: 'attributeFilter' must be an array, Set, or null. Got ${typeof attributeFilter}`,
15574
+ );
15575
+
15576
+ // Start values
15577
+ this.#target = target instanceof Window ? document.documentElement : target;
15578
+ this.#useWindow = target instanceof Window;
15579
+ this.#autoScrollBottom = autoScrollBottom;
15580
+ this.#observeMutations = observeMutations;
15581
+ this.#preserveScrollOnLayoutShift = preserveScrollOnLayoutShift;
15582
+ this.#debounceTime = debounceTime;
15583
+ this.#extraScrollBoundary = extraScrollBoundary;
15584
+ this.#querySelector = querySelector || '';
15585
+ this.#attributeFilter = new Set(attributeFilter || undefined);
15586
+
15587
+ // Bind scroll
15588
+ /** @type {NodeJS.Timeout} */
15589
+ let timeout;
15590
+ this.#handler = () => {
15591
+ this._scrollDataUpdater();
15592
+ clearTimeout(timeout);
15593
+ timeout = setTimeout(() => this._onScroll(), this.#debounceTime);
15594
+ };
15595
+ (this.#useWindow ? window : this.#target).addEventListener('scroll', this.#handler, {
15596
+ passive: true,
15597
+ });
15598
+
15599
+ // Mutations
15600
+ if (this.#observeMutations) {
15601
+ this._observeMutations();
15602
+ this._observeResizes(this.#target.children);
15603
+ }
15604
+
15605
+ this._scrollDataUpdater();
15606
+ }
15607
+
15608
+ /**
15609
+ * Returns a size difference callback that only reacts when height changes, filtered by tag name.
15610
+ *
15611
+ * @param {string[]} filter - List of tag names to allow. If empty, all tags are accepted.
15612
+ * @returns {NodeSizesEvent} A function that compares previous and current height, returning height delta.
15613
+ */
15614
+ getSimpleOnHeight(filter = []) {
15615
+ if (!Array.isArray(filter))
15616
+ throw new TypeError('getSimpleOnHeight(filter): filter must be an array of tag names');
15617
+ return (elem, sizes, amounts) => {
15618
+ if ((filter.length > 0 && !filter.includes(elem.tagName)) || amounts.now !== amounts.old)
15619
+ return;
15620
+ const oldSize = sizes.old;
15621
+ const newSize = sizes.now;
15622
+ const height = newSize.height - oldSize.height;
15623
+ return { height, width: 0 };
15624
+ };
15625
+ }
15626
+
15627
+ /**
15628
+ * Adds a height difference callback to the size filter system.
15629
+ *
15630
+ * @param {string[]} filter - List of tag names to allow.
15631
+ * @returns {NodeSizesEvent} The added size difference callback.
15632
+ */
15633
+ addSimpleOnHeight(filter) {
15634
+ if (!Array.isArray(filter))
15635
+ throw new TypeError('addSimpleOnHeight(filter): filter must be an array of tag names');
15636
+ const result = this.getSimpleOnHeight(filter);
15637
+ this.onSize(result);
15638
+ return result;
15639
+ }
15640
+
15641
+ /**
15642
+ * Returns a list of all currently tracked load tags.
15643
+ *
15644
+ * @returns {string[]} Array of tag names.
15645
+ */
15646
+ getLoadTags() {
15647
+ return Array.from(this.#loadTags);
15648
+ }
15649
+
15650
+ /**
15651
+ * Adds a new tag to the set of load tags.
15652
+ *
15653
+ * @param {string} tag - The tag name to add (e.g., 'IMG').
15654
+ */
15655
+ addLoadTag(tag) {
15656
+ if (typeof tag !== 'string') throw new TypeError('addLoadTag(tag): tag must be a string');
15657
+ this.#loadTags.add(tag.toUpperCase());
15658
+ }
15659
+
15660
+ /**
15661
+ * Removes a tag from the set of load tags.
15662
+ *
15663
+ * @param {string} tag - The tag name to remove.
15664
+ */
15665
+ removeLoadTag(tag) {
15666
+ if (typeof tag !== 'string') throw new TypeError('removeLoadTag(tag): tag must be a string');
15667
+ this.#loadTags.delete(tag.toUpperCase());
15668
+ }
15669
+
15670
+ /**
15671
+ * Checks whether a tag is tracked as a load tag.
15672
+ *
15673
+ * @param {string} tag - The tag name to check.
15674
+ * @returns {boolean} True if the tag is being tracked.
15675
+ */
15676
+ hasLoadTag(tag) {
15677
+ if (typeof tag !== 'string') throw new TypeError('hasLoadTag(tag): tag must be a string');
15678
+ return this.#loadTags.has(tag.toUpperCase());
15679
+ }
15680
+
15681
+ /**
15682
+ * Clears the set of load tags. If `addDefault` is true, it will reset to the default tags: 'IMG', 'IFRAME', and 'VIDEO'.
15683
+ *
15684
+ * @param {boolean} [addDefault=false] - Whether to restore the default tags after clearing.
15685
+ * @throws {TypeError} If `addDefault` is not a boolean.
15686
+ */
15687
+ resetLoadTags(addDefault = false) {
15688
+ if (typeof addDefault !== 'boolean')
15689
+ throw new TypeError('resetLoadTags(addDefault): addDefault must be a boolean');
15690
+ this.#loadTags.clear();
15691
+ if (!addDefault) return;
15692
+ this.#loadTags.add('IMG');
15693
+ this.#loadTags.add('IFRAME');
15694
+ this.#loadTags.add('VIDEO');
15695
+ }
15696
+
15697
+ /**
15698
+ * Returns a list of all currently tracked attribute filters.
15699
+ *
15700
+ * @returns {string[]} Array of attribute names.
15701
+ */
15702
+ getAttributeFilters() {
15703
+ return Array.from(this.#attributeFilter);
15704
+ }
15705
+
15706
+ /**
15707
+ * Adds an attribute to the filter list.
15708
+ *
15709
+ * @param {string} attr - The attribute name to add.
15710
+ */
15711
+ addAttributeFilter(attr) {
15712
+ if (typeof attr !== 'string')
15713
+ throw new TypeError('addAttributeFilter(attr): attr must be a string');
15714
+ this.#attributeFilter.add(attr);
15715
+ }
15716
+
15717
+ /**
15718
+ * Removes an attribute from the filter list.
15719
+ *
15720
+ * @param {string} attr - The attribute name to remove.
15721
+ */
15722
+ removeAttributeFilter(attr) {
15723
+ if (typeof attr !== 'string')
15724
+ throw new TypeError('removeAttributeFilter(attr): attr must be a string');
15725
+ this.#attributeFilter.delete(attr);
15726
+ }
15727
+
15728
+ /**
15729
+ * Checks whether a specific attribute is being filtered.
15730
+ *
15731
+ * @param {string} attr - The attribute name to check.
15732
+ * @returns {boolean} True if the attribute is being filtered.
15733
+ */
15734
+ hasAttributeFilter(attr) {
15735
+ if (typeof attr !== 'string')
15736
+ throw new TypeError('hasAttributeFilter(attr): attr must be a string');
15737
+ return this.#attributeFilter.has(attr);
15738
+ }
15739
+
15740
+ /**
15741
+ * Clears the set of observed attribute filters. If `addDefault` is true, it will reset to the default attributes:
15742
+ * 'class', 'style', 'src', 'data-*', 'height', and 'width'.
15743
+ *
15744
+ * @param {boolean} [addDefault=false] - Whether to restore the default attribute filters after clearing.
15745
+ * @throws {TypeError} If `addDefault` is not a boolean.
15746
+ */
15747
+ resetAttributeFilters(addDefault = false) {
15748
+ if (typeof addDefault !== 'boolean')
15749
+ throw new TypeError('resetAttributeFilters(addDefault): addDefault must be a boolean');
15750
+ this.#attributeFilter.clear();
15751
+ if (!addDefault) return;
15752
+ ['class', 'style', 'src', 'data-*', 'height', 'width'].forEach((attr) =>
15753
+ this.#attributeFilter.add(attr),
15754
+ );
15755
+ }
15756
+
15757
+ /**
15758
+ * Registers a custom node size change handler to the internal size filter set.
15759
+ *
15760
+ * @param {NodeSizesEvent} handler - Function that compares old and new sizes.
15761
+ */
15762
+ onSize(handler) {
15763
+ if (this.#destroyed) return;
15764
+ if (typeof handler !== 'function')
15765
+ throw new TypeError('onSize(handler): handler must be a function');
15766
+ this.#sizeFilter.add(handler);
15767
+ }
15768
+
15769
+ /**
15770
+ * Unregisters a previously registered size handler from the internal filter set.
15771
+ *
15772
+ * @param {NodeSizesEvent} handler - The handler function to remove.
15773
+ */
15774
+ offSize(handler) {
15775
+ if (this.#destroyed) return;
15776
+ if (typeof handler !== 'function')
15777
+ throw new TypeError('offSize(handler): handler must be a function');
15778
+ this.#sizeFilter.delete(handler);
15779
+ }
15780
+
15781
+ /**
15782
+ * Adds a scroll-related event listener.
15783
+ *
15784
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15785
+ * @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
15786
+ */
15787
+ on(event, handler) {
15788
+ if (this.#destroyed) return;
15789
+ if (typeof event !== 'string')
15790
+ throw new TypeError('on(event, handler): event name must be a string');
15791
+ if (typeof handler !== 'function')
15792
+ throw new TypeError('on(event, handler): handler must be a function');
15793
+
15794
+ if (!this.#scrollListeners[event]) this.#scrollListeners[event] = [];
15795
+ this.#scrollListeners[event].push(handler);
15796
+ }
15797
+
15798
+ /**
15799
+ * Removes a previously registered scroll-related event listener.
15800
+ *
15801
+ * @param {string} event - The name of the event to remove the handler from.
15802
+ * @param {ScrollListenersFunc} handler - The specific callback function to remove.
15803
+ */
15804
+ off(event, handler) {
15805
+ if (this.#destroyed) return;
15806
+ if (typeof event !== 'string')
15807
+ throw new TypeError('off(event, handler): event name must be a string');
15808
+ if (typeof handler !== 'function')
15809
+ throw new TypeError('off(event, handler): handler must be a function');
15810
+
15811
+ const listeners = this.#scrollListeners[event];
15812
+ if (!Array.isArray(listeners)) return;
15813
+
15814
+ const index = listeners.indexOf(handler);
15815
+ if (index !== -1) listeners.splice(index, 1);
15816
+
15817
+ // Optionally clean up empty arrays (optional)
15818
+ if (listeners.length === 0) delete this.#scrollListeners[event];
15819
+ }
15820
+
15821
+ /**
15822
+ * Checks which elements inside the target are currently visible and updates internal maps.
15823
+ *
15824
+ * @returns {Map<Element, { oldIsVisible: boolean; isVisible: boolean }>} Visibility comparison results.
15825
+ */
15826
+ _scrollDataUpdater() {
15827
+ const results = new Map();
15828
+ this.#target.querySelectorAll(this.#querySelector || '*').forEach((target) => {
15829
+ const oldIsVisible = this.#newVisibles.get(target) ?? false;
15830
+ this.#oldVisibles.set(target, oldIsVisible);
15831
+
15832
+ const isVisible = libs_TinyHtml.isInContainer(this.#target, target);
15833
+ this.#newVisibles.set(target, isVisible);
15834
+
15835
+ results.set(target, { oldIsVisible, isVisible });
15836
+ });
15837
+ return results;
15838
+ }
15839
+
15840
+ /**
15841
+ * Emits a scroll-related event to all registered listeners.
15842
+ *
15843
+ * @param {string} event - Event name.
15844
+ * @param {*} [payload] - Optional event data payload.
15845
+ */
15846
+ _emit(event, payload) {
15847
+ if (this.#destroyed) return;
15848
+ if (typeof event !== 'string')
15849
+ throw new TypeError('_emit(event, payload): event name must be a string');
15850
+ (this.#scrollListeners[event] || []).forEach((fn) => fn(payload));
15851
+ }
15852
+
15853
+ /**
15854
+ * Handles scroll events, calculates position-related statuses, and emits appropriate events.
15855
+ */
15856
+ _onScroll() {
15857
+ if (this.#destroyed) return;
15858
+ // Get values
15859
+ const scrollCache = this._scrollDataUpdater();
15860
+ const el = this.#target;
15861
+ const scrollTop = el.scrollTop;
15862
+ const scrollHeight = el.scrollHeight;
15863
+ const clientHeight = el.clientHeight;
15864
+
15865
+ // Prepare sroll values
15866
+ const scrollResult = { scrollTop, scrollHeight, clientHeight };
15867
+ let atResult = null;
15868
+ let atCustomResult = null;
15869
+
15870
+ const atTop = scrollTop === 0;
15871
+ const atBottom = scrollTop + clientHeight >= scrollHeight - 1;
15872
+
15873
+ const atCustomTop = scrollTop <= 0 + this.#extraScrollBoundary;
15874
+ const atCustomBottom = scrollTop + clientHeight >= scrollHeight - 1 - this.#extraScrollBoundary;
15875
+
15876
+ // Scroll results
15877
+ if (atTop && atBottom) atResult = 'all';
15878
+ else if (atTop) atResult = 'top';
15879
+ else if (atBottom) atResult = 'bottom';
15880
+
15881
+ if (atCustomTop && atCustomBottom) atCustomResult = 'all';
15882
+ else if (atCustomTop) atCustomResult = 'top';
15883
+ else if (atCustomBottom) atCustomResult = 'bottom';
15884
+
15885
+ this.#isAtTop = atTop;
15886
+ this.#isAtBottom = atBottom;
15887
+
15888
+ this.#isAtCustomTop = atCustomTop;
15889
+ this.#isAtCustomBottom = atCustomBottom;
15890
+
15891
+ this.#scrollPaused = !(this.#autoScrollBottom && this.#isAtBottom);
15892
+
15893
+ this.#lastKnownScrollBottomOffset = scrollHeight - scrollTop - clientHeight;
15894
+
15895
+ // Send results
15896
+ this._emit('onScrollBoundary', { status: atResult, ...scrollResult, scrollCache });
15897
+ this._emit('onExtraScrollBoundary', { status: atCustomResult, ...scrollResult, scrollCache });
15898
+
15899
+ if (!this.#scrollPaused) {
15900
+ this._emit('onAutoScroll', { ...scrollResult, scrollCache });
15901
+ } else {
15902
+ this._emit('onScrollPause', { ...scrollResult, scrollCache });
15903
+ }
15904
+ }
15905
+
15906
+ /**
15907
+ * Attempts to correct the scroll position when layout shifts happen, preserving the user position if needed.
15908
+ *
15909
+ * @param {Element[]} [targets=[]] - List of elements involved in the size change.
15910
+ */
15911
+ _fixScroll(targets = []) {
15912
+ if (this.#destroyed) return;
15913
+ // === Validation ===
15914
+ if (!Array.isArray(targets))
15915
+ throw new TypeError('_fixScroll: targets must be an array of Elements');
15916
+
15917
+ // Get Scroll data
15918
+ const prevScrollHeight = this.#target.scrollHeight;
15919
+ const prevScrollTop = this.#target.scrollTop;
15920
+ const prevBottomOffset =
15921
+ this.#target.scrollHeight - this.#target.scrollTop - this.#target.clientHeight;
15922
+
15923
+ // Get new size
15924
+ const newScrollHeight = this.#target.scrollHeight;
15925
+ const heightDelta = newScrollHeight - prevScrollHeight;
15926
+
15927
+ /** @type {() => NodeSizes} */
15928
+ const calculateScrollSize = () => {
15929
+ // Run size getter
15930
+ const scrollSize = { height: 0, width: 0 };
15931
+ for (const target of targets) {
15932
+ const tgOs = this.#oldSizes.get(target) || { height: 0, width: 0 };
15933
+ const tgNs = this.#newSizes.get(target) || { height: 0, width: 0 };
15934
+ this.#sizeFilter.forEach((fn) => {
15935
+ /** @type {NodeSizes| undefined} */
15936
+ const sizes = fn(
15937
+ target,
15938
+ { old: tgOs, now: tgNs },
15939
+ { old: this.#elemOldAmount, now: this.#elemAmount },
15940
+ );
15941
+
15942
+ // Fix size
15943
+ if (this.#newVisibles.get(target) || this.#newVisiblesByTime.get(target)) {
15944
+ if (typeof sizes !== 'undefined' && typeof sizes !== 'object')
15945
+ throw new Error('_fixScroll: size filter must return an object or undefined');
15946
+ if (typeof sizes === 'undefined') return;
15947
+ scrollSize.height = sizes.height;
15948
+ scrollSize.width = sizes.width;
15949
+ }
15950
+ });
15951
+ }
15952
+
15953
+ // Checker
15954
+ if (typeof scrollSize.height !== 'number' && scrollSize.height < 0)
15955
+ throw new Error('_fixScroll: invalid scrollSize.height value');
15956
+ if (typeof scrollSize.width !== 'number' && scrollSize.width < 0)
15957
+ throw new Error('_fixScroll: invalid scrollSize.width value');
15958
+
15959
+ if (scrollSize.height !== 0 || scrollSize.width !== 0) {
15960
+ for (const target of targets) {
15961
+ this.#newVisiblesByTime.set(target, this.#newVisibles.get(target) ?? false);
15962
+ this.#oldVisiblesByTime.set(target, this.#oldVisibles.get(target) ?? false);
15963
+ }
15964
+ }
15965
+
15966
+ return scrollSize;
15967
+ };
15968
+
15969
+ // Fix scroll size
15970
+ if (
15971
+ this.#elemOldAmount > 0 &&
15972
+ libs_TinyHtml.hasScroll(this.#target).v &&
15973
+ this.#autoScrollBottom &&
15974
+ this.#preserveScrollOnLayoutShift &&
15975
+ !this.#isAtBottom &&
15976
+ !this.#isAtTop
15977
+ ) {
15978
+ const scrollSize = calculateScrollSize();
15979
+ // Complete
15980
+ this.#target.scrollTop = prevScrollTop + heightDelta + scrollSize.height;
15981
+ if (scrollSize.width > 0)
15982
+ this.#target.scrollLeft = this.#target.scrollLeft + scrollSize.width;
15983
+ }
15984
+
15985
+ // Normal stuff
15986
+ else if (!this.#scrollPaused && this.#autoScrollBottom) {
15987
+ calculateScrollSize();
15988
+ this.scrollToBottom();
15989
+ } else if (!this.#autoScrollBottom && !this.#isAtBottom) {
15990
+ calculateScrollSize();
15991
+ this.#target.scrollTop =
15992
+ this.#target.scrollHeight - this.#target.clientHeight - prevBottomOffset;
15993
+ }
15994
+ }
15995
+ /**
15996
+ * Sets up a MutationObserver to watch for DOM changes and react accordingly to maintain scroll consistency.
15997
+ */
15998
+ _observeMutations() {
15999
+ this.#mutationObserver = new MutationObserver((mutations) => {
16000
+ if (this.#destroyed) return;
16001
+ this._scrollDataUpdater();
16002
+ this.#elemOldAmount = this.#elemAmount;
16003
+ this.#elemAmount = this.#target.childElementCount;
16004
+
16005
+ mutations.forEach((mutation) => {
16006
+ mutation.addedNodes.forEach((node) => {
16007
+ if (!(node instanceof Element) || node.nodeType !== 1) return;
16008
+
16009
+ this._observeResizes([node]);
16010
+ this._listenLoadEvents(node);
16011
+
16012
+ if (this.#querySelector) {
16013
+ const children = node.querySelectorAll(this.#querySelector);
16014
+ this._observeResizes(children);
16015
+ this._listenLoadEvents(children);
16016
+ }
16017
+ });
16018
+ });
16019
+
16020
+ this._fixScroll();
16021
+ });
16022
+
16023
+ // Install observer
16024
+ this.#mutationObserver.observe(this.#target, {
16025
+ childList: true,
16026
+ subtree: true,
16027
+ attributes: true,
16028
+ attributeFilter:
16029
+ this.#attributeFilter.size > 0 ? Array.from(this.#attributeFilter) : undefined,
16030
+ });
16031
+ }
16032
+
16033
+ /**
16034
+ * Adds a ResizeObserver to monitor elements' size changes and trigger layout adjustments.
16035
+ *
16036
+ * @param {NodeListOf<Element>|Element[]|HTMLCollection} elements - Elements to observe.
16037
+ */
16038
+ _observeResizes(elements) {
16039
+ // Add resize observer
16040
+ if (!this.#resizeObserver) {
16041
+ this.#resizeObserver = new ResizeObserver((entries) => {
16042
+ if (this.#destroyed) return;
16043
+ this._scrollDataUpdater();
16044
+ /** @type {Element[]} */
16045
+ const targets = [];
16046
+ for (const entry of entries) {
16047
+ // Target
16048
+ const target = entry.target;
16049
+
16050
+ // Update old size
16051
+ const oldSize = this.#newSizes.get(target);
16052
+ if (oldSize) this.#oldSizes.set(target, oldSize);
16053
+
16054
+ // Set new size
16055
+ const { width, height } = entry.contentRect;
16056
+ this.#newSizes.set(target, { width, height });
16057
+ targets.push(target);
16058
+ }
16059
+
16060
+ // Animation frame
16061
+ this._fixScroll(targets);
16062
+ });
16063
+ }
16064
+
16065
+ // Execute observer
16066
+ Array.from(elements).forEach((el) => {
16067
+ if (!this.#resizeObserver)
16068
+ throw new Error('_observeResizes: ResizeObserver instance is not initialized');
16069
+ this.#resizeObserver.observe(el);
16070
+ });
16071
+ }
16072
+
16073
+ /**
16074
+ * Listens for media/content load events (e.g., images, iframes, videos) to trigger scroll updates.
16075
+ *
16076
+ * @param {NodeListOf<Element>|Element} elements - Target element(s) to listen on.
16077
+ */
16078
+ _listenLoadEvents(elements) {
16079
+ if (this.#destroyed) return;
16080
+ const list = elements instanceof NodeList ? Array.from(elements) : [elements];
16081
+
16082
+ list.forEach((el) => {
16083
+ if (this.#loadTags.has(el.tagName)) {
16084
+ // @ts-ignore
16085
+ if (!el.complete) {
16086
+ el.addEventListener('load', () => {
16087
+ this._scrollDataUpdater();
16088
+ if (!this.#scrollPaused && this.#autoScrollBottom) {
16089
+ this.scrollToBottom();
16090
+ }
16091
+ });
16092
+ }
16093
+ }
16094
+ });
16095
+ }
16096
+
16097
+ /**
16098
+ * Returns the internal scroll container element being monitored.
16099
+ *
16100
+ * @returns {Element} The DOM element used as the scroll container target.
16101
+ */
16102
+ get target() {
16103
+ return this.#target;
16104
+ }
16105
+
16106
+ /**
16107
+ * Returns the previous size of a given element, or undefined if not tracked.
16108
+ *
16109
+ * @param {Element} el - The DOM element to query.
16110
+ * @returns {NodeSizes|null} The old size, or undefined.
16111
+ */
16112
+ getOldSize(el) {
16113
+ return this.#oldSizes.get(el) ?? null;
16114
+ }
16115
+
16116
+ /**
16117
+ * Returns the current size of a given element, or undefined if not tracked.
16118
+ *
16119
+ * @param {Element} el - The DOM element to query.
16120
+ * @returns {NodeSizes|null} The new size, or undefined.
16121
+ */
16122
+ getNewSize(el) {
16123
+ return this.#newSizes.get(el) ?? null;
16124
+ }
16125
+
16126
+ /**
16127
+ * Returns whether the given element was visible in the last scroll update.
16128
+ *
16129
+ * @param {Element} el - The DOM element to check.
16130
+ * @returns {boolean} True if visible, false if not, or undefined if not tracked.
16131
+ */
16132
+ wasVisible(el) {
16133
+ return this.#oldVisibles.get(el) ?? false;
16134
+ }
16135
+
16136
+ /**
16137
+ * Returns whether the given element is currently visible.
16138
+ *
16139
+ * @param {Element} el - The DOM element to check.
16140
+ * @returns {boolean} True if visible, false if not, or undefined if not tracked.
16141
+ */
16142
+ isVisible(el) {
16143
+ return this.#newVisibles.get(el) ?? false;
16144
+ }
16145
+
16146
+ /**
16147
+ * Returns whether the element was visible in the last time-based visibility check.
16148
+ *
16149
+ * @param {Element} el - The DOM element to check.
16150
+ * @returns {boolean} Visibility state from the previous timed check.
16151
+ */
16152
+ wasTimedVisible(el) {
16153
+ return this.#oldVisiblesByTime.get(el) ?? false;
16154
+ }
16155
+
16156
+ /**
16157
+ * Returns whether the element is currently visible in the time-based check.
16158
+ *
16159
+ * @param {Element} el - The DOM element to check.
16160
+ * @returns {boolean} Visibility state from the current timed check.
16161
+ */
16162
+ isTimedVisible(el) {
16163
+ return this.#newVisiblesByTime.get(el) ?? false;
16164
+ }
16165
+
16166
+ /**
16167
+ * Sets the extra scroll boundary margin used when determining if the user is at a "custom" bottom or top.
16168
+ *
16169
+ * @param {number} value - Pixels of additional margin to use.
16170
+ */
16171
+ setExtraScrollBoundary(value) {
16172
+ if (typeof value !== 'number' || Number.isNaN(value))
16173
+ throw new TypeError('setExtraScrollBoundary(value): value must be a valid number');
16174
+ this.#extraScrollBoundary = value;
16175
+ }
16176
+
16177
+ /**
16178
+ * Returns the current extra scroll boundary setting.
16179
+ *
16180
+ * @returns {number}
16181
+ */
16182
+ getExtraScrollBoundary() {
16183
+ return this.#extraScrollBoundary;
16184
+ }
16185
+
16186
+ /**
16187
+ * Returns the last known distance (in pixels) from the bottom of the scroll container.
16188
+ *
16189
+ * @returns {number}
16190
+ */
16191
+ getLastKnownScrollBottomOffset() {
16192
+ return this.#lastKnownScrollBottomOffset;
16193
+ }
16194
+
16195
+ /**
16196
+ * Forces the scroll position to move to the very bottom of the target.
16197
+ */
16198
+ scrollToBottom() {
16199
+ this.#target.scrollTop = this.#target.scrollHeight;
16200
+ }
16201
+
16202
+ /**
16203
+ * Forces the scroll position to move to the very top of the target.
16204
+ */
16205
+ scrollToTop() {
16206
+ this.#target.scrollTop = 0;
16207
+ }
16208
+
16209
+ /**
16210
+ * Checks if the user is within the defined extra scroll boundary from the bottom.
16211
+ *
16212
+ * @returns {boolean}
16213
+ */
16214
+ isUserAtCustomBottom() {
16215
+ return this.#isAtCustomBottom;
16216
+ }
16217
+
16218
+ /**
16219
+ * Checks if the user is within the defined extra scroll boundary from the top.
16220
+ *
16221
+ * @returns {boolean}
16222
+ */
16223
+ isUserAtCustomTop() {
16224
+ return this.#isAtCustomTop;
16225
+ }
16226
+
16227
+ /**
16228
+ * Returns true if the user is currently scrolled to the bottom of the element.
16229
+ *
16230
+ * @returns {boolean}
16231
+ */
16232
+ isUserAtBottom() {
16233
+ return this.#isAtBottom;
16234
+ }
16235
+
16236
+ /**
16237
+ * Returns true if the user is currently scrolled to the top of the element.
16238
+ *
16239
+ * @returns {boolean}
16240
+ */
16241
+ isUserAtTop() {
16242
+ return this.#isAtTop;
16243
+ }
16244
+
16245
+ /**
16246
+ * Returns true if automatic scrolling is currently paused.
16247
+ *
16248
+ * @returns {boolean}
16249
+ */
16250
+ isScrollPaused() {
16251
+ return this.#scrollPaused;
16252
+ }
16253
+
16254
+ /**
16255
+ * Returns whether the target is the window object.
16256
+ *
16257
+ * @returns {boolean} True if the scroll target is window, false otherwise.
16258
+ */
16259
+ isWindow() {
16260
+ return this.#useWindow;
16261
+ }
16262
+
16263
+ /**
16264
+ * Returns whether the instance has been destroyed.
16265
+ *
16266
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
16267
+ */
16268
+ isDestroyed() {
16269
+ return this.#destroyed;
16270
+ }
16271
+
16272
+ /**
16273
+ * Returns whether auto-scroll-to-bottom is enabled.
16274
+ *
16275
+ * @returns {boolean} True if auto-scroll is active, false otherwise.
16276
+ */
16277
+ getAutoScrollBottom() {
16278
+ return this.#autoScrollBottom;
16279
+ }
16280
+
16281
+ /**
16282
+ * Returns whether MutationObserver is enabled.
16283
+ *
16284
+ * @returns {boolean} True if mutation observation is active, false otherwise.
16285
+ */
16286
+ getObserveMutations() {
16287
+ return this.#observeMutations;
16288
+ }
16289
+
16290
+ /**
16291
+ * Returns whether layout shift protection is enabled.
16292
+ *
16293
+ * @returns {boolean} True if scroll preservation is active, false otherwise.
16294
+ */
16295
+ getPreserveScrollOnLayoutShift() {
16296
+ return this.#preserveScrollOnLayoutShift;
16297
+ }
16298
+
16299
+ /**
16300
+ * Returns the debounce delay in milliseconds used for scroll events.
16301
+ *
16302
+ * @returns {number} Debounce delay time.
16303
+ */
16304
+ getDebounceTime() {
16305
+ return this.#debounceTime;
16306
+ }
16307
+
16308
+ /**
16309
+ * Returns the current number of matching elements observed inside the scroll target.
16310
+ *
16311
+ * @returns {number} Current count of matching elements.
16312
+ */
16313
+ getElemAmount() {
16314
+ return this.#elemAmount;
16315
+ }
16316
+
16317
+ /**
16318
+ * Returns the previous known count of matching elements from the last update.
16319
+ *
16320
+ * @returns {number} Previous count of matching elements.
16321
+ */
16322
+ getPrevElemAmount() {
16323
+ return this.#elemOldAmount;
16324
+ }
16325
+
16326
+ /**
16327
+ * Returns the query selector string used to filter observed elements.
16328
+ *
16329
+ * @returns {string} The CSS selector string, or an empty string if none was provided.
16330
+ */
16331
+ getQuerySelector() {
16332
+ return this.#querySelector;
16333
+ }
16334
+
16335
+ /**
16336
+ * Disconnects all listeners, observers, and clears memory structures.
16337
+ * Once destroyed, this instance should no longer be used.
16338
+ */
16339
+ destroy() {
16340
+ if (this.#destroyed) return;
16341
+ this.#destroyed = true;
16342
+
16343
+ // Disconnects MutationObserver
16344
+ if (this.#mutationObserver) {
16345
+ this.#mutationObserver.disconnect();
16346
+ this.#mutationObserver = null;
16347
+ }
16348
+
16349
+ // Disconnect ResizeObserver
16350
+ if (this.#resizeObserver) {
16351
+ this.#resizeObserver.disconnect();
16352
+ this.#resizeObserver = null;
16353
+ }
16354
+
16355
+ // Removes scroll listener
16356
+ const target = this.#useWindow ? window : this.#target;
16357
+ if (this.#handler) target.removeEventListener('scroll', this.#handler);
16358
+
16359
+ // Clean the WeakMaps
16360
+ this.#oldSizes = new WeakMap();
16361
+ this.#newSizes = new WeakMap();
16362
+ this.#newVisibles = new WeakMap();
16363
+ this.#oldVisibles = new WeakMap();
16364
+ this.#newVisiblesByTime = new WeakMap();
16365
+ this.#oldVisiblesByTime = new WeakMap();
16366
+
16367
+ // Cleans listeners and filters
16368
+ this.#scrollListeners = {};
16369
+ this.#sizeFilter.clear();
16370
+ this.#loadTags.clear();
16371
+ }
16372
+ }
16373
+
16374
+ /* harmony default export */ const libs_TinySmartScroller = (TinySmartScroller);
16375
+
16376
+ ;// ./src/v1/index.mjs
16377
+
16378
+
13932
16379
 
13933
16380
 
13934
16381