tiny-essentials 1.20.2 → 1.20.3

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.
@@ -515,23 +515,75 @@ declare class TinySmartScroller {
515
515
  *
516
516
  * @returns {boolean}
517
517
  */
518
+ isAtCustomBottom(): boolean;
519
+ /**
520
+ * Checks if the user is within the defined extra scroll boundary from the top.
521
+ *
522
+ * @returns {boolean}
523
+ */
524
+ isAtCustomTop(): boolean;
525
+ /**
526
+ * Returns true if the user is currently scrolled to the bottom of the element.
527
+ *
528
+ * @returns {boolean}
529
+ */
530
+ isAtBottom(): boolean;
531
+ /**
532
+ * Returns true if the user is currently scrolled to the top of the element.
533
+ *
534
+ * @returns {boolean}
535
+ */
536
+ isAtTop(): boolean;
537
+ /**
538
+ * Returns true if the user has already passed beyond the bottom boundary at some point.
539
+ *
540
+ * @returns {boolean}
541
+ */
542
+ isPastAtBottom(): boolean;
543
+ /**
544
+ * Returns true if the user has already passed beyond the top boundary at some point.
545
+ *
546
+ * @returns {boolean}
547
+ */
548
+ isPastAtTop(): boolean;
549
+ /**
550
+ * Returns true if the user has passed beyond the defined extra scroll boundary from the top at some point.
551
+ *
552
+ * @returns {boolean}
553
+ */
554
+ isPastAtCustomTop(): boolean;
555
+ /**
556
+ * Returns true if the user has passed beyond the defined extra scroll boundary from the bottom at some point.
557
+ *
558
+ * @returns {boolean}
559
+ */
560
+ isPastAtCustomBottom(): boolean;
561
+ /**
562
+ * Checks if the user is within the defined extra scroll boundary from the bottom.
563
+ *
564
+ * @returns {boolean}
565
+ * @deprecated - Use isAtCustomBottom instead.
566
+ */
518
567
  isUserAtCustomBottom(): boolean;
519
568
  /**
520
569
  * Checks if the user is within the defined extra scroll boundary from the top.
521
570
  *
522
571
  * @returns {boolean}
572
+ * @deprecated - Use isAtCustomTop instead.
523
573
  */
524
574
  isUserAtCustomTop(): boolean;
525
575
  /**
526
576
  * Returns true if the user is currently scrolled to the bottom of the element.
527
577
  *
528
578
  * @returns {boolean}
579
+ * @deprecated - Use isAtBottom instead.
529
580
  */
530
581
  isUserAtBottom(): boolean;
531
582
  /**
532
583
  * Returns true if the user is currently scrolled to the top of the element.
533
584
  *
534
585
  * @returns {boolean}
586
+ * @deprecated - Use isAtTop instead.
535
587
  */
536
588
  isUserAtTop(): boolean;
537
589
  /**
@@ -236,6 +236,10 @@ class TinySmartScroller {
236
236
  #loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
237
237
  /** @type {null|EventListenerOrEventListenerObject} */
238
238
  #handler = null;
239
+ #isPastAtBottom = false;
240
+ #isPastAtTop = false;
241
+ #isPastAtCustomTop = false;
242
+ #isPastAtCustomBottom = false;
239
243
  #isAtBottom = false;
240
244
  #isAtTop = false;
241
245
  #isAtCustomTop = false;
@@ -550,6 +554,10 @@ class TinySmartScroller {
550
554
  atCustomResult = 'top';
551
555
  else if (atCustomBottom)
552
556
  atCustomResult = 'bottom';
557
+ this.#isPastAtTop = this.#isAtTop ?? false;
558
+ this.#isPastAtBottom = this.#isAtBottom ?? false;
559
+ this.#isPastAtCustomTop = this.#isAtCustomTop ?? false;
560
+ this.#isPastAtCustomBottom = this.#isAtCustomBottom ?? false;
553
561
  this.#isAtTop = atTop;
554
562
  this.#isAtBottom = atBottom;
555
563
  this.#isAtCustomTop = atCustomTop;
@@ -840,6 +848,71 @@ class TinySmartScroller {
840
848
  *
841
849
  * @returns {boolean}
842
850
  */
851
+ isAtCustomBottom() {
852
+ return this.#isAtCustomBottom;
853
+ }
854
+ /**
855
+ * Checks if the user is within the defined extra scroll boundary from the top.
856
+ *
857
+ * @returns {boolean}
858
+ */
859
+ isAtCustomTop() {
860
+ return this.#isAtCustomTop;
861
+ }
862
+ /**
863
+ * Returns true if the user is currently scrolled to the bottom of the element.
864
+ *
865
+ * @returns {boolean}
866
+ */
867
+ isAtBottom() {
868
+ return this.#isAtBottom;
869
+ }
870
+ /**
871
+ * Returns true if the user is currently scrolled to the top of the element.
872
+ *
873
+ * @returns {boolean}
874
+ */
875
+ isAtTop() {
876
+ return this.#isAtTop;
877
+ }
878
+ /**
879
+ * Returns true if the user has already passed beyond the bottom boundary at some point.
880
+ *
881
+ * @returns {boolean}
882
+ */
883
+ isPastAtBottom() {
884
+ return this.#isPastAtBottom;
885
+ }
886
+ /**
887
+ * Returns true if the user has already passed beyond the top boundary at some point.
888
+ *
889
+ * @returns {boolean}
890
+ */
891
+ isPastAtTop() {
892
+ return this.#isPastAtTop;
893
+ }
894
+ /**
895
+ * Returns true if the user has passed beyond the defined extra scroll boundary from the top at some point.
896
+ *
897
+ * @returns {boolean}
898
+ */
899
+ isPastAtCustomTop() {
900
+ return this.#isPastAtCustomTop;
901
+ }
902
+ /**
903
+ * Returns true if the user has passed beyond the defined extra scroll boundary from the bottom at some point.
904
+ *
905
+ * @returns {boolean}
906
+ */
907
+ isPastAtCustomBottom() {
908
+ return this.#isPastAtCustomBottom;
909
+ }
910
+ /**
911
+ * Checks if the user is within the defined extra scroll boundary from the bottom.
912
+ *
913
+ * @returns {boolean}
914
+ * @deprecated - Use isAtCustomBottom instead.
915
+ */
843
916
  isUserAtCustomBottom() {
844
917
  return this.#isAtCustomBottom;
845
918
  }
@@ -847,6 +920,7 @@ class TinySmartScroller {
847
920
  * Checks if the user is within the defined extra scroll boundary from the top.
848
921
  *
849
922
  * @returns {boolean}
923
+ * @deprecated - Use isAtCustomTop instead.
850
924
  */
851
925
  isUserAtCustomTop() {
852
926
  return this.#isAtCustomTop;
@@ -855,6 +929,7 @@ class TinySmartScroller {
855
929
  * Returns true if the user is currently scrolled to the bottom of the element.
856
930
  *
857
931
  * @returns {boolean}
932
+ * @deprecated - Use isAtBottom instead.
858
933
  */
859
934
  isUserAtBottom() {
860
935
  return this.#isAtBottom;
@@ -863,6 +938,7 @@ class TinySmartScroller {
863
938
  * Returns true if the user is currently scrolled to the top of the element.
864
939
  *
865
940
  * @returns {boolean}
941
+ * @deprecated - Use isAtTop instead.
866
942
  */
867
943
  isUserAtTop() {
868
944
  return this.#isAtTop;
@@ -0,0 +1,230 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @typedef {Object} OnInputInfo
5
+ * @property {number} breakLines - Total number of `\n` line breaks in the textarea value.
6
+ * @property {number} height - Final calculated height (in pixels) applied to the textarea.
7
+ * @property {number} scrollHeight - Internal scrollHeight before limiting.
8
+ * @property {number} maxHeight - Maximum allowed height before scrolling is forced.
9
+ * @property {number} lineHeight - Height of one line of text, computed from CSS.
10
+ * @property {number} maxRows - Maximum number of visible rows allowed.
11
+ * @property {number} rows - Effective number of visual rows being used.
12
+ */
13
+
14
+ /**
15
+ * A lightweight utility class that automatically adjusts the height of a `<textarea>`
16
+ * element based on its content. It prevents scrollbars by expanding vertically as needed,
17
+ * up to a configurable maximum number of visible rows.
18
+ *
19
+ * Features:
20
+ * - Automatically resizes the textarea as the user types
21
+ * - Prevents vertical scrollbars until a maximum row limit is reached
22
+ * - Supports additional height padding
23
+ * - Provides real-time callbacks for input and resize events
24
+ * - Allows manual refresh and cleanup of behavior
25
+ *
26
+ * Ideal for chat inputs, note editors, or any form where dynamic space usage
27
+ * is preferred without relying on scrollbars too early.
28
+ *
29
+ * @class
30
+ * @beta
31
+ */
32
+ class TinyTextarea {
33
+ #lineHeight;
34
+ #maxRows;
35
+ #extraHeight;
36
+
37
+ #lastKnownHeight = 0;
38
+ #lastKnownRows = 0;
39
+
40
+ /** @type {HTMLTextAreaElement} */
41
+ #textarea;
42
+
43
+ /**
44
+ * @type {((info: OnInputInfo) => void) | null}
45
+ */
46
+ #onResize = null;
47
+
48
+ /**
49
+ * @type {((info: OnInputInfo) => void) | null}
50
+ */
51
+ #onInput = null;
52
+
53
+ /**
54
+ * Returns the computed line height in pixels.
55
+ * @returns {number}
56
+ */
57
+ get lineHeight() {
58
+ return this.#lineHeight;
59
+ }
60
+
61
+ /**
62
+ * Returns the maximum number of rows allowed.
63
+ * @returns {number}
64
+ */
65
+ get maxRows() {
66
+ return this.#maxRows - 1;
67
+ }
68
+
69
+ /**
70
+ * Returns the additional height added to the textarea.
71
+ * @returns {number}
72
+ */
73
+ get extraHeight() {
74
+ return this.#extraHeight;
75
+ }
76
+
77
+ /**
78
+ * Returns the most recently applied height.
79
+ * @returns {number}
80
+ */
81
+ get currentHeight() {
82
+ return this.#lastKnownHeight;
83
+ }
84
+
85
+ /**
86
+ * Returns the most recently calculated row count.
87
+ * @returns {number}
88
+ */
89
+ get currentRows() {
90
+ return this.#lastKnownRows;
91
+ }
92
+
93
+ /**
94
+ * Returns the original textarea element managed by this instance.
95
+ * @returns {HTMLTextAreaElement}
96
+ */
97
+ get textarea() {
98
+ return this.#textarea;
99
+ }
100
+
101
+ /**
102
+ * Creates a new TinyTextarea instance.
103
+ *
104
+ * @param {HTMLTextAreaElement} textarea - The `<textarea>` element to enhance.
105
+ * @param {Object} [options={}] - Optional configuration parameters.
106
+ * @param {number} [options.maxRows] - Maximum number of visible rows before scrolling.
107
+ * @param {number} [options.extraHeight] - Additional pixels to add to final height.
108
+ * @param {(info: OnInputInfo) => void} [options.onResize] - Callback when the number of rows changes.
109
+ * @param {(info: OnInputInfo) => void} [options.onInput] - Callback on every input event.
110
+ * @throws {Error} If `textarea` is not a valid `<textarea>` element.
111
+ * @throws {TypeError} If provided options are of invalid types.
112
+ */
113
+ constructor(textarea, options = {}) {
114
+ if (!(textarea instanceof HTMLTextAreaElement))
115
+ throw new Error('TinyTextarea: Provided element is not a <textarea>.');
116
+ if (typeof options !== 'object' || options === null)
117
+ throw new TypeError('TinyTextarea: Options must be an object if provided.');
118
+ if ('maxRows' in options && typeof options.maxRows !== 'number')
119
+ throw new TypeError('TinyTextarea: `maxRows` must be a number.');
120
+ if ('extraHeight' in options && typeof options.extraHeight !== 'number')
121
+ throw new TypeError('TinyTextarea: `extraHeight` must be a number.');
122
+ if ('onResize' in options && typeof options.onResize !== 'function')
123
+ throw new TypeError('TinyTextarea: `onResize` must be a function.');
124
+ if ('onInput' in options && typeof options.onInput !== 'function')
125
+ throw new TypeError('TinyTextarea: `onInput` must be a function.');
126
+
127
+ this.#textarea = textarea;
128
+ this.#maxRows = (options.maxRows ?? 5) + 1;
129
+ this.#extraHeight = options.extraHeight ?? 0;
130
+ this.#onResize = options.onResize ?? null;
131
+ this.#onInput = options.onInput ?? null;
132
+
133
+ this.#lineHeight = this.#getLineHeight();
134
+
135
+ textarea.style.overflowY = 'hidden';
136
+ textarea.style.resize = 'none';
137
+
138
+ this._handleInput = () => this.#resize();
139
+ textarea.addEventListener('input', this._handleInput);
140
+ this.#resize();
141
+ }
142
+
143
+ /**
144
+ * Automatically resize the textarea based on its content and notify listeners.
145
+ * Triggers `onResize` if the number of rows has changed.
146
+ * Always triggers `onInput`.
147
+ */
148
+ #resize() {
149
+ this.#textarea.style.height = 'auto';
150
+
151
+ const style = window.getComputedStyle(this.#textarea);
152
+ const paddingTop = parseFloat(style.paddingTop) || 0;
153
+ const paddingBottom = parseFloat(style.paddingBottom) || 0;
154
+
155
+ const breakLines = (this.#textarea.value.match(/\n/g) || []).length;
156
+ const scrollHeight = this.#textarea.scrollHeight;
157
+ const maxHeight = this.#lineHeight * this.#maxRows;
158
+ const newHeight = Math.ceil(
159
+ Math.min(scrollHeight, maxHeight) - paddingTop - paddingBottom + this.#extraHeight,
160
+ );
161
+
162
+ // const rows = Math.round(newHeight / this.#lineHeight);
163
+ const maxRows = this.#maxRows - 1;
164
+ const rows = breakLines < maxRows ? breakLines + 1 : maxRows;
165
+
166
+ this.#textarea.style.height = `${newHeight}px`;
167
+ this.#textarea.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden';
168
+ this.#lastKnownHeight = newHeight;
169
+
170
+ const info = {
171
+ breakLines,
172
+ rows,
173
+ height: newHeight,
174
+ scrollHeight,
175
+ maxHeight,
176
+ lineHeight: this.#lineHeight,
177
+ maxRows,
178
+ };
179
+
180
+ if (rows !== this.#lastKnownRows) {
181
+ this.#lastKnownRows = rows;
182
+ if (typeof this.#onResize === 'function') {
183
+ this.#onResize({ ...info });
184
+ }
185
+ }
186
+
187
+ if (typeof this.#onInput === 'function') {
188
+ this.#onInput(info);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Computes the current line height from the textarea's computed styles.
194
+ * Falls back to `fontSize * 1.2` if lineHeight is not a number.
195
+ * @returns {number} - The computed line height in pixels.
196
+ */
197
+ #getLineHeight() {
198
+ const style = window.getComputedStyle(this.#textarea);
199
+ const line = parseFloat(style.lineHeight);
200
+ if (!Number.isNaN(line)) return line;
201
+ return parseFloat(style.fontSize) * 1.2;
202
+ }
203
+
204
+ /**
205
+ * Returns the latest height and row count of the textarea.
206
+ * @returns {{ height: number, rows: number }} - Last known resize state.
207
+ */
208
+ getData() {
209
+ return {
210
+ rows: this.#lastKnownRows,
211
+ height: this.#lastKnownHeight,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Manually trigger a resize check.
217
+ */
218
+ refresh() {
219
+ this.#resize();
220
+ }
221
+
222
+ /**
223
+ * Cleans up internal listeners and disables dynamic behavior.
224
+ */
225
+ destroy() {
226
+ this.#textarea.removeEventListener('input', this._handleInput);
227
+ }
228
+ }
229
+
230
+ module.exports = TinyTextarea;
@@ -0,0 +1,128 @@
1
+ export default TinyTextarea;
2
+ export type OnInputInfo = {
3
+ /**
4
+ * - Total number of `\n` line breaks in the textarea value.
5
+ */
6
+ breakLines: number;
7
+ /**
8
+ * - Final calculated height (in pixels) applied to the textarea.
9
+ */
10
+ height: number;
11
+ /**
12
+ * - Internal scrollHeight before limiting.
13
+ */
14
+ scrollHeight: number;
15
+ /**
16
+ * - Maximum allowed height before scrolling is forced.
17
+ */
18
+ maxHeight: number;
19
+ /**
20
+ * - Height of one line of text, computed from CSS.
21
+ */
22
+ lineHeight: number;
23
+ /**
24
+ * - Maximum number of visible rows allowed.
25
+ */
26
+ maxRows: number;
27
+ /**
28
+ * - Effective number of visual rows being used.
29
+ */
30
+ rows: number;
31
+ };
32
+ /**
33
+ * @typedef {Object} OnInputInfo
34
+ * @property {number} breakLines - Total number of `\n` line breaks in the textarea value.
35
+ * @property {number} height - Final calculated height (in pixels) applied to the textarea.
36
+ * @property {number} scrollHeight - Internal scrollHeight before limiting.
37
+ * @property {number} maxHeight - Maximum allowed height before scrolling is forced.
38
+ * @property {number} lineHeight - Height of one line of text, computed from CSS.
39
+ * @property {number} maxRows - Maximum number of visible rows allowed.
40
+ * @property {number} rows - Effective number of visual rows being used.
41
+ */
42
+ /**
43
+ * A lightweight utility class that automatically adjusts the height of a `<textarea>`
44
+ * element based on its content. It prevents scrollbars by expanding vertically as needed,
45
+ * up to a configurable maximum number of visible rows.
46
+ *
47
+ * Features:
48
+ * - Automatically resizes the textarea as the user types
49
+ * - Prevents vertical scrollbars until a maximum row limit is reached
50
+ * - Supports additional height padding
51
+ * - Provides real-time callbacks for input and resize events
52
+ * - Allows manual refresh and cleanup of behavior
53
+ *
54
+ * Ideal for chat inputs, note editors, or any form where dynamic space usage
55
+ * is preferred without relying on scrollbars too early.
56
+ *
57
+ * @class
58
+ * @beta
59
+ */
60
+ declare class TinyTextarea {
61
+ /**
62
+ * Creates a new TinyTextarea instance.
63
+ *
64
+ * @param {HTMLTextAreaElement} textarea - The `<textarea>` element to enhance.
65
+ * @param {Object} [options={}] - Optional configuration parameters.
66
+ * @param {number} [options.maxRows] - Maximum number of visible rows before scrolling.
67
+ * @param {number} [options.extraHeight] - Additional pixels to add to final height.
68
+ * @param {(info: OnInputInfo) => void} [options.onResize] - Callback when the number of rows changes.
69
+ * @param {(info: OnInputInfo) => void} [options.onInput] - Callback on every input event.
70
+ * @throws {Error} If `textarea` is not a valid `<textarea>` element.
71
+ * @throws {TypeError} If provided options are of invalid types.
72
+ */
73
+ constructor(textarea: HTMLTextAreaElement, options?: {
74
+ maxRows?: number | undefined;
75
+ extraHeight?: number | undefined;
76
+ onResize?: ((info: OnInputInfo) => void) | undefined;
77
+ onInput?: ((info: OnInputInfo) => void) | undefined;
78
+ });
79
+ /**
80
+ * Returns the computed line height in pixels.
81
+ * @returns {number}
82
+ */
83
+ get lineHeight(): number;
84
+ /**
85
+ * Returns the maximum number of rows allowed.
86
+ * @returns {number}
87
+ */
88
+ get maxRows(): number;
89
+ /**
90
+ * Returns the additional height added to the textarea.
91
+ * @returns {number}
92
+ */
93
+ get extraHeight(): number;
94
+ /**
95
+ * Returns the most recently applied height.
96
+ * @returns {number}
97
+ */
98
+ get currentHeight(): number;
99
+ /**
100
+ * Returns the most recently calculated row count.
101
+ * @returns {number}
102
+ */
103
+ get currentRows(): number;
104
+ /**
105
+ * Returns the original textarea element managed by this instance.
106
+ * @returns {HTMLTextAreaElement}
107
+ */
108
+ get textarea(): HTMLTextAreaElement;
109
+ _handleInput: () => void;
110
+ /**
111
+ * Returns the latest height and row count of the textarea.
112
+ * @returns {{ height: number, rows: number }} - Last known resize state.
113
+ */
114
+ getData(): {
115
+ height: number;
116
+ rows: number;
117
+ };
118
+ /**
119
+ * Manually trigger a resize check.
120
+ */
121
+ refresh(): void;
122
+ /**
123
+ * Cleans up internal listeners and disables dynamic behavior.
124
+ */
125
+ destroy(): void;
126
+ #private;
127
+ }
128
+ //# sourceMappingURL=TinyTextarea.d.mts.map