tiny-essentials 1.18.1 → 1.19.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 (74) hide show
  1. package/README.md +17 -3
  2. package/dist/node_modules/firebase-functions/lib/common/trace.cjs +0 -1
  3. package/dist/node_modules/firebase-functions/lib/logger/index.cjs +1 -0
  4. package/dist/v1/ColorSafeStringify.min.js +1 -1
  5. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
  6. package/dist/v1/TinyBasicsEs.js +13 -6
  7. package/dist/v1/TinyBasicsEs.min.js +1 -1
  8. package/dist/v1/TinyClipboard.min.js +1 -1
  9. package/dist/v1/TinyColorConverter.js +617 -0
  10. package/dist/v1/TinyColorConverter.min.js +1 -0
  11. package/dist/v1/TinyDomReadyManager.min.js +1 -1
  12. package/dist/v1/TinyDragger.min.js +1 -1
  13. package/dist/v1/TinyEssentials.js +2635 -482
  14. package/dist/v1/TinyEssentials.min.js +1 -1
  15. package/dist/v1/TinyEvents.js +402 -0
  16. package/dist/v1/TinyEvents.min.js +1 -0
  17. package/dist/v1/TinyHtml.min.js +1 -1
  18. package/dist/v1/TinyLocalStorage.js +1292 -0
  19. package/dist/v1/TinyLocalStorage.min.js +1 -0
  20. package/dist/v1/TinyNotifications.min.js +1 -1
  21. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  22. package/dist/v1/TinyPromiseQueue.min.js +1 -1
  23. package/dist/v1/TinyRateLimiter.js +2 -1
  24. package/dist/v1/TinyRateLimiter.min.js +1 -1
  25. package/dist/v1/TinySmartScroller.js +570 -52
  26. package/dist/v1/TinySmartScroller.min.js +1 -1
  27. package/dist/v1/TinyTextRangeEditor.min.js +1 -1
  28. package/dist/v1/TinyTimeout.js +233 -0
  29. package/dist/v1/TinyTimeout.min.js +1 -0
  30. package/dist/v1/TinyToastNotify.min.js +1 -1
  31. package/dist/v1/TinyUploadClicker.js +1457 -106
  32. package/dist/v1/TinyUploadClicker.min.js +1 -1
  33. package/dist/v1/UltraRandomMsgGen.min.js +1 -1
  34. package/dist/v1/basics/html.cjs +13 -6
  35. package/dist/v1/basics/html.d.mts +12 -4
  36. package/dist/v1/basics/html.mjs +13 -6
  37. package/dist/v1/build/TinyColorConverter.cjs +7 -0
  38. package/dist/v1/build/TinyColorConverter.d.mts +3 -0
  39. package/dist/v1/build/TinyColorConverter.mjs +2 -0
  40. package/dist/v1/build/TinyEvents.cjs +7 -0
  41. package/dist/v1/build/TinyEvents.d.mts +3 -0
  42. package/dist/v1/build/TinyEvents.mjs +2 -0
  43. package/dist/v1/build/TinyLocalStorage.cjs +7 -0
  44. package/dist/v1/build/TinyLocalStorage.d.mts +3 -0
  45. package/dist/v1/build/TinyLocalStorage.mjs +2 -0
  46. package/dist/v1/build/TinyTimeout.cjs +7 -0
  47. package/dist/v1/build/TinyTimeout.d.mts +3 -0
  48. package/dist/v1/build/TinyTimeout.mjs +2 -0
  49. package/dist/v1/index.cjs +8 -0
  50. package/dist/v1/index.d.mts +5 -1
  51. package/dist/v1/index.mjs +5 -1
  52. package/dist/v1/libs/TinyColorConverter.cjs +578 -0
  53. package/dist/v1/libs/TinyColorConverter.d.mts +396 -0
  54. package/dist/v1/libs/TinyColorConverter.mjs +520 -0
  55. package/dist/v1/libs/TinyEvents.cjs +363 -0
  56. package/dist/v1/libs/TinyEvents.d.mts +160 -0
  57. package/dist/v1/libs/TinyEvents.mjs +328 -0
  58. package/dist/v1/libs/TinyLocalStorage.cjs +847 -0
  59. package/dist/v1/libs/TinyLocalStorage.d.mts +407 -0
  60. package/dist/v1/libs/TinyLocalStorage.mjs +740 -0
  61. package/dist/v1/libs/TinySmartScroller.cjs +207 -52
  62. package/dist/v1/libs/TinySmartScroller.d.mts +164 -16
  63. package/dist/v1/libs/TinySmartScroller.mjs +181 -52
  64. package/dist/v1/libs/TinyTimeout.cjs +194 -0
  65. package/dist/v1/libs/TinyTimeout.d.mts +89 -0
  66. package/dist/v1/libs/TinyTimeout.mjs +179 -0
  67. package/dist/v1/libs/TinyUploadClicker.cjs +1 -0
  68. package/docs/v1/README.md +4 -0
  69. package/docs/v1/libs/TinyColorConverter.md +220 -0
  70. package/docs/v1/libs/TinyEvents.md +199 -0
  71. package/docs/v1/libs/TinyLocalStorage.md +350 -0
  72. package/docs/v1/libs/TinyRateLimiter.md +0 -3
  73. package/docs/v1/libs/TinyTimeout.md +190 -0
  74. package/package.json +28 -5
@@ -2888,17 +2888,21 @@ __webpack_require__.d(__webpack_exports__, {
2888
2888
  ColorSafeStringify: () => (/* reexport */ libs_ColorSafeStringify),
2889
2889
  TinyAfterScrollWatcher: () => (/* reexport */ libs_TinyAfterScrollWatcher),
2890
2890
  TinyClipboard: () => (/* reexport */ libs_TinyClipboard),
2891
+ TinyColorConverter: () => (/* reexport */ libs_TinyColorConverter),
2891
2892
  TinyDomReadyManager: () => (/* reexport */ libs_TinyDomReadyManager),
2892
2893
  TinyDragDropDetector: () => (/* reexport */ libs_TinyDragDropDetector),
2893
2894
  TinyDragger: () => (/* reexport */ libs_TinyDragger),
2895
+ TinyEvents: () => (/* reexport */ libs_TinyEvents),
2894
2896
  TinyHtml: () => (/* reexport */ libs_TinyHtml),
2895
2897
  TinyLevelUp: () => (/* reexport */ userLevel),
2898
+ TinyLocalStorage: () => (/* reexport */ libs_TinyLocalStorage),
2896
2899
  TinyNotifications: () => (/* reexport */ libs_TinyNotifications),
2897
2900
  TinyNotifyCenter: () => (/* reexport */ libs_TinyNotifyCenter),
2898
2901
  TinyPromiseQueue: () => (/* reexport */ libs_TinyPromiseQueue),
2899
2902
  TinyRateLimiter: () => (/* reexport */ libs_TinyRateLimiter),
2900
2903
  TinySmartScroller: () => (/* reexport */ libs_TinySmartScroller),
2901
2904
  TinyTextRangeEditor: () => (/* reexport */ libs_TinyTextRangeEditor),
2905
+ TinyTimeout: () => (/* reexport */ libs_TinyTimeout),
2902
2906
  TinyToastNotify: () => (/* reexport */ libs_TinyToastNotify),
2903
2907
  UltraRandomMsgGen: () => (/* reexport */ libs_UltraRandomMsgGen),
2904
2908
  addAiMarkerShortcut: () => (/* reexport */ addAiMarkerShortcut),
@@ -5153,7 +5157,8 @@ class TinyRateLimiter {
5153
5157
  */
5154
5158
  reset(userId) {
5155
5159
  if (false)
5156
- {}
5160
+ // removed by dead control flow
5161
+ {}
5157
5162
  return this.resetUserGroup(userId);
5158
5163
  }
5159
5164
 
@@ -6527,8 +6532,8 @@ async function fetchBlob(url, allowedMimeTypes, options) {
6527
6532
  * @param {Element} [settings.element=document.body] - The element to receive visibility classes.
6528
6533
  * @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
6529
6534
  * @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
6530
- * @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
6531
- * @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
6535
+ * @param {(data: { type: string; oldType: string; oldClass: string; }) => void} [settings.onVisible] - Callback called when page becomes visible.
6536
+ * @param {(data: { type: string; oldType: string; oldClass: string; }) => void} [settings.onHidden] - Callback called when page becomes hidden.
6532
6537
  * @returns {() => void} Function that removes all installed event listeners.
6533
6538
  * @throws {TypeError} If any provided setting is invalid.
6534
6539
  */
@@ -6548,6 +6553,8 @@ function installWindowHiddenScript({
6548
6553
  if (onHidden !== undefined && typeof onHidden !== 'function')
6549
6554
  throw new TypeError(`"onHidden" must be a function if provided.`);
6550
6555
 
6556
+ let oldType = '';
6557
+ let oldClass = '';
6551
6558
  const removeClass = () => {
6552
6559
  element.classList.remove(hiddenClass);
6553
6560
  element.classList.remove(visibleClass);
@@ -6585,19 +6592,24 @@ function installWindowHiddenScript({
6585
6592
 
6586
6593
  if (visibleEvents.includes(type)) {
6587
6594
  element.classList.add(visibleClass);
6588
- onVisible?.();
6595
+ onVisible?.({ type, oldClass, oldType });
6596
+ oldClass = visibleClass;
6589
6597
  } else if (hiddenEvents.includes(type)) {
6590
6598
  element.classList.add(hiddenClass);
6591
- onHidden?.();
6599
+ onHidden?.({ type, oldClass, oldType });
6600
+ oldClass = hiddenClass;
6592
6601
  } else {
6593
6602
  if (isHidden) {
6594
6603
  element.classList.add(hiddenClass);
6595
- onHidden?.();
6604
+ onHidden?.({ type, oldClass, oldType });
6605
+ oldClass = hiddenClass;
6596
6606
  } else {
6597
6607
  element.classList.add(visibleClass);
6598
- onVisible?.();
6608
+ onVisible?.({ type, oldClass, oldType });
6609
+ oldClass = visibleClass;
6599
6610
  }
6600
6611
  }
6612
+ oldType = type;
6601
6613
  };
6602
6614
 
6603
6615
  /** @type {() => void} */
@@ -15694,10 +15706,374 @@ class UltraRandomMsgGen {
15694
15706
 
15695
15707
  /* harmony default export */ const libs_UltraRandomMsgGen = (UltraRandomMsgGen);
15696
15708
 
15709
+ ;// ./src/v1/libs/TinyEvents.mjs
15710
+ /**
15711
+ * A generic event listener callback function.
15712
+ *
15713
+ * @callback handler
15714
+ * @param {...any} payload - The data payload passed when the event is triggered.
15715
+ * @returns {void}
15716
+ */
15717
+
15718
+ /**
15719
+ * TinyEvents provides a minimalistic event emitter system similar to Node.js's EventEmitter,
15720
+ * enabling components to subscribe to, emit, and manage events and their listeners.
15721
+ *
15722
+ * Features include:
15723
+ * - Adding/removing event listeners (`on`, `off`, `offAll`, `offAllTypes`)
15724
+ * - One-time listeners (`once`)
15725
+ * - Emitting events (`emit`)
15726
+ * - Listener inspection and limits (`listenerCount`, `listeners`, `eventNames`)
15727
+ * - Maximum listener control (`setMaxListeners`, `getMaxListeners`)
15728
+ *
15729
+ * This class is useful for lightweight, dependency-free publish/subscribe event handling
15730
+ * within modular JavaScript applications.
15731
+ *
15732
+ * @class
15733
+ */
15734
+ class TinyEvents {
15735
+ /** @type {Map<string, { handler: handler; config: { once: boolean } }[]>} */
15736
+ #listeners = new Map();
15737
+
15738
+ /** @type {number} */
15739
+ #maxListeners = 10;
15740
+
15741
+ /** @type {boolean} */
15742
+ #throwMaxListeners = false;
15743
+
15744
+ /**
15745
+ * Enables or disables throwing an error when the maximum number of listeners is exceeded.
15746
+ *
15747
+ * @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
15748
+ */
15749
+ setThrowOnMaxListeners(shouldThrow) {
15750
+ if (typeof shouldThrow !== 'boolean')
15751
+ throw new TypeError('setThrowOnMaxListeners(value): value must be a boolean');
15752
+ this.#throwMaxListeners = shouldThrow;
15753
+ }
15754
+
15755
+ /**
15756
+ * Checks whether an error will be thrown when the max listener limit is exceeded.
15757
+ *
15758
+ * @returns {boolean} True if an error will be thrown, false if only a warning is shown.
15759
+ */
15760
+ getThrowOnMaxListeners() {
15761
+ return this.#throwMaxListeners;
15762
+ }
15763
+
15764
+ ///////////////////////////////////////////////////
15765
+
15766
+ /**
15767
+ * Internal method to prepend a listener with options.
15768
+ *
15769
+ * @param {string} event - Event name.
15770
+ * @param {handler} handler - The callback function.
15771
+ * @param {Object} [settings={}] - Optional settings.
15772
+ * @param {boolean} [settings.once=false] - If the listener should be executed once.
15773
+ */
15774
+ #prepend(event, handler, { once = false } = {}) {
15775
+ let eventData = this.#listeners.get(event);
15776
+ if (!Array.isArray(eventData)) {
15777
+ eventData = [];
15778
+ this.#listeners.set(event, eventData);
15779
+ }
15780
+ eventData.unshift({ handler, config: { once } });
15781
+
15782
+ const max = this.#maxListeners;
15783
+ if (max > 0 && eventData.length > max) {
15784
+ const warnMessage =
15785
+ `Possible memory leak detected. ${eventData.length} "${event}" listeners added. ` +
15786
+ `Use setMaxListeners() to increase limit.`;
15787
+ if (!this.#throwMaxListeners) console.warn(warnMessage);
15788
+ else throw new Error(warnMessage);
15789
+ }
15790
+ }
15791
+
15792
+ /**
15793
+ * Adds a listener to the beginning of the listeners array for the specified event.
15794
+ *
15795
+ * @param {string} event - Event name.
15796
+ * @param {handler} handler - The callback function.
15797
+ */
15798
+ prependListener(event, handler) {
15799
+ if (typeof event !== 'string')
15800
+ throw new TypeError('prepend(event, handler): event name must be a string');
15801
+ if (typeof handler !== 'function')
15802
+ throw new TypeError('prepend(event, handler): handler must be a function');
15803
+ this.#prepend(event, handler);
15804
+ }
15805
+
15806
+ /**
15807
+ * Adds a one-time listener to the beginning of the listeners array for the specified event.
15808
+ *
15809
+ * @param {string} event - Event name.
15810
+ * @param {handler} handler - The callback function.
15811
+ * @returns {handler} - The wrapped handler used internally.
15812
+ */
15813
+ prependListenerOnce(event, handler) {
15814
+ if (typeof event !== 'string')
15815
+ throw new TypeError('prependOnceListener(event, handler): event name must be a string');
15816
+ if (typeof handler !== 'function')
15817
+ throw new TypeError('prependOnceListener(event, handler): handler must be a function');
15818
+
15819
+ /** @type {handler} */
15820
+ const wrapped = (...args) => {
15821
+ this.off(event, wrapped);
15822
+ handler(...args);
15823
+ };
15824
+
15825
+ this.#prepend(event, wrapped, { once: true });
15826
+ return wrapped;
15827
+ }
15828
+
15829
+ ////////////////////////////////////////////////////////////
15830
+
15831
+ /**
15832
+ * Adds a event listener.
15833
+ *
15834
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15835
+ * @param {handler} handler - Callback function to be called when event fires.
15836
+ * @param {Object} [settings={}] - Optional settings.
15837
+ * @param {boolean} [settings.once=false] - This is a once event.
15838
+ */
15839
+ #on(event, handler, { once = false } = {}) {
15840
+ let eventData = this.#listeners.get(event);
15841
+ if (!Array.isArray(eventData)) {
15842
+ eventData = [];
15843
+ this.#listeners.set(event, eventData);
15844
+ }
15845
+ eventData.push({ handler, config: { once } });
15846
+ // Warn if listener count exceeds the max allowed
15847
+ const max = this.#maxListeners;
15848
+ if (max > 0 && eventData.length > max) {
15849
+ const warnMessage =
15850
+ `Possible memory leak detected. ${eventData.length} "${event}" listeners added. ` +
15851
+ `Use setMaxListeners() to increase limit.`;
15852
+ if (!this.#throwMaxListeners) console.warn(warnMessage);
15853
+ else throw new Error(warnMessage);
15854
+ }
15855
+ }
15856
+
15857
+ /**
15858
+ * Adds a event listener.
15859
+ *
15860
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15861
+ * @param {handler} handler - Callback function to be called when event fires.
15862
+ */
15863
+ on(event, handler) {
15864
+ if (typeof event !== 'string')
15865
+ throw new TypeError('on(event, handler): event name must be a string');
15866
+ if (typeof handler !== 'function')
15867
+ throw new TypeError('on(event, handler): handler must be a function');
15868
+ return this.#on(event, handler);
15869
+ }
15870
+
15871
+ /**
15872
+ * Registers an event listener that runs only once, then is removed.
15873
+ *
15874
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15875
+ * @param {handler} handler - The callback function to run on event.
15876
+ * @returns {handler} - The wrapped version of the handler.
15877
+ */
15878
+ once(event, handler) {
15879
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
15880
+ if (typeof handler !== 'function')
15881
+ throw new TypeError('once(event, handler): handler must be a function');
15882
+
15883
+ /** @type {handler} */
15884
+ const wrapped = (e) => {
15885
+ this.off(event, wrapped);
15886
+ if (typeof handler === 'function') handler(e);
15887
+ };
15888
+ this.#on(event, wrapped, { once: true });
15889
+ return wrapped;
15890
+ }
15891
+
15892
+ /**
15893
+ * Adds a event listener.
15894
+ *
15895
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15896
+ * @param {handler} handler - Callback function to be called when event fires.
15897
+ */
15898
+ appendListener(event, handler) {
15899
+ return this.on(event, handler);
15900
+ }
15901
+
15902
+ /**
15903
+ * Registers an event listener that runs only once, then is removed.
15904
+ *
15905
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
15906
+ * @param {handler} handler - The callback function to run on event.
15907
+ * @returns {handler} - The wrapped version of the handler.
15908
+ */
15909
+ appendListenerOnce(event, handler) {
15910
+ return this.once(event, handler);
15911
+ }
15912
+
15913
+ ///////////////////////////////////////////////
15914
+
15915
+ /**
15916
+ * Removes a previously registered event listener.
15917
+ *
15918
+ * @param {string} event - The name of the event to remove the handler from.
15919
+ * @param {handler} handler - The specific callback function to remove.
15920
+ */
15921
+ off(event, handler) {
15922
+ if (typeof event !== 'string')
15923
+ throw new TypeError('off(event, handler): event name must be a string');
15924
+ if (typeof handler !== 'function')
15925
+ throw new TypeError('off(event, handler): handler must be a function');
15926
+
15927
+ const listeners = this.#listeners.get(event);
15928
+ if (!Array.isArray(listeners)) return;
15929
+
15930
+ const index = listeners.findIndex((listener) => listener.handler === handler);
15931
+ if (index !== -1) listeners.splice(index, 1);
15932
+
15933
+ // Optionally clean up empty arrays (optional)
15934
+ if (listeners.length === 0) this.#listeners.delete(event);
15935
+ }
15936
+
15937
+ /**
15938
+ * Removes all event listeners of a specific type from the element.
15939
+ *
15940
+ * @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
15941
+ */
15942
+ offAll(event) {
15943
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
15944
+ this.#listeners.delete(event);
15945
+ }
15946
+
15947
+ /**
15948
+ * Removes all event listeners of all types from the element.
15949
+ */
15950
+ offAllTypes() {
15951
+ this.#listeners.clear();
15952
+ }
15953
+
15954
+ /////////////////////////////////////////////
15955
+
15956
+ /**
15957
+ * Returns the number of listeners for a given event.
15958
+ *
15959
+ * @param {string} event - The name of the event.
15960
+ * @returns {number} Number of listeners for the event.
15961
+ */
15962
+ listenerCount(event) {
15963
+ if (typeof event !== 'string')
15964
+ throw new TypeError('listenerCount(event): event name must be a string');
15965
+
15966
+ const listeners = this.#listeners.get(event);
15967
+ return Array.isArray(listeners) ? listeners.length : 0;
15968
+ }
15969
+
15970
+ /**
15971
+ * Returns a copy of the array of listeners for the specified event.
15972
+ *
15973
+ * @param {string} event - The name of the event.
15974
+ * @returns {handler[]} Array of listener functions.
15975
+ */
15976
+ listeners(event) {
15977
+ if (typeof event !== 'string')
15978
+ throw new TypeError('listeners(event): event name must be a string');
15979
+
15980
+ const listeners = this.#listeners.get(event);
15981
+ return Array.isArray(listeners)
15982
+ ? [...listeners]
15983
+ .filter((listener) => !listener.config.once)
15984
+ .map((listener) => listener.handler)
15985
+ : [];
15986
+ }
15987
+
15988
+ /**
15989
+ * Returns a copy of the array of listeners for the specified event.
15990
+ *
15991
+ * @param {string} event - The name of the event.
15992
+ * @returns {handler[]} Array of listener functions.
15993
+ */
15994
+ onceListeners(event) {
15995
+ if (typeof event !== 'string')
15996
+ throw new TypeError('onceListeners(event): event name must be a string');
15997
+
15998
+ const listeners = this.#listeners.get(event);
15999
+ return Array.isArray(listeners)
16000
+ ? [...listeners]
16001
+ .filter((listener) => listener.config.once)
16002
+ .map((listener) => listener.handler)
16003
+ : [];
16004
+ }
16005
+
16006
+ /**
16007
+ * Returns a copy of the internal listeners array for the specified event,
16008
+ * including wrapper functions like those used by `.once()`.
16009
+ * @param {string | symbol} event - The event name.
16010
+ * @returns {handler[]} An array of raw listener functions.
16011
+ */
16012
+ allListeners(event) {
16013
+ if (typeof event !== 'string')
16014
+ throw new TypeError('allListeners(event): event name must be a string');
16015
+ const listeners = this.#listeners.get(event);
16016
+ return Array.isArray(listeners) ? [...listeners].map((listener) => listener.handler) : [];
16017
+ }
16018
+
16019
+ /**
16020
+ * Returns an array of event names for which there are registered listeners.
16021
+ *
16022
+ * @returns {string[]} Array of registered event names.
16023
+ */
16024
+ eventNames() {
16025
+ return [...this.#listeners.keys()];
16026
+ }
16027
+
16028
+ /**
16029
+ * Emits an event, triggering all registered handlers for that event.
16030
+ *
16031
+ * @param {string} event - The event name to emit.
16032
+ * @param {...any} payload - Optional data to pass to each handler.
16033
+ * @returns {boolean} True if any listeners were called, false otherwise.
16034
+ */
16035
+ emit(event, ...payload) {
16036
+ if (typeof event !== 'string')
16037
+ throw new TypeError('emit(event, data): event name must be a string');
16038
+
16039
+ const listeners = this.#listeners.get(event);
16040
+ if (!Array.isArray(listeners) || listeners.length === 0) return false;
16041
+
16042
+ // Call all listeners with the provided data
16043
+ listeners.forEach((listener) => listener.handler(...payload));
16044
+ return true;
16045
+ }
16046
+
16047
+ ///////////////////////////////////
16048
+
16049
+ /**
16050
+ * Sets the maximum number of listeners per event before a warning is shown.
16051
+ *
16052
+ * @param {number} n - The maximum number of listeners.
16053
+ */
16054
+ setMaxListeners(n) {
16055
+ if (!Number.isInteger(n) || n < 0)
16056
+ throw new TypeError('setMaxListeners(n): n must be a non-negative integer');
16057
+ this.#maxListeners = n;
16058
+ }
16059
+
16060
+ /**
16061
+ * Gets the maximum number of listeners allowed per event.
16062
+ *
16063
+ * @returns {number} The maximum number of listeners.
16064
+ */
16065
+ getMaxListeners() {
16066
+ return this.#maxListeners;
16067
+ }
16068
+ }
16069
+
16070
+ /* harmony default export */ const libs_TinyEvents = (TinyEvents);
16071
+
15697
16072
  ;// ./src/v1/libs/TinySmartScroller.mjs
15698
16073
 
15699
16074
 
15700
16075
 
16076
+
15701
16077
  /**
15702
16078
  * Represents the dimensions of a DOM element.
15703
16079
  *
@@ -15744,71 +16120,267 @@ class UltraRandomMsgGen {
15744
16120
  class TinySmartScroller {
15745
16121
  static Utils = { ...collision_namespaceObject, TinyHtml: libs_TinyHtml };
15746
16122
 
15747
- /** @type {WeakMap<Element, NodeSizes>} */
15748
- #oldSizes = new WeakMap();
15749
- /** @type {WeakMap<Element, NodeSizes>} */
15750
- #newSizes = new WeakMap();
15751
-
15752
- /** @type {WeakMap<Element, boolean>} */
15753
- #newVisibles = new WeakMap();
15754
- /** @type {WeakMap<Element, boolean>} */
15755
- #oldVisibles = new WeakMap();
15756
-
15757
- /** @type {WeakMap<Element, boolean>} */
15758
- #newVisiblesByTime = new WeakMap();
15759
- /** @type {WeakMap<Element, boolean>} */
15760
- #oldVisiblesByTime = new WeakMap();
15761
-
15762
- /** @type {Record<string, ScrollListenersFunc[]>} */
15763
- #scrollListeners = {};
15764
-
15765
- /** @type {ResizeObserver|null} */
15766
- #resizeObserver = null;
15767
- /** @type {MutationObserver|null} */
15768
- #mutationObserver = null;
15769
-
15770
- /** @type {Set<string>} */
15771
- #loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
16123
+ #events = new libs_TinyEvents();
15772
16124
 
15773
- /** @type {null|EventListenerOrEventListenerObject} */
15774
- #handler = null;
16125
+ /**
16126
+ * Enables or disables throwing an error when the maximum number of listeners is exceeded.
16127
+ *
16128
+ * @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
16129
+ */
16130
+ setThrowOnMaxListeners(shouldThrow) {
16131
+ return this.#events.setThrowOnMaxListeners(shouldThrow);
16132
+ }
15775
16133
 
15776
- #isAtBottom = false;
15777
- #isAtTop = false;
15778
- #isAtCustomTop = false;
15779
- #isAtCustomBottom = false;
16134
+ /**
16135
+ * Checks whether an error will be thrown when the max listener limit is exceeded.
16136
+ *
16137
+ * @returns {boolean} True if an error will be thrown, false if only a warning is shown.
16138
+ */
16139
+ getThrowOnMaxListeners() {
16140
+ return this.#events.getThrowOnMaxListeners();
16141
+ }
15780
16142
 
15781
- #querySelector = '';
15782
- #useWindow = false;
15783
- #destroyed = false;
15784
- #scrollPaused = false;
15785
- #autoScrollBottom = false;
15786
- #observeMutations = false;
15787
- #preserveScrollOnLayoutShift = false;
15788
- #debounceTime = 0;
15789
- #elemAmount = 0;
15790
- #elemOldAmount = 0;
15791
- #lastKnownScrollBottomOffset = 0;
15792
- #extraScrollBoundary = 0;
16143
+ /////////////////////////////////////////////////////////////
15793
16144
 
15794
- /** @type {Set<string>} */
15795
- #attributeFilter;
16145
+ /**
16146
+ * Adds a listener to the beginning of the listeners array for the specified event.
16147
+ *
16148
+ * @param {string} event - Event name.
16149
+ * @param {ScrollListenersFunc} handler - The callback function.
16150
+ */
16151
+ prependListener(event, handler) {
16152
+ return this.#events.prependListener(event, handler);
16153
+ }
15796
16154
 
15797
- /** @type {Element} */
15798
- #target;
16155
+ /**
16156
+ * Adds a one-time listener to the beginning of the listeners array for the specified event.
16157
+ *
16158
+ * @param {string} event - Event name.
16159
+ * @param {ScrollListenersFunc} handler - The callback function.
16160
+ * @returns {ScrollListenersFunc} - The wrapped handler used internally.
16161
+ */
16162
+ prependListenerOnce(event, handler) {
16163
+ return this.#events.prependListenerOnce(event, handler);
16164
+ }
15799
16165
 
15800
- /** @type {Set<NodeSizesEvent>} */
15801
- #sizeFilter = new Set();
16166
+ //////////////////////////////////////////////////////////////////////
15802
16167
 
15803
16168
  /**
15804
- * Creates a new instance of TinySmartScroller, attaching scroll and resize observers to manage
15805
- * automatic scroll behaviors, layout shift correction, and visibility tracking.
16169
+ * Adds a event listener.
15806
16170
  *
15807
- * @param {Element|Window} target - The scroll container to monitor. Can be an element or `window`.
15808
- * @param {Object} [options={}] - Optional settings to configure scroll behavior.
15809
- * @param {number} [options.extraScrollBoundary=0] - Extra margin in pixels to extend scroll boundary detection.
15810
- * @param {boolean} [options.autoScrollBottom=true] - Whether to auto-scroll to bottom on layout updates.
15811
- * @param {boolean} [options.observeMutations=true] - Enables MutationObserver to detect DOM changes.
16171
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
16172
+ * @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
16173
+ */
16174
+ appendListener(event, handler) {
16175
+ return this.#events.appendListener(event, handler);
16176
+ }
16177
+
16178
+ /**
16179
+ * Registers an event listener that runs only once, then is removed.
16180
+ *
16181
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
16182
+ * @param {ScrollListenersFunc} handler - The callback function to run on event.
16183
+ * @returns {ScrollListenersFunc} - The wrapped version of the handler.
16184
+ */
16185
+ appendListenerOnce(event, handler) {
16186
+ return this.#events.appendListenerOnce(event, handler);
16187
+ }
16188
+
16189
+ /**
16190
+ * Adds a event listener.
16191
+ *
16192
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
16193
+ * @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
16194
+ */
16195
+ on(event, handler) {
16196
+ return this.#events.on(event, handler);
16197
+ }
16198
+
16199
+ /**
16200
+ * Registers an event listener that runs only once, then is removed.
16201
+ *
16202
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
16203
+ * @param {ScrollListenersFunc} handler - The callback function to run on event.
16204
+ * @returns {ScrollListenersFunc} - The wrapped version of the handler.
16205
+ */
16206
+ once(event, handler) {
16207
+ return this.#events.once(event, handler);
16208
+ }
16209
+
16210
+ ////////////////////////////////////////////////////////////////////
16211
+
16212
+ /**
16213
+ * Removes a previously registered event listener.
16214
+ *
16215
+ * @param {string} event - The name of the event to remove the handler from.
16216
+ * @param {ScrollListenersFunc} handler - The specific callback function to remove.
16217
+ */
16218
+ off(event, handler) {
16219
+ return this.#events.off(event, handler);
16220
+ }
16221
+
16222
+ /**
16223
+ * Removes all event listeners of a specific type from the element.
16224
+ *
16225
+ * @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
16226
+ */
16227
+ offAll(event) {
16228
+ return this.#events.offAll(event);
16229
+ }
16230
+
16231
+ /**
16232
+ * Removes all event listeners of all types from the element.
16233
+ */
16234
+ offAllTypes() {
16235
+ return this.#events.offAllTypes();
16236
+ }
16237
+
16238
+ ////////////////////////////////////////////////////////////
16239
+
16240
+ /**
16241
+ * Returns the number of listeners for a given event.
16242
+ *
16243
+ * @param {string} event - The name of the event.
16244
+ * @returns {number} Number of listeners for the event.
16245
+ */
16246
+ listenerCount(event) {
16247
+ return this.#events.listenerCount(event);
16248
+ }
16249
+
16250
+ /**
16251
+ * Returns a copy of the array of listeners for the specified event.
16252
+ *
16253
+ * @param {string} event - The name of the event.
16254
+ * @returns {ScrollListenersFunc[]} Array of listener functions.
16255
+ */
16256
+ listeners(event) {
16257
+ return this.#events.listeners(event);
16258
+ }
16259
+
16260
+ /**
16261
+ * Returns a copy of the array of listeners for the specified event.
16262
+ *
16263
+ * @param {string} event - The name of the event.
16264
+ * @returns {ScrollListenersFunc[]} Array of listener functions.
16265
+ */
16266
+ onceListeners(event) {
16267
+ return this.#events.onceListeners(event);
16268
+ }
16269
+
16270
+ /**
16271
+ * Returns a copy of the internal listeners array for the specified event,
16272
+ * including wrapper functions like those used by `.once()`.
16273
+ * @param {string | symbol} event - The event name.
16274
+ * @returns {ScrollListenersFunc[]} An array of raw listener functions.
16275
+ */
16276
+ allListeners(event) {
16277
+ return this.#events.allListeners(event);
16278
+ }
16279
+
16280
+ /**
16281
+ * Returns an array of event names for which there are registered listeners.
16282
+ *
16283
+ * @returns {string[]} Array of registered event names.
16284
+ */
16285
+ eventNames() {
16286
+ return this.#events.eventNames();
16287
+ }
16288
+
16289
+ //////////////////////////////////////////////////////
16290
+
16291
+ /**
16292
+ * Emits an event, triggering all registered handlers for that event.
16293
+ *
16294
+ * @param {string} event - The event name to emit.
16295
+ * @param {...any} payload - Optional data to pass to each handler.
16296
+ * @returns {boolean} True if any listeners were called, false otherwise.
16297
+ */
16298
+ emit(event, ...payload) {
16299
+ return this.#events.emit(event, ...payload);
16300
+ }
16301
+
16302
+ /**
16303
+ * Sets the maximum number of listeners per event before a warning is shown.
16304
+ *
16305
+ * @param {number} n - The maximum number of listeners.
16306
+ */
16307
+ setMaxListeners(n) {
16308
+ return this.#events.setMaxListeners(n);
16309
+ }
16310
+
16311
+ /**
16312
+ * Gets the maximum number of listeners allowed per event.
16313
+ *
16314
+ * @returns {number} The maximum number of listeners.
16315
+ */
16316
+ getMaxListeners() {
16317
+ return this.#events.getMaxListeners();
16318
+ }
16319
+
16320
+ ///////////////////////////////////////////////////
16321
+
16322
+ /** @type {WeakMap<Element, NodeSizes>} */
16323
+ #oldSizes = new WeakMap();
16324
+ /** @type {WeakMap<Element, NodeSizes>} */
16325
+ #newSizes = new WeakMap();
16326
+
16327
+ /** @type {WeakMap<Element, boolean>} */
16328
+ #newVisibles = new WeakMap();
16329
+ /** @type {WeakMap<Element, boolean>} */
16330
+ #oldVisibles = new WeakMap();
16331
+
16332
+ /** @type {WeakMap<Element, boolean>} */
16333
+ #newVisiblesByTime = new WeakMap();
16334
+ /** @type {WeakMap<Element, boolean>} */
16335
+ #oldVisiblesByTime = new WeakMap();
16336
+
16337
+ /** @type {ResizeObserver|null} */
16338
+ #resizeObserver = null;
16339
+ /** @type {MutationObserver|null} */
16340
+ #mutationObserver = null;
16341
+
16342
+ /** @type {Set<string>} */
16343
+ #loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
16344
+
16345
+ /** @type {null|EventListenerOrEventListenerObject} */
16346
+ #handler = null;
16347
+
16348
+ #isAtBottom = false;
16349
+ #isAtTop = false;
16350
+ #isAtCustomTop = false;
16351
+ #isAtCustomBottom = false;
16352
+
16353
+ #querySelector = '';
16354
+ #useWindow = false;
16355
+ #destroyed = false;
16356
+ #scrollPaused = false;
16357
+ #autoScrollBottom = false;
16358
+ #observeMutations = false;
16359
+ #preserveScrollOnLayoutShift = false;
16360
+ #debounceTime = 0;
16361
+ #elemAmount = 0;
16362
+ #elemOldAmount = 0;
16363
+ #lastKnownScrollBottomOffset = 0;
16364
+ #extraScrollBoundary = 0;
16365
+
16366
+ /** @type {Set<string>} */
16367
+ #attributeFilter;
16368
+
16369
+ /** @type {Element} */
16370
+ #target;
16371
+
16372
+ /** @type {Set<NodeSizesEvent>} */
16373
+ #sizeFilter = new Set();
16374
+
16375
+ /**
16376
+ * Creates a new instance of TinySmartScroller, attaching scroll and resize observers to manage
16377
+ * automatic scroll behaviors, layout shift correction, and visibility tracking.
16378
+ *
16379
+ * @param {Element|Window} target - The scroll container to monitor. Can be an element or `window`.
16380
+ * @param {Object} [options={}] - Optional settings to configure scroll behavior.
16381
+ * @param {number} [options.extraScrollBoundary=0] - Extra margin in pixels to extend scroll boundary detection.
16382
+ * @param {boolean} [options.autoScrollBottom=true] - Whether to auto-scroll to bottom on layout updates.
16383
+ * @param {boolean} [options.observeMutations=true] - Enables MutationObserver to detect DOM changes.
15812
16384
  * @param {boolean} [options.preserveScrollOnLayoutShift=true] - Prevents scroll jumps when layout changes.
15813
16385
  * @param {number} [options.debounceTime=100] - Debounce time in milliseconds for scroll events.
15814
16386
  * @param {string|null} [options.querySelector=null] - Optional CSS selector to filter observed child nodes.
@@ -16075,46 +16647,6 @@ class TinySmartScroller {
16075
16647
  this.#sizeFilter.delete(handler);
16076
16648
  }
16077
16649
 
16078
- /**
16079
- * Adds a scroll-related event listener.
16080
- *
16081
- * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
16082
- * @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
16083
- */
16084
- on(event, handler) {
16085
- if (this.#destroyed) return;
16086
- if (typeof event !== 'string')
16087
- throw new TypeError('on(event, handler): event name must be a string');
16088
- if (typeof handler !== 'function')
16089
- throw new TypeError('on(event, handler): handler must be a function');
16090
-
16091
- if (!this.#scrollListeners[event]) this.#scrollListeners[event] = [];
16092
- this.#scrollListeners[event].push(handler);
16093
- }
16094
-
16095
- /**
16096
- * Removes a previously registered scroll-related event listener.
16097
- *
16098
- * @param {string} event - The name of the event to remove the handler from.
16099
- * @param {ScrollListenersFunc} handler - The specific callback function to remove.
16100
- */
16101
- off(event, handler) {
16102
- if (this.#destroyed) return;
16103
- if (typeof event !== 'string')
16104
- throw new TypeError('off(event, handler): event name must be a string');
16105
- if (typeof handler !== 'function')
16106
- throw new TypeError('off(event, handler): handler must be a function');
16107
-
16108
- const listeners = this.#scrollListeners[event];
16109
- if (!Array.isArray(listeners)) return;
16110
-
16111
- const index = listeners.indexOf(handler);
16112
- if (index !== -1) listeners.splice(index, 1);
16113
-
16114
- // Optionally clean up empty arrays (optional)
16115
- if (listeners.length === 0) delete this.#scrollListeners[event];
16116
- }
16117
-
16118
16650
  /**
16119
16651
  * Checks which elements inside the target are currently visible and updates internal maps.
16120
16652
  *
@@ -16139,12 +16671,10 @@ class TinySmartScroller {
16139
16671
  *
16140
16672
  * @param {string} event - Event name.
16141
16673
  * @param {*} [payload] - Optional event data payload.
16674
+ * @deprecated - Use emit() instead.
16142
16675
  */
16143
16676
  _emit(event, payload) {
16144
- if (this.#destroyed) return;
16145
- if (typeof event !== 'string')
16146
- throw new TypeError('_emit(event, payload): event name must be a string');
16147
- (this.#scrollListeners[event] || []).forEach((fn) => fn(payload));
16677
+ this.emit(event, payload);
16148
16678
  }
16149
16679
 
16150
16680
  /**
@@ -16190,13 +16720,13 @@ class TinySmartScroller {
16190
16720
  this.#lastKnownScrollBottomOffset = scrollHeight - scrollTop - clientHeight;
16191
16721
 
16192
16722
  // Send results
16193
- this._emit('onScrollBoundary', { status: atResult, ...scrollResult, scrollCache });
16194
- this._emit('onExtraScrollBoundary', { status: atCustomResult, ...scrollResult, scrollCache });
16723
+ this.emit('onScrollBoundary', { status: atResult, ...scrollResult, scrollCache });
16724
+ this.emit('onExtraScrollBoundary', { status: atCustomResult, ...scrollResult, scrollCache });
16195
16725
 
16196
16726
  if (!this.#scrollPaused) {
16197
- this._emit('onAutoScroll', { ...scrollResult, scrollCache });
16727
+ this.emit('onAutoScroll', { ...scrollResult, scrollCache });
16198
16728
  } else {
16199
- this._emit('onScrollPause', { ...scrollResult, scrollCache });
16729
+ this.emit('onScrollPause', { ...scrollResult, scrollCache });
16200
16730
  }
16201
16731
  }
16202
16732
 
@@ -16662,7 +17192,7 @@ class TinySmartScroller {
16662
17192
  this.#oldVisiblesByTime = new WeakMap();
16663
17193
 
16664
17194
  // Cleans listeners and filters
16665
- this.#scrollListeners = {};
17195
+ this.#events.offAllTypes();
16666
17196
  this.#sizeFilter.clear();
16667
17197
  this.#loadTags.clear();
16668
17198
  }
@@ -17031,522 +17561,2141 @@ class TinyTextRangeEditor {
17031
17561
  }
17032
17562
 
17033
17563
  /**
17034
- * Wraps the current selection with a tag, optionally including attributes.
17564
+ * Wraps the current selection with a tag, optionally including attributes.
17565
+ *
17566
+ * @param {string} tagName - The tag name (e.g., `b`, `color`, etc.).
17567
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes for the opening tag.
17568
+ * - If an object: key-value pairs (e.g., `{ color: "red" }` → `color="red"`).
17569
+ * - If an array: boolean attributes (e.g., `["disabled", "readonly"]`).
17570
+ *
17571
+ * @returns {TinyTextRangeEditor}
17572
+ */
17573
+ wrapWithTag(tagName, attributes = {}) {
17574
+ if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17575
+ const attrStr = this._insertAttr(attributes);
17576
+ const openTag = attrStr
17577
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17578
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
17579
+ const closeTag = `${this.#openTag}/${tagName}${this.#closeTag}`;
17580
+ this.surroundSelection(openTag, closeTag);
17581
+ return this;
17582
+ }
17583
+
17584
+ /**
17585
+ * Inserts a tag with optional inner content.
17586
+ * @param {string} tagName - The tag to insert.
17587
+ * @param {string} [content=''] - Optional content between tags.
17588
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
17589
+ * @returns {TinyTextRangeEditor}
17590
+ */
17591
+ insertTag(tagName, content = '', attributes = {}) {
17592
+ if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17593
+ if (typeof content !== 'string') throw new TypeError('content must be a string.');
17594
+
17595
+ const attrStr = this._insertAttr(attributes);
17596
+ const open = attrStr
17597
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17598
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
17599
+ const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
17600
+
17601
+ this.insertText(`${open}${content}${close}`);
17602
+ return this;
17603
+ }
17604
+
17605
+ /**
17606
+ * Inserts a self-closing tag.
17607
+ * @param {string} tagName - The tag name.
17608
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
17609
+ * @returns {TinyTextRangeEditor}
17610
+ */
17611
+ insertSelfClosingTag(tagName, attributes = {}) {
17612
+ if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17613
+
17614
+ const attrStr = this._insertAttr(attributes);
17615
+ const tag = attrStr
17616
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17617
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
17618
+
17619
+ this.insertText(tag);
17620
+ return this;
17621
+ }
17622
+
17623
+ /**
17624
+ * Toggles a tag around the current selection.
17625
+ * Supports tags with attributes. If already wrapped, it unwraps.
17626
+ * @param {string} tagName - The tag to toggle.
17627
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes to apply when wrapping.
17628
+ * @returns {TinyTextRangeEditor}
17629
+ */
17630
+ toggleTag(tagName, attributes = {}) {
17631
+ if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17632
+ const selected = this.getSelectedText();
17633
+
17634
+ // Regex: opening tag with optional attributes, and closing tag
17635
+ const openRegex = new RegExp(`^\\[${tagName}(\\s+[^\\]]*)?\\]`);
17636
+ const closeRegex = new RegExp(`\\[/${tagName}\\]$`);
17637
+
17638
+ const hasOpen = openRegex.test(selected);
17639
+ const hasClose = closeRegex.test(selected);
17640
+
17641
+ if (hasOpen && hasClose) {
17642
+ const unwrapped = selected
17643
+ .replace(openRegex, '') // remove opening tag
17644
+ .replace(closeRegex, ''); // remove closing tag
17645
+ this.insertText(unwrapped);
17646
+ } else {
17647
+ const attrStr = this._insertAttr(attributes);
17648
+ const open = attrStr
17649
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17650
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
17651
+ const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
17652
+ this.insertText(`${open}${selected}${close}`);
17653
+ }
17654
+
17655
+ return this;
17656
+ }
17657
+ }
17658
+
17659
+ /* harmony default export */ const libs_TinyTextRangeEditor = (TinyTextRangeEditor);
17660
+
17661
+ ;// ./src/v1/libs/TinyClipboard.mjs
17662
+ /**
17663
+ * Utility class to handle clipboard operations for text and blob data.
17664
+ * Supports modern Clipboard API, custom platform, and legacy execCommand fallback.
17665
+ */
17666
+ class TinyClipboard {
17667
+ /**
17668
+ * Indicates whether the legacy `document.execCommand()` API is available.
17669
+ * Used as a fallback for clipboard operations when modern APIs are not supported.
17670
+ *
17671
+ * @type {boolean}
17672
+ */
17673
+ #existExecCommand = false;
17674
+
17675
+ /**
17676
+ * Indicates whether the modern Clipboard API (`navigator.clipboard`) is available.
17677
+ *
17678
+ * @type {boolean}
17679
+ */
17680
+ #existNavigator = false;
17681
+
17682
+ /**
17683
+ * Function used to copy plain text to the clipboard.
17684
+ * Can be overridden using `setCopyText()`.
17685
+ *
17686
+ * @type {((text: string) => Promise<void>) | null}
17687
+ */
17688
+ #copyText = null;
17689
+
17690
+ /**
17691
+ * Function used to copy a Blob (binary data) to the clipboard.
17692
+ * Can be overridden using `setCopyBlob()`.
17693
+ *
17694
+ * @type {((blob: Blob) => Promise<void>) | null}
17695
+ */
17696
+ #copyBlob = null;
17697
+
17698
+ /**
17699
+ * Constructs a new TinyClipboard instance.
17700
+ * Automatically detects and configures available clipboard APIs.
17701
+ */
17702
+ constructor() {
17703
+ // Whether the Clipboard API is available.
17704
+ if (typeof navigator.clipboard !== 'undefined' && navigator.clipboard !== null) {
17705
+ this.#existNavigator = true;
17706
+ this.#copyText = (text) => navigator.clipboard.writeText(text);
17707
+ this.#copyBlob = (blob) =>
17708
+ navigator.clipboard.write([
17709
+ new ClipboardItem({
17710
+ [blob.type]: blob,
17711
+ }),
17712
+ ]);
17713
+ }
17714
+
17715
+ /**
17716
+ * @type {boolean}
17717
+ * Whether the legacy execCommand API is available.
17718
+ */
17719
+ this.#existExecCommand =
17720
+ typeof document.execCommand !== 'undefined' && document.execCommand !== null;
17721
+ }
17722
+
17723
+ /**
17724
+ * Override the default text copy behavior.
17725
+ * This allows you to provide your own clipboard implementation or
17726
+ * integrate with external systems like Capacitor or Electron.
17727
+ *
17728
+ * @param {(text: string) => Promise<void>} callback - The function to use for copying text.
17729
+ * @throws {TypeError} If the callback is not a function.
17730
+ */
17731
+ setCopyText(callback) {
17732
+ if (typeof callback !== 'function')
17733
+ throw new TypeError('setCopyText expected a function that returns Promise<void>.');
17734
+ this.#copyText = callback;
17735
+ }
17736
+
17737
+ /**
17738
+ * Override the default blob copy behavior.
17739
+ * This allows you to provide a custom clipboard handling method for blob data.
17740
+ *
17741
+ * @param {(blob: Blob) => Promise<void>} callback - The function to use for copying blob data.
17742
+ * @throws {TypeError} If the callback is not a function.
17743
+ */
17744
+ setCopyBlob(callback) {
17745
+ if (typeof callback !== 'function')
17746
+ throw new TypeError('setCopyBlob expected a function that returns Promise<void>.');
17747
+ this.#copyBlob = callback;
17748
+ }
17749
+
17750
+ /**
17751
+ * Copy a plain text string to the clipboard.
17752
+ * Uses modern or legacy fallback.
17753
+ *
17754
+ * @param {string} text - The text string to be copied.
17755
+ * @returns {Promise<void>} A promise resolving when the text is copied or boolean for legacy.
17756
+ */
17757
+ copyText(text) {
17758
+ if (typeof text !== 'string') throw new TypeError('copyText expected a string.');
17759
+ // Clipboard API
17760
+ if (this.#copyText) return this.#copyText(text);
17761
+ // Classic API
17762
+ else if (this.#existExecCommand) {
17763
+ const host = document.body;
17764
+ const copyInput = document.createElement('input');
17765
+ copyInput.style.position = 'fixed';
17766
+ copyInput.style.opacity = '0';
17767
+ copyInput.value = text;
17768
+ host.append(copyInput);
17769
+
17770
+ copyInput.select();
17771
+ copyInput.setSelectionRange(0, 99999);
17772
+ document.execCommand('Copy');
17773
+ copyInput.remove();
17774
+ return new Promise((resolve) => resolve(undefined));
17775
+ }
17776
+ throw new Error('Clipboard API not found!');
17777
+ }
17778
+
17779
+ /**
17780
+ * Copy a Blob (binary data) to the clipboard.
17781
+ *
17782
+ * @param {Blob} blob - The blob object to copy.
17783
+ * @returns {Promise<void>} A promise that resolves when the blob is copied or null on fallback.
17784
+ */
17785
+ copyBlob(blob) {
17786
+ if (!(blob instanceof Blob)) throw new TypeError('copyBlob expected a Blob instance.');
17787
+ return new Promise((resolve, reject) => {
17788
+ if (this.#copyBlob) {
17789
+ return this.#copyBlob(blob).then(resolve).catch(reject);
17790
+ }
17791
+ throw new Error('Clipboard API not found!');
17792
+ });
17793
+ }
17794
+
17795
+ /**
17796
+ * Internal: Handle getting blob data from a clipboard item.
17797
+ *
17798
+ * @private
17799
+ * @param {string} type - The MIME type to fetch.
17800
+ * @param {ClipboardItem} clipboardItem - Clipboard item instance.
17801
+ * @returns {Promise<Blob>} A promise that resolves with the Blob.
17802
+ */
17803
+ _handleBlob(type, clipboardItem) {
17804
+ return clipboardItem.getType(type);
17805
+ }
17806
+
17807
+ /**
17808
+ * Internal: Handle getting plain text from a clipboard item.
17809
+ *
17810
+ * @private
17811
+ * @param {string} type - The MIME type (should be 'text/plain').
17812
+ * @param {ClipboardItem} clipboardItem - Clipboard item instance.
17813
+ * @returns {Promise<string>} A promise that resolves with the text content.
17814
+ */
17815
+ _handleText(type, clipboardItem) {
17816
+ return this._handleBlob(type, clipboardItem).then((blob) => blob.text());
17817
+ }
17818
+
17819
+ /**
17820
+ * Read clipboard data based on filters like type, mime, index.
17821
+ *
17822
+ * @param {number|null} [index=0] - Item index or null for all.
17823
+ * @param {'text'|'custom'|null} [type=null] - Data type to filter.
17824
+ * @param {string|null} [mimeFormat=null] - MIME type or prefix.
17825
+ * @param {boolean} [fixValue=false] - If true, exact match on MIME type.
17826
+ * @returns {Promise<Blob|string|Array<Blob|string>|null>} A promise resolving with matching data.
17827
+ */
17828
+ _readData(index = 0, type = null, mimeFormat = null, fixValue = false) {
17829
+ return new Promise((resolve, reject) => {
17830
+ this._read(index)
17831
+ .then((items) => {
17832
+ if (!items) return resolve(null);
17833
+ /** @type {Array<Blob|string>} */
17834
+ const finalResult = [];
17835
+
17836
+ // Complete task
17837
+ let continueLoop = true;
17838
+
17839
+ /**
17840
+ * @param {string} mimeType
17841
+ * @param {ClipboardItem} item
17842
+ */
17843
+ const completeTask = async (mimeType, item) => {
17844
+ if (!continueLoop) return;
17845
+ // Custom
17846
+ if (
17847
+ (type === null || type === 'custom') &&
17848
+ typeof mimeFormat === 'string' &&
17849
+ ((!fixValue && mimeType.startsWith(mimeFormat)) ||
17850
+ (fixValue && mimeType === mimeFormat))
17851
+ ) {
17852
+ continueLoop = false;
17853
+ const result = await this._handleBlob(mimeType, item);
17854
+ if (result) finalResult.push(result);
17855
+ }
17856
+
17857
+ // Text
17858
+ else if ((type === null || type === 'text') && mimeType === 'text/plain') {
17859
+ continueLoop = false;
17860
+ const result = await this._handleText(mimeType, item);
17861
+ if (result) finalResult.push(result);
17862
+ }
17863
+
17864
+ // Blob
17865
+ else if (type === null) {
17866
+ continueLoop = false;
17867
+ const result = await this._handleBlob(mimeType, item);
17868
+ if (result) finalResult.push(result);
17869
+ }
17870
+ };
17871
+
17872
+ /** @type {Promise<void>[]} */
17873
+ const promises = [];
17874
+
17875
+ /**
17876
+ * Read Item
17877
+ * @param {ClipboardItem | ClipboardItems} item
17878
+ */
17879
+ const readItem = (item) => {
17880
+ if (!(item instanceof ClipboardItem))
17881
+ throw new Error('Expected ClipboardItem when reading data.');
17882
+ for (const tIndex in item.types) promises.push(completeTask(item.types[tIndex], item));
17883
+ };
17884
+
17885
+ // Specific Item
17886
+ if (
17887
+ typeof index === 'number' &&
17888
+ !Number.isNaN(index) &&
17889
+ Number.isFinite(index) &&
17890
+ index > -1
17891
+ ) {
17892
+ readItem(items);
17893
+ Promise.all(promises)
17894
+ .then(() => {
17895
+ if (finalResult[0]) resolve(finalResult[0]);
17896
+ else resolve(null);
17897
+ })
17898
+ .catch(reject);
17899
+ }
17900
+
17901
+ // All
17902
+ else if (Array.isArray(items)) {
17903
+ for (const tIndex in items) readItem(items[tIndex]);
17904
+ Promise.all(promises)
17905
+ .then(() => resolve(finalResult))
17906
+ .catch(reject);
17907
+ }
17908
+ })
17909
+ // Fail
17910
+ .catch(reject);
17911
+ });
17912
+ }
17913
+
17914
+ /**
17915
+ * Read plain text from the clipboard (single item by index).
17916
+ *
17917
+ * @param {number} [index=0] - The index of the clipboard item to read.
17918
+ * @returns {Promise<string|null>} A promise that resolves to the clipboard text or null.
17919
+ */
17920
+ async readText(index = 0) {
17921
+ const value = await this._readData(index, 'text');
17922
+ if (typeof value !== 'string') throw new Error('Failed to read text: expected string result.');
17923
+ return value;
17924
+ }
17925
+
17926
+ /**
17927
+ * Read custom clipboard data based on MIME type from a specific index.
17928
+ *
17929
+ * @param {string|null} [mimeFormat=null] - MIME prefix to match (e.g., "image/").
17930
+ * @param {boolean} [fixValue=false] - If true, matches exact MIME instead of prefix.
17931
+ * @param {number} [index=0] - Clipboard item index.
17932
+ * @returns {Promise<Blob|null>} A promise resolving with a blob or null.
17933
+ */
17934
+ async readCustom(mimeFormat = null, fixValue = false, index = 0) {
17935
+ const value = await this._readData(index, 'custom', mimeFormat, fixValue);
17936
+ if (!(value instanceof Blob)) throw new Error('Failed to read custom data: expected Blob.');
17937
+ return value;
17938
+ }
17939
+
17940
+ /**
17941
+ * Read all available plain text entries from the clipboard.
17942
+ *
17943
+ * @returns {Promise<string[]>} A promise resolving to an array of strings or null.
17944
+ */
17945
+ async readAllTexts() {
17946
+ const values = await this._readData(null, 'text');
17947
+ if (!Array.isArray(values))
17948
+ throw new Error('Expected array of strings when reading all texts.');
17949
+ if (!values.every((value) => typeof value === 'string'))
17950
+ throw new Error('Some values returned were not strings.');
17951
+ return values;
17952
+ }
17953
+
17954
+ /**
17955
+ * Read all clipboard data matching a specific custom MIME type.
17956
+ *
17957
+ * @param {string|null} [mimeFormat=null] - MIME prefix or exact type.
17958
+ * @param {boolean} [fixValue=false] - Match prefix or exact MIME.
17959
+ * @returns {Promise<Blob[]>} A promise resolving with array of Blobs or null.
17960
+ */
17961
+ async readAllCustom(mimeFormat = null, fixValue = false) {
17962
+ const values = await this._readData(null, 'custom', mimeFormat, fixValue);
17963
+ if (!Array.isArray(values))
17964
+ throw new Error('Expected array of blobs when reading all custom items.');
17965
+ if (!values.every((value) => value instanceof Blob))
17966
+ throw new Error('Some values returned were not Blob instances.');
17967
+ return values;
17968
+ }
17969
+
17970
+ /**
17971
+ * Read all clipboard data as Blob or text depending on type.
17972
+ *
17973
+ * @param {'text'|'custom'|null} [type=null] - The type of data to retrieve.
17974
+ * @param {string|null} [mimeFormat=null] - The MIME type or prefix to match.
17975
+ * @returns {Promise<Array<Blob|string>>} A promise resolving with matching data array.
17976
+ */
17977
+ async readAllData(type = null, mimeFormat = null) {
17978
+ const value = await this._readData(null, type, mimeFormat);
17979
+ if (!Array.isArray(value)) throw new Error('Expected array result when reading all data.');
17980
+ return value;
17981
+ }
17982
+
17983
+ /**
17984
+ * Read clipboard data at a specific index or all if null.
17985
+ *
17986
+ * @param {number|null} index - Index of the item to retrieve or null to get all.
17987
+ * @returns {Promise<ClipboardItem|ClipboardItems|null>} A promise resolving with a clipboard item or array of items.
17988
+ */
17989
+ _read(index) {
17990
+ return new Promise((resolve, reject) => {
17991
+ if (!this.#existNavigator) reject(new Error('Clipboard API not found!'));
17992
+ navigator.clipboard
17993
+ .read()
17994
+ .then((items) => {
17995
+ // Index is number
17996
+ if (typeof index === 'number') {
17997
+ if (Number.isNaN(index) || !Number.isFinite(index) || index < 0)
17998
+ throw new Error(`Invalid index value: ${index}`);
17999
+ if (items[index]) resolve(items[index]);
18000
+ // Not found
18001
+ else resolve(null);
18002
+ }
18003
+ // Get All
18004
+ resolve(items);
18005
+ })
18006
+ .catch(reject);
18007
+ });
18008
+ }
18009
+
18010
+ /**
18011
+ * Read clipboard data at a specific index.
18012
+ *
18013
+ * @param {number} index - Index of the item to retrieve
18014
+ * @returns {Promise<ClipboardItem|null>} A promise resolving with a clipboard item.
18015
+ */
18016
+ async readIndex(index) {
18017
+ const value = await this._read(index);
18018
+ if (value !== null && !(value instanceof ClipboardItem))
18019
+ throw new Error(`Value at index ${index} is not a ClipboardItem.`);
18020
+ return value;
18021
+ }
18022
+
18023
+ /**
18024
+ * Read all clipboard content without any filters.
18025
+ *
18026
+ * @returns {Promise<ClipboardItems>} A promise resolving with all clipboard items.
18027
+ */
18028
+ async readAll() {
18029
+ const value = await this._read(null);
18030
+ if (!Array.isArray(value)) throw new Error('Expected array result from clipboard read.');
18031
+ for (const item of value) {
18032
+ if (!(item instanceof ClipboardItem))
18033
+ throw new Error('Invalid item type found in clipboard result.');
18034
+ }
18035
+ return value;
18036
+ }
18037
+
18038
+ /**
18039
+ * Returns whether the legacy `document.execCommand()` API is available.
18040
+ * This can be used to determine if a fallback clipboard method is usable.
18041
+ *
18042
+ * @returns {boolean} True if `document.execCommand` is available.
18043
+ */
18044
+ isExecCommandAvailable() {
18045
+ return this.#existExecCommand;
18046
+ }
18047
+
18048
+ /**
18049
+ * Returns whether the modern Clipboard API (`navigator.clipboard`) is available.
18050
+ * Useful to know if full clipboard features can be accessed.
18051
+ *
18052
+ * @returns {boolean} True if `navigator.clipboard` is available.
18053
+ */
18054
+ isNavigatorClipboardAvailable() {
18055
+ return this.#existNavigator;
18056
+ }
18057
+
18058
+ /**
18059
+ * Returns the function used to copy plain text to the clipboard.
18060
+ * This function may be built-in or set manually via `setCopyText`.
18061
+ *
18062
+ * @returns {((text: string) => Promise<void>) | null} The current text copy function or null if unavailable.
18063
+ */
18064
+ getCopyTextFunc() {
18065
+ return this.#copyText;
18066
+ }
18067
+
18068
+ /**
18069
+ * Returns the function used to copy Blob (binary data) to the clipboard.
18070
+ * This function may be built-in or set manually via `setCopyBlob`.
18071
+ *
18072
+ * @returns {((blob: Blob) => Promise<void>) | null} The current blob copy function or null if unavailable.
18073
+ */
18074
+ getCopyBlobFunc() {
18075
+ return this.#copyBlob;
18076
+ }
18077
+ }
18078
+
18079
+ /* harmony default export */ const libs_TinyClipboard = (TinyClipboard);
18080
+
18081
+ ;// ./src/v1/libs/TinyColorConverter.mjs
18082
+ /**
18083
+ * Represents a color in RGBA format.
18084
+ * Each element must be a number between 0 and 255.
18085
+ * The fourth value represents the alpha (transparency) channel.
18086
+ *
18087
+ * @typedef {number[]} RgbaColor
18088
+ * @property {number} 0 - Red component (0–255)
18089
+ * @property {number} 1 - Green component (0–255)
18090
+ * @property {number} 2 - Blue component (0–255)
18091
+ * @property {number} 3 - Alpha component (0–255)
18092
+ */
18093
+
18094
+ /**
18095
+ * Represents a color in RGB format.
18096
+ * Each element must be a number between 0 and 255.
18097
+ *
18098
+ * @typedef {number[]} RgbColor
18099
+ * @property {number} 0 - Red component (0–255)
18100
+ * @property {number} 1 - Green component (0–255)
18101
+ * @property {number} 2 - Blue component (0–255)
18102
+ */
18103
+
18104
+ /**
18105
+ * Represents a color in HSLA format.
18106
+ * The fourth value represents the alpha (transparency) channel.
18107
+ *
18108
+ * @typedef {number[]} HslaColor
18109
+ * @property {number} 0 - Hue (0–360)
18110
+ * @property {number} 1 - Saturation (0–100)
18111
+ * @property {number} 2 - Lightness (0–100)
18112
+ * @property {number} 3 - Alpha component (0–255)
18113
+ */
18114
+
18115
+ /**
18116
+ * Represents a color in HSL format.
18117
+ *
18118
+ * @typedef {number[]} HslColor
18119
+ * @property {number} 0 - Hue (0–360)
18120
+ * @property {number} 1 - Saturation (0–100)
18121
+ * @property {number} 2 - Lightness (0–100)
18122
+ */
18123
+
18124
+ /**
18125
+ * Represents a hex color.
18126
+ *
18127
+ * @typedef {string} HexColor
18128
+ */
18129
+
18130
+ /**
18131
+ * A union type representing various accepted color formats.
18132
+ * Can be a hex color string, a numeric value, or an array-based RGB/RGBA representation.
18133
+ *
18134
+ * @typedef {HexColor | number | RgbColor | RgbaColor} ColorTypes
18135
+ */
18136
+
18137
+ /**
18138
+ * @typedef {Object} RgbaResult
18139
+ * @property {number} r - Red component (0–255)
18140
+ * @property {number} g - Green component (0–255)
18141
+ * @property {number} b - Blue component (0–255)
18142
+ * @property {number} a - Alpha component (0–255)
18143
+ */
18144
+
18145
+ /**
18146
+ * @typedef {Object} RgbResult
18147
+ * @property {number} r - Red component (0–255)
18148
+ * @property {number} g - Green component (0–255)
18149
+ * @property {number} b - Blue component (0–255)
18150
+ */
18151
+
18152
+ /**
18153
+ * @typedef {Object} HexResult
18154
+ * @property {string} hex - Hex color
18155
+ */
18156
+
18157
+ /**
18158
+ * @typedef {Object} HslResult
18159
+ * @property {number} h - Hue (0–360)
18160
+ * @property {number} s - Saturation (0–100)
18161
+ * @property {number} l - Lightness (0–100)
18162
+ */
18163
+
18164
+ /**
18165
+ * A class that allows converting colors between all common formats.
18166
+ */
18167
+ class TinyColorConverter {
18168
+ /**
18169
+ * Generates a smooth gradient of colors based on sine wave patterns.
18170
+ *
18171
+ * @see {@link https://www.npmjs.com/package/rainbow-colors-array} Code Reference
18172
+ * @param {number} [len=24] - The number of colors to generate.
18173
+ * @param {'rgb'|'hex'|'hsl'} [type='rgb'] - The format of the colors returned: `'rgb'`, `'hex'`, or `'hsl'`.
18174
+ * @param {boolean} [pastel=false] - If true, generates pastel tones by adjusting the intensity and offset.
18175
+ * @returns {Array<RgbResult|HexResult|HslResult>} An array of color values in the selected format:
18176
+ */
18177
+ static _rca(len, type, pastel) {
18178
+ let eq1 = 127;
18179
+ let eq2 = 128;
18180
+ if (len === undefined) {
18181
+ len = 24;
18182
+ }
18183
+ if (type === undefined) {
18184
+ type = 'rgb';
18185
+ }
18186
+ if (pastel === true) {
18187
+ eq1 = 55;
18188
+ eq2 = 200;
18189
+ }
18190
+ const frequency = (Math.PI * 2) / len;
18191
+
18192
+ const cvparr = [];
18193
+ for (let i = 0; i < len; ++i) {
18194
+ const red = Math.sin(frequency * i + 2) * eq1 + eq2;
18195
+ const green = Math.sin(frequency * i + 0) * eq1 + eq2;
18196
+ const blue = Math.sin(frequency * i + 4) * eq1 + eq2;
18197
+
18198
+ switch (type) {
18199
+ case 'hex':
18200
+ cvparr.push({ hex: this.rgbToHex(Math.round(red), Math.round(green), Math.round(blue)) });
18201
+ break;
18202
+ case 'rgb':
18203
+ cvparr.push({ r: red, g: green, b: blue });
18204
+ break;
18205
+ case 'hsl':
18206
+ const [h, s, l] = this.rgbaToHsl(Math.round(red), Math.round(green), Math.round(blue));
18207
+ cvparr.push({ h, s, l });
18208
+ break;
18209
+ }
18210
+ }
18211
+
18212
+ return cvparr;
18213
+ }
18214
+
18215
+ /**
18216
+ * Generates a smooth gradient of colors based on sine wave patterns.
18217
+ *
18218
+ * @param {number} [len=24] - The number of colors to generate.
18219
+ * @param {boolean} [pastel=false] - If true, generates pastel tones by adjusting the intensity and offset.
18220
+ * @returns {RgbResult[]} An array of rgb color values.
18221
+ */
18222
+ static rcaRgb(len, pastel) {
18223
+ return /** @type {RgbResult[]} */ (TinyColorConverter._rca(len, 'rgb', pastel));
18224
+ }
18225
+
18226
+ /**
18227
+ * Generates a smooth gradient of colors based on sine wave patterns.
18228
+ *
18229
+ * @param {number} [len=24] - The number of colors to generate.
18230
+ * @param {boolean} [pastel=false] - If true, generates pastel tones by adjusting the intensity and offset.
18231
+ * @returns {HslResult[]} An array of hsl color values.
18232
+ */
18233
+ static rcaHsl(len, pastel) {
18234
+ return /** @type {HslResult[]} */ (TinyColorConverter._rca(len, 'hsl', pastel));
18235
+ }
18236
+
18237
+ /**
18238
+ * Generates a smooth gradient of colors based on sine wave patterns.
18239
+ *
18240
+ * @param {number} [len=24] - The number of colors to generate.
18241
+ * @param {boolean} [pastel=false] - If true, generates pastel tones by adjusting the intensity and offset.
18242
+ * @returns {HexResult[]} An array of hex color values.
18243
+ */
18244
+ static rcaHex(len, pastel) {
18245
+ return /** @type {HexResult[]} */ (TinyColorConverter._rca(len, 'hex', pastel));
18246
+ }
18247
+
18248
+ /**
18249
+ * Generates a random color in hexadecimal format.
18250
+ *
18251
+ * @returns {HexColor} A hex color string (e.g. `#a3e5f2`).
18252
+ */
18253
+ static randomColor() {
18254
+ const hex = Math.floor(Math.random() * 0x1000000).toString(16);
18255
+ return `#${hex.padStart(6, '0')}`;
18256
+ }
18257
+
18258
+ /**
18259
+ * Parses input into RGBA array.
18260
+ * @param {ColorTypes} input
18261
+ * @param {boolean} isHsl
18262
+ * @returns {RgbaColor}
18263
+ */
18264
+ static parseInput(input, isHsl) {
18265
+ if (typeof input === 'string') {
18266
+ input = input.trim().toLowerCase();
18267
+ if (input.startsWith('#')) return this.hexToRgba(input);
18268
+ if (input.startsWith('rgb')) return this.rgbStringToRgbaArray(input);
18269
+ if (input.startsWith('hsl')) return this.hslStringToRgbaArray(input);
18270
+ }
18271
+ if (typeof input === 'number') return this.intToRgba(input);
18272
+ if (
18273
+ Array.isArray(input) &&
18274
+ (input.length === 3 || input.length === 4) &&
18275
+ input.every((item) => typeof item === 'number')
18276
+ ) {
18277
+ if (isHsl) {
18278
+ const [h, s, l, a2] = input;
18279
+ if (h <= 360 && s <= 100 && l <= 100) return this.hslToRgba(h, s, l, a2);
18280
+ }
18281
+ return [...input, 1].slice(0, 4);
18282
+ }
18283
+
18284
+ throw new Error('Unsupported color format.');
18285
+ }
18286
+
18287
+ // HSL Color
18288
+
18289
+ /**
18290
+ * Converts hsl to integer.
18291
+ * @param {number} h - Hue (0–360)
18292
+ * @param {number} s - Saturation (0–100)
18293
+ * @param {number} l - Lightness (0–100)
18294
+ * @returns {number}
18295
+ */
18296
+ static hslToInt(h, s, l) {
18297
+ const [r, g, b] = TinyColorConverter.hslToRgba(h, s, l);
18298
+ return TinyColorConverter.rgbToInt(r, g, b);
18299
+ }
18300
+
18301
+ /**
18302
+ * Converts hsl to hex.
18303
+ * @param {number} h - Hue (0–360)
18304
+ * @param {number} s - Saturation (0–100)
18305
+ * @param {number} l - Lightness (0–100)
18306
+ * @returns {HexColor}
18307
+ */
18308
+ static hslToHex(h, s, l) {
18309
+ const [r, g, b] = TinyColorConverter.hslToRgba(h, s, l);
18310
+ return TinyColorConverter.rgbToHex(r, g, b);
18311
+ }
18312
+
18313
+ /**
18314
+ * Converts hsl(a) string to RGBA array.
18315
+ * @param {string} hsl
18316
+ * @returns {RgbaColor}
18317
+ */
18318
+ static hslStringToRgbaArray(hsl) {
18319
+ const match = hsl.match(/[\d.]+/g)?.map(Number);
18320
+ if (!match || match.length < 3) return [0, 0, 0, 1];
18321
+ const [h, s, l, a = 1] = match;
18322
+ return this.hslToRgba(h, s, l, a);
18323
+ }
18324
+
18325
+ /**
18326
+ * Converts HSL or HSLA to RGBA.
18327
+ * @param {number} h - Hue (0–360)
18328
+ * @param {number} s - Saturation (0–100)
18329
+ * @param {number} l - Lightness (0–100)
18330
+ * @param {number} [a=1] - Alpha (0–1)
18331
+ * @returns {RgbaColor}
18332
+ */
18333
+ static hslToRgba(h, s, l, a = 1) {
18334
+ s /= 100;
18335
+ l /= 100;
18336
+ /** @type {(n: number) => number} */
18337
+ const k = (n) => (n + h / 30) % 12;
18338
+ const a_ = s * Math.min(l, 1 - l);
18339
+ /** @type {(n: number) => number} */
18340
+ const f = (n) => l - a_ * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
18341
+ return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255), a];
18342
+ }
18343
+
18344
+ /**
18345
+ * Converts HSL or HSLA to RGB.
18346
+ * @param {number} h - Hue (0–360)
18347
+ * @param {number} s - Saturation (0–100)
18348
+ * @param {number} l - Lightness (0–100)
18349
+ * @param {number} [a=1] - Alpha (0–1)
18350
+ * @returns {RgbColor}
18351
+ */
18352
+ static hslToRgb(h, s, l, a = 1) {
18353
+ return TinyColorConverter.hslToRgba(h, s, l, a).slice(0, 3);
18354
+ }
18355
+
18356
+ // Hex Color
18357
+
18358
+ /**
18359
+ * Converts hex to integer.
18360
+ * @param {HexColor} hex
18361
+ * @returns {number}
18362
+ */
18363
+ static hexToInt(hex) {
18364
+ return parseInt(hex.replace(/^#/, ''), 16);
18365
+ }
18366
+
18367
+ /**
18368
+ * Converts hex string to HSL array.
18369
+ * @param {HexColor} hex
18370
+ * @returns {HslColor}
18371
+ */
18372
+ static hexToHsl(hex) {
18373
+ const [r, g, b, a] = TinyColorConverter.hexToRgba(hex);
18374
+ return TinyColorConverter.rgbaToHsl(r, g, b, a);
18375
+ }
18376
+
18377
+ /**
18378
+ * Converts hex string to HSL array.
18379
+ * @param {HexColor} hex
18380
+ * @returns {HslaColor}
18381
+ */
18382
+ static hexToHsla(hex) {
18383
+ const [r, g, b, a] = TinyColorConverter.hexToRgba(hex);
18384
+ return TinyColorConverter.rgbaToHsla(r, g, b, a);
18385
+ }
18386
+
18387
+ /**
18388
+ * Converts hex string to RGBA array.
18389
+ * @param {HexColor} hex
18390
+ * @returns {RgbaColor}
18391
+ */
18392
+ static hexToRgba(hex) {
18393
+ hex = hex.replace(/^#/, '');
18394
+ if (hex.length === 3)
18395
+ hex = hex
18396
+ .split('')
18397
+ .map((c) => c + c)
18398
+ .join('');
18399
+ const intVal = parseInt(hex, 16);
18400
+ const r = (intVal >> 16) & 255;
18401
+ const g = (intVal >> 8) & 255;
18402
+ const b = intVal & 255;
18403
+ return [r, g, b, 1];
18404
+ }
18405
+
18406
+ /**
18407
+ * Converts HEX to RGB.
18408
+ * @param {HexColor} hex
18409
+ * @returns {RgbColor}
18410
+ */
18411
+ static hexToRgb(hex) {
18412
+ return this.hexToRgba(hex).slice(0, 3);
18413
+ }
18414
+
18415
+ // RGBA Color
18416
+
18417
+ /**
18418
+ * Converts RGB to HEX.
18419
+ * @param {number} r
18420
+ * @param {number} g
18421
+ * @param {number} b
18422
+ * @returns {HexColor}
18423
+ */
18424
+ static rgbToHex(r, g, b) {
18425
+ return '#' + [r, g, b].map((v) => v.toString(16).padStart(2, '0')).join('');
18426
+ }
18427
+
18428
+ /**
18429
+ * Converts RGB to integer.
18430
+ * @param {number} r
18431
+ * @param {number} g
18432
+ * @param {number} b
18433
+ * @returns {number}
18434
+ */
18435
+ static rgbToInt(r, g, b) {
18436
+ return (r << 16) | (g << 8) | b;
18437
+ }
18438
+
18439
+ /**
18440
+ * Converts RGBA to HSLA.
18441
+ * @param {number} r
18442
+ * @param {number} g
18443
+ * @param {number} b
18444
+ * @param {number} [a=1]
18445
+ * @returns {HslaColor}
18446
+ */
18447
+ static rgbaToHsla(r, g, b, a = 1) {
18448
+ r /= 255;
18449
+ g /= 255;
18450
+ b /= 255;
18451
+ const max = Math.max(r, g, b);
18452
+ const min = Math.min(r, g, b);
18453
+ let h = 0,
18454
+ s = 0,
18455
+ l = (max + min) / 2;
18456
+ const d = max - min;
18457
+
18458
+ if (d !== 0) {
18459
+ s = d / (1 - Math.abs(2 * l - 1));
18460
+ switch (max) {
18461
+ case r:
18462
+ h = (g - b) / d + (g < b ? 6 : 0);
18463
+ break;
18464
+ case g:
18465
+ h = (b - r) / d + 2;
18466
+ break;
18467
+ case b:
18468
+ h = (r - g) / d + 4;
18469
+ break;
18470
+ }
18471
+ h *= 60;
18472
+ }
18473
+
18474
+ return [Math.round(h), Math.round(s * 100), Math.round(l * 100), a];
18475
+ }
18476
+
18477
+ /**
18478
+ * Converts RGBA to HSL.
18479
+ * @param {number} r
18480
+ * @param {number} g
18481
+ * @param {number} b
18482
+ * @param {number} [a=1]
18483
+ * @returns {HslColor}
18484
+ */
18485
+ static rgbaToHsl(r, g, b, a) {
18486
+ return this.rgbaToHsla(r, g, b, a).slice(0, 3);
18487
+ }
18488
+
18489
+ /**
18490
+ * Converts rgb(a) string to RGBA array.
18491
+ * @param {string} rgb
18492
+ * @returns {RgbaColor}
18493
+ */
18494
+ static rgbStringToRgbaArray(rgb) {
18495
+ const match = rgb.match(/[\d.]+/g)?.map(Number);
18496
+ if (!match) return [];
18497
+ return [...match, 1].slice(0, 4);
18498
+ }
18499
+
18500
+ // Integer Color
18501
+
18502
+ /**
18503
+ * Converts integer color to HSL.
18504
+ * @param {number} int
18505
+ * @returns {HslColor}
18506
+ */
18507
+ static intToHsl(int) {
18508
+ const [r, g, b, a] = TinyColorConverter.intToRgba(int);
18509
+ return TinyColorConverter.rgbaToHsl(r, g, b, a);
18510
+ }
18511
+
18512
+ /**
18513
+ * Converts integer color to HSL.
18514
+ * @param {number} int
18515
+ * @returns {HslaColor}
18516
+ */
18517
+ static intToHsla(int) {
18518
+ const [r, g, b, a] = TinyColorConverter.intToRgba(int);
18519
+ return TinyColorConverter.rgbaToHsla(r, g, b, a);
18520
+ }
18521
+
18522
+ /**
18523
+ * Converts integer color to hex.
18524
+ * @param {number} int
18525
+ * @returns {HexColor}
18526
+ */
18527
+ static intToHex(int) {
18528
+ return '#' + int.toString(16).padStart(6, '0');
18529
+ }
18530
+
18531
+ /**
18532
+ * Converts an integer (0xRRGGBB) to RGBA.
18533
+ * @param {number} value
18534
+ * @returns {RgbaColor}
18535
+ */
18536
+ static intToRgba(value) {
18537
+ const r = (value >> 16) & 255;
18538
+ const g = (value >> 8) & 255;
18539
+ const b = value & 255;
18540
+ return [r, g, b, 1];
18541
+ }
18542
+
18543
+ // Class Script
18544
+
18545
+ /** @type {ColorTypes} */
18546
+ #original = '#000000';
18547
+ /** @type {RgbaColor} */
18548
+ #rgba = [0, 0, 0, 0];
18549
+
18550
+ #checkIsHsl;
18551
+
18552
+ /**
18553
+ * @param {ColorTypes|null} [input=null] - Any valid color (hex, rgb string, rgba string, hsl(a), css name, array or int).
18554
+ * @param {boolean} [checkIsHsl=false]
18555
+ */
18556
+ constructor(input = null, checkIsHsl = false) {
18557
+ this.#checkIsHsl = checkIsHsl;
18558
+ if (typeof input !== 'undefined' && input !== null) this.setColor(input);
18559
+ }
18560
+
18561
+ /**
18562
+ * @param {ColorTypes} input - Any valid color (hex, rgb string, rgba string, hsl(a), css name, array or int).
18563
+ */
18564
+ setColor(input) {
18565
+ this.#original = input;
18566
+ const isHsl =
18567
+ this.#checkIsHsl &&
18568
+ Array.isArray(input) &&
18569
+ input[0] <= 360 &&
18570
+ input[1] <= 100 &&
18571
+ input[2] <= 100;
18572
+ this.#rgba = TinyColorConverter.parseInput(input, isHsl);
18573
+ }
18574
+
18575
+ /**
18576
+ * Returns HSLA array.
18577
+ * @returns {HslaColor}
18578
+ */
18579
+ toHslaArray() {
18580
+ const [r, g, b, a] = this.#rgba;
18581
+ return TinyColorConverter.rgbaToHsla(r, g, b, a);
18582
+ }
18583
+
18584
+ /**
18585
+ * Returns RGB string.
18586
+ * @returns {string}
18587
+ */
18588
+ toHslString() {
18589
+ const [r, g, b] = this.#rgba;
18590
+ const [h, s, l] = TinyColorConverter.rgbaToHsl(r, g, b);
18591
+ return `hsl(${h}, ${s}%, ${l}%)`;
18592
+ }
18593
+
18594
+ /**
18595
+ * Returns RGBA string.
18596
+ * @returns {string}
18597
+ */
18598
+ toHslaString() {
18599
+ const [r, g, b, a] = this.#rgba;
18600
+ const [h, s, l, a2] = TinyColorConverter.rgbaToHsla(r, g, b, a);
18601
+ return `hsla(${h}, ${s}%, ${l}%, ${a2 ?? 1})`;
18602
+ }
18603
+
18604
+ /**
18605
+ * Returns RGBA array.
18606
+ * @returns {RgbaColor}
18607
+ */
18608
+ toRgbaArray() {
18609
+ return [...this.#rgba];
18610
+ }
18611
+
18612
+ /**
18613
+ * Returns RGB string.
18614
+ * @returns {string}
18615
+ */
18616
+ toRgbString() {
18617
+ const [r, g, b] = this.#rgba;
18618
+ return `rgb(${r}, ${g}, ${b})`;
18619
+ }
18620
+
18621
+ /**
18622
+ * Returns RGBA string.
18623
+ * @returns {string}
18624
+ */
18625
+ toRgbaString() {
18626
+ const [r, g, b, a] = this.#rgba;
18627
+ return `rgba(${r}, ${g}, ${b}, ${a ?? 1})`;
18628
+ }
18629
+
18630
+ /**
18631
+ * Returns hex color.
18632
+ * @returns {HexColor}
18633
+ */
18634
+ toHex() {
18635
+ const [r, g, b] = this.#rgba;
18636
+ return TinyColorConverter.rgbToHex(r, g, b);
18637
+ }
18638
+
18639
+ /**
18640
+ * Returns color as integer.
18641
+ * @returns {number}
18642
+ */
18643
+ toInt() {
18644
+ const [r, g, b] = this.#rgba;
18645
+ return TinyColorConverter.rgbToInt(r, g, b);
18646
+ }
18647
+
18648
+ /**
18649
+ * Returns the original input.
18650
+ * @returns {ColorTypes}
18651
+ */
18652
+ getOriginal() {
18653
+ return this.#original;
18654
+ }
18655
+ }
18656
+
18657
+ /* harmony default export */ const libs_TinyColorConverter = (TinyColorConverter);
18658
+
18659
+ ;// ./src/v1/libs/TinyTimeout.mjs
18660
+ /**
18661
+ * A utility class to manage dynamically adjusted timeouts based on how often
18662
+ * each unique ID is triggered. Also provides polling support for asynchronous conditions.
18663
+ */
18664
+ class TinyTimeout {
18665
+ /** @type {boolean} Whether this instance has been destroyed. */
18666
+ #isDestroyed = false;
18667
+
18668
+ /** @type {boolean} Whether to allow auto-updating an ID's timeout config if `value` changes. */
18669
+ #allowAutoConfigChange;
18670
+
18671
+ /** @type {number} The interval time (ms) used to decrement cooldown counters. */
18672
+ #cooldownWatcherTime;
18673
+
18674
+ /** @type {NodeJS.Timeout|null} Reference to the internal cooldown interval. */
18675
+ #cooldownWatcher = null;
18676
+
18677
+ /**
18678
+ * Internal map that keeps track of how many times each ID has been triggered,
18679
+ * along with the base multiplier used to calculate delays.
18680
+ *
18681
+ * @type {Map<string, { value: number, now: number }>}
18682
+ */
18683
+ #timeoutFixer = new Map();
18684
+
18685
+ /**
18686
+ * Creates a new instance of TinyTimeout.
18687
+ *
18688
+ * @param {Object} [options={}] Optional configuration object.
18689
+ * @param {number} [options.cooldownWatcherTime=5000] Interval in milliseconds for reducing `now` counters.
18690
+ * @param {boolean} [options.allowAutoConfigChange=false] Whether to allow auto value changes for existing IDs.
18691
+ */
18692
+ constructor({ cooldownWatcherTime = 5000, allowAutoConfigChange = false } = {}) {
18693
+ if (!Number.isFinite(cooldownWatcherTime) || cooldownWatcherTime <= 0)
18694
+ throw new TypeError(`Expected 'cooldownWatcherTime' to be a positive number.`);
18695
+ if (typeof allowAutoConfigChange !== 'boolean')
18696
+ throw new TypeError(`Expected 'allowAutoConfigChange' to be a boolean.`);
18697
+ this.#cooldownWatcherTime = cooldownWatcherTime;
18698
+ this.#allowAutoConfigChange = allowAutoConfigChange;
18699
+ this.setCooldownWatcherTime(cooldownWatcherTime);
18700
+ }
18701
+
18702
+ /**
18703
+ * Whether this instance has been destroyed and is no longer usable.
18704
+ *
18705
+ * @returns {boolean}
18706
+ */
18707
+ isDestroyed() {
18708
+ return this.#isDestroyed;
18709
+ }
18710
+
18711
+ /**
18712
+ * Whether auto config change is enabled.
18713
+ *
18714
+ * @returns {boolean}
18715
+ */
18716
+ getAllowAutoConfigChange() {
18717
+ return this.#allowAutoConfigChange;
18718
+ }
18719
+
18720
+ /**
18721
+ * Gets the interval time used for cooldown decrementing.
18722
+ *
18723
+ * @returns {number}
18724
+ */
18725
+ getCooldownWatcherTime() {
18726
+ return this.#cooldownWatcherTime;
18727
+ }
18728
+
18729
+ /**
18730
+ * Sets whether to allow auto-updating an ID's timeout config if `value` changes.
18731
+ *
18732
+ * @param {boolean} value
18733
+ */
18734
+ setAllowAutoConfigChange(value) {
18735
+ if (typeof value !== 'boolean') throw new TypeError(`Expected 'value' to be a boolean.`);
18736
+ this.#allowAutoConfigChange = value;
18737
+ }
18738
+
18739
+ /**
18740
+ * Sets the cooldown watcher interval time.
18741
+ * Automatically resets the interval if it was already running.
18742
+ *
18743
+ * @param {number} value
18744
+ */
18745
+ setCooldownWatcherTime(value) {
18746
+ if (this.#isDestroyed) throw new Error('TinyTimeout has been destroyed.');
18747
+ if (!Number.isFinite(value) || value <= 0)
18748
+ throw new TypeError(`Expected 'value' to be a positive number.`);
18749
+
18750
+ this.#cooldownWatcherTime = value;
18751
+
18752
+ if (this.#cooldownWatcher) clearInterval(this.#cooldownWatcher);
18753
+ this.#cooldownWatcher = setInterval(() => {
18754
+ this.#timeoutFixer.forEach((data) => {
18755
+ if (data.now > 0) data.now--;
18756
+ });
18757
+ }, this.#cooldownWatcherTime);
18758
+ }
18759
+
18760
+ /**
18761
+ * Schedules a callback using a dynamically adjusted timeout based on usage frequency.
18762
+ * The more often an ID is triggered, the longer the timeout becomes,
18763
+ * scaled by the provided `value`. Optionally, the delay can be limited by `limit`.
18764
+ *
18765
+ * @param {string} id - A unique identifier to track timeout usage.
18766
+ * @param {Function} callback - The function to execute after the delay.
18767
+ * @param {number} value - Base delay multiplier in milliseconds.
18768
+ * @param {number|null} [limit=null] - Optional maximum delay cap.
18769
+ * @returns {number} Handle to the scheduled timeout.
18770
+ * @throws {Error} Throws if the instance has been destroyed or arguments are invalid.
18771
+ */
18772
+ set(id, callback, value, limit = null) {
18773
+ if (this.#isDestroyed) throw new Error('TinyTimeout has been destroyed.');
18774
+ if (typeof id !== 'string' || id.trim() === '')
18775
+ throw new TypeError(`Expected 'id' to be a non-empty string.`);
18776
+ if (typeof callback !== 'function')
18777
+ throw new TypeError(`Expected 'callback' to be a function.`);
18778
+ if (!Number.isFinite(value) || value < 0)
18779
+ throw new TypeError(`Expected 'value' to be a non-negative number.`);
18780
+ if (limit !== null && (!Number.isFinite(limit) || limit < 0))
18781
+ throw new TypeError(`Expected 'limit' to be null or a non-negative number.`);
18782
+
18783
+ let entry = this.#timeoutFixer.get(id);
18784
+ if (!entry || (this.#allowAutoConfigChange && value !== entry.value)) {
18785
+ entry = { value, now: 0 };
18786
+ this.#timeoutFixer.set(id, entry);
18787
+ }
18788
+
18789
+ const delay = entry.value * entry.now;
18790
+ entry.now++;
18791
+
18792
+ return setTimeout(callback, typeof limit === 'number' ? Math.min(delay, limit) : delay);
18793
+ }
18794
+
18795
+ /**
18796
+ * Waits until a provided function returns `true`, checking repeatedly at the defined interval.
18797
+ * Useful for polling asynchronous conditions.
18798
+ *
18799
+ * @param {() => boolean} getValue - A function that returns `true` when the condition is met.
18800
+ * @param {number} [checkInterval=100] - How often (in ms) to check the condition.
18801
+ * @returns {Promise<void>} Resolves when the condition is met.
18802
+ * @throws {TypeError} If arguments are invalid.
18803
+ */
18804
+ static waitForTrue(getValue, checkInterval = 100) {
18805
+ if (typeof getValue !== 'function')
18806
+ throw new TypeError(`Expected 'getValue' to be a function.`);
18807
+ if (!Number.isFinite(checkInterval) || checkInterval <= 0)
18808
+ throw new TypeError(`Expected 'checkInterval' to be a positive number.`);
18809
+
18810
+ return new Promise((resolve) => {
18811
+ const interval = setInterval(() => {
18812
+ if (getValue()) {
18813
+ clearInterval(interval);
18814
+ resolve();
18815
+ }
18816
+ }, checkInterval);
18817
+ });
18818
+ }
18819
+
18820
+ /**
18821
+ * Instance version of `waitForTrue`, which defaults to using the instance's
18822
+ * cooldownWatcherTime if not explicitly provided.
18823
+ *
18824
+ * @param {() => boolean} getValue - A function that returns `true` when the condition is met.
18825
+ * @param {number|null} [checkInterval=100] - How often (in ms) to check the condition.
18826
+ * @returns {Promise<void>} Resolves when the condition is met.
18827
+ * @throws {Error} If the instance is destroyed or arguments are invalid.
18828
+ */
18829
+ waitForTrue(getValue, checkInterval = 100) {
18830
+ if (this.#isDestroyed) throw new Error('TinyTimeout has been destroyed.');
18831
+ if (typeof getValue !== 'function')
18832
+ throw new TypeError(`Expected 'getValue' to be a function.`);
18833
+ if (checkInterval !== null && (!Number.isFinite(checkInterval) || checkInterval <= 0))
18834
+ throw new TypeError(`Expected 'checkInterval' to be null or a positive number.`);
18835
+ return TinyTimeout.waitForTrue(getValue, checkInterval ?? this.#cooldownWatcherTime);
18836
+ }
18837
+
18838
+ /**
18839
+ * Cleans up all internal references and stops the cooldown watcher.
18840
+ * After calling this, the instance becomes unusable.
18841
+ */
18842
+ destroy() {
18843
+ if (this.#isDestroyed) return;
18844
+ this.#isDestroyed = true;
18845
+ if (this.#cooldownWatcher) clearInterval(this.#cooldownWatcher);
18846
+ this.#cooldownWatcher = null;
18847
+ this.#timeoutFixer.clear();
18848
+ }
18849
+ }
18850
+
18851
+ /* harmony default export */ const libs_TinyTimeout = (TinyTimeout);
18852
+
18853
+ ;// ./src/v1/libs/TinyLocalStorage.mjs
18854
+
18855
+
18856
+
18857
+ /** @type {Map<any, EncodeFn>} */
18858
+ const customEncoders = new Map();
18859
+
18860
+ /** @type {Map<any, DecodeFn>} */
18861
+ const customDecoders = new Map();
18862
+
18863
+ /**
18864
+ * A function that encodes a value into a serializable JSON-compatible format.
18865
+ *
18866
+ * @callback EncodeFn
18867
+ * @param {any} value - The value to encode.
18868
+ * @param {encodeSpecialJson} encodeSpecialJson - Recursive encoder helper.
18869
+ * @returns {any} The encoded value.
18870
+ */
18871
+
18872
+ /**
18873
+ * An object that defines how to check and decode a specific serialized type.
18874
+ *
18875
+ * @typedef {Object} DecodeFn
18876
+ * @property {(value: any) => any} check - Checks if the value matches the custom encoded structure.
18877
+ * @property {(value: any, decodeSpecialJson: decodeSpecialJson) => any} decode - Decodes the structure back into its original form.
18878
+ */
18879
+
18880
+ /**
18881
+ * Encodes extended JSON-compatible structures recursively.
18882
+ * @callback encodeSpecialJson
18883
+ * @param {any} value
18884
+ * @returns {any}
18885
+ */
18886
+
18887
+ /**
18888
+ * Decodes extended JSON-compatible structures recursively.
18889
+ * @callback decodeSpecialJson
18890
+ * @param {any} value
18891
+ * @returns {any}
18892
+ */
18893
+
18894
+ /**
18895
+ * Represents a value that can be safely stored and restored using JSON in `localStorage`,
18896
+ * including structures like arrays, plain objects, Map and Set.
18897
+ *
18898
+ * - `Record<string|number|symbol, any>` → plain object (e.g., `{ key: value }`)
18899
+ * - `any[]` → array of any JSON-serializable values
18900
+ * - `Map<string|number|symbol, any>` → converted to `{ __map__: true, data: [[k, v], ...] }`
18901
+ * - `Set<any>` → converted to `{ __set__: true, data: [v1, v2, ...] }`
18902
+ *
18903
+ * These conversions allow complex structures to be restored after JSON serialization.
18904
+ *
18905
+ * @typedef {(Record<string|number|symbol, any> | any[] | Map<string|number|symbol, any> | Set<any>)} LocalStorageJsonValue
18906
+ */
18907
+
18908
+ /**
18909
+ * A powerful wrapper for Web Storage (`localStorage` or `sessionStorage`) that supports
18910
+ * type-safe methods and full JSON-like structure encoding and decoding.
18911
+ *
18912
+ * `TinyLocalStorage` allows storing and retrieving complex types such as:
18913
+ * - `Map`, `Set`
18914
+ * - `Date`, `RegExp`
18915
+ * - `BigInt`, `Symbol`
18916
+ * - `undefined`, `null`
18917
+ * - Plain objects and arrays
18918
+ *
18919
+ * Includes:
18920
+ * - Type-specific `set` and `get` methods (`setDate`, `getBool`, etc.)
18921
+ * - `getValue()` to retrieve any structure regardless of type
18922
+ * - Auto-encoding/decoding with support for custom types via `registerJsonType`
18923
+ * - Built-in event system (`TinyEvents`) to listen for changes
18924
+ * - Optional fallback values on decoding errors
18925
+ *
18926
+ * Supports registering and unregistering custom types via:
18927
+ * - `registerJsonType(...)`
18928
+ * - `deleteJsonType(...)`
18929
+ *
18930
+ * This class is suitable for applications that require structured persistence in the browser.
18931
+ */
18932
+ class TinyLocalStorage {
18933
+ /** @typedef {import('./TinyEvents.mjs').handler} handler */
18934
+
18935
+ #events = new libs_TinyEvents();
18936
+
18937
+ /**
18938
+ * Enables or disables throwing an error when the maximum number of listeners is exceeded.
18939
+ *
18940
+ * @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
18941
+ */
18942
+ setThrowOnMaxListeners(shouldThrow) {
18943
+ return this.#events.setThrowOnMaxListeners(shouldThrow);
18944
+ }
18945
+
18946
+ /**
18947
+ * Checks whether an error will be thrown when the max listener limit is exceeded.
18948
+ *
18949
+ * @returns {boolean} True if an error will be thrown, false if only a warning is shown.
18950
+ */
18951
+ getThrowOnMaxListeners() {
18952
+ return this.#events.getThrowOnMaxListeners();
18953
+ }
18954
+
18955
+ /////////////////////////////////////////////////////////////
18956
+
18957
+ /**
18958
+ * Adds a listener to the beginning of the listeners array for the specified event.
18959
+ *
18960
+ * @param {string} event - Event name.
18961
+ * @param {handler} handler - The callback function.
18962
+ */
18963
+ prependListener(event, handler) {
18964
+ return this.#events.prependListener(event, handler);
18965
+ }
18966
+
18967
+ /**
18968
+ * Adds a one-time listener to the beginning of the listeners array for the specified event.
18969
+ *
18970
+ * @param {string} event - Event name.
18971
+ * @param {handler} handler - The callback function.
18972
+ * @returns {handler} - The wrapped handler used internally.
18973
+ */
18974
+ prependListenerOnce(event, handler) {
18975
+ return this.#events.prependListenerOnce(event, handler);
18976
+ }
18977
+
18978
+ //////////////////////////////////////////////////////////////////////
18979
+
18980
+ /**
18981
+ * Adds a event listener.
18982
+ *
18983
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
18984
+ * @param {handler} handler - Callback function to be called when event fires.
18985
+ */
18986
+ appendListener(event, handler) {
18987
+ return this.#events.appendListener(event, handler);
18988
+ }
18989
+
18990
+ /**
18991
+ * Registers an event listener that runs only once, then is removed.
18992
+ *
18993
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
18994
+ * @param {handler} handler - The callback function to run on event.
18995
+ * @returns {handler} - The wrapped version of the handler.
18996
+ */
18997
+ appendListenerOnce(event, handler) {
18998
+ return this.#events.appendListenerOnce(event, handler);
18999
+ }
19000
+
19001
+ /**
19002
+ * Adds a event listener.
19003
+ *
19004
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
19005
+ * @param {handler} handler - Callback function to be called when event fires.
19006
+ */
19007
+ on(event, handler) {
19008
+ return this.#events.on(event, handler);
19009
+ }
19010
+
19011
+ /**
19012
+ * Registers an event listener that runs only once, then is removed.
19013
+ *
19014
+ * @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
19015
+ * @param {handler} handler - The callback function to run on event.
19016
+ * @returns {handler} - The wrapped version of the handler.
19017
+ */
19018
+ once(event, handler) {
19019
+ return this.#events.once(event, handler);
19020
+ }
19021
+
19022
+ ////////////////////////////////////////////////////////////////////
19023
+
19024
+ /**
19025
+ * Removes a previously registered event listener.
17035
19026
  *
17036
- * @param {string} tagName - The tag name (e.g., `b`, `color`, etc.).
17037
- * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes for the opening tag.
17038
- * - If an object: key-value pairs (e.g., `{ color: "red" }` → `color="red"`).
17039
- * - If an array: boolean attributes (e.g., `["disabled", "readonly"]`).
19027
+ * @param {string} event - The name of the event to remove the handler from.
19028
+ * @param {handler} handler - The specific callback function to remove.
19029
+ */
19030
+ off(event, handler) {
19031
+ return this.#events.off(event, handler);
19032
+ }
19033
+
19034
+ /**
19035
+ * Removes all event listeners of a specific type from the element.
17040
19036
  *
17041
- * @returns {TinyTextRangeEditor}
19037
+ * @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
17042
19038
  */
17043
- wrapWithTag(tagName, attributes = {}) {
17044
- if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17045
- const attrStr = this._insertAttr(attributes);
17046
- const openTag = attrStr
17047
- ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17048
- : `${this.#openTag}${tagName}${this.#closeTag}`;
17049
- const closeTag = `${this.#openTag}/${tagName}${this.#closeTag}`;
17050
- this.surroundSelection(openTag, closeTag);
17051
- return this;
19039
+ offAll(event) {
19040
+ return this.#events.offAll(event);
17052
19041
  }
17053
19042
 
17054
19043
  /**
17055
- * Inserts a tag with optional inner content.
17056
- * @param {string} tagName - The tag to insert.
17057
- * @param {string} [content=''] - Optional content between tags.
17058
- * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
17059
- * @returns {TinyTextRangeEditor}
19044
+ * Removes all event listeners of all types from the element.
17060
19045
  */
17061
- insertTag(tagName, content = '', attributes = {}) {
17062
- if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17063
- if (typeof content !== 'string') throw new TypeError('content must be a string.');
19046
+ offAllTypes() {
19047
+ return this.#events.offAllTypes();
19048
+ }
17064
19049
 
17065
- const attrStr = this._insertAttr(attributes);
17066
- const open = attrStr
17067
- ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17068
- : `${this.#openTag}${tagName}${this.#closeTag}`;
17069
- const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
19050
+ ////////////////////////////////////////////////////////////
17070
19051
 
17071
- this.insertText(`${open}${content}${close}`);
17072
- return this;
19052
+ /**
19053
+ * Returns the number of listeners for a given event.
19054
+ *
19055
+ * @param {string} event - The name of the event.
19056
+ * @returns {number} Number of listeners for the event.
19057
+ */
19058
+ listenerCount(event) {
19059
+ return this.#events.listenerCount(event);
17073
19060
  }
17074
19061
 
17075
19062
  /**
17076
- * Inserts a self-closing tag.
17077
- * @param {string} tagName - The tag name.
17078
- * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
17079
- * @returns {TinyTextRangeEditor}
19063
+ * Returns a copy of the array of listeners for the specified event.
19064
+ *
19065
+ * @param {string} event - The name of the event.
19066
+ * @returns {handler[]} Array of listener functions.
17080
19067
  */
17081
- insertSelfClosingTag(tagName, attributes = {}) {
17082
- if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
19068
+ listeners(event) {
19069
+ return this.#events.listeners(event);
19070
+ }
17083
19071
 
17084
- const attrStr = this._insertAttr(attributes);
17085
- const tag = attrStr
17086
- ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17087
- : `${this.#openTag}${tagName}${this.#closeTag}`;
19072
+ /**
19073
+ * Returns a copy of the array of listeners for the specified event.
19074
+ *
19075
+ * @param {string} event - The name of the event.
19076
+ * @returns {handler[]} Array of listener functions.
19077
+ */
19078
+ onceListeners(event) {
19079
+ return this.#events.onceListeners(event);
19080
+ }
17088
19081
 
17089
- this.insertText(tag);
17090
- return this;
19082
+ /**
19083
+ * Returns a copy of the internal listeners array for the specified event,
19084
+ * including wrapper functions like those used by `.once()`.
19085
+ * @param {string | symbol} event - The event name.
19086
+ * @returns {handler[]} An array of raw listener functions.
19087
+ */
19088
+ allListeners(event) {
19089
+ return this.#events.allListeners(event);
17091
19090
  }
17092
19091
 
17093
19092
  /**
17094
- * Toggles a tag around the current selection.
17095
- * Supports tags with attributes. If already wrapped, it unwraps.
17096
- * @param {string} tagName - The tag to toggle.
17097
- * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes to apply when wrapping.
17098
- * @returns {TinyTextRangeEditor}
19093
+ * Returns an array of event names for which there are registered listeners.
19094
+ *
19095
+ * @returns {string[]} Array of registered event names.
17099
19096
  */
17100
- toggleTag(tagName, attributes = {}) {
17101
- if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
17102
- const selected = this.getSelectedText();
19097
+ eventNames() {
19098
+ return this.#events.eventNames();
19099
+ }
17103
19100
 
17104
- // Regex: opening tag with optional attributes, and closing tag
17105
- const openRegex = new RegExp(`^\\[${tagName}(\\s+[^\\]]*)?\\]`);
17106
- const closeRegex = new RegExp(`\\[/${tagName}\\]$`);
19101
+ //////////////////////////////////////////////////////
17107
19102
 
17108
- const hasOpen = openRegex.test(selected);
17109
- const hasClose = closeRegex.test(selected);
19103
+ /**
19104
+ * Emits an event, triggering all registered handlers for that event.
19105
+ *
19106
+ * @param {string} event - The event name to emit.
19107
+ * @param {...any} payload - Optional data to pass to each handler.
19108
+ * @returns {boolean} True if any listeners were called, false otherwise.
19109
+ */
19110
+ emit(event, ...payload) {
19111
+ return this.#events.emit(event, ...payload);
19112
+ }
17110
19113
 
17111
- if (hasOpen && hasClose) {
17112
- const unwrapped = selected
17113
- .replace(openRegex, '') // remove opening tag
17114
- .replace(closeRegex, ''); // remove closing tag
17115
- this.insertText(unwrapped);
17116
- } else {
17117
- const attrStr = this._insertAttr(attributes);
17118
- const open = attrStr
17119
- ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
17120
- : `${this.#openTag}${tagName}${this.#closeTag}`;
17121
- const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
17122
- this.insertText(`${open}${selected}${close}`);
17123
- }
19114
+ /**
19115
+ * Sets the maximum number of listeners per event before a warning is shown.
19116
+ *
19117
+ * @param {number} n - The maximum number of listeners.
19118
+ */
19119
+ setMaxListeners(n) {
19120
+ return this.#events.setMaxListeners(n);
19121
+ }
17124
19122
 
17125
- return this;
19123
+ /**
19124
+ * Gets the maximum number of listeners allowed per event.
19125
+ *
19126
+ * @returns {number} The maximum number of listeners.
19127
+ */
19128
+ getMaxListeners() {
19129
+ return this.#events.getMaxListeners();
17126
19130
  }
17127
- }
17128
19131
 
17129
- /* harmony default export */ const libs_TinyTextRangeEditor = (TinyTextRangeEditor);
19132
+ ///////////////////////////////////////////////////
17130
19133
 
17131
- ;// ./src/v1/libs/TinyClipboard.mjs
17132
- /**
17133
- * Utility class to handle clipboard operations for text and blob data.
17134
- * Supports modern Clipboard API, custom platform, and legacy execCommand fallback.
17135
- */
17136
- class TinyClipboard {
17137
19134
  /**
17138
- * Indicates whether the legacy `document.execCommand()` API is available.
17139
- * Used as a fallback for clipboard operations when modern APIs are not supported.
19135
+ * Registers a new JSON-serializable type with its encoder and decoder.
17140
19136
  *
17141
- * @type {boolean}
19137
+ * @param {any} type - The type or primitive type name (e.g. `"bigint"`, `"symbol"`, etc).
19138
+ * @param {EncodeFn} encodeFn - The function that encodes the value.
19139
+ * @param {DecodeFn} decodeFn - An object with `check` and `decode` methods for restoring the value.
17142
19140
  */
17143
- #existExecCommand = false;
19141
+ static registerJsonType(type, encodeFn, decodeFn) {
19142
+ customEncoders.set(type, encodeFn);
19143
+ customDecoders.set(type, decodeFn);
19144
+ }
17144
19145
 
17145
19146
  /**
17146
- * Indicates whether the modern Clipboard API (`navigator.clipboard`) is available.
19147
+ * Removes a previously registered custom type from the encoding/decoding system.
17147
19148
  *
17148
- * @type {boolean}
19149
+ * @param {string} type - The primitive name or constructor reference used in registration.
17149
19150
  */
17150
- #existNavigator = false;
19151
+ static deleteJsonType(type) {
19152
+ customEncoders.delete(type);
19153
+ customDecoders.delete(type);
19154
+ }
19155
+
19156
+ //////////////////////////////////////////////////////
17151
19157
 
17152
19158
  /**
17153
- * Function used to copy plain text to the clipboard.
17154
- * Can be overridden using `setCopyText()`.
19159
+ * Recursively serializes a value to a JSON-compatible format.
17155
19160
  *
17156
- * @type {((text: string) => Promise<void>) | null}
19161
+ * This includes custom types (via `registerJsonType`), plus support for:
19162
+ * - `undefined` → `{ __undefined__: true }`
19163
+ * - `null` → `{ __null__: true }`
19164
+ *
19165
+ * @type {encodeSpecialJson}
17157
19166
  */
17158
- #copyText = null;
19167
+ static encodeSpecialJson(value) {
19168
+ if (typeof value === 'undefined') return { __undefined__: true };
19169
+ if (value === null) return { __null__: true };
19170
+ for (const [type, encoder] of customEncoders.entries()) {
19171
+ if ((typeof type !== 'string' && value instanceof type) || typeof value === type) {
19172
+ return encoder(value, TinyLocalStorage.encodeSpecialJson);
19173
+ }
19174
+ }
19175
+
19176
+ if (Array.isArray(value)) {
19177
+ return value.map(TinyLocalStorage.encodeSpecialJson);
19178
+ }
19179
+
19180
+ if (isJsonObject(value)) {
19181
+ const encoded = {};
19182
+ for (const key in value) {
19183
+ // @ts-ignore
19184
+ encoded[key] = TinyLocalStorage.encodeSpecialJson(value[key]);
19185
+ }
19186
+ return encoded;
19187
+ }
19188
+
19189
+ return value;
19190
+ }
17159
19191
 
17160
19192
  /**
17161
- * Function used to copy a Blob (binary data) to the clipboard.
17162
- * Can be overridden using `setCopyBlob()`.
19193
+ * Recursively deserializes a JSON-compatible value into its original structure.
17163
19194
  *
17164
- * @type {((blob: Blob) => Promise<void>) | null}
19195
+ * Automatically handles:
19196
+ * - `__undefined__` → `undefined`
19197
+ * - `__null__` → `null`
19198
+ * - Any type registered via `registerJsonType`
19199
+ *
19200
+ * @type {decodeSpecialJson}
17165
19201
  */
17166
- #copyBlob = null;
19202
+ static decodeSpecialJson(value) {
19203
+ const isJson = isJsonObject(value);
19204
+ if (isJson) {
19205
+ if (value.__undefined__) return undefined;
19206
+ if (value.__null__) return null;
19207
+ }
19208
+
19209
+ if (Array.isArray(value)) {
19210
+ return value.map(TinyLocalStorage.decodeSpecialJson);
19211
+ }
19212
+
19213
+ if (isJson) {
19214
+ for (const [type, decoder] of customDecoders.entries()) {
19215
+ if (decoder.check && decoder.check(value)) {
19216
+ return decoder.decode(value, TinyLocalStorage.decodeSpecialJson);
19217
+ }
19218
+ }
19219
+
19220
+ const decoded = {};
19221
+ for (const key in value) {
19222
+ // @ts-ignore
19223
+ decoded[key] = TinyLocalStorage.decodeSpecialJson(value[key]);
19224
+ }
19225
+ return decoded;
19226
+ }
19227
+
19228
+ return value;
19229
+ }
19230
+
19231
+ //////////////////////////////////////////////////////
19232
+
19233
+ /** @type {Storage} */
19234
+ #localStorage = window.localStorage;
19235
+
19236
+ /** @type {(ev: StorageEvent) => any} */
19237
+ #storageEvent = (ev) => this.emit('storage', ev);
17167
19238
 
17168
19239
  /**
17169
- * Constructs a new TinyClipboard instance.
17170
- * Automatically detects and configures available clipboard APIs.
19240
+ * Initializes the TinyLocalStorage instance and sets up cross-tab sync.
19241
+ *
19242
+ * Adds listener for the native `storage` event to support tab synchronization.
17171
19243
  */
17172
19244
  constructor() {
17173
- // Whether the Clipboard API is available.
17174
- if (typeof navigator.clipboard !== 'undefined' && navigator.clipboard !== null) {
17175
- this.#existNavigator = true;
17176
- this.#copyText = (text) => navigator.clipboard.writeText(text);
17177
- this.#copyBlob = (blob) =>
17178
- navigator.clipboard.write([
17179
- new ClipboardItem({
17180
- [blob.type]: blob,
17181
- }),
17182
- ]);
17183
- }
19245
+ window.addEventListener('storage', this.#storageEvent);
19246
+ }
17184
19247
 
17185
- /**
17186
- * @type {boolean}
17187
- * Whether the legacy execCommand API is available.
17188
- */
17189
- this.#existExecCommand =
17190
- typeof document.execCommand !== 'undefined' && document.execCommand !== null;
19248
+ /**
19249
+ * Defines a custom storage interface (e.g. `sessionStorage`).
19250
+ *
19251
+ * @param {Storage} localstorage - A valid Storage object (localStorage or sessionStorage).
19252
+ */
19253
+ setLocalStorage(localstorage) {
19254
+ if (!(localstorage instanceof Storage))
19255
+ throw new Error('Argument must be a valid instance of Storage.');
19256
+ this.#localStorage = localstorage;
17191
19257
  }
17192
19258
 
17193
19259
  /**
17194
- * Override the default text copy behavior.
17195
- * This allows you to provide your own clipboard implementation or
17196
- * integrate with external systems like Capacitor or Electron.
19260
+ * Checks if `localStorage` is supported by the current environment.
17197
19261
  *
17198
- * @param {(text: string) => Promise<void>} callback - The function to use for copying text.
17199
- * @throws {TypeError} If the callback is not a function.
19262
+ * @returns {boolean} True if `localStorage` exists, false otherwise.
17200
19263
  */
17201
- setCopyText(callback) {
17202
- if (typeof callback !== 'function')
17203
- throw new TypeError('setCopyText expected a function that returns Promise<void>.');
17204
- this.#copyText = callback;
19264
+ localStorageExists() {
19265
+ return this.#localStorage instanceof Storage;
17205
19266
  }
17206
19267
 
17207
19268
  /**
17208
- * Override the default blob copy behavior.
17209
- * This allows you to provide a custom clipboard handling method for blob data.
19269
+ * Automatically serializes nested instances.
17210
19270
  *
17211
- * @param {(blob: Blob) => Promise<void>} callback - The function to use for copying blob data.
17212
- * @throws {TypeError} If the callback is not a function.
19271
+ * @param {string} name - The key under which to store the data.
19272
+ * @param {*} data - The data to be serialized.
19273
+ * @returns {*}
17213
19274
  */
17214
- setCopyBlob(callback) {
17215
- if (typeof callback !== 'function')
17216
- throw new TypeError('setCopyBlob expected a function that returns Promise<void>.');
17217
- this.#copyBlob = callback;
19275
+ #setJson(name, data) {
19276
+ if (typeof name !== 'string' || !name.length)
19277
+ throw new Error('Key must be a non-empty string.');
19278
+ return TinyLocalStorage.encodeSpecialJson(data);
17218
19279
  }
17219
19280
 
17220
19281
  /**
17221
- * Copy a plain text string to the clipboard.
17222
- * Uses modern or legacy fallback.
19282
+ * Stores a JSON-compatible value in `localStorage`.
17223
19283
  *
17224
- * @param {string} text - The text string to be copied.
17225
- * @returns {Promise<void>} A promise resolving when the text is copied or boolean for legacy.
19284
+ * Automatically serializes nested `Map` and `Set` instances.
19285
+ *
19286
+ * @param {string} name - The key under which to store the data.
19287
+ * @param {LocalStorageJsonValue} data - The data to be serialized and stored.
17226
19288
  */
17227
- copyText(text) {
17228
- if (typeof text !== 'string') throw new TypeError('copyText expected a string.');
17229
- // Clipboard API
17230
- if (this.#copyText) return this.#copyText(text);
17231
- // Classic API
17232
- else if (this.#existExecCommand) {
17233
- const host = document.body;
17234
- const copyInput = document.createElement('input');
17235
- copyInput.style.position = 'fixed';
17236
- copyInput.style.opacity = '0';
17237
- copyInput.value = text;
17238
- host.append(copyInput);
19289
+ setJson(name, data) {
19290
+ if (
19291
+ !isJsonObject(data) &&
19292
+ !Array.isArray(data) &&
19293
+ !(data instanceof Map) &&
19294
+ !(data instanceof Set)
19295
+ ) {
19296
+ throw new Error('The storage value is not a valid JSON-compatible structure.');
19297
+ }
19298
+ const encoded = this.#setJson(name, data);
19299
+ this.emit('setJson', name, data);
19300
+ return this.#localStorage.setItem(name, JSON.stringify(encoded));
19301
+ }
17239
19302
 
17240
- copyInput.select();
17241
- copyInput.setSelectionRange(0, 99999);
17242
- document.execCommand('Copy');
17243
- copyInput.remove();
17244
- return new Promise((resolve) => resolve(undefined));
19303
+ /**
19304
+ * Retrieves a value from `localStorage`.
19305
+ *
19306
+ * Automatically restores nested instances.
19307
+ *
19308
+ * @param {string} name - The key to retrieve.
19309
+ * @param {'array'|'obj'|'map'|'set'|'null'} [defaultData] - Default fallback format if value is invalid.
19310
+ * @returns {{ decoded: any, fallback: any }} The parsed object or fallback.
19311
+ */
19312
+ #getJson(name, defaultData) {
19313
+ if (typeof name !== 'string' || !name.length)
19314
+ throw new Error('Key must be a non-empty string.');
19315
+
19316
+ const raw = this.#localStorage.getItem(name);
19317
+ const fallbackTypes = {
19318
+ obj: () => ({}),
19319
+ array: () => [],
19320
+ map: () => new Map(),
19321
+ set: () => new Set(),
19322
+ };
19323
+
19324
+ const fallback =
19325
+ // @ts-ignore
19326
+ typeof fallbackTypes[defaultData] === 'function' ? fallbackTypes[defaultData]() : null;
19327
+
19328
+ let parsed;
19329
+
19330
+ try {
19331
+ // @ts-ignore
19332
+ parsed = JSON.parse(raw);
19333
+ } catch {
19334
+ // @ts-ignore
19335
+ return fallback;
17245
19336
  }
17246
- throw new Error('Clipboard API not found!');
19337
+
19338
+ return { decoded: TinyLocalStorage.decodeSpecialJson(parsed), fallback };
17247
19339
  }
17248
19340
 
17249
19341
  /**
17250
- * Copy a Blob (binary data) to the clipboard.
19342
+ * Retrieves and parses a JSON value from `localStorage`.
17251
19343
  *
17252
- * @param {Blob} blob - The blob object to copy.
17253
- * @returns {Promise<void>} A promise that resolves when the blob is copied or null on fallback.
19344
+ * Automatically restores nested `Map` and `Set` instances.
19345
+ *
19346
+ * @param {string} name - The key to retrieve.
19347
+ * @param {'array'|'obj'|'map'|'set'|'null'} [defaultData] - Default fallback format if value is invalid.
19348
+ * @returns {LocalStorageJsonValue|null} The parsed object or fallback.
17254
19349
  */
17255
- copyBlob(blob) {
17256
- if (!(blob instanceof Blob)) throw new TypeError('copyBlob expected a Blob instance.');
17257
- return new Promise((resolve, reject) => {
17258
- if (this.#copyBlob) {
17259
- return this.#copyBlob(blob).then(resolve).catch(reject);
17260
- }
17261
- throw new Error('Clipboard API not found!');
17262
- });
19350
+ getJson(name, defaultData) {
19351
+ const { decoded, fallback } = this.#getJson(name, defaultData);
19352
+ if (
19353
+ decoded instanceof Map ||
19354
+ decoded instanceof Set ||
19355
+ Array.isArray(decoded) ||
19356
+ isJsonObject(decoded)
19357
+ )
19358
+ return decoded;
19359
+ return fallback;
17263
19360
  }
17264
19361
 
17265
19362
  /**
17266
- * Internal: Handle getting blob data from a clipboard item.
17267
- *
17268
- * @private
17269
- * @param {string} type - The MIME type to fetch.
17270
- * @param {ClipboardItem} clipboardItem - Clipboard item instance.
17271
- * @returns {Promise<Blob>} A promise that resolves with the Blob.
19363
+ * Stores a Date in localStorage.
19364
+ * @param {string} name
19365
+ * @param {Date} data
17272
19366
  */
17273
- _handleBlob(type, clipboardItem) {
17274
- return clipboardItem.getType(type);
19367
+ setDate(name, data) {
19368
+ if (!(data instanceof Date)) throw new Error('Value must be a Date.');
19369
+ const encoded = this.#setJson(name, data);
19370
+ this.emit('setDate', name, data);
19371
+ return this.#localStorage.setItem(name, JSON.stringify(encoded));
17275
19372
  }
17276
19373
 
17277
19374
  /**
17278
- * Internal: Handle getting plain text from a clipboard item.
17279
- *
17280
- * @private
17281
- * @param {string} type - The MIME type (should be 'text/plain').
17282
- * @param {ClipboardItem} clipboardItem - Clipboard item instance.
17283
- * @returns {Promise<string>} A promise that resolves with the text content.
19375
+ * Retrieves a Date from localStorage.
19376
+ * @param {string} name
19377
+ * @returns {Date|null}
17284
19378
  */
17285
- _handleText(type, clipboardItem) {
17286
- return this._handleBlob(type, clipboardItem).then((blob) => blob.text());
19379
+ getDate(name) {
19380
+ const value = this.#getJson(name).decoded;
19381
+ return value instanceof Date ? value : null;
17287
19382
  }
17288
19383
 
17289
19384
  /**
17290
- * Read clipboard data based on filters like type, mime, index.
17291
- *
17292
- * @param {number|null} [index=0] - Item index or null for all.
17293
- * @param {'text'|'custom'|null} [type=null] - Data type to filter.
17294
- * @param {string|null} [mimeFormat=null] - MIME type or prefix.
17295
- * @param {boolean} [fixValue=false] - If true, exact match on MIME type.
17296
- * @returns {Promise<Blob|string|Array<Blob|string>|null>} A promise resolving with matching data.
19385
+ * Stores a RegExp in localStorage.
19386
+ * @param {string} name
19387
+ * @param {RegExp} data
17297
19388
  */
17298
- _readData(index = 0, type = null, mimeFormat = null, fixValue = false) {
17299
- return new Promise((resolve, reject) => {
17300
- this._read(index)
17301
- .then((items) => {
17302
- if (!items) return resolve(null);
17303
- /** @type {Array<Blob|string>} */
17304
- const finalResult = [];
17305
-
17306
- // Complete task
17307
- let continueLoop = true;
17308
-
17309
- /**
17310
- * @param {string} mimeType
17311
- * @param {ClipboardItem} item
17312
- */
17313
- const completeTask = async (mimeType, item) => {
17314
- if (!continueLoop) return;
17315
- // Custom
17316
- if (
17317
- (type === null || type === 'custom') &&
17318
- typeof mimeFormat === 'string' &&
17319
- ((!fixValue && mimeType.startsWith(mimeFormat)) ||
17320
- (fixValue && mimeType === mimeFormat))
17321
- ) {
17322
- continueLoop = false;
17323
- const result = await this._handleBlob(mimeType, item);
17324
- if (result) finalResult.push(result);
17325
- }
17326
-
17327
- // Text
17328
- else if ((type === null || type === 'text') && mimeType === 'text/plain') {
17329
- continueLoop = false;
17330
- const result = await this._handleText(mimeType, item);
17331
- if (result) finalResult.push(result);
17332
- }
17333
-
17334
- // Blob
17335
- else if (type === null) {
17336
- continueLoop = false;
17337
- const result = await this._handleBlob(mimeType, item);
17338
- if (result) finalResult.push(result);
17339
- }
17340
- };
19389
+ setRegExp(name, data) {
19390
+ if (!(data instanceof RegExp)) throw new Error('Value must be a RegExp.');
19391
+ const encoded = this.#setJson(name, data);
19392
+ this.emit('setRegExp', name, data);
19393
+ return this.#localStorage.setItem(name, JSON.stringify(encoded));
19394
+ }
17341
19395
 
17342
- /** @type {Promise<void>[]} */
17343
- const promises = [];
19396
+ /**
19397
+ * Retrieves a RegExp from localStorage.
19398
+ * @param {string} name
19399
+ * @returns {RegExp|null}
19400
+ */
19401
+ getRegExp(name) {
19402
+ const value = this.#getJson(name).decoded;
19403
+ return value instanceof RegExp ? value : null;
19404
+ }
17344
19405
 
17345
- /**
17346
- * Read Item
17347
- * @param {ClipboardItem | ClipboardItems} item
17348
- */
17349
- const readItem = (item) => {
17350
- if (!(item instanceof ClipboardItem))
17351
- throw new Error('Expected ClipboardItem when reading data.');
17352
- for (const tIndex in item.types) promises.push(completeTask(item.types[tIndex], item));
17353
- };
19406
+ /**
19407
+ * Stores a BigInt in localStorage.
19408
+ * @param {string} name
19409
+ * @param {bigint} data
19410
+ */
19411
+ setBigInt(name, data) {
19412
+ if (typeof data !== 'bigint') throw new Error('Value must be a BigInt.');
19413
+ const encoded = this.#setJson(name, data);
19414
+ this.emit('setBigInt', name, data);
19415
+ return this.#localStorage.setItem(name, JSON.stringify(encoded));
19416
+ }
17354
19417
 
17355
- // Specific Item
17356
- if (
17357
- typeof index === 'number' &&
17358
- !Number.isNaN(index) &&
17359
- Number.isFinite(index) &&
17360
- index > -1
17361
- ) {
17362
- readItem(items);
17363
- Promise.all(promises)
17364
- .then(() => {
17365
- if (finalResult[0]) resolve(finalResult[0]);
17366
- else resolve(null);
17367
- })
17368
- .catch(reject);
17369
- }
19418
+ /**
19419
+ * Retrieves a BigInt from localStorage.
19420
+ * @param {string} name
19421
+ * @returns {bigint|null}
19422
+ */
19423
+ getBigInt(name) {
19424
+ const value = this.#getJson(name).decoded;
19425
+ return typeof value === 'bigint' ? value : null;
19426
+ }
17370
19427
 
17371
- // All
17372
- else if (Array.isArray(items)) {
17373
- for (const tIndex in items) readItem(items[tIndex]);
17374
- Promise.all(promises)
17375
- .then(() => resolve(finalResult))
17376
- .catch(reject);
17377
- }
17378
- })
17379
- // Fail
17380
- .catch(reject);
17381
- });
19428
+ /**
19429
+ * Stores a Symbol in localStorage.
19430
+ * Only global symbols (`Symbol.for`) will preserve the key.
19431
+ * @param {string} name
19432
+ * @param {symbol} data
19433
+ */
19434
+ setSymbol(name, data) {
19435
+ if (typeof data !== 'symbol') throw new Error('Value must be a Symbol.');
19436
+ const encoded = this.#setJson(name, data);
19437
+ this.emit('setSymbol', name, data);
19438
+ return this.#localStorage.setItem(name, JSON.stringify(encoded));
17382
19439
  }
17383
19440
 
17384
19441
  /**
17385
- * Read plain text from the clipboard (single item by index).
17386
- *
17387
- * @param {number} [index=0] - The index of the clipboard item to read.
17388
- * @returns {Promise<string|null>} A promise that resolves to the clipboard text or null.
19442
+ * Retrieves a Symbol from localStorage.
19443
+ * @param {string} name
19444
+ * @returns {symbol|null}
17389
19445
  */
17390
- async readText(index = 0) {
17391
- const value = await this._readData(index, 'text');
17392
- if (typeof value !== 'string') throw new Error('Failed to read text: expected string result.');
17393
- return value;
19446
+ getSymbol(name) {
19447
+ const value = this.#getJson(name).decoded;
19448
+ return typeof value === 'symbol' ? value : null;
17394
19449
  }
17395
19450
 
17396
19451
  /**
17397
- * Read custom clipboard data based on MIME type from a specific index.
19452
+ * Retrieves a value from `localStorage`.
17398
19453
  *
17399
- * @param {string|null} [mimeFormat=null] - MIME prefix to match (e.g., "image/").
17400
- * @param {boolean} [fixValue=false] - If true, matches exact MIME instead of prefix.
17401
- * @param {number} [index=0] - Clipboard item index.
17402
- * @returns {Promise<Blob|null>} A promise resolving with a blob or null.
19454
+ * @param {string} name - The key to retrieve.
19455
+ * @returns {any} The stored value or null if not found.
17403
19456
  */
17404
- async readCustom(mimeFormat = null, fixValue = false, index = 0) {
17405
- const value = await this._readData(index, 'custom', mimeFormat, fixValue);
17406
- if (!(value instanceof Blob)) throw new Error('Failed to read custom data: expected Blob.');
17407
- return value;
19457
+ getValue(name) {
19458
+ return this.#getJson(name).decoded ?? null;
17408
19459
  }
17409
19460
 
17410
19461
  /**
17411
- * Read all available plain text entries from the clipboard.
19462
+ * Stores a raw string value in `localStorage`.
17412
19463
  *
17413
- * @returns {Promise<string[]>} A promise resolving to an array of strings or null.
19464
+ * @param {string} name - The key to use.
19465
+ * @param {any} data - The data to store.
17414
19466
  */
17415
- async readAllTexts() {
17416
- const values = await this._readData(null, 'text');
17417
- if (!Array.isArray(values))
17418
- throw new Error('Expected array of strings when reading all texts.');
17419
- if (!values.every((value) => typeof value === 'string'))
17420
- throw new Error('Some values returned were not strings.');
17421
- return values;
19467
+ setItem(name, data) {
19468
+ if (typeof name !== 'string' || !name.length)
19469
+ throw new Error('Key must be a non-empty string.');
19470
+ this.emit('setItem', name, data);
19471
+ return this.#localStorage.setItem(name, data);
17422
19472
  }
17423
19473
 
17424
19474
  /**
17425
- * Read all clipboard data matching a specific custom MIME type.
19475
+ * Retrieves a raw string value from `localStorage`.
17426
19476
  *
17427
- * @param {string|null} [mimeFormat=null] - MIME prefix or exact type.
17428
- * @param {boolean} [fixValue=false] - Match prefix or exact MIME.
17429
- * @returns {Promise<Blob[]>} A promise resolving with array of Blobs or null.
19477
+ * @param {string} name - The key to retrieve.
19478
+ * @returns {string|null} The stored value or null if not found.
17430
19479
  */
17431
- async readAllCustom(mimeFormat = null, fixValue = false) {
17432
- const values = await this._readData(null, 'custom', mimeFormat, fixValue);
17433
- if (!Array.isArray(values))
17434
- throw new Error('Expected array of blobs when reading all custom items.');
17435
- if (!values.every((value) => value instanceof Blob))
17436
- throw new Error('Some values returned were not Blob instances.');
17437
- return values;
19480
+ getItem(name) {
19481
+ if (typeof name !== 'string' || !name.length)
19482
+ throw new Error('Key must be a non-empty string.');
19483
+ return this.#localStorage.getItem(name);
17438
19484
  }
17439
19485
 
17440
19486
  /**
17441
- * Read all clipboard data as Blob or text depending on type.
19487
+ * Stores a string in `localStorage`, ensuring the data is a valid string.
17442
19488
  *
17443
- * @param {'text'|'custom'|null} [type=null] - The type of data to retrieve.
17444
- * @param {string|null} [mimeFormat=null] - The MIME type or prefix to match.
17445
- * @returns {Promise<Array<Blob|string>>} A promise resolving with matching data array.
19489
+ * @param {string} name - The key to store the string under.
19490
+ * @param {string} data - The string to store.
17446
19491
  */
17447
- async readAllData(type = null, mimeFormat = null) {
17448
- const value = await this._readData(null, type, mimeFormat);
17449
- if (!Array.isArray(value)) throw new Error('Expected array result when reading all data.');
17450
- return value;
19492
+ setString(name, data) {
19493
+ if (typeof name !== 'string' || !name.length)
19494
+ throw new Error('Key must be a non-empty string.');
19495
+ if (typeof data !== 'string') throw new Error('Value must be a string.');
19496
+
19497
+ this.emit('setString', name, data);
19498
+ return this.#localStorage.setItem(name, data);
17451
19499
  }
17452
19500
 
17453
19501
  /**
17454
- * Read clipboard data at a specific index or all if null.
19502
+ * Retrieves a string value from `localStorage`.
17455
19503
  *
17456
- * @param {number|null} index - Index of the item to retrieve or null to get all.
17457
- * @returns {Promise<ClipboardItem|ClipboardItems|null>} A promise resolving with a clipboard item or array of items.
19504
+ * @param {string} name - The key to retrieve.
19505
+ * @returns {string|null} The string if valid, or null.
17458
19506
  */
17459
- _read(index) {
17460
- return new Promise((resolve, reject) => {
17461
- if (!this.#existNavigator) reject(new Error('Clipboard API not found!'));
17462
- navigator.clipboard
17463
- .read()
17464
- .then((items) => {
17465
- // Index is number
17466
- if (typeof index === 'number') {
17467
- if (Number.isNaN(index) || !Number.isFinite(index) || index < 0)
17468
- throw new Error(`Invalid index value: ${index}`);
17469
- if (items[index]) resolve(items[index]);
17470
- // Not found
17471
- else resolve(null);
17472
- }
17473
- // Get All
17474
- resolve(items);
17475
- })
17476
- .catch(reject);
17477
- });
19507
+ getString(name) {
19508
+ if (typeof name !== 'string' || !name.length)
19509
+ throw new Error('Key must be a non-empty string.');
19510
+ let value = this.#localStorage.getItem(name);
19511
+ if (typeof value === 'string') return value;
19512
+ return null;
17478
19513
  }
17479
19514
 
17480
19515
  /**
17481
- * Read clipboard data at a specific index.
19516
+ * Stores a number value in `localStorage`.
17482
19517
  *
17483
- * @param {number} index - Index of the item to retrieve
17484
- * @returns {Promise<ClipboardItem|null>} A promise resolving with a clipboard item.
19518
+ * @param {string} name - The key to use.
19519
+ * @param {number} data - The number to store.
17485
19520
  */
17486
- async readIndex(index) {
17487
- const value = await this._read(index);
17488
- if (value !== null && !(value instanceof ClipboardItem))
17489
- throw new Error(`Value at index ${index} is not a ClipboardItem.`);
17490
- return value;
19521
+ setNumber(name, data) {
19522
+ if (typeof name !== 'string' || !name.length)
19523
+ throw new Error('Key must be a non-empty string.');
19524
+ if (typeof data !== 'number') throw new Error('Value must be a number.');
19525
+ this.emit('setNumber', name, data);
19526
+ return this.#localStorage.setItem(name, String(data));
17491
19527
  }
17492
19528
 
17493
19529
  /**
17494
- * Read all clipboard content without any filters.
19530
+ * Retrieves a number from `localStorage`.
17495
19531
  *
17496
- * @returns {Promise<ClipboardItems>} A promise resolving with all clipboard items.
19532
+ * @param {string} name - The key to retrieve.
19533
+ * @returns {number|null} The number or null if invalid.
17497
19534
  */
17498
- async readAll() {
17499
- const value = await this._read(null);
17500
- if (!Array.isArray(value)) throw new Error('Expected array result from clipboard read.');
17501
- for (const item of value) {
17502
- if (!(item instanceof ClipboardItem))
17503
- throw new Error('Invalid item type found in clipboard result.');
19535
+ getNumber(name) {
19536
+ if (typeof name !== 'string' || !name.length)
19537
+ throw new Error('Key must be a non-empty string.');
19538
+
19539
+ /** @type {number|string|null} */
19540
+ let number = this.#localStorage.getItem(name);
19541
+ if (typeof number === 'number') return number;
19542
+ if (typeof number === 'string' && number.length > 0) {
19543
+ number = parseFloat(number);
19544
+ if (!Number.isNaN(number)) return number;
17504
19545
  }
17505
- return value;
19546
+ return null;
17506
19547
  }
17507
19548
 
17508
19549
  /**
17509
- * Returns whether the legacy `document.execCommand()` API is available.
17510
- * This can be used to determine if a fallback clipboard method is usable.
19550
+ * Stores a boolean value in `localStorage`.
17511
19551
  *
17512
- * @returns {boolean} True if `document.execCommand` is available.
19552
+ * @param {string} name - The key to use.
19553
+ * @param {boolean} data - The boolean value to store.
17513
19554
  */
17514
- isExecCommandAvailable() {
17515
- return this.#existExecCommand;
19555
+ setBool(name, data) {
19556
+ if (typeof name !== 'string' || !name.length)
19557
+ throw new Error('Key must be a non-empty string.');
19558
+ if (typeof data !== 'boolean') throw new Error('Value must be a boolean.');
19559
+ this.emit('setBool', name, data);
19560
+ return this.#localStorage.setItem(name, String(data));
17516
19561
  }
17517
19562
 
17518
19563
  /**
17519
- * Returns whether the modern Clipboard API (`navigator.clipboard`) is available.
17520
- * Useful to know if full clipboard features can be accessed.
19564
+ * Retrieves a boolean value from `localStorage`.
17521
19565
  *
17522
- * @returns {boolean} True if `navigator.clipboard` is available.
19566
+ * @param {string} name - The key to retrieve.
19567
+ * @returns {boolean|null} The boolean or null if invalid.
17523
19568
  */
17524
- isNavigatorClipboardAvailable() {
17525
- return this.#existNavigator;
19569
+ getBool(name) {
19570
+ if (typeof name !== 'string' || !name.length)
19571
+ throw new Error('Key must be a non-empty string.');
19572
+
19573
+ const value = this.#localStorage.getItem(name);
19574
+ if (typeof value === 'boolean') return value;
19575
+ if (typeof value === 'string') {
19576
+ if (value === 'true') return true;
19577
+ if (value === 'false') return false;
19578
+ }
19579
+
19580
+ return null;
17526
19581
  }
17527
19582
 
17528
19583
  /**
17529
- * Returns the function used to copy plain text to the clipboard.
17530
- * This function may be built-in or set manually via `setCopyText`.
19584
+ * Removes a value from `localStorage`.
17531
19585
  *
17532
- * @returns {((text: string) => Promise<void>) | null} The current text copy function or null if unavailable.
19586
+ * @param {string} name - The key to remove.
17533
19587
  */
17534
- getCopyTextFunc() {
17535
- return this.#copyText;
19588
+ removeItem(name) {
19589
+ if (typeof name !== 'string' || !name.length)
19590
+ throw new Error('Key must be a non-empty string.');
19591
+
19592
+ this.emit('removeItem', name);
19593
+ return this.#localStorage.removeItem(name);
17536
19594
  }
17537
19595
 
17538
19596
  /**
17539
- * Returns the function used to copy Blob (binary data) to the clipboard.
17540
- * This function may be built-in or set manually via `setCopyBlob`.
17541
- *
17542
- * @returns {((blob: Blob) => Promise<void>) | null} The current blob copy function or null if unavailable.
19597
+ * Clears all data from `localStorage`.
17543
19598
  */
17544
- getCopyBlobFunc() {
17545
- return this.#copyBlob;
19599
+ clearLocalStorage() {
19600
+ return this.#localStorage.clear();
19601
+ }
19602
+
19603
+ /**
19604
+ * Destroys the storage instance by removing the storage event listener.
19605
+ */
19606
+ destroy() {
19607
+ window.removeEventListener('storage', this.#storageEvent);
19608
+ this.#events.offAllTypes();
17546
19609
  }
17547
19610
  }
17548
19611
 
17549
- /* harmony default export */ const libs_TinyClipboard = (TinyClipboard);
19612
+ // First registers
19613
+
19614
+ // Map
19615
+ TinyLocalStorage.registerJsonType(
19616
+ Map,
19617
+ (value, encodeSpecialJson) => ({
19618
+ __map__: true,
19619
+ data: Array.from(value.entries()).map(([k, v]) => [k, encodeSpecialJson(v)]),
19620
+ }),
19621
+ {
19622
+ check: (value) => value.__map__,
19623
+ /** @param {{ data: any[] }} value */
19624
+ decode: (value, decodeSpecialJson) =>
19625
+ new Map(value.data.map(([k, v]) => [k, decodeSpecialJson(v)])),
19626
+ },
19627
+ );
19628
+
19629
+ // Set
19630
+ TinyLocalStorage.registerJsonType(
19631
+ Set,
19632
+ (value, encodeSpecialJson) => ({
19633
+ __set__: true,
19634
+ data: Array.from(value).map(encodeSpecialJson),
19635
+ }),
19636
+ {
19637
+ check: (value) => value.__set__,
19638
+ decode: (value, decodeSpecialJson) => new Set(value.data.map(decodeSpecialJson)),
19639
+ },
19640
+ );
19641
+
19642
+ // Date
19643
+ TinyLocalStorage.registerJsonType(
19644
+ Date,
19645
+ (value) => ({
19646
+ __date__: true,
19647
+ value: value.toISOString(),
19648
+ }),
19649
+ {
19650
+ check: (value) => value.__date__,
19651
+ decode: (value) => new Date(value.value),
19652
+ },
19653
+ );
19654
+
19655
+ // Regex
19656
+ TinyLocalStorage.registerJsonType(
19657
+ RegExp,
19658
+ (value) => ({
19659
+ __regexp__: true,
19660
+ source: value.source,
19661
+ flags: value.flags,
19662
+ }),
19663
+ {
19664
+ check: (value) => value.__regexp__,
19665
+ decode: (value) => new RegExp(value.source, value.flags),
19666
+ },
19667
+ );
19668
+
19669
+ // Big Int
19670
+ TinyLocalStorage.registerJsonType(
19671
+ 'bigint',
19672
+ (value) => ({
19673
+ __bigint__: true,
19674
+ value: value.toString(),
19675
+ }),
19676
+ {
19677
+ check: (value) => value.__bigint__,
19678
+ decode: (value) => BigInt(value.value),
19679
+ },
19680
+ );
19681
+
19682
+ // Symbol
19683
+ TinyLocalStorage.registerJsonType(
19684
+ 'symbol',
19685
+ (value) => ({
19686
+ __symbol__: true,
19687
+ key: Symbol.keyFor(value) ?? value.description ?? null,
19688
+ }),
19689
+ {
19690
+ check: (value) => value.__symbol__,
19691
+ decode: (value) => {
19692
+ const key = value.key;
19693
+ return key != null ? Symbol.for(key) : Symbol();
19694
+ },
19695
+ },
19696
+ );
19697
+
19698
+ /* harmony default export */ const libs_TinyLocalStorage = (TinyLocalStorage);
17550
19699
 
17551
19700
  ;// ./src/v1/index.mjs
17552
19701
 
@@ -17580,6 +19729,10 @@ class TinyClipboard {
17580
19729
 
17581
19730
 
17582
19731
 
19732
+
19733
+
19734
+
19735
+
17583
19736
 
17584
19737
 
17585
19738