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.
Files changed (36) hide show
  1. package/dist/v1/TinyBasicsEs.js +656 -26
  2. package/dist/v1/TinyBasicsEs.min.js +1 -1
  3. package/dist/v1/TinyDragger.js +196 -26
  4. package/dist/v1/TinyEssentials.js +896 -26
  5. package/dist/v1/TinyEssentials.min.js +1 -1
  6. package/dist/v1/TinyNotifications.js +408 -0
  7. package/dist/v1/TinyNotifications.min.js +1 -0
  8. package/dist/v1/TinyUploadClicker.js +198 -26
  9. package/dist/v1/basics/collision.cjs +413 -0
  10. package/dist/v1/basics/collision.d.mts +187 -0
  11. package/dist/v1/basics/collision.mjs +350 -0
  12. package/dist/v1/basics/html.cjs +201 -26
  13. package/dist/v1/basics/html.d.mts +71 -7
  14. package/dist/v1/basics/html.mjs +186 -23
  15. package/dist/v1/basics/index.cjs +24 -0
  16. package/dist/v1/basics/index.d.mts +24 -1
  17. package/dist/v1/basics/index.mjs +4 -3
  18. package/dist/v1/basics/text.cjs +43 -0
  19. package/dist/v1/basics/text.d.mts +16 -0
  20. package/dist/v1/basics/text.mjs +37 -0
  21. package/dist/v1/build/TinyNotifications.cjs +7 -0
  22. package/dist/v1/build/TinyNotifications.d.mts +3 -0
  23. package/dist/v1/build/TinyNotifications.mjs +2 -0
  24. package/dist/v1/index.cjs +26 -0
  25. package/dist/v1/index.d.mts +25 -1
  26. package/dist/v1/index.mjs +5 -3
  27. package/dist/v1/libs/TinyNotifications.cjs +238 -0
  28. package/dist/v1/libs/TinyNotifications.d.mts +106 -0
  29. package/dist/v1/libs/TinyNotifications.mjs +211 -0
  30. package/docs/v1/README.md +5 -3
  31. package/docs/v1/basics/array.md +16 -43
  32. package/docs/v1/basics/collision.md +237 -0
  33. package/docs/v1/basics/html.md +117 -28
  34. package/docs/v1/basics/text.md +44 -14
  35. package/docs/v1/libs/TinyNotifications.md +189 -0
  36. package/package.json +5 -2
@@ -7,12 +7,56 @@
7
7
  */
8
8
  export function areHtmlElsColliding(elem1: Element, elem2: Element): boolean;
9
9
  /**
10
- * Reads and parses a JSON data using FileReader.
11
- * Throws an error if the content is not valid JSON.
12
- * @param {File} file
13
- * @returns {Promise<any>}
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;
@@ -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 !(rect1.right < rect2.left ||
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
- * Reads and parses a JSON data using FileReader.
19
- * Throws an error if the content is not valid JSON.
20
- * @param {File} file
21
- * @returns {Promise<any>}
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 readJsonBlob(file) {
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
- // @ts-ignore
29
- const result = JSON.parse(reader.result);
30
- resolve(result);
76
+ resolve(reader.result);
31
77
  }
32
78
  catch (error) {
33
- // @ts-ignore
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(new Error(`Error reading file: ${file.name}`));
83
+ reject(reader.error);
39
84
  };
40
- reader.readAsText(file);
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
- handler = function (evt) {
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
- element.classList.add(isHidden ? hiddenClass : visibleClass);
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
+ }
@@ -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
@@ -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
- 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, };
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, };
@@ -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
@@ -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
 
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ var TinyNotifications = require('../libs/TinyNotifications.cjs');
4
+
5
+
6
+
7
+ exports.TinyNotifications = TinyNotifications;
@@ -0,0 +1,3 @@
1
+ export { TinyNotifications };
2
+ import TinyNotifications from '../libs/TinyNotifications.mjs';
3
+ //# sourceMappingURL=TinyNotifications.d.mts.map
@@ -0,0 +1,2 @@
1
+ import TinyNotifications from '../libs/TinyNotifications.mjs';
2
+ export { TinyNotifications };