tiny-essentials 1.13.2 → 1.15.0
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.
- package/dist/v1/TinyBasicsEs.js +656 -26
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyDragger.js +196 -26
- package/dist/v1/TinyEssentials.js +896 -26
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyNotifications.js +408 -0
- package/dist/v1/TinyNotifications.min.js +1 -0
- package/dist/v1/TinyUploadClicker.js +198 -26
- package/dist/v1/basics/collision.cjs +413 -0
- package/dist/v1/basics/collision.d.mts +187 -0
- package/dist/v1/basics/collision.mjs +350 -0
- package/dist/v1/basics/html.cjs +201 -26
- package/dist/v1/basics/html.d.mts +71 -7
- package/dist/v1/basics/html.mjs +186 -23
- package/dist/v1/basics/index.cjs +24 -0
- package/dist/v1/basics/index.d.mts +24 -1
- package/dist/v1/basics/index.mjs +4 -3
- package/dist/v1/basics/text.cjs +43 -0
- package/dist/v1/basics/text.d.mts +16 -0
- package/dist/v1/basics/text.mjs +37 -0
- package/dist/v1/build/TinyNotifications.cjs +7 -0
- package/dist/v1/build/TinyNotifications.d.mts +3 -0
- package/dist/v1/build/TinyNotifications.mjs +2 -0
- package/dist/v1/index.cjs +26 -0
- package/dist/v1/index.d.mts +25 -1
- package/dist/v1/index.mjs +5 -3
- package/dist/v1/libs/TinyNotifications.cjs +238 -0
- package/dist/v1/libs/TinyNotifications.d.mts +106 -0
- package/dist/v1/libs/TinyNotifications.mjs +211 -0
- package/docs/v1/README.md +5 -3
- package/docs/v1/basics/array.md +16 -43
- package/docs/v1/basics/collision.md +237 -0
- package/docs/v1/basics/html.md +117 -28
- package/docs/v1/basics/text.md +44 -14
- package/docs/v1/libs/TinyNotifications.md +189 -0
- package/package.json +5 -2
|
@@ -7,12 +7,56 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export function areHtmlElsColliding(elem1: Element, elem2: Element): boolean;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
10
|
+
* Checks if two DOM elements are colliding on the screen, and locks the collision
|
|
11
|
+
* until the element exits through the same side it entered.
|
|
12
|
+
*
|
|
13
|
+
* @param {Element} elem1 - First DOM element (e.g. draggable or moving element).
|
|
14
|
+
* @param {Element} elem2 - Second DOM element (e.g. a container or boundary element).
|
|
15
|
+
* @param {'top'|'bottom'|'left'|'right'} lockDirection - Direction that must be respected to unlock the collision.
|
|
16
|
+
* @param {WeakMap<Element, string>} stateMap - A shared WeakMap to track persistent entry direction per element.
|
|
17
|
+
* @returns {boolean} True if collision is still active.
|
|
18
|
+
*/
|
|
19
|
+
export function areHtmlElsCollidingWithLock(elem1: Element, elem2: Element, lockDirection: "top" | "bottom" | "left" | "right", stateMap: WeakMap<Element, string>): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Reads the contents of a file using the specified FileReader method.
|
|
22
|
+
*
|
|
23
|
+
* @param {File} file - The file to be read.
|
|
24
|
+
* @param {'readAsArrayBuffer'|'readAsDataURL'|'readAsText'|'readAsBinaryString'} method -
|
|
25
|
+
* The FileReader method to use for reading the file.
|
|
26
|
+
* @returns {Promise<any>} - A promise that resolves with the file content, according to the chosen method.
|
|
27
|
+
* @throws {Error} - If an unexpected error occurs while handling the result.
|
|
28
|
+
* @throws {DOMException} - If the FileReader encounters an error while reading the file.
|
|
29
|
+
*/
|
|
30
|
+
export function readFileBlob(file: File, method: "readAsArrayBuffer" | "readAsDataURL" | "readAsText" | "readAsBinaryString"): Promise<any>;
|
|
31
|
+
/**
|
|
32
|
+
* Reads a file as a Base64 string using FileReader, and optionally formats it as a full data URL.
|
|
33
|
+
*
|
|
34
|
+
* Performs strict validation to ensure the result is a valid Base64 string or a proper data URL.
|
|
35
|
+
*
|
|
36
|
+
* @param {File} file - The file to be read.
|
|
37
|
+
* @param {boolean|string} [isDataUrl=false] - If true, returns a full data URL; if false, returns only the Base64 string;
|
|
38
|
+
* if a string is passed, it is used as the MIME type in the data URL.
|
|
39
|
+
* @returns {Promise<string>} - A promise that resolves with the Base64 string or data URL.
|
|
40
|
+
*
|
|
41
|
+
* @throws {TypeError} - If the result is not a string or if `isDataUrl` is not a valid type.
|
|
42
|
+
* @throws {Error} - If the result does not match the expected data URL format or Base64 structure.
|
|
43
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
44
|
+
*/
|
|
45
|
+
export function readBase64Blob(file: File, isDataUrl?: boolean | string): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Reads a file and strictly validates its content as proper JSON using FileReader.
|
|
48
|
+
*
|
|
49
|
+
* Performs several checks to ensure the file contains valid, parsable JSON data.
|
|
50
|
+
*
|
|
51
|
+
* @param {File} file - The file to be read. It must contain valid JSON as plain text.
|
|
52
|
+
* @returns {Promise<Record<string|number|symbol, any>|any[]>} - A promise that resolves with the parsed JSON object.
|
|
53
|
+
*
|
|
54
|
+
* @throws {SyntaxError} - If the file content is not valid JSON syntax.
|
|
55
|
+
* @throws {TypeError} - If the result is not a string or does not represent a JSON value.
|
|
56
|
+
* @throws {Error} - If the result is empty or structurally invalid as JSON.
|
|
57
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
14
58
|
*/
|
|
15
|
-
export function readJsonBlob(file: File): Promise<any>;
|
|
59
|
+
export function readJsonBlob(file: File): Promise<Record<string | number | symbol, any> | any[]>;
|
|
16
60
|
/**
|
|
17
61
|
* Saves a JSON object as a downloadable file.
|
|
18
62
|
* @param {string} filename
|
|
@@ -44,19 +88,39 @@ export function fetchJson(url: string, { method, body, timeout, retries, headers
|
|
|
44
88
|
}): Promise<any>;
|
|
45
89
|
/**
|
|
46
90
|
* Installs a script that toggles CSS classes on a given element
|
|
47
|
-
* based on the page's visibility or focus state
|
|
91
|
+
* based on the page's visibility or focus state, and optionally
|
|
92
|
+
* triggers callbacks on visibility changes.
|
|
48
93
|
*
|
|
49
94
|
* @param {Object} [settings={}]
|
|
50
95
|
* @param {HTMLElement} [settings.element=document.body] - The element to receive visibility classes.
|
|
51
96
|
* @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
|
|
52
97
|
* @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
|
|
98
|
+
* @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
|
|
99
|
+
* @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
|
|
53
100
|
* @returns {() => void} Function that removes all installed event listeners.
|
|
101
|
+
* @throws {TypeError} If any provided setting is invalid.
|
|
54
102
|
*/
|
|
55
|
-
export function installWindowHiddenScript({ element, hiddenClass, visibleClass, }?: {
|
|
103
|
+
export function installWindowHiddenScript({ element, hiddenClass, visibleClass, onVisible, onHidden, }?: {
|
|
56
104
|
element?: HTMLElement | undefined;
|
|
57
105
|
hiddenClass?: string | undefined;
|
|
58
106
|
visibleClass?: string | undefined;
|
|
107
|
+
onVisible?: (() => void) | undefined;
|
|
108
|
+
onHidden?: (() => void) | undefined;
|
|
59
109
|
}): () => void;
|
|
110
|
+
/**
|
|
111
|
+
* Checks if the given element is at least partially visible in the viewport.
|
|
112
|
+
*
|
|
113
|
+
* @param {HTMLElement} element - The DOM element to check.
|
|
114
|
+
* @returns {boolean} True if the element is partially in the viewport, false otherwise.
|
|
115
|
+
*/
|
|
116
|
+
export function isInViewport(element: HTMLElement): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Checks if the given element is fully visible in the viewport (top and bottom).
|
|
119
|
+
*
|
|
120
|
+
* @param {HTMLElement} element - The DOM element to check.
|
|
121
|
+
* @returns {boolean} True if the element is fully visible in the viewport, false otherwise.
|
|
122
|
+
*/
|
|
123
|
+
export function isScrolledIntoView(element: HTMLElement): boolean;
|
|
60
124
|
export function getHtmlElBordersWidth(el: Element): HtmlElBoxSides;
|
|
61
125
|
export function getHtmlElBorders(el: Element): HtmlElBoxSides;
|
|
62
126
|
export function getHtmlElMargin(el: Element): HtmlElBoxSides;
|
package/dist/v1/basics/html.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isJsonObject } from './objFilter.mjs';
|
|
2
|
+
import { areElsColliding, areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, } from './collision.mjs';
|
|
2
3
|
/**
|
|
3
4
|
* Checks if two DOM elements are colliding on the screen.
|
|
4
5
|
*
|
|
@@ -9,37 +10,150 @@ import { isJsonObject } from './objFilter.mjs';
|
|
|
9
10
|
export function areHtmlElsColliding(elem1, elem2) {
|
|
10
11
|
const rect1 = elem1.getBoundingClientRect();
|
|
11
12
|
const rect2 = elem2.getBoundingClientRect();
|
|
12
|
-
return
|
|
13
|
-
rect1.left > rect2.right ||
|
|
14
|
-
rect1.bottom < rect2.top ||
|
|
15
|
-
rect1.top > rect2.bottom);
|
|
13
|
+
return areElsColliding(rect1, rect2);
|
|
16
14
|
}
|
|
17
15
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
16
|
+
* Checks if two DOM elements are colliding on the screen, and locks the collision
|
|
17
|
+
* until the element exits through the same side it entered.
|
|
18
|
+
*
|
|
19
|
+
* @param {Element} elem1 - First DOM element (e.g. draggable or moving element).
|
|
20
|
+
* @param {Element} elem2 - Second DOM element (e.g. a container or boundary element).
|
|
21
|
+
* @param {'top'|'bottom'|'left'|'right'} lockDirection - Direction that must be respected to unlock the collision.
|
|
22
|
+
* @param {WeakMap<Element, string>} stateMap - A shared WeakMap to track persistent entry direction per element.
|
|
23
|
+
* @returns {boolean} True if collision is still active.
|
|
22
24
|
*/
|
|
23
|
-
export function
|
|
25
|
+
export function areHtmlElsCollidingWithLock(elem1, elem2, lockDirection, stateMap) {
|
|
26
|
+
const rect1 = elem1.getBoundingClientRect();
|
|
27
|
+
const rect2 = elem2.getBoundingClientRect();
|
|
28
|
+
const isColliding = areElsColliding(rect1, rect2);
|
|
29
|
+
if (isColliding) {
|
|
30
|
+
// Save entry direction
|
|
31
|
+
if (!stateMap.has(elem1)) {
|
|
32
|
+
stateMap.set(elem1, lockDirection);
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
// Handle unlock logic
|
|
37
|
+
if (stateMap.has(elem1)) {
|
|
38
|
+
const lastDirection = stateMap.get(elem1);
|
|
39
|
+
switch (lastDirection) {
|
|
40
|
+
case 'top':
|
|
41
|
+
if (areElsCollTop(rect1, rect2))
|
|
42
|
+
stateMap.delete(elem1); // exited from top
|
|
43
|
+
break;
|
|
44
|
+
case 'bottom':
|
|
45
|
+
if (areElsCollBottom(rect1, rect2))
|
|
46
|
+
stateMap.delete(elem1); // exited from bottom
|
|
47
|
+
break;
|
|
48
|
+
case 'left':
|
|
49
|
+
if (areElsCollLeft(rect1, rect2))
|
|
50
|
+
stateMap.delete(elem1); // exited from left
|
|
51
|
+
break;
|
|
52
|
+
case 'right':
|
|
53
|
+
if (areElsCollRight(rect1, rect2))
|
|
54
|
+
stateMap.delete(elem1); // exited from right
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
return stateMap.has(elem1); // still colliding (locked)
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reads the contents of a file using the specified FileReader method.
|
|
63
|
+
*
|
|
64
|
+
* @param {File} file - The file to be read.
|
|
65
|
+
* @param {'readAsArrayBuffer'|'readAsDataURL'|'readAsText'|'readAsBinaryString'} method -
|
|
66
|
+
* The FileReader method to use for reading the file.
|
|
67
|
+
* @returns {Promise<any>} - A promise that resolves with the file content, according to the chosen method.
|
|
68
|
+
* @throws {Error} - If an unexpected error occurs while handling the result.
|
|
69
|
+
* @throws {DOMException} - If the FileReader encounters an error while reading the file.
|
|
70
|
+
*/
|
|
71
|
+
export function readFileBlob(file, method) {
|
|
24
72
|
return new Promise((resolve, reject) => {
|
|
25
73
|
const reader = new FileReader();
|
|
26
74
|
reader.onload = () => {
|
|
27
75
|
try {
|
|
28
|
-
|
|
29
|
-
const result = JSON.parse(reader.result);
|
|
30
|
-
resolve(result);
|
|
76
|
+
resolve(reader.result);
|
|
31
77
|
}
|
|
32
78
|
catch (error) {
|
|
33
|
-
|
|
34
|
-
reject(new Error(`Invalid JSON in file: ${file.name}\n${error.message}`));
|
|
79
|
+
reject(error);
|
|
35
80
|
}
|
|
36
81
|
};
|
|
37
82
|
reader.onerror = () => {
|
|
38
|
-
reject(
|
|
83
|
+
reject(reader.error);
|
|
39
84
|
};
|
|
40
|
-
reader
|
|
85
|
+
reader[method](file);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reads a file as a Base64 string using FileReader, and optionally formats it as a full data URL.
|
|
90
|
+
*
|
|
91
|
+
* Performs strict validation to ensure the result is a valid Base64 string or a proper data URL.
|
|
92
|
+
*
|
|
93
|
+
* @param {File} file - The file to be read.
|
|
94
|
+
* @param {boolean|string} [isDataUrl=false] - If true, returns a full data URL; if false, returns only the Base64 string;
|
|
95
|
+
* if a string is passed, it is used as the MIME type in the data URL.
|
|
96
|
+
* @returns {Promise<string>} - A promise that resolves with the Base64 string or data URL.
|
|
97
|
+
*
|
|
98
|
+
* @throws {TypeError} - If the result is not a string or if `isDataUrl` is not a valid type.
|
|
99
|
+
* @throws {Error} - If the result does not match the expected data URL format or Base64 structure.
|
|
100
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
101
|
+
*/
|
|
102
|
+
export function readBase64Blob(file, isDataUrl = false) {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
if (typeof isDataUrl !== 'string' && typeof isDataUrl !== 'boolean')
|
|
105
|
+
reject(new TypeError('The isDataUrl parameter must be a boolean or a string.'));
|
|
106
|
+
readFileBlob(file, 'readAsDataURL')
|
|
107
|
+
.then(
|
|
108
|
+
/**
|
|
109
|
+
* Ensure that the URL format is correct in the required pattern
|
|
110
|
+
* @param {string} base64Data
|
|
111
|
+
*/ (base64Data) => {
|
|
112
|
+
if (typeof base64Data !== 'string')
|
|
113
|
+
throw new TypeError('Expected file content to be a string.');
|
|
114
|
+
const match = base64Data.match(/^data:(.+);base64,(.*)$/);
|
|
115
|
+
if (!match || !match[2])
|
|
116
|
+
throw new Error('Invalid data URL format or missing Base64 content.');
|
|
117
|
+
const [, mimeType, base64] = match;
|
|
118
|
+
if (!/^[\w/+]+=*$/.test(base64))
|
|
119
|
+
throw new Error('Base64 content is malformed.');
|
|
120
|
+
if (typeof isDataUrl === 'boolean')
|
|
121
|
+
return resolve(isDataUrl ? base64Data : base64);
|
|
122
|
+
if (!/^[\w-]+\/[\w.+-]+$/.test(isDataUrl))
|
|
123
|
+
throw new Error(`Invalid MIME type string: ${isDataUrl}`);
|
|
124
|
+
return resolve(`data:${isDataUrl};base64,${base64}`);
|
|
125
|
+
})
|
|
126
|
+
.catch(reject);
|
|
41
127
|
});
|
|
42
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Reads a file and strictly validates its content as proper JSON using FileReader.
|
|
131
|
+
*
|
|
132
|
+
* Performs several checks to ensure the file contains valid, parsable JSON data.
|
|
133
|
+
*
|
|
134
|
+
* @param {File} file - The file to be read. It must contain valid JSON as plain text.
|
|
135
|
+
* @returns {Promise<Record<string|number|symbol, any>|any[]>} - A promise that resolves with the parsed JSON object.
|
|
136
|
+
*
|
|
137
|
+
* @throws {SyntaxError} - If the file content is not valid JSON syntax.
|
|
138
|
+
* @throws {TypeError} - If the result is not a string or does not represent a JSON value.
|
|
139
|
+
* @throws {Error} - If the result is empty or structurally invalid as JSON.
|
|
140
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
141
|
+
*/
|
|
142
|
+
export function readJsonBlob(file) {
|
|
143
|
+
return new Promise((resolve, reject) => readFileBlob(file, 'readAsText')
|
|
144
|
+
.then((data) => {
|
|
145
|
+
if (typeof data !== 'string')
|
|
146
|
+
throw new TypeError('Expected file content to be a string.');
|
|
147
|
+
const trimmed = data.trim();
|
|
148
|
+
if (trimmed.length === 0)
|
|
149
|
+
throw new Error('File is empty or contains only whitespace.');
|
|
150
|
+
const parsed = JSON.parse(trimmed);
|
|
151
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
152
|
+
throw new Error('Parsed content is not a valid JSON object or array.');
|
|
153
|
+
resolve(parsed);
|
|
154
|
+
})
|
|
155
|
+
.catch(reject));
|
|
156
|
+
}
|
|
43
157
|
/**
|
|
44
158
|
* Saves a JSON object as a downloadable file.
|
|
45
159
|
* @param {string} filename
|
|
@@ -119,7 +233,7 @@ export async function fetchJson(url, { method = 'GET', body, timeout = 0, retrie
|
|
|
119
233
|
if (!contentType.includes('application/json'))
|
|
120
234
|
throw new Error(`Unexpected content-type: ${contentType}`);
|
|
121
235
|
const data = await response.json();
|
|
122
|
-
if (!isJsonObject(data))
|
|
236
|
+
if (!Array.isArray(data) && !isJsonObject(data))
|
|
123
237
|
throw new Error('Received invalid data instead of valid JSON.');
|
|
124
238
|
return data;
|
|
125
239
|
}
|
|
@@ -208,23 +322,35 @@ export const getHtmlElPadding = (el) => {
|
|
|
208
322
|
};
|
|
209
323
|
/**
|
|
210
324
|
* Installs a script that toggles CSS classes on a given element
|
|
211
|
-
* based on the page's visibility or focus state
|
|
325
|
+
* based on the page's visibility or focus state, and optionally
|
|
326
|
+
* triggers callbacks on visibility changes.
|
|
212
327
|
*
|
|
213
328
|
* @param {Object} [settings={}]
|
|
214
329
|
* @param {HTMLElement} [settings.element=document.body] - The element to receive visibility classes.
|
|
215
330
|
* @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
|
|
216
331
|
* @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
|
|
332
|
+
* @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
|
|
333
|
+
* @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
|
|
217
334
|
* @returns {() => void} Function that removes all installed event listeners.
|
|
335
|
+
* @throws {TypeError} If any provided setting is invalid.
|
|
218
336
|
*/
|
|
219
|
-
export function installWindowHiddenScript({ element = document.body, hiddenClass = 'windowHidden', visibleClass = 'windowVisible', } = {}) {
|
|
337
|
+
export function installWindowHiddenScript({ element = document.body, hiddenClass = 'windowHidden', visibleClass = 'windowVisible', onVisible, onHidden, } = {}) {
|
|
338
|
+
if (!(element instanceof HTMLElement))
|
|
339
|
+
throw new TypeError(`"element" must be an instance of HTMLElement.`);
|
|
340
|
+
if (typeof hiddenClass !== 'string')
|
|
341
|
+
throw new TypeError(`"hiddenClass" must be a string.`);
|
|
342
|
+
if (typeof visibleClass !== 'string')
|
|
343
|
+
throw new TypeError(`"visibleClass" must be a string.`);
|
|
344
|
+
if (onVisible !== undefined && typeof onVisible !== 'function')
|
|
345
|
+
throw new TypeError(`"onVisible" must be a function if provided.`);
|
|
346
|
+
if (onHidden !== undefined && typeof onHidden !== 'function')
|
|
347
|
+
throw new TypeError(`"onHidden" must be a function if provided.`);
|
|
220
348
|
const removeClass = () => {
|
|
221
349
|
element.classList.remove(hiddenClass);
|
|
222
350
|
element.classList.remove(visibleClass);
|
|
223
351
|
};
|
|
224
352
|
/** @type {string|null} */
|
|
225
353
|
let hiddenProp = null;
|
|
226
|
-
/** @type {(this: any, evt: Event) => void} */
|
|
227
|
-
let handler;
|
|
228
354
|
const visibilityEvents = [
|
|
229
355
|
'visibilitychange',
|
|
230
356
|
'mozvisibilitychange',
|
|
@@ -238,7 +364,8 @@ export function installWindowHiddenScript({ element = document.body, hiddenClass
|
|
|
238
364
|
break;
|
|
239
365
|
}
|
|
240
366
|
}
|
|
241
|
-
|
|
367
|
+
/** @type {(this: any, evt: Event) => void} */
|
|
368
|
+
const handler = function (evt) {
|
|
242
369
|
removeClass();
|
|
243
370
|
const type = evt?.type;
|
|
244
371
|
// @ts-ignore
|
|
@@ -247,12 +374,21 @@ export function installWindowHiddenScript({ element = document.body, hiddenClass
|
|
|
247
374
|
const hiddenEvents = ['blur', 'focusout', 'pagehide'];
|
|
248
375
|
if (visibleEvents.includes(type)) {
|
|
249
376
|
element.classList.add(visibleClass);
|
|
377
|
+
onVisible?.();
|
|
250
378
|
}
|
|
251
379
|
else if (hiddenEvents.includes(type)) {
|
|
252
380
|
element.classList.add(hiddenClass);
|
|
381
|
+
onHidden?.();
|
|
253
382
|
}
|
|
254
383
|
else {
|
|
255
|
-
|
|
384
|
+
if (isHidden) {
|
|
385
|
+
element.classList.add(hiddenClass);
|
|
386
|
+
onHidden?.();
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
element.classList.add(visibleClass);
|
|
390
|
+
onVisible?.();
|
|
391
|
+
}
|
|
256
392
|
}
|
|
257
393
|
};
|
|
258
394
|
/** @type {() => void} */
|
|
@@ -287,8 +423,35 @@ export function installWindowHiddenScript({ element = document.body, hiddenClass
|
|
|
287
423
|
removeClass();
|
|
288
424
|
};
|
|
289
425
|
}
|
|
426
|
+
// Trigger initial state
|
|
290
427
|
// @ts-ignore
|
|
291
428
|
const simulatedEvent = new Event(hiddenProp && document[hiddenProp] ? 'blur' : 'focus');
|
|
292
429
|
handler(simulatedEvent);
|
|
293
430
|
return uninstall;
|
|
294
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Checks if the given element is at least partially visible in the viewport.
|
|
434
|
+
*
|
|
435
|
+
* @param {HTMLElement} element - The DOM element to check.
|
|
436
|
+
* @returns {boolean} True if the element is partially in the viewport, false otherwise.
|
|
437
|
+
*/
|
|
438
|
+
export function isInViewport(element) {
|
|
439
|
+
const elementTop = element.offsetTop;
|
|
440
|
+
const elementBottom = elementTop + element.offsetHeight;
|
|
441
|
+
const viewportTop = window.scrollY;
|
|
442
|
+
const viewportBottom = viewportTop + window.innerHeight;
|
|
443
|
+
return elementBottom > viewportTop && elementTop < viewportBottom;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Checks if the given element is fully visible in the viewport (top and bottom).
|
|
447
|
+
*
|
|
448
|
+
* @param {HTMLElement} element - The DOM element to check.
|
|
449
|
+
* @returns {boolean} True if the element is fully visible in the viewport, false otherwise.
|
|
450
|
+
*/
|
|
451
|
+
export function isScrolledIntoView(element) {
|
|
452
|
+
const viewportTop = window.scrollY;
|
|
453
|
+
const viewportBottom = viewportTop + window.innerHeight;
|
|
454
|
+
const elemTop = element.offsetTop;
|
|
455
|
+
const elemBottom = elemTop + element.offsetHeight;
|
|
456
|
+
return elemBottom <= viewportBottom && elemTop >= viewportTop;
|
|
457
|
+
}
|
package/dist/v1/basics/index.cjs
CHANGED
|
@@ -9,6 +9,7 @@ var objFilter = require('./objFilter.cjs');
|
|
|
9
9
|
var fullScreen = require('./fullScreen.cjs');
|
|
10
10
|
var simpleMath = require('./simpleMath.cjs');
|
|
11
11
|
var text = require('./text.cjs');
|
|
12
|
+
var collision = require('./collision.cjs');
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
|
|
@@ -26,6 +27,10 @@ exports.getHtmlElBordersWidth = html.getHtmlElBordersWidth;
|
|
|
26
27
|
exports.getHtmlElMargin = html.getHtmlElMargin;
|
|
27
28
|
exports.getHtmlElPadding = html.getHtmlElPadding;
|
|
28
29
|
exports.installWindowHiddenScript = html.installWindowHiddenScript;
|
|
30
|
+
exports.isInViewport = html.isInViewport;
|
|
31
|
+
exports.isScrolledIntoView = html.isScrolledIntoView;
|
|
32
|
+
exports.readBase64Blob = html.readBase64Blob;
|
|
33
|
+
exports.readFileBlob = html.readFileBlob;
|
|
29
34
|
exports.readJsonBlob = html.readJsonBlob;
|
|
30
35
|
exports.saveJsonFile = html.saveJsonFile;
|
|
31
36
|
exports.cloneObjTypeOrder = objFilter.cloneObjTypeOrder;
|
|
@@ -47,5 +52,24 @@ exports.getAge = simpleMath.getAge;
|
|
|
47
52
|
exports.getSimplePerc = simpleMath.getSimplePerc;
|
|
48
53
|
exports.ruleOfThree = simpleMath.ruleOfThree;
|
|
49
54
|
exports.addAiMarkerShortcut = text.addAiMarkerShortcut;
|
|
55
|
+
exports.safeTextTrim = text.safeTextTrim;
|
|
50
56
|
exports.toTitleCase = text.toTitleCase;
|
|
51
57
|
exports.toTitleCaseLowerFirst = text.toTitleCaseLowerFirst;
|
|
58
|
+
exports.areElsCollBottom = collision.areElsCollBottom;
|
|
59
|
+
exports.areElsCollLeft = collision.areElsCollLeft;
|
|
60
|
+
exports.areElsCollPerfBottom = collision.areElsCollPerfBottom;
|
|
61
|
+
exports.areElsCollPerfLeft = collision.areElsCollPerfLeft;
|
|
62
|
+
exports.areElsCollPerfRight = collision.areElsCollPerfRight;
|
|
63
|
+
exports.areElsCollPerfTop = collision.areElsCollPerfTop;
|
|
64
|
+
exports.areElsCollRight = collision.areElsCollRight;
|
|
65
|
+
exports.areElsCollTop = collision.areElsCollTop;
|
|
66
|
+
exports.areElsColliding = collision.areElsColliding;
|
|
67
|
+
exports.areElsPerfColliding = collision.areElsPerfColliding;
|
|
68
|
+
exports.getElsCollDetails = collision.getElsCollDetails;
|
|
69
|
+
exports.getElsCollDirDepth = collision.getElsCollDirDepth;
|
|
70
|
+
exports.getElsCollOverlap = collision.getElsCollOverlap;
|
|
71
|
+
exports.getElsCollOverlapPos = collision.getElsCollOverlapPos;
|
|
72
|
+
exports.getElsColliding = collision.getElsColliding;
|
|
73
|
+
exports.getElsPerfColliding = collision.getElsPerfColliding;
|
|
74
|
+
exports.getElsRelativeCenterOffset = collision.getElsRelativeCenterOffset;
|
|
75
|
+
exports.getRectCenter = collision.getRectCenter;
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import { areElsCollTop } from './collision.mjs';
|
|
2
|
+
import { areElsCollBottom } from './collision.mjs';
|
|
3
|
+
import { areElsCollLeft } from './collision.mjs';
|
|
4
|
+
import { areElsCollRight } from './collision.mjs';
|
|
5
|
+
import { areElsCollPerfTop } from './collision.mjs';
|
|
6
|
+
import { areElsCollPerfBottom } from './collision.mjs';
|
|
7
|
+
import { areElsCollPerfLeft } from './collision.mjs';
|
|
8
|
+
import { areElsCollPerfRight } from './collision.mjs';
|
|
9
|
+
import { areElsColliding } from './collision.mjs';
|
|
10
|
+
import { areElsPerfColliding } from './collision.mjs';
|
|
11
|
+
import { getElsColliding } from './collision.mjs';
|
|
12
|
+
import { getElsPerfColliding } from './collision.mjs';
|
|
13
|
+
import { getElsCollOverlap } from './collision.mjs';
|
|
14
|
+
import { getElsCollOverlapPos } from './collision.mjs';
|
|
15
|
+
import { getRectCenter } from './collision.mjs';
|
|
16
|
+
import { getElsRelativeCenterOffset } from './collision.mjs';
|
|
17
|
+
import { getElsCollDirDepth } from './collision.mjs';
|
|
18
|
+
import { getElsCollDetails } from './collision.mjs';
|
|
19
|
+
import { isInViewport } from './html.mjs';
|
|
20
|
+
import { isScrolledIntoView } from './html.mjs';
|
|
21
|
+
import { safeTextTrim } from './text.mjs';
|
|
1
22
|
import { installWindowHiddenScript } from './html.mjs';
|
|
2
23
|
import { genFibonacciSeq } from './simpleMath.mjs';
|
|
3
24
|
import { getHtmlElBorders } from './html.mjs';
|
|
@@ -6,6 +27,8 @@ import { getHtmlElMargin } from './html.mjs';
|
|
|
6
27
|
import { getHtmlElPadding } from './html.mjs';
|
|
7
28
|
import { fetchJson } from './html.mjs';
|
|
8
29
|
import { readJsonBlob } from './html.mjs';
|
|
30
|
+
import { readFileBlob } from './html.mjs';
|
|
31
|
+
import { readBase64Blob } from './html.mjs';
|
|
9
32
|
import { saveJsonFile } from './html.mjs';
|
|
10
33
|
import { documentIsFullScreen } from './fullScreen.mjs';
|
|
11
34
|
import { isScreenFilled } from './fullScreen.mjs';
|
|
@@ -35,5 +58,5 @@ import { getTimeDuration } from './clock.mjs';
|
|
|
35
58
|
import { shuffleArray } from './array.mjs';
|
|
36
59
|
import { toTitleCase } from './text.mjs';
|
|
37
60
|
import { toTitleCaseLowerFirst } from './text.mjs';
|
|
38
|
-
export { installWindowHiddenScript, genFibonacciSeq, getHtmlElBorders, getHtmlElBordersWidth, getHtmlElMargin, getHtmlElPadding, fetchJson, readJsonBlob, saveJsonFile, documentIsFullScreen, isScreenFilled, requestFullScreen, exitFullScreen, isFullScreenMode, onFullScreenChange, offFullScreenChange, areHtmlElsColliding, isJsonObject, arraySortPositions, formatBytes, addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst };
|
|
61
|
+
export { areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, areElsCollPerfTop, areElsCollPerfBottom, areElsCollPerfLeft, areElsCollPerfRight, areElsColliding, areElsPerfColliding, getElsColliding, getElsPerfColliding, getElsCollOverlap, getElsCollOverlapPos, getRectCenter, getElsRelativeCenterOffset, getElsCollDirDepth, getElsCollDetails, isInViewport, isScrolledIntoView, safeTextTrim, installWindowHiddenScript, genFibonacciSeq, getHtmlElBorders, getHtmlElBordersWidth, getHtmlElMargin, getHtmlElPadding, fetchJson, readJsonBlob, readFileBlob, readBase64Blob, saveJsonFile, documentIsFullScreen, isScreenFilled, requestFullScreen, exitFullScreen, isFullScreenMode, onFullScreenChange, offFullScreenChange, areHtmlElsColliding, isJsonObject, arraySortPositions, formatBytes, addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst };
|
|
39
62
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/v1/basics/index.mjs
CHANGED
|
@@ -2,9 +2,10 @@ import arraySortPositions from '../../legacy/libs/arraySortPositions.mjs';
|
|
|
2
2
|
import asyncReplace from '../../legacy/libs/replaceAsync.mjs';
|
|
3
3
|
import { shuffleArray } from './array.mjs';
|
|
4
4
|
import { formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration } from './clock.mjs';
|
|
5
|
-
import { areHtmlElsColliding, readJsonBlob, saveJsonFile, fetchJson, getHtmlElBorders, getHtmlElBordersWidth, getHtmlElMargin, getHtmlElPadding, installWindowHiddenScript, } from './html.mjs';
|
|
5
|
+
import { areHtmlElsColliding, readJsonBlob, saveJsonFile, fetchJson, getHtmlElBorders, getHtmlElBordersWidth, getHtmlElMargin, getHtmlElPadding, installWindowHiddenScript, readFileBlob, readBase64Blob, isInViewport, isScrolledIntoView, } from './html.mjs';
|
|
6
6
|
import { countObj, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, objType, isJsonObject, } from './objFilter.mjs';
|
|
7
7
|
import { documentIsFullScreen, isScreenFilled, requestFullScreen, exitFullScreen, isFullScreenMode, onFullScreenChange, offFullScreenChange, } from './fullScreen.mjs';
|
|
8
8
|
import { formatBytes, genFibonacciSeq, getAge, getSimplePerc, ruleOfThree } from './simpleMath.mjs';
|
|
9
|
-
import { addAiMarkerShortcut, toTitleCase, toTitleCaseLowerFirst } from './text.mjs';
|
|
10
|
-
|
|
9
|
+
import { addAiMarkerShortcut, safeTextTrim, toTitleCase, toTitleCaseLowerFirst } from './text.mjs';
|
|
10
|
+
import { areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, areElsCollPerfTop, areElsCollPerfBottom, areElsCollPerfLeft, areElsCollPerfRight, areElsColliding, areElsPerfColliding, getElsColliding, getElsPerfColliding, getElsCollOverlap, getElsCollOverlapPos, getRectCenter, getElsRelativeCenterOffset, getElsCollDirDepth, getElsCollDetails, } from './collision.mjs';
|
|
11
|
+
export { areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, areElsCollPerfTop, areElsCollPerfBottom, areElsCollPerfLeft, areElsCollPerfRight, areElsColliding, areElsPerfColliding, getElsColliding, getElsPerfColliding, getElsCollOverlap, getElsCollOverlapPos, getRectCenter, getElsRelativeCenterOffset, getElsCollDirDepth, getElsCollDetails, isInViewport, isScrolledIntoView, safeTextTrim, installWindowHiddenScript, genFibonacciSeq, getHtmlElBorders, getHtmlElBordersWidth, getHtmlElMargin, getHtmlElPadding, fetchJson, readJsonBlob, readFileBlob, readBase64Blob, saveJsonFile, documentIsFullScreen, isScreenFilled, requestFullScreen, exitFullScreen, isFullScreenMode, onFullScreenChange, offFullScreenChange, areHtmlElsColliding, isJsonObject, arraySortPositions, formatBytes, addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst, };
|
package/dist/v1/basics/text.cjs
CHANGED
|
@@ -64,6 +64,48 @@ function addAiMarkerShortcut(key = 'a') {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Trims a text string to a specified character limit, attempting to avoid cutting words in half.
|
|
69
|
+
* If a space is found before the limit and it's not too far from the limit (at least 60%),
|
|
70
|
+
* the cut is made at that space; otherwise, the text is hard-cut at the limit.
|
|
71
|
+
* If the input is shorter than the limit, it is returned unchanged.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} text - The input text to be trimmed.
|
|
74
|
+
* @param {number} limit - The maximum number of characters allowed.
|
|
75
|
+
* @param {number} [safeCutZone=0.6] - A decimal between 0 and 1 representing the minimal acceptable position
|
|
76
|
+
* (as a fraction of `limit`) to cut at a space. Defaults to 0.6.
|
|
77
|
+
* @returns {string} - The trimmed text, possibly ending with an ellipsis ("...").
|
|
78
|
+
* @throws {TypeError} - Throws if `text` is not a string.
|
|
79
|
+
* @throws {TypeError} - Throws if `limit` is not a positive integer.
|
|
80
|
+
* @throws {TypeError} - Throws if `safeCutZone` is not a number between 0 and 1 (inclusive).
|
|
81
|
+
*/
|
|
82
|
+
function safeTextTrim(text, limit, safeCutZone = 0.6) {
|
|
83
|
+
if (typeof text !== 'string')
|
|
84
|
+
throw new TypeError(`Expected a string for 'text', but received ${typeof text}`);
|
|
85
|
+
if (!Number.isInteger(limit) || limit <= 0)
|
|
86
|
+
throw new TypeError(`Expected 'limit' to be a positive integer, but received ${limit}`);
|
|
87
|
+
if (typeof safeCutZone !== 'number' || safeCutZone < 0 || safeCutZone > 1)
|
|
88
|
+
throw new TypeError(
|
|
89
|
+
`Expected 'safeCutZone' to be a number between 0 and 1, but received ${safeCutZone}`,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
let result = text.trim();
|
|
93
|
+
if (result.length > limit) {
|
|
94
|
+
// Try to cut the string into a space before the limit
|
|
95
|
+
const safeCut = result.lastIndexOf(' ', limit);
|
|
96
|
+
|
|
97
|
+
if (safeCut > 0 && safeCut >= limit * safeCutZone) {
|
|
98
|
+
// Only cuts where there is a space, and if the cut is not too early
|
|
99
|
+
return `${result.substring(0, safeCut).trim()}...`;
|
|
100
|
+
} else {
|
|
101
|
+
// Emergency: Cuts straight to the limit and adds "...".
|
|
102
|
+
return `${result.substring(0, limit).trim()}...`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
67
109
|
/*
|
|
68
110
|
import { useEffect } from "react";
|
|
69
111
|
|
|
@@ -89,5 +131,6 @@ export default KeyPressHandler;
|
|
|
89
131
|
*/
|
|
90
132
|
|
|
91
133
|
exports.addAiMarkerShortcut = addAiMarkerShortcut;
|
|
134
|
+
exports.safeTextTrim = safeTextTrim;
|
|
92
135
|
exports.toTitleCase = toTitleCase;
|
|
93
136
|
exports.toTitleCaseLowerFirst = toTitleCaseLowerFirst;
|
|
@@ -32,4 +32,20 @@ export function toTitleCaseLowerFirst(str: string): string;
|
|
|
32
32
|
* @param {string} [key='a'] - The lowercase character key to be used in combination with Ctrl and Alt.
|
|
33
33
|
*/
|
|
34
34
|
export function addAiMarkerShortcut(key?: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Trims a text string to a specified character limit, attempting to avoid cutting words in half.
|
|
37
|
+
* If a space is found before the limit and it's not too far from the limit (at least 60%),
|
|
38
|
+
* the cut is made at that space; otherwise, the text is hard-cut at the limit.
|
|
39
|
+
* If the input is shorter than the limit, it is returned unchanged.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} text - The input text to be trimmed.
|
|
42
|
+
* @param {number} limit - The maximum number of characters allowed.
|
|
43
|
+
* @param {number} [safeCutZone=0.6] - A decimal between 0 and 1 representing the minimal acceptable position
|
|
44
|
+
* (as a fraction of `limit`) to cut at a space. Defaults to 0.6.
|
|
45
|
+
* @returns {string} - The trimmed text, possibly ending with an ellipsis ("...").
|
|
46
|
+
* @throws {TypeError} - Throws if `text` is not a string.
|
|
47
|
+
* @throws {TypeError} - Throws if `limit` is not a positive integer.
|
|
48
|
+
* @throws {TypeError} - Throws if `safeCutZone` is not a number between 0 and 1 (inclusive).
|
|
49
|
+
*/
|
|
50
|
+
export function safeTextTrim(text: string, limit: number, safeCutZone?: number): string;
|
|
35
51
|
//# sourceMappingURL=text.d.mts.map
|
package/dist/v1/basics/text.mjs
CHANGED
|
@@ -52,6 +52,43 @@ export function addAiMarkerShortcut(key = 'a') {
|
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Trims a text string to a specified character limit, attempting to avoid cutting words in half.
|
|
57
|
+
* If a space is found before the limit and it's not too far from the limit (at least 60%),
|
|
58
|
+
* the cut is made at that space; otherwise, the text is hard-cut at the limit.
|
|
59
|
+
* If the input is shorter than the limit, it is returned unchanged.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} text - The input text to be trimmed.
|
|
62
|
+
* @param {number} limit - The maximum number of characters allowed.
|
|
63
|
+
* @param {number} [safeCutZone=0.6] - A decimal between 0 and 1 representing the minimal acceptable position
|
|
64
|
+
* (as a fraction of `limit`) to cut at a space. Defaults to 0.6.
|
|
65
|
+
* @returns {string} - The trimmed text, possibly ending with an ellipsis ("...").
|
|
66
|
+
* @throws {TypeError} - Throws if `text` is not a string.
|
|
67
|
+
* @throws {TypeError} - Throws if `limit` is not a positive integer.
|
|
68
|
+
* @throws {TypeError} - Throws if `safeCutZone` is not a number between 0 and 1 (inclusive).
|
|
69
|
+
*/
|
|
70
|
+
export function safeTextTrim(text, limit, safeCutZone = 0.6) {
|
|
71
|
+
if (typeof text !== 'string')
|
|
72
|
+
throw new TypeError(`Expected a string for 'text', but received ${typeof text}`);
|
|
73
|
+
if (!Number.isInteger(limit) || limit <= 0)
|
|
74
|
+
throw new TypeError(`Expected 'limit' to be a positive integer, but received ${limit}`);
|
|
75
|
+
if (typeof safeCutZone !== 'number' || safeCutZone < 0 || safeCutZone > 1)
|
|
76
|
+
throw new TypeError(`Expected 'safeCutZone' to be a number between 0 and 1, but received ${safeCutZone}`);
|
|
77
|
+
let result = text.trim();
|
|
78
|
+
if (result.length > limit) {
|
|
79
|
+
// Try to cut the string into a space before the limit
|
|
80
|
+
const safeCut = result.lastIndexOf(' ', limit);
|
|
81
|
+
if (safeCut > 0 && safeCut >= limit * safeCutZone) {
|
|
82
|
+
// Only cuts where there is a space, and if the cut is not too early
|
|
83
|
+
return `${result.substring(0, safeCut).trim()}...`;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Emergency: Cuts straight to the limit and adds "...".
|
|
87
|
+
return `${result.substring(0, limit).trim()}...`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
55
92
|
/*
|
|
56
93
|
import { useEffect } from "react";
|
|
57
94
|
|