tiny-essentials 1.16.1 → 1.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,8 +49,8 @@ class TinyAfterScrollWatcher {
49
49
  /** @type {Element|Window} */
50
50
  #scrollTarget;
51
51
 
52
- /** @type {number} */
53
- lastScrollTime = 0;
52
+ /** @type {null|NodeJS.Timeout} */
53
+ #lastScrollTime = null;
54
54
 
55
55
  /** @type {FnData[]} */
56
56
  #afterScrollQueue = [];
@@ -61,6 +61,9 @@ class TinyAfterScrollWatcher {
61
61
  /** @type {Set<OnScrollFunc>} */
62
62
  #externalScrollListeners = new Set();
63
63
 
64
+ /** @type {Set<FnData>} */
65
+ #onStopListeners = new Set();
66
+
64
67
  /** @type {boolean} */
65
68
  #destroyed = false;
66
69
 
@@ -74,16 +77,20 @@ class TinyAfterScrollWatcher {
74
77
  if (!(scrollTarget instanceof Element) && !(scrollTarget instanceof Window))
75
78
  throw new TypeError('scrollTarget must be an Element or the Window object.');
76
79
  this.#scrollTarget = scrollTarget;
80
+ this._checkTimer = this._checkTimer.bind(this);
77
81
 
78
- this._onScroll = this._onScroll.bind(this);
79
- this._checkQueue = this._checkQueue.bind(this);
80
-
81
- this.#scrollTarget.addEventListener('scroll', this._onScroll);
82
+ this.#scrollTarget.addEventListener('scroll', this._checkTimer);
82
83
  this.#inactivityTime = inactivityTime;
83
-
84
- requestAnimationFrame(this._checkQueue);
85
84
  }
86
85
 
86
+ _checkTimer = () => {
87
+ if (this.#lastScrollTime) clearTimeout(this.#lastScrollTime);
88
+ this.#lastScrollTime = setTimeout(() => {
89
+ this.#lastScrollTime = null;
90
+ this.#checkQueue();
91
+ }, this.#inactivityTime);
92
+ };
93
+
87
94
  /**
88
95
  * Gets the current inactivity time in milliseconds.
89
96
  * @returns {number}
@@ -104,29 +111,21 @@ class TinyAfterScrollWatcher {
104
111
  this.#inactivityTime = value;
105
112
  }
106
113
 
107
- /**
108
- * Internal handler for scroll events.
109
- * Updates the last scroll timestamp.
110
- * @private
111
- */
112
- _onScroll() {
113
- this.lastScrollTime = Date.now();
114
- }
115
-
116
114
  /**
117
115
  * Continuously checks whether the user has stopped scrolling,
118
116
  * and if so, runs all queued functions.
119
- * @private
120
117
  */
121
- _checkQueue() {
118
+ #checkQueue() {
122
119
  if (this.#destroyed) return;
123
- requestAnimationFrame(this._checkQueue);
120
+ // Runs all onStop first listeners
121
+ for (const fn of this.#onStopListeners) {
122
+ if (typeof fn === 'function') fn();
123
+ }
124
124
 
125
- if (Date.now() - this.lastScrollTime > this.#inactivityTime) {
126
- while (this.#afterScrollQueue.length) {
127
- const fn = this.#afterScrollQueue.pop();
128
- if (typeof fn === 'function') fn();
129
- }
125
+ // Then execute the queue afterScrollQueue
126
+ while (this.#afterScrollQueue.length) {
127
+ const fn = this.#afterScrollQueue.pop();
128
+ if (typeof fn === 'function') fn();
130
129
  }
131
130
  }
132
131
 
@@ -143,6 +142,29 @@ class TinyAfterScrollWatcher {
143
142
  this.#afterScrollQueue.push(fn);
144
143
  }
145
144
 
145
+ /**
146
+ * Registers a function to run once after scrolling has stopped,
147
+ * before any afterScrollQueue functions.
148
+ *
149
+ * @param {FnData} fn - A function to execute after scroll stop.
150
+ * @throws {TypeError} If the argument is not a function.
151
+ */
152
+ onStop(fn) {
153
+ if (typeof fn !== 'function') throw new TypeError('Argument must be a function.');
154
+ this.#onStopListeners.add(fn);
155
+ }
156
+
157
+ /**
158
+ * Removes a previously registered onStop function.
159
+ *
160
+ * @param {FnData} fn - The function to remove.
161
+ * @throws {TypeError} If the argument is not a function.
162
+ */
163
+ offStop(fn) {
164
+ if (typeof fn !== 'function') throw new TypeError('Argument must be a function.');
165
+ this.#onStopListeners.delete(fn);
166
+ }
167
+
146
168
  /**
147
169
  * Registers an external scroll listener on the tracked element.
148
170
  *
@@ -176,11 +198,12 @@ class TinyAfterScrollWatcher {
176
198
  if (this.#destroyed) return;
177
199
  this.#destroyed = true;
178
200
 
179
- this.#scrollTarget.removeEventListener('scroll', this._onScroll);
201
+ this.#scrollTarget.removeEventListener('scroll', this._checkTimer);
180
202
  for (const fn of this.#externalScrollListeners)
181
203
  this.#scrollTarget.removeEventListener('scroll', fn);
182
204
 
183
205
  this.#externalScrollListeners.clear();
206
+ this.#onStopListeners.clear();
184
207
  }
185
208
  }
186
209
 
@@ -1 +1 @@
1
- (()=>{"use strict";var e={d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyAfterScrollWatcher:()=>r});const r=class{#e;lastScrollTime=0;#t=[];#r=100;#i=new Set;#o=!1;constructor(e=window,t=100){if(!(e instanceof Element||e instanceof Window))throw new TypeError("scrollTarget must be an Element or the Window object.");this.#e=e,this._onScroll=this._onScroll.bind(this),this._checkQueue=this._checkQueue.bind(this),this.#e.addEventListener("scroll",this._onScroll),this.#r=t,requestAnimationFrame(this._checkQueue)}get inactivityTime(){return this.#r}set inactivityTime(e){if("number"!=typeof e||e<=0||!Number.isFinite(e))throw new Error("inactivityTime must be a positive number in milliseconds.");this.#r=e}_onScroll(){this.lastScrollTime=Date.now()}_checkQueue(){if(!this.#o&&(requestAnimationFrame(this._checkQueue),Date.now()-this.lastScrollTime>this.#r))for(;this.#t.length;){const e=this.#t.pop();"function"==typeof e&&e()}}doAfterScroll(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.lastScrollTime=Date.now(),this.#t.push(e)}onScroll(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.#e.addEventListener("scroll",e),this.#i.add(e)}offScroll(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.#i.has(e)&&(this.#e.removeEventListener("scroll",e),this.#i.delete(e))}destroy(){if(!this.#o){this.#o=!0,this.#e.removeEventListener("scroll",this._onScroll);for(const e of this.#i)this.#e.removeEventListener("scroll",e);this.#i.clear()}}};window.TinyAfterScrollWatcher=t.TinyAfterScrollWatcher})();
1
+ (()=>{"use strict";var e={d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyAfterScrollWatcher:()=>r});const r=class{#e;#t=null;#r=[];#i=100;#o=new Set;#n=new Set;#s=!1;constructor(e=window,t=100){if(!(e instanceof Element||e instanceof Window))throw new TypeError("scrollTarget must be an Element or the Window object.");this.#e=e,this._checkTimer=this._checkTimer.bind(this),this.#e.addEventListener("scroll",this._checkTimer),this.#i=t}_checkTimer=()=>{this.#t&&clearTimeout(this.#t),this.#t=setTimeout((()=>{this.#t=null,this.#l()}),this.#i)};get inactivityTime(){return this.#i}set inactivityTime(e){if("number"!=typeof e||e<=0||!Number.isFinite(e))throw new Error("inactivityTime must be a positive number in milliseconds.");this.#i=e}#l(){if(!this.#s){for(const e of this.#n)"function"==typeof e&&e();for(;this.#r.length;){const e=this.#r.pop();"function"==typeof e&&e()}}}doAfterScroll(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.lastScrollTime=Date.now(),this.#r.push(e)}onStop(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.#n.add(e)}offStop(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.#n.delete(e)}onScroll(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.#e.addEventListener("scroll",e),this.#o.add(e)}offScroll(e){if("function"!=typeof e)throw new TypeError("Argument must be a function.");this.#o.has(e)&&(this.#e.removeEventListener("scroll",e),this.#o.delete(e))}destroy(){if(!this.#s){this.#s=!0,this.#e.removeEventListener("scroll",this._checkTimer);for(const e of this.#o)this.#e.removeEventListener("scroll",e);this.#o.clear(),this.#n.clear()}}};window.TinyAfterScrollWatcher=t.TinyAfterScrollWatcher})();
@@ -13791,8 +13791,8 @@ class TinyAfterScrollWatcher {
13791
13791
  /** @type {Element|Window} */
13792
13792
  #scrollTarget;
13793
13793
 
13794
- /** @type {number} */
13795
- lastScrollTime = 0;
13794
+ /** @type {null|NodeJS.Timeout} */
13795
+ #lastScrollTime = null;
13796
13796
 
13797
13797
  /** @type {FnData[]} */
13798
13798
  #afterScrollQueue = [];
@@ -13803,6 +13803,9 @@ class TinyAfterScrollWatcher {
13803
13803
  /** @type {Set<OnScrollFunc>} */
13804
13804
  #externalScrollListeners = new Set();
13805
13805
 
13806
+ /** @type {Set<FnData>} */
13807
+ #onStopListeners = new Set();
13808
+
13806
13809
  /** @type {boolean} */
13807
13810
  #destroyed = false;
13808
13811
 
@@ -13816,16 +13819,20 @@ class TinyAfterScrollWatcher {
13816
13819
  if (!(scrollTarget instanceof Element) && !(scrollTarget instanceof Window))
13817
13820
  throw new TypeError('scrollTarget must be an Element or the Window object.');
13818
13821
  this.#scrollTarget = scrollTarget;
13822
+ this._checkTimer = this._checkTimer.bind(this);
13819
13823
 
13820
- this._onScroll = this._onScroll.bind(this);
13821
- this._checkQueue = this._checkQueue.bind(this);
13822
-
13823
- this.#scrollTarget.addEventListener('scroll', this._onScroll);
13824
+ this.#scrollTarget.addEventListener('scroll', this._checkTimer);
13824
13825
  this.#inactivityTime = inactivityTime;
13825
-
13826
- requestAnimationFrame(this._checkQueue);
13827
13826
  }
13828
13827
 
13828
+ _checkTimer = () => {
13829
+ if (this.#lastScrollTime) clearTimeout(this.#lastScrollTime);
13830
+ this.#lastScrollTime = setTimeout(() => {
13831
+ this.#lastScrollTime = null;
13832
+ this.#checkQueue();
13833
+ }, this.#inactivityTime);
13834
+ };
13835
+
13829
13836
  /**
13830
13837
  * Gets the current inactivity time in milliseconds.
13831
13838
  * @returns {number}
@@ -13846,29 +13853,21 @@ class TinyAfterScrollWatcher {
13846
13853
  this.#inactivityTime = value;
13847
13854
  }
13848
13855
 
13849
- /**
13850
- * Internal handler for scroll events.
13851
- * Updates the last scroll timestamp.
13852
- * @private
13853
- */
13854
- _onScroll() {
13855
- this.lastScrollTime = Date.now();
13856
- }
13857
-
13858
13856
  /**
13859
13857
  * Continuously checks whether the user has stopped scrolling,
13860
13858
  * and if so, runs all queued functions.
13861
- * @private
13862
13859
  */
13863
- _checkQueue() {
13860
+ #checkQueue() {
13864
13861
  if (this.#destroyed) return;
13865
- requestAnimationFrame(this._checkQueue);
13862
+ // Runs all onStop first listeners
13863
+ for (const fn of this.#onStopListeners) {
13864
+ if (typeof fn === 'function') fn();
13865
+ }
13866
13866
 
13867
- if (Date.now() - this.lastScrollTime > this.#inactivityTime) {
13868
- while (this.#afterScrollQueue.length) {
13869
- const fn = this.#afterScrollQueue.pop();
13870
- if (typeof fn === 'function') fn();
13871
- }
13867
+ // Then execute the queue afterScrollQueue
13868
+ while (this.#afterScrollQueue.length) {
13869
+ const fn = this.#afterScrollQueue.pop();
13870
+ if (typeof fn === 'function') fn();
13872
13871
  }
13873
13872
  }
13874
13873
 
@@ -13885,6 +13884,29 @@ class TinyAfterScrollWatcher {
13885
13884
  this.#afterScrollQueue.push(fn);
13886
13885
  }
13887
13886
 
13887
+ /**
13888
+ * Registers a function to run once after scrolling has stopped,
13889
+ * before any afterScrollQueue functions.
13890
+ *
13891
+ * @param {FnData} fn - A function to execute after scroll stop.
13892
+ * @throws {TypeError} If the argument is not a function.
13893
+ */
13894
+ onStop(fn) {
13895
+ if (typeof fn !== 'function') throw new TypeError('Argument must be a function.');
13896
+ this.#onStopListeners.add(fn);
13897
+ }
13898
+
13899
+ /**
13900
+ * Removes a previously registered onStop function.
13901
+ *
13902
+ * @param {FnData} fn - The function to remove.
13903
+ * @throws {TypeError} If the argument is not a function.
13904
+ */
13905
+ offStop(fn) {
13906
+ if (typeof fn !== 'function') throw new TypeError('Argument must be a function.');
13907
+ this.#onStopListeners.delete(fn);
13908
+ }
13909
+
13888
13910
  /**
13889
13911
  * Registers an external scroll listener on the tracked element.
13890
13912
  *
@@ -13918,11 +13940,12 @@ class TinyAfterScrollWatcher {
13918
13940
  if (this.#destroyed) return;
13919
13941
  this.#destroyed = true;
13920
13942
 
13921
- this.#scrollTarget.removeEventListener('scroll', this._onScroll);
13943
+ this.#scrollTarget.removeEventListener('scroll', this._checkTimer);
13922
13944
  for (const fn of this.#externalScrollListeners)
13923
13945
  this.#scrollTarget.removeEventListener('scroll', fn);
13924
13946
 
13925
13947
  this.#externalScrollListeners.clear();
13948
+ this.#onStopListeners.clear();
13926
13949
  }
13927
13950
  }
13928
13951