tiny-essentials 1.16.2 → 1.17.1

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 (133) 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/TinyBasicsEs.js +729 -93
  94. package/dist/v1/TinyBasicsEs.min.js +1 -1
  95. package/dist/v1/TinyDragger.js +656 -69
  96. package/dist/v1/TinyDragger.min.js +1 -1
  97. package/dist/v1/TinyEssentials.js +2688 -116
  98. package/dist/v1/TinyEssentials.min.js +1 -1
  99. package/dist/v1/TinyHtml.js +656 -69
  100. package/dist/v1/TinyHtml.min.js +1 -1
  101. package/dist/v1/TinySmartScroller.js +6378 -0
  102. package/dist/v1/TinySmartScroller.min.js +1 -0
  103. package/dist/v1/TinyUploadClicker.js +1732 -118
  104. package/dist/v1/TinyUploadClicker.min.js +1 -1
  105. package/dist/v1/UltraRandomMsgGen.js +995 -0
  106. package/dist/v1/UltraRandomMsgGen.min.js +1 -0
  107. package/dist/v1/basics/html.cjs +74 -24
  108. package/dist/v1/basics/html.d.mts +38 -15
  109. package/dist/v1/basics/html.mjs +63 -20
  110. package/dist/v1/build/TinySmartScroller.cjs +7 -0
  111. package/dist/v1/build/TinySmartScroller.d.mts +3 -0
  112. package/dist/v1/build/TinySmartScroller.mjs +2 -0
  113. package/dist/v1/build/UltraRandomMsgGen.cjs +7 -0
  114. package/dist/v1/build/UltraRandomMsgGen.d.mts +3 -0
  115. package/dist/v1/build/UltraRandomMsgGen.mjs +2 -0
  116. package/dist/v1/index.cjs +4 -0
  117. package/dist/v1/index.d.mts +3 -1
  118. package/dist/v1/index.mjs +3 -1
  119. package/dist/v1/libs/TinyHtml.cjs +656 -69
  120. package/dist/v1/libs/TinyHtml.d.mts +440 -139
  121. package/dist/v1/libs/TinyHtml.mjs +591 -72
  122. package/dist/v1/libs/TinySmartScroller.cjs +976 -0
  123. package/dist/v1/libs/TinySmartScroller.d.mts +458 -0
  124. package/dist/v1/libs/TinySmartScroller.mjs +856 -0
  125. package/dist/v1/libs/UltraRandomMsgGen.cjs +956 -0
  126. package/dist/v1/libs/UltraRandomMsgGen.d.mts +390 -0
  127. package/dist/v1/libs/UltraRandomMsgGen.mjs +825 -0
  128. package/docs/v1/README.md +2 -0
  129. package/docs/v1/basics/html.md +17 -2
  130. package/docs/v1/libs/TinyHtml.md +296 -8
  131. package/docs/v1/libs/TinySmartScroller.md +523 -0
  132. package/docs/v1/libs/UltraRandomMsgGen.md +289 -0
  133. 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
  /**
@@ -6990,6 +7041,19 @@ const {
6990
7041
  areElsCollRight: TinyHtml_areElsCollRight,
6991
7042
  } = collision_namespaceObject;
6992
7043
 
7044
+ /**
7045
+ * Callback invoked on each animation frame with the current scroll position,
7046
+ * normalized animation time (`0` to `1`), and a completion flag.
7047
+ *
7048
+ * @typedef {(progress: { x: number, y: number, isComplete: boolean, time: number }) => void} OnScrollAnimation
7049
+ */
7050
+
7051
+ /**
7052
+ * A list of supported easing function names for smooth animations.
7053
+ *
7054
+ * @typedef {'linear' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'} Easings
7055
+ */
7056
+
6993
7057
  /**
6994
7058
  * Represents a raw Node element or an instance of TinyHtml.
6995
7059
  * This type is used to abstract interactions with both plain elements
@@ -7045,6 +7109,21 @@ const {
7045
7109
  * @typedef {Element|Window} ElementAndWindow
7046
7110
  */
7047
7111
 
7112
+ /**
7113
+ * Represents a raw DOM element/window/document or an instance of TinyHtml.
7114
+ * This type is used to abstract interactions with both plain elements
7115
+ * and wrapped elements via the TinyHtml class.
7116
+ *
7117
+ * @typedef {ElementAndWinAndDoc|TinyHtml} TinyElementAndWinAndDoc
7118
+ */
7119
+
7120
+ /**
7121
+ * Represents a value that can be either a DOM Element, or the global Window object, or the document object.
7122
+ * Useful for functions that operate generically on scrollable or measurable targets.
7123
+ *
7124
+ * @typedef {Element|Window|Document} ElementAndWinAndDoc
7125
+ */
7126
+
7048
7127
  /**
7049
7128
  * A parameter type used for filtering or matching elements.
7050
7129
  * It can be:
@@ -7061,7 +7140,7 @@ const {
7061
7140
  * Elements accepted as constructor values for TinyHtml.
7062
7141
  * These include common DOM elements and root containers.
7063
7142
  *
7064
- * @typedef {Window|Element|Document} ConstructorElValues
7143
+ * @typedef {Window|Element|Document|Text} ConstructorElValues
7065
7144
  */
7066
7145
 
7067
7146
  /**
@@ -7182,40 +7261,114 @@ class TinyHtml_TinyHtml {
7182
7261
 
7183
7262
  static Utils = { ...collision_namespaceObject };
7184
7263
 
7264
+ /**
7265
+ * Creates a new DOM element with the specified tag name and options, then wraps it in a TinyHtml instance.
7266
+ *
7267
+ * @param {string} tagName - The tag name of the element to create (e.g., 'div', 'span').
7268
+ * @param {ElementCreationOptions} ops - Optional settings for creating the element.
7269
+ * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM element.
7270
+ * @throws {TypeError} If tagName is not a string or ops is not an object.
7271
+ */
7272
+ static createElement(tagName, ops) {
7273
+ if (typeof tagName !== 'string')
7274
+ throw new TypeError(`[TinyHtml] createElement(): The tagName must be a string.`);
7275
+ if (typeof ops !== 'undefined' && typeof ops !== 'object')
7276
+ throw new TypeError(`[TinyHtml] createElement(): The ops must be a object.`);
7277
+ return new TinyHtml_TinyHtml(document.createElement(tagName, ops));
7278
+ }
7279
+
7280
+ /**
7281
+ * Creates a new TinyHtml instance that wraps a DOM TextNode.
7282
+ *
7283
+ * This method is useful when you want to insert raw text content into the DOM
7284
+ * without it being interpreted as HTML. The returned instance behaves like any
7285
+ * other TinyHtml element and can be appended or manipulated as needed.
7286
+ *
7287
+ * @param {string} value - The plain text content to be wrapped in a TextNode.
7288
+ * @returns {TinyHtml} A TinyHtml instance wrapping the newly created DOM TextNode.
7289
+ * @throws {TypeError} If the provided value is not a string.
7290
+ */
7291
+ static createTextNode(value) {
7292
+ if (typeof value !== 'string')
7293
+ throw new TypeError(`[TinyHtml] createTextNode(): The value must be a string.`);
7294
+ return new TinyHtml_TinyHtml(document.createTextNode(value));
7295
+ }
7296
+
7297
+ /**
7298
+ * Creates an HTMLElement or TextNode from an HTML string.
7299
+ * Supports both elements and plain text.
7300
+ *
7301
+ * @param {string} htmlString - The HTML string to convert.
7302
+ * @returns {TinyHtml} - A single HTMLElement or TextNode.
7303
+ */
7304
+ static createElementFromHTML(htmlString) {
7305
+ const template = document.createElement('template');
7306
+ htmlString = htmlString.trim();
7307
+
7308
+ // If it's only text (e.g., no "<" tag), return a TextNode
7309
+ if (!htmlString.startsWith('<')) {
7310
+ return TinyHtml_TinyHtml.createTextNode(htmlString);
7311
+ }
7312
+
7313
+ template.innerHTML = htmlString;
7314
+ if (!(template.content.firstChild instanceof Element)) throw new Error('');
7315
+ return new TinyHtml_TinyHtml(template.content.firstChild);
7316
+ }
7317
+
7185
7318
  /**
7186
7319
  * Queries the document for the first element matching the CSS selector and wraps it in a TinyHtml instance.
7187
7320
  *
7188
7321
  * @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.
7322
+ * @param {Document|Element} elem - Target element.
7323
+ * @returns {TinyHtml|null} A TinyHtml instance wrapping the matched element.
7191
7324
  */
7192
- static query(selector) {
7193
- const newEl = document.querySelector(selector);
7194
- if (!newEl) throw new Error(`[TinyHtml] query(): No element found for selector "${selector}".`);
7325
+ static query(selector, elem = document) {
7326
+ const newEl = elem.querySelector(selector);
7327
+ if (!newEl) return null;
7195
7328
  return new TinyHtml_TinyHtml(newEl);
7196
7329
  }
7197
7330
 
7331
+ /**
7332
+ * Queries the element for the first element matching the CSS selector and wraps it in a TinyHtml instance.
7333
+ *
7334
+ * @param {string} selector - A valid CSS selector string.
7335
+ * @returns {TinyHtml|null} A TinyHtml instance wrapping the matched element.
7336
+ */
7337
+ querySelector(selector) {
7338
+ return TinyHtml_TinyHtml.query(selector, TinyHtml_TinyHtml._preElem(this, 'query'));
7339
+ }
7340
+
7198
7341
  /**
7199
7342
  * Queries the document for all elements matching the CSS selector and wraps them in TinyHtml instances.
7200
7343
  *
7201
7344
  * @param {string} selector - A valid CSS selector string.
7345
+ * @param {Document|Element} elem - Target element.
7202
7346
  * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
7203
7347
  */
7204
- static queryAll(selector) {
7205
- const newEls = document.querySelectorAll(selector);
7348
+ static queryAll(selector, elem = document) {
7349
+ const newEls = elem.querySelectorAll(selector);
7206
7350
  return TinyHtml_TinyHtml.toTinyElm([...newEls]);
7207
7351
  }
7208
7352
 
7353
+ /**
7354
+ * Queries the element for all elements matching the CSS selector and wraps them in TinyHtml instances.
7355
+ *
7356
+ * @param {string} selector - A valid CSS selector string.
7357
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
7358
+ */
7359
+ querySelectorAll(selector) {
7360
+ return TinyHtml_TinyHtml.queryAll(selector, TinyHtml_TinyHtml._preElem(this, 'queryAll'));
7361
+ }
7362
+
7209
7363
  /**
7210
7364
  * Retrieves an element by its ID and wraps it in a TinyHtml instance.
7211
7365
  *
7212
7366
  * @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.
7367
+ * @returns {TinyHtml|null} A TinyHtml instance wrapping the found element.
7215
7368
  */
7216
7369
  static getById(selector) {
7217
7370
  const newEl = document.getElementById(selector);
7218
- if (!newEl) throw new Error(`[TinyHtml] getById(): No element found with ID "${selector}".`);
7371
+ if (!newEl) return null;
7219
7372
  return new TinyHtml_TinyHtml(newEl);
7220
7373
  }
7221
7374
 
@@ -7223,13 +7376,24 @@ class TinyHtml_TinyHtml {
7223
7376
  * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
7224
7377
  *
7225
7378
  * @param {string} selector - The class name to search for.
7379
+ * @param {Document|Element} elem - Target element.
7226
7380
  * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7227
7381
  */
7228
- static getByClassName(selector) {
7229
- const newEls = document.getElementsByClassName(selector);
7382
+ static getByClassName(selector, elem = document) {
7383
+ const newEls = elem.getElementsByClassName(selector);
7230
7384
  return TinyHtml_TinyHtml.toTinyElm([...newEls]);
7231
7385
  }
7232
7386
 
7387
+ /**
7388
+ * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
7389
+ *
7390
+ * @param {string} selector - The class name to search for.
7391
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7392
+ */
7393
+ getElementsByClassName(selector) {
7394
+ return TinyHtml_TinyHtml.getByClassName(selector, TinyHtml_TinyHtml._preElem(this, 'getByClassName'));
7395
+ }
7396
+
7233
7397
  /**
7234
7398
  * Retrieves all elements with the specified name attribute and wraps them in TinyHtml instances.
7235
7399
  *
@@ -7247,13 +7411,30 @@ class TinyHtml_TinyHtml {
7247
7411
  *
7248
7412
  * @param {string} localName - The local name (tag) of the elements to search for.
7249
7413
  * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
7414
+ * @param {Document|Element} elem - Target element.
7250
7415
  * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7251
7416
  */
7252
- static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
7253
- const newEls = document.getElementsByTagNameNS(namespaceURI, localName);
7417
+ static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml', elem = document) {
7418
+ const newEls = elem.getElementsByTagNameNS(namespaceURI, localName);
7254
7419
  return TinyHtml_TinyHtml.toTinyElm([...newEls]);
7255
7420
  }
7256
7421
 
7422
+ /**
7423
+ * Retrieves all elements with the specified local tag name within the given namespace URI,
7424
+ * and wraps them in TinyHtml instances.
7425
+ *
7426
+ * @param {string} localName - The local name (tag) of the elements to search for.
7427
+ * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
7428
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
7429
+ */
7430
+ getElementsByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
7431
+ return TinyHtml_TinyHtml.getByTagNameNS(
7432
+ localName,
7433
+ namespaceURI,
7434
+ TinyHtml_TinyHtml._preElem(this, 'getByTagNameNS'),
7435
+ );
7436
+ }
7437
+
7257
7438
  //////////////////////////////////////////////////////////////////
7258
7439
 
7259
7440
  /**
@@ -7534,6 +7715,45 @@ class TinyHtml_TinyHtml {
7534
7715
  return TinyHtml_TinyHtml._preElemTemplate(elems, where, [Element, Window], ['Element', 'Window']);
7535
7716
  }
7536
7717
 
7718
+ /**
7719
+ * Ensures the input is returned as an array.
7720
+ * Useful to normalize operations across multiple or single element/window/document elements.
7721
+ *
7722
+ * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/window element or array of html elements.
7723
+ * @param {string} where - The method or context name where validation is being called.
7724
+ * @returns {ElementAndWindow[]} - Always returns an array of element/window elements.
7725
+ * @readonly
7726
+ */
7727
+ static _preElemsAndWinAndDoc(elems, where) {
7728
+ const result = TinyHtml_TinyHtml._preElemsTemplate(
7729
+ elems,
7730
+ where,
7731
+ [Element, Window, Document],
7732
+ ['Element', 'Window', 'Document'],
7733
+ );
7734
+ return result.map((elem) => (!(elem instanceof Document) ? elem : elem.documentElement));
7735
+ }
7736
+
7737
+ /**
7738
+ * Ensures the input is returned as an single element/window/document element.
7739
+ * Useful to normalize operations across multiple or single element/window/document elements.
7740
+ *
7741
+ * @param {TinyElementAndWinAndDoc|TinyElementAndWinAndDoc[]} elems - A single element/window element or array of html elements.
7742
+ * @param {string} where - The method or context name where validation is being called.
7743
+ * @returns {ElementAndWindow} - Always returns an single element/window element.
7744
+ * @readonly
7745
+ */
7746
+ static _preElemAndWinAndDoc(elems, where) {
7747
+ const result = TinyHtml_TinyHtml._preElemTemplate(
7748
+ elems,
7749
+ where,
7750
+ [Element, Window, Document],
7751
+ ['Element', 'Window', 'Document'],
7752
+ );
7753
+ if (result instanceof Document) return result.documentElement;
7754
+ return result;
7755
+ }
7756
+
7537
7757
  /**
7538
7758
  * Normalizes and converts one or more DOM elements (or TinyHtml instances)
7539
7759
  * into an array of `TinyHtml` instances.
@@ -7880,12 +8100,13 @@ class TinyHtml_TinyHtml {
7880
8100
  * @param {string} key - The key under which the data will be stored.
7881
8101
  * @param {any} value - The value to store.
7882
8102
  * @param {boolean} [isPrivate=false] - Whether to store the data in the private store.
7883
- * @returns {void}
8103
+ * @returns {TinyElement}
7884
8104
  */
7885
8105
  static setData(el, key, value, isPrivate = false) {
7886
8106
  const data = TinyHtml_TinyHtml._dataSelector[!isPrivate ? 'public' : 'private']('setData', el);
7887
8107
  if (typeof key !== 'string') throw new TypeError('The key must be a string.');
7888
8108
  data[key] = value;
8109
+ return el;
7889
8110
  }
7890
8111
 
7891
8112
  /**
@@ -7894,7 +8115,7 @@ class TinyHtml_TinyHtml {
7894
8115
  * @param {string} key - The key under which the data will be stored.
7895
8116
  * @param {any} value - The value to store.
7896
8117
  * @param {boolean} [isPrivate=false] - Whether to store the data in the private store.
7897
- * @returns {void}
8118
+ * @returns {TinyElement}
7898
8119
  */
7899
8120
  setData(key, value, isPrivate = false) {
7900
8121
  return TinyHtml_TinyHtml.setData(this, key, value, isPrivate);
@@ -8241,18 +8462,21 @@ class TinyHtml_TinyHtml {
8241
8462
  /**
8242
8463
  * Appends child elements or strings to the end of the target element(s).
8243
8464
  *
8244
- * @param {TinyElement | TinyElement[]} el - The target element(s) to receive children.
8465
+ * @param {TinyElement} el - The target element(s) to receive children.
8245
8466
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to append.
8467
+ * @returns {TinyElement}
8246
8468
  */
8247
8469
  static append(el, ...children) {
8248
8470
  const elem = TinyHtml_TinyHtml._preElem(el, 'append');
8249
8471
  elem.append(...TinyHtml_TinyHtml._appendChecker('append', ...children));
8472
+ return el;
8250
8473
  }
8251
8474
 
8252
8475
  /**
8253
8476
  * Appends child elements or strings to the end of the target element(s).
8254
8477
  *
8255
8478
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to append.
8479
+ * @returns {TinyElement}
8256
8480
  */
8257
8481
  append(...children) {
8258
8482
  return TinyHtml_TinyHtml.append(this, ...children);
@@ -8261,18 +8485,21 @@ class TinyHtml_TinyHtml {
8261
8485
  /**
8262
8486
  * Prepends child elements or strings to the beginning of the target element(s).
8263
8487
  *
8264
- * @param {TinyElement | TinyElement[]} el - The target element(s) to receive children.
8488
+ * @param {TinyElement} el - The target element(s) to receive children.
8265
8489
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to prepend.
8490
+ * @returns {TinyElement}
8266
8491
  */
8267
8492
  static prepend(el, ...children) {
8268
8493
  const elem = TinyHtml_TinyHtml._preElem(el, 'prepend');
8269
8494
  elem.prepend(...TinyHtml_TinyHtml._appendChecker('prepend', ...children));
8495
+ return el;
8270
8496
  }
8271
8497
 
8272
8498
  /**
8273
8499
  * Prepends child elements or strings to the beginning of the target element(s).
8274
8500
  *
8275
8501
  * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to prepend.
8502
+ * @returns {TinyElement}
8276
8503
  */
8277
8504
  prepend(...children) {
8278
8505
  return TinyHtml_TinyHtml.prepend(this, ...children);
@@ -8281,18 +8508,21 @@ class TinyHtml_TinyHtml {
8281
8508
  /**
8282
8509
  * Inserts elements or strings immediately before the target element(s) in the DOM.
8283
8510
  *
8284
- * @param {TinyElement | TinyElement[]} el - The target element(s) before which new content is inserted.
8511
+ * @param {TinyElement} el - The target element(s) before which new content is inserted.
8285
8512
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert before the target.
8513
+ * @returns {TinyElement}
8286
8514
  */
8287
8515
  static before(el, ...children) {
8288
8516
  const elem = TinyHtml_TinyHtml._preElem(el, 'before');
8289
8517
  elem.before(...TinyHtml_TinyHtml._appendChecker('before', ...children));
8518
+ return el;
8290
8519
  }
8291
8520
 
8292
8521
  /**
8293
8522
  * Inserts elements or strings immediately before the target element(s) in the DOM.
8294
8523
  *
8295
8524
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert before the target.
8525
+ * @returns {TinyElement}
8296
8526
  */
8297
8527
  before(...children) {
8298
8528
  return TinyHtml_TinyHtml.before(this, ...children);
@@ -8301,18 +8531,21 @@ class TinyHtml_TinyHtml {
8301
8531
  /**
8302
8532
  * Inserts elements or strings immediately after the target element(s) in the DOM.
8303
8533
  *
8304
- * @param {TinyElement | TinyElement[]} el - The target element(s) after which new content is inserted.
8534
+ * @param {TinyElement} el - The target element(s) after which new content is inserted.
8305
8535
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert after the target.
8536
+ * @returns {TinyElement}
8306
8537
  */
8307
8538
  static after(el, ...children) {
8308
8539
  const elem = TinyHtml_TinyHtml._preElem(el, 'after');
8309
8540
  elem.after(...TinyHtml_TinyHtml._appendChecker('after', ...children));
8541
+ return el;
8310
8542
  }
8311
8543
 
8312
8544
  /**
8313
8545
  * Inserts elements or strings immediately after the target element(s) in the DOM.
8314
8546
  *
8315
8547
  * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert after the target.
8548
+ * @returns {TinyElement}
8316
8549
  */
8317
8550
  after(...children) {
8318
8551
  return TinyHtml_TinyHtml.after(this, ...children);
@@ -8321,18 +8554,21 @@ class TinyHtml_TinyHtml {
8321
8554
  /**
8322
8555
  * Replaces the target element(s) in the DOM with new elements or text.
8323
8556
  *
8324
- * @param {TinyElement | TinyElement[]} el - The element(s) to be replaced.
8557
+ * @param {TinyElement} el - The element(s) to be replaced.
8325
8558
  * @param {...(TinyNode | TinyNode[] | string)} newNodes - New elements or text to replace the target.
8559
+ * @returns {TinyElement}
8326
8560
  */
8327
8561
  static replaceWith(el, ...newNodes) {
8328
8562
  const elem = TinyHtml_TinyHtml._preElem(el, 'replaceWith');
8329
8563
  elem.replaceWith(...TinyHtml_TinyHtml._appendChecker('replaceWith', ...newNodes));
8564
+ return el;
8330
8565
  }
8331
8566
 
8332
8567
  /**
8333
8568
  * Replaces the target element(s) in the DOM with new elements or text.
8334
8569
  *
8335
8570
  * @param {...(TinyNode | TinyNode[] | string)} newNodes - New elements or text to replace the target.
8571
+ * @returns {TinyElement}
8336
8572
  */
8337
8573
  replaceWith(...newNodes) {
8338
8574
  return TinyHtml_TinyHtml.replaceWith(this, ...newNodes);
@@ -8343,6 +8579,7 @@ class TinyHtml_TinyHtml {
8343
8579
  *
8344
8580
  * @param {TinyNode | TinyNode[]} el - The element(s) to append.
8345
8581
  * @param {TinyNode | TinyNode[]} targets - Target element(s) where content will be appended.
8582
+ * @returns {TinyNode|TinyNode[]}
8346
8583
  */
8347
8584
  static appendTo(el, targets) {
8348
8585
  const elems = TinyHtml_TinyHtml._preNodeElems(el, 'appendTo');
@@ -8352,12 +8589,14 @@ class TinyHtml_TinyHtml {
8352
8589
  target.appendChild(i === tars.length - 1 ? elem : elem.cloneNode(true)),
8353
8590
  );
8354
8591
  });
8592
+ return el;
8355
8593
  }
8356
8594
 
8357
8595
  /**
8358
8596
  * Appends the given element(s) to each target element in sequence.
8359
8597
  *
8360
8598
  * @param {TinyNode | TinyNode[]} targets - Target element(s) where content will be appended.
8599
+ * @returns {TinyNode|TinyNode[]}
8361
8600
  */
8362
8601
  appendTo(targets) {
8363
8602
  return TinyHtml_TinyHtml.appendTo(this, targets);
@@ -8368,6 +8607,7 @@ class TinyHtml_TinyHtml {
8368
8607
  *
8369
8608
  * @param {TinyElement | TinyElement[]} el - The element(s) to prepend.
8370
8609
  * @param {TinyElement | TinyElement[]} targets - Target element(s) where content will be prepended.
8610
+ * @returns {TinyElement|TinyElement[]}
8371
8611
  */
8372
8612
  static prependTo(el, targets) {
8373
8613
  const elems = TinyHtml_TinyHtml._preElems(el, 'prependTo');
@@ -8378,12 +8618,14 @@ class TinyHtml_TinyHtml {
8378
8618
  .reverse()
8379
8619
  .forEach((elem) => target.prepend(i === tars.length - 1 ? elem : elem.cloneNode(true)));
8380
8620
  });
8621
+ return el;
8381
8622
  }
8382
8623
 
8383
8624
  /**
8384
8625
  * Prepends the given element(s) to each target element in sequence.
8385
8626
  *
8386
8627
  * @param {TinyElement | TinyElement[]} targets - Target element(s) where content will be prepended.
8628
+ * @returns {TinyElement|TinyElement[]}
8387
8629
  */
8388
8630
  prependTo(targets) {
8389
8631
  return TinyHtml_TinyHtml.prependTo(this, targets);
@@ -8395,6 +8637,7 @@ class TinyHtml_TinyHtml {
8395
8637
  * @param {TinyNode | TinyNode[]} el - The element(s) to insert.
8396
8638
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8397
8639
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert before, defaults to target.
8640
+ * @returns {TinyNode|TinyNode[]}
8398
8641
  */
8399
8642
  static insertBefore(el, target, child = null) {
8400
8643
  const elem = TinyHtml_TinyHtml._preNodeElem(el, 'insertBefore');
@@ -8402,6 +8645,7 @@ class TinyHtml_TinyHtml {
8402
8645
  const childNode = TinyHtml_TinyHtml._preNodeElemWithNull(child, 'insertBefore');
8403
8646
  if (!targ.parentNode) throw new Error('');
8404
8647
  targ.parentNode.insertBefore(elem, childNode || targ);
8648
+ return el;
8405
8649
  }
8406
8650
 
8407
8651
  /**
@@ -8409,6 +8653,7 @@ class TinyHtml_TinyHtml {
8409
8653
  *
8410
8654
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8411
8655
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert before, defaults to target.
8656
+ * @returns {TinyNode|TinyNode[]}
8412
8657
  */
8413
8658
  insertBefore(target, child) {
8414
8659
  return TinyHtml_TinyHtml.insertBefore(this, target, child);
@@ -8420,6 +8665,7 @@ class TinyHtml_TinyHtml {
8420
8665
  * @param {TinyNode | TinyNode[]} el - The element(s) to insert.
8421
8666
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8422
8667
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert after, defaults to target.
8668
+ * @returns {TinyNode|TinyNode[]}
8423
8669
  */
8424
8670
  static insertAfter(el, target, child = null) {
8425
8671
  const elem = TinyHtml_TinyHtml._preNodeElem(el, 'insertAfter');
@@ -8427,6 +8673,7 @@ class TinyHtml_TinyHtml {
8427
8673
  const childNode = TinyHtml_TinyHtml._preNodeElemWithNull(child, 'insertBefore');
8428
8674
  if (!targ.parentNode) throw new Error('');
8429
8675
  targ.parentNode.insertBefore(elem, childNode || targ.nextSibling);
8676
+ return el;
8430
8677
  }
8431
8678
 
8432
8679
  /**
@@ -8434,6 +8681,7 @@ class TinyHtml_TinyHtml {
8434
8681
  *
8435
8682
  * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
8436
8683
  * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert after, defaults to target.
8684
+ * @returns {TinyNode|TinyNode[]}
8437
8685
  */
8438
8686
  insertAfter(target, child) {
8439
8687
  return TinyHtml_TinyHtml.insertAfter(this, target, child);
@@ -8445,6 +8693,7 @@ class TinyHtml_TinyHtml {
8445
8693
  *
8446
8694
  * @param {TinyNode | TinyNode[]} el - The new element(s) to insert.
8447
8695
  * @param {TinyNode | TinyNode[]} targets - The elements to be replaced.
8696
+ * @returns {TinyNode|TinyNode[]}
8448
8697
  */
8449
8698
  static replaceAll(el, targets) {
8450
8699
  const elems = TinyHtml_TinyHtml._preNodeElems(el, 'replaceAll');
@@ -8456,6 +8705,7 @@ class TinyHtml_TinyHtml {
8456
8705
  parent.replaceChild(i === tars.length - 1 ? elem : elem.cloneNode(true), target);
8457
8706
  });
8458
8707
  });
8708
+ return el;
8459
8709
  }
8460
8710
 
8461
8711
  /**
@@ -8463,6 +8713,7 @@ class TinyHtml_TinyHtml {
8463
8713
  * If multiple targets exist, the inserted elements are cloned accordingly.
8464
8714
  *
8465
8715
  * @param {TinyNode | TinyNode[]} targets - The elements to be replaced.
8716
+ * @returns {TinyNode|TinyNode[]}
8466
8717
  */
8467
8718
  replaceAll(targets) {
8468
8719
  return TinyHtml_TinyHtml.replaceAll(this, targets);
@@ -8486,7 +8737,12 @@ class TinyHtml_TinyHtml {
8486
8737
  throw new Error(
8487
8738
  `[TinyHtml] You are trying to put a TinyHtml inside another TinyHtml in constructor.`,
8488
8739
  );
8489
- if (!(el instanceof Element) && !(el instanceof Window) && !(el instanceof Document))
8740
+ if (
8741
+ !(el instanceof Element) &&
8742
+ !(el instanceof Window) &&
8743
+ !(el instanceof Document) &&
8744
+ !(el instanceof Text)
8745
+ )
8490
8746
  throw new Error(`[TinyHtml] Invalid Target in constructor.`);
8491
8747
  this.#el = el;
8492
8748
  }
@@ -8936,6 +9192,7 @@ class TinyHtml_TinyHtml {
8936
9192
  * @param {TinyHtmlElement|TinyHtmlElement[]} el - The element to inspect.
8937
9193
  * @param {string|Object} prop - The property name or an object with key-value pairs
8938
9194
  * @param {string|null} [value=null] - The value to set (if `prop` is a string)
9195
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
8939
9196
  */
8940
9197
  static setStyle(el, prop, value = null) {
8941
9198
  TinyHtml_TinyHtml._preHtmlElems(el, 'setStyle').forEach((elem) => {
@@ -8948,6 +9205,7 @@ class TinyHtml_TinyHtml {
8948
9205
  }
8949
9206
  } else elem.style.setProperty(TinyHtml_TinyHtml.toStyleKc(prop), value);
8950
9207
  });
9208
+ return el;
8951
9209
  }
8952
9210
 
8953
9211
  /**
@@ -8958,6 +9216,7 @@ class TinyHtml_TinyHtml {
8958
9216
  *
8959
9217
  * @param {string|Object} prop - The property name or an object with key-value pairs
8960
9218
  * @param {string|null} [value=null] - The value to set (if `prop` is a string)
9219
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
8961
9220
  */
8962
9221
  setStyle(prop, value) {
8963
9222
  return TinyHtml_TinyHtml.setStyle(this, prop, value);
@@ -9053,6 +9312,7 @@ class TinyHtml_TinyHtml {
9053
9312
  *
9054
9313
  * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element or an array of elements.
9055
9314
  * @param {string|string[]} prop - A property name or an array of property names to remove.
9315
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9056
9316
  */
9057
9317
  static removeStyle(el, prop) {
9058
9318
  TinyHtml_TinyHtml._preHtmlElems(el, 'removeStyle').forEach((elem) => {
@@ -9062,12 +9322,14 @@ class TinyHtml_TinyHtml {
9062
9322
  }
9063
9323
  } else elem.style.removeProperty(TinyHtml_TinyHtml.toStyleKc(prop));
9064
9324
  });
9325
+ return el;
9065
9326
  }
9066
9327
 
9067
9328
  /**
9068
9329
  * Removes one or more inline CSS properties from the given element(s).
9069
9330
  *
9070
9331
  * @param {string|string[]} prop - A property name or an array of property names to remove.
9332
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9071
9333
  */
9072
9334
  removeStyle(prop) {
9073
9335
  return TinyHtml_TinyHtml.removeStyle(this, prop);
@@ -9082,6 +9344,7 @@ class TinyHtml_TinyHtml {
9082
9344
  * @param {string} prop - The CSS property to toggle.
9083
9345
  * @param {string} val1 - The first value (used as "current" check).
9084
9346
  * @param {string} val2 - The second value (used as the "alternative").
9347
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9085
9348
  */
9086
9349
  static toggleStyle(el, prop, val1, val2) {
9087
9350
  TinyHtml_TinyHtml._preHtmlElems(el, 'toggleStyle').forEach((elem) => {
@@ -9089,6 +9352,7 @@ class TinyHtml_TinyHtml {
9089
9352
  const newVal = current === TinyHtml_TinyHtml.toStyleKc(val1) ? val2 : val1;
9090
9353
  TinyHtml_TinyHtml.setStyle(elem, prop, newVal);
9091
9354
  });
9355
+ return el;
9092
9356
  }
9093
9357
 
9094
9358
  /**
@@ -9099,6 +9363,7 @@ class TinyHtml_TinyHtml {
9099
9363
  * @param {string} prop - The CSS property to toggle.
9100
9364
  * @param {string} val1 - The first value (used as "current" check).
9101
9365
  * @param {string} val2 - The second value (used as the "alternative").
9366
+ * @returns {TinyHtmlElement|TinyHtmlElement[]}
9102
9367
  */
9103
9368
  toggleStyle(prop, val1, val2) {
9104
9369
  return TinyHtml_TinyHtml.toggleStyle(this, prop, val1, val2);
@@ -9108,14 +9373,16 @@ class TinyHtml_TinyHtml {
9108
9373
  * Removes all inline styles (`style` attribute) from the given element(s).
9109
9374
  *
9110
9375
  * @param {TinyElement|TinyElement[]} el - A single element or an array of elements.
9376
+ * @returns {TinyElement|TinyElement[]}
9111
9377
  */
9112
9378
  static clearStyle(el) {
9113
9379
  TinyHtml_TinyHtml._preElems(el, 'clearStyle').forEach((elem) => elem.removeAttribute('style'));
9380
+ return el;
9114
9381
  }
9115
9382
 
9116
9383
  /**
9117
9384
  * Removes all inline styles (`style` attribute) from the given element(s).
9118
- *
9385
+ * @returns {TinyElement|TinyElement[]}
9119
9386
  */
9120
9387
  clearStyle() {
9121
9388
  return TinyHtml_TinyHtml.clearStyle(this);
@@ -9127,14 +9394,17 @@ class TinyHtml_TinyHtml {
9127
9394
  * Focus the element.
9128
9395
  *
9129
9396
  * @param {TinyHtmlElement} el - The element or a selector string.
9397
+ * @returns {TinyHtmlElement}
9130
9398
  */
9131
9399
  static focus(el) {
9132
9400
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'focus');
9133
9401
  elem.focus();
9402
+ return el;
9134
9403
  }
9135
9404
 
9136
9405
  /**
9137
9406
  * Focus the element.
9407
+ * @returns {TinyHtmlElement}
9138
9408
  */
9139
9409
  focus() {
9140
9410
  return TinyHtml_TinyHtml.focus(this);
@@ -9144,19 +9414,46 @@ class TinyHtml_TinyHtml {
9144
9414
  * Blur the element.
9145
9415
  *
9146
9416
  * @param {TinyHtmlElement} el - The element or a selector string.
9417
+ * @returns {TinyHtmlElement}
9147
9418
  */
9148
9419
  static blur(el) {
9149
9420
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'blur');
9150
9421
  elem.blur();
9422
+ return el;
9151
9423
  }
9152
9424
 
9153
9425
  /**
9154
9426
  * Blur the element.
9427
+ * @returns {TinyHtmlElement}
9155
9428
  */
9156
9429
  blur() {
9157
9430
  return TinyHtml_TinyHtml.blur(this);
9158
9431
  }
9159
9432
 
9433
+ /**
9434
+ * Interprets a value as a boolean `true` if it matches a common truthy representation.
9435
+ *
9436
+ * This method checks if the input is any of the common forms used to represent `true`,
9437
+ * such as the string `'true'`, `'1'`, `'on'`, the boolean `true`, or the number `1`.
9438
+ *
9439
+ * @param {string|boolean|number} [value] - The value to interpret as boolean.
9440
+ * @returns {boolean} `true` if the value represents a truthy state, otherwise `false`.
9441
+ */
9442
+ static boolCheck(value) {
9443
+ if (
9444
+ typeof value !== 'undefined' &&
9445
+ (value === 'true' ||
9446
+ value === '1' ||
9447
+ value === true ||
9448
+ value === 'on' ||
9449
+ (typeof value === 'number' && value > 0))
9450
+ ) {
9451
+ return true;
9452
+ } else {
9453
+ return false;
9454
+ }
9455
+ }
9456
+
9160
9457
  //////////////////////////////////////////////////////////////////////
9161
9458
 
9162
9459
  /**
@@ -9165,7 +9462,7 @@ class TinyHtml_TinyHtml {
9165
9462
  */
9166
9463
  static setWinScrollTop(value) {
9167
9464
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
9168
- window.scrollTo({ top: value });
9465
+ TinyHtml_TinyHtml.setScrollTop(window, value);
9169
9466
  }
9170
9467
 
9171
9468
  /**
@@ -9174,7 +9471,7 @@ class TinyHtml_TinyHtml {
9174
9471
  */
9175
9472
  static setWinScrollLeft(value) {
9176
9473
  if (typeof value !== 'number') throw new TypeError('The value must be a number.');
9177
- window.scrollTo({ left: value });
9474
+ TinyHtml_TinyHtml.setScrollLeft(window, value);
9178
9475
  }
9179
9476
 
9180
9477
  /**
@@ -9209,16 +9506,40 @@ class TinyHtml_TinyHtml {
9209
9506
  return window.innerWidth || document.documentElement.clientWidth;
9210
9507
  }
9211
9508
 
9509
+ /**
9510
+ * Checks if the page is currently scrolled to the very top.
9511
+ *
9512
+ * This method compares the current vertical scroll position with the total document height.
9513
+ * It's useful for detecting if the user has reached the top of the page.
9514
+ *
9515
+ * @returns {boolean} `true` if the page is scrolled to the top, otherwise `false`.
9516
+ */
9517
+ static isPageTop() {
9518
+ return window.scrollY === 0;
9519
+ }
9520
+
9521
+ /**
9522
+ * Checks if the page is currently scrolled to the very bottom.
9523
+ *
9524
+ * This method uses the `scrollY` and `innerHeight` properties to determine if the
9525
+ * user has reached the end of the document.
9526
+ *
9527
+ * @returns {boolean} `true` if the page is scrolled to the bottom, otherwise `false`.
9528
+ */
9529
+ static isPageBottom() {
9530
+ return window.innerHeight + window.scrollY >= document.body.offsetHeight;
9531
+ }
9532
+
9212
9533
  /**
9213
9534
  * Gets the width or height of an element based on the box model.
9214
- * @param {TinyElementAndWindow} el - The element or window.
9535
+ * @param {TinyElementAndWinAndDoc} el - The element or window.
9215
9536
  * @param {"width"|"height"} type - Dimension type.
9216
9537
  * @param {"content"|"padding"|"border"|"margin"} [extra='content'] - Box model context.
9217
9538
  * @returns {number} - Computed dimension.
9218
9539
  * @throws {TypeError} If `type` or `extra` is not a string.
9219
9540
  */
9220
9541
  static getDimension(el, type, extra = 'content') {
9221
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'getDimension');
9542
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'getDimension');
9222
9543
  if (typeof type !== 'string') throw new TypeError('The type must be a string.');
9223
9544
  if (typeof extra !== 'string') throw new TypeError('The extra must be a string.');
9224
9545
 
@@ -9306,17 +9627,20 @@ class TinyHtml_TinyHtml {
9306
9627
  * @param {TinyHtmlElement} el - Target element.
9307
9628
  * @param {string|number} value - Height value.
9308
9629
  * @throws {TypeError} If `value` is neither a string nor number.
9630
+ * @returns {TinyHtmlElement}
9309
9631
  */
9310
9632
  static setHeight(el, value) {
9311
9633
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'setHeight');
9312
9634
  if (typeof value !== 'number' && typeof value !== 'string')
9313
9635
  throw new TypeError('The value must be a string or number.');
9314
9636
  elem.style.height = typeof value === 'number' ? `${value}px` : value;
9637
+ return el;
9315
9638
  }
9316
9639
 
9317
9640
  /**
9318
9641
  * Sets the height of the element.
9319
9642
  * @param {string|number} value - Height value.
9643
+ * @returns {TinyHtmlElement}
9320
9644
  */
9321
9645
  setHeight(value) {
9322
9646
  return TinyHtml_TinyHtml.setHeight(this, value);
@@ -9327,17 +9651,20 @@ class TinyHtml_TinyHtml {
9327
9651
  * @param {TinyHtmlElement} el - Target element.
9328
9652
  * @param {string|number} value - Width value.
9329
9653
  * @throws {TypeError} If `value` is neither a string nor number.
9654
+ * @returns {TinyHtmlElement}
9330
9655
  */
9331
9656
  static setWidth(el, value) {
9332
9657
  const elem = TinyHtml_TinyHtml._preHtmlElem(el, 'setWidth');
9333
9658
  if (typeof value !== 'number' && typeof value !== 'string')
9334
9659
  throw new TypeError('The value must be a string or number.');
9335
9660
  elem.style.width = typeof value === 'number' ? `${value}px` : value;
9661
+ return el;
9336
9662
  }
9337
9663
 
9338
9664
  /**
9339
9665
  * Sets the width of the element.
9340
9666
  * @param {string|number} value - Width value.
9667
+ * @returns {TinyHtmlElement}
9341
9668
  */
9342
9669
  setWidth(value) {
9343
9670
  return TinyHtml_TinyHtml.setWidth(this, value);
@@ -9345,11 +9672,11 @@ class TinyHtml_TinyHtml {
9345
9672
 
9346
9673
  /**
9347
9674
  * Returns content box height.
9348
- * @param {TinyElementAndWindow} el - Target element.
9675
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9349
9676
  * @returns {number}
9350
9677
  */
9351
9678
  static height(el) {
9352
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'height');
9679
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'height');
9353
9680
  return TinyHtml_TinyHtml.getDimension(elem, 'height', 'content');
9354
9681
  }
9355
9682
 
@@ -9363,11 +9690,11 @@ class TinyHtml_TinyHtml {
9363
9690
 
9364
9691
  /**
9365
9692
  * Returns content box width.
9366
- * @param {TinyElementAndWindow} el - Target element.
9693
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9367
9694
  * @returns {number}
9368
9695
  */
9369
9696
  static width(el) {
9370
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'width');
9697
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'width');
9371
9698
  return TinyHtml_TinyHtml.getDimension(elem, 'width', 'content');
9372
9699
  }
9373
9700
 
@@ -9381,11 +9708,11 @@ class TinyHtml_TinyHtml {
9381
9708
 
9382
9709
  /**
9383
9710
  * Returns padding box height.
9384
- * @param {TinyElementAndWindow} el - Target element.
9711
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9385
9712
  * @returns {number}
9386
9713
  */
9387
9714
  static innerHeight(el) {
9388
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'innerHeight');
9715
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'innerHeight');
9389
9716
  return TinyHtml_TinyHtml.getDimension(elem, 'height', 'padding');
9390
9717
  }
9391
9718
 
@@ -9399,11 +9726,11 @@ class TinyHtml_TinyHtml {
9399
9726
 
9400
9727
  /**
9401
9728
  * Returns padding box width.
9402
- * @param {TinyElementAndWindow} el - Target element.
9729
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9403
9730
  * @returns {number}
9404
9731
  */
9405
9732
  static innerWidth(el) {
9406
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'innerWidth');
9733
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'innerWidth');
9407
9734
  return TinyHtml_TinyHtml.getDimension(elem, 'width', 'padding');
9408
9735
  }
9409
9736
 
@@ -9417,14 +9744,14 @@ class TinyHtml_TinyHtml {
9417
9744
 
9418
9745
  /**
9419
9746
  * Returns outer height of the element, optionally including margin.
9420
- * @param {TinyElementAndWindow} el - Target element.
9747
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9421
9748
  * @param {boolean} [includeMargin=false] - Whether to include margin.
9422
9749
  * @returns {number}
9423
9750
  */
9424
9751
  static outerHeight(el, includeMargin = false) {
9425
9752
  if (typeof includeMargin !== 'boolean')
9426
9753
  throw new TypeError('The "includeMargin" must be a boolean.');
9427
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'outerHeight');
9754
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'outerHeight');
9428
9755
  return TinyHtml_TinyHtml.getDimension(elem, 'height', includeMargin ? 'margin' : 'border');
9429
9756
  }
9430
9757
 
@@ -9439,14 +9766,14 @@ class TinyHtml_TinyHtml {
9439
9766
 
9440
9767
  /**
9441
9768
  * Returns outer width of the element, optionally including margin.
9442
- * @param {TinyElementAndWindow} el - Target element.
9769
+ * @param {TinyElementAndWinAndDoc} el - Target element.
9443
9770
  * @param {boolean} [includeMargin=false] - Whether to include margin.
9444
9771
  * @returns {number}
9445
9772
  */
9446
9773
  static outerWidth(el, includeMargin = false) {
9447
9774
  if (typeof includeMargin !== 'boolean')
9448
9775
  throw new TypeError('The "includeMargin" must be a boolean.');
9449
- const elem = TinyHtml_TinyHtml._preElemAndWindow(el, 'outerWidth');
9776
+ const elem = TinyHtml_TinyHtml._preElemAndWinAndDoc(el, 'outerWidth');
9450
9777
  return TinyHtml_TinyHtml.getDimension(elem, 'width', includeMargin ? 'margin' : 'border');
9451
9778
  }
9452
9779
 
@@ -9461,6 +9788,30 @@ class TinyHtml_TinyHtml {
9461
9788
 
9462
9789
  //////////////////////////////////////////////////
9463
9790
 
9791
+ /**
9792
+ * Applies an animation to one or multiple TinyElement instances.
9793
+ *
9794
+ * @param {TinyElement|TinyElement[]} el - A single TinyElement or an array of TinyElements to animate.
9795
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
9796
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
9797
+ * @returns {TinyElement|TinyElement[]}
9798
+ */
9799
+ static animate(el, keyframes, ops) {
9800
+ TinyHtml_TinyHtml._preElems(el, 'animate').forEach((elem) => elem.animate(keyframes, ops));
9801
+ return el;
9802
+ }
9803
+
9804
+ /**
9805
+ * Applies an animation to one or multiple TinyElement instances.
9806
+ *
9807
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
9808
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
9809
+ * @returns {TinyElement|TinyElement[]}
9810
+ */
9811
+ animate(keyframes, ops) {
9812
+ return TinyHtml_TinyHtml.animate(this, keyframes, ops);
9813
+ }
9814
+
9464
9815
  /**
9465
9816
  * Gets the offset of the element relative to the document.
9466
9817
  * @param {TinyElement} el - Target element.
@@ -9615,28 +9966,152 @@ class TinyHtml_TinyHtml {
9615
9966
  return TinyHtml_TinyHtml.scrollLeft(this);
9616
9967
  }
9617
9968
 
9969
+ /**
9970
+ * Collection of easing functions used for scroll and animation calculations.
9971
+ * Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
9972
+ *
9973
+ * @type {Record<string, (t: number) => number>}
9974
+ */
9975
+ static easings = {
9976
+ linear: (t) => t,
9977
+ easeInQuad: (t) => t * t,
9978
+ easeOutQuad: (t) => t * (2 - t),
9979
+ easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
9980
+ easeInCubic: (t) => t * t * t,
9981
+ easeOutCubic: (t) => --t * t * t + 1,
9982
+ easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
9983
+ };
9984
+
9985
+ /**
9986
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
9987
+ * using a custom duration and easing function.
9988
+ *
9989
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
9990
+ *
9991
+ * @param {TinyElementAndWindow | TinyElementAndWindow[]} el - A single element, array of elements, or the window to scroll.
9992
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
9993
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
9994
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
9995
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
9996
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
9997
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
9998
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
9999
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
10000
+ * @throws {TypeError} If `el` is not a valid element, array, or window.
10001
+ * @throws {TypeError} If `targetX` or `targetY` is defined but not a number.
10002
+ * @throws {TypeError} If `duration` is defined but not a number.
10003
+ * @throws {TypeError} If `easing` is defined but not a valid easing function name.
10004
+ * @throws {TypeError} If `onAnimation` is defined but not a function.
10005
+ */
10006
+ static scrollToXY(el, { targetX, targetY, duration, easing, onAnimation } = {}) {
10007
+ if (targetX !== undefined && typeof targetX !== 'number')
10008
+ throw new TypeError('`targetX` must be a number if provided.');
10009
+ if (targetY !== undefined && typeof targetY !== 'number')
10010
+ throw new TypeError('`targetY` must be a number if provided.');
10011
+ if (duration !== undefined && typeof duration !== 'number')
10012
+ throw new TypeError('`duration` must be a number if provided.');
10013
+ if (easing !== undefined && typeof easing !== 'string')
10014
+ throw new TypeError('`easing` must be a string if provided.');
10015
+ if (easing !== undefined && typeof TinyHtml_TinyHtml.easings[easing] !== 'function')
10016
+ throw new TypeError(`Unknown easing function: "${easing}".`);
10017
+ if (onAnimation !== undefined && typeof onAnimation !== 'function')
10018
+ throw new TypeError('`onAnimation` must be a function if provided.');
10019
+
10020
+ /**
10021
+ * Performs an instant scroll to the given coordinates.
10022
+ *
10023
+ * @param {ElementAndWindow} elem - The element or window to scroll.
10024
+ * @param {number} newX - The final horizontal scroll position.
10025
+ * @param {number} newY - The final vertical scroll position.
10026
+ * @param {number} time - Normalized progress value.
10027
+ */
10028
+ const executeScroll = (elem, newX, newY, time) => {
10029
+ if (elem instanceof Window) {
10030
+ window.scrollTo(newX, newY);
10031
+ } else if (elem.nodeType === 9) {
10032
+ // @ts-ignore
10033
+ elem.defaultView.scrollTo(newX, newY);
10034
+ } else {
10035
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
10036
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
10037
+ if (startX !== newX) elem.scrollLeft = newX;
10038
+ if (startY !== newY) elem.scrollTop = newY;
10039
+ }
10040
+ if (typeof onAnimation === 'function')
10041
+ onAnimation({ x: newX, y: newY, isComplete: time >= 1, time });
10042
+ };
10043
+
10044
+ TinyHtml_TinyHtml._preElemsAndWindow(el, 'scrollToXY').forEach((elem) => {
10045
+ const startX = elem instanceof Window ? window.scrollX : elem.scrollLeft;
10046
+ const startY = elem instanceof Window ? window.scrollY : elem.scrollTop;
10047
+ const targX = targetX ?? startX;
10048
+ const targY = targetY ?? startY;
10049
+
10050
+ const changeX = targX - startX;
10051
+ const changeY = targY - startY;
10052
+
10053
+ const ease = (typeof easing === 'string' && TinyHtml_TinyHtml.easings[easing]) || null;
10054
+ if (typeof duration !== 'number' || typeof ease !== 'function')
10055
+ return executeScroll(elem, targX, targY, 1);
10056
+ const startTime = performance.now();
10057
+ const dur = duration ?? 0;
10058
+
10059
+ /**
10060
+ * Animates the scroll position based on easing and time.
10061
+ *
10062
+ * @param {number} currentTime - Timestamp provided by requestAnimationFrame.
10063
+ */
10064
+ function animateScroll(currentTime) {
10065
+ if (typeof ease !== 'function') return;
10066
+ const time = Math.min(1, (currentTime - startTime) / dur);
10067
+ const easedTime = ease(time);
10068
+
10069
+ const newX = startX + changeX * easedTime;
10070
+ const newY = startY + changeY * easedTime;
10071
+ executeScroll(elem, newX, newY, time);
10072
+
10073
+ if (time < 1) requestAnimationFrame(animateScroll);
10074
+ }
10075
+
10076
+ requestAnimationFrame(animateScroll);
10077
+ });
10078
+ return el;
10079
+ }
10080
+
10081
+ /**
10082
+ * Smoothly scrolls one or more elements (or the window) to the specified X and Y coordinates
10083
+ * using a custom duration and easing function.
10084
+ *
10085
+ * If `duration` or a valid `easing` is not provided, the scroll will be performed immediately.
10086
+ *
10087
+ * @param {Object} [settings={}] - Configuration object for the scroll animation.
10088
+ * @param {number} [settings.targetX] - The horizontal scroll target in pixels.
10089
+ * @param {number} [settings.targetY] - The vertical scroll target in pixels.
10090
+ * @param {number} [settings.duration] - The duration of the animation in milliseconds.
10091
+ * @param {Easings} [settings.easing] - The easing function name to use for the scroll animation.
10092
+ * @param {OnScrollAnimation} [settings.onAnimation] - Optional callback invoked on each animation
10093
+ * frame with the current scroll position, normalized animation time (`0` to `1`), and a completion flag.
10094
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
10095
+ */
10096
+ scrollToXY({ targetX, targetY, duration, easing, onAnimation } = {}) {
10097
+ return TinyHtml_TinyHtml.scrollToXY(this, { targetX, targetY, duration, easing, onAnimation });
10098
+ }
10099
+
9618
10100
  /**
9619
10101
  * Sets the vertical scroll position.
9620
10102
  * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
9621
10103
  * @param {number} value - Scroll top value.
10104
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9622
10105
  */
9623
10106
  static setScrollTop(el, value) {
9624
10107
  if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
9625
- TinyHtml_TinyHtml._preElemsAndWindow(el, 'setScrollTop').forEach((elem) => {
9626
- if (TinyHtml_TinyHtml.isWindow(elem)) {
9627
- elem.scrollTo(elem.pageXOffset, value);
9628
- } else if (elem.nodeType === 9) {
9629
- // @ts-ignore
9630
- elem.defaultView.scrollTo(elem.defaultView.pageXOffset, value);
9631
- } else {
9632
- elem.scrollTop = value;
9633
- }
9634
- });
10108
+ return TinyHtml_TinyHtml.scrollToXY(el, { targetY: value });
9635
10109
  }
9636
10110
 
9637
10111
  /**
9638
10112
  * Sets the vertical scroll position.
9639
10113
  * @param {number} value - Scroll top value.
10114
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9640
10115
  */
9641
10116
  setScrollTop(value) {
9642
10117
  return TinyHtml_TinyHtml.setScrollTop(this, value);
@@ -9646,24 +10121,17 @@ class TinyHtml_TinyHtml {
9646
10121
  * Sets the horizontal scroll position.
9647
10122
  * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
9648
10123
  * @param {number} value - Scroll left value.
10124
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9649
10125
  */
9650
10126
  static setScrollLeft(el, value) {
9651
10127
  if (typeof value !== 'number') throw new TypeError('ScrollLeft value must be a number.');
9652
- TinyHtml_TinyHtml._preElemsAndWindow(el, 'setScrollLeft').forEach((elem) => {
9653
- if (TinyHtml_TinyHtml.isWindow(elem)) {
9654
- elem.scrollTo(value, elem.pageYOffset);
9655
- } else if (elem.nodeType === 9) {
9656
- // @ts-ignore
9657
- elem.defaultView.scrollTo(value, elem.defaultView.pageYOffset);
9658
- } else {
9659
- elem.scrollLeft = value;
9660
- }
9661
- });
10128
+ return TinyHtml_TinyHtml.scrollToXY(el, { targetX: value });
9662
10129
  }
9663
10130
 
9664
10131
  /**
9665
10132
  * Sets the horizontal scroll position.
9666
10133
  * @param {number} value - Scroll left value.
10134
+ * @returns {TinyElementAndWindow|TinyElementAndWindow[]}
9667
10135
  */
9668
10136
  setScrollLeft(value) {
9669
10137
  return TinyHtml_TinyHtml.setScrollLeft(this, value);
@@ -9794,15 +10262,16 @@ class TinyHtml_TinyHtml {
9794
10262
 
9795
10263
  /**
9796
10264
  * 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.
10265
+ * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to add.
9798
10266
  */
9799
10267
  static addClass(el, ...args) {
9800
10268
  TinyHtml_TinyHtml._preElems(el, 'addClass').forEach((elem) => elem.classList.add(...args));
10269
+ return el;
9801
10270
  }
9802
10271
 
9803
10272
  /**
9804
10273
  * Adds one or more CSS class names to the element.
9805
- * @type {(...tokens: string[]) => void} - One or more class names to add.
10274
+ * @type {(...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to add.
9806
10275
  */
9807
10276
  addClass(...args) {
9808
10277
  return TinyHtml_TinyHtml.addClass(this, ...args);
@@ -9810,15 +10279,16 @@ class TinyHtml_TinyHtml {
9810
10279
 
9811
10280
  /**
9812
10281
  * 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.
10282
+ * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to remove.
9814
10283
  */
9815
10284
  static removeClass(el, ...args) {
9816
10285
  TinyHtml_TinyHtml._preElems(el, 'removeClass').forEach((elem) => elem.classList.remove(...args));
10286
+ return el;
9817
10287
  }
9818
10288
 
9819
10289
  /**
9820
10290
  * Removes one or more CSS class names from the element.
9821
- * @type {(...tokens: string[]) => void} - One or more class names to remove.
10291
+ * @type {(...tokens: string[]) => (TinyElement|TinyElement[])} - One or more class names to remove.
9822
10292
  */
9823
10293
  removeClass(...args) {
9824
10294
  return TinyHtml_TinyHtml.removeClass(this, ...args);
@@ -10028,15 +10498,18 @@ class TinyHtml_TinyHtml {
10028
10498
  * Set text content of elements.
10029
10499
  * @param {TinyElement|TinyElement[]} el
10030
10500
  * @param {string} value
10501
+ * @returns {TinyElement|TinyElement[]}
10031
10502
  */
10032
10503
  static setText(el, value) {
10033
10504
  if (typeof value !== 'string') throw new Error('Value is not a valid string.');
10034
10505
  TinyHtml_TinyHtml._preElems(el, 'setText').forEach((el) => (el.textContent = value));
10506
+ return el;
10035
10507
  }
10036
10508
 
10037
10509
  /**
10038
10510
  * Set text content of the element.
10039
10511
  * @param {string} value
10512
+ * @returns {TinyElement|TinyElement[]}
10040
10513
  */
10041
10514
  setText(value) {
10042
10515
  return TinyHtml_TinyHtml.setText(this, value);
@@ -10045,13 +10518,16 @@ class TinyHtml_TinyHtml {
10045
10518
  /**
10046
10519
  * Remove all child nodes from each element.
10047
10520
  * @param {TinyElement|TinyElement[]} el
10521
+ * @returns {TinyElement|TinyElement[]}
10048
10522
  */
10049
10523
  static empty(el) {
10050
10524
  TinyHtml_TinyHtml._preElems(el, 'empty').forEach((el) => (el.textContent = ''));
10525
+ return el;
10051
10526
  }
10052
10527
 
10053
10528
  /**
10054
10529
  * Remove all child nodes of the element.
10530
+ * @returns {TinyElement|TinyElement[]}
10055
10531
  */
10056
10532
  empty() {
10057
10533
  return TinyHtml_TinyHtml.empty(this);
@@ -10059,35 +10535,40 @@ class TinyHtml_TinyHtml {
10059
10535
 
10060
10536
  /**
10061
10537
  * Get the innerHTML of the element.
10062
- * @param {TinyElement|TinyElement[]} el
10538
+ * @param {TinyElement} el
10539
+ * @param {GetHTMLOptions} [ops]
10063
10540
  * @returns {string}
10064
10541
  */
10065
- static html(el) {
10542
+ static html(el, ops) {
10066
10543
  const elem = TinyHtml_TinyHtml._preElem(el, 'html');
10067
- return elem.innerHTML;
10544
+ return elem.getHTML(ops);
10068
10545
  }
10069
10546
 
10070
10547
  /**
10071
10548
  * Get the innerHTML of the element.
10549
+ * @param {GetHTMLOptions} [ops]
10072
10550
  * @returns {string}
10073
10551
  */
10074
- html() {
10075
- return TinyHtml_TinyHtml.html(this);
10552
+ html(ops) {
10553
+ return TinyHtml_TinyHtml.html(this, ops);
10076
10554
  }
10077
10555
 
10078
10556
  /**
10079
10557
  * Set the innerHTML of each element.
10080
10558
  * @param {TinyElement|TinyElement[]} el
10081
10559
  * @param {string} value
10560
+ * @returns {TinyElement|TinyElement[]}
10082
10561
  */
10083
10562
  static setHtml(el, value) {
10084
10563
  if (typeof value !== 'string') throw new Error('Value is not a valid string.');
10085
10564
  TinyHtml_TinyHtml._preElems(el, 'setHtml').forEach((el) => (el.innerHTML = value));
10565
+ return el;
10086
10566
  }
10087
10567
 
10088
10568
  /**
10089
10569
  * Set the innerHTML of the element.
10090
10570
  * @param {string} value
10571
+ * @returns {TinyElement|TinyElement[]}
10091
10572
  */
10092
10573
  setHtml(value) {
10093
10574
  return TinyHtml_TinyHtml.setHtml(this, value);
@@ -10221,6 +10702,7 @@ class TinyHtml_TinyHtml {
10221
10702
  * @param {TinyInputElement|TinyInputElement[]} el - Target element.
10222
10703
  * @param {SetValueList|((el: InputElement, val: SetValueList) => SetValueList)} value - The value to assign or a function that returns it.
10223
10704
  * @throws {Error} If the computed value is not a valid string or boolean.
10705
+ * @returns {TinyInputElement|TinyInputElement[]}
10224
10706
  */
10225
10707
  static setVal(el, value) {
10226
10708
  TinyHtml_TinyHtml._preInputElems(el, 'setVal').forEach((elem) => {
@@ -10260,6 +10742,7 @@ class TinyHtml_TinyHtml {
10260
10742
  if (typeof valToSet === 'string') elem.value = valToSet;
10261
10743
  }
10262
10744
  });
10745
+ return el;
10263
10746
  }
10264
10747
 
10265
10748
  /**
@@ -10267,6 +10750,7 @@ class TinyHtml_TinyHtml {
10267
10750
  * Accepts strings, numbers, booleans or arrays of these values, or a callback function that computes them.
10268
10751
  *
10269
10752
  * @param {SetValueList|((el: InputElement, val: SetValueList) => SetValueList)} value - The value to assign or a function that returns it.
10753
+ * @returns {TinyInputElement|TinyInputElement[]}
10270
10754
  * @throws {Error} If the computed value is not a valid string or boolean.
10271
10755
  */
10272
10756
  setVal(value) {
@@ -10536,6 +11020,7 @@ class TinyHtml_TinyHtml {
10536
11020
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10537
11021
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10538
11022
  * @param {EventRegistryOptions} [options] - Optional event listener options.
11023
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10539
11024
  */
10540
11025
  static on(el, event, handler, options) {
10541
11026
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10548,6 +11033,7 @@ class TinyHtml_TinyHtml {
10548
11033
  if (!Array.isArray(events[event])) events[event] = [];
10549
11034
  events[event].push({ handler, options });
10550
11035
  });
11036
+ return el;
10551
11037
  }
10552
11038
 
10553
11039
  /**
@@ -10556,6 +11042,7 @@ class TinyHtml_TinyHtml {
10556
11042
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10557
11043
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10558
11044
  * @param {EventRegistryOptions} [options] - Optional event listener options.
11045
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10559
11046
  */
10560
11047
  on(event, handler, options) {
10561
11048
  return TinyHtml_TinyHtml.on(this, event, handler, options);
@@ -10568,6 +11055,7 @@ class TinyHtml_TinyHtml {
10568
11055
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10569
11056
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10570
11057
  * @param {EventRegistryOptions} [options={}] - Optional event listener options.
11058
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10571
11059
  */
10572
11060
  static once(el, event, handler, options = {}) {
10573
11061
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10585,6 +11073,7 @@ class TinyHtml_TinyHtml {
10585
11073
  typeof options === 'boolean' ? options : { ...options, once: true },
10586
11074
  );
10587
11075
  });
11076
+ return el;
10588
11077
  }
10589
11078
 
10590
11079
  /**
@@ -10593,6 +11082,7 @@ class TinyHtml_TinyHtml {
10593
11082
  * @param {string} event - The event type (e.g. 'click', 'keydown').
10594
11083
  * @param {EventRegistryHandle} handler - The callback function to run on event.
10595
11084
  * @param {EventRegistryOptions} [options={}] - Optional event listener options.
11085
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10596
11086
  */
10597
11087
  once(event, handler, options = {}) {
10598
11088
  return TinyHtml_TinyHtml.once(this, event, handler, options);
@@ -10605,6 +11095,7 @@ class TinyHtml_TinyHtml {
10605
11095
  * @param {string} event - The event type.
10606
11096
  * @param {EventRegistryHandle} handler - The function originally bound to the event.
10607
11097
  * @param {boolean|EventListenerOptions} [options] - Optional listener options.
11098
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10608
11099
  */
10609
11100
  static off(el, event, handler, options) {
10610
11101
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10617,6 +11108,7 @@ class TinyHtml_TinyHtml {
10617
11108
  if (events[event].length === 0) delete events[event];
10618
11109
  }
10619
11110
  });
11111
+ return el;
10620
11112
  }
10621
11113
 
10622
11114
  /**
@@ -10625,6 +11117,7 @@ class TinyHtml_TinyHtml {
10625
11117
  * @param {string} event - The event type.
10626
11118
  * @param {EventRegistryHandle} handler - The function originally bound to the event.
10627
11119
  * @param {boolean|EventListenerOptions} [options] - Optional listener options.
11120
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10628
11121
  */
10629
11122
  off(event, handler, options) {
10630
11123
  return TinyHtml_TinyHtml.off(this, event, handler, options);
@@ -10635,6 +11128,7 @@ class TinyHtml_TinyHtml {
10635
11128
  *
10636
11129
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
10637
11130
  * @param {string} event - The event type to remove (e.g. 'click').
11131
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10638
11132
  */
10639
11133
  static offAll(el, event) {
10640
11134
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10647,12 +11141,14 @@ class TinyHtml_TinyHtml {
10647
11141
  delete events[event];
10648
11142
  }
10649
11143
  });
11144
+ return el;
10650
11145
  }
10651
11146
 
10652
11147
  /**
10653
11148
  * Removes all event listeners of a specific type from the element.
10654
11149
  *
10655
11150
  * @param {string} event - The event type to remove (e.g. 'click').
11151
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10656
11152
  */
10657
11153
  offAll(event) {
10658
11154
  return TinyHtml_TinyHtml.offAll(this, event);
@@ -10664,6 +11160,7 @@ class TinyHtml_TinyHtml {
10664
11160
  * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
10665
11161
  * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
10666
11162
  * Optional filter function to selectively remove specific handlers.
11163
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10667
11164
  */
10668
11165
  static offAllTypes(el, filterFn = null) {
10669
11166
  if (filterFn !== null && typeof filterFn !== 'function')
@@ -10682,6 +11179,7 @@ class TinyHtml_TinyHtml {
10682
11179
 
10683
11180
  __eventRegistry.delete(elem);
10684
11181
  });
11182
+ return el;
10685
11183
  }
10686
11184
 
10687
11185
  /**
@@ -10689,6 +11187,7 @@ class TinyHtml_TinyHtml {
10689
11187
  *
10690
11188
  * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
10691
11189
  * Optional filter function to selectively remove specific handlers.
11190
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10692
11191
  */
10693
11192
  offAllTypes(filterFn = null) {
10694
11193
  return TinyHtml_TinyHtml.offAllTypes(this, filterFn);
@@ -10700,6 +11199,7 @@ class TinyHtml_TinyHtml {
10700
11199
  * @param {TinyEventTarget|TinyEventTarget[]} el - Target element where the event should be triggered.
10701
11200
  * @param {string} event - Name of the event to trigger.
10702
11201
  * @param {Event|CustomEvent|CustomEventInit} [payload] - Optional event object or data to pass.
11202
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10703
11203
  */
10704
11204
  static trigger(el, event, payload = {}) {
10705
11205
  if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
@@ -10715,6 +11215,7 @@ class TinyHtml_TinyHtml {
10715
11215
 
10716
11216
  elem.dispatchEvent(evt);
10717
11217
  });
11218
+ return el;
10718
11219
  }
10719
11220
 
10720
11221
  /**
@@ -10722,6 +11223,7 @@ class TinyHtml_TinyHtml {
10722
11223
  *
10723
11224
  * @param {string} event - Name of the event to trigger.
10724
11225
  * @param {Event|CustomEvent|CustomEventInit} [payload] - Optional event object or data to pass.
11226
+ * @returns {TinyEventTarget|TinyEventTarget[]}
10725
11227
  */
10726
11228
  trigger(event, payload = {}) {
10727
11229
  return TinyHtml_TinyHtml.trigger(this, event, payload);
@@ -10764,6 +11266,7 @@ class TinyHtml_TinyHtml {
10764
11266
  * @param {TinyElement|TinyElement[]} el
10765
11267
  * @param {string} name
10766
11268
  * @param {string|null} [value=null]
11269
+ * @returns {TinyElement|TinyElement[]}
10767
11270
  */
10768
11271
  static setAttr(el, name, value = null) {
10769
11272
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
@@ -10773,12 +11276,14 @@ class TinyHtml_TinyHtml {
10773
11276
  if (value === null) elem.removeAttribute(name);
10774
11277
  else elem.setAttribute(name, value);
10775
11278
  });
11279
+ return el;
10776
11280
  }
10777
11281
 
10778
11282
  /**
10779
11283
  * Set an attribute on an element.
10780
11284
  * @param {string} name
10781
11285
  * @param {string|null} [value=null]
11286
+ * @returns {TinyElement|TinyElement[]}
10782
11287
  */
10783
11288
  setAttr(name, value) {
10784
11289
  return TinyHtml_TinyHtml.setAttr(this, name, value);
@@ -10788,15 +11293,18 @@ class TinyHtml_TinyHtml {
10788
11293
  * Remove attribute(s) from an element.
10789
11294
  * @param {TinyElement|TinyElement[]} el
10790
11295
  * @param {string} name Space-separated list of attributes.
11296
+ * @returns {TinyElement|TinyElement[]}
10791
11297
  */
10792
11298
  static removeAttr(el, name) {
10793
11299
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
10794
11300
  TinyHtml_TinyHtml._preElems(el, 'removeAttr').forEach((elem) => elem.removeAttribute(name));
11301
+ return el;
10795
11302
  }
10796
11303
 
10797
11304
  /**
10798
11305
  * Remove attribute(s) from an element.
10799
11306
  * @param {string} name Space-separated list of attributes.
11307
+ * @returns {TinyElement|TinyElement[]}
10800
11308
  */
10801
11309
  removeAttr(name) {
10802
11310
  return TinyHtml_TinyHtml.removeAttr(this, name);
@@ -10851,6 +11359,7 @@ class TinyHtml_TinyHtml {
10851
11359
  * Set a property on an element.
10852
11360
  * @param {TinyElement|TinyElement[]} el
10853
11361
  * @param {string} name
11362
+ * @returns {TinyElement|TinyElement[]}
10854
11363
  */
10855
11364
  static addProp(el, name) {
10856
11365
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
@@ -10860,11 +11369,13 @@ class TinyHtml_TinyHtml {
10860
11369
  // @ts-ignore
10861
11370
  elem[name] = true;
10862
11371
  });
11372
+ return el;
10863
11373
  }
10864
11374
 
10865
11375
  /**
10866
11376
  * Set a property on an element.
10867
11377
  * @param {string} name
11378
+ * @returns {TinyElement|TinyElement[]}
10868
11379
  */
10869
11380
  addProp(name) {
10870
11381
  return TinyHtml_TinyHtml.addProp(this, name);
@@ -10874,6 +11385,7 @@ class TinyHtml_TinyHtml {
10874
11385
  * Remove a property from an element.
10875
11386
  * @param {TinyElement|TinyElement[]} el
10876
11387
  * @param {string} name
11388
+ * @returns {TinyElement|TinyElement[]}
10877
11389
  */
10878
11390
  static removeProp(el, name) {
10879
11391
  if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
@@ -10883,11 +11395,13 @@ class TinyHtml_TinyHtml {
10883
11395
  // @ts-ignore
10884
11396
  elem[name] = false;
10885
11397
  });
11398
+ return el;
10886
11399
  }
10887
11400
 
10888
11401
  /**
10889
11402
  * Remove a property from an element.
10890
11403
  * @param {string} name
11404
+ * @returns {TinyElement|TinyElement[]}
10891
11405
  */
10892
11406
  removeProp(name) {
10893
11407
  return TinyHtml_TinyHtml.removeProp(this, name);
@@ -10928,13 +11442,16 @@ class TinyHtml_TinyHtml {
10928
11442
  /**
10929
11443
  * Removes an element from the DOM.
10930
11444
  * @param {TinyElement|TinyElement[]} el - The DOM element or selector to remove.
11445
+ * @returns {TinyElement|TinyElement[]}
10931
11446
  */
10932
11447
  static remove(el) {
10933
11448
  TinyHtml_TinyHtml._preElems(el, 'remove').forEach((elem) => elem.remove());
11449
+ return el;
10934
11450
  }
10935
11451
 
10936
11452
  /**
10937
11453
  * Removes the element from the DOM.
11454
+ * @returns {TinyElement|TinyElement[]}
10938
11455
  */
10939
11456
  remove() {
10940
11457
  return TinyHtml_TinyHtml.remove(this);
@@ -11278,6 +11795,14 @@ class TinyHtml_TinyHtml {
11278
11795
  */
11279
11796
  static isInViewport(el) {
11280
11797
  const elem = TinyHtml_TinyHtml._preElem(el, 'isInViewport');
11798
+ if (
11799
+ !elem.checkVisibility({
11800
+ contentVisibilityAuto: false,
11801
+ opacityProperty: false,
11802
+ visibilityProperty: false,
11803
+ })
11804
+ )
11805
+ return false;
11281
11806
  const elementTop = TinyHtml_TinyHtml.offset(elem).top;
11282
11807
  const elementBottom = elementTop + TinyHtml_TinyHtml.outerHeight(elem);
11283
11808
 
@@ -11304,6 +11829,14 @@ class TinyHtml_TinyHtml {
11304
11829
  */
11305
11830
  static isScrolledIntoView(el) {
11306
11831
  const elem = TinyHtml_TinyHtml._preElem(el, 'isScrolledIntoView');
11832
+ if (
11833
+ !elem.checkVisibility({
11834
+ contentVisibilityAuto: false,
11835
+ opacityProperty: false,
11836
+ visibilityProperty: false,
11837
+ })
11838
+ )
11839
+ return false;
11307
11840
  const docViewTop = TinyHtml_TinyHtml.scrollTop(window);
11308
11841
  const docViewBottom = docViewTop + TinyHtml_TinyHtml.height(window);
11309
11842
 
@@ -11321,33 +11854,138 @@ class TinyHtml_TinyHtml {
11321
11854
  isScrolledIntoView() {
11322
11855
  return TinyHtml_TinyHtml.isScrolledIntoView(this);
11323
11856
  }
11324
- }
11325
11857
 
11326
- /* harmony default export */ const libs_TinyHtml = (TinyHtml_TinyHtml);
11858
+ /**
11859
+ * Checks if the given element is at least partially visible within the boundaries of a container.
11860
+ *
11861
+ * @param {TinyElement} el - The DOM element to check.
11862
+ * @param {TinyElement} cont - The container element acting as the viewport.
11863
+ * @returns {boolean} True if the element is partially visible within the container, false otherwise.
11864
+ */
11865
+ static isInContainer(el, cont) {
11866
+ const elem = TinyHtml_TinyHtml._preElem(el, 'isInContainer');
11867
+ const container = TinyHtml_TinyHtml._preElem(cont, 'isInContainer');
11327
11868
 
11328
- ;// ./src/v1/basics/html_deprecated.mjs
11869
+ if (
11870
+ !elem.checkVisibility({
11871
+ contentVisibilityAuto: false,
11872
+ opacityProperty: false,
11873
+ visibilityProperty: false,
11874
+ })
11875
+ )
11876
+ return false;
11877
+ const elemRect = elem.getBoundingClientRect();
11878
+ const containerRect = container.getBoundingClientRect();
11329
11879
 
11880
+ const verticallyVisible =
11881
+ elemRect.bottom > containerRect.top && elemRect.top < containerRect.bottom;
11330
11882
 
11331
- /**
11332
- * Checks if two DOM elements are colliding on the screen.
11333
- *
11334
- * @param {Element} elem1 - First DOM element.
11335
- * @param {Element} elem2 - Second DOM element.
11336
- * @returns {boolean} - Returns true if the elements are colliding.
11337
- * @deprecated - Use TinyHtml.isCollWith instead.
11338
- */
11339
- function areHtmlElsColliding(elem1, elem2) {
11340
- return libs_TinyHtml.isCollWith(elem1, elem2);
11341
- }
11883
+ const horizontallyVisible =
11884
+ elemRect.right > containerRect.left && elemRect.left < containerRect.right;
11342
11885
 
11343
- /**
11344
- * Checks if two DOM elements are colliding on the screen.
11345
- *
11346
- * @param {Element} elem1 - First DOM element.
11347
- * @param {Element} elem2 - Second DOM element.
11348
- * @returns {boolean} - Returns true if the elements are colliding.
11349
- * @deprecated - Use TinyHtml.isCollPerfWith instead.
11350
- */
11886
+ return verticallyVisible && horizontallyVisible;
11887
+ }
11888
+
11889
+ /**
11890
+ * Checks if the given element is at least partially visible within the boundaries of a container.
11891
+ *
11892
+ * @param {TinyElement} cont - The container element acting as the viewport.
11893
+ * @returns {boolean} True if the element is partially visible within the container, false otherwise.
11894
+ */
11895
+ isInContainer(cont) {
11896
+ return TinyHtml_TinyHtml.isInContainer(this, cont);
11897
+ }
11898
+
11899
+ /**
11900
+ * Checks if the given element is fully visible within the boundaries of a container (top and bottom).
11901
+ *
11902
+ * @param {TinyElement} el - The DOM element to check.
11903
+ * @param {TinyElement} cont - The container element acting as the viewport.
11904
+ * @returns {boolean} True if the element is fully visible within the container, false otherwise.
11905
+ */
11906
+ static isFullyInContainer(el, cont) {
11907
+ const elem = TinyHtml_TinyHtml._preElem(el, 'isScrolledIntoView');
11908
+ const container = TinyHtml_TinyHtml._preElem(cont, 'isInContainer');
11909
+
11910
+ if (
11911
+ !elem.checkVisibility({
11912
+ contentVisibilityAuto: false,
11913
+ opacityProperty: false,
11914
+ visibilityProperty: false,
11915
+ })
11916
+ )
11917
+ return false;
11918
+ const elemRect = elem.getBoundingClientRect();
11919
+ const containerRect = container.getBoundingClientRect();
11920
+
11921
+ const isFullyVisible =
11922
+ elemRect.top >= containerRect.top &&
11923
+ elemRect.bottom <= containerRect.bottom &&
11924
+ elemRect.left >= containerRect.left &&
11925
+ elemRect.right <= containerRect.right;
11926
+
11927
+ return isFullyVisible;
11928
+ }
11929
+
11930
+ /**
11931
+ * Checks if the given element is fully visible within the boundaries of a container (top and bottom).
11932
+ *
11933
+ * @param {TinyElement} cont - The container element acting as the viewport.
11934
+ * @returns {boolean} True if the element is fully visible within the container, false otherwise.
11935
+ */
11936
+ isFullyInContainer(cont) {
11937
+ return TinyHtml_TinyHtml.isFullyInContainer(this, cont);
11938
+ }
11939
+
11940
+ /**
11941
+ * Checks if an element has scrollable content.
11942
+ *
11943
+ * @param {TinyElement} el - The DOM element to check.
11944
+ * @returns {{ v: boolean, h: boolean }} - True if scroll is needed in that direction.
11945
+ */
11946
+ static hasScroll(el) {
11947
+ const elem = TinyHtml_TinyHtml._preElem(el, 'hasScroll');
11948
+ return {
11949
+ v: elem.scrollHeight > elem.clientHeight,
11950
+ h: elem.scrollWidth > elem.clientWidth,
11951
+ };
11952
+ }
11953
+
11954
+ /**
11955
+ * Checks if an element has scrollable content.
11956
+ *
11957
+ * @returns {{ v: boolean, h: boolean }} - True if scroll is needed in that direction.
11958
+ */
11959
+ hasScroll() {
11960
+ return TinyHtml_TinyHtml.hasScroll(this);
11961
+ }
11962
+ }
11963
+
11964
+ /* harmony default export */ const libs_TinyHtml = (TinyHtml_TinyHtml);
11965
+
11966
+ ;// ./src/v1/basics/html_deprecated.mjs
11967
+
11968
+
11969
+ /**
11970
+ * Checks if two DOM elements are colliding on the screen.
11971
+ *
11972
+ * @param {Element} elem1 - First DOM element.
11973
+ * @param {Element} elem2 - Second DOM element.
11974
+ * @returns {boolean} - Returns true if the elements are colliding.
11975
+ * @deprecated - Use TinyHtml.isCollWith instead.
11976
+ */
11977
+ function areHtmlElsColliding(elem1, elem2) {
11978
+ return libs_TinyHtml.isCollWith(elem1, elem2);
11979
+ }
11980
+
11981
+ /**
11982
+ * Checks if two DOM elements are colliding on the screen.
11983
+ *
11984
+ * @param {Element} elem1 - First DOM element.
11985
+ * @param {Element} elem2 - Second DOM element.
11986
+ * @returns {boolean} - Returns true if the elements are colliding.
11987
+ * @deprecated - Use TinyHtml.isCollPerfWith instead.
11988
+ */
11351
11989
  function areHtmlElsPerfColliding(elem1, elem2) {
11352
11990
  return TinyHtml.isCollPerfWith(elem1, elem2);
11353
11991
  }
@@ -13951,7 +14589,1941 @@ class TinyAfterScrollWatcher {
13951
14589
 
13952
14590
  /* harmony default export */ const libs_TinyAfterScrollWatcher = (TinyAfterScrollWatcher);
13953
14591
 
13954
- ;// ./src/v1/index.mjs
14592
+ ;// ./src/v1/libs/UltraRandomMsgGen.mjs
14593
+ const defaultWords = [
14594
+ 'lorem',
14595
+ 'ipsum',
14596
+ 'dolor',
14597
+ 'sit',
14598
+ 'amet',
14599
+ 'consectetur',
14600
+ 'adipiscing',
14601
+ 'elit',
14602
+ 'sed',
14603
+ 'do',
14604
+ 'eiusmod',
14605
+ 'tempor',
14606
+ 'incididunt',
14607
+ 'ut',
14608
+ 'labore',
14609
+ 'et',
14610
+ 'dolore',
14611
+ 'magna',
14612
+ 'aliqua',
14613
+ ];
14614
+ const defaultEmojis = [
14615
+ '😂',
14616
+ '🌈',
14617
+ '🤖',
14618
+ '💥',
14619
+ '🐸',
14620
+ '🍕',
14621
+ '🦄',
14622
+ '🧠',
14623
+ '🧬',
14624
+ '🚀',
14625
+ '🫠',
14626
+ '💫',
14627
+ '🍩',
14628
+ '👾',
14629
+ '🎉',
14630
+ '🥴',
14631
+ '🐙',
14632
+ '🧃',
14633
+ '🪐',
14634
+ '🎈',
14635
+ '🧸',
14636
+ '👻',
14637
+ '🥳',
14638
+ '🍭',
14639
+ '💖',
14640
+ '😺',
14641
+ '🌮',
14642
+ '🪅',
14643
+ '🎮',
14644
+ '🥓',
14645
+ '🍮',
14646
+ ];
14647
+
14648
+ const defaultNouns = [
14649
+ 'pudding',
14650
+ 'bean',
14651
+ 'snuggle',
14652
+ 'bloop',
14653
+ 'jelly',
14654
+ 'unicorn',
14655
+ 'floof',
14656
+ 'giggle',
14657
+ 'bubble',
14658
+ 'muffin',
14659
+ 'puff',
14660
+ 'pickle',
14661
+ 'goblin',
14662
+ 'waffle',
14663
+ 'sprinkle',
14664
+ 'cupcake',
14665
+ 'fizzle',
14666
+ 'marshmallow',
14667
+ 'duckling',
14668
+ 'sock',
14669
+ 'cloud',
14670
+ 'teacup',
14671
+ 'nugget',
14672
+ 'gnome',
14673
+ 'hug',
14674
+ 'moonbean',
14675
+ 'crayon',
14676
+ 'jiggle',
14677
+ 'glitter',
14678
+ 'carrot',
14679
+ 'goober',
14680
+ ];
14681
+
14682
+ const defaultVerbs = [
14683
+ 'wiggles',
14684
+ 'bounces',
14685
+ 'flies',
14686
+ 'splats',
14687
+ 'scoots',
14688
+ 'giggles',
14689
+ 'wigglesnacks',
14690
+ 'twirls',
14691
+ 'boops',
14692
+ 'pops',
14693
+ 'sings',
14694
+ 'hugs',
14695
+ 'floats',
14696
+ 'glows',
14697
+ 'flaps',
14698
+ 'mlems',
14699
+ 'dances',
14700
+ 'puddles',
14701
+ 'nomnoms',
14702
+ 'wigglejumps',
14703
+ 'sniffs',
14704
+ 'tumbles',
14705
+ 'slides',
14706
+ 'chirps',
14707
+ 'burps',
14708
+ 'sparkles',
14709
+ 'waddles',
14710
+ 'rambles',
14711
+ 'blinks',
14712
+ 'mews',
14713
+ ];
14714
+
14715
+ const defaultAdjectives = [
14716
+ 'fluffy',
14717
+ 'silly',
14718
+ 'yummy',
14719
+ 'squishy',
14720
+ 'wobbly',
14721
+ 'magical',
14722
+ 'tiny',
14723
+ 'sleepy',
14724
+ 'wiggly',
14725
+ 'bubbly',
14726
+ 'glittery',
14727
+ 'fuzzy',
14728
+ 'jiggly',
14729
+ 'sparkly',
14730
+ 'giggly',
14731
+ 'crunchy',
14732
+ 'goofy',
14733
+ 'soft',
14734
+ 'mushy',
14735
+ 'sweet',
14736
+ 'loopy',
14737
+ 'floaty',
14738
+ 'bonkers',
14739
+ 'chewy',
14740
+ 'ticklish',
14741
+ 'dreamy',
14742
+ 'pastel',
14743
+ 'cozy',
14744
+ 'teensy',
14745
+ 'grumbly',
14746
+ ];
14747
+
14748
+ const defaultTemplates = [
14749
+ // 🧁 Tiny
14750
+ 'Boop!',
14751
+ 'Wiggle achieved.',
14752
+ 'Oops, {noun} everywhere!',
14753
+ 'Tiny {adj} {noun}, big {adj} {noun}.',
14754
+ 'Snuggle initiated with a {adj} {noun}.',
14755
+ '{adj} vibes only, powered by {noun}.',
14756
+ 'No {noun}, no {noun}.',
14757
+ 'Bounce first, {verb} later.',
14758
+ 'Mlem. {verb}. {noun}.',
14759
+ '{noun} go brrr and {verb}.',
14760
+ 'Why not? {noun} did. Then {noun} followed.',
14761
+
14762
+ // 🍬 Simple
14763
+ 'A {adj} {noun} just {verb} near the {adj} window.',
14764
+ 'That {adj} {noun} stole my {noun}!',
14765
+ 'Look, a {adj} {noun} trying to {verb} again!',
14766
+ 'Every {noun} deserves a {adj} nap.',
14767
+ 'The {adj} {noun} is my {adj} spirit animal.',
14768
+ 'Someone call the {noun}, it’s {verb}ing and {verb}ing again!',
14769
+ 'Don’t touch the {adj} {noun}. It {verb}s loudly.',
14770
+ '{noun} forgot how {noun}s work and just {verb}ed.',
14771
+
14772
+ // 🍩 Averages
14773
+ 'There once was a {adj} {noun} who loved to {verb} with a {adj} {noun} all day.',
14774
+ 'Apparently, {noun}s are banned from {verb}ing and {verb}ing in the {noun} library.',
14775
+ 'That {adj} noise? Just a {noun} learning to {verb} on a {adj} {noun}.',
14776
+ 'I saw a {adj} {noun} {verb} so hard, it became a {adj} muffin.',
14777
+ 'The {adj} {noun} met a {adj} pickle and everything {verb}ed.',
14778
+ '{noun}s are like {noun}: {adj}, unpredictable, and wiggly.',
14779
+ 'Don’t judge a {adj} {noun} by its ability to {verb}, or by its {adj} hat.',
14780
+ 'All I wanted was peace, but a {adj} {noun} with a {adj} {noun} had other plans.',
14781
+
14782
+ // 🍓 Giants
14783
+ '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.',
14784
+ 'I tried to be {adj} today, but then a {noun} {verb}ed across my {noun} and typed “asdfgh{noun}” repeatedly.',
14785
+ 'If you think you’ve seen everything, wait until a {adj} {noun} {verb}s on a {noun} wearing {adj} socks with tiny {noun}s.',
14786
+ '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.',
14787
+ '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.',
14788
+
14789
+ // 🍒 Giants and narratives
14790
+ '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}.',
14791
+ '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.',
14792
+ '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}.',
14793
+
14794
+ // ☎️ Chats and chats
14795
+ 'Hey... do you ever wonder if {noun}s dream of {noun}s?',
14796
+ 'Okay but hear me out: what if the {adj} {noun} could actually {verb} backwards?',
14797
+ 'Can we talk about the {noun} that just {verb}ed and vanished?',
14798
+ 'So I was talking to a {noun}, and it told me to stop being {adj}. Rude.',
14799
+ 'Not to alarm you, but there’s a {adj} {noun} behind you doing the {verb}.',
14800
+ 'Why does this {noun} keep staring at me like I owe it pudding?',
14801
+ 'Is this a safe space to admit I accidentally {verb}ed a {noun}?',
14802
+ 'I just walked into the room and someone shouted “{adj} {noun}!” — what did I miss?',
14803
+ 'Wait... we were supposed to bring a {noun}? No one told me!',
14804
+ 'Okay but what if the {adj} {noun} has feelings too?',
14805
+ 'Be honest, do I look like a {noun} that forgot how to {verb}?',
14806
+ 'If I say "oops", do I still have to explain why the {noun} is glowing?',
14807
+ 'I don’t know what’s going on but I brought snacks and a confused {noun}.',
14808
+ 'Do you think pudding knows it’s pudding? Just a thought.',
14809
+ 'Is this about the time I {verb}ed the {adj} {noun} by accident? Because I can explain.',
14810
+
14811
+ // 🌀 Confusions, unexpected entrances, lost people
14812
+ 'Wait, are we talking about {noun}s or am I on the wrong chat again?',
14813
+ 'What is this group even about? I just saw "pudding" and joined.',
14814
+ 'Hi! I have no idea what’s happening but I’m here and I brought a {noun}.',
14815
+ 'I blinked and now there’s a {adj} {noun} in charge of everything.',
14816
+ 'Can someone please explain why the {noun} is floating and chanting?',
14817
+ 'Okay who gave the {noun} access to glitter and emotional support pickles?',
14818
+ 'I came for a calm discussion and now there’s a {adj} battle between {noun}s.',
14819
+ 'Not sure if I’m early, late, or inside a {noun} dream.',
14820
+ 'I was gone for 3 minutes and now someone’s riding a {noun} into the pudding realm.',
14821
+ 'This isn’t the pudding appreciation group, is it? ...oh no.',
14822
+ 'So... who summoned the {adj} {noun} this time? Be honest.',
14823
+ 'I came here for vibes and stayed for the {noun}s.',
14824
+ 'Does anyone else hear faint giggling or is that just the {adj} {noun} again?',
14825
+ 'I feel like I missed step one, two, and also three of this conversation.',
14826
+ 'All I asked was “what’s up?” and now I’m emotionally attached to a {noun}.',
14827
+
14828
+ // 💬 More interactive chat style
14829
+ 'Me: "I’ll be normal today." Also me: *{verb}s into a meeting riding a {adj} {noun}*',
14830
+ '"It’s just pudding," they said. "It can’t hurt you," they said. They were wrong.',
14831
+ 'I said one nice thing to a {noun} and now it follows me everywhere.',
14832
+ 'Can we take a moment to appreciate how the {adj} {noun} is just vibing?',
14833
+ 'Who put a tiny hat on the {noun}? Because that’s adorable.',
14834
+ 'My life has been different ever since the {noun} started {verb}ing.',
14835
+ 'Raise your hand if you’ve ever been personally attacked by a {adj} {noun}.',
14836
+ '"Don’t be weird," they said. So I became a {noun} instead.',
14837
+ 'Do {noun}s have feelings? Asking for a {adj} friend.',
14838
+ 'I trusted the {noun} and all I got was a glitter-covered sock.',
14839
+ 'Plot twist: the {adj} {noun} was inside us all along.',
14840
+
14841
+ // Mini cute explosions and chaos
14842
+ 'The {adj} {noun} {verb} and {verb} all over the {adj} {noun}!',
14843
+ 'Oops! {noun}s {verb}ed into the {adj} {noun} while {verb}ing crazily.',
14844
+ '{adj}, {adj}, and more {adj} {noun}s just {verb}ed by!',
14845
+ 'Why does the {adj} {noun} keep {verb}ing and {verb}ing without stopping?',
14846
+ 'Look! A {adj} {noun}, a {adj} {noun}, and a {noun} all {verb}ing together!',
14847
+ 'Sometimes, the {noun} just {verb}s, then {verb}s again, and never stops being {adj}.',
14848
+
14849
+ // Phrases with repetition and multiple spaces for chaos
14850
+ '{noun}, {noun}, and {noun} — all {verb}ing and {verb}ing in a {adj} {noun}.',
14851
+ 'I saw a {adj} {noun} {verb}, then another {noun} {verb}ing with a {adj} {noun}.',
14852
+ 'The {noun} {verb}s {verb} while the {adj} {noun} {verb}s and the {noun} just {verb}s.',
14853
+ 'Can a {adj} {noun} {verb} {verb} without a {adj} {noun} watching?',
14854
+
14855
+ // Phrases with interactions and breaks for fun
14856
+ 'Whoa! The {adj} {noun} just {verb}ed... and then {verb}ed again!',
14857
+ 'Hey... did you see that {adj} {noun} {verb} over there?',
14858
+ 'Umm, the {noun} is {verb}ing but also {verb}ing and {verb}ing!',
14859
+ 'Lol! {noun}s {verb} so {adj}ly, it’s impossible not to giggle.',
14860
+
14861
+ // Mini exaggerated and cute dialogues
14862
+ '"Hey! What’s up?" asked the {adj} {noun}, who {verb}ed loudly.',
14863
+ '"I’m just a {adj} {noun} trying to {verb}, you know?"',
14864
+ '"Did you see the {adj} {noun} that just {verb}ed in the pudding?"',
14865
+ '"No way! The {noun} {verb}s better than me!"',
14866
+ '"Wait, the {adj} {noun} said it would {verb}, but it {verb}ed instead!"',
14867
+
14868
+ // Extensive, crazy and fun narrative phrases
14869
+ '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.',
14870
+ 'The legend tells of a {adj} {noun} who could {verb} faster than any {noun} ever seen, leaving trails of {adj} sparkles behind.',
14871
+ 'Every day, the {adj} {noun} would {verb} around the {adj} meadow, trying to convince the {noun}s to join the grand pudding party.',
14872
+ '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.',
14873
+ '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.',
14874
+
14875
+ // Total cases with many placeholders
14876
+ '{adj} {noun} {verb} {verb} {verb} {noun} {verb} {adj} {noun} {verb} {noun}!',
14877
+ '{noun} {noun} {verb} {adj} {noun} and then {verb} {verb} the {adj} {noun}.',
14878
+ '{verb} the {adj} {noun}, then {verb} the {noun} while {verb}ing {adj}ly.',
14879
+ 'Can the {adj} {noun} {verb} and {verb} the {adj} {noun} at the same time?',
14880
+ '{noun} {verb} {noun} {verb}, but the {adj} {noun} {verb}s better.',
14881
+
14882
+ // Frases with cute interjections and silly sounds
14883
+ 'Boop! The {adj} {noun} just {verb}ed in the pudding.',
14884
+ 'Bloop bloop, the {noun} is {verb}ing all over again!',
14885
+ 'Mlem mlem, a {adj} {noun} {verb}s quietly in the corner.',
14886
+ 'Splat! {noun} {verb}ed right on the {adj} {noun}.',
14887
+ 'Yum! A {adj} {noun} just {verb}ed in my mouth.',
14888
+
14889
+ // Phrases with silly questions
14890
+ 'Do {noun}s really {verb} when nobody’s watching?',
14891
+ 'Why does the {adj} {noun} always {verb} at midnight?',
14892
+ 'Can a {noun} be too {adj} to {verb} properly?',
14893
+ 'Is it normal for a {adj} {noun} to {verb} three times in a row?',
14894
+ 'Who taught the {noun} to {verb} like that?',
14895
+
14896
+ // Frases of confusion and fun surprises
14897
+ 'Wait, did the {adj} {noun} just {verb} itself?',
14898
+ 'I can’t believe the {noun} {verb}ed into a {adj} {noun}!',
14899
+ 'Suddenly, a {adj} {noun} appeared and started to {verb} wildly.',
14900
+ 'That {noun} was {verb}ing so {adj}ly it broke the pudding meter.',
14901
+ 'Everyone’s talking about the {adj} {noun} that {verb}ed out of nowhere.',
14902
+ ];
14903
+
14904
+ /**
14905
+ * @typedef {'mixed'|'readable'|'chaotic'|'natural'} RandomMsgModes
14906
+ * Defines how the message content is generated:
14907
+ * - `mixed`: Combines readable words, gibberish, symbols, emojis, etc.
14908
+ * - `readable`: Focuses on human-readable words only.
14909
+ * - `chaotic`: Pure chaos, mostly gibberish and symbols.
14910
+ * - `natural`: Uses structured grammar templates to form silly but proper sentences.
14911
+ */
14912
+
14913
+ /**
14914
+ * @typedef {'inline'|'end'|'none'} EmojiPlacement
14915
+ * Controls where emojis are placed:
14916
+ * - `inline`: Emojis may appear throughout the text.
14917
+ * - `end`: Emojis appear at the end of lines.
14918
+ * - `none`: No emojis will be used.
14919
+ */
14920
+
14921
+ /**
14922
+ * @typedef {Object} MsgGenConfig
14923
+ * Configuration object for customizing message generation.
14924
+ *
14925
+ * @property {number} minLength - Minimum total length (in characters) of the final generated text.
14926
+ * @property {number} maxLength - Maximum total length (in characters) of the final generated text.
14927
+ * @property {boolean} readable - Whether to favor readable word-like strings.
14928
+ * @property {boolean} useEmojis - Whether emojis are allowed in the generated content.
14929
+ * @property {boolean} includeNumbers - Whether random numbers can appear in the text.
14930
+ * @property {boolean} includeSymbols - Whether random symbols (e.g., !@#) are included.
14931
+ * @property {boolean} allowWeirdSpacing - Enables fun spacing effects (e.g., extra spaces, newlines, uppercase words).
14932
+ *
14933
+ * @property {string[]} emojiSet - List of emojis to choose from. Overrides default set if provided.
14934
+ * @property {string[]} wordSet - List of base words used in readable and mixed modes.
14935
+ *
14936
+ * @property {RandomMsgModes} mode - Determines the overall generation strategy (`mixed`, `readable`, `chaotic`, or `natural`).
14937
+ *
14938
+ * @property {Object} grammar - Grammar configuration used when `mode` is set to `'natural'`.
14939
+ * @property {string[]} grammar.templates - Sentence templates using placeholders (`{noun}`, `{verb}`, `{adj}`).
14940
+ * @property {string[]} grammar.nouns - List of nouns to insert into `{noun}` placeholders.
14941
+ * @property {string[]} grammar.verbs - List of verbs to insert into `{verb}` placeholders.
14942
+ * @property {string[]} grammar.adjectives - List of adjectives to insert into `{adj}` placeholders.
14943
+ *
14944
+ * @property {boolean} repeatWords - If false, avoids repeating the same word within a single generation.
14945
+ * @property {EmojiPlacement} emojiPlacement - Controls how emojis are placed in the text.
14946
+ *
14947
+ * @property {Object} [paragraphs] - Optional paragraph configuration.
14948
+ * @property {number} paragraphs.min - Minimum number of paragraphs to generate.
14949
+ * @property {number} paragraphs.max - Maximum number of paragraphs to generate.
14950
+ *
14951
+ * @property {Object} line - Configuration for line-based generation.
14952
+ * @property {number} line.minLength - Minimum number of characters per line.
14953
+ * @property {number} line.maxLength - Maximum number of characters per line.
14954
+ * @property {number} line.emojiChance - Probability (0 to 1) that a line will end with an emoji when allowed.
14955
+ */
14956
+
14957
+ /**
14958
+ * UltraRandomMsgGen - Phrase templates and word lists
14959
+ *
14960
+ * Portions of the templates, word sets, and phrase structures
14961
+ * were generated and expanded by ChatGPT (OpenAI).
14962
+ *
14963
+ * This content was crafted to produce innocent, playful, and diverse
14964
+ * random messages focused on a pudding-themed, whimsical style.
14965
+ *
14966
+ * @class
14967
+ */
14968
+ class UltraRandomMsgGen {
14969
+ /** @type {string[]} */
14970
+ static defaultWords = defaultWords;
14971
+ /** @type {string[]} */
14972
+ static defaultEmojis = defaultEmojis;
14973
+ /** @type {string[]} */
14974
+ static defaultNouns = defaultNouns;
14975
+ /** @type {string[]} */
14976
+ static defaultVerbs = defaultVerbs;
14977
+ /** @type {string[]} */
14978
+ static defaultAdjectives = defaultAdjectives;
14979
+ /** @type {string[]} */
14980
+ static defaultTemplates = defaultTemplates;
14981
+
14982
+ /** @type {MsgGenConfig} */
14983
+ config = {
14984
+ minLength: 10,
14985
+ maxLength: 300,
14986
+ readable: true,
14987
+ useEmojis: true,
14988
+ includeNumbers: true,
14989
+ includeSymbols: true,
14990
+ allowWeirdSpacing: false,
14991
+ emojiSet: [],
14992
+ wordSet: [],
14993
+ mode: 'mixed', // 'mixed', 'readable', 'chaotic'
14994
+ grammar: {
14995
+ templates: [],
14996
+ nouns: [],
14997
+ verbs: [],
14998
+ adjectives: [],
14999
+ },
15000
+ repeatWords: true,
15001
+ emojiPlacement: 'inline', // 'inline' | 'end' | 'none'
15002
+ line: {
15003
+ minLength: 20,
15004
+ maxLength: 120,
15005
+ emojiChance: 0.3, // 30% chance per line
15006
+ },
15007
+ };
15008
+
15009
+ /**
15010
+ * Creates an instance of UltraRandomMsgGen.
15011
+ *
15012
+ * @param {Object} [config={}] - Configuration object to customize the generator.
15013
+ * @param {number} [config.minLength=10] - Minimum total length (in characters) of generated text.
15014
+ * @param {number} [config.maxLength=300] - Maximum total length (in characters) of generated text.
15015
+ * @param {boolean} [config.readable=true] - Whether to generate readable words or random strings.
15016
+ * @param {boolean} [config.useEmojis=true] - Whether to include emojis in the generated text.
15017
+ * @param {boolean} [config.includeNumbers=true] - Whether to include numbers randomly.
15018
+ * @param {boolean} [config.includeSymbols=true] - Whether to include symbols randomly.
15019
+ * @param {boolean} [config.allowWeirdSpacing=false] - Whether to allow weird spacing (newlines, extra spaces, uppercase).
15020
+ * @param {string[]} [config.emojiSet] - Array of emojis to use (defaults to internal emoji set).
15021
+ * @param {string[]} [config.wordSet] - Array of words to use (defaults to internal word set).
15022
+ * @param {RandomMsgModes} [config.mode='mixed'] - Mode of text generation.
15023
+ * @param {Object} [config.grammar] - Grammar configuration with templates and word categories.
15024
+ * @param {string[]} [config.grammar.templates] - Array of string templates with placeholders for generating sentences.
15025
+ * @param {string[]} [config.grammar.nouns] - Array of noun strings for template substitution.
15026
+ * @param {string[]} [config.grammar.verbs] - Array of verb strings for template substitution.
15027
+ * @param {string[]} [config.grammar.adjectives] - Array of adjective strings for template substitution.
15028
+ * @param {boolean} [config.repeatWords=true] - Whether to allow repeating words in the output.
15029
+ * @param {EmojiPlacement} [config.emojiPlacement='inline'] - Placement mode for emojis in generated text.
15030
+ * @param {Object} [config.paragraphs] - Paragraph configuration object or null for single block text.
15031
+ * @param {number} [config.paragraphs.min] - Minimum number of paragraphs to generate.
15032
+ * @param {number} [config.paragraphs.max] - Maximum number of paragraphs to generate.
15033
+ * @param {Object} [config.line] - Line configuration for generated text.
15034
+ * @param {number} [config.line.minLength=20] - Minimum length (characters) per line.
15035
+ * @param {number} [config.line.maxLength=120] - Maximum length (characters) per line.
15036
+ * @param {number} [config.line.emojiChance=0.3] - Probability (0–1) of placing emoji per line.
15037
+ */
15038
+ constructor(config = {}) {
15039
+ this.symbols = '!@#$%^&*()-_=+[]{}|;:,.<>?/\\~'.split('');
15040
+
15041
+ const {
15042
+ minLength,
15043
+ maxLength,
15044
+ readable,
15045
+ useEmojis,
15046
+ includeNumbers,
15047
+ includeSymbols,
15048
+ allowWeirdSpacing,
15049
+ emojiSet,
15050
+ wordSet,
15051
+ mode,
15052
+ grammar,
15053
+ repeatWords,
15054
+ emojiPlacement,
15055
+ paragraphs,
15056
+ line,
15057
+ } = config;
15058
+
15059
+ // Validations
15060
+ if (minLength !== undefined && (!Number.isInteger(minLength) || minLength < 1)) {
15061
+ throw new TypeError('config.minLength must be an integer >= 1');
15062
+ }
15063
+
15064
+ if (maxLength !== undefined && (!Number.isInteger(maxLength) || maxLength < 1)) {
15065
+ throw new TypeError('config.maxLength must be an integer >= 1');
15066
+ }
15067
+
15068
+ if (minLength !== undefined && maxLength !== undefined && minLength > maxLength) {
15069
+ throw new RangeError('config.minLength cannot be greater than config.maxLength');
15070
+ }
15071
+
15072
+ if (readable !== undefined && typeof readable !== 'boolean') {
15073
+ throw new TypeError('config.readable must be a boolean');
15074
+ }
15075
+
15076
+ if (useEmojis !== undefined && typeof useEmojis !== 'boolean') {
15077
+ throw new TypeError('config.useEmojis must be a boolean');
15078
+ }
15079
+
15080
+ if (includeNumbers !== undefined && typeof includeNumbers !== 'boolean') {
15081
+ throw new TypeError('config.includeNumbers must be a boolean');
15082
+ }
15083
+
15084
+ if (includeSymbols !== undefined && typeof includeSymbols !== 'boolean') {
15085
+ throw new TypeError('config.includeSymbols must be a boolean');
15086
+ }
15087
+
15088
+ if (allowWeirdSpacing !== undefined && typeof allowWeirdSpacing !== 'boolean') {
15089
+ throw new TypeError('config.allowWeirdSpacing must be a boolean');
15090
+ }
15091
+
15092
+ if (emojiSet !== undefined && !Array.isArray(emojiSet)) {
15093
+ throw new TypeError('config.emojiSet must be an array of strings');
15094
+ }
15095
+
15096
+ if (wordSet !== undefined && !Array.isArray(wordSet)) {
15097
+ throw new TypeError('config.wordSet must be an array of strings');
15098
+ }
15099
+
15100
+ if (mode !== undefined && !['mixed', 'readable', 'chaotic', 'natural'].includes(mode)) {
15101
+ throw new RangeError('config.mode must be one of: "mixed", "readable", "chaotic", "natural');
15102
+ }
15103
+
15104
+ if (grammar !== undefined) {
15105
+ if (typeof grammar !== 'object' || grammar === null) {
15106
+ throw new TypeError('config.grammar must be an object');
15107
+ }
15108
+ const { templates, nouns, verbs, adjectives } = grammar;
15109
+ if (templates !== undefined && !Array.isArray(templates)) {
15110
+ throw new TypeError('config.grammar.templates must be an array of strings');
15111
+ }
15112
+ if (nouns !== undefined && !Array.isArray(nouns)) {
15113
+ throw new TypeError('config.grammar.nouns must be an array of strings');
15114
+ }
15115
+ if (verbs !== undefined && !Array.isArray(verbs)) {
15116
+ throw new TypeError('config.grammar.verbs must be an array of strings');
15117
+ }
15118
+ if (adjectives !== undefined && !Array.isArray(adjectives)) {
15119
+ throw new TypeError('config.grammar.adjectives must be an array of strings');
15120
+ }
15121
+ }
15122
+
15123
+ if (repeatWords !== undefined && typeof repeatWords !== 'boolean') {
15124
+ throw new TypeError('config.repeatWords must be a boolean');
15125
+ }
15126
+
15127
+ if (emojiPlacement !== undefined && !['inline', 'end', 'none'].includes(emojiPlacement)) {
15128
+ throw new RangeError('config.emojiPlacement must be one of: "inline", "end", "none"');
15129
+ }
15130
+
15131
+ if (paragraphs !== undefined) {
15132
+ if (typeof paragraphs !== 'object') {
15133
+ throw new TypeError('config.paragraphs must be an object or null');
15134
+ }
15135
+ const { min, max } = paragraphs;
15136
+ if (min !== undefined && (!Number.isInteger(min) || min < 1)) {
15137
+ throw new TypeError('config.paragraphs.min must be an integer >= 1');
15138
+ }
15139
+ if (max !== undefined && (!Number.isInteger(max) || max < 1)) {
15140
+ throw new TypeError('config.paragraphs.max must be an integer >= 1');
15141
+ }
15142
+ if (min !== undefined && max !== undefined && min > max) {
15143
+ throw new RangeError('config.paragraphs.min cannot be greater than config.paragraphs.max');
15144
+ }
15145
+ }
15146
+
15147
+ if (line !== undefined) {
15148
+ if (typeof line !== 'object' || line === null) {
15149
+ throw new TypeError('config.line must be an object');
15150
+ }
15151
+ const { minLength: lineMin, maxLength: lineMax, emojiChance } = line;
15152
+ if (lineMin !== undefined && (!Number.isInteger(lineMin) || lineMin < 1)) {
15153
+ throw new TypeError('config.line.minLength must be an integer >= 1');
15154
+ }
15155
+ if (lineMax !== undefined && (!Number.isInteger(lineMax) || lineMax < 1)) {
15156
+ throw new TypeError('config.line.maxLength must be an integer >= 1');
15157
+ }
15158
+ if (lineMin !== undefined && lineMax !== undefined && lineMin > lineMax) {
15159
+ throw new RangeError('config.line.minLength cannot be greater than config.line.maxLength');
15160
+ }
15161
+ if (
15162
+ emojiChance !== undefined &&
15163
+ (typeof emojiChance !== 'number' || emojiChance < 0 || emojiChance > 1)
15164
+ ) {
15165
+ throw new RangeError('config.line.emojiChance must be a number between 0 and 1');
15166
+ }
15167
+ }
15168
+
15169
+ this.config.emojiSet = [...defaultEmojis];
15170
+ this.config.wordSet = [...defaultWords];
15171
+
15172
+ this.config.grammar.templates = [...defaultTemplates];
15173
+ this.config.grammar.nouns = [...defaultNouns];
15174
+ this.config.grammar.verbs = [...defaultVerbs];
15175
+ this.config.grammar.adjectives = [...defaultAdjectives];
15176
+ Object.assign(this.config, config);
15177
+ }
15178
+
15179
+ /**
15180
+ * Merges new configuration values into the current instance.
15181
+ * @param {Object} newConfig - Object with one or more configuration overrides.
15182
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15183
+ */
15184
+ configure(newConfig = {}) {
15185
+ Object.assign(this.config, newConfig);
15186
+ return this;
15187
+ }
15188
+
15189
+ /**
15190
+ * Replaces the entire list of grammar templates.
15191
+ * @param {...string[]} templates - One or more arrays or strings containing sentence templates.
15192
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15193
+ */
15194
+ setGrammarTemplates(...templates) {
15195
+ this.config.grammar.templates = templates.flat();
15196
+ return this;
15197
+ }
15198
+
15199
+ /**
15200
+ * Adds new grammar templates to the existing list.
15201
+ * @param {...string[]} templates - One or more arrays or strings containing sentence templates.
15202
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15203
+ */
15204
+ addGrammarTemplates(...templates) {
15205
+ this.config.grammar.templates.push(...templates.flat());
15206
+ return this;
15207
+ }
15208
+
15209
+ /**
15210
+ * Replaces the list of noun words used in grammar templates.
15211
+ * @param {...string[]} nouns - One or more arrays or strings of nouns.
15212
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15213
+ */
15214
+ setGrammarNouns(...nouns) {
15215
+ this.config.grammar.nouns = nouns.flat();
15216
+ return this;
15217
+ }
15218
+
15219
+ /**
15220
+ * Adds noun words to the existing list used in grammar templates.
15221
+ * @param {...string[]} nouns - One or more arrays or strings of nouns.
15222
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15223
+ */
15224
+ addGrammarNouns(...nouns) {
15225
+ this.config.grammar.nouns.push(...nouns.flat());
15226
+ return this;
15227
+ }
15228
+
15229
+ /**
15230
+ * Replaces the list of verb words used in grammar templates.
15231
+ * @param {...string[]} verbs - One or more arrays or strings of verbs.
15232
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15233
+ */
15234
+ setGrammarVerbs(...verbs) {
15235
+ this.config.grammar.verbs = verbs.flat();
15236
+ return this;
15237
+ }
15238
+
15239
+ /**
15240
+ * Adds verb words to the existing list used in grammar templates.
15241
+ * @param {...string[]} verbs - One or more arrays or strings of verbs.
15242
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15243
+ */
15244
+ addGrammarVerbs(...verbs) {
15245
+ this.config.grammar.verbs.push(...verbs.flat());
15246
+ return this;
15247
+ }
15248
+
15249
+ /**
15250
+ * Replaces the list of adjective words used in grammar templates.
15251
+ * @param {...string[]} adjectives - One or more arrays or strings of adjectives.
15252
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15253
+ */
15254
+ setGrammarAdjectives(...adjectives) {
15255
+ this.config.grammar.adjectives = adjectives.flat();
15256
+ return this;
15257
+ }
15258
+
15259
+ /**
15260
+ * Adds adjective words to the existing list used in grammar templates.
15261
+ * @param {...string[]} adjectives - One or more arrays or strings of adjectives.
15262
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15263
+ */
15264
+ addGrammarAdjectives(...adjectives) {
15265
+ this.config.grammar.adjectives.push(...adjectives.flat());
15266
+ return this;
15267
+ }
15268
+
15269
+ /**
15270
+ * Removes specific grammar templates from the current list.
15271
+ * @param {...string[]} templates - One or more arrays or strings of templates to remove.
15272
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15273
+ */
15274
+ removeGrammarTemplates(...templates) {
15275
+ const removeSet = new Set(templates.flat());
15276
+ this.config.grammar.templates = this.config.grammar.templates.filter((t) => !removeSet.has(t));
15277
+ return this;
15278
+ }
15279
+
15280
+ /**
15281
+ * Removes specific noun words from the grammar noun list.
15282
+ * @param {...string[]} nouns - One or more arrays or strings of nouns to remove.
15283
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15284
+ */
15285
+ removeGrammarNouns(...nouns) {
15286
+ const removeSet = new Set(nouns.flat());
15287
+ this.config.grammar.nouns = this.config.grammar.nouns.filter((n) => !removeSet.has(n));
15288
+ return this;
15289
+ }
15290
+
15291
+ /**
15292
+ * Removes specific verb words from the grammar verb list.
15293
+ * @param {...string[]} verbs - One or more arrays or strings of verbs to remove.
15294
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15295
+ */
15296
+ removeGrammarVerbs(...verbs) {
15297
+ const removeSet = new Set(verbs.flat());
15298
+ this.config.grammar.verbs = this.config.grammar.verbs.filter((v) => !removeSet.has(v));
15299
+ return this;
15300
+ }
15301
+
15302
+ /**
15303
+ * Removes specific adjective words from the grammar adjective list.
15304
+ * @param {...string[]} adjectives - One or more arrays or strings of adjectives to remove.
15305
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15306
+ */
15307
+ removeGrammarAdjectives(...adjectives) {
15308
+ const removeSet = new Set(adjectives.flat());
15309
+ this.config.grammar.adjectives = this.config.grammar.adjectives.filter(
15310
+ (a) => !removeSet.has(a),
15311
+ );
15312
+ return this;
15313
+ }
15314
+
15315
+ /**
15316
+ * Replaces the entire word set used in readable/mixed modes.
15317
+ * @param {...string[]} words - One or more arrays or strings of words.
15318
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15319
+ */
15320
+ setWords(...words) {
15321
+ this.config.wordSet = words.flat();
15322
+ return this;
15323
+ }
15324
+
15325
+ /**
15326
+ * Adds new words to the word set used in readable/mixed modes.
15327
+ * @param {...string[]} words - One or more arrays or strings of words.
15328
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15329
+ */
15330
+ addWords(...words) {
15331
+ this.config.wordSet.push(...words.flat());
15332
+ return this;
15333
+ }
15334
+
15335
+ /**
15336
+ * Removes specific words from the word set.
15337
+ * @param {...string[]} words - Words to be removed.
15338
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15339
+ */
15340
+ removeWords(...words) {
15341
+ const removeSet = new Set(words.flat());
15342
+ this.config.wordSet = this.config.wordSet.filter((word) => !removeSet.has(word));
15343
+ return this;
15344
+ }
15345
+
15346
+ /**
15347
+ * Replaces the emoji set used in generated output.
15348
+ * @param {...string[]} emojis - One or more arrays or strings of emojis.
15349
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15350
+ */
15351
+ setEmojis(...emojis) {
15352
+ this.config.emojiSet = emojis.flat();
15353
+ return this;
15354
+ }
15355
+
15356
+ /**
15357
+ * Adds new emojis to the emoji set.
15358
+ * @param {...string[]} emojis - One or more arrays or strings of emojis.
15359
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15360
+ */
15361
+ addEmojis(...emojis) {
15362
+ this.config.emojiSet.push(...emojis.flat());
15363
+ return this;
15364
+ }
15365
+
15366
+ /**
15367
+ * Removes specific emojis from the emoji set.
15368
+ * @param {...string[]} emojis - Emojis to be removed.
15369
+ * @returns {UltraRandomMsgGen} - The instance for chaining.
15370
+ */
15371
+ removeEmojis(...emojis) {
15372
+ const removeSet = new Set(emojis.flat());
15373
+ this.config.emojiSet = this.config.emojiSet.filter((emoji) => !removeSet.has(emoji));
15374
+ return this;
15375
+ }
15376
+
15377
+ /**
15378
+ * Returns a random item from an array.
15379
+ * @private
15380
+ * @param {string[]} array - Array to pick from.
15381
+ * @returns {string} - Random item from array.
15382
+ */
15383
+ _getRandomItem(array) {
15384
+ return array[Math.floor(Math.random() * array.length)];
15385
+ }
15386
+
15387
+ /**
15388
+ * Generates a single random content chunk based on the mode and settings.
15389
+ * @private
15390
+ * @returns {string} - A chunk (word, emoji, symbol, number, etc.).
15391
+ */
15392
+ _generateChunk() {
15393
+ const {
15394
+ wordSet,
15395
+ emojiSet,
15396
+ includeNumbers,
15397
+ includeSymbols,
15398
+ useEmojis,
15399
+ readable,
15400
+ mode,
15401
+ emojiPlacement,
15402
+ } = this.config;
15403
+
15404
+ if (mode === 'natural') {
15405
+ return this._generateNaturalSentence();
15406
+ }
15407
+
15408
+ const pools = [];
15409
+
15410
+ if (readable || mode === 'readable' || mode === 'mixed') {
15411
+ pools.push(this._getRandomItem(wordSet));
15412
+ }
15413
+
15414
+ if (mode === 'chaotic' || mode === 'mixed') {
15415
+ pools.push(Math.random().toString(36).slice(2));
15416
+ }
15417
+
15418
+ if (includeNumbers) {
15419
+ pools.push(Math.floor(Math.random() * 99999).toString());
15420
+ }
15421
+
15422
+ if (includeSymbols) {
15423
+ pools.push(this._getRandomItem(this.symbols));
15424
+ }
15425
+
15426
+ if (emojiPlacement === 'inline' && useEmojis && emojiSet.length) {
15427
+ pools.push(this._getRandomItem(emojiSet));
15428
+ }
15429
+
15430
+ return this._getRandomItem(pools);
15431
+ }
15432
+
15433
+ /**
15434
+ * Generates a natural sentence by replacing placeholders in a template.
15435
+ * @private
15436
+ * @returns {string} - A generated sentence.
15437
+ */
15438
+ _generateNaturalSentence() {
15439
+ const { templates, nouns, verbs, adjectives } = this.config.grammar;
15440
+
15441
+ let template = this._getRandomItem(templates);
15442
+
15443
+ return template
15444
+ .replace(/{noun}/g, () => this._getRandomItem(nouns))
15445
+ .replace(/{verb}/g, () => this._getRandomItem(verbs))
15446
+ .replace(/{adj}/g, () => this._getRandomItem(adjectives));
15447
+ }
15448
+
15449
+ /**
15450
+ * Generates a single line of text with target length and rules.
15451
+ * @private
15452
+ * @param {number} targetLength - Target character length for the line.
15453
+ * @param {Set<string>} [seenWords] - Set of already used words (for avoiding repeats).
15454
+ * @returns {string} - A generated line.
15455
+ */
15456
+ _generateLine(targetLength, seenWords) {
15457
+ const { allowWeirdSpacing, repeatWords, readable, emojiSet, useEmojis, emojiPlacement, line } =
15458
+ this.config;
15459
+
15460
+ const parts = [];
15461
+ seenWords ??= new Set();
15462
+
15463
+ while (parts.join(' ').length < targetLength) {
15464
+ let chunk = this._generateChunk();
15465
+
15466
+ if (!repeatWords && seenWords.has(chunk)) continue;
15467
+ seenWords.add(chunk);
15468
+
15469
+ if (allowWeirdSpacing) {
15470
+ if (Math.random() < 0.1) chunk = '\n' + chunk;
15471
+ if (Math.random() < 0.1) chunk = ' ' + chunk;
15472
+ if (Math.random() < 0.05) chunk = chunk.toUpperCase();
15473
+ }
15474
+
15475
+ parts.push(chunk);
15476
+ }
15477
+
15478
+ let lineText = parts.join(readable ? ' ' : '');
15479
+
15480
+ if (
15481
+ emojiPlacement === 'end' &&
15482
+ useEmojis &&
15483
+ emojiSet.length &&
15484
+ Math.random() < (line?.emojiChance || 0)
15485
+ ) {
15486
+ lineText += ' ' + this._getRandomItem(emojiSet);
15487
+ }
15488
+
15489
+ return lineText;
15490
+ }
15491
+
15492
+ /**
15493
+ * Generates lines to form a paragraph based on total length.
15494
+ * @private
15495
+ * @param {number} totalLength - Total target character count for the paragraph.
15496
+ * @returns {string[]} - Array of lines that form the paragraph.
15497
+ */
15498
+ _generateParagraphLines(totalLength) {
15499
+ const { line } = this.config;
15500
+ const lines = [];
15501
+ const seenWords = new Set();
15502
+ let currentTotal = 0;
15503
+
15504
+ while (currentTotal < totalLength) {
15505
+ const lineLength = Math.floor(
15506
+ Math.random() * (line.maxLength - line.minLength + 1) + line.minLength,
15507
+ );
15508
+
15509
+ const adjustedLength = Math.min(lineLength, totalLength - currentTotal);
15510
+
15511
+ lines.push(this._generateLine(adjustedLength, seenWords));
15512
+ currentTotal += adjustedLength;
15513
+ }
15514
+
15515
+ return lines;
15516
+ }
15517
+
15518
+ /**
15519
+ * Generates the final random message, which can be a paragraph or block of lines.
15520
+ * Uses full configuration rules (grammar, symbols, emojis, etc).
15521
+ * @returns {string} - A full generated message.
15522
+ */
15523
+ generate() {
15524
+ const { minLength, maxLength, paragraphs } = this.config;
15525
+
15526
+ const totalLength = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
15527
+
15528
+ if (paragraphs && typeof paragraphs === 'object') {
15529
+ const paraCount =
15530
+ Math.floor(Math.random() * (paragraphs.max - paragraphs.min + 1)) + paragraphs.min;
15531
+ const approxLength = Math.floor(totalLength / paraCount);
15532
+ const paragraphsArray = [];
15533
+
15534
+ for (let i = 0; i < paraCount; i++) {
15535
+ const lines = this._generateParagraphLines(approxLength);
15536
+ paragraphsArray.push(lines.join('\n'));
15537
+ }
15538
+
15539
+ return paragraphsArray.join('\n\n');
15540
+ }
15541
+
15542
+ return this._generateParagraphLines(totalLength).join('\n');
15543
+ }
15544
+ }
15545
+
15546
+ /* harmony default export */ const libs_UltraRandomMsgGen = (UltraRandomMsgGen);
15547
+
15548
+ ;// ./src/v1/libs/TinySmartScroller.mjs
15549
+
15550
+
15551
+
15552
+ /**
15553
+ * Represents the dimensions of a DOM element.
15554
+ *
15555
+ * @typedef {Object} NodeSizes
15556
+ * @property {number} height - The height of the element in pixels.
15557
+ * @property {number} width - The width of the element in pixels.
15558
+ */
15559
+
15560
+ /**
15561
+ * A callback function that receives node size change data and optionally returns a modified NodeSizes object.
15562
+ *
15563
+ * @callback NodeSizesEvent
15564
+ * @param {Element} elem - The DOM element whose size is being tracked.
15565
+ * @param {{ old: NodeSizes, now: NodeSizes }} sizes - The old and new size measurements of the element.
15566
+ * @param {{ old: number, now: number }} elemAmount - The number of matching elements before and after the update.
15567
+ * @returns {NodeSizes|undefined} A modified NodeSizes object to override the default measurement, or undefined to use the original.
15568
+ */
15569
+
15570
+ /**
15571
+ * A generic scroll-related event listener callback function.
15572
+ *
15573
+ * @callback ScrollListenersFunc
15574
+ * @param {any} payload - The data payload passed when the scroll event is triggered. The type may vary depending on the event.
15575
+ * @returns {void}
15576
+ */
15577
+
15578
+ /**
15579
+ * TinySmartScroller is a utility class designed to enhance and manage scroll behaviors within containers or the window.
15580
+ *
15581
+ * It enables advanced scroll monitoring, auto-scrolling to bottom, preserving scroll position during DOM changes,
15582
+ * and detecting visibility changes of elements. This is particularly useful for dynamic UIs like chat applications,
15583
+ * feed viewers, or live content containers.
15584
+ *
15585
+ * Features:
15586
+ * - Detects when the scroll reaches the top, bottom, or custom boundaries
15587
+ * - Supports automatic scrolling to the bottom unless the user scrolls away
15588
+ * - Observes DOM mutations and resizes, and adapts scroll position accordingly
15589
+ * - Emits scroll-related events such as 'onScrollBoundary', 'onAutoScroll', and 'onScrollPause'
15590
+ * - Includes customizable scroll correction filters for layout shift mitigation
15591
+ * - Handles media element load events (e.g. `<img>`, `<iframe>`, `<video>`) to prevent sudden scroll jumps
15592
+ *
15593
+ * This class is **not framework-dependent** and works with vanilla DOM elements and the window object.
15594
+ */
15595
+ class TinySmartScroller {
15596
+ static Utils = { ...collision_namespaceObject, TinyHtml: libs_TinyHtml };
15597
+
15598
+ /** @type {WeakMap<Element, NodeSizes>} */
15599
+ #oldSizes = new WeakMap();
15600
+ /** @type {WeakMap<Element, NodeSizes>} */
15601
+ #newSizes = new WeakMap();
15602
+
15603
+ /** @type {WeakMap<Element, boolean>} */
15604
+ #newVisibles = new WeakMap();
15605
+ /** @type {WeakMap<Element, boolean>} */
15606
+ #oldVisibles = new WeakMap();
15607
+
15608
+ /** @type {WeakMap<Element, boolean>} */
15609
+ #newVisiblesByTime = new WeakMap();
15610
+ /** @type {WeakMap<Element, boolean>} */
15611
+ #oldVisiblesByTime = new WeakMap();
15612
+
15613
+ /** @type {Record<string, ScrollListenersFunc[]>} */
15614
+ #scrollListeners = {};
15615
+
15616
+ /** @type {ResizeObserver|null} */
15617
+ #resizeObserver = null;
15618
+ /** @type {MutationObserver|null} */
15619
+ #mutationObserver = null;
15620
+
15621
+ /** @type {Set<string>} */
15622
+ #loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
15623
+
15624
+ /** @type {null|EventListenerOrEventListenerObject} */
15625
+ #handler = null;
15626
+
15627
+ #isAtBottom = false;
15628
+ #isAtTop = false;
15629
+ #isAtCustomTop = false;
15630
+ #isAtCustomBottom = false;
15631
+
15632
+ #querySelector = '';
15633
+ #useWindow = false;
15634
+ #destroyed = false;
15635
+ #scrollPaused = false;
15636
+ #autoScrollBottom = false;
15637
+ #observeMutations = false;
15638
+ #preserveScrollOnLayoutShift = false;
15639
+ #debounceTime = 0;
15640
+ #elemAmount = 0;
15641
+ #elemOldAmount = 0;
15642
+ #lastKnownScrollBottomOffset = 0;
15643
+ #extraScrollBoundary = 0;
15644
+
15645
+ /** @type {Set<string>} */
15646
+ #attributeFilter;
15647
+
15648
+ /** @type {Element} */
15649
+ #target;
15650
+
15651
+ /** @type {Set<NodeSizesEvent>} */
15652
+ #sizeFilter = new Set();
15653
+
15654
+ /**
15655
+ * Creates a new instance of TinySmartScroller, attaching scroll and resize observers to manage
15656
+ * automatic scroll behaviors, layout shift correction, and visibility tracking.
15657
+ *
15658
+ * @param {Element|Window} target - The scroll container to monitor. Can be an element or `window`.
15659
+ * @param {Object} [options={}] - Optional settings to configure scroll behavior.
15660
+ * @param {number} [options.extraScrollBoundary=0] - Extra margin in pixels to extend scroll boundary detection.
15661
+ * @param {boolean} [options.autoScrollBottom=true] - Whether to auto-scroll to bottom on layout updates.
15662
+ * @param {boolean} [options.observeMutations=true] - Enables MutationObserver to detect DOM changes.
15663
+ * @param {boolean} [options.preserveScrollOnLayoutShift=true] - Prevents scroll jumps when layout changes.
15664
+ * @param {number} [options.debounceTime=100] - Debounce time in milliseconds for scroll events.
15665
+ * @param {string|null} [options.querySelector=null] - Optional CSS selector to filter observed child nodes.
15666
+ * @param {string[]|Set<string>|null} [options.attributeFilter=['class', 'style', 'src', 'data-*', 'height', 'width']]
15667
+ * - Which attributes to observe for changes.
15668
+ */
15669
+ constructor(
15670
+ target,
15671
+ {
15672
+ extraScrollBoundary = 0,
15673
+ autoScrollBottom = true,
15674
+ observeMutations = true,
15675
+ preserveScrollOnLayoutShift = true,
15676
+ debounceTime = 100,
15677
+ querySelector = null,
15678
+ attributeFilter = ['class', 'style', 'src', 'data-*', 'height', 'width'],
15679
+ } = {},
15680
+ ) {
15681
+ // === target ===
15682
+ if (!(target instanceof Element || target === window))
15683
+ throw new TypeError(
15684
+ `TinySmartScroller: 'target' must be a DOM Element or 'window', but got ${typeof target}`,
15685
+ );
15686
+ // === extraScrollBoundary ===
15687
+ if (typeof extraScrollBoundary !== 'number' || Number.isNaN(extraScrollBoundary))
15688
+ throw new TypeError(
15689
+ `TinySmartScroller: 'extraScrollBoundary' must be a valid number, received ${extraScrollBoundary}`,
15690
+ );
15691
+ // === autoScrollBottom ===
15692
+ if (typeof autoScrollBottom !== 'boolean')
15693
+ throw new TypeError(
15694
+ `TinySmartScroller: 'autoScrollBottom' must be a boolean, received ${typeof autoScrollBottom}`,
15695
+ );
15696
+ // === observeMutations ===
15697
+ if (typeof observeMutations !== 'boolean')
15698
+ throw new TypeError(
15699
+ `TinySmartScroller: 'observeMutations' must be a boolean, received ${typeof observeMutations}`,
15700
+ );
15701
+ // === preserveScrollOnLayoutShift ===
15702
+ if (typeof preserveScrollOnLayoutShift !== 'boolean')
15703
+ throw new TypeError(
15704
+ `TinySmartScroller: 'preserveScrollOnLayoutShift' must be a boolean, received ${typeof preserveScrollOnLayoutShift}`,
15705
+ );
15706
+ // === debounceTime ===
15707
+ if (typeof debounceTime !== 'number' || debounceTime < 0 || Number.isNaN(debounceTime))
15708
+ throw new TypeError(
15709
+ `TinySmartScroller: 'debounceTime' must be a non-negative number, received ${debounceTime}`,
15710
+ );
15711
+ // === querySelector ===
15712
+ if (querySelector !== null && typeof querySelector !== 'string')
15713
+ throw new TypeError(
15714
+ `TinySmartScroller: 'querySelector' must be a string or null, received ${typeof querySelector}`,
15715
+ );
15716
+ // === attributeFilter ===
15717
+ const isValidAttrList =
15718
+ attributeFilter === null || Array.isArray(attributeFilter) || attributeFilter instanceof Set;
15719
+ if (!isValidAttrList)
15720
+ throw new TypeError(
15721
+ `TinySmartScroller: 'attributeFilter' must be an array, Set, or null. Got ${typeof attributeFilter}`,
15722
+ );
15723
+
15724
+ // Start values
15725
+ this.#target = target instanceof Window ? document.documentElement : target;
15726
+ this.#useWindow = target instanceof Window;
15727
+ this.#autoScrollBottom = autoScrollBottom;
15728
+ this.#observeMutations = observeMutations;
15729
+ this.#preserveScrollOnLayoutShift = preserveScrollOnLayoutShift;
15730
+ this.#debounceTime = debounceTime;
15731
+ this.#extraScrollBoundary = extraScrollBoundary;
15732
+ this.#querySelector = querySelector || '';
15733
+ this.#attributeFilter = new Set(attributeFilter || undefined);
15734
+
15735
+ // Bind scroll
15736
+ /** @type {NodeJS.Timeout} */
15737
+ let timeout;
15738
+ this.#handler = () => {
15739
+ this._scrollDataUpdater();
15740
+ clearTimeout(timeout);
15741
+ timeout = setTimeout(() => this._onScroll(), this.#debounceTime);
15742
+ };
15743
+ (this.#useWindow ? window : this.#target).addEventListener('scroll', this.#handler, {
15744
+ passive: true,
15745
+ });
15746
+
15747
+ // Mutations
15748
+ if (this.#observeMutations) {
15749
+ this._observeMutations();
15750
+ this._observeResizes(this.#target.children);
15751
+ }
15752
+
15753
+ this._scrollDataUpdater();
15754
+ }
15755
+
15756
+ /**
15757
+ * Returns a size difference callback that only reacts when height changes, filtered by tag name.
15758
+ *
15759
+ * @param {string[]} filter - List of tag names to allow. If empty, all tags are accepted.
15760
+ * @returns {NodeSizesEvent} A function that compares previous and current height, returning height delta.
15761
+ */
15762
+ getSimpleOnHeight(filter = []) {
15763
+ if (!Array.isArray(filter))
15764
+ throw new TypeError('getSimpleOnHeight(filter): filter must be an array of tag names');
15765
+ return (elem, sizes, amounts) => {
15766
+ if ((filter.length > 0 && !filter.includes(elem.tagName)) || amounts.now !== amounts.old)
15767
+ return;
15768
+ const oldSize = sizes.old;
15769
+ const newSize = sizes.now;
15770
+ const height = newSize.height - oldSize.height;
15771
+ return { height, width: 0 };
15772
+ };
15773
+ }
15774
+
15775
+ /**
15776
+ * Adds a height difference callback to the size filter system.
15777
+ *
15778
+ * @param {string[]} filter - List of tag names to allow.
15779
+ * @returns {NodeSizesEvent} The added size difference callback.
15780
+ */
15781
+ addSimpleOnHeight(filter) {
15782
+ if (!Array.isArray(filter))
15783
+ throw new TypeError('addSimpleOnHeight(filter): filter must be an array of tag names');
15784
+ const result = this.getSimpleOnHeight(filter);
15785
+ this.onSize(result);
15786
+ return result;
15787
+ }
15788
+
15789
+ /**
15790
+ * Returns a list of all currently tracked load tags.
15791
+ *
15792
+ * @returns {string[]} Array of tag names.
15793
+ */
15794
+ getLoadTags() {
15795
+ return Array.from(this.#loadTags);
15796
+ }
15797
+
15798
+ /**
15799
+ * Adds a new tag to the set of load tags.
15800
+ *
15801
+ * @param {string} tag - The tag name to add (e.g., 'IMG').
15802
+ */
15803
+ addLoadTag(tag) {
15804
+ if (typeof tag !== 'string') throw new TypeError('addLoadTag(tag): tag must be a string');
15805
+ this.#loadTags.add(tag.toUpperCase());
15806
+ }
15807
+
15808
+ /**
15809
+ * Removes a tag from the set of load tags.
15810
+ *
15811
+ * @param {string} tag - The tag name to remove.
15812
+ */
15813
+ removeLoadTag(tag) {
15814
+ if (typeof tag !== 'string') throw new TypeError('removeLoadTag(tag): tag must be a string');
15815
+ this.#loadTags.delete(tag.toUpperCase());
15816
+ }
15817
+
15818
+ /**
15819
+ * Checks whether a tag is tracked as a load tag.
15820
+ *
15821
+ * @param {string} tag - The tag name to check.
15822
+ * @returns {boolean} True if the tag is being tracked.
15823
+ */
15824
+ hasLoadTag(tag) {
15825
+ if (typeof tag !== 'string') throw new TypeError('hasLoadTag(tag): tag must be a string');
15826
+ return this.#loadTags.has(tag.toUpperCase());
15827
+ }
15828
+
15829
+ /**
15830
+ * Clears the set of load tags. If `addDefault` is true, it will reset to the default tags: 'IMG', 'IFRAME', and 'VIDEO'.
15831
+ *
15832
+ * @param {boolean} [addDefault=false] - Whether to restore the default tags after clearing.
15833
+ * @throws {TypeError} If `addDefault` is not a boolean.
15834
+ */
15835
+ resetLoadTags(addDefault = false) {
15836
+ if (typeof addDefault !== 'boolean')
15837
+ throw new TypeError('resetLoadTags(addDefault): addDefault must be a boolean');
15838
+ this.#loadTags.clear();
15839
+ if (!addDefault) return;
15840
+ this.#loadTags.add('IMG');
15841
+ this.#loadTags.add('IFRAME');
15842
+ this.#loadTags.add('VIDEO');
15843
+ }
15844
+
15845
+ /**
15846
+ * Returns a list of all currently tracked attribute filters.
15847
+ *
15848
+ * @returns {string[]} Array of attribute names.
15849
+ */
15850
+ getAttributeFilters() {
15851
+ return Array.from(this.#attributeFilter);
15852
+ }
15853
+
15854
+ /**
15855
+ * Adds an attribute to the filter list.
15856
+ *
15857
+ * @param {string} attr - The attribute name to add.
15858
+ */
15859
+ addAttributeFilter(attr) {
15860
+ if (typeof attr !== 'string')
15861
+ throw new TypeError('addAttributeFilter(attr): attr must be a string');
15862
+ this.#attributeFilter.add(attr);
15863
+ }
15864
+
15865
+ /**
15866
+ * Removes an attribute from the filter list.
15867
+ *
15868
+ * @param {string} attr - The attribute name to remove.
15869
+ */
15870
+ removeAttributeFilter(attr) {
15871
+ if (typeof attr !== 'string')
15872
+ throw new TypeError('removeAttributeFilter(attr): attr must be a string');
15873
+ this.#attributeFilter.delete(attr);
15874
+ }
15875
+
15876
+ /**
15877
+ * Checks whether a specific attribute is being filtered.
15878
+ *
15879
+ * @param {string} attr - The attribute name to check.
15880
+ * @returns {boolean} True if the attribute is being filtered.
15881
+ */
15882
+ hasAttributeFilter(attr) {
15883
+ if (typeof attr !== 'string')
15884
+ throw new TypeError('hasAttributeFilter(attr): attr must be a string');
15885
+ return this.#attributeFilter.has(attr);
15886
+ }
15887
+
15888
+ /**
15889
+ * Clears the set of observed attribute filters. If `addDefault` is true, it will reset to the default attributes:
15890
+ * 'class', 'style', 'src', 'data-*', 'height', and 'width'.
15891
+ *
15892
+ * @param {boolean} [addDefault=false] - Whether to restore the default attribute filters after clearing.
15893
+ * @throws {TypeError} If `addDefault` is not a boolean.
15894
+ */
15895
+ resetAttributeFilters(addDefault = false) {
15896
+ if (typeof addDefault !== 'boolean')
15897
+ throw new TypeError('resetAttributeFilters(addDefault): addDefault must be a boolean');
15898
+ this.#attributeFilter.clear();
15899
+ if (!addDefault) return;
15900
+ ['class', 'style', 'src', 'data-*', 'height', 'width'].forEach((attr) =>
15901
+ this.#attributeFilter.add(attr),
15902
+ );
15903
+ }
15904
+
15905
+ /**
15906
+ * Registers a custom node size change handler to the internal size filter set.
15907
+ *
15908
+ * @param {NodeSizesEvent} handler - Function that compares old and new sizes.
15909
+ */
15910
+ onSize(handler) {
15911
+ if (this.#destroyed) return;
15912
+ if (typeof handler !== 'function')
15913
+ throw new TypeError('onSize(handler): handler must be a function');
15914
+ this.#sizeFilter.add(handler);
15915
+ }
15916
+
15917
+ /**
15918
+ * Unregisters a previously registered size handler from the internal filter set.
15919
+ *
15920
+ * @param {NodeSizesEvent} handler - The handler function to remove.
15921
+ */
15922
+ offSize(handler) {
15923
+ if (this.#destroyed) return;
15924
+ if (typeof handler !== 'function')
15925
+ throw new TypeError('offSize(handler): handler must be a function');
15926
+ this.#sizeFilter.delete(handler);
15927
+ }
15928
+
15929
+ /**
15930
+ * Adds a scroll-related event listener.
15931
+ *
15932
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15933
+ * @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
15934
+ */
15935
+ on(event, handler) {
15936
+ if (this.#destroyed) return;
15937
+ if (typeof event !== 'string')
15938
+ throw new TypeError('on(event, handler): event name must be a string');
15939
+ if (typeof handler !== 'function')
15940
+ throw new TypeError('on(event, handler): handler must be a function');
15941
+
15942
+ if (!this.#scrollListeners[event]) this.#scrollListeners[event] = [];
15943
+ this.#scrollListeners[event].push(handler);
15944
+ }
15945
+
15946
+ /**
15947
+ * Removes a previously registered scroll-related event listener.
15948
+ *
15949
+ * @param {string} event - The name of the event to remove the handler from.
15950
+ * @param {ScrollListenersFunc} handler - The specific callback function to remove.
15951
+ */
15952
+ off(event, handler) {
15953
+ if (this.#destroyed) return;
15954
+ if (typeof event !== 'string')
15955
+ throw new TypeError('off(event, handler): event name must be a string');
15956
+ if (typeof handler !== 'function')
15957
+ throw new TypeError('off(event, handler): handler must be a function');
15958
+
15959
+ const listeners = this.#scrollListeners[event];
15960
+ if (!Array.isArray(listeners)) return;
15961
+
15962
+ const index = listeners.indexOf(handler);
15963
+ if (index !== -1) listeners.splice(index, 1);
15964
+
15965
+ // Optionally clean up empty arrays (optional)
15966
+ if (listeners.length === 0) delete this.#scrollListeners[event];
15967
+ }
15968
+
15969
+ /**
15970
+ * Checks which elements inside the target are currently visible and updates internal maps.
15971
+ *
15972
+ * @returns {Map<Element, { oldIsVisible: boolean; isVisible: boolean }>} Visibility comparison results.
15973
+ */
15974
+ _scrollDataUpdater() {
15975
+ const results = new Map();
15976
+ this.#target.querySelectorAll(this.#querySelector || '*').forEach((target) => {
15977
+ const oldIsVisible = this.#newVisibles.get(target) ?? false;
15978
+ this.#oldVisibles.set(target, oldIsVisible);
15979
+
15980
+ const isVisible = libs_TinyHtml.isInContainer(this.#target, target);
15981
+ this.#newVisibles.set(target, isVisible);
15982
+
15983
+ results.set(target, { oldIsVisible, isVisible });
15984
+ });
15985
+ return results;
15986
+ }
15987
+
15988
+ /**
15989
+ * Emits a scroll-related event to all registered listeners.
15990
+ *
15991
+ * @param {string} event - Event name.
15992
+ * @param {*} [payload] - Optional event data payload.
15993
+ */
15994
+ _emit(event, payload) {
15995
+ if (this.#destroyed) return;
15996
+ if (typeof event !== 'string')
15997
+ throw new TypeError('_emit(event, payload): event name must be a string');
15998
+ (this.#scrollListeners[event] || []).forEach((fn) => fn(payload));
15999
+ }
16000
+
16001
+ /**
16002
+ * Handles scroll events, calculates position-related statuses, and emits appropriate events.
16003
+ */
16004
+ _onScroll() {
16005
+ if (this.#destroyed) return;
16006
+ // Get values
16007
+ const scrollCache = this._scrollDataUpdater();
16008
+ const el = this.#target;
16009
+ const scrollTop = el.scrollTop;
16010
+ const scrollHeight = el.scrollHeight;
16011
+ const clientHeight = el.clientHeight;
16012
+
16013
+ // Prepare sroll values
16014
+ const scrollResult = { scrollTop, scrollHeight, clientHeight };
16015
+ let atResult = null;
16016
+ let atCustomResult = null;
16017
+
16018
+ const atTop = scrollTop === 0;
16019
+ const atBottom = scrollTop + clientHeight >= scrollHeight - 1;
16020
+
16021
+ const atCustomTop = scrollTop <= 0 + this.#extraScrollBoundary;
16022
+ const atCustomBottom = scrollTop + clientHeight >= scrollHeight - 1 - this.#extraScrollBoundary;
16023
+
16024
+ // Scroll results
16025
+ if (atTop && atBottom) atResult = 'all';
16026
+ else if (atTop) atResult = 'top';
16027
+ else if (atBottom) atResult = 'bottom';
16028
+
16029
+ if (atCustomTop && atCustomBottom) atCustomResult = 'all';
16030
+ else if (atCustomTop) atCustomResult = 'top';
16031
+ else if (atCustomBottom) atCustomResult = 'bottom';
16032
+
16033
+ this.#isAtTop = atTop;
16034
+ this.#isAtBottom = atBottom;
16035
+
16036
+ this.#isAtCustomTop = atCustomTop;
16037
+ this.#isAtCustomBottom = atCustomBottom;
16038
+
16039
+ this.#scrollPaused = !(this.#autoScrollBottom && this.#isAtBottom);
16040
+
16041
+ this.#lastKnownScrollBottomOffset = scrollHeight - scrollTop - clientHeight;
16042
+
16043
+ // Send results
16044
+ this._emit('onScrollBoundary', { status: atResult, ...scrollResult, scrollCache });
16045
+ this._emit('onExtraScrollBoundary', { status: atCustomResult, ...scrollResult, scrollCache });
16046
+
16047
+ if (!this.#scrollPaused) {
16048
+ this._emit('onAutoScroll', { ...scrollResult, scrollCache });
16049
+ } else {
16050
+ this._emit('onScrollPause', { ...scrollResult, scrollCache });
16051
+ }
16052
+ }
16053
+
16054
+ /**
16055
+ * Attempts to correct the scroll position when layout shifts happen, preserving the user position if needed.
16056
+ *
16057
+ * @param {Element[]} [targets=[]] - List of elements involved in the size change.
16058
+ */
16059
+ _fixScroll(targets = []) {
16060
+ if (this.#destroyed) return;
16061
+ // === Validation ===
16062
+ if (!Array.isArray(targets))
16063
+ throw new TypeError('_fixScroll: targets must be an array of Elements');
16064
+
16065
+ // Get Scroll data
16066
+ const prevScrollHeight = this.#target.scrollHeight;
16067
+ const prevScrollTop = this.#target.scrollTop;
16068
+ const prevBottomOffset =
16069
+ this.#target.scrollHeight - this.#target.scrollTop - this.#target.clientHeight;
16070
+
16071
+ // Get new size
16072
+ const newScrollHeight = this.#target.scrollHeight;
16073
+ const heightDelta = newScrollHeight - prevScrollHeight;
16074
+
16075
+ /** @type {() => NodeSizes} */
16076
+ const calculateScrollSize = () => {
16077
+ // Run size getter
16078
+ const scrollSize = { height: 0, width: 0 };
16079
+ for (const target of targets) {
16080
+ const tgOs = this.#oldSizes.get(target) || { height: 0, width: 0 };
16081
+ const tgNs = this.#newSizes.get(target) || { height: 0, width: 0 };
16082
+ this.#sizeFilter.forEach((fn) => {
16083
+ /** @type {NodeSizes| undefined} */
16084
+ const sizes = fn(
16085
+ target,
16086
+ { old: tgOs, now: tgNs },
16087
+ { old: this.#elemOldAmount, now: this.#elemAmount },
16088
+ );
16089
+
16090
+ // Fix size
16091
+ if (this.#newVisibles.get(target) || this.#newVisiblesByTime.get(target)) {
16092
+ if (typeof sizes !== 'undefined' && typeof sizes !== 'object')
16093
+ throw new Error('_fixScroll: size filter must return an object or undefined');
16094
+ if (typeof sizes === 'undefined') return;
16095
+ scrollSize.height = sizes.height;
16096
+ scrollSize.width = sizes.width;
16097
+ }
16098
+ });
16099
+ }
16100
+
16101
+ // Checker
16102
+ if (typeof scrollSize.height !== 'number' && scrollSize.height < 0)
16103
+ throw new Error('_fixScroll: invalid scrollSize.height value');
16104
+ if (typeof scrollSize.width !== 'number' && scrollSize.width < 0)
16105
+ throw new Error('_fixScroll: invalid scrollSize.width value');
16106
+
16107
+ if (scrollSize.height !== 0 || scrollSize.width !== 0) {
16108
+ for (const target of targets) {
16109
+ this.#newVisiblesByTime.set(target, this.#newVisibles.get(target) ?? false);
16110
+ this.#oldVisiblesByTime.set(target, this.#oldVisibles.get(target) ?? false);
16111
+ }
16112
+ }
16113
+
16114
+ return scrollSize;
16115
+ };
16116
+
16117
+ // Fix scroll size
16118
+ if (
16119
+ this.#elemOldAmount > 0 &&
16120
+ libs_TinyHtml.hasScroll(this.#target).v &&
16121
+ this.#autoScrollBottom &&
16122
+ this.#preserveScrollOnLayoutShift &&
16123
+ !this.#isAtBottom &&
16124
+ !this.#isAtTop
16125
+ ) {
16126
+ const scrollSize = calculateScrollSize();
16127
+ // Complete
16128
+ this.#target.scrollTop = prevScrollTop + heightDelta + scrollSize.height;
16129
+ if (scrollSize.width > 0)
16130
+ this.#target.scrollLeft = this.#target.scrollLeft + scrollSize.width;
16131
+ }
16132
+
16133
+ // Normal stuff
16134
+ else if (!this.#scrollPaused && this.#autoScrollBottom) {
16135
+ calculateScrollSize();
16136
+ this.scrollToBottom();
16137
+ } else if (!this.#autoScrollBottom && !this.#isAtBottom) {
16138
+ calculateScrollSize();
16139
+ this.#target.scrollTop =
16140
+ this.#target.scrollHeight - this.#target.clientHeight - prevBottomOffset;
16141
+ }
16142
+ }
16143
+ /**
16144
+ * Sets up a MutationObserver to watch for DOM changes and react accordingly to maintain scroll consistency.
16145
+ */
16146
+ _observeMutations() {
16147
+ this.#mutationObserver = new MutationObserver((mutations) => {
16148
+ if (this.#destroyed) return;
16149
+ this._scrollDataUpdater();
16150
+ this.#elemOldAmount = this.#elemAmount;
16151
+ this.#elemAmount = this.#target.childElementCount;
16152
+
16153
+ mutations.forEach((mutation) => {
16154
+ mutation.addedNodes.forEach((node) => {
16155
+ if (!(node instanceof Element) || node.nodeType !== 1) return;
16156
+
16157
+ this._observeResizes([node]);
16158
+ this._listenLoadEvents(node);
16159
+
16160
+ if (this.#querySelector) {
16161
+ const children = node.querySelectorAll(this.#querySelector);
16162
+ this._observeResizes(children);
16163
+ this._listenLoadEvents(children);
16164
+ }
16165
+ });
16166
+ });
16167
+
16168
+ this._fixScroll();
16169
+ });
16170
+
16171
+ // Install observer
16172
+ this.#mutationObserver.observe(this.#target, {
16173
+ childList: true,
16174
+ subtree: true,
16175
+ attributes: true,
16176
+ attributeFilter:
16177
+ this.#attributeFilter.size > 0 ? Array.from(this.#attributeFilter) : undefined,
16178
+ });
16179
+ }
16180
+
16181
+ /**
16182
+ * Adds a ResizeObserver to monitor elements' size changes and trigger layout adjustments.
16183
+ *
16184
+ * @param {NodeListOf<Element>|Element[]|HTMLCollection} elements - Elements to observe.
16185
+ */
16186
+ _observeResizes(elements) {
16187
+ // Add resize observer
16188
+ if (!this.#resizeObserver) {
16189
+ this.#resizeObserver = new ResizeObserver((entries) => {
16190
+ if (this.#destroyed) return;
16191
+ this._scrollDataUpdater();
16192
+ /** @type {Element[]} */
16193
+ const targets = [];
16194
+ for (const entry of entries) {
16195
+ // Target
16196
+ const target = entry.target;
16197
+
16198
+ // Update old size
16199
+ const oldSize = this.#newSizes.get(target);
16200
+ if (oldSize) this.#oldSizes.set(target, oldSize);
16201
+
16202
+ // Set new size
16203
+ const { width, height } = entry.contentRect;
16204
+ this.#newSizes.set(target, { width, height });
16205
+ targets.push(target);
16206
+ }
16207
+
16208
+ // Animation frame
16209
+ this._fixScroll(targets);
16210
+ });
16211
+ }
16212
+
16213
+ // Execute observer
16214
+ Array.from(elements).forEach((el) => {
16215
+ if (!this.#resizeObserver)
16216
+ throw new Error('_observeResizes: ResizeObserver instance is not initialized');
16217
+ this.#resizeObserver.observe(el);
16218
+ });
16219
+ }
16220
+
16221
+ /**
16222
+ * Listens for media/content load events (e.g., images, iframes, videos) to trigger scroll updates.
16223
+ *
16224
+ * @param {NodeListOf<Element>|Element} elements - Target element(s) to listen on.
16225
+ */
16226
+ _listenLoadEvents(elements) {
16227
+ if (this.#destroyed) return;
16228
+ const list = elements instanceof NodeList ? Array.from(elements) : [elements];
16229
+
16230
+ list.forEach((el) => {
16231
+ if (this.#loadTags.has(el.tagName)) {
16232
+ // @ts-ignore
16233
+ if (!el.complete) {
16234
+ el.addEventListener('load', () => {
16235
+ this._scrollDataUpdater();
16236
+ if (!this.#scrollPaused && this.#autoScrollBottom) {
16237
+ this.scrollToBottom();
16238
+ }
16239
+ });
16240
+ }
16241
+ }
16242
+ });
16243
+ }
16244
+
16245
+ /**
16246
+ * Returns the internal scroll container element being monitored.
16247
+ *
16248
+ * @returns {Element} The DOM element used as the scroll container target.
16249
+ */
16250
+ get target() {
16251
+ return this.#target;
16252
+ }
16253
+
16254
+ /**
16255
+ * Returns the previous size of a given element, or undefined if not tracked.
16256
+ *
16257
+ * @param {Element} el - The DOM element to query.
16258
+ * @returns {NodeSizes|null} The old size, or undefined.
16259
+ */
16260
+ getOldSize(el) {
16261
+ return this.#oldSizes.get(el) ?? null;
16262
+ }
16263
+
16264
+ /**
16265
+ * Returns the current size of a given element, or undefined if not tracked.
16266
+ *
16267
+ * @param {Element} el - The DOM element to query.
16268
+ * @returns {NodeSizes|null} The new size, or undefined.
16269
+ */
16270
+ getNewSize(el) {
16271
+ return this.#newSizes.get(el) ?? null;
16272
+ }
16273
+
16274
+ /**
16275
+ * Returns whether the given element was visible in the last scroll update.
16276
+ *
16277
+ * @param {Element} el - The DOM element to check.
16278
+ * @returns {boolean} True if visible, false if not, or undefined if not tracked.
16279
+ */
16280
+ wasVisible(el) {
16281
+ return this.#oldVisibles.get(el) ?? false;
16282
+ }
16283
+
16284
+ /**
16285
+ * Returns whether the given element is currently visible.
16286
+ *
16287
+ * @param {Element} el - The DOM element to check.
16288
+ * @returns {boolean} True if visible, false if not, or undefined if not tracked.
16289
+ */
16290
+ isVisible(el) {
16291
+ return this.#newVisibles.get(el) ?? false;
16292
+ }
16293
+
16294
+ /**
16295
+ * Returns whether the element was visible in the last time-based visibility check.
16296
+ *
16297
+ * @param {Element} el - The DOM element to check.
16298
+ * @returns {boolean} Visibility state from the previous timed check.
16299
+ */
16300
+ wasTimedVisible(el) {
16301
+ return this.#oldVisiblesByTime.get(el) ?? false;
16302
+ }
16303
+
16304
+ /**
16305
+ * Returns whether the element is currently visible in the time-based check.
16306
+ *
16307
+ * @param {Element} el - The DOM element to check.
16308
+ * @returns {boolean} Visibility state from the current timed check.
16309
+ */
16310
+ isTimedVisible(el) {
16311
+ return this.#newVisiblesByTime.get(el) ?? false;
16312
+ }
16313
+
16314
+ /**
16315
+ * Sets the extra scroll boundary margin used when determining if the user is at a "custom" bottom or top.
16316
+ *
16317
+ * @param {number} value - Pixels of additional margin to use.
16318
+ */
16319
+ setExtraScrollBoundary(value) {
16320
+ if (typeof value !== 'number' || Number.isNaN(value))
16321
+ throw new TypeError('setExtraScrollBoundary(value): value must be a valid number');
16322
+ this.#extraScrollBoundary = value;
16323
+ }
16324
+
16325
+ /**
16326
+ * Returns the current extra scroll boundary setting.
16327
+ *
16328
+ * @returns {number}
16329
+ */
16330
+ getExtraScrollBoundary() {
16331
+ return this.#extraScrollBoundary;
16332
+ }
16333
+
16334
+ /**
16335
+ * Returns the last known distance (in pixels) from the bottom of the scroll container.
16336
+ *
16337
+ * @returns {number}
16338
+ */
16339
+ getLastKnownScrollBottomOffset() {
16340
+ return this.#lastKnownScrollBottomOffset;
16341
+ }
16342
+
16343
+ /**
16344
+ * Forces the scroll position to move to the very bottom of the target.
16345
+ */
16346
+ scrollToBottom() {
16347
+ this.#target.scrollTop = this.#target.scrollHeight;
16348
+ }
16349
+
16350
+ /**
16351
+ * Forces the scroll position to move to the very top of the target.
16352
+ */
16353
+ scrollToTop() {
16354
+ this.#target.scrollTop = 0;
16355
+ }
16356
+
16357
+ /**
16358
+ * Checks if the user is within the defined extra scroll boundary from the bottom.
16359
+ *
16360
+ * @returns {boolean}
16361
+ */
16362
+ isUserAtCustomBottom() {
16363
+ return this.#isAtCustomBottom;
16364
+ }
16365
+
16366
+ /**
16367
+ * Checks if the user is within the defined extra scroll boundary from the top.
16368
+ *
16369
+ * @returns {boolean}
16370
+ */
16371
+ isUserAtCustomTop() {
16372
+ return this.#isAtCustomTop;
16373
+ }
16374
+
16375
+ /**
16376
+ * Returns true if the user is currently scrolled to the bottom of the element.
16377
+ *
16378
+ * @returns {boolean}
16379
+ */
16380
+ isUserAtBottom() {
16381
+ return this.#isAtBottom;
16382
+ }
16383
+
16384
+ /**
16385
+ * Returns true if the user is currently scrolled to the top of the element.
16386
+ *
16387
+ * @returns {boolean}
16388
+ */
16389
+ isUserAtTop() {
16390
+ return this.#isAtTop;
16391
+ }
16392
+
16393
+ /**
16394
+ * Returns true if automatic scrolling is currently paused.
16395
+ *
16396
+ * @returns {boolean}
16397
+ */
16398
+ isScrollPaused() {
16399
+ return this.#scrollPaused;
16400
+ }
16401
+
16402
+ /**
16403
+ * Returns whether the target is the window object.
16404
+ *
16405
+ * @returns {boolean} True if the scroll target is window, false otherwise.
16406
+ */
16407
+ isWindow() {
16408
+ return this.#useWindow;
16409
+ }
16410
+
16411
+ /**
16412
+ * Returns whether the instance has been destroyed.
16413
+ *
16414
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
16415
+ */
16416
+ isDestroyed() {
16417
+ return this.#destroyed;
16418
+ }
16419
+
16420
+ /**
16421
+ * Returns whether auto-scroll-to-bottom is enabled.
16422
+ *
16423
+ * @returns {boolean} True if auto-scroll is active, false otherwise.
16424
+ */
16425
+ getAutoScrollBottom() {
16426
+ return this.#autoScrollBottom;
16427
+ }
16428
+
16429
+ /**
16430
+ * Returns whether MutationObserver is enabled.
16431
+ *
16432
+ * @returns {boolean} True if mutation observation is active, false otherwise.
16433
+ */
16434
+ getObserveMutations() {
16435
+ return this.#observeMutations;
16436
+ }
16437
+
16438
+ /**
16439
+ * Returns whether layout shift protection is enabled.
16440
+ *
16441
+ * @returns {boolean} True if scroll preservation is active, false otherwise.
16442
+ */
16443
+ getPreserveScrollOnLayoutShift() {
16444
+ return this.#preserveScrollOnLayoutShift;
16445
+ }
16446
+
16447
+ /**
16448
+ * Returns the debounce delay in milliseconds used for scroll events.
16449
+ *
16450
+ * @returns {number} Debounce delay time.
16451
+ */
16452
+ getDebounceTime() {
16453
+ return this.#debounceTime;
16454
+ }
16455
+
16456
+ /**
16457
+ * Returns the current number of matching elements observed inside the scroll target.
16458
+ *
16459
+ * @returns {number} Current count of matching elements.
16460
+ */
16461
+ getElemAmount() {
16462
+ return this.#elemAmount;
16463
+ }
16464
+
16465
+ /**
16466
+ * Returns the previous known count of matching elements from the last update.
16467
+ *
16468
+ * @returns {number} Previous count of matching elements.
16469
+ */
16470
+ getPrevElemAmount() {
16471
+ return this.#elemOldAmount;
16472
+ }
16473
+
16474
+ /**
16475
+ * Returns the query selector string used to filter observed elements.
16476
+ *
16477
+ * @returns {string} The CSS selector string, or an empty string if none was provided.
16478
+ */
16479
+ getQuerySelector() {
16480
+ return this.#querySelector;
16481
+ }
16482
+
16483
+ /**
16484
+ * Disconnects all listeners, observers, and clears memory structures.
16485
+ * Once destroyed, this instance should no longer be used.
16486
+ */
16487
+ destroy() {
16488
+ if (this.#destroyed) return;
16489
+ this.#destroyed = true;
16490
+
16491
+ // Disconnects MutationObserver
16492
+ if (this.#mutationObserver) {
16493
+ this.#mutationObserver.disconnect();
16494
+ this.#mutationObserver = null;
16495
+ }
16496
+
16497
+ // Disconnect ResizeObserver
16498
+ if (this.#resizeObserver) {
16499
+ this.#resizeObserver.disconnect();
16500
+ this.#resizeObserver = null;
16501
+ }
16502
+
16503
+ // Removes scroll listener
16504
+ const target = this.#useWindow ? window : this.#target;
16505
+ if (this.#handler) target.removeEventListener('scroll', this.#handler);
16506
+
16507
+ // Clean the WeakMaps
16508
+ this.#oldSizes = new WeakMap();
16509
+ this.#newSizes = new WeakMap();
16510
+ this.#newVisibles = new WeakMap();
16511
+ this.#oldVisibles = new WeakMap();
16512
+ this.#newVisiblesByTime = new WeakMap();
16513
+ this.#oldVisiblesByTime = new WeakMap();
16514
+
16515
+ // Cleans listeners and filters
16516
+ this.#scrollListeners = {};
16517
+ this.#sizeFilter.clear();
16518
+ this.#loadTags.clear();
16519
+ }
16520
+ }
16521
+
16522
+ /* harmony default export */ const libs_TinySmartScroller = (TinySmartScroller);
16523
+
16524
+ ;// ./src/v1/index.mjs
16525
+
16526
+
13955
16527
 
13956
16528
 
13957
16529