tiny-essentials 1.13.1 → 1.14.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 +256 -15
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyDragger.js +210 -15
- package/dist/v1/TinyEssentials.js +496 -15
- 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 +211 -15
- package/dist/v1/basics/html.cjs +213 -15
- package/dist/v1/basics/html.d.mts +59 -5
- package/dist/v1/basics/html.mjs +192 -13
- package/dist/v1/basics/index.cjs +4 -0
- package/dist/v1/basics/index.d.mts +5 -1
- package/dist/v1/basics/index.mjs +3 -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 +6 -0
- package/dist/v1/index.d.mts +6 -1
- package/dist/v1/index.mjs +4 -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 +4 -3
- package/docs/v1/basics/array.md +16 -43
- package/docs/v1/basics/html.md +199 -33
- package/docs/v1/basics/text.md +44 -14
- package/docs/v1/libs/TinyNotifications.md +189 -0
- package/package.json +1 -1
package/dist/v1/basics/html.cjs
CHANGED
|
@@ -22,34 +22,105 @@ function areHtmlElsColliding(elem1, elem2) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Reads
|
|
26
|
-
*
|
|
27
|
-
* @param {File} file
|
|
28
|
-
* @
|
|
25
|
+
* Reads the contents of a file using the specified FileReader method.
|
|
26
|
+
*
|
|
27
|
+
* @param {File} file - The file to be read.
|
|
28
|
+
* @param {'readAsArrayBuffer'|'readAsDataURL'|'readAsText'|'readAsBinaryString'} method -
|
|
29
|
+
* The FileReader method to use for reading the file.
|
|
30
|
+
* @returns {Promise<any>} - A promise that resolves with the file content, according to the chosen method.
|
|
31
|
+
* @throws {Error} - If an unexpected error occurs while handling the result.
|
|
32
|
+
* @throws {DOMException} - If the FileReader encounters an error while reading the file.
|
|
29
33
|
*/
|
|
30
|
-
function
|
|
34
|
+
function readFileBlob(file, method) {
|
|
31
35
|
return new Promise((resolve, reject) => {
|
|
32
36
|
const reader = new FileReader();
|
|
33
|
-
|
|
34
37
|
reader.onload = () => {
|
|
35
38
|
try {
|
|
36
|
-
|
|
37
|
-
const result = JSON.parse(reader.result);
|
|
38
|
-
resolve(result);
|
|
39
|
+
resolve(reader.result);
|
|
39
40
|
} catch (error) {
|
|
40
|
-
|
|
41
|
-
reject(new Error(`Invalid JSON in file: ${file.name}\n${error.message}`));
|
|
41
|
+
reject(error);
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
-
|
|
45
44
|
reader.onerror = () => {
|
|
46
|
-
reject(
|
|
45
|
+
reject(reader.error);
|
|
47
46
|
};
|
|
47
|
+
reader[method](file);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Reads a file as a Base64 string using FileReader, and optionally formats it as a full data URL.
|
|
53
|
+
*
|
|
54
|
+
* Performs strict validation to ensure the result is a valid Base64 string or a proper data URL.
|
|
55
|
+
*
|
|
56
|
+
* @param {File} file - The file to be read.
|
|
57
|
+
* @param {boolean|string} [isDataUrl=false] - If true, returns a full data URL; if false, returns only the Base64 string;
|
|
58
|
+
* if a string is passed, it is used as the MIME type in the data URL.
|
|
59
|
+
* @returns {Promise<string>} - A promise that resolves with the Base64 string or data URL.
|
|
60
|
+
*
|
|
61
|
+
* @throws {TypeError} - If the result is not a string or if `isDataUrl` is not a valid type.
|
|
62
|
+
* @throws {Error} - If the result does not match the expected data URL format or Base64 structure.
|
|
63
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
64
|
+
*/
|
|
65
|
+
function readBase64Blob(file, isDataUrl = false) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
if (typeof isDataUrl !== 'string' && typeof isDataUrl !== 'boolean')
|
|
68
|
+
reject(new TypeError('The isDataUrl parameter must be a boolean or a string.'));
|
|
69
|
+
readFileBlob(file, 'readAsDataURL')
|
|
70
|
+
.then(
|
|
71
|
+
/**
|
|
72
|
+
* Ensure that the URL format is correct in the required pattern
|
|
73
|
+
* @param {string} base64Data
|
|
74
|
+
*/ (base64Data) => {
|
|
75
|
+
if (typeof base64Data !== 'string')
|
|
76
|
+
throw new TypeError('Expected file content to be a string.');
|
|
77
|
+
|
|
78
|
+
const match = base64Data.match(/^data:(.+);base64,(.*)$/);
|
|
79
|
+
if (!match || !match[2])
|
|
80
|
+
throw new Error('Invalid data URL format or missing Base64 content.');
|
|
81
|
+
const [, mimeType, base64] = match;
|
|
82
|
+
if (!/^[\w/+]+=*$/.test(base64)) throw new Error('Base64 content is malformed.');
|
|
83
|
+
|
|
84
|
+
if (typeof isDataUrl === 'boolean') return resolve(isDataUrl ? base64Data : base64);
|
|
85
|
+
if (!/^[\w-]+\/[\w.+-]+$/.test(isDataUrl))
|
|
86
|
+
throw new Error(`Invalid MIME type string: ${isDataUrl}`);
|
|
87
|
+
|
|
88
|
+
return resolve(`data:${isDataUrl};base64,${base64}`);
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
.catch(reject);
|
|
50
92
|
});
|
|
51
93
|
}
|
|
52
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Reads a file and strictly validates its content as proper JSON using FileReader.
|
|
97
|
+
*
|
|
98
|
+
* Performs several checks to ensure the file contains valid, parsable JSON data.
|
|
99
|
+
*
|
|
100
|
+
* @param {File} file - The file to be read. It must contain valid JSON as plain text.
|
|
101
|
+
* @returns {Promise<Record<string|number|symbol, any>|any[]>} - A promise that resolves with the parsed JSON object.
|
|
102
|
+
*
|
|
103
|
+
* @throws {SyntaxError} - If the file content is not valid JSON syntax.
|
|
104
|
+
* @throws {TypeError} - If the result is not a string or does not represent a JSON value.
|
|
105
|
+
* @throws {Error} - If the result is empty or structurally invalid as JSON.
|
|
106
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
107
|
+
*/
|
|
108
|
+
function readJsonBlob(file) {
|
|
109
|
+
return new Promise((resolve, reject) =>
|
|
110
|
+
readFileBlob(file, 'readAsText')
|
|
111
|
+
.then((data) => {
|
|
112
|
+
if (typeof data !== 'string') throw new TypeError('Expected file content to be a string.');
|
|
113
|
+
const trimmed = data.trim();
|
|
114
|
+
if (trimmed.length === 0) throw new Error('File is empty or contains only whitespace.');
|
|
115
|
+
const parsed = JSON.parse(trimmed);
|
|
116
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
117
|
+
throw new Error('Parsed content is not a valid JSON object or array.');
|
|
118
|
+
resolve(parsed);
|
|
119
|
+
})
|
|
120
|
+
.catch(reject),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
53
124
|
/**
|
|
54
125
|
* Saves a JSON object as a downloadable file.
|
|
55
126
|
* @param {string} filename
|
|
@@ -152,7 +223,8 @@ async function fetchJson(
|
|
|
152
223
|
|
|
153
224
|
const data = await response.json();
|
|
154
225
|
|
|
155
|
-
if (!
|
|
226
|
+
if (!Array.isArray(data) && !objFilter.isJsonObject(data))
|
|
227
|
+
throw new Error('Received invalid data instead of valid JSON.');
|
|
156
228
|
|
|
157
229
|
return data;
|
|
158
230
|
} catch (err) {
|
|
@@ -252,11 +324,137 @@ const getHtmlElPadding = (el) => {
|
|
|
252
324
|
return { x, y, left, right, top, bottom };
|
|
253
325
|
};
|
|
254
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Installs a script that toggles CSS classes on a given element
|
|
329
|
+
* based on the page's visibility or focus state, and optionally
|
|
330
|
+
* triggers callbacks on visibility changes.
|
|
331
|
+
*
|
|
332
|
+
* @param {Object} [settings={}]
|
|
333
|
+
* @param {HTMLElement} [settings.element=document.body] - The element to receive visibility classes.
|
|
334
|
+
* @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
|
|
335
|
+
* @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
|
|
336
|
+
* @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
|
|
337
|
+
* @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
|
|
338
|
+
* @returns {() => void} Function that removes all installed event listeners.
|
|
339
|
+
* @throws {TypeError} If any provided setting is invalid.
|
|
340
|
+
*/
|
|
341
|
+
function installWindowHiddenScript({
|
|
342
|
+
element = document.body,
|
|
343
|
+
hiddenClass = 'windowHidden',
|
|
344
|
+
visibleClass = 'windowVisible',
|
|
345
|
+
onVisible,
|
|
346
|
+
onHidden,
|
|
347
|
+
} = {}) {
|
|
348
|
+
if (!(element instanceof HTMLElement))
|
|
349
|
+
throw new TypeError(`"element" must be an instance of HTMLElement.`);
|
|
350
|
+
if (typeof hiddenClass !== 'string') throw new TypeError(`"hiddenClass" must be a string.`);
|
|
351
|
+
if (typeof visibleClass !== 'string') throw new TypeError(`"visibleClass" must be a string.`);
|
|
352
|
+
if (onVisible !== undefined && typeof onVisible !== 'function')
|
|
353
|
+
throw new TypeError(`"onVisible" must be a function if provided.`);
|
|
354
|
+
if (onHidden !== undefined && typeof onHidden !== 'function')
|
|
355
|
+
throw new TypeError(`"onHidden" must be a function if provided.`);
|
|
356
|
+
|
|
357
|
+
const removeClass = () => {
|
|
358
|
+
element.classList.remove(hiddenClass);
|
|
359
|
+
element.classList.remove(visibleClass);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/** @type {string|null} */
|
|
363
|
+
let hiddenProp = null;
|
|
364
|
+
|
|
365
|
+
const visibilityEvents = [
|
|
366
|
+
'visibilitychange',
|
|
367
|
+
'mozvisibilitychange',
|
|
368
|
+
'webkitvisibilitychange',
|
|
369
|
+
'msvisibilitychange',
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
const visibilityProps = ['hidden', 'mozHidden', 'webkitHidden', 'msHidden'];
|
|
373
|
+
|
|
374
|
+
for (let i = 0; i < visibilityProps.length; i++) {
|
|
375
|
+
if (visibilityProps[i] in document) {
|
|
376
|
+
hiddenProp = visibilityProps[i];
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** @type {(this: any, evt: Event) => void} */
|
|
382
|
+
const handler = function (evt) {
|
|
383
|
+
removeClass();
|
|
384
|
+
|
|
385
|
+
const type = evt?.type;
|
|
386
|
+
// @ts-ignore
|
|
387
|
+
const isHidden = hiddenProp && document[hiddenProp];
|
|
388
|
+
|
|
389
|
+
const visibleEvents = ['focus', 'focusin', 'pageshow'];
|
|
390
|
+
const hiddenEvents = ['blur', 'focusout', 'pagehide'];
|
|
391
|
+
|
|
392
|
+
if (visibleEvents.includes(type)) {
|
|
393
|
+
element.classList.add(visibleClass);
|
|
394
|
+
onVisible?.();
|
|
395
|
+
} else if (hiddenEvents.includes(type)) {
|
|
396
|
+
element.classList.add(hiddenClass);
|
|
397
|
+
onHidden?.();
|
|
398
|
+
} else {
|
|
399
|
+
if (isHidden) {
|
|
400
|
+
element.classList.add(hiddenClass);
|
|
401
|
+
onHidden?.();
|
|
402
|
+
} else {
|
|
403
|
+
element.classList.add(visibleClass);
|
|
404
|
+
onVisible?.();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/** @type {() => void} */
|
|
410
|
+
let uninstall = () => {};
|
|
411
|
+
|
|
412
|
+
if (hiddenProp) {
|
|
413
|
+
const eventType = visibilityEvents[visibilityProps.indexOf(hiddenProp)];
|
|
414
|
+
document.addEventListener(eventType, handler);
|
|
415
|
+
window.addEventListener('focus', handler);
|
|
416
|
+
window.addEventListener('blur', handler);
|
|
417
|
+
|
|
418
|
+
uninstall = () => {
|
|
419
|
+
document.removeEventListener(eventType, handler);
|
|
420
|
+
window.removeEventListener('focus', handler);
|
|
421
|
+
window.removeEventListener('blur', handler);
|
|
422
|
+
removeClass();
|
|
423
|
+
};
|
|
424
|
+
} else if ('onfocusin' in document) {
|
|
425
|
+
// Fallback for IE9 and older
|
|
426
|
+
// @ts-ignore
|
|
427
|
+
document.onfocusin = document.onfocusout = handler;
|
|
428
|
+
uninstall = () => {
|
|
429
|
+
// @ts-ignore
|
|
430
|
+
document.onfocusin = document.onfocusout = null;
|
|
431
|
+
removeClass();
|
|
432
|
+
};
|
|
433
|
+
} else {
|
|
434
|
+
// Last resort fallback
|
|
435
|
+
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = handler;
|
|
436
|
+
uninstall = () => {
|
|
437
|
+
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = null;
|
|
438
|
+
removeClass();
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Trigger initial state
|
|
443
|
+
// @ts-ignore
|
|
444
|
+
const simulatedEvent = new Event(hiddenProp && document[hiddenProp] ? 'blur' : 'focus');
|
|
445
|
+
handler(simulatedEvent);
|
|
446
|
+
|
|
447
|
+
return uninstall;
|
|
448
|
+
}
|
|
449
|
+
|
|
255
450
|
exports.areHtmlElsColliding = areHtmlElsColliding;
|
|
256
451
|
exports.fetchJson = fetchJson;
|
|
257
452
|
exports.getHtmlElBorders = getHtmlElBorders;
|
|
258
453
|
exports.getHtmlElBordersWidth = getHtmlElBordersWidth;
|
|
259
454
|
exports.getHtmlElMargin = getHtmlElMargin;
|
|
260
455
|
exports.getHtmlElPadding = getHtmlElPadding;
|
|
456
|
+
exports.installWindowHiddenScript = installWindowHiddenScript;
|
|
457
|
+
exports.readBase64Blob = readBase64Blob;
|
|
458
|
+
exports.readFileBlob = readFileBlob;
|
|
261
459
|
exports.readJsonBlob = readJsonBlob;
|
|
262
460
|
exports.saveJsonFile = saveJsonFile;
|
|
@@ -7,12 +7,45 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export function areHtmlElsColliding(elem1: Element, elem2: Element): boolean;
|
|
9
9
|
/**
|
|
10
|
-
* Reads
|
|
11
|
-
*
|
|
12
|
-
* @param {File} file
|
|
13
|
-
* @
|
|
10
|
+
* Reads the contents of a file using the specified FileReader method.
|
|
11
|
+
*
|
|
12
|
+
* @param {File} file - The file to be read.
|
|
13
|
+
* @param {'readAsArrayBuffer'|'readAsDataURL'|'readAsText'|'readAsBinaryString'} method -
|
|
14
|
+
* The FileReader method to use for reading the file.
|
|
15
|
+
* @returns {Promise<any>} - A promise that resolves with the file content, according to the chosen method.
|
|
16
|
+
* @throws {Error} - If an unexpected error occurs while handling the result.
|
|
17
|
+
* @throws {DOMException} - If the FileReader encounters an error while reading the file.
|
|
18
|
+
*/
|
|
19
|
+
export function readFileBlob(file: File, method: "readAsArrayBuffer" | "readAsDataURL" | "readAsText" | "readAsBinaryString"): Promise<any>;
|
|
20
|
+
/**
|
|
21
|
+
* Reads a file as a Base64 string using FileReader, and optionally formats it as a full data URL.
|
|
22
|
+
*
|
|
23
|
+
* Performs strict validation to ensure the result is a valid Base64 string or a proper data URL.
|
|
24
|
+
*
|
|
25
|
+
* @param {File} file - The file to be read.
|
|
26
|
+
* @param {boolean|string} [isDataUrl=false] - If true, returns a full data URL; if false, returns only the Base64 string;
|
|
27
|
+
* if a string is passed, it is used as the MIME type in the data URL.
|
|
28
|
+
* @returns {Promise<string>} - A promise that resolves with the Base64 string or data URL.
|
|
29
|
+
*
|
|
30
|
+
* @throws {TypeError} - If the result is not a string or if `isDataUrl` is not a valid type.
|
|
31
|
+
* @throws {Error} - If the result does not match the expected data URL format or Base64 structure.
|
|
32
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
33
|
+
*/
|
|
34
|
+
export function readBase64Blob(file: File, isDataUrl?: boolean | string): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Reads a file and strictly validates its content as proper JSON using FileReader.
|
|
37
|
+
*
|
|
38
|
+
* Performs several checks to ensure the file contains valid, parsable JSON data.
|
|
39
|
+
*
|
|
40
|
+
* @param {File} file - The file to be read. It must contain valid JSON as plain text.
|
|
41
|
+
* @returns {Promise<Record<string|number|symbol, any>|any[]>} - A promise that resolves with the parsed JSON object.
|
|
42
|
+
*
|
|
43
|
+
* @throws {SyntaxError} - If the file content is not valid JSON syntax.
|
|
44
|
+
* @throws {TypeError} - If the result is not a string or does not represent a JSON value.
|
|
45
|
+
* @throws {Error} - If the result is empty or structurally invalid as JSON.
|
|
46
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
14
47
|
*/
|
|
15
|
-
export function readJsonBlob(file: File): Promise<any>;
|
|
48
|
+
export function readJsonBlob(file: File): Promise<Record<string | number | symbol, any> | any[]>;
|
|
16
49
|
/**
|
|
17
50
|
* Saves a JSON object as a downloadable file.
|
|
18
51
|
* @param {string} filename
|
|
@@ -42,6 +75,27 @@ export function fetchJson(url: string, { method, body, timeout, retries, headers
|
|
|
42
75
|
headers?: Headers | Record<string, any> | undefined;
|
|
43
76
|
signal?: AbortSignal | null | undefined;
|
|
44
77
|
}): Promise<any>;
|
|
78
|
+
/**
|
|
79
|
+
* Installs a script that toggles CSS classes on a given element
|
|
80
|
+
* based on the page's visibility or focus state, and optionally
|
|
81
|
+
* triggers callbacks on visibility changes.
|
|
82
|
+
*
|
|
83
|
+
* @param {Object} [settings={}]
|
|
84
|
+
* @param {HTMLElement} [settings.element=document.body] - The element to receive visibility classes.
|
|
85
|
+
* @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
|
|
86
|
+
* @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
|
|
87
|
+
* @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
|
|
88
|
+
* @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
|
|
89
|
+
* @returns {() => void} Function that removes all installed event listeners.
|
|
90
|
+
* @throws {TypeError} If any provided setting is invalid.
|
|
91
|
+
*/
|
|
92
|
+
export function installWindowHiddenScript({ element, hiddenClass, visibleClass, onVisible, onHidden, }?: {
|
|
93
|
+
element?: HTMLElement | undefined;
|
|
94
|
+
hiddenClass?: string | undefined;
|
|
95
|
+
visibleClass?: string | undefined;
|
|
96
|
+
onVisible?: (() => void) | undefined;
|
|
97
|
+
onHidden?: (() => void) | undefined;
|
|
98
|
+
}): () => void;
|
|
45
99
|
export function getHtmlElBordersWidth(el: Element): HtmlElBoxSides;
|
|
46
100
|
export function getHtmlElBorders(el: Element): HtmlElBoxSides;
|
|
47
101
|
export function getHtmlElMargin(el: Element): HtmlElBoxSides;
|
package/dist/v1/basics/html.mjs
CHANGED
|
@@ -15,31 +15,101 @@ export function areHtmlElsColliding(elem1, elem2) {
|
|
|
15
15
|
rect1.top > rect2.bottom);
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Reads
|
|
19
|
-
*
|
|
20
|
-
* @param {File} file
|
|
21
|
-
* @
|
|
18
|
+
* Reads the contents of a file using the specified FileReader method.
|
|
19
|
+
*
|
|
20
|
+
* @param {File} file - The file to be read.
|
|
21
|
+
* @param {'readAsArrayBuffer'|'readAsDataURL'|'readAsText'|'readAsBinaryString'} method -
|
|
22
|
+
* The FileReader method to use for reading the file.
|
|
23
|
+
* @returns {Promise<any>} - A promise that resolves with the file content, according to the chosen method.
|
|
24
|
+
* @throws {Error} - If an unexpected error occurs while handling the result.
|
|
25
|
+
* @throws {DOMException} - If the FileReader encounters an error while reading the file.
|
|
22
26
|
*/
|
|
23
|
-
export function
|
|
27
|
+
export function readFileBlob(file, method) {
|
|
24
28
|
return new Promise((resolve, reject) => {
|
|
25
29
|
const reader = new FileReader();
|
|
26
30
|
reader.onload = () => {
|
|
27
31
|
try {
|
|
28
|
-
|
|
29
|
-
const result = JSON.parse(reader.result);
|
|
30
|
-
resolve(result);
|
|
32
|
+
resolve(reader.result);
|
|
31
33
|
}
|
|
32
34
|
catch (error) {
|
|
33
|
-
|
|
34
|
-
reject(new Error(`Invalid JSON in file: ${file.name}\n${error.message}`));
|
|
35
|
+
reject(error);
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
38
|
reader.onerror = () => {
|
|
38
|
-
reject(
|
|
39
|
+
reject(reader.error);
|
|
39
40
|
};
|
|
40
|
-
reader
|
|
41
|
+
reader[method](file);
|
|
41
42
|
});
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Reads a file as a Base64 string using FileReader, and optionally formats it as a full data URL.
|
|
46
|
+
*
|
|
47
|
+
* Performs strict validation to ensure the result is a valid Base64 string or a proper data URL.
|
|
48
|
+
*
|
|
49
|
+
* @param {File} file - The file to be read.
|
|
50
|
+
* @param {boolean|string} [isDataUrl=false] - If true, returns a full data URL; if false, returns only the Base64 string;
|
|
51
|
+
* if a string is passed, it is used as the MIME type in the data URL.
|
|
52
|
+
* @returns {Promise<string>} - A promise that resolves with the Base64 string or data URL.
|
|
53
|
+
*
|
|
54
|
+
* @throws {TypeError} - If the result is not a string or if `isDataUrl` is not a valid type.
|
|
55
|
+
* @throws {Error} - If the result does not match the expected data URL format or Base64 structure.
|
|
56
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
57
|
+
*/
|
|
58
|
+
export function readBase64Blob(file, isDataUrl = false) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
if (typeof isDataUrl !== 'string' && typeof isDataUrl !== 'boolean')
|
|
61
|
+
reject(new TypeError('The isDataUrl parameter must be a boolean or a string.'));
|
|
62
|
+
readFileBlob(file, 'readAsDataURL')
|
|
63
|
+
.then(
|
|
64
|
+
/**
|
|
65
|
+
* Ensure that the URL format is correct in the required pattern
|
|
66
|
+
* @param {string} base64Data
|
|
67
|
+
*/ (base64Data) => {
|
|
68
|
+
if (typeof base64Data !== 'string')
|
|
69
|
+
throw new TypeError('Expected file content to be a string.');
|
|
70
|
+
const match = base64Data.match(/^data:(.+);base64,(.*)$/);
|
|
71
|
+
if (!match || !match[2])
|
|
72
|
+
throw new Error('Invalid data URL format or missing Base64 content.');
|
|
73
|
+
const [, mimeType, base64] = match;
|
|
74
|
+
if (!/^[\w/+]+=*$/.test(base64))
|
|
75
|
+
throw new Error('Base64 content is malformed.');
|
|
76
|
+
if (typeof isDataUrl === 'boolean')
|
|
77
|
+
return resolve(isDataUrl ? base64Data : base64);
|
|
78
|
+
if (!/^[\w-]+\/[\w.+-]+$/.test(isDataUrl))
|
|
79
|
+
throw new Error(`Invalid MIME type string: ${isDataUrl}`);
|
|
80
|
+
return resolve(`data:${isDataUrl};base64,${base64}`);
|
|
81
|
+
})
|
|
82
|
+
.catch(reject);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reads a file and strictly validates its content as proper JSON using FileReader.
|
|
87
|
+
*
|
|
88
|
+
* Performs several checks to ensure the file contains valid, parsable JSON data.
|
|
89
|
+
*
|
|
90
|
+
* @param {File} file - The file to be read. It must contain valid JSON as plain text.
|
|
91
|
+
* @returns {Promise<Record<string|number|symbol, any>|any[]>} - A promise that resolves with the parsed JSON object.
|
|
92
|
+
*
|
|
93
|
+
* @throws {SyntaxError} - If the file content is not valid JSON syntax.
|
|
94
|
+
* @throws {TypeError} - If the result is not a string or does not represent a JSON value.
|
|
95
|
+
* @throws {Error} - If the result is empty or structurally invalid as JSON.
|
|
96
|
+
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
97
|
+
*/
|
|
98
|
+
export function readJsonBlob(file) {
|
|
99
|
+
return new Promise((resolve, reject) => readFileBlob(file, 'readAsText')
|
|
100
|
+
.then((data) => {
|
|
101
|
+
if (typeof data !== 'string')
|
|
102
|
+
throw new TypeError('Expected file content to be a string.');
|
|
103
|
+
const trimmed = data.trim();
|
|
104
|
+
if (trimmed.length === 0)
|
|
105
|
+
throw new Error('File is empty or contains only whitespace.');
|
|
106
|
+
const parsed = JSON.parse(trimmed);
|
|
107
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
108
|
+
throw new Error('Parsed content is not a valid JSON object or array.');
|
|
109
|
+
resolve(parsed);
|
|
110
|
+
})
|
|
111
|
+
.catch(reject));
|
|
112
|
+
}
|
|
43
113
|
/**
|
|
44
114
|
* Saves a JSON object as a downloadable file.
|
|
45
115
|
* @param {string} filename
|
|
@@ -119,7 +189,7 @@ export async function fetchJson(url, { method = 'GET', body, timeout = 0, retrie
|
|
|
119
189
|
if (!contentType.includes('application/json'))
|
|
120
190
|
throw new Error(`Unexpected content-type: ${contentType}`);
|
|
121
191
|
const data = await response.json();
|
|
122
|
-
if (!isJsonObject(data))
|
|
192
|
+
if (!Array.isArray(data) && !isJsonObject(data))
|
|
123
193
|
throw new Error('Received invalid data instead of valid JSON.');
|
|
124
194
|
return data;
|
|
125
195
|
}
|
|
@@ -206,3 +276,112 @@ export const getHtmlElPadding = (el) => {
|
|
|
206
276
|
const y = top + bottom;
|
|
207
277
|
return { x, y, left, right, top, bottom };
|
|
208
278
|
};
|
|
279
|
+
/**
|
|
280
|
+
* Installs a script that toggles CSS classes on a given element
|
|
281
|
+
* based on the page's visibility or focus state, and optionally
|
|
282
|
+
* triggers callbacks on visibility changes.
|
|
283
|
+
*
|
|
284
|
+
* @param {Object} [settings={}]
|
|
285
|
+
* @param {HTMLElement} [settings.element=document.body] - The element to receive visibility classes.
|
|
286
|
+
* @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
|
|
287
|
+
* @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
|
|
288
|
+
* @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
|
|
289
|
+
* @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
|
|
290
|
+
* @returns {() => void} Function that removes all installed event listeners.
|
|
291
|
+
* @throws {TypeError} If any provided setting is invalid.
|
|
292
|
+
*/
|
|
293
|
+
export function installWindowHiddenScript({ element = document.body, hiddenClass = 'windowHidden', visibleClass = 'windowVisible', onVisible, onHidden, } = {}) {
|
|
294
|
+
if (!(element instanceof HTMLElement))
|
|
295
|
+
throw new TypeError(`"element" must be an instance of HTMLElement.`);
|
|
296
|
+
if (typeof hiddenClass !== 'string')
|
|
297
|
+
throw new TypeError(`"hiddenClass" must be a string.`);
|
|
298
|
+
if (typeof visibleClass !== 'string')
|
|
299
|
+
throw new TypeError(`"visibleClass" must be a string.`);
|
|
300
|
+
if (onVisible !== undefined && typeof onVisible !== 'function')
|
|
301
|
+
throw new TypeError(`"onVisible" must be a function if provided.`);
|
|
302
|
+
if (onHidden !== undefined && typeof onHidden !== 'function')
|
|
303
|
+
throw new TypeError(`"onHidden" must be a function if provided.`);
|
|
304
|
+
const removeClass = () => {
|
|
305
|
+
element.classList.remove(hiddenClass);
|
|
306
|
+
element.classList.remove(visibleClass);
|
|
307
|
+
};
|
|
308
|
+
/** @type {string|null} */
|
|
309
|
+
let hiddenProp = null;
|
|
310
|
+
const visibilityEvents = [
|
|
311
|
+
'visibilitychange',
|
|
312
|
+
'mozvisibilitychange',
|
|
313
|
+
'webkitvisibilitychange',
|
|
314
|
+
'msvisibilitychange',
|
|
315
|
+
];
|
|
316
|
+
const visibilityProps = ['hidden', 'mozHidden', 'webkitHidden', 'msHidden'];
|
|
317
|
+
for (let i = 0; i < visibilityProps.length; i++) {
|
|
318
|
+
if (visibilityProps[i] in document) {
|
|
319
|
+
hiddenProp = visibilityProps[i];
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** @type {(this: any, evt: Event) => void} */
|
|
324
|
+
const handler = function (evt) {
|
|
325
|
+
removeClass();
|
|
326
|
+
const type = evt?.type;
|
|
327
|
+
// @ts-ignore
|
|
328
|
+
const isHidden = hiddenProp && document[hiddenProp];
|
|
329
|
+
const visibleEvents = ['focus', 'focusin', 'pageshow'];
|
|
330
|
+
const hiddenEvents = ['blur', 'focusout', 'pagehide'];
|
|
331
|
+
if (visibleEvents.includes(type)) {
|
|
332
|
+
element.classList.add(visibleClass);
|
|
333
|
+
onVisible?.();
|
|
334
|
+
}
|
|
335
|
+
else if (hiddenEvents.includes(type)) {
|
|
336
|
+
element.classList.add(hiddenClass);
|
|
337
|
+
onHidden?.();
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
if (isHidden) {
|
|
341
|
+
element.classList.add(hiddenClass);
|
|
342
|
+
onHidden?.();
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
element.classList.add(visibleClass);
|
|
346
|
+
onVisible?.();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
/** @type {() => void} */
|
|
351
|
+
let uninstall = () => { };
|
|
352
|
+
if (hiddenProp) {
|
|
353
|
+
const eventType = visibilityEvents[visibilityProps.indexOf(hiddenProp)];
|
|
354
|
+
document.addEventListener(eventType, handler);
|
|
355
|
+
window.addEventListener('focus', handler);
|
|
356
|
+
window.addEventListener('blur', handler);
|
|
357
|
+
uninstall = () => {
|
|
358
|
+
document.removeEventListener(eventType, handler);
|
|
359
|
+
window.removeEventListener('focus', handler);
|
|
360
|
+
window.removeEventListener('blur', handler);
|
|
361
|
+
removeClass();
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
else if ('onfocusin' in document) {
|
|
365
|
+
// Fallback for IE9 and older
|
|
366
|
+
// @ts-ignore
|
|
367
|
+
document.onfocusin = document.onfocusout = handler;
|
|
368
|
+
uninstall = () => {
|
|
369
|
+
// @ts-ignore
|
|
370
|
+
document.onfocusin = document.onfocusout = null;
|
|
371
|
+
removeClass();
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// Last resort fallback
|
|
376
|
+
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = handler;
|
|
377
|
+
uninstall = () => {
|
|
378
|
+
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = null;
|
|
379
|
+
removeClass();
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
// Trigger initial state
|
|
383
|
+
// @ts-ignore
|
|
384
|
+
const simulatedEvent = new Event(hiddenProp && document[hiddenProp] ? 'blur' : 'focus');
|
|
385
|
+
handler(simulatedEvent);
|
|
386
|
+
return uninstall;
|
|
387
|
+
}
|
package/dist/v1/basics/index.cjs
CHANGED
|
@@ -25,6 +25,9 @@ exports.getHtmlElBorders = html.getHtmlElBorders;
|
|
|
25
25
|
exports.getHtmlElBordersWidth = html.getHtmlElBordersWidth;
|
|
26
26
|
exports.getHtmlElMargin = html.getHtmlElMargin;
|
|
27
27
|
exports.getHtmlElPadding = html.getHtmlElPadding;
|
|
28
|
+
exports.installWindowHiddenScript = html.installWindowHiddenScript;
|
|
29
|
+
exports.readBase64Blob = html.readBase64Blob;
|
|
30
|
+
exports.readFileBlob = html.readFileBlob;
|
|
28
31
|
exports.readJsonBlob = html.readJsonBlob;
|
|
29
32
|
exports.saveJsonFile = html.saveJsonFile;
|
|
30
33
|
exports.cloneObjTypeOrder = objFilter.cloneObjTypeOrder;
|
|
@@ -46,5 +49,6 @@ exports.getAge = simpleMath.getAge;
|
|
|
46
49
|
exports.getSimplePerc = simpleMath.getSimplePerc;
|
|
47
50
|
exports.ruleOfThree = simpleMath.ruleOfThree;
|
|
48
51
|
exports.addAiMarkerShortcut = text.addAiMarkerShortcut;
|
|
52
|
+
exports.safeTextTrim = text.safeTextTrim;
|
|
49
53
|
exports.toTitleCase = text.toTitleCase;
|
|
50
54
|
exports.toTitleCaseLowerFirst = text.toTitleCaseLowerFirst;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { safeTextTrim } from './text.mjs';
|
|
2
|
+
import { installWindowHiddenScript } from './html.mjs';
|
|
1
3
|
import { genFibonacciSeq } from './simpleMath.mjs';
|
|
2
4
|
import { getHtmlElBorders } from './html.mjs';
|
|
3
5
|
import { getHtmlElBordersWidth } from './html.mjs';
|
|
@@ -5,6 +7,8 @@ import { getHtmlElMargin } from './html.mjs';
|
|
|
5
7
|
import { getHtmlElPadding } from './html.mjs';
|
|
6
8
|
import { fetchJson } from './html.mjs';
|
|
7
9
|
import { readJsonBlob } from './html.mjs';
|
|
10
|
+
import { readFileBlob } from './html.mjs';
|
|
11
|
+
import { readBase64Blob } from './html.mjs';
|
|
8
12
|
import { saveJsonFile } from './html.mjs';
|
|
9
13
|
import { documentIsFullScreen } from './fullScreen.mjs';
|
|
10
14
|
import { isScreenFilled } from './fullScreen.mjs';
|
|
@@ -34,5 +38,5 @@ import { getTimeDuration } from './clock.mjs';
|
|
|
34
38
|
import { shuffleArray } from './array.mjs';
|
|
35
39
|
import { toTitleCase } from './text.mjs';
|
|
36
40
|
import { toTitleCaseLowerFirst } from './text.mjs';
|
|
37
|
-
export { 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 };
|
|
41
|
+
export { 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 };
|
|
38
42
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/v1/basics/index.mjs
CHANGED
|
@@ -2,9 +2,9 @@ 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, } from './html.mjs';
|
|
5
|
+
import { areHtmlElsColliding, readJsonBlob, saveJsonFile, fetchJson, getHtmlElBorders, getHtmlElBordersWidth, getHtmlElMargin, getHtmlElPadding, installWindowHiddenScript, readFileBlob, readBase64Blob, } 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
|
-
export { 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, };
|
|
9
|
+
import { addAiMarkerShortcut, safeTextTrim, toTitleCase, toTitleCaseLowerFirst } from './text.mjs';
|
|
10
|
+
export { 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, };
|