tiny-essentials 1.25.0 → 1.25.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.
@@ -0,0 +1,288 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Represents the result of a single text segment comparison.
5
+ * @typedef {Object} DiffResult
6
+ * @property {string} value - The actual string content of the segment.
7
+ * @property {"normal"|"added"|"deleted"} type - The status of the segment relative to the comparison.
8
+ */
9
+
10
+ /**
11
+ * A utility class to store a history of text strings and compute the differences
12
+ * between any two versions, generating a detailed diff output.
13
+ */
14
+ class TinyTextDiffer {
15
+ /** @type {string[]} */
16
+ #history = [];
17
+
18
+ /** @type {boolean} */
19
+ #destroyed = false;
20
+
21
+ /**
22
+ * Retrieves a shallow copy of the current history array.
23
+ * @returns {string[]}
24
+ */
25
+ get history() {
26
+ return [...this.#history];
27
+ }
28
+
29
+ /**
30
+ * Overwrites the current history array, ensuring all elements are valid strings.
31
+ * @param {string[]} history
32
+ */
33
+ set history(history) {
34
+ if (!Array.isArray(history)) {
35
+ throw new TypeError('History data must be provided as an array.');
36
+ }
37
+ if (!history.every((item) => typeof item === 'string')) {
38
+ throw new TypeError('All items in the history array must be strings.');
39
+ }
40
+ /** @type {string[]} */
41
+ this.#history = [...history];
42
+ }
43
+
44
+ /**
45
+ * Gets the total number of versions currently stored in the history.
46
+ * @returns {number}
47
+ */
48
+ get size() {
49
+ return this.#history.length;
50
+ }
51
+
52
+ /**
53
+ * Initializes a new instance of TinyTextDiffer.
54
+ * @param {string[]} [history=[]]
55
+ */
56
+ constructor(history = []) {
57
+ this.history = history;
58
+ }
59
+
60
+ /**
61
+ * @throws {Error}
62
+ * @returns {void}
63
+ */
64
+ #checkDestroyed() {
65
+ if (this.#destroyed) {
66
+ throw new Error('Cannot perform operations on a destroyed TinyTextDiffer instance.');
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param {any} index
72
+ * @throws {TypeError}
73
+ * @returns {void}
74
+ */
75
+ #checkIndex(index) {
76
+ if (typeof index !== 'number') {
77
+ throw new TypeError('The provided index must be a valid number.');
78
+ }
79
+ }
80
+
81
+ /**
82
+ * @param {any} text
83
+ * @throws {TypeError}
84
+ * @returns {void}
85
+ */
86
+ #checkText(text) {
87
+ if (typeof text !== 'string') {
88
+ throw new TypeError('The provided text must be a valid string.');
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Retrieves the text string at the specified index.
94
+ * @param {number} index
95
+ * @returns {string}
96
+ */
97
+ get(index) {
98
+ this.#checkDestroyed();
99
+ this.#checkIndex(index);
100
+ if (typeof this.#history[index] === 'undefined') {
101
+ throw new Error(`No text version found at index ${index}.`);
102
+ }
103
+ return this.#history[index];
104
+ }
105
+
106
+ /**
107
+ * Checks if a text string exists at the specified index.
108
+ * @param {number} index
109
+ * @returns {boolean}
110
+ */
111
+ has(index) {
112
+ this.#checkDestroyed();
113
+ this.#checkIndex(index);
114
+ return typeof this.#history[index] !== 'undefined';
115
+ }
116
+
117
+ /**
118
+ * Appends a new text string to the end of the history.
119
+ * @param {string} text
120
+ * @returns {void}
121
+ */
122
+ add(text) {
123
+ this.#checkDestroyed();
124
+ this.#checkText(text);
125
+ this.#history.push(text);
126
+ }
127
+
128
+ /**
129
+ * Removes and returns the last text string from the history.
130
+ * @returns {string | undefined}
131
+ */
132
+ remove() {
133
+ this.#checkDestroyed();
134
+ return this.#history.pop();
135
+ }
136
+
137
+ /**
138
+ * Inserts a text string at the specified index position.
139
+ * @param {number} index
140
+ * @param {string} text
141
+ * @returns {void}
142
+ */
143
+ addAt(index, text) {
144
+ this.#checkDestroyed();
145
+ this.#checkIndex(index);
146
+ this.#checkText(text);
147
+ this.#history.splice(index, 0, text);
148
+ }
149
+
150
+ /**
151
+ * Removes the text string at the specified index position.
152
+ * @param {number} index
153
+ * @returns {boolean}
154
+ */
155
+ removeAt(index) {
156
+ this.#checkDestroyed();
157
+ this.#checkIndex(index);
158
+ const oldSize = this.#history.length;
159
+ this.#history.splice(index, 1);
160
+ const newSize = this.#history.length;
161
+ return oldSize !== newSize;
162
+ }
163
+
164
+ /**
165
+ * Empties the entire history.
166
+ * @returns {void}
167
+ */
168
+ clear() {
169
+ this.#checkDestroyed();
170
+ this.#history = [];
171
+ }
172
+
173
+ /**
174
+ * Compares multiple pairs of text strings from the history based on their indices.
175
+ * Each pair of indices (e.g., index1 and index2) will produce a DiffResult array.
176
+ * @param {...number} indexes - An even number of indices to be compared in pairs.
177
+ * @returns {(DiffResult[])[]} An array of DiffResult arrays for each compared pair.
178
+ */
179
+ compare(...indexes) {
180
+ this.#checkDestroyed();
181
+
182
+ /** @type {number} */
183
+ const totalIndexes = indexes.length;
184
+
185
+ if (totalIndexes === 0 || totalIndexes % 2 !== 0) {
186
+ throw new Error(
187
+ 'The compare method requires an even number of indices to form comparison pairs.',
188
+ );
189
+ }
190
+
191
+ /** @type {(DiffResult[])[]} */
192
+ const results = [];
193
+
194
+ for (let i = 0; i < totalIndexes; i += 2) {
195
+ const i2 = i + 1;
196
+ if (i2 > totalIndexes - 1) continue;
197
+ this.#checkIndex(indexes[i]);
198
+ this.#checkIndex(indexes[i2]);
199
+
200
+ /** @type {string} */
201
+ const str1 = this.#history[indexes[i]];
202
+ /** @type {string} */
203
+ const str2 = this.#history[indexes[i2]];
204
+
205
+ results.push(this._computeDiff(str1, str2));
206
+ }
207
+
208
+ return results;
209
+ }
210
+
211
+ /**
212
+ * Computes the Longest Common Subsequence (LCS) to generate the diff result.
213
+ * @private
214
+ * @param {string} str1
215
+ * @param {string} str2
216
+ * @returns {DiffResult[]}
217
+ */
218
+ _computeDiff(str1, str2) {
219
+ /** @type {number} */
220
+ const len1 = str1.length;
221
+ /** @type {number} */
222
+ const len2 = str2.length;
223
+
224
+ /** @type {number[][]} */
225
+ const matrix = Array(len1 + 1)
226
+ .fill(null)
227
+ .map(() => Array(len2 + 1).fill(0));
228
+
229
+ /** @type {number} */
230
+ let i = 1;
231
+ /** @type {number} */
232
+ let j = 1;
233
+
234
+ for (i = 1; i <= len1; i++) {
235
+ for (j = 1; j <= len2; j++) {
236
+ if (str1[i - 1] === str2[j - 1]) {
237
+ matrix[i][j] = matrix[i - 1][j - 1] + 1;
238
+ } else {
239
+ matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]);
240
+ }
241
+ }
242
+ }
243
+
244
+ i = len1;
245
+ j = len2;
246
+
247
+ /** @type {DiffResult[]} */
248
+ const result = [];
249
+
250
+ while (i > 0 || j > 0) {
251
+ if (i > 0 && j > 0 && str1[i - 1] === str2[j - 1]) {
252
+ result.unshift({ value: str1[i - 1], type: 'normal' });
253
+ i--;
254
+ j--;
255
+ } else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
256
+ result.unshift({ value: str2[j - 1], type: 'added' });
257
+ j--;
258
+ } else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
259
+ result.unshift({ value: str1[i - 1], type: 'deleted' });
260
+ i--;
261
+ }
262
+ }
263
+
264
+ return result.reduce((/** @type {DiffResult[]} */ acc, /** @type {DiffResult} */ curr) => {
265
+ /** @type {number} */
266
+ const lastIndex = acc.length - 1;
267
+
268
+ if (acc.length > 0 && acc[lastIndex].type === curr.type) {
269
+ acc[lastIndex].value += curr.value;
270
+ } else {
271
+ acc.push(curr);
272
+ }
273
+ return acc;
274
+ }, []);
275
+ }
276
+
277
+ /**
278
+ * Cleans up internal references and marks the instance as destroyed to prevent memory leaks.
279
+ * @returns {void}
280
+ */
281
+ destroy() {
282
+ if (this.#destroyed) return;
283
+ this.#history = [];
284
+ this.#destroyed = true;
285
+ }
286
+ }
287
+
288
+ module.exports = TinyTextDiffer;
@@ -0,0 +1,109 @@
1
+ export default TinyTextDiffer;
2
+ /**
3
+ * Represents the result of a single text segment comparison.
4
+ */
5
+ export type DiffResult = {
6
+ /**
7
+ * - The actual string content of the segment.
8
+ */
9
+ value: string;
10
+ /**
11
+ * - The status of the segment relative to the comparison.
12
+ */
13
+ type: "normal" | "added" | "deleted";
14
+ };
15
+ /**
16
+ * Represents the result of a single text segment comparison.
17
+ * @typedef {Object} DiffResult
18
+ * @property {string} value - The actual string content of the segment.
19
+ * @property {"normal"|"added"|"deleted"} type - The status of the segment relative to the comparison.
20
+ */
21
+ /**
22
+ * A utility class to store a history of text strings and compute the differences
23
+ * between any two versions, generating a detailed diff output.
24
+ */
25
+ declare class TinyTextDiffer {
26
+ /**
27
+ * Initializes a new instance of TinyTextDiffer.
28
+ * @param {string[]} [history=[]]
29
+ */
30
+ constructor(history?: string[]);
31
+ /**
32
+ * Overwrites the current history array, ensuring all elements are valid strings.
33
+ * @param {string[]} history
34
+ */
35
+ set history(history: string[]);
36
+ /**
37
+ * Retrieves a shallow copy of the current history array.
38
+ * @returns {string[]}
39
+ */
40
+ get history(): string[];
41
+ /**
42
+ * Gets the total number of versions currently stored in the history.
43
+ * @returns {number}
44
+ */
45
+ get size(): number;
46
+ /**
47
+ * Retrieves the text string at the specified index.
48
+ * @param {number} index
49
+ * @returns {string}
50
+ */
51
+ get(index: number): string;
52
+ /**
53
+ * Checks if a text string exists at the specified index.
54
+ * @param {number} index
55
+ * @returns {boolean}
56
+ */
57
+ has(index: number): boolean;
58
+ /**
59
+ * Appends a new text string to the end of the history.
60
+ * @param {string} text
61
+ * @returns {void}
62
+ */
63
+ add(text: string): void;
64
+ /**
65
+ * Removes and returns the last text string from the history.
66
+ * @returns {string | undefined}
67
+ */
68
+ remove(): string | undefined;
69
+ /**
70
+ * Inserts a text string at the specified index position.
71
+ * @param {number} index
72
+ * @param {string} text
73
+ * @returns {void}
74
+ */
75
+ addAt(index: number, text: string): void;
76
+ /**
77
+ * Removes the text string at the specified index position.
78
+ * @param {number} index
79
+ * @returns {boolean}
80
+ */
81
+ removeAt(index: number): boolean;
82
+ /**
83
+ * Empties the entire history.
84
+ * @returns {void}
85
+ */
86
+ clear(): void;
87
+ /**
88
+ * Compares multiple pairs of text strings from the history based on their indices.
89
+ * Each pair of indices (e.g., index1 and index2) will produce a DiffResult array.
90
+ * @param {...number} indexes - An even number of indices to be compared in pairs.
91
+ * @returns {(DiffResult[])[]} An array of DiffResult arrays for each compared pair.
92
+ */
93
+ compare(...indexes: number[]): (DiffResult[])[];
94
+ /**
95
+ * Computes the Longest Common Subsequence (LCS) to generate the diff result.
96
+ * @private
97
+ * @param {string} str1
98
+ * @param {string} str2
99
+ * @returns {DiffResult[]}
100
+ */
101
+ private _computeDiff;
102
+ /**
103
+ * Cleans up internal references and marks the instance as destroyed to prevent memory leaks.
104
+ * @returns {void}
105
+ */
106
+ destroy(): void;
107
+ #private;
108
+ }
109
+ //# sourceMappingURL=TinyTextDiffer.d.mts.map
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Represents the result of a single text segment comparison.
3
+ * @typedef {Object} DiffResult
4
+ * @property {string} value - The actual string content of the segment.
5
+ * @property {"normal"|"added"|"deleted"} type - The status of the segment relative to the comparison.
6
+ */
7
+ /**
8
+ * A utility class to store a history of text strings and compute the differences
9
+ * between any two versions, generating a detailed diff output.
10
+ */
11
+ class TinyTextDiffer {
12
+ /** @type {string[]} */
13
+ #history = [];
14
+ /** @type {boolean} */
15
+ #destroyed = false;
16
+ /**
17
+ * Retrieves a shallow copy of the current history array.
18
+ * @returns {string[]}
19
+ */
20
+ get history() {
21
+ return [...this.#history];
22
+ }
23
+ /**
24
+ * Overwrites the current history array, ensuring all elements are valid strings.
25
+ * @param {string[]} history
26
+ */
27
+ set history(history) {
28
+ if (!Array.isArray(history)) {
29
+ throw new TypeError('History data must be provided as an array.');
30
+ }
31
+ if (!history.every((item) => typeof item === 'string')) {
32
+ throw new TypeError('All items in the history array must be strings.');
33
+ }
34
+ /** @type {string[]} */
35
+ this.#history = [...history];
36
+ }
37
+ /**
38
+ * Gets the total number of versions currently stored in the history.
39
+ * @returns {number}
40
+ */
41
+ get size() {
42
+ return this.#history.length;
43
+ }
44
+ /**
45
+ * Initializes a new instance of TinyTextDiffer.
46
+ * @param {string[]} [history=[]]
47
+ */
48
+ constructor(history = []) {
49
+ this.history = history;
50
+ }
51
+ /**
52
+ * @throws {Error}
53
+ * @returns {void}
54
+ */
55
+ #checkDestroyed() {
56
+ if (this.#destroyed) {
57
+ throw new Error('Cannot perform operations on a destroyed TinyTextDiffer instance.');
58
+ }
59
+ }
60
+ /**
61
+ * @param {any} index
62
+ * @throws {TypeError}
63
+ * @returns {void}
64
+ */
65
+ #checkIndex(index) {
66
+ if (typeof index !== 'number') {
67
+ throw new TypeError('The provided index must be a valid number.');
68
+ }
69
+ }
70
+ /**
71
+ * @param {any} text
72
+ * @throws {TypeError}
73
+ * @returns {void}
74
+ */
75
+ #checkText(text) {
76
+ if (typeof text !== 'string') {
77
+ throw new TypeError('The provided text must be a valid string.');
78
+ }
79
+ }
80
+ /**
81
+ * Retrieves the text string at the specified index.
82
+ * @param {number} index
83
+ * @returns {string}
84
+ */
85
+ get(index) {
86
+ this.#checkDestroyed();
87
+ this.#checkIndex(index);
88
+ if (typeof this.#history[index] === 'undefined') {
89
+ throw new Error(`No text version found at index ${index}.`);
90
+ }
91
+ return this.#history[index];
92
+ }
93
+ /**
94
+ * Checks if a text string exists at the specified index.
95
+ * @param {number} index
96
+ * @returns {boolean}
97
+ */
98
+ has(index) {
99
+ this.#checkDestroyed();
100
+ this.#checkIndex(index);
101
+ return typeof this.#history[index] !== 'undefined';
102
+ }
103
+ /**
104
+ * Appends a new text string to the end of the history.
105
+ * @param {string} text
106
+ * @returns {void}
107
+ */
108
+ add(text) {
109
+ this.#checkDestroyed();
110
+ this.#checkText(text);
111
+ this.#history.push(text);
112
+ }
113
+ /**
114
+ * Removes and returns the last text string from the history.
115
+ * @returns {string | undefined}
116
+ */
117
+ remove() {
118
+ this.#checkDestroyed();
119
+ return this.#history.pop();
120
+ }
121
+ /**
122
+ * Inserts a text string at the specified index position.
123
+ * @param {number} index
124
+ * @param {string} text
125
+ * @returns {void}
126
+ */
127
+ addAt(index, text) {
128
+ this.#checkDestroyed();
129
+ this.#checkIndex(index);
130
+ this.#checkText(text);
131
+ this.#history.splice(index, 0, text);
132
+ }
133
+ /**
134
+ * Removes the text string at the specified index position.
135
+ * @param {number} index
136
+ * @returns {boolean}
137
+ */
138
+ removeAt(index) {
139
+ this.#checkDestroyed();
140
+ this.#checkIndex(index);
141
+ const oldSize = this.#history.length;
142
+ this.#history.splice(index, 1);
143
+ const newSize = this.#history.length;
144
+ return oldSize !== newSize;
145
+ }
146
+ /**
147
+ * Empties the entire history.
148
+ * @returns {void}
149
+ */
150
+ clear() {
151
+ this.#checkDestroyed();
152
+ this.#history = [];
153
+ }
154
+ /**
155
+ * Compares multiple pairs of text strings from the history based on their indices.
156
+ * Each pair of indices (e.g., index1 and index2) will produce a DiffResult array.
157
+ * @param {...number} indexes - An even number of indices to be compared in pairs.
158
+ * @returns {(DiffResult[])[]} An array of DiffResult arrays for each compared pair.
159
+ */
160
+ compare(...indexes) {
161
+ this.#checkDestroyed();
162
+ /** @type {number} */
163
+ const totalIndexes = indexes.length;
164
+ if (totalIndexes === 0 || totalIndexes % 2 !== 0) {
165
+ throw new Error('The compare method requires an even number of indices to form comparison pairs.');
166
+ }
167
+ /** @type {(DiffResult[])[]} */
168
+ const results = [];
169
+ for (let i = 0; i < totalIndexes; i += 2) {
170
+ const i2 = i + 1;
171
+ if (i2 > totalIndexes - 1)
172
+ continue;
173
+ this.#checkIndex(indexes[i]);
174
+ this.#checkIndex(indexes[i2]);
175
+ /** @type {string} */
176
+ const str1 = this.#history[indexes[i]];
177
+ /** @type {string} */
178
+ const str2 = this.#history[indexes[i2]];
179
+ results.push(this._computeDiff(str1, str2));
180
+ }
181
+ return results;
182
+ }
183
+ /**
184
+ * Computes the Longest Common Subsequence (LCS) to generate the diff result.
185
+ * @private
186
+ * @param {string} str1
187
+ * @param {string} str2
188
+ * @returns {DiffResult[]}
189
+ */
190
+ _computeDiff(str1, str2) {
191
+ /** @type {number} */
192
+ const len1 = str1.length;
193
+ /** @type {number} */
194
+ const len2 = str2.length;
195
+ /** @type {number[][]} */
196
+ const matrix = Array(len1 + 1)
197
+ .fill(null)
198
+ .map(() => Array(len2 + 1).fill(0));
199
+ /** @type {number} */
200
+ let i = 1;
201
+ /** @type {number} */
202
+ let j = 1;
203
+ for (i = 1; i <= len1; i++) {
204
+ for (j = 1; j <= len2; j++) {
205
+ if (str1[i - 1] === str2[j - 1]) {
206
+ matrix[i][j] = matrix[i - 1][j - 1] + 1;
207
+ }
208
+ else {
209
+ matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]);
210
+ }
211
+ }
212
+ }
213
+ i = len1;
214
+ j = len2;
215
+ /** @type {DiffResult[]} */
216
+ const result = [];
217
+ while (i > 0 || j > 0) {
218
+ if (i > 0 && j > 0 && str1[i - 1] === str2[j - 1]) {
219
+ result.unshift({ value: str1[i - 1], type: 'normal' });
220
+ i--;
221
+ j--;
222
+ }
223
+ else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
224
+ result.unshift({ value: str2[j - 1], type: 'added' });
225
+ j--;
226
+ }
227
+ else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
228
+ result.unshift({ value: str1[i - 1], type: 'deleted' });
229
+ i--;
230
+ }
231
+ }
232
+ return result.reduce((/** @type {DiffResult[]} */ acc, /** @type {DiffResult} */ curr) => {
233
+ /** @type {number} */
234
+ const lastIndex = acc.length - 1;
235
+ if (acc.length > 0 && acc[lastIndex].type === curr.type) {
236
+ acc[lastIndex].value += curr.value;
237
+ }
238
+ else {
239
+ acc.push(curr);
240
+ }
241
+ return acc;
242
+ }, []);
243
+ }
244
+ /**
245
+ * Cleans up internal references and marks the instance as destroyed to prevent memory leaks.
246
+ * @returns {void}
247
+ */
248
+ destroy() {
249
+ if (this.#destroyed)
250
+ return;
251
+ this.#history = [];
252
+ this.#destroyed = true;
253
+ }
254
+ }
255
+ export default TinyTextDiffer;
package/docs/v1/README.md CHANGED
@@ -60,6 +60,8 @@ This folder contains the core scripts we have worked on so far. Each file is a m
60
60
  - 👀 **[TinyElementObserver](./libs/TinyElementObserver.md)** — A DOM mutation tracking utility built on MutationObserver, with customizable detectors for handling changes, event dispatching, and lifecycle management.
61
61
  - ⏳ **[TinyLoadingScreen](./libs/TinyLoadingScreen.md)** — A lightweight, fully-configurable loading overlay with fade-in/out animations, custom messages (string or HTMLElement), HTML rendering option, and status-change callbacks.
62
62
  - 🎨 **[TinyColorValidator](./libs/TinyColorValidator.md)** — A comprehensive CSS color validation and parsing utility supporting HEX, HEXA, RGB, RGBA, HSL, HSLA, HWB, Lab, LCH, standard HTML color names, and special keywords, with automatic type detection and parsing.
63
+ * 📝 **[TinyTextDiffer](./libs/TinyTextDiffer.md)** — A granular text comparison utility using the LCS algorithm to detect additions, deletions, and unchanged segments between multiple history versions, returning a clean, parseable diff structure.
64
+ * 🕒 **[TinyAnalogClock](./libs/TinyAnalogClock.md)** — A lightweight analog clock engine for managing time-based rotations, supporting custom offsets, smooth transitions, and easy binding to CSS variables rendering.
63
65
 
64
66
  ### 3. **`fileManager/`**
65
67
  * 📁 **[Main](./fileManager/main.md)** — A Node.js file/directory utility module with support for JSON, backups, renaming, size analysis, and more.