tiny-essentials 1.28.0 → 1.28.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 (168) hide show
  1. package/changelog/1/28/1.md +23 -0
  2. package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
  3. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
  4. package/dist/v1/TinyAnalogClock.min.js +1 -1
  5. package/dist/v1/TinyBasicsEs.min.js +1 -1
  6. package/dist/v1/TinyDayNightCycle.min.js +1 -1
  7. package/dist/v1/TinyDragDropDetector.min.js +1 -1
  8. package/dist/v1/TinyDragger.min.js +1 -1
  9. package/dist/v1/TinyElementObserver.min.js +1 -1
  10. package/dist/v1/TinyEssentials.min.js +1 -1
  11. package/dist/v1/TinyGamepad.min.js +1 -1
  12. package/dist/v1/TinyHtml.min.js +1 -1
  13. package/dist/v1/TinyI18.min.js +1 -1
  14. package/dist/v1/TinyIframeEvents.min.js +1 -1
  15. package/dist/v1/TinyLocalStorage.min.js +1 -1
  16. package/dist/v1/TinyMediaPlayer.min.js +1 -1
  17. package/dist/v1/TinyNewWinEvents.min.js +1 -1
  18. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  19. package/dist/v1/TinyRadioFm.min.js +1 -1
  20. package/dist/v1/TinyRateLimiter.min.js +1 -1
  21. package/dist/v1/TinySmartScroller.min.js +1 -1
  22. package/dist/v1/TinyTextDiffer.min.js +1 -1
  23. package/dist/v1/TinyTextarea.min.js +1 -1
  24. package/dist/v1/TinyTimeout.min.js +1 -1
  25. package/dist/v1/TinyToastNotify.min.js +1 -1
  26. package/dist/v1/TinyUploadClicker.min.js +1 -1
  27. package/dist/v1/UltraRandomMsgGen.min.js +1 -1
  28. package/dist/v1/basics/array.cjs +57 -9
  29. package/dist/v1/basics/array.d.mts +27 -11
  30. package/dist/v1/basics/array.mjs +51 -9
  31. package/dist/v1/basics/clock.cjs +94 -42
  32. package/dist/v1/basics/clock.d.mts +10 -10
  33. package/dist/v1/basics/clock.mjs +91 -54
  34. package/dist/v1/basics/deprecated/html.cjs +246 -0
  35. package/dist/v1/basics/deprecated/html.d.mts +63 -0
  36. package/dist/v1/basics/deprecated/html.mjs +190 -0
  37. package/dist/v1/basics/html.cjs +149 -130
  38. package/dist/v1/basics/html.d.mts +26 -61
  39. package/dist/v1/basics/html.mjs +113 -105
  40. package/dist/v1/basics/index.cjs +2 -0
  41. package/dist/v1/basics/index.d.mts +3 -1
  42. package/dist/v1/basics/index.mjs +3 -3
  43. package/dist/v1/basics/mediaContent.cjs +51 -9
  44. package/dist/v1/basics/mediaContent.d.mts +0 -87
  45. package/dist/v1/basics/mediaContent.mjs +40 -10
  46. package/dist/v1/basics/objChecker.cjs +7 -5
  47. package/dist/v1/basics/objChecker.d.mts +3 -2
  48. package/dist/v1/basics/objChecker.mjs +6 -6
  49. package/dist/v1/basics/objFilter.cjs +40 -7
  50. package/dist/v1/basics/objFilter.d.mts +6 -6
  51. package/dist/v1/basics/objFilter.mjs +32 -7
  52. package/dist/v1/basics/simpleMath.cjs +81 -25
  53. package/dist/v1/basics/simpleMath.d.mts +2 -2
  54. package/dist/v1/basics/simpleMath.mjs +70 -27
  55. package/dist/v1/basics/text.cjs +76 -31
  56. package/dist/v1/basics/text.d.mts +69 -7
  57. package/dist/v1/basics/text.mjs +62 -31
  58. package/dist/v1/index.cjs +4 -0
  59. package/dist/v1/index.d.mts +4 -1
  60. package/dist/v1/index.mjs +4 -3
  61. package/dist/v1/libs/TinyAdvancedRaffle.cjs +64 -50
  62. package/dist/v1/libs/TinyAdvancedRaffle.d.mts +4 -13
  63. package/dist/v1/libs/TinyAdvancedRaffle.mjs +62 -49
  64. package/dist/v1/libs/TinyAfterScrollWatcher.cjs +17 -1
  65. package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +4 -1
  66. package/dist/v1/libs/TinyAfterScrollWatcher.mjs +15 -1
  67. package/dist/v1/libs/TinyAnalogClock.cjs +60 -0
  68. package/dist/v1/libs/TinyAnalogClock.d.mts +6 -0
  69. package/dist/v1/libs/TinyAnalogClock.mjs +57 -0
  70. package/dist/v1/libs/TinyClipboard.cjs +2 -0
  71. package/dist/v1/libs/TinyClipboard.d.mts +4 -2
  72. package/dist/v1/libs/TinyClipboard.mjs +2 -0
  73. package/dist/v1/libs/TinyColorConverter.cjs +1 -0
  74. package/dist/v1/libs/TinyColorConverter.d.mts +2 -1
  75. package/dist/v1/libs/TinyColorConverter.mjs +1 -0
  76. package/dist/v1/libs/TinyDayNightCycle.cjs +67 -42
  77. package/dist/v1/libs/TinyDayNightCycle.d.mts +2 -7
  78. package/dist/v1/libs/TinyDayNightCycle.mjs +65 -41
  79. package/dist/v1/libs/TinyDragDropDetector.cjs +25 -0
  80. package/dist/v1/libs/TinyDragDropDetector.d.mts +6 -0
  81. package/dist/v1/libs/TinyDragDropDetector.mjs +22 -0
  82. package/dist/v1/libs/TinyDragger.cjs +46 -19
  83. package/dist/v1/libs/TinyDragger.mjs +45 -18
  84. package/dist/v1/libs/TinyElementObserver.cjs +39 -0
  85. package/dist/v1/libs/TinyElementObserver.d.mts +6 -0
  86. package/dist/v1/libs/TinyElementObserver.mjs +36 -0
  87. package/dist/v1/libs/TinyGamepad.cjs +131 -1
  88. package/dist/v1/libs/TinyGamepad.mjs +129 -1
  89. package/dist/v1/libs/TinyHtml.cjs +1 -1
  90. package/dist/v1/libs/TinyHtml.mjs +2 -2
  91. package/dist/v1/libs/TinyI18.cjs +61 -0
  92. package/dist/v1/libs/TinyI18.d.mts +14 -0
  93. package/dist/v1/libs/TinyI18.mjs +55 -0
  94. package/dist/v1/libs/TinyIframeEvents.cjs +18 -1
  95. package/dist/v1/libs/TinyIframeEvents.mjs +18 -1
  96. package/dist/v1/libs/TinyLocalStorage.cjs +48 -0
  97. package/dist/v1/libs/TinyLocalStorage.d.mts +6 -0
  98. package/dist/v1/libs/TinyLocalStorage.mjs +45 -0
  99. package/dist/v1/libs/TinyMediaPlayer.cjs +72 -0
  100. package/dist/v1/libs/TinyMediaPlayer.d.mts +10 -0
  101. package/dist/v1/libs/TinyMediaPlayer.mjs +66 -0
  102. package/dist/v1/libs/TinyNewWinEvents.cjs +29 -0
  103. package/dist/v1/libs/TinyNewWinEvents.d.mts +6 -0
  104. package/dist/v1/libs/TinyNewWinEvents.mjs +26 -0
  105. package/dist/v1/libs/TinyNotifyCenter.cjs +36 -0
  106. package/dist/v1/libs/TinyNotifyCenter.d.mts +6 -0
  107. package/dist/v1/libs/TinyNotifyCenter.mjs +32 -0
  108. package/dist/v1/libs/TinyRadioFm.cjs +38 -0
  109. package/dist/v1/libs/TinyRadioFm.d.mts +1 -0
  110. package/dist/v1/libs/TinyRadioFm.mjs +35 -0
  111. package/dist/v1/libs/TinyRateLimiter.cjs +52 -0
  112. package/dist/v1/libs/TinyRateLimiter.d.mts +6 -0
  113. package/dist/v1/libs/TinyRateLimiter.mjs +48 -0
  114. package/dist/v1/libs/TinySmartScroller.cjs +52 -3
  115. package/dist/v1/libs/TinySmartScroller.d.mts +17 -15
  116. package/dist/v1/libs/TinySmartScroller.mjs +51 -5
  117. package/dist/v1/libs/TinyTextDiffer.cjs +19 -18
  118. package/dist/v1/libs/TinyTextDiffer.d.mts +1 -0
  119. package/dist/v1/libs/TinyTextDiffer.mjs +16 -17
  120. package/dist/v1/libs/TinyTextarea.cjs +19 -0
  121. package/dist/v1/libs/TinyTextarea.d.mts +6 -0
  122. package/dist/v1/libs/TinyTextarea.mjs +17 -0
  123. package/dist/v1/libs/TinyTimeout.cjs +11 -1
  124. package/dist/v1/libs/TinyTimeout.mjs +9 -1
  125. package/dist/v1/libs/TinyToastNotify.cjs +35 -0
  126. package/dist/v1/libs/TinyToastNotify.d.mts +6 -0
  127. package/dist/v1/libs/TinyToastNotify.mjs +31 -0
  128. package/dist/v1/libs/TinyUploadClicker.cjs +19 -0
  129. package/dist/v1/libs/TinyUploadClicker.d.mts +6 -0
  130. package/dist/v1/libs/TinyUploadClicker.mjs +17 -0
  131. package/dist/v1/libs/UltraRandomMsgGen.cjs +148 -35
  132. package/dist/v1/libs/UltraRandomMsgGen.d.mts +17 -3
  133. package/dist/v1/libs/UltraRandomMsgGen.mjs +133 -34
  134. package/dist/v1/libs/utils.cjs +21 -0
  135. package/dist/v1/libs/utils.d.mts +2 -0
  136. package/dist/v1/libs/utils.mjs +16 -0
  137. package/docs/v1/README.md +1 -0
  138. package/docs/v1/basics/array.md +2 -2
  139. package/docs/v1/basics/deprecated/html.md +17 -0
  140. package/docs/v1/basics/fullScreen.md +1 -1
  141. package/docs/v1/basics/html.md +12 -26
  142. package/docs/v1/basics/mediaContent.md +1 -1
  143. package/docs/v1/basics/objChecker.md +1 -1
  144. package/docs/v1/basics/text.md +2 -2
  145. package/docs/v1/fileManager/main.md +1 -1
  146. package/docs/v1/libs/ColorSafeStringify.md +1 -1
  147. package/docs/v1/libs/TinyAfterScrollWatcher.md +1 -1
  148. package/docs/v1/libs/TinyAnalogClock.md +1 -1
  149. package/docs/v1/libs/TinyArrayComparator.md +1 -1
  150. package/docs/v1/libs/TinyArrayPaginator.md +1 -1
  151. package/docs/v1/libs/TinyClassManager/TinyPluginInliner.md +2 -2
  152. package/docs/v1/libs/TinyClassManager.md +1 -1
  153. package/docs/v1/libs/TinyClipboard.md +1 -1
  154. package/docs/v1/libs/TinyCookieConsent.md +1 -1
  155. package/docs/v1/libs/TinyDomReadyManager.md +1 -1
  156. package/docs/v1/libs/TinyDragDropDetector.md +1 -1
  157. package/docs/v1/libs/TinyDragger.md +1 -1
  158. package/docs/v1/libs/TinyElementObserver.md +1 -1
  159. package/docs/v1/libs/TinyHtml.md +1 -1
  160. package/docs/v1/libs/TinyIframeEvents.md +2 -2
  161. package/docs/v1/libs/TinyLevelUp.md +1 -1
  162. package/docs/v1/libs/TinyLoadingScreen.md +1 -1
  163. package/docs/v1/libs/TinyNewWinEvents.md +2 -2
  164. package/docs/v1/libs/TinyNotifications.md +1 -1
  165. package/docs/v1/libs/TinyPromiseQueue.md +2 -2
  166. package/docs/v1/libs/TinyTextDiffer.md +1 -1
  167. package/docs/v1/libs/TinyTextarea.md +1 -1
  168. package/package.json +10 -1
@@ -38,6 +38,12 @@ declare class TinyNewWinEvents extends EventEmitter<any> {
38
38
  name?: string | undefined;
39
39
  features?: string | undefined;
40
40
  });
41
+ /**
42
+ * Gets the current destruction status of the instance.
43
+ *
44
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
45
+ */
46
+ get destroyed(): boolean;
41
47
  /**
42
48
  * Sets the internal handshake event name.
43
49
  * @param {string} name
@@ -1,4 +1,6 @@
1
1
  import { EventEmitter } from 'events';
2
+ import { createCheckDestroyed } from './utils.mjs';
3
+ const checkDestroy = createCheckDestroyed('TinyNewWinEvents');
2
4
  /**
3
5
  * Stores polling intervals associated with window references.
4
6
  * Used to detect when the window is closed.
@@ -38,11 +40,25 @@ class TinyNewWinEvents extends EventEmitter {
38
40
  #readyEventName = '__TNE_READY__';
39
41
  /** @type {string} Internal message type for routed communication */
40
42
  #routeEventName = '__TNE_ROUTE__';
43
+ /**
44
+ * Tracks whether the instance has been destroyed.
45
+ * @type {boolean}
46
+ */
47
+ #destroyed = false;
48
+ /**
49
+ * Gets the current destruction status of the instance.
50
+ *
51
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
52
+ */
53
+ get destroyed() {
54
+ return this.#destroyed;
55
+ }
41
56
  /**
42
57
  * Gets the internal handshake event name.
43
58
  * @returns {string}
44
59
  */
45
60
  get readyEventName() {
61
+ checkDestroy(this.#destroyed);
46
62
  return this.#readyEventName;
47
63
  }
48
64
  /**
@@ -51,6 +67,7 @@ class TinyNewWinEvents extends EventEmitter {
51
67
  * @throws {TypeError} If the value is not a string.
52
68
  */
53
69
  set readyEventName(name) {
70
+ checkDestroy(this.#destroyed);
54
71
  if (typeof name !== 'string')
55
72
  throw new TypeError('TinyNewWinEvents: readyEventName must be a string.');
56
73
  this.#readyEventName = name;
@@ -60,6 +77,7 @@ class TinyNewWinEvents extends EventEmitter {
60
77
  * @returns {string}
61
78
  */
62
79
  get routeEventName() {
80
+ checkDestroy(this.#destroyed);
63
81
  return this.#routeEventName;
64
82
  }
65
83
  /**
@@ -68,6 +86,7 @@ class TinyNewWinEvents extends EventEmitter {
68
86
  * @throws {TypeError} If the value is not a string.
69
87
  */
70
88
  set routeEventName(name) {
89
+ checkDestroy(this.#destroyed);
71
90
  if (typeof name !== 'string')
72
91
  throw new TypeError('TinyNewWinEvents: routeEventName must be a string.');
73
92
  this.#routeEventName = name;
@@ -120,6 +139,7 @@ class TinyNewWinEvents extends EventEmitter {
120
139
  * @returns {Window|null}
121
140
  */
122
141
  getWin() {
142
+ checkDestroy(this.#destroyed);
123
143
  return this.#windowRef;
124
144
  }
125
145
  /**
@@ -176,6 +196,7 @@ class TinyNewWinEvents extends EventEmitter {
176
196
  * @returns {void}
177
197
  */
178
198
  close() {
199
+ checkDestroy(this.#destroyed);
179
200
  if (!this.#isHost)
180
201
  throw new Error('Only host can close the window.');
181
202
  if (this.#windowRef && !this.#windowRef.closed)
@@ -192,6 +213,7 @@ class TinyNewWinEvents extends EventEmitter {
192
213
  * @returns {void}
193
214
  */
194
215
  winEmit(route, payload) {
216
+ checkDestroy(this.#destroyed);
195
217
  if (typeof route !== 'string')
196
218
  throw new TypeError('Event name must be a string.');
197
219
  if (this.isDestroyed())
@@ -208,6 +230,7 @@ class TinyNewWinEvents extends EventEmitter {
208
230
  * @returns {boolean}
209
231
  */
210
232
  isConnected() {
233
+ checkDestroy(this.#destroyed);
211
234
  return this.#ready && this.#windowRef && !this.#windowRef.closed ? true : false;
212
235
  }
213
236
  /**
@@ -233,6 +256,7 @@ class TinyNewWinEvents extends EventEmitter {
233
256
  * @returns {void}
234
257
  */
235
258
  onClose(callback) {
259
+ checkDestroy(this.#destroyed);
236
260
  this.on('WINDOW_REF_CLOSED', callback);
237
261
  }
238
262
  /**
@@ -242,6 +266,7 @@ class TinyNewWinEvents extends EventEmitter {
242
266
  * @returns {void}
243
267
  */
244
268
  offClose(callback) {
269
+ checkDestroy(this.#destroyed);
245
270
  this.off('WINDOW_REF_CLOSED', callback);
246
271
  }
247
272
  /**
@@ -270,6 +295,7 @@ class TinyNewWinEvents extends EventEmitter {
270
295
  this.#ready = false;
271
296
  this.#windowRef = null;
272
297
  this.removeAllListeners();
298
+ this.#destroyed = true;
273
299
  }
274
300
  }
275
301
  export default TinyNewWinEvents;
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ var utils = require('./utils.cjs');
4
+
5
+ const checkDestroy = utils.createCheckDestroyed('TinyNotifyCenter');
6
+
3
7
  /**
4
8
  * Represents a single notification entry.
5
9
  *
@@ -109,6 +113,21 @@ class TinyNotifyCenter {
109
113
  document.body.insertAdjacentHTML(where, TinyNotifyCenter.getTemplate());
110
114
  }
111
115
 
116
+ /**
117
+ * Tracks whether the instance has been destroyed.
118
+ * @type {boolean}
119
+ */
120
+ #destroyed = false;
121
+
122
+ /**
123
+ * Gets the current destruction status of the instance.
124
+ *
125
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
126
+ */
127
+ get destroyed() {
128
+ return this.#destroyed;
129
+ }
130
+
112
131
  /** @type {HTMLElement} */
113
132
  #center;
114
133
  /** @type {HTMLElement} */
@@ -206,6 +225,7 @@ class TinyNotifyCenter {
206
225
  * @param {boolean} value
207
226
  */
208
227
  setMarkAllAsReadOnClose(value) {
228
+ checkDestroy(this.#destroyed);
209
229
  if (typeof value !== 'boolean')
210
230
  throw new TypeError(`Expected boolean for markAllAsReadOnClose, got ${typeof value}`);
211
231
  this.#markAllAsReadOnClose = value;
@@ -216,6 +236,7 @@ class TinyNotifyCenter {
216
236
  * @param {number} ms
217
237
  */
218
238
  setRemoveDelay(ms) {
239
+ checkDestroy(this.#destroyed);
219
240
  if (typeof ms !== 'number') throw new Error(`NotificationCenter: "ms" must be an number.`);
220
241
  this.#removeDelay = ms;
221
242
  }
@@ -226,6 +247,7 @@ class TinyNotifyCenter {
226
247
  * @returns {'text' | 'html' | null}
227
248
  */
228
249
  getItemMode(index) {
250
+ checkDestroy(this.#destroyed);
229
251
  const item = this.getItem(index);
230
252
  return item ? this.#modes.get(item) : null;
231
253
  }
@@ -236,6 +258,7 @@ class TinyNotifyCenter {
236
258
  * @returns {HTMLElement}
237
259
  */
238
260
  getItem(index) {
261
+ checkDestroy(this.#destroyed);
239
262
  const element = this.#list.children.item(index);
240
263
  if (!(element instanceof HTMLElement))
241
264
  throw new Error(`NotificationCenter: "item" must be an HTMLElement. Got: ${element}`);
@@ -248,6 +271,7 @@ class TinyNotifyCenter {
248
271
  * @returns {boolean}
249
272
  */
250
273
  hasItem(index) {
274
+ checkDestroy(this.#destroyed);
251
275
  return index >= 0 && index < this.#list.children.length;
252
276
  }
253
277
 
@@ -256,6 +280,7 @@ class TinyNotifyCenter {
256
280
  * @param {number|HTMLElement} index
257
281
  */
258
282
  markAsRead(index) {
283
+ checkDestroy(this.#destroyed);
259
284
  const item = index instanceof HTMLElement ? index : this.getItem(index);
260
285
  if (item.classList.contains('unread')) {
261
286
  item.classList.remove('unread');
@@ -270,6 +295,7 @@ class TinyNotifyCenter {
270
295
  * @param {'text'|'html'} [mode='text'] - How to treat the message content.
271
296
  */
272
297
  add(message, mode = 'text') {
298
+ checkDestroy(this.#destroyed);
273
299
  const item = document.createElement('div');
274
300
  item.className = 'item unread';
275
301
 
@@ -351,6 +377,7 @@ class TinyNotifyCenter {
351
377
  * @param {number} index
352
378
  */
353
379
  remove(index) {
380
+ checkDestroy(this.#destroyed);
354
381
  const item = this.getItem(index);
355
382
  this.#removeItem(item);
356
383
  }
@@ -359,6 +386,7 @@ class TinyNotifyCenter {
359
386
  * Clear all notifications safely.
360
387
  */
361
388
  clear() {
389
+ checkDestroy(this.#destroyed);
362
390
  let needAgain = true;
363
391
  while (needAgain) {
364
392
  needAgain = false;
@@ -376,6 +404,7 @@ class TinyNotifyCenter {
376
404
  * Open the notify center.
377
405
  */
378
406
  open() {
407
+ checkDestroy(this.#destroyed);
379
408
  this.#overlay.classList.remove('hidden');
380
409
  this.#center.classList.add('open');
381
410
  }
@@ -384,6 +413,7 @@ class TinyNotifyCenter {
384
413
  * Close the notify center.
385
414
  */
386
415
  close() {
416
+ checkDestroy(this.#destroyed);
387
417
  this.#overlay.classList.add('hidden');
388
418
  this.#center.classList.remove('open');
389
419
  if (this.#markAllAsReadOnClose) {
@@ -398,6 +428,7 @@ class TinyNotifyCenter {
398
428
  * Toggle open/close state.
399
429
  */
400
430
  toggle() {
431
+ checkDestroy(this.#destroyed);
401
432
  if (this.#center.classList.contains('open')) this.close();
402
433
  else this.open();
403
434
  }
@@ -406,6 +437,7 @@ class TinyNotifyCenter {
406
437
  * Recalculate the number of notifications based on the actual DOM list.
407
438
  */
408
439
  recount() {
440
+ checkDestroy(this.#destroyed);
409
441
  const count = this.#list.querySelectorAll('.item.unread').length;
410
442
  this.#updateCount(count);
411
443
  }
@@ -427,6 +459,7 @@ class TinyNotifyCenter {
427
459
  * @returns {void}
428
460
  */
429
461
  destroy() {
462
+ if (this.#destroyed) return;
430
463
  // Remove event listeners
431
464
  this.#button?.removeEventListener('click', this.toggle);
432
465
  this.#center?.querySelector('.close')?.removeEventListener('click', this.close);
@@ -448,6 +481,9 @@ class TinyNotifyCenter {
448
481
  // this.#overlay = null;
449
482
  this.#count = 0;
450
483
  this.#modes = new WeakMap();
484
+
485
+ // Lock the instance
486
+ this.#destroyed = true;
451
487
  }
452
488
  }
453
489
 
@@ -91,6 +91,12 @@ declare class TinyNotifyCenter {
91
91
  button?: HTMLElement | undefined;
92
92
  overlay?: HTMLElement | undefined;
93
93
  });
94
+ /**
95
+ * Gets the current destruction status of the instance.
96
+ *
97
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
98
+ */
99
+ get destroyed(): boolean;
94
100
  /**
95
101
  * Enable or disable automatic mark-as-read on close.
96
102
  * @param {boolean} value
@@ -1,3 +1,5 @@
1
+ import { createCheckDestroyed } from './utils.mjs';
2
+ const checkDestroy = createCheckDestroyed('TinyNotifyCenter');
1
3
  /**
2
4
  * Represents a single notification entry.
3
5
  *
@@ -104,6 +106,19 @@ class TinyNotifyCenter {
104
106
  static insertTemplate(where = 'afterbegin') {
105
107
  document.body.insertAdjacentHTML(where, TinyNotifyCenter.getTemplate());
106
108
  }
109
+ /**
110
+ * Tracks whether the instance has been destroyed.
111
+ * @type {boolean}
112
+ */
113
+ #destroyed = false;
114
+ /**
115
+ * Gets the current destruction status of the instance.
116
+ *
117
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
118
+ */
119
+ get destroyed() {
120
+ return this.#destroyed;
121
+ }
107
122
  /** @type {HTMLElement} */
108
123
  #center;
109
124
  /** @type {HTMLElement} */
@@ -188,6 +203,7 @@ class TinyNotifyCenter {
188
203
  * @param {boolean} value
189
204
  */
190
205
  setMarkAllAsReadOnClose(value) {
206
+ checkDestroy(this.#destroyed);
191
207
  if (typeof value !== 'boolean')
192
208
  throw new TypeError(`Expected boolean for markAllAsReadOnClose, got ${typeof value}`);
193
209
  this.#markAllAsReadOnClose = value;
@@ -197,6 +213,7 @@ class TinyNotifyCenter {
197
213
  * @param {number} ms
198
214
  */
199
215
  setRemoveDelay(ms) {
216
+ checkDestroy(this.#destroyed);
200
217
  if (typeof ms !== 'number')
201
218
  throw new Error(`NotificationCenter: "ms" must be an number.`);
202
219
  this.#removeDelay = ms;
@@ -207,6 +224,7 @@ class TinyNotifyCenter {
207
224
  * @returns {'text' | 'html' | null}
208
225
  */
209
226
  getItemMode(index) {
227
+ checkDestroy(this.#destroyed);
210
228
  const item = this.getItem(index);
211
229
  return item ? this.#modes.get(item) : null;
212
230
  }
@@ -216,6 +234,7 @@ class TinyNotifyCenter {
216
234
  * @returns {HTMLElement}
217
235
  */
218
236
  getItem(index) {
237
+ checkDestroy(this.#destroyed);
219
238
  const element = this.#list.children.item(index);
220
239
  if (!(element instanceof HTMLElement))
221
240
  throw new Error(`NotificationCenter: "item" must be an HTMLElement. Got: ${element}`);
@@ -227,6 +246,7 @@ class TinyNotifyCenter {
227
246
  * @returns {boolean}
228
247
  */
229
248
  hasItem(index) {
249
+ checkDestroy(this.#destroyed);
230
250
  return index >= 0 && index < this.#list.children.length;
231
251
  }
232
252
  /**
@@ -234,6 +254,7 @@ class TinyNotifyCenter {
234
254
  * @param {number|HTMLElement} index
235
255
  */
236
256
  markAsRead(index) {
257
+ checkDestroy(this.#destroyed);
237
258
  const item = index instanceof HTMLElement ? index : this.getItem(index);
238
259
  if (item.classList.contains('unread')) {
239
260
  item.classList.remove('unread');
@@ -247,6 +268,7 @@ class TinyNotifyCenter {
247
268
  * @param {'text'|'html'} [mode='text'] - How to treat the message content.
248
269
  */
249
270
  add(message, mode = 'text') {
271
+ checkDestroy(this.#destroyed);
250
272
  const item = document.createElement('div');
251
273
  item.className = 'item unread';
252
274
  let titleText = null;
@@ -318,6 +340,7 @@ class TinyNotifyCenter {
318
340
  * @param {number} index
319
341
  */
320
342
  remove(index) {
343
+ checkDestroy(this.#destroyed);
321
344
  const item = this.getItem(index);
322
345
  this.#removeItem(item);
323
346
  }
@@ -325,6 +348,7 @@ class TinyNotifyCenter {
325
348
  * Clear all notifications safely.
326
349
  */
327
350
  clear() {
351
+ checkDestroy(this.#destroyed);
328
352
  let needAgain = true;
329
353
  while (needAgain) {
330
354
  needAgain = false;
@@ -341,6 +365,7 @@ class TinyNotifyCenter {
341
365
  * Open the notify center.
342
366
  */
343
367
  open() {
368
+ checkDestroy(this.#destroyed);
344
369
  this.#overlay.classList.remove('hidden');
345
370
  this.#center.classList.add('open');
346
371
  }
@@ -348,6 +373,7 @@ class TinyNotifyCenter {
348
373
  * Close the notify center.
349
374
  */
350
375
  close() {
376
+ checkDestroy(this.#destroyed);
351
377
  this.#overlay.classList.add('hidden');
352
378
  this.#center.classList.remove('open');
353
379
  if (this.#markAllAsReadOnClose) {
@@ -362,6 +388,7 @@ class TinyNotifyCenter {
362
388
  * Toggle open/close state.
363
389
  */
364
390
  toggle() {
391
+ checkDestroy(this.#destroyed);
365
392
  if (this.#center.classList.contains('open'))
366
393
  this.close();
367
394
  else
@@ -371,6 +398,7 @@ class TinyNotifyCenter {
371
398
  * Recalculate the number of notifications based on the actual DOM list.
372
399
  */
373
400
  recount() {
401
+ checkDestroy(this.#destroyed);
374
402
  const count = this.#list.querySelectorAll('.item.unread').length;
375
403
  this.#updateCount(count);
376
404
  }
@@ -390,6 +418,8 @@ class TinyNotifyCenter {
390
418
  * @returns {void}
391
419
  */
392
420
  destroy() {
421
+ if (this.#destroyed)
422
+ return;
393
423
  // Remove event listeners
394
424
  this.#button?.removeEventListener('click', this.toggle);
395
425
  this.#center?.querySelector('.close')?.removeEventListener('click', this.close);
@@ -408,6 +438,8 @@ class TinyNotifyCenter {
408
438
  // this.#overlay = null;
409
439
  this.#count = 0;
410
440
  this.#modes = new WeakMap();
441
+ // Lock the instance
442
+ this.#destroyed = true;
411
443
  }
412
444
  }
413
445
  export default TinyNotifyCenter;
@@ -2,6 +2,9 @@
2
2
 
3
3
  var events = require('events');
4
4
  var mediaContent = require('../basics/mediaContent.cjs');
5
+ var utils = require('./utils.cjs');
6
+
7
+ const checkDestroy = utils.createCheckDestroyed('TinyRadioFm');
5
8
 
6
9
  /**
7
10
  * @typedef {import('../basics/mediaContent.mjs').MediaContentBase} MediaContentBase
@@ -179,11 +182,19 @@ class TinyRadioFm extends events.EventEmitter {
179
182
  TinyRadioFm.#unknownArtist = value;
180
183
  }
181
184
 
185
+ /** @type {boolean} */
186
+ #destroyed = false;
187
+
188
+ get destroyed() {
189
+ return this.#destroyed;
190
+ }
191
+
182
192
  /**
183
193
  * Gets the total count of all content items (music and voice) in the system.
184
194
  * @returns {number}
185
195
  */
186
196
  get size() {
197
+ checkDestroy(this.#destroyed);
187
198
  return this.#musicList.length + this.#voiceList.length;
188
199
  }
189
200
 
@@ -192,6 +203,7 @@ class TinyRadioFm extends events.EventEmitter {
192
203
  * @returns {number}
193
204
  */
194
205
  get musicSize() {
206
+ checkDestroy(this.#destroyed);
195
207
  return this.#musicList.length;
196
208
  }
197
209
 
@@ -200,6 +212,7 @@ class TinyRadioFm extends events.EventEmitter {
200
212
  * @returns {number}
201
213
  */
202
214
  get voiceSize() {
215
+ checkDestroy(this.#destroyed);
203
216
  return this.#voiceList.length;
204
217
  }
205
218
 
@@ -208,6 +221,7 @@ class TinyRadioFm extends events.EventEmitter {
208
221
  * @returns {number}
209
222
  */
210
223
  get customPosSize() {
224
+ checkDestroy(this.#destroyed);
211
225
  return this.#customPositions.length;
212
226
  }
213
227
 
@@ -216,6 +230,7 @@ class TinyRadioFm extends events.EventEmitter {
216
230
  * @returns {number}
217
231
  */
218
232
  get tasksSize() {
233
+ checkDestroy(this.#destroyed);
219
234
  return this.#scheduledTasks.length;
220
235
  }
221
236
 
@@ -224,6 +239,7 @@ class TinyRadioFm extends events.EventEmitter {
224
239
  * @returns {number}
225
240
  */
226
241
  get cycleCacheSize() {
242
+ checkDestroy(this.#destroyed);
227
243
  return this.#cycleCache.size;
228
244
  }
229
245
 
@@ -234,6 +250,7 @@ class TinyRadioFm extends events.EventEmitter {
234
250
  * @returns {MediaContent[]}
235
251
  */
236
252
  get musicList() {
253
+ checkDestroy(this.#destroyed);
237
254
  return structuredClone(this.#musicList);
238
255
  }
239
256
 
@@ -244,6 +261,7 @@ class TinyRadioFm extends events.EventEmitter {
244
261
  * @returns {MediaContent[]}
245
262
  */
246
263
  get voiceList() {
264
+ checkDestroy(this.#destroyed);
247
265
  return structuredClone(this.#voiceList);
248
266
  }
249
267
 
@@ -254,6 +272,7 @@ class TinyRadioFm extends events.EventEmitter {
254
272
  * @returns {CustomPosition[]}
255
273
  */
256
274
  get customPositions() {
275
+ checkDestroy(this.#destroyed);
257
276
  return structuredClone(this.#customPositions);
258
277
  }
259
278
 
@@ -264,6 +283,7 @@ class TinyRadioFm extends events.EventEmitter {
264
283
  * @returns {ScheduledTask[]}
265
284
  */
266
285
  get scheduledTasks() {
286
+ checkDestroy(this.#destroyed);
267
287
  return structuredClone(this.#scheduledTasks);
268
288
  }
269
289
 
@@ -272,6 +292,7 @@ class TinyRadioFm extends events.EventEmitter {
272
292
  * @returns {MediaContent[]} A cloned object of the cache.
273
293
  */
274
294
  get allList() {
295
+ checkDestroy(this.#destroyed);
275
296
  return [...this.musicList, ...this.voiceList];
276
297
  }
277
298
 
@@ -282,6 +303,7 @@ class TinyRadioFm extends events.EventEmitter {
282
303
  * @returns {number}
283
304
  */
284
305
  get seed() {
306
+ checkDestroy(this.#destroyed);
285
307
  return this.#seed;
286
308
  }
287
309
  /**
@@ -289,6 +311,7 @@ class TinyRadioFm extends events.EventEmitter {
289
311
  * @param {number} seed - The new seed.
290
312
  */
291
313
  set seed(seed) {
314
+ checkDestroy(this.#destroyed);
292
315
  if (typeof seed !== 'number') throw new TypeError('Seed must be a number.');
293
316
  this.#seed = seed;
294
317
  this.#cycleCache.clear();
@@ -302,6 +325,7 @@ class TinyRadioFm extends events.EventEmitter {
302
325
  * @returns {number}
303
326
  */
304
327
  get anchorDate() {
328
+ checkDestroy(this.#destroyed);
305
329
  return this.#anchorDate;
306
330
  }
307
331
 
@@ -325,6 +349,7 @@ class TinyRadioFm extends events.EventEmitter {
325
349
  * @returns {RadioConfig}
326
350
  */
327
351
  get config() {
352
+ checkDestroy(this.#destroyed);
328
353
  return structuredClone(this.#config);
329
354
  }
330
355
  /**
@@ -333,6 +358,7 @@ class TinyRadioFm extends events.EventEmitter {
333
358
  * @throws {TypeError|RangeError} If the new configuration is invalid.
334
359
  */
335
360
  set config(config) {
361
+ checkDestroy(this.#destroyed);
336
362
  // Validate the entire object before applying it
337
363
  this.#validateConfig(config);
338
364
 
@@ -455,6 +481,7 @@ class TinyRadioFm extends events.EventEmitter {
455
481
  * @throws {TypeError} If the type is invalid or the data lacks a valid ID and numerical duration.
456
482
  */
457
483
  add(type, data, smartQueue = true) {
484
+ checkDestroy(this.#destroyed);
458
485
  if (!['music', 'voice', 'custom'].includes(type)) {
459
486
  throw new TypeError('Type must be "music", "voice", or "custom".');
460
487
  }
@@ -491,6 +518,7 @@ class TinyRadioFm extends events.EventEmitter {
491
518
  * @throws {TypeError} If arguments do not match the required types or action/type constraints.
492
519
  */
493
520
  scheduleTask(timestamp, action, type, payload, smartQueue = true) {
521
+ checkDestroy(this.#destroyed);
494
522
  if (typeof timestamp !== 'number' || isNaN(timestamp))
495
523
  throw new TypeError('timestamp must be a valid number.');
496
524
  if (!['add', 'remove', 'move'].includes(action))
@@ -560,6 +588,7 @@ class TinyRadioFm extends events.EventEmitter {
560
588
  * @throws {TypeError} If the id is not a string.
561
589
  */
562
590
  remove(id) {
591
+ checkDestroy(this.#destroyed);
563
592
  if (typeof id !== 'string') throw new TypeError('id must be a string.');
564
593
 
565
594
  // Revoke Blob URLs of items being removed to free memory
@@ -605,6 +634,7 @@ class TinyRadioFm extends events.EventEmitter {
605
634
  * @throws {TypeError|RangeError} If the provided values or the resulting state is invalid.
606
635
  */
607
636
  setConfig(config) {
637
+ checkDestroy(this.#destroyed);
608
638
  // First, validate the incoming partial object for basic type correctness
609
639
  this.#validateConfig(config);
610
640
 
@@ -624,6 +654,7 @@ class TinyRadioFm extends events.EventEmitter {
624
654
  * @returns {RadioEvent|null} The current active event, or null if empty.
625
655
  */
626
656
  getCurrentEvent() {
657
+ checkDestroy(this.#destroyed);
627
658
  const now = Date.now();
628
659
  this.#syncRealTimeState(now);
629
660
  return this.#getEventAtTime(now, now);
@@ -639,6 +670,7 @@ class TinyRadioFm extends events.EventEmitter {
639
670
  * @throws {RangeError} If the limit exceeds the configured queryLimit or is <= 0.
640
671
  */
641
672
  queryTimeline(targetDate, limit = 10) {
673
+ checkDestroy(this.#destroyed);
642
674
  if (typeof limit !== 'number' || isNaN(limit)) {
643
675
  throw new TypeError(`Invalid query limit value. Ensure it is a number.`);
644
676
  }
@@ -677,6 +709,7 @@ class TinyRadioFm extends events.EventEmitter {
677
709
  * @returns {CustomPosition[]} Shallow copy of custom positions array.
678
710
  */
679
711
  searchCustomPositions() {
712
+ checkDestroy(this.#destroyed);
680
713
  this.#syncRealTimeState(Date.now());
681
714
  return [...this.#customPositions];
682
715
  }
@@ -726,6 +759,7 @@ class TinyRadioFm extends events.EventEmitter {
726
759
  * @returns {Promise<string>} Stringified JSON state.
727
760
  */
728
761
  async exportState() {
762
+ checkDestroy(this.#destroyed);
729
763
  const processedMusic = await this._processListForExport(this.#musicList);
730
764
  const processedVoice = await this._processListForExport(this.#voiceList);
731
765
 
@@ -770,6 +804,7 @@ class TinyRadioFm extends events.EventEmitter {
770
804
  * @throws {TypeError} If json is not a valid string or object.
771
805
  */
772
806
  importState(json) {
807
+ checkDestroy(this.#destroyed);
773
808
  /** @type {TinyRadioFmImport} */
774
809
  let data;
775
810
  if (typeof json === 'string') {
@@ -1347,6 +1382,8 @@ class TinyRadioFm extends events.EventEmitter {
1347
1382
  * @param {boolean} [destroyThumbs=true]
1348
1383
  */
1349
1384
  destroy(destroyThumbs = true) {
1385
+ if (this.#destroyed) return;
1386
+
1350
1387
  if (destroyThumbs) {
1351
1388
  this.#musicList.forEach((item) => mediaContent.revokeContentUrls(item));
1352
1389
  this.#voiceList.forEach((item) => mediaContent.revokeContentUrls(item));
@@ -1371,6 +1408,7 @@ class TinyRadioFm extends events.EventEmitter {
1371
1408
  this.#cycleCache.clear();
1372
1409
  this.emit('destroyed', { timestamp: Date.now() });
1373
1410
  this.removeAllListeners();
1411
+ this.#destroyed = true;
1374
1412
  }
1375
1413
  }
1376
1414
 
@@ -377,6 +377,7 @@ declare class TinyRadioFm extends EventEmitter<any> {
377
377
  * @throws {TypeError} If initialData is not an object or null, or if seed is not a number.
378
378
  */
379
379
  constructor(initialData?: TinyRadioFmImport | null, seed?: number);
380
+ get destroyed(): boolean;
380
381
  /**
381
382
  * Gets the total count of all content items (music and voice) in the system.
382
383
  * @returns {number}