tiny-essentials 1.18.0 → 1.19.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/README.md +17 -3
- package/dist/legacy/get/countObj.cjs +2 -2
- package/dist/legacy/get/countObj.d.mts +1 -1
- package/dist/legacy/get/countObj.mjs +1 -1
- package/dist/legacy/index.cjs +2 -1
- package/dist/node_modules/firebase-functions/lib/common/trace.cjs +0 -1
- package/dist/node_modules/firebase-functions/lib/logger/index.cjs +1 -0
- package/dist/v1/ColorSafeStringify.min.js +1 -1
- package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
- package/dist/v1/TinyBasicsEs.js +402 -390
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyClipboard.min.js +1 -1
- package/dist/v1/TinyColorConverter.js +617 -0
- package/dist/v1/TinyColorConverter.min.js +1 -0
- package/dist/v1/TinyDomReadyManager.min.js +1 -1
- package/dist/v1/TinyDragger.js +6 -2433
- package/dist/v1/TinyDragger.min.js +1 -2
- package/dist/v1/TinyEssentials.js +3546 -1388
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyEvents.js +402 -0
- package/dist/v1/TinyEvents.min.js +1 -0
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinyLocalStorage.js +1292 -0
- package/dist/v1/TinyLocalStorage.min.js +1 -0
- package/dist/v1/TinyNotifications.min.js +1 -1
- package/dist/v1/TinyNotifyCenter.min.js +1 -1
- package/dist/v1/TinyPromiseQueue.min.js +1 -1
- package/dist/v1/TinyRateLimiter.js +2 -1
- package/dist/v1/TinyRateLimiter.min.js +1 -1
- package/dist/v1/TinySmartScroller.js +570 -52
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextRangeEditor.min.js +1 -1
- package/dist/v1/TinyTimeout.js +233 -0
- package/dist/v1/TinyTimeout.min.js +1 -0
- package/dist/v1/TinyToastNotify.min.js +1 -1
- package/dist/v1/TinyUploadClicker.js +2087 -1129
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/UltraRandomMsgGen.min.js +1 -1
- package/dist/v1/basics/html.cjs +16 -9
- package/dist/v1/basics/html.d.mts +12 -4
- package/dist/v1/basics/html.mjs +14 -7
- package/dist/v1/basics/index.cjs +3 -2
- package/dist/v1/basics/index.d.mts +2 -2
- package/dist/v1/basics/index.mjs +2 -1
- package/dist/v1/basics/objChecker.cjs +46 -0
- package/dist/v1/basics/objChecker.d.mts +29 -0
- package/dist/v1/basics/objChecker.mjs +45 -0
- package/dist/v1/basics/objFilter.cjs +4 -45
- package/dist/v1/basics/objFilter.d.mts +3 -28
- package/dist/v1/basics/objFilter.mjs +2 -45
- package/dist/v1/build/TinyColorConverter.cjs +7 -0
- package/dist/v1/build/TinyColorConverter.d.mts +3 -0
- package/dist/v1/build/TinyColorConverter.mjs +2 -0
- package/dist/v1/build/TinyEvents.cjs +7 -0
- package/dist/v1/build/TinyEvents.d.mts +3 -0
- package/dist/v1/build/TinyEvents.mjs +2 -0
- package/dist/v1/build/TinyLocalStorage.cjs +7 -0
- package/dist/v1/build/TinyLocalStorage.d.mts +3 -0
- package/dist/v1/build/TinyLocalStorage.mjs +2 -0
- package/dist/v1/build/TinyTimeout.cjs +7 -0
- package/dist/v1/build/TinyTimeout.d.mts +3 -0
- package/dist/v1/build/TinyTimeout.mjs +2 -0
- package/dist/v1/index.cjs +11 -2
- package/dist/v1/index.d.mts +7 -3
- package/dist/v1/index.mjs +7 -2
- package/dist/v1/libs/TinyColorConverter.cjs +578 -0
- package/dist/v1/libs/TinyColorConverter.d.mts +396 -0
- package/dist/v1/libs/TinyColorConverter.mjs +520 -0
- package/dist/v1/libs/TinyDragger.cjs +3 -3
- package/dist/v1/libs/TinyDragger.mjs +1 -1
- package/dist/v1/libs/TinyEvents.cjs +363 -0
- package/dist/v1/libs/TinyEvents.d.mts +160 -0
- package/dist/v1/libs/TinyEvents.mjs +328 -0
- package/dist/v1/libs/TinyLocalStorage.cjs +847 -0
- package/dist/v1/libs/TinyLocalStorage.d.mts +407 -0
- package/dist/v1/libs/TinyLocalStorage.mjs +740 -0
- package/dist/v1/libs/TinySmartScroller.cjs +207 -52
- package/dist/v1/libs/TinySmartScroller.d.mts +164 -16
- package/dist/v1/libs/TinySmartScroller.mjs +181 -52
- package/dist/v1/libs/TinyTimeout.cjs +194 -0
- package/dist/v1/libs/TinyTimeout.d.mts +89 -0
- package/dist/v1/libs/TinyTimeout.mjs +179 -0
- package/dist/v1/libs/TinyUploadClicker.cjs +6 -4
- package/docs/v1/README.md +5 -0
- package/docs/v1/basics/objChecker.md +47 -0
- package/docs/v1/basics/objFilter.md +0 -40
- package/docs/v1/libs/TinyColorConverter.md +220 -0
- package/docs/v1/libs/TinyEvents.md +199 -0
- package/docs/v1/libs/TinyLocalStorage.md +350 -0
- package/docs/v1/libs/TinyRateLimiter.md +0 -3
- package/docs/v1/libs/TinyTimeout.md +190 -0
- package/package.json +28 -5
- package/dist/v1/TinyDragger.min.js.LICENSE.txt +0 -8
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/******/ (() => { // webpackBootstrap
|
|
2
2
|
/******/ var __webpack_modules__ = ({
|
|
3
3
|
|
|
4
|
-
/***/ 133:
|
|
5
|
-
/***/ (() => {
|
|
6
|
-
|
|
7
|
-
/* (ignored) */
|
|
8
|
-
|
|
9
|
-
/***/ }),
|
|
10
|
-
|
|
11
4
|
/***/ 251:
|
|
12
5
|
/***/ ((__unused_webpack_module, exports) => {
|
|
13
6
|
|
|
@@ -1924,13 +1917,6 @@ var hexSliceLookupTable = (function () {
|
|
|
1924
1917
|
})()
|
|
1925
1918
|
|
|
1926
1919
|
|
|
1927
|
-
/***/ }),
|
|
1928
|
-
|
|
1929
|
-
/***/ 370:
|
|
1930
|
-
/***/ (() => {
|
|
1931
|
-
|
|
1932
|
-
/* (ignored) */
|
|
1933
|
-
|
|
1934
1920
|
/***/ }),
|
|
1935
1921
|
|
|
1936
1922
|
/***/ 526:
|
|
@@ -2912,9 +2898,55 @@ __webpack_require__.d(collision_namespaceObject, {
|
|
|
2912
2898
|
|
|
2913
2899
|
// EXTERNAL MODULE: ./node_modules/buffer/index.js
|
|
2914
2900
|
var buffer = __webpack_require__(287);
|
|
2901
|
+
;// ./src/v1/basics/objChecker.mjs
|
|
2902
|
+
/**
|
|
2903
|
+
* Counts the number of elements in an array or the number of properties in an object.
|
|
2904
|
+
*
|
|
2905
|
+
* @param {Array<*>|Record<string | number | symbol, any>} obj - The array or object to count.
|
|
2906
|
+
* @returns {number} - The count of items (array elements or object keys), or `0` if the input is neither an array nor an object.
|
|
2907
|
+
*
|
|
2908
|
+
* @example
|
|
2909
|
+
* countObj([1, 2, 3]); // 3
|
|
2910
|
+
* countObj({ a: 1, b: 2 }); // 2
|
|
2911
|
+
* countObj('not an object'); // 0
|
|
2912
|
+
*/
|
|
2913
|
+
function countObj(obj) {
|
|
2914
|
+
// Is Array
|
|
2915
|
+
if (Array.isArray(obj)) return obj.length;
|
|
2916
|
+
// Object
|
|
2917
|
+
if (isJsonObject(obj)) return Object.keys(obj).length;
|
|
2918
|
+
// Nothing
|
|
2919
|
+
return 0;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
/**
|
|
2923
|
+
* Determines whether a given value is a pure JSON object (plain object).
|
|
2924
|
+
*
|
|
2925
|
+
* A pure object satisfies the following:
|
|
2926
|
+
* - It is not null.
|
|
2927
|
+
* - Its type is "object".
|
|
2928
|
+
* - Its internal [[Class]] is "[object Object]".
|
|
2929
|
+
* - It is not an instance of built-in types like Array, Date, Map, Set, etc.
|
|
2930
|
+
*
|
|
2931
|
+
* This function is useful for strict data validation when you want to ensure
|
|
2932
|
+
* a value is a clean JSON-compatible object, free of class instances or special types.
|
|
2933
|
+
*
|
|
2934
|
+
* @param {unknown} value - The value to test.
|
|
2935
|
+
* @returns {value is Record<string | number | symbol, unknown>} Returns true if the value is a pure object.
|
|
2936
|
+
*/
|
|
2937
|
+
function isJsonObject(value) {
|
|
2938
|
+
if (value === null || typeof value !== 'object') return false;
|
|
2939
|
+
if (Array.isArray(value)) return false;
|
|
2940
|
+
if (Object.prototype.toString.call(value) !== '[object Object]') return false;
|
|
2941
|
+
return true;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2915
2944
|
;// ./src/v1/basics/objFilter.mjs
|
|
2916
2945
|
|
|
2917
2946
|
|
|
2947
|
+
|
|
2948
|
+
|
|
2949
|
+
|
|
2918
2950
|
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
2919
2951
|
|
|
2920
2952
|
/**
|
|
@@ -3107,48 +3139,6 @@ function getCheckObj() {
|
|
|
3107
3139
|
return Object.fromEntries(Object.entries(typeValidator.items).map(([key, fn]) => [key, fn]));
|
|
3108
3140
|
}
|
|
3109
3141
|
|
|
3110
|
-
/**
|
|
3111
|
-
* Counts the number of elements in an array or the number of properties in an object.
|
|
3112
|
-
*
|
|
3113
|
-
* @param {Array<*>|Record<string|number, any>} obj - The array or object to count.
|
|
3114
|
-
* @returns {number} - The count of items (array elements or object keys), or `0` if the input is neither an array nor an object.
|
|
3115
|
-
*
|
|
3116
|
-
* @example
|
|
3117
|
-
* countObj([1, 2, 3]); // 3
|
|
3118
|
-
* countObj({ a: 1, b: 2 }); // 2
|
|
3119
|
-
* countObj('not an object'); // 0
|
|
3120
|
-
*/
|
|
3121
|
-
function countObj(obj) {
|
|
3122
|
-
// Is Array
|
|
3123
|
-
if (Array.isArray(obj)) return obj.length;
|
|
3124
|
-
// Object
|
|
3125
|
-
if (objType(obj, 'object')) return Object.keys(obj).length;
|
|
3126
|
-
// Nothing
|
|
3127
|
-
return 0;
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
/**
|
|
3131
|
-
* Determines whether a given value is a pure JSON object (plain object).
|
|
3132
|
-
*
|
|
3133
|
-
* A pure object satisfies the following:
|
|
3134
|
-
* - It is not null.
|
|
3135
|
-
* - Its type is "object".
|
|
3136
|
-
* - Its internal [[Class]] is "[object Object]".
|
|
3137
|
-
* - It is not an instance of built-in types like Array, Date, Map, Set, etc.
|
|
3138
|
-
*
|
|
3139
|
-
* This function is useful for strict data validation when you want to ensure
|
|
3140
|
-
* a value is a clean JSON-compatible object, free of class instances or special types.
|
|
3141
|
-
*
|
|
3142
|
-
* @param {unknown} value - The value to test.
|
|
3143
|
-
* @returns {value is Record<string | number | symbol, unknown>} Returns true if the value is a pure object.
|
|
3144
|
-
*/
|
|
3145
|
-
function objFilter_isJsonObject(value) {
|
|
3146
|
-
if (value === null || typeof value !== 'object') return false;
|
|
3147
|
-
if (Array.isArray(value)) return false;
|
|
3148
|
-
if (Object.prototype.toString.call(value) !== '[object Object]') return false;
|
|
3149
|
-
return true;
|
|
3150
|
-
}
|
|
3151
|
-
|
|
3152
3142
|
// Insert obj types
|
|
3153
3143
|
|
|
3154
3144
|
extendObjType([
|
|
@@ -3271,7 +3261,7 @@ extendObjType([
|
|
|
3271
3261
|
[
|
|
3272
3262
|
'object',
|
|
3273
3263
|
/** @param {*} val @returns {val is Record<string | number | symbol, unknown>} */
|
|
3274
|
-
(val) =>
|
|
3264
|
+
(val) => isJsonObject(val),
|
|
3275
3265
|
],
|
|
3276
3266
|
]);
|
|
3277
3267
|
|
|
@@ -3471,404 +3461,6 @@ class ColorSafeStringify {
|
|
|
3471
3461
|
|
|
3472
3462
|
/* harmony default export */ const libs_ColorSafeStringify = ((/* unused pure expression or super */ null && (ColorSafeStringify)));
|
|
3473
3463
|
|
|
3474
|
-
;// ./src/v1/basics/html.mjs
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
/////////////////////////////////////////////////////////////////
|
|
3478
|
-
|
|
3479
|
-
/**
|
|
3480
|
-
* Reads the contents of a file using the specified FileReader method.
|
|
3481
|
-
*
|
|
3482
|
-
* @param {File} file - The file to be read.
|
|
3483
|
-
* @param {'readAsArrayBuffer'|'readAsDataURL'|'readAsText'|'readAsBinaryString'} method -
|
|
3484
|
-
* The FileReader method to use for reading the file.
|
|
3485
|
-
* @returns {Promise<any>} - A promise that resolves with the file content, according to the chosen method.
|
|
3486
|
-
* @throws {Error} - If an unexpected error occurs while handling the result.
|
|
3487
|
-
* @throws {DOMException} - If the FileReader encounters an error while reading the file.
|
|
3488
|
-
*/
|
|
3489
|
-
function readFileBlob(file, method) {
|
|
3490
|
-
return new Promise((resolve, reject) => {
|
|
3491
|
-
const reader = new FileReader();
|
|
3492
|
-
reader.onload = () => {
|
|
3493
|
-
try {
|
|
3494
|
-
resolve(reader.result);
|
|
3495
|
-
} catch (error) {
|
|
3496
|
-
reject(error);
|
|
3497
|
-
}
|
|
3498
|
-
};
|
|
3499
|
-
reader.onerror = () => {
|
|
3500
|
-
reject(reader.error);
|
|
3501
|
-
};
|
|
3502
|
-
reader[method](file);
|
|
3503
|
-
});
|
|
3504
|
-
}
|
|
3505
|
-
|
|
3506
|
-
/**
|
|
3507
|
-
* Reads a file as a Base64 string using FileReader, and optionally formats it as a full data URL.
|
|
3508
|
-
*
|
|
3509
|
-
* Performs strict validation to ensure the result is a valid Base64 string or a proper data URL.
|
|
3510
|
-
*
|
|
3511
|
-
* @param {File} file - The file to be read.
|
|
3512
|
-
* @param {boolean|string} [isDataUrl=false] - If true, returns a full data URL; if false, returns only the Base64 string;
|
|
3513
|
-
* if a string is passed, it is used as the MIME type in the data URL.
|
|
3514
|
-
* @returns {Promise<string>} - A promise that resolves with the Base64 string or data URL.
|
|
3515
|
-
*
|
|
3516
|
-
* @throws {TypeError} - If the result is not a string or if `isDataUrl` is not a valid type.
|
|
3517
|
-
* @throws {Error} - If the result does not match the expected data URL format or Base64 structure.
|
|
3518
|
-
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
3519
|
-
*/
|
|
3520
|
-
function readBase64Blob(file, isDataUrl = false) {
|
|
3521
|
-
return new Promise((resolve, reject) => {
|
|
3522
|
-
if (typeof isDataUrl !== 'string' && typeof isDataUrl !== 'boolean')
|
|
3523
|
-
reject(new TypeError('The isDataUrl parameter must be a boolean or a string.'));
|
|
3524
|
-
readFileBlob(file, 'readAsDataURL')
|
|
3525
|
-
.then(
|
|
3526
|
-
/**
|
|
3527
|
-
* Ensure that the URL format is correct in the required pattern
|
|
3528
|
-
* @param {string} base64Data
|
|
3529
|
-
*/ (base64Data) => {
|
|
3530
|
-
if (typeof base64Data !== 'string')
|
|
3531
|
-
throw new TypeError('Expected file content to be a string.');
|
|
3532
|
-
|
|
3533
|
-
const match = base64Data.match(/^data:(.+);base64,(.*)$/);
|
|
3534
|
-
if (!match || !match[2])
|
|
3535
|
-
throw new Error('Invalid data URL format or missing Base64 content.');
|
|
3536
|
-
const [, mimeType, base64] = match;
|
|
3537
|
-
if (!/^[\w/+]+=*$/.test(base64)) throw new Error('Base64 content is malformed.');
|
|
3538
|
-
|
|
3539
|
-
if (typeof isDataUrl === 'boolean') return resolve(isDataUrl ? base64Data : base64);
|
|
3540
|
-
if (!/^[\w-]+\/[\w.+-]+$/.test(isDataUrl))
|
|
3541
|
-
throw new Error(`Invalid MIME type string: ${isDataUrl}`);
|
|
3542
|
-
|
|
3543
|
-
return resolve(`data:${isDataUrl};base64,${base64}`);
|
|
3544
|
-
},
|
|
3545
|
-
)
|
|
3546
|
-
.catch(reject);
|
|
3547
|
-
});
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
/**
|
|
3551
|
-
* Reads a file and strictly validates its content as proper JSON using FileReader.
|
|
3552
|
-
*
|
|
3553
|
-
* Performs several checks to ensure the file contains valid, parsable JSON data.
|
|
3554
|
-
*
|
|
3555
|
-
* @param {File} file - The file to be read. It must contain valid JSON as plain text.
|
|
3556
|
-
* @returns {Promise<Record<string|number|symbol, any>|any[]>} - A promise that resolves with the parsed JSON object.
|
|
3557
|
-
*
|
|
3558
|
-
* @throws {SyntaxError} - If the file content is not valid JSON syntax.
|
|
3559
|
-
* @throws {TypeError} - If the result is not a string or does not represent a JSON value.
|
|
3560
|
-
* @throws {Error} - If the result is empty or structurally invalid as JSON.
|
|
3561
|
-
* @throws {DOMException} - If the FileReader fails to read the file.
|
|
3562
|
-
*/
|
|
3563
|
-
function readJsonBlob(file) {
|
|
3564
|
-
return new Promise((resolve, reject) =>
|
|
3565
|
-
readFileBlob(file, 'readAsText')
|
|
3566
|
-
.then((data) => {
|
|
3567
|
-
if (typeof data !== 'string') throw new TypeError('Expected file content to be a string.');
|
|
3568
|
-
const trimmed = data.trim();
|
|
3569
|
-
if (trimmed.length === 0) throw new Error('File is empty or contains only whitespace.');
|
|
3570
|
-
const parsed = JSON.parse(trimmed);
|
|
3571
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
3572
|
-
throw new Error('Parsed content is not a valid JSON object or array.');
|
|
3573
|
-
resolve(parsed);
|
|
3574
|
-
})
|
|
3575
|
-
.catch(reject),
|
|
3576
|
-
);
|
|
3577
|
-
}
|
|
3578
|
-
|
|
3579
|
-
/**
|
|
3580
|
-
* Saves a JSON object as a downloadable file.
|
|
3581
|
-
* @param {string} filename
|
|
3582
|
-
* @param {any} data
|
|
3583
|
-
* @param {number} [spaces=2]
|
|
3584
|
-
*/
|
|
3585
|
-
function saveJsonFile(filename, data, spaces = 2) {
|
|
3586
|
-
const json = JSON.stringify(data, null, spaces);
|
|
3587
|
-
const blob = new Blob([json], { type: 'application/json' });
|
|
3588
|
-
const url = URL.createObjectURL(blob);
|
|
3589
|
-
|
|
3590
|
-
const link = document.createElement('a');
|
|
3591
|
-
link.href = url;
|
|
3592
|
-
link.download = filename;
|
|
3593
|
-
|
|
3594
|
-
document.body.appendChild(link);
|
|
3595
|
-
link.click();
|
|
3596
|
-
document.body.removeChild(link);
|
|
3597
|
-
|
|
3598
|
-
URL.revokeObjectURL(url);
|
|
3599
|
-
}
|
|
3600
|
-
|
|
3601
|
-
/**
|
|
3602
|
-
* @typedef {Object} FetchTemplateOptions
|
|
3603
|
-
* @property {string} [method="GET"] - HTTP method to use (GET, POST, etc.).
|
|
3604
|
-
* @property {any} [body] - Request body (only for methods like POST, PUT).
|
|
3605
|
-
* @property {number} [timeout=0] - Timeout in milliseconds (ignored if signal is provided).
|
|
3606
|
-
* @property {number} [retries=0] - Number of retry attempts (ignored if signal is provided).
|
|
3607
|
-
* @property {Headers|Record<string, *>} [headers={}] - Additional headers.
|
|
3608
|
-
* @property {AbortSignal|null} [signal] - External AbortSignal; disables timeout and retries.
|
|
3609
|
-
*/
|
|
3610
|
-
|
|
3611
|
-
/**
|
|
3612
|
-
* @param {string} url - The full URL to fetch data from.
|
|
3613
|
-
* @param {FetchTemplateOptions} [options] - Optional settings.
|
|
3614
|
-
* @returns {Promise<Response>} Result data.
|
|
3615
|
-
* @throws {Error} Throws if fetch fails, times out.
|
|
3616
|
-
*/
|
|
3617
|
-
async function fetchTemplate(
|
|
3618
|
-
url,
|
|
3619
|
-
{ method = 'GET', body, timeout = 0, retries = 0, headers = {}, signal = null } = {},
|
|
3620
|
-
) {
|
|
3621
|
-
if (
|
|
3622
|
-
typeof url !== 'string' ||
|
|
3623
|
-
(!url.startsWith('../') &&
|
|
3624
|
-
!url.startsWith('./') &&
|
|
3625
|
-
!url.startsWith('/') &&
|
|
3626
|
-
!url.startsWith('https://') &&
|
|
3627
|
-
!url.startsWith('http://'))
|
|
3628
|
-
)
|
|
3629
|
-
throw new Error('Invalid URL: must be a valid http or https address.');
|
|
3630
|
-
|
|
3631
|
-
if (typeof method !== 'string' || !method.trim())
|
|
3632
|
-
throw new Error('Invalid method: must be a non-empty string.');
|
|
3633
|
-
|
|
3634
|
-
if (!signal) {
|
|
3635
|
-
if (
|
|
3636
|
-
typeof timeout !== 'number' ||
|
|
3637
|
-
!Number.isFinite(timeout) ||
|
|
3638
|
-
Number.isNaN(timeout) ||
|
|
3639
|
-
timeout < 0
|
|
3640
|
-
)
|
|
3641
|
-
throw new Error('Invalid timeout: must be a positive number.');
|
|
3642
|
-
|
|
3643
|
-
if (
|
|
3644
|
-
typeof retries !== 'number' ||
|
|
3645
|
-
!Number.isFinite(retries) ||
|
|
3646
|
-
Number.isNaN(retries) ||
|
|
3647
|
-
retries < 0
|
|
3648
|
-
)
|
|
3649
|
-
throw new Error('Invalid retries: must be a positive number.');
|
|
3650
|
-
}
|
|
3651
|
-
|
|
3652
|
-
const attempts = signal ? 1 : retries + 1;
|
|
3653
|
-
/** @type {Error|null} */
|
|
3654
|
-
let lastError = null;
|
|
3655
|
-
|
|
3656
|
-
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
3657
|
-
const controller = signal ? null : new AbortController();
|
|
3658
|
-
const localSignal = signal || (controller?.signal ?? null);
|
|
3659
|
-
const timer =
|
|
3660
|
-
!signal && timeout && controller ? setTimeout(() => controller.abort(), timeout) : null;
|
|
3661
|
-
|
|
3662
|
-
try {
|
|
3663
|
-
const response = await fetch(url, {
|
|
3664
|
-
method: method.toUpperCase(),
|
|
3665
|
-
headers: {
|
|
3666
|
-
Accept: 'application/json',
|
|
3667
|
-
...headers,
|
|
3668
|
-
},
|
|
3669
|
-
body: body !== undefined ? (isJsonObject(body) ? JSON.stringify(body) : body) : undefined,
|
|
3670
|
-
signal: localSignal,
|
|
3671
|
-
});
|
|
3672
|
-
|
|
3673
|
-
if (timer) clearTimeout(timer);
|
|
3674
|
-
|
|
3675
|
-
if (!response.ok) throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
3676
|
-
return response;
|
|
3677
|
-
} catch (err) {
|
|
3678
|
-
lastError = /** @type {Error} */ (err);
|
|
3679
|
-
if (signal) break; // if an external signal came, it does not retry
|
|
3680
|
-
if (attempt < retries)
|
|
3681
|
-
await new Promise((resolve) => setTimeout(resolve, 300 * (attempt + 1)));
|
|
3682
|
-
}
|
|
3683
|
-
}
|
|
3684
|
-
|
|
3685
|
-
throw new Error(
|
|
3686
|
-
`Failed to fetch JSON from "${url}"${lastError ? `: ${lastError.message}` : '.'}`,
|
|
3687
|
-
);
|
|
3688
|
-
}
|
|
3689
|
-
|
|
3690
|
-
/**
|
|
3691
|
-
* Loads and parses a JSON from a remote URL using Fetch API.
|
|
3692
|
-
*
|
|
3693
|
-
* @param {string} url - The full URL to fetch JSON from.
|
|
3694
|
-
* @param {Object} [options] - Optional settings.
|
|
3695
|
-
* @returns {Promise<any[] | Record<string | number | symbol, unknown>>} Parsed JSON object.
|
|
3696
|
-
* @throws {Error} Throws if fetch fails, times out, or returns invalid JSON.
|
|
3697
|
-
*/
|
|
3698
|
-
async function fetchJson(url, options) {
|
|
3699
|
-
return new Promise((resolve, reject) => {
|
|
3700
|
-
fetchTemplate(url, options)
|
|
3701
|
-
.then(async (res) => {
|
|
3702
|
-
const contentType = res.headers.get('content-type') || '';
|
|
3703
|
-
if (!contentType.includes('application/json'))
|
|
3704
|
-
throw new Error(`Unexpected content-type: ${contentType}`);
|
|
3705
|
-
|
|
3706
|
-
const data = await res.json();
|
|
3707
|
-
|
|
3708
|
-
if (!Array.isArray(data) && !isJsonObject(data))
|
|
3709
|
-
throw new Error('Received invalid data instead of valid JSON.');
|
|
3710
|
-
|
|
3711
|
-
return resolve(data);
|
|
3712
|
-
})
|
|
3713
|
-
.catch(reject);
|
|
3714
|
-
});
|
|
3715
|
-
}
|
|
3716
|
-
|
|
3717
|
-
/**
|
|
3718
|
-
* Loads a remote file as a Blob using Fetch API.
|
|
3719
|
-
*
|
|
3720
|
-
* @param {string} url - The full URL to fetch the file from.
|
|
3721
|
-
* @param {Object} [options] - Optional fetch options.
|
|
3722
|
-
* @param {string[]} [allowedMimeTypes] - Optional list of accepted MIME types (e.g., ['image/jpeg']).
|
|
3723
|
-
* @returns {Promise<Blob>} - The fetched file as a Blob.
|
|
3724
|
-
* @throws {Error} Throws if fetch fails, response is not ok, or MIME type is not allowed.
|
|
3725
|
-
*/
|
|
3726
|
-
async function fetchBlob(url, allowedMimeTypes, options) {
|
|
3727
|
-
return new Promise((resolve, reject) => {
|
|
3728
|
-
fetchTemplate(url, options)
|
|
3729
|
-
.then(async (res) => {
|
|
3730
|
-
const contentType = res.headers.get('content-type') || '';
|
|
3731
|
-
|
|
3732
|
-
if (
|
|
3733
|
-
Array.isArray(allowedMimeTypes) &&
|
|
3734
|
-
allowedMimeTypes.length > 0 &&
|
|
3735
|
-
!allowedMimeTypes.some((type) => contentType.includes(type))
|
|
3736
|
-
) {
|
|
3737
|
-
throw new Error(`Blocked MIME type: ${contentType}`);
|
|
3738
|
-
}
|
|
3739
|
-
|
|
3740
|
-
const data = await res.blob();
|
|
3741
|
-
return resolve(data);
|
|
3742
|
-
})
|
|
3743
|
-
.catch(reject);
|
|
3744
|
-
});
|
|
3745
|
-
}
|
|
3746
|
-
|
|
3747
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3748
|
-
|
|
3749
|
-
/**
|
|
3750
|
-
* Installs a script that toggles CSS classes on a given element
|
|
3751
|
-
* based on the page's visibility or focus state, and optionally
|
|
3752
|
-
* triggers callbacks on visibility changes.
|
|
3753
|
-
*
|
|
3754
|
-
* @param {Object} [settings={}]
|
|
3755
|
-
* @param {Element} [settings.element=document.body] - The element to receive visibility classes.
|
|
3756
|
-
* @param {string} [settings.hiddenClass='windowHidden'] - CSS class applied when the page is hidden.
|
|
3757
|
-
* @param {string} [settings.visibleClass='windowVisible'] - CSS class applied when the page is visible.
|
|
3758
|
-
* @param {() => void} [settings.onVisible] - Callback called when page becomes visible.
|
|
3759
|
-
* @param {() => void} [settings.onHidden] - Callback called when page becomes hidden.
|
|
3760
|
-
* @returns {() => void} Function that removes all installed event listeners.
|
|
3761
|
-
* @throws {TypeError} If any provided setting is invalid.
|
|
3762
|
-
*/
|
|
3763
|
-
function installWindowHiddenScript({
|
|
3764
|
-
element = document.body,
|
|
3765
|
-
hiddenClass = 'windowHidden',
|
|
3766
|
-
visibleClass = 'windowVisible',
|
|
3767
|
-
onVisible,
|
|
3768
|
-
onHidden,
|
|
3769
|
-
} = {}) {
|
|
3770
|
-
if (!(element instanceof Element))
|
|
3771
|
-
throw new TypeError(`"element" must be an instance of Element.`);
|
|
3772
|
-
if (typeof hiddenClass !== 'string') throw new TypeError(`"hiddenClass" must be a string.`);
|
|
3773
|
-
if (typeof visibleClass !== 'string') throw new TypeError(`"visibleClass" must be a string.`);
|
|
3774
|
-
if (onVisible !== undefined && typeof onVisible !== 'function')
|
|
3775
|
-
throw new TypeError(`"onVisible" must be a function if provided.`);
|
|
3776
|
-
if (onHidden !== undefined && typeof onHidden !== 'function')
|
|
3777
|
-
throw new TypeError(`"onHidden" must be a function if provided.`);
|
|
3778
|
-
|
|
3779
|
-
const removeClass = () => {
|
|
3780
|
-
element.classList.remove(hiddenClass);
|
|
3781
|
-
element.classList.remove(visibleClass);
|
|
3782
|
-
};
|
|
3783
|
-
|
|
3784
|
-
/** @type {string|null} */
|
|
3785
|
-
let hiddenProp = null;
|
|
3786
|
-
|
|
3787
|
-
const visibilityEvents = [
|
|
3788
|
-
'visibilitychange',
|
|
3789
|
-
'mozvisibilitychange',
|
|
3790
|
-
'webkitvisibilitychange',
|
|
3791
|
-
'msvisibilitychange',
|
|
3792
|
-
];
|
|
3793
|
-
|
|
3794
|
-
const visibilityProps = ['hidden', 'mozHidden', 'webkitHidden', 'msHidden'];
|
|
3795
|
-
|
|
3796
|
-
for (let i = 0; i < visibilityProps.length; i++) {
|
|
3797
|
-
if (visibilityProps[i] in document) {
|
|
3798
|
-
hiddenProp = visibilityProps[i];
|
|
3799
|
-
break;
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
|
|
3803
|
-
/** @type {(this: any, evt: Event) => void} */
|
|
3804
|
-
const handler = function (evt) {
|
|
3805
|
-
removeClass();
|
|
3806
|
-
|
|
3807
|
-
const type = evt?.type;
|
|
3808
|
-
// @ts-ignore
|
|
3809
|
-
const isHidden = hiddenProp && document[hiddenProp];
|
|
3810
|
-
|
|
3811
|
-
const visibleEvents = ['focus', 'focusin', 'pageshow'];
|
|
3812
|
-
const hiddenEvents = ['blur', 'focusout', 'pagehide'];
|
|
3813
|
-
|
|
3814
|
-
if (visibleEvents.includes(type)) {
|
|
3815
|
-
element.classList.add(visibleClass);
|
|
3816
|
-
onVisible?.();
|
|
3817
|
-
} else if (hiddenEvents.includes(type)) {
|
|
3818
|
-
element.classList.add(hiddenClass);
|
|
3819
|
-
onHidden?.();
|
|
3820
|
-
} else {
|
|
3821
|
-
if (isHidden) {
|
|
3822
|
-
element.classList.add(hiddenClass);
|
|
3823
|
-
onHidden?.();
|
|
3824
|
-
} else {
|
|
3825
|
-
element.classList.add(visibleClass);
|
|
3826
|
-
onVisible?.();
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
};
|
|
3830
|
-
|
|
3831
|
-
/** @type {() => void} */
|
|
3832
|
-
let uninstall = () => {};
|
|
3833
|
-
|
|
3834
|
-
if (hiddenProp) {
|
|
3835
|
-
const eventType = visibilityEvents[visibilityProps.indexOf(hiddenProp)];
|
|
3836
|
-
document.addEventListener(eventType, handler);
|
|
3837
|
-
window.addEventListener('focus', handler);
|
|
3838
|
-
window.addEventListener('blur', handler);
|
|
3839
|
-
|
|
3840
|
-
uninstall = () => {
|
|
3841
|
-
document.removeEventListener(eventType, handler);
|
|
3842
|
-
window.removeEventListener('focus', handler);
|
|
3843
|
-
window.removeEventListener('blur', handler);
|
|
3844
|
-
removeClass();
|
|
3845
|
-
};
|
|
3846
|
-
} else if ('onfocusin' in document) {
|
|
3847
|
-
// Fallback for IE9 and older
|
|
3848
|
-
// @ts-ignore
|
|
3849
|
-
document.onfocusin = document.onfocusout = handler;
|
|
3850
|
-
uninstall = () => {
|
|
3851
|
-
// @ts-ignore
|
|
3852
|
-
document.onfocusin = document.onfocusout = null;
|
|
3853
|
-
removeClass();
|
|
3854
|
-
};
|
|
3855
|
-
} else {
|
|
3856
|
-
// Last resort fallback
|
|
3857
|
-
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = handler;
|
|
3858
|
-
uninstall = () => {
|
|
3859
|
-
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = null;
|
|
3860
|
-
removeClass();
|
|
3861
|
-
};
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
|
-
// Trigger initial state
|
|
3865
|
-
// @ts-ignore
|
|
3866
|
-
const simulatedEvent = new Event(hiddenProp && document[hiddenProp] ? 'blur' : 'focus');
|
|
3867
|
-
handler(simulatedEvent);
|
|
3868
|
-
|
|
3869
|
-
return uninstall;
|
|
3870
|
-
}
|
|
3871
|
-
|
|
3872
3464
|
;// ./src/v1/basics/collision.mjs
|
|
3873
3465
|
/**
|
|
3874
3466
|
* A direction relative to a rectangle.
|
|
@@ -9455,8 +9047,6 @@ function isScrolledIntoView(element) {
|
|
|
9455
9047
|
return elemBottom <= viewportBottom && elemTop >= viewportTop;
|
|
9456
9048
|
}
|
|
9457
9049
|
|
|
9458
|
-
// EXTERNAL MODULE: fs (ignored)
|
|
9459
|
-
var fs_ignored_ = __webpack_require__(133);
|
|
9460
9050
|
// EXTERNAL MODULE: ./node_modules/path-browserify/index.js
|
|
9461
9051
|
var path_browserify = __webpack_require__(975);
|
|
9462
9052
|
;// ./src/v1/fileManager/normalFuncs.mjs
|
|
@@ -9865,8 +9455,6 @@ function renameFilePadNumbers(dirPath, totalDigits = 3, extensions = []) {
|
|
|
9865
9455
|
);
|
|
9866
9456
|
}
|
|
9867
9457
|
|
|
9868
|
-
// EXTERNAL MODULE: fs/promises (ignored)
|
|
9869
|
-
var promises_ignored_ = __webpack_require__(370);
|
|
9870
9458
|
;// ./src/v1/fileManager/asyncFuncs.mjs
|
|
9871
9459
|
|
|
9872
9460
|
|
|
@@ -10223,7 +9811,7 @@ class TinyDragger {
|
|
|
10223
9811
|
if (
|
|
10224
9812
|
options.vibration !== undefined &&
|
|
10225
9813
|
options.vibration !== false &&
|
|
10226
|
-
!
|
|
9814
|
+
!isJsonObject(options.vibration)
|
|
10227
9815
|
)
|
|
10228
9816
|
throw new Error('The "vibration" option must be an object or false.');
|
|
10229
9817
|
|
|
@@ -10267,7 +9855,7 @@ class TinyDragger {
|
|
|
10267
9855
|
const vibrationTemplate = { start: false, end: false, collide: false, move: false };
|
|
10268
9856
|
this.#vibration = Object.assign(
|
|
10269
9857
|
vibrationTemplate,
|
|
10270
|
-
|
|
9858
|
+
isJsonObject(options.vibration) ? options.vibration : {},
|
|
10271
9859
|
);
|
|
10272
9860
|
|
|
10273
9861
|
if (typeof options.classDragging === 'string') this.#classDragging = options.classDragging;
|
|
@@ -11106,574 +10694,1092 @@ class TinyDragger {
|
|
|
11106
10694
|
|
|
11107
10695
|
/* harmony default export */ const libs_TinyDragger = ((/* unused pure expression or super */ null && (TinyDragger)));
|
|
11108
10696
|
|
|
11109
|
-
;// ./src/v1/libs/
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
/**
|
|
11114
|
-
* Represents the dimensions of a DOM element.
|
|
11115
|
-
*
|
|
11116
|
-
* @typedef {Object} NodeSizes
|
|
11117
|
-
* @property {number} height - The height of the element in pixels.
|
|
11118
|
-
* @property {number} width - The width of the element in pixels.
|
|
11119
|
-
*/
|
|
11120
|
-
|
|
10697
|
+
;// ./src/v1/libs/TinyEvents.mjs
|
|
11121
10698
|
/**
|
|
11122
|
-
* A
|
|
11123
|
-
*
|
|
11124
|
-
* @callback NodeSizesEvent
|
|
11125
|
-
* @param {Element} elem - The DOM element whose size is being tracked.
|
|
11126
|
-
* @param {{ old: NodeSizes, now: NodeSizes }} sizes - The old and new size measurements of the element.
|
|
11127
|
-
* @param {{ old: number, now: number }} elemAmount - The number of matching elements before and after the update.
|
|
11128
|
-
* @returns {NodeSizes|undefined} A modified NodeSizes object to override the default measurement, or undefined to use the original.
|
|
11129
|
-
*/
|
|
11130
|
-
|
|
11131
|
-
/**
|
|
11132
|
-
* A generic scroll-related event listener callback function.
|
|
10699
|
+
* A generic event listener callback function.
|
|
11133
10700
|
*
|
|
11134
|
-
* @callback
|
|
11135
|
-
* @param {any} payload - The data payload passed when the
|
|
10701
|
+
* @callback handler
|
|
10702
|
+
* @param {...any} payload - The data payload passed when the event is triggered.
|
|
11136
10703
|
* @returns {void}
|
|
11137
10704
|
*/
|
|
11138
10705
|
|
|
11139
10706
|
/**
|
|
11140
|
-
*
|
|
10707
|
+
* TinyEvents provides a minimalistic event emitter system similar to Node.js's EventEmitter,
|
|
10708
|
+
* enabling components to subscribe to, emit, and manage events and their listeners.
|
|
11141
10709
|
*
|
|
11142
|
-
*
|
|
11143
|
-
*
|
|
11144
|
-
*
|
|
10710
|
+
* Features include:
|
|
10711
|
+
* - Adding/removing event listeners (`on`, `off`, `offAll`, `offAllTypes`)
|
|
10712
|
+
* - One-time listeners (`once`)
|
|
10713
|
+
* - Emitting events (`emit`)
|
|
10714
|
+
* - Listener inspection and limits (`listenerCount`, `listeners`, `eventNames`)
|
|
10715
|
+
* - Maximum listener control (`setMaxListeners`, `getMaxListeners`)
|
|
11145
10716
|
*
|
|
11146
|
-
*
|
|
11147
|
-
*
|
|
11148
|
-
* - Supports automatic scrolling to the bottom unless the user scrolls away
|
|
11149
|
-
* - Observes DOM mutations and resizes, and adapts scroll position accordingly
|
|
11150
|
-
* - Emits scroll-related events such as 'onScrollBoundary', 'onAutoScroll', and 'onScrollPause'
|
|
11151
|
-
* - Includes customizable scroll correction filters for layout shift mitigation
|
|
11152
|
-
* - Handles media element load events (e.g. `<img>`, `<iframe>`, `<video>`) to prevent sudden scroll jumps
|
|
10717
|
+
* This class is useful for lightweight, dependency-free publish/subscribe event handling
|
|
10718
|
+
* within modular JavaScript applications.
|
|
11153
10719
|
*
|
|
11154
|
-
*
|
|
10720
|
+
* @class
|
|
11155
10721
|
*/
|
|
11156
|
-
class
|
|
11157
|
-
|
|
10722
|
+
class TinyEvents {
|
|
10723
|
+
/** @type {Map<string, { handler: handler; config: { once: boolean } }[]>} */
|
|
10724
|
+
#listeners = new Map();
|
|
11158
10725
|
|
|
11159
|
-
/** @type {
|
|
11160
|
-
#
|
|
11161
|
-
/** @type {WeakMap<Element, NodeSizes>} */
|
|
11162
|
-
#newSizes = new WeakMap();
|
|
10726
|
+
/** @type {number} */
|
|
10727
|
+
#maxListeners = 10;
|
|
11163
10728
|
|
|
11164
|
-
/** @type {
|
|
11165
|
-
#
|
|
11166
|
-
/** @type {WeakMap<Element, boolean>} */
|
|
11167
|
-
#oldVisibles = new WeakMap();
|
|
11168
|
-
|
|
11169
|
-
/** @type {WeakMap<Element, boolean>} */
|
|
11170
|
-
#newVisiblesByTime = new WeakMap();
|
|
11171
|
-
/** @type {WeakMap<Element, boolean>} */
|
|
11172
|
-
#oldVisiblesByTime = new WeakMap();
|
|
11173
|
-
|
|
11174
|
-
/** @type {Record<string, ScrollListenersFunc[]>} */
|
|
11175
|
-
#scrollListeners = {};
|
|
11176
|
-
|
|
11177
|
-
/** @type {ResizeObserver|null} */
|
|
11178
|
-
#resizeObserver = null;
|
|
11179
|
-
/** @type {MutationObserver|null} */
|
|
11180
|
-
#mutationObserver = null;
|
|
11181
|
-
|
|
11182
|
-
/** @type {Set<string>} */
|
|
11183
|
-
#loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
|
|
11184
|
-
|
|
11185
|
-
/** @type {null|EventListenerOrEventListenerObject} */
|
|
11186
|
-
#handler = null;
|
|
11187
|
-
|
|
11188
|
-
#isAtBottom = false;
|
|
11189
|
-
#isAtTop = false;
|
|
11190
|
-
#isAtCustomTop = false;
|
|
11191
|
-
#isAtCustomBottom = false;
|
|
11192
|
-
|
|
11193
|
-
#querySelector = '';
|
|
11194
|
-
#useWindow = false;
|
|
11195
|
-
#destroyed = false;
|
|
11196
|
-
#scrollPaused = false;
|
|
11197
|
-
#autoScrollBottom = false;
|
|
11198
|
-
#observeMutations = false;
|
|
11199
|
-
#preserveScrollOnLayoutShift = false;
|
|
11200
|
-
#debounceTime = 0;
|
|
11201
|
-
#elemAmount = 0;
|
|
11202
|
-
#elemOldAmount = 0;
|
|
11203
|
-
#lastKnownScrollBottomOffset = 0;
|
|
11204
|
-
#extraScrollBoundary = 0;
|
|
11205
|
-
|
|
11206
|
-
/** @type {Set<string>} */
|
|
11207
|
-
#attributeFilter;
|
|
11208
|
-
|
|
11209
|
-
/** @type {Element} */
|
|
11210
|
-
#target;
|
|
11211
|
-
|
|
11212
|
-
/** @type {Set<NodeSizesEvent>} */
|
|
11213
|
-
#sizeFilter = new Set();
|
|
10729
|
+
/** @type {boolean} */
|
|
10730
|
+
#throwMaxListeners = false;
|
|
11214
10731
|
|
|
11215
10732
|
/**
|
|
11216
|
-
*
|
|
11217
|
-
* automatic scroll behaviors, layout shift correction, and visibility tracking.
|
|
10733
|
+
* Enables or disables throwing an error when the maximum number of listeners is exceeded.
|
|
11218
10734
|
*
|
|
11219
|
-
* @param {
|
|
11220
|
-
* @param {Object} [options={}] - Optional settings to configure scroll behavior.
|
|
11221
|
-
* @param {number} [options.extraScrollBoundary=0] - Extra margin in pixels to extend scroll boundary detection.
|
|
11222
|
-
* @param {boolean} [options.autoScrollBottom=true] - Whether to auto-scroll to bottom on layout updates.
|
|
11223
|
-
* @param {boolean} [options.observeMutations=true] - Enables MutationObserver to detect DOM changes.
|
|
11224
|
-
* @param {boolean} [options.preserveScrollOnLayoutShift=true] - Prevents scroll jumps when layout changes.
|
|
11225
|
-
* @param {number} [options.debounceTime=100] - Debounce time in milliseconds for scroll events.
|
|
11226
|
-
* @param {string|null} [options.querySelector=null] - Optional CSS selector to filter observed child nodes.
|
|
11227
|
-
* @param {string[]|Set<string>|null} [options.attributeFilter=['class', 'style', 'src', 'data-*', 'height', 'width']]
|
|
11228
|
-
* - Which attributes to observe for changes.
|
|
10735
|
+
* @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
|
|
11229
10736
|
*/
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
autoScrollBottom = true,
|
|
11235
|
-
observeMutations = true,
|
|
11236
|
-
preserveScrollOnLayoutShift = true,
|
|
11237
|
-
debounceTime = 100,
|
|
11238
|
-
querySelector = null,
|
|
11239
|
-
attributeFilter = ['class', 'style', 'src', 'data-*', 'height', 'width'],
|
|
11240
|
-
} = {},
|
|
11241
|
-
) {
|
|
11242
|
-
// === target ===
|
|
11243
|
-
if (!(target instanceof Element || target === window))
|
|
11244
|
-
throw new TypeError(
|
|
11245
|
-
`TinySmartScroller: 'target' must be a DOM Element or 'window', but got ${typeof target}`,
|
|
11246
|
-
);
|
|
11247
|
-
// === extraScrollBoundary ===
|
|
11248
|
-
if (typeof extraScrollBoundary !== 'number' || Number.isNaN(extraScrollBoundary))
|
|
11249
|
-
throw new TypeError(
|
|
11250
|
-
`TinySmartScroller: 'extraScrollBoundary' must be a valid number, received ${extraScrollBoundary}`,
|
|
11251
|
-
);
|
|
11252
|
-
// === autoScrollBottom ===
|
|
11253
|
-
if (typeof autoScrollBottom !== 'boolean')
|
|
11254
|
-
throw new TypeError(
|
|
11255
|
-
`TinySmartScroller: 'autoScrollBottom' must be a boolean, received ${typeof autoScrollBottom}`,
|
|
11256
|
-
);
|
|
11257
|
-
// === observeMutations ===
|
|
11258
|
-
if (typeof observeMutations !== 'boolean')
|
|
11259
|
-
throw new TypeError(
|
|
11260
|
-
`TinySmartScroller: 'observeMutations' must be a boolean, received ${typeof observeMutations}`,
|
|
11261
|
-
);
|
|
11262
|
-
// === preserveScrollOnLayoutShift ===
|
|
11263
|
-
if (typeof preserveScrollOnLayoutShift !== 'boolean')
|
|
11264
|
-
throw new TypeError(
|
|
11265
|
-
`TinySmartScroller: 'preserveScrollOnLayoutShift' must be a boolean, received ${typeof preserveScrollOnLayoutShift}`,
|
|
11266
|
-
);
|
|
11267
|
-
// === debounceTime ===
|
|
11268
|
-
if (typeof debounceTime !== 'number' || debounceTime < 0 || Number.isNaN(debounceTime))
|
|
11269
|
-
throw new TypeError(
|
|
11270
|
-
`TinySmartScroller: 'debounceTime' must be a non-negative number, received ${debounceTime}`,
|
|
11271
|
-
);
|
|
11272
|
-
// === querySelector ===
|
|
11273
|
-
if (querySelector !== null && typeof querySelector !== 'string')
|
|
11274
|
-
throw new TypeError(
|
|
11275
|
-
`TinySmartScroller: 'querySelector' must be a string or null, received ${typeof querySelector}`,
|
|
11276
|
-
);
|
|
11277
|
-
// === attributeFilter ===
|
|
11278
|
-
const isValidAttrList =
|
|
11279
|
-
attributeFilter === null || Array.isArray(attributeFilter) || attributeFilter instanceof Set;
|
|
11280
|
-
if (!isValidAttrList)
|
|
11281
|
-
throw new TypeError(
|
|
11282
|
-
`TinySmartScroller: 'attributeFilter' must be an array, Set, or null. Got ${typeof attributeFilter}`,
|
|
11283
|
-
);
|
|
11284
|
-
|
|
11285
|
-
// Start values
|
|
11286
|
-
this.#target = target instanceof Window ? document.documentElement : target;
|
|
11287
|
-
this.#useWindow = target instanceof Window;
|
|
11288
|
-
this.#autoScrollBottom = autoScrollBottom;
|
|
11289
|
-
this.#observeMutations = observeMutations;
|
|
11290
|
-
this.#preserveScrollOnLayoutShift = preserveScrollOnLayoutShift;
|
|
11291
|
-
this.#debounceTime = debounceTime;
|
|
11292
|
-
this.#extraScrollBoundary = extraScrollBoundary;
|
|
11293
|
-
this.#querySelector = querySelector || '';
|
|
11294
|
-
this.#attributeFilter = new Set(attributeFilter || undefined);
|
|
11295
|
-
|
|
11296
|
-
// Bind scroll
|
|
11297
|
-
/** @type {NodeJS.Timeout} */
|
|
11298
|
-
let timeout;
|
|
11299
|
-
this.#handler = () => {
|
|
11300
|
-
this._scrollDataUpdater();
|
|
11301
|
-
clearTimeout(timeout);
|
|
11302
|
-
timeout = setTimeout(() => this._onScroll(), this.#debounceTime);
|
|
11303
|
-
};
|
|
11304
|
-
(this.#useWindow ? window : this.#target).addEventListener('scroll', this.#handler, {
|
|
11305
|
-
passive: true,
|
|
11306
|
-
});
|
|
11307
|
-
|
|
11308
|
-
// Mutations
|
|
11309
|
-
if (this.#observeMutations) {
|
|
11310
|
-
this._observeMutations();
|
|
11311
|
-
this._observeResizes(this.#target.children);
|
|
11312
|
-
}
|
|
11313
|
-
|
|
11314
|
-
this._scrollDataUpdater();
|
|
10737
|
+
setThrowOnMaxListeners(shouldThrow) {
|
|
10738
|
+
if (typeof shouldThrow !== 'boolean')
|
|
10739
|
+
throw new TypeError('setThrowOnMaxListeners(value): value must be a boolean');
|
|
10740
|
+
this.#throwMaxListeners = shouldThrow;
|
|
11315
10741
|
}
|
|
11316
10742
|
|
|
11317
10743
|
/**
|
|
11318
|
-
*
|
|
10744
|
+
* Checks whether an error will be thrown when the max listener limit is exceeded.
|
|
11319
10745
|
*
|
|
11320
|
-
* @
|
|
11321
|
-
* @returns {NodeSizesEvent} A function that compares previous and current height, returning height delta.
|
|
10746
|
+
* @returns {boolean} True if an error will be thrown, false if only a warning is shown.
|
|
11322
10747
|
*/
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
throw new TypeError('getSimpleOnHeight(filter): filter must be an array of tag names');
|
|
11326
|
-
return (elem, sizes, amounts) => {
|
|
11327
|
-
if ((filter.length > 0 && !filter.includes(elem.tagName)) || amounts.now !== amounts.old)
|
|
11328
|
-
return;
|
|
11329
|
-
const oldSize = sizes.old;
|
|
11330
|
-
const newSize = sizes.now;
|
|
11331
|
-
const height = newSize.height - oldSize.height;
|
|
11332
|
-
return { height, width: 0 };
|
|
11333
|
-
};
|
|
10748
|
+
getThrowOnMaxListeners() {
|
|
10749
|
+
return this.#throwMaxListeners;
|
|
11334
10750
|
}
|
|
11335
10751
|
|
|
11336
|
-
|
|
11337
|
-
* Adds a height difference callback to the size filter system.
|
|
11338
|
-
*
|
|
11339
|
-
* @param {string[]} filter - List of tag names to allow.
|
|
11340
|
-
* @returns {NodeSizesEvent} The added size difference callback.
|
|
11341
|
-
*/
|
|
11342
|
-
addSimpleOnHeight(filter) {
|
|
11343
|
-
if (!Array.isArray(filter))
|
|
11344
|
-
throw new TypeError('addSimpleOnHeight(filter): filter must be an array of tag names');
|
|
11345
|
-
const result = this.getSimpleOnHeight(filter);
|
|
11346
|
-
this.onSize(result);
|
|
11347
|
-
return result;
|
|
11348
|
-
}
|
|
10752
|
+
///////////////////////////////////////////////////
|
|
11349
10753
|
|
|
11350
10754
|
/**
|
|
11351
|
-
*
|
|
10755
|
+
* Internal method to prepend a listener with options.
|
|
11352
10756
|
*
|
|
11353
|
-
* @
|
|
10757
|
+
* @param {string} event - Event name.
|
|
10758
|
+
* @param {handler} handler - The callback function.
|
|
10759
|
+
* @param {Object} [settings={}] - Optional settings.
|
|
10760
|
+
* @param {boolean} [settings.once=false] - If the listener should be executed once.
|
|
11354
10761
|
*/
|
|
11355
|
-
|
|
11356
|
-
|
|
10762
|
+
#prepend(event, handler, { once = false } = {}) {
|
|
10763
|
+
let eventData = this.#listeners.get(event);
|
|
10764
|
+
if (!Array.isArray(eventData)) {
|
|
10765
|
+
eventData = [];
|
|
10766
|
+
this.#listeners.set(event, eventData);
|
|
10767
|
+
}
|
|
10768
|
+
eventData.unshift({ handler, config: { once } });
|
|
10769
|
+
|
|
10770
|
+
const max = this.#maxListeners;
|
|
10771
|
+
if (max > 0 && eventData.length > max) {
|
|
10772
|
+
const warnMessage =
|
|
10773
|
+
`Possible memory leak detected. ${eventData.length} "${event}" listeners added. ` +
|
|
10774
|
+
`Use setMaxListeners() to increase limit.`;
|
|
10775
|
+
if (!this.#throwMaxListeners) console.warn(warnMessage);
|
|
10776
|
+
else throw new Error(warnMessage);
|
|
10777
|
+
}
|
|
11357
10778
|
}
|
|
11358
10779
|
|
|
11359
10780
|
/**
|
|
11360
|
-
* Adds a
|
|
10781
|
+
* Adds a listener to the beginning of the listeners array for the specified event.
|
|
11361
10782
|
*
|
|
11362
|
-
* @param {string}
|
|
10783
|
+
* @param {string} event - Event name.
|
|
10784
|
+
* @param {handler} handler - The callback function.
|
|
11363
10785
|
*/
|
|
11364
|
-
|
|
11365
|
-
if (typeof
|
|
11366
|
-
|
|
10786
|
+
prependListener(event, handler) {
|
|
10787
|
+
if (typeof event !== 'string')
|
|
10788
|
+
throw new TypeError('prepend(event, handler): event name must be a string');
|
|
10789
|
+
if (typeof handler !== 'function')
|
|
10790
|
+
throw new TypeError('prepend(event, handler): handler must be a function');
|
|
10791
|
+
this.#prepend(event, handler);
|
|
11367
10792
|
}
|
|
11368
10793
|
|
|
11369
10794
|
/**
|
|
11370
|
-
*
|
|
10795
|
+
* Adds a one-time listener to the beginning of the listeners array for the specified event.
|
|
11371
10796
|
*
|
|
11372
|
-
* @param {string}
|
|
10797
|
+
* @param {string} event - Event name.
|
|
10798
|
+
* @param {handler} handler - The callback function.
|
|
10799
|
+
* @returns {handler} - The wrapped handler used internally.
|
|
11373
10800
|
*/
|
|
11374
|
-
|
|
11375
|
-
if (typeof
|
|
11376
|
-
|
|
10801
|
+
prependListenerOnce(event, handler) {
|
|
10802
|
+
if (typeof event !== 'string')
|
|
10803
|
+
throw new TypeError('prependOnceListener(event, handler): event name must be a string');
|
|
10804
|
+
if (typeof handler !== 'function')
|
|
10805
|
+
throw new TypeError('prependOnceListener(event, handler): handler must be a function');
|
|
10806
|
+
|
|
10807
|
+
/** @type {handler} */
|
|
10808
|
+
const wrapped = (...args) => {
|
|
10809
|
+
this.off(event, wrapped);
|
|
10810
|
+
handler(...args);
|
|
10811
|
+
};
|
|
10812
|
+
|
|
10813
|
+
this.#prepend(event, wrapped, { once: true });
|
|
10814
|
+
return wrapped;
|
|
11377
10815
|
}
|
|
11378
10816
|
|
|
10817
|
+
////////////////////////////////////////////////////////////
|
|
10818
|
+
|
|
11379
10819
|
/**
|
|
11380
|
-
*
|
|
10820
|
+
* Adds a event listener.
|
|
11381
10821
|
*
|
|
11382
|
-
* @param {string}
|
|
11383
|
-
* @
|
|
10822
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
10823
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
10824
|
+
* @param {Object} [settings={}] - Optional settings.
|
|
10825
|
+
* @param {boolean} [settings.once=false] - This is a once event.
|
|
11384
10826
|
*/
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
10827
|
+
#on(event, handler, { once = false } = {}) {
|
|
10828
|
+
let eventData = this.#listeners.get(event);
|
|
10829
|
+
if (!Array.isArray(eventData)) {
|
|
10830
|
+
eventData = [];
|
|
10831
|
+
this.#listeners.set(event, eventData);
|
|
10832
|
+
}
|
|
10833
|
+
eventData.push({ handler, config: { once } });
|
|
10834
|
+
// Warn if listener count exceeds the max allowed
|
|
10835
|
+
const max = this.#maxListeners;
|
|
10836
|
+
if (max > 0 && eventData.length > max) {
|
|
10837
|
+
const warnMessage =
|
|
10838
|
+
`Possible memory leak detected. ${eventData.length} "${event}" listeners added. ` +
|
|
10839
|
+
`Use setMaxListeners() to increase limit.`;
|
|
10840
|
+
if (!this.#throwMaxListeners) console.warn(warnMessage);
|
|
10841
|
+
else throw new Error(warnMessage);
|
|
10842
|
+
}
|
|
11388
10843
|
}
|
|
11389
10844
|
|
|
11390
10845
|
/**
|
|
11391
|
-
*
|
|
10846
|
+
* Adds a event listener.
|
|
11392
10847
|
*
|
|
11393
|
-
* @param {
|
|
11394
|
-
* @
|
|
10848
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
10849
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
11395
10850
|
*/
|
|
11396
|
-
|
|
11397
|
-
if (typeof
|
|
11398
|
-
throw new TypeError('
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
this.#
|
|
11402
|
-
this.#loadTags.add('IFRAME');
|
|
11403
|
-
this.#loadTags.add('VIDEO');
|
|
10851
|
+
on(event, handler) {
|
|
10852
|
+
if (typeof event !== 'string')
|
|
10853
|
+
throw new TypeError('on(event, handler): event name must be a string');
|
|
10854
|
+
if (typeof handler !== 'function')
|
|
10855
|
+
throw new TypeError('on(event, handler): handler must be a function');
|
|
10856
|
+
return this.#on(event, handler);
|
|
11404
10857
|
}
|
|
11405
10858
|
|
|
11406
10859
|
/**
|
|
11407
|
-
*
|
|
10860
|
+
* Registers an event listener that runs only once, then is removed.
|
|
11408
10861
|
*
|
|
11409
|
-
* @
|
|
10862
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
10863
|
+
* @param {handler} handler - The callback function to run on event.
|
|
10864
|
+
* @returns {handler} - The wrapped version of the handler.
|
|
11410
10865
|
*/
|
|
11411
|
-
|
|
11412
|
-
|
|
10866
|
+
once(event, handler) {
|
|
10867
|
+
if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
|
|
10868
|
+
if (typeof handler !== 'function')
|
|
10869
|
+
throw new TypeError('once(event, handler): handler must be a function');
|
|
10870
|
+
|
|
10871
|
+
/** @type {handler} */
|
|
10872
|
+
const wrapped = (e) => {
|
|
10873
|
+
this.off(event, wrapped);
|
|
10874
|
+
if (typeof handler === 'function') handler(e);
|
|
10875
|
+
};
|
|
10876
|
+
this.#on(event, wrapped, { once: true });
|
|
10877
|
+
return wrapped;
|
|
11413
10878
|
}
|
|
11414
10879
|
|
|
11415
10880
|
/**
|
|
11416
|
-
* Adds
|
|
10881
|
+
* Adds a event listener.
|
|
11417
10882
|
*
|
|
11418
|
-
* @param {string}
|
|
10883
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
10884
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
11419
10885
|
*/
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
throw new TypeError('addAttributeFilter(attr): attr must be a string');
|
|
11423
|
-
this.#attributeFilter.add(attr);
|
|
10886
|
+
appendListener(event, handler) {
|
|
10887
|
+
return this.on(event, handler);
|
|
11424
10888
|
}
|
|
11425
10889
|
|
|
11426
10890
|
/**
|
|
11427
|
-
*
|
|
10891
|
+
* Registers an event listener that runs only once, then is removed.
|
|
11428
10892
|
*
|
|
11429
|
-
* @param {string}
|
|
10893
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
10894
|
+
* @param {handler} handler - The callback function to run on event.
|
|
10895
|
+
* @returns {handler} - The wrapped version of the handler.
|
|
11430
10896
|
*/
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
throw new TypeError('removeAttributeFilter(attr): attr must be a string');
|
|
11434
|
-
this.#attributeFilter.delete(attr);
|
|
10897
|
+
appendListenerOnce(event, handler) {
|
|
10898
|
+
return this.once(event, handler);
|
|
11435
10899
|
}
|
|
11436
10900
|
|
|
10901
|
+
///////////////////////////////////////////////
|
|
10902
|
+
|
|
11437
10903
|
/**
|
|
11438
|
-
*
|
|
10904
|
+
* Removes a previously registered event listener.
|
|
11439
10905
|
*
|
|
11440
|
-
* @param {string}
|
|
11441
|
-
* @
|
|
10906
|
+
* @param {string} event - The name of the event to remove the handler from.
|
|
10907
|
+
* @param {handler} handler - The specific callback function to remove.
|
|
11442
10908
|
*/
|
|
11443
|
-
|
|
11444
|
-
if (typeof
|
|
11445
|
-
throw new TypeError('
|
|
11446
|
-
|
|
10909
|
+
off(event, handler) {
|
|
10910
|
+
if (typeof event !== 'string')
|
|
10911
|
+
throw new TypeError('off(event, handler): event name must be a string');
|
|
10912
|
+
if (typeof handler !== 'function')
|
|
10913
|
+
throw new TypeError('off(event, handler): handler must be a function');
|
|
10914
|
+
|
|
10915
|
+
const listeners = this.#listeners.get(event);
|
|
10916
|
+
if (!Array.isArray(listeners)) return;
|
|
10917
|
+
|
|
10918
|
+
const index = listeners.findIndex((listener) => listener.handler === handler);
|
|
10919
|
+
if (index !== -1) listeners.splice(index, 1);
|
|
10920
|
+
|
|
10921
|
+
// Optionally clean up empty arrays (optional)
|
|
10922
|
+
if (listeners.length === 0) this.#listeners.delete(event);
|
|
11447
10923
|
}
|
|
11448
10924
|
|
|
11449
10925
|
/**
|
|
11450
|
-
*
|
|
11451
|
-
* 'class', 'style', 'src', 'data-*', 'height', and 'width'.
|
|
10926
|
+
* Removes all event listeners of a specific type from the element.
|
|
11452
10927
|
*
|
|
11453
|
-
* @param {
|
|
11454
|
-
* @throws {TypeError} If `addDefault` is not a boolean.
|
|
10928
|
+
* @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
|
|
11455
10929
|
*/
|
|
11456
|
-
|
|
11457
|
-
if (typeof
|
|
11458
|
-
|
|
11459
|
-
this.#attributeFilter.clear();
|
|
11460
|
-
if (!addDefault) return;
|
|
11461
|
-
['class', 'style', 'src', 'data-*', 'height', 'width'].forEach((attr) =>
|
|
11462
|
-
this.#attributeFilter.add(attr),
|
|
11463
|
-
);
|
|
10930
|
+
offAll(event) {
|
|
10931
|
+
if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
|
|
10932
|
+
this.#listeners.delete(event);
|
|
11464
10933
|
}
|
|
11465
10934
|
|
|
11466
10935
|
/**
|
|
11467
|
-
*
|
|
11468
|
-
*
|
|
11469
|
-
* @param {NodeSizesEvent} handler - Function that compares old and new sizes.
|
|
10936
|
+
* Removes all event listeners of all types from the element.
|
|
11470
10937
|
*/
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
if (typeof handler !== 'function')
|
|
11474
|
-
throw new TypeError('onSize(handler): handler must be a function');
|
|
11475
|
-
this.#sizeFilter.add(handler);
|
|
10938
|
+
offAllTypes() {
|
|
10939
|
+
this.#listeners.clear();
|
|
11476
10940
|
}
|
|
11477
10941
|
|
|
10942
|
+
/////////////////////////////////////////////
|
|
10943
|
+
|
|
11478
10944
|
/**
|
|
11479
|
-
*
|
|
10945
|
+
* Returns the number of listeners for a given event.
|
|
11480
10946
|
*
|
|
11481
|
-
* @param {
|
|
10947
|
+
* @param {string} event - The name of the event.
|
|
10948
|
+
* @returns {number} Number of listeners for the event.
|
|
11482
10949
|
*/
|
|
11483
|
-
|
|
11484
|
-
if (
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
this.#
|
|
10950
|
+
listenerCount(event) {
|
|
10951
|
+
if (typeof event !== 'string')
|
|
10952
|
+
throw new TypeError('listenerCount(event): event name must be a string');
|
|
10953
|
+
|
|
10954
|
+
const listeners = this.#listeners.get(event);
|
|
10955
|
+
return Array.isArray(listeners) ? listeners.length : 0;
|
|
11488
10956
|
}
|
|
11489
10957
|
|
|
11490
10958
|
/**
|
|
11491
|
-
*
|
|
10959
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
11492
10960
|
*
|
|
11493
|
-
* @param {string} event -
|
|
11494
|
-
* @
|
|
10961
|
+
* @param {string} event - The name of the event.
|
|
10962
|
+
* @returns {handler[]} Array of listener functions.
|
|
11495
10963
|
*/
|
|
11496
|
-
|
|
11497
|
-
if (this.#destroyed) return;
|
|
10964
|
+
listeners(event) {
|
|
11498
10965
|
if (typeof event !== 'string')
|
|
11499
|
-
throw new TypeError('
|
|
11500
|
-
if (typeof handler !== 'function')
|
|
11501
|
-
throw new TypeError('on(event, handler): handler must be a function');
|
|
10966
|
+
throw new TypeError('listeners(event): event name must be a string');
|
|
11502
10967
|
|
|
11503
|
-
|
|
11504
|
-
|
|
10968
|
+
const listeners = this.#listeners.get(event);
|
|
10969
|
+
return Array.isArray(listeners)
|
|
10970
|
+
? [...listeners]
|
|
10971
|
+
.filter((listener) => !listener.config.once)
|
|
10972
|
+
.map((listener) => listener.handler)
|
|
10973
|
+
: [];
|
|
11505
10974
|
}
|
|
11506
10975
|
|
|
11507
10976
|
/**
|
|
11508
|
-
*
|
|
10977
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
11509
10978
|
*
|
|
11510
|
-
* @param {string} event - The name of the event
|
|
11511
|
-
* @
|
|
10979
|
+
* @param {string} event - The name of the event.
|
|
10980
|
+
* @returns {handler[]} Array of listener functions.
|
|
11512
10981
|
*/
|
|
11513
|
-
|
|
11514
|
-
if (this.#destroyed) return;
|
|
10982
|
+
onceListeners(event) {
|
|
11515
10983
|
if (typeof event !== 'string')
|
|
11516
|
-
throw new TypeError('
|
|
11517
|
-
if (typeof handler !== 'function')
|
|
11518
|
-
throw new TypeError('off(event, handler): handler must be a function');
|
|
10984
|
+
throw new TypeError('onceListeners(event): event name must be a string');
|
|
11519
10985
|
|
|
11520
|
-
const listeners = this.#
|
|
11521
|
-
|
|
10986
|
+
const listeners = this.#listeners.get(event);
|
|
10987
|
+
return Array.isArray(listeners)
|
|
10988
|
+
? [...listeners]
|
|
10989
|
+
.filter((listener) => listener.config.once)
|
|
10990
|
+
.map((listener) => listener.handler)
|
|
10991
|
+
: [];
|
|
10992
|
+
}
|
|
11522
10993
|
|
|
11523
|
-
|
|
11524
|
-
|
|
10994
|
+
/**
|
|
10995
|
+
* Returns a copy of the internal listeners array for the specified event,
|
|
10996
|
+
* including wrapper functions like those used by `.once()`.
|
|
10997
|
+
* @param {string | symbol} event - The event name.
|
|
10998
|
+
* @returns {handler[]} An array of raw listener functions.
|
|
10999
|
+
*/
|
|
11000
|
+
allListeners(event) {
|
|
11001
|
+
if (typeof event !== 'string')
|
|
11002
|
+
throw new TypeError('allListeners(event): event name must be a string');
|
|
11003
|
+
const listeners = this.#listeners.get(event);
|
|
11004
|
+
return Array.isArray(listeners) ? [...listeners].map((listener) => listener.handler) : [];
|
|
11005
|
+
}
|
|
11525
11006
|
|
|
11526
|
-
|
|
11527
|
-
|
|
11007
|
+
/**
|
|
11008
|
+
* Returns an array of event names for which there are registered listeners.
|
|
11009
|
+
*
|
|
11010
|
+
* @returns {string[]} Array of registered event names.
|
|
11011
|
+
*/
|
|
11012
|
+
eventNames() {
|
|
11013
|
+
return [...this.#listeners.keys()];
|
|
11528
11014
|
}
|
|
11529
11015
|
|
|
11530
11016
|
/**
|
|
11531
|
-
*
|
|
11017
|
+
* Emits an event, triggering all registered handlers for that event.
|
|
11532
11018
|
*
|
|
11533
|
-
* @
|
|
11019
|
+
* @param {string} event - The event name to emit.
|
|
11020
|
+
* @param {...any} payload - Optional data to pass to each handler.
|
|
11021
|
+
* @returns {boolean} True if any listeners were called, false otherwise.
|
|
11534
11022
|
*/
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
const oldIsVisible = this.#newVisibles.get(target) ?? false;
|
|
11539
|
-
this.#oldVisibles.set(target, oldIsVisible);
|
|
11023
|
+
emit(event, ...payload) {
|
|
11024
|
+
if (typeof event !== 'string')
|
|
11025
|
+
throw new TypeError('emit(event, data): event name must be a string');
|
|
11540
11026
|
|
|
11541
|
-
|
|
11542
|
-
|
|
11027
|
+
const listeners = this.#listeners.get(event);
|
|
11028
|
+
if (!Array.isArray(listeners) || listeners.length === 0) return false;
|
|
11543
11029
|
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
return
|
|
11030
|
+
// Call all listeners with the provided data
|
|
11031
|
+
listeners.forEach((listener) => listener.handler(...payload));
|
|
11032
|
+
return true;
|
|
11547
11033
|
}
|
|
11548
11034
|
|
|
11035
|
+
///////////////////////////////////
|
|
11036
|
+
|
|
11549
11037
|
/**
|
|
11550
|
-
*
|
|
11038
|
+
* Sets the maximum number of listeners per event before a warning is shown.
|
|
11551
11039
|
*
|
|
11552
|
-
* @param {
|
|
11553
|
-
* @param {*} [payload] - Optional event data payload.
|
|
11040
|
+
* @param {number} n - The maximum number of listeners.
|
|
11554
11041
|
*/
|
|
11555
|
-
|
|
11556
|
-
if (
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
(this.#scrollListeners[event] || []).forEach((fn) => fn(payload));
|
|
11042
|
+
setMaxListeners(n) {
|
|
11043
|
+
if (!Number.isInteger(n) || n < 0)
|
|
11044
|
+
throw new TypeError('setMaxListeners(n): n must be a non-negative integer');
|
|
11045
|
+
this.#maxListeners = n;
|
|
11560
11046
|
}
|
|
11561
11047
|
|
|
11562
11048
|
/**
|
|
11563
|
-
*
|
|
11049
|
+
* Gets the maximum number of listeners allowed per event.
|
|
11050
|
+
*
|
|
11051
|
+
* @returns {number} The maximum number of listeners.
|
|
11564
11052
|
*/
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
const el = this.#target;
|
|
11570
|
-
const scrollTop = el.scrollTop;
|
|
11571
|
-
const scrollHeight = el.scrollHeight;
|
|
11572
|
-
const clientHeight = el.clientHeight;
|
|
11053
|
+
getMaxListeners() {
|
|
11054
|
+
return this.#maxListeners;
|
|
11055
|
+
}
|
|
11056
|
+
}
|
|
11573
11057
|
|
|
11574
|
-
|
|
11575
|
-
const scrollResult = { scrollTop, scrollHeight, clientHeight };
|
|
11576
|
-
let atResult = null;
|
|
11577
|
-
let atCustomResult = null;
|
|
11058
|
+
/* harmony default export */ const libs_TinyEvents = (TinyEvents);
|
|
11578
11059
|
|
|
11579
|
-
|
|
11580
|
-
const atBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
11060
|
+
;// ./src/v1/libs/TinySmartScroller.mjs
|
|
11581
11061
|
|
|
11582
|
-
const atCustomTop = scrollTop <= 0 + this.#extraScrollBoundary;
|
|
11583
|
-
const atCustomBottom = scrollTop + clientHeight >= scrollHeight - 1 - this.#extraScrollBoundary;
|
|
11584
11062
|
|
|
11585
|
-
// Scroll results
|
|
11586
|
-
if (atTop && atBottom) atResult = 'all';
|
|
11587
|
-
else if (atTop) atResult = 'top';
|
|
11588
|
-
else if (atBottom) atResult = 'bottom';
|
|
11589
11063
|
|
|
11590
|
-
if (atCustomTop && atCustomBottom) atCustomResult = 'all';
|
|
11591
|
-
else if (atCustomTop) atCustomResult = 'top';
|
|
11592
|
-
else if (atCustomBottom) atCustomResult = 'bottom';
|
|
11593
11064
|
|
|
11594
|
-
|
|
11595
|
-
|
|
11065
|
+
/**
|
|
11066
|
+
* Represents the dimensions of a DOM element.
|
|
11067
|
+
*
|
|
11068
|
+
* @typedef {Object} NodeSizes
|
|
11069
|
+
* @property {number} height - The height of the element in pixels.
|
|
11070
|
+
* @property {number} width - The width of the element in pixels.
|
|
11071
|
+
*/
|
|
11596
11072
|
|
|
11597
|
-
|
|
11598
|
-
|
|
11073
|
+
/**
|
|
11074
|
+
* A callback function that receives node size change data and optionally returns a modified NodeSizes object.
|
|
11075
|
+
*
|
|
11076
|
+
* @callback NodeSizesEvent
|
|
11077
|
+
* @param {Element} elem - The DOM element whose size is being tracked.
|
|
11078
|
+
* @param {{ old: NodeSizes, now: NodeSizes }} sizes - The old and new size measurements of the element.
|
|
11079
|
+
* @param {{ old: number, now: number }} elemAmount - The number of matching elements before and after the update.
|
|
11080
|
+
* @returns {NodeSizes|undefined} A modified NodeSizes object to override the default measurement, or undefined to use the original.
|
|
11081
|
+
*/
|
|
11599
11082
|
|
|
11600
|
-
|
|
11083
|
+
/**
|
|
11084
|
+
* A generic scroll-related event listener callback function.
|
|
11085
|
+
*
|
|
11086
|
+
* @callback ScrollListenersFunc
|
|
11087
|
+
* @param {any} payload - The data payload passed when the scroll event is triggered. The type may vary depending on the event.
|
|
11088
|
+
* @returns {void}
|
|
11089
|
+
*/
|
|
11601
11090
|
|
|
11602
|
-
|
|
11091
|
+
/**
|
|
11092
|
+
* TinySmartScroller is a utility class designed to enhance and manage scroll behaviors within containers or the window.
|
|
11093
|
+
*
|
|
11094
|
+
* It enables advanced scroll monitoring, auto-scrolling to bottom, preserving scroll position during DOM changes,
|
|
11095
|
+
* and detecting visibility changes of elements. This is particularly useful for dynamic UIs like chat applications,
|
|
11096
|
+
* feed viewers, or live content containers.
|
|
11097
|
+
*
|
|
11098
|
+
* Features:
|
|
11099
|
+
* - Detects when the scroll reaches the top, bottom, or custom boundaries
|
|
11100
|
+
* - Supports automatic scrolling to the bottom unless the user scrolls away
|
|
11101
|
+
* - Observes DOM mutations and resizes, and adapts scroll position accordingly
|
|
11102
|
+
* - Emits scroll-related events such as 'onScrollBoundary', 'onAutoScroll', and 'onScrollPause'
|
|
11103
|
+
* - Includes customizable scroll correction filters for layout shift mitigation
|
|
11104
|
+
* - Handles media element load events (e.g. `<img>`, `<iframe>`, `<video>`) to prevent sudden scroll jumps
|
|
11105
|
+
*
|
|
11106
|
+
* This class is **not framework-dependent** and works with vanilla DOM elements and the window object.
|
|
11107
|
+
*/
|
|
11108
|
+
class TinySmartScroller {
|
|
11109
|
+
static Utils = { ...collision_namespaceObject, TinyHtml: libs_TinyHtml };
|
|
11603
11110
|
|
|
11604
|
-
|
|
11605
|
-
this._emit('onScrollBoundary', { status: atResult, ...scrollResult, scrollCache });
|
|
11606
|
-
this._emit('onExtraScrollBoundary', { status: atCustomResult, ...scrollResult, scrollCache });
|
|
11111
|
+
#events = new libs_TinyEvents();
|
|
11607
11112
|
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11113
|
+
/**
|
|
11114
|
+
* Enables or disables throwing an error when the maximum number of listeners is exceeded.
|
|
11115
|
+
*
|
|
11116
|
+
* @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
|
|
11117
|
+
*/
|
|
11118
|
+
setThrowOnMaxListeners(shouldThrow) {
|
|
11119
|
+
return this.#events.setThrowOnMaxListeners(shouldThrow);
|
|
11613
11120
|
}
|
|
11614
11121
|
|
|
11615
11122
|
/**
|
|
11616
|
-
*
|
|
11123
|
+
* Checks whether an error will be thrown when the max listener limit is exceeded.
|
|
11617
11124
|
*
|
|
11618
|
-
* @
|
|
11125
|
+
* @returns {boolean} True if an error will be thrown, false if only a warning is shown.
|
|
11619
11126
|
*/
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
if (!Array.isArray(targets))
|
|
11624
|
-
throw new TypeError('_fixScroll: targets must be an array of Elements');
|
|
11625
|
-
|
|
11626
|
-
// Get Scroll data
|
|
11627
|
-
const prevScrollHeight = this.#target.scrollHeight;
|
|
11628
|
-
const prevScrollTop = this.#target.scrollTop;
|
|
11629
|
-
const prevBottomOffset =
|
|
11630
|
-
this.#target.scrollHeight - this.#target.scrollTop - this.#target.clientHeight;
|
|
11127
|
+
getThrowOnMaxListeners() {
|
|
11128
|
+
return this.#events.getThrowOnMaxListeners();
|
|
11129
|
+
}
|
|
11631
11130
|
|
|
11632
|
-
|
|
11633
|
-
const newScrollHeight = this.#target.scrollHeight;
|
|
11634
|
-
const heightDelta = newScrollHeight - prevScrollHeight;
|
|
11131
|
+
/////////////////////////////////////////////////////////////
|
|
11635
11132
|
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
const sizes = fn(
|
|
11646
|
-
target,
|
|
11647
|
-
{ old: tgOs, now: tgNs },
|
|
11648
|
-
{ old: this.#elemOldAmount, now: this.#elemAmount },
|
|
11649
|
-
);
|
|
11133
|
+
/**
|
|
11134
|
+
* Adds a listener to the beginning of the listeners array for the specified event.
|
|
11135
|
+
*
|
|
11136
|
+
* @param {string} event - Event name.
|
|
11137
|
+
* @param {ScrollListenersFunc} handler - The callback function.
|
|
11138
|
+
*/
|
|
11139
|
+
prependListener(event, handler) {
|
|
11140
|
+
return this.#events.prependListener(event, handler);
|
|
11141
|
+
}
|
|
11650
11142
|
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
11143
|
+
/**
|
|
11144
|
+
* Adds a one-time listener to the beginning of the listeners array for the specified event.
|
|
11145
|
+
*
|
|
11146
|
+
* @param {string} event - Event name.
|
|
11147
|
+
* @param {ScrollListenersFunc} handler - The callback function.
|
|
11148
|
+
* @returns {ScrollListenersFunc} - The wrapped handler used internally.
|
|
11149
|
+
*/
|
|
11150
|
+
prependListenerOnce(event, handler) {
|
|
11151
|
+
return this.#events.prependListenerOnce(event, handler);
|
|
11152
|
+
}
|
|
11661
11153
|
|
|
11662
|
-
|
|
11663
|
-
if (typeof scrollSize.height !== 'number' && scrollSize.height < 0)
|
|
11664
|
-
throw new Error('_fixScroll: invalid scrollSize.height value');
|
|
11665
|
-
if (typeof scrollSize.width !== 'number' && scrollSize.width < 0)
|
|
11666
|
-
throw new Error('_fixScroll: invalid scrollSize.width value');
|
|
11154
|
+
//////////////////////////////////////////////////////////////////////
|
|
11667
11155
|
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11156
|
+
/**
|
|
11157
|
+
* Adds a event listener.
|
|
11158
|
+
*
|
|
11159
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
11160
|
+
* @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
|
|
11161
|
+
*/
|
|
11162
|
+
appendListener(event, handler) {
|
|
11163
|
+
return this.#events.appendListener(event, handler);
|
|
11164
|
+
}
|
|
11674
11165
|
|
|
11675
|
-
|
|
11676
|
-
|
|
11166
|
+
/**
|
|
11167
|
+
* Registers an event listener that runs only once, then is removed.
|
|
11168
|
+
*
|
|
11169
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
11170
|
+
* @param {ScrollListenersFunc} handler - The callback function to run on event.
|
|
11171
|
+
* @returns {ScrollListenersFunc} - The wrapped version of the handler.
|
|
11172
|
+
*/
|
|
11173
|
+
appendListenerOnce(event, handler) {
|
|
11174
|
+
return this.#events.appendListenerOnce(event, handler);
|
|
11175
|
+
}
|
|
11176
|
+
|
|
11177
|
+
/**
|
|
11178
|
+
* Adds a event listener.
|
|
11179
|
+
*
|
|
11180
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
11181
|
+
* @param {ScrollListenersFunc} handler - Callback function to be called when event fires.
|
|
11182
|
+
*/
|
|
11183
|
+
on(event, handler) {
|
|
11184
|
+
return this.#events.on(event, handler);
|
|
11185
|
+
}
|
|
11186
|
+
|
|
11187
|
+
/**
|
|
11188
|
+
* Registers an event listener that runs only once, then is removed.
|
|
11189
|
+
*
|
|
11190
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
11191
|
+
* @param {ScrollListenersFunc} handler - The callback function to run on event.
|
|
11192
|
+
* @returns {ScrollListenersFunc} - The wrapped version of the handler.
|
|
11193
|
+
*/
|
|
11194
|
+
once(event, handler) {
|
|
11195
|
+
return this.#events.once(event, handler);
|
|
11196
|
+
}
|
|
11197
|
+
|
|
11198
|
+
////////////////////////////////////////////////////////////////////
|
|
11199
|
+
|
|
11200
|
+
/**
|
|
11201
|
+
* Removes a previously registered event listener.
|
|
11202
|
+
*
|
|
11203
|
+
* @param {string} event - The name of the event to remove the handler from.
|
|
11204
|
+
* @param {ScrollListenersFunc} handler - The specific callback function to remove.
|
|
11205
|
+
*/
|
|
11206
|
+
off(event, handler) {
|
|
11207
|
+
return this.#events.off(event, handler);
|
|
11208
|
+
}
|
|
11209
|
+
|
|
11210
|
+
/**
|
|
11211
|
+
* Removes all event listeners of a specific type from the element.
|
|
11212
|
+
*
|
|
11213
|
+
* @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
|
|
11214
|
+
*/
|
|
11215
|
+
offAll(event) {
|
|
11216
|
+
return this.#events.offAll(event);
|
|
11217
|
+
}
|
|
11218
|
+
|
|
11219
|
+
/**
|
|
11220
|
+
* Removes all event listeners of all types from the element.
|
|
11221
|
+
*/
|
|
11222
|
+
offAllTypes() {
|
|
11223
|
+
return this.#events.offAllTypes();
|
|
11224
|
+
}
|
|
11225
|
+
|
|
11226
|
+
////////////////////////////////////////////////////////////
|
|
11227
|
+
|
|
11228
|
+
/**
|
|
11229
|
+
* Returns the number of listeners for a given event.
|
|
11230
|
+
*
|
|
11231
|
+
* @param {string} event - The name of the event.
|
|
11232
|
+
* @returns {number} Number of listeners for the event.
|
|
11233
|
+
*/
|
|
11234
|
+
listenerCount(event) {
|
|
11235
|
+
return this.#events.listenerCount(event);
|
|
11236
|
+
}
|
|
11237
|
+
|
|
11238
|
+
/**
|
|
11239
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
11240
|
+
*
|
|
11241
|
+
* @param {string} event - The name of the event.
|
|
11242
|
+
* @returns {ScrollListenersFunc[]} Array of listener functions.
|
|
11243
|
+
*/
|
|
11244
|
+
listeners(event) {
|
|
11245
|
+
return this.#events.listeners(event);
|
|
11246
|
+
}
|
|
11247
|
+
|
|
11248
|
+
/**
|
|
11249
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
11250
|
+
*
|
|
11251
|
+
* @param {string} event - The name of the event.
|
|
11252
|
+
* @returns {ScrollListenersFunc[]} Array of listener functions.
|
|
11253
|
+
*/
|
|
11254
|
+
onceListeners(event) {
|
|
11255
|
+
return this.#events.onceListeners(event);
|
|
11256
|
+
}
|
|
11257
|
+
|
|
11258
|
+
/**
|
|
11259
|
+
* Returns a copy of the internal listeners array for the specified event,
|
|
11260
|
+
* including wrapper functions like those used by `.once()`.
|
|
11261
|
+
* @param {string | symbol} event - The event name.
|
|
11262
|
+
* @returns {ScrollListenersFunc[]} An array of raw listener functions.
|
|
11263
|
+
*/
|
|
11264
|
+
allListeners(event) {
|
|
11265
|
+
return this.#events.allListeners(event);
|
|
11266
|
+
}
|
|
11267
|
+
|
|
11268
|
+
/**
|
|
11269
|
+
* Returns an array of event names for which there are registered listeners.
|
|
11270
|
+
*
|
|
11271
|
+
* @returns {string[]} Array of registered event names.
|
|
11272
|
+
*/
|
|
11273
|
+
eventNames() {
|
|
11274
|
+
return this.#events.eventNames();
|
|
11275
|
+
}
|
|
11276
|
+
|
|
11277
|
+
//////////////////////////////////////////////////////
|
|
11278
|
+
|
|
11279
|
+
/**
|
|
11280
|
+
* Emits an event, triggering all registered handlers for that event.
|
|
11281
|
+
*
|
|
11282
|
+
* @param {string} event - The event name to emit.
|
|
11283
|
+
* @param {...any} payload - Optional data to pass to each handler.
|
|
11284
|
+
* @returns {boolean} True if any listeners were called, false otherwise.
|
|
11285
|
+
*/
|
|
11286
|
+
emit(event, ...payload) {
|
|
11287
|
+
return this.#events.emit(event, ...payload);
|
|
11288
|
+
}
|
|
11289
|
+
|
|
11290
|
+
/**
|
|
11291
|
+
* Sets the maximum number of listeners per event before a warning is shown.
|
|
11292
|
+
*
|
|
11293
|
+
* @param {number} n - The maximum number of listeners.
|
|
11294
|
+
*/
|
|
11295
|
+
setMaxListeners(n) {
|
|
11296
|
+
return this.#events.setMaxListeners(n);
|
|
11297
|
+
}
|
|
11298
|
+
|
|
11299
|
+
/**
|
|
11300
|
+
* Gets the maximum number of listeners allowed per event.
|
|
11301
|
+
*
|
|
11302
|
+
* @returns {number} The maximum number of listeners.
|
|
11303
|
+
*/
|
|
11304
|
+
getMaxListeners() {
|
|
11305
|
+
return this.#events.getMaxListeners();
|
|
11306
|
+
}
|
|
11307
|
+
|
|
11308
|
+
///////////////////////////////////////////////////
|
|
11309
|
+
|
|
11310
|
+
/** @type {WeakMap<Element, NodeSizes>} */
|
|
11311
|
+
#oldSizes = new WeakMap();
|
|
11312
|
+
/** @type {WeakMap<Element, NodeSizes>} */
|
|
11313
|
+
#newSizes = new WeakMap();
|
|
11314
|
+
|
|
11315
|
+
/** @type {WeakMap<Element, boolean>} */
|
|
11316
|
+
#newVisibles = new WeakMap();
|
|
11317
|
+
/** @type {WeakMap<Element, boolean>} */
|
|
11318
|
+
#oldVisibles = new WeakMap();
|
|
11319
|
+
|
|
11320
|
+
/** @type {WeakMap<Element, boolean>} */
|
|
11321
|
+
#newVisiblesByTime = new WeakMap();
|
|
11322
|
+
/** @type {WeakMap<Element, boolean>} */
|
|
11323
|
+
#oldVisiblesByTime = new WeakMap();
|
|
11324
|
+
|
|
11325
|
+
/** @type {ResizeObserver|null} */
|
|
11326
|
+
#resizeObserver = null;
|
|
11327
|
+
/** @type {MutationObserver|null} */
|
|
11328
|
+
#mutationObserver = null;
|
|
11329
|
+
|
|
11330
|
+
/** @type {Set<string>} */
|
|
11331
|
+
#loadTags = new Set(['IMG', 'IFRAME', 'VIDEO']);
|
|
11332
|
+
|
|
11333
|
+
/** @type {null|EventListenerOrEventListenerObject} */
|
|
11334
|
+
#handler = null;
|
|
11335
|
+
|
|
11336
|
+
#isAtBottom = false;
|
|
11337
|
+
#isAtTop = false;
|
|
11338
|
+
#isAtCustomTop = false;
|
|
11339
|
+
#isAtCustomBottom = false;
|
|
11340
|
+
|
|
11341
|
+
#querySelector = '';
|
|
11342
|
+
#useWindow = false;
|
|
11343
|
+
#destroyed = false;
|
|
11344
|
+
#scrollPaused = false;
|
|
11345
|
+
#autoScrollBottom = false;
|
|
11346
|
+
#observeMutations = false;
|
|
11347
|
+
#preserveScrollOnLayoutShift = false;
|
|
11348
|
+
#debounceTime = 0;
|
|
11349
|
+
#elemAmount = 0;
|
|
11350
|
+
#elemOldAmount = 0;
|
|
11351
|
+
#lastKnownScrollBottomOffset = 0;
|
|
11352
|
+
#extraScrollBoundary = 0;
|
|
11353
|
+
|
|
11354
|
+
/** @type {Set<string>} */
|
|
11355
|
+
#attributeFilter;
|
|
11356
|
+
|
|
11357
|
+
/** @type {Element} */
|
|
11358
|
+
#target;
|
|
11359
|
+
|
|
11360
|
+
/** @type {Set<NodeSizesEvent>} */
|
|
11361
|
+
#sizeFilter = new Set();
|
|
11362
|
+
|
|
11363
|
+
/**
|
|
11364
|
+
* Creates a new instance of TinySmartScroller, attaching scroll and resize observers to manage
|
|
11365
|
+
* automatic scroll behaviors, layout shift correction, and visibility tracking.
|
|
11366
|
+
*
|
|
11367
|
+
* @param {Element|Window} target - The scroll container to monitor. Can be an element or `window`.
|
|
11368
|
+
* @param {Object} [options={}] - Optional settings to configure scroll behavior.
|
|
11369
|
+
* @param {number} [options.extraScrollBoundary=0] - Extra margin in pixels to extend scroll boundary detection.
|
|
11370
|
+
* @param {boolean} [options.autoScrollBottom=true] - Whether to auto-scroll to bottom on layout updates.
|
|
11371
|
+
* @param {boolean} [options.observeMutations=true] - Enables MutationObserver to detect DOM changes.
|
|
11372
|
+
* @param {boolean} [options.preserveScrollOnLayoutShift=true] - Prevents scroll jumps when layout changes.
|
|
11373
|
+
* @param {number} [options.debounceTime=100] - Debounce time in milliseconds for scroll events.
|
|
11374
|
+
* @param {string|null} [options.querySelector=null] - Optional CSS selector to filter observed child nodes.
|
|
11375
|
+
* @param {string[]|Set<string>|null} [options.attributeFilter=['class', 'style', 'src', 'data-*', 'height', 'width']]
|
|
11376
|
+
* - Which attributes to observe for changes.
|
|
11377
|
+
*/
|
|
11378
|
+
constructor(
|
|
11379
|
+
target,
|
|
11380
|
+
{
|
|
11381
|
+
extraScrollBoundary = 0,
|
|
11382
|
+
autoScrollBottom = true,
|
|
11383
|
+
observeMutations = true,
|
|
11384
|
+
preserveScrollOnLayoutShift = true,
|
|
11385
|
+
debounceTime = 100,
|
|
11386
|
+
querySelector = null,
|
|
11387
|
+
attributeFilter = ['class', 'style', 'src', 'data-*', 'height', 'width'],
|
|
11388
|
+
} = {},
|
|
11389
|
+
) {
|
|
11390
|
+
// === target ===
|
|
11391
|
+
if (!(target instanceof Element || target === window))
|
|
11392
|
+
throw new TypeError(
|
|
11393
|
+
`TinySmartScroller: 'target' must be a DOM Element or 'window', but got ${typeof target}`,
|
|
11394
|
+
);
|
|
11395
|
+
// === extraScrollBoundary ===
|
|
11396
|
+
if (typeof extraScrollBoundary !== 'number' || Number.isNaN(extraScrollBoundary))
|
|
11397
|
+
throw new TypeError(
|
|
11398
|
+
`TinySmartScroller: 'extraScrollBoundary' must be a valid number, received ${extraScrollBoundary}`,
|
|
11399
|
+
);
|
|
11400
|
+
// === autoScrollBottom ===
|
|
11401
|
+
if (typeof autoScrollBottom !== 'boolean')
|
|
11402
|
+
throw new TypeError(
|
|
11403
|
+
`TinySmartScroller: 'autoScrollBottom' must be a boolean, received ${typeof autoScrollBottom}`,
|
|
11404
|
+
);
|
|
11405
|
+
// === observeMutations ===
|
|
11406
|
+
if (typeof observeMutations !== 'boolean')
|
|
11407
|
+
throw new TypeError(
|
|
11408
|
+
`TinySmartScroller: 'observeMutations' must be a boolean, received ${typeof observeMutations}`,
|
|
11409
|
+
);
|
|
11410
|
+
// === preserveScrollOnLayoutShift ===
|
|
11411
|
+
if (typeof preserveScrollOnLayoutShift !== 'boolean')
|
|
11412
|
+
throw new TypeError(
|
|
11413
|
+
`TinySmartScroller: 'preserveScrollOnLayoutShift' must be a boolean, received ${typeof preserveScrollOnLayoutShift}`,
|
|
11414
|
+
);
|
|
11415
|
+
// === debounceTime ===
|
|
11416
|
+
if (typeof debounceTime !== 'number' || debounceTime < 0 || Number.isNaN(debounceTime))
|
|
11417
|
+
throw new TypeError(
|
|
11418
|
+
`TinySmartScroller: 'debounceTime' must be a non-negative number, received ${debounceTime}`,
|
|
11419
|
+
);
|
|
11420
|
+
// === querySelector ===
|
|
11421
|
+
if (querySelector !== null && typeof querySelector !== 'string')
|
|
11422
|
+
throw new TypeError(
|
|
11423
|
+
`TinySmartScroller: 'querySelector' must be a string or null, received ${typeof querySelector}`,
|
|
11424
|
+
);
|
|
11425
|
+
// === attributeFilter ===
|
|
11426
|
+
const isValidAttrList =
|
|
11427
|
+
attributeFilter === null || Array.isArray(attributeFilter) || attributeFilter instanceof Set;
|
|
11428
|
+
if (!isValidAttrList)
|
|
11429
|
+
throw new TypeError(
|
|
11430
|
+
`TinySmartScroller: 'attributeFilter' must be an array, Set, or null. Got ${typeof attributeFilter}`,
|
|
11431
|
+
);
|
|
11432
|
+
|
|
11433
|
+
// Start values
|
|
11434
|
+
this.#target = target instanceof Window ? document.documentElement : target;
|
|
11435
|
+
this.#useWindow = target instanceof Window;
|
|
11436
|
+
this.#autoScrollBottom = autoScrollBottom;
|
|
11437
|
+
this.#observeMutations = observeMutations;
|
|
11438
|
+
this.#preserveScrollOnLayoutShift = preserveScrollOnLayoutShift;
|
|
11439
|
+
this.#debounceTime = debounceTime;
|
|
11440
|
+
this.#extraScrollBoundary = extraScrollBoundary;
|
|
11441
|
+
this.#querySelector = querySelector || '';
|
|
11442
|
+
this.#attributeFilter = new Set(attributeFilter || undefined);
|
|
11443
|
+
|
|
11444
|
+
// Bind scroll
|
|
11445
|
+
/** @type {NodeJS.Timeout} */
|
|
11446
|
+
let timeout;
|
|
11447
|
+
this.#handler = () => {
|
|
11448
|
+
this._scrollDataUpdater();
|
|
11449
|
+
clearTimeout(timeout);
|
|
11450
|
+
timeout = setTimeout(() => this._onScroll(), this.#debounceTime);
|
|
11451
|
+
};
|
|
11452
|
+
(this.#useWindow ? window : this.#target).addEventListener('scroll', this.#handler, {
|
|
11453
|
+
passive: true,
|
|
11454
|
+
});
|
|
11455
|
+
|
|
11456
|
+
// Mutations
|
|
11457
|
+
if (this.#observeMutations) {
|
|
11458
|
+
this._observeMutations();
|
|
11459
|
+
this._observeResizes(this.#target.children);
|
|
11460
|
+
}
|
|
11461
|
+
|
|
11462
|
+
this._scrollDataUpdater();
|
|
11463
|
+
}
|
|
11464
|
+
|
|
11465
|
+
/**
|
|
11466
|
+
* Returns a size difference callback that only reacts when height changes, filtered by tag name.
|
|
11467
|
+
*
|
|
11468
|
+
* @param {string[]} filter - List of tag names to allow. If empty, all tags are accepted.
|
|
11469
|
+
* @returns {NodeSizesEvent} A function that compares previous and current height, returning height delta.
|
|
11470
|
+
*/
|
|
11471
|
+
getSimpleOnHeight(filter = []) {
|
|
11472
|
+
if (!Array.isArray(filter))
|
|
11473
|
+
throw new TypeError('getSimpleOnHeight(filter): filter must be an array of tag names');
|
|
11474
|
+
return (elem, sizes, amounts) => {
|
|
11475
|
+
if ((filter.length > 0 && !filter.includes(elem.tagName)) || amounts.now !== amounts.old)
|
|
11476
|
+
return;
|
|
11477
|
+
const oldSize = sizes.old;
|
|
11478
|
+
const newSize = sizes.now;
|
|
11479
|
+
const height = newSize.height - oldSize.height;
|
|
11480
|
+
return { height, width: 0 };
|
|
11481
|
+
};
|
|
11482
|
+
}
|
|
11483
|
+
|
|
11484
|
+
/**
|
|
11485
|
+
* Adds a height difference callback to the size filter system.
|
|
11486
|
+
*
|
|
11487
|
+
* @param {string[]} filter - List of tag names to allow.
|
|
11488
|
+
* @returns {NodeSizesEvent} The added size difference callback.
|
|
11489
|
+
*/
|
|
11490
|
+
addSimpleOnHeight(filter) {
|
|
11491
|
+
if (!Array.isArray(filter))
|
|
11492
|
+
throw new TypeError('addSimpleOnHeight(filter): filter must be an array of tag names');
|
|
11493
|
+
const result = this.getSimpleOnHeight(filter);
|
|
11494
|
+
this.onSize(result);
|
|
11495
|
+
return result;
|
|
11496
|
+
}
|
|
11497
|
+
|
|
11498
|
+
/**
|
|
11499
|
+
* Returns a list of all currently tracked load tags.
|
|
11500
|
+
*
|
|
11501
|
+
* @returns {string[]} Array of tag names.
|
|
11502
|
+
*/
|
|
11503
|
+
getLoadTags() {
|
|
11504
|
+
return Array.from(this.#loadTags);
|
|
11505
|
+
}
|
|
11506
|
+
|
|
11507
|
+
/**
|
|
11508
|
+
* Adds a new tag to the set of load tags.
|
|
11509
|
+
*
|
|
11510
|
+
* @param {string} tag - The tag name to add (e.g., 'IMG').
|
|
11511
|
+
*/
|
|
11512
|
+
addLoadTag(tag) {
|
|
11513
|
+
if (typeof tag !== 'string') throw new TypeError('addLoadTag(tag): tag must be a string');
|
|
11514
|
+
this.#loadTags.add(tag.toUpperCase());
|
|
11515
|
+
}
|
|
11516
|
+
|
|
11517
|
+
/**
|
|
11518
|
+
* Removes a tag from the set of load tags.
|
|
11519
|
+
*
|
|
11520
|
+
* @param {string} tag - The tag name to remove.
|
|
11521
|
+
*/
|
|
11522
|
+
removeLoadTag(tag) {
|
|
11523
|
+
if (typeof tag !== 'string') throw new TypeError('removeLoadTag(tag): tag must be a string');
|
|
11524
|
+
this.#loadTags.delete(tag.toUpperCase());
|
|
11525
|
+
}
|
|
11526
|
+
|
|
11527
|
+
/**
|
|
11528
|
+
* Checks whether a tag is tracked as a load tag.
|
|
11529
|
+
*
|
|
11530
|
+
* @param {string} tag - The tag name to check.
|
|
11531
|
+
* @returns {boolean} True if the tag is being tracked.
|
|
11532
|
+
*/
|
|
11533
|
+
hasLoadTag(tag) {
|
|
11534
|
+
if (typeof tag !== 'string') throw new TypeError('hasLoadTag(tag): tag must be a string');
|
|
11535
|
+
return this.#loadTags.has(tag.toUpperCase());
|
|
11536
|
+
}
|
|
11537
|
+
|
|
11538
|
+
/**
|
|
11539
|
+
* Clears the set of load tags. If `addDefault` is true, it will reset to the default tags: 'IMG', 'IFRAME', and 'VIDEO'.
|
|
11540
|
+
*
|
|
11541
|
+
* @param {boolean} [addDefault=false] - Whether to restore the default tags after clearing.
|
|
11542
|
+
* @throws {TypeError} If `addDefault` is not a boolean.
|
|
11543
|
+
*/
|
|
11544
|
+
resetLoadTags(addDefault = false) {
|
|
11545
|
+
if (typeof addDefault !== 'boolean')
|
|
11546
|
+
throw new TypeError('resetLoadTags(addDefault): addDefault must be a boolean');
|
|
11547
|
+
this.#loadTags.clear();
|
|
11548
|
+
if (!addDefault) return;
|
|
11549
|
+
this.#loadTags.add('IMG');
|
|
11550
|
+
this.#loadTags.add('IFRAME');
|
|
11551
|
+
this.#loadTags.add('VIDEO');
|
|
11552
|
+
}
|
|
11553
|
+
|
|
11554
|
+
/**
|
|
11555
|
+
* Returns a list of all currently tracked attribute filters.
|
|
11556
|
+
*
|
|
11557
|
+
* @returns {string[]} Array of attribute names.
|
|
11558
|
+
*/
|
|
11559
|
+
getAttributeFilters() {
|
|
11560
|
+
return Array.from(this.#attributeFilter);
|
|
11561
|
+
}
|
|
11562
|
+
|
|
11563
|
+
/**
|
|
11564
|
+
* Adds an attribute to the filter list.
|
|
11565
|
+
*
|
|
11566
|
+
* @param {string} attr - The attribute name to add.
|
|
11567
|
+
*/
|
|
11568
|
+
addAttributeFilter(attr) {
|
|
11569
|
+
if (typeof attr !== 'string')
|
|
11570
|
+
throw new TypeError('addAttributeFilter(attr): attr must be a string');
|
|
11571
|
+
this.#attributeFilter.add(attr);
|
|
11572
|
+
}
|
|
11573
|
+
|
|
11574
|
+
/**
|
|
11575
|
+
* Removes an attribute from the filter list.
|
|
11576
|
+
*
|
|
11577
|
+
* @param {string} attr - The attribute name to remove.
|
|
11578
|
+
*/
|
|
11579
|
+
removeAttributeFilter(attr) {
|
|
11580
|
+
if (typeof attr !== 'string')
|
|
11581
|
+
throw new TypeError('removeAttributeFilter(attr): attr must be a string');
|
|
11582
|
+
this.#attributeFilter.delete(attr);
|
|
11583
|
+
}
|
|
11584
|
+
|
|
11585
|
+
/**
|
|
11586
|
+
* Checks whether a specific attribute is being filtered.
|
|
11587
|
+
*
|
|
11588
|
+
* @param {string} attr - The attribute name to check.
|
|
11589
|
+
* @returns {boolean} True if the attribute is being filtered.
|
|
11590
|
+
*/
|
|
11591
|
+
hasAttributeFilter(attr) {
|
|
11592
|
+
if (typeof attr !== 'string')
|
|
11593
|
+
throw new TypeError('hasAttributeFilter(attr): attr must be a string');
|
|
11594
|
+
return this.#attributeFilter.has(attr);
|
|
11595
|
+
}
|
|
11596
|
+
|
|
11597
|
+
/**
|
|
11598
|
+
* Clears the set of observed attribute filters. If `addDefault` is true, it will reset to the default attributes:
|
|
11599
|
+
* 'class', 'style', 'src', 'data-*', 'height', and 'width'.
|
|
11600
|
+
*
|
|
11601
|
+
* @param {boolean} [addDefault=false] - Whether to restore the default attribute filters after clearing.
|
|
11602
|
+
* @throws {TypeError} If `addDefault` is not a boolean.
|
|
11603
|
+
*/
|
|
11604
|
+
resetAttributeFilters(addDefault = false) {
|
|
11605
|
+
if (typeof addDefault !== 'boolean')
|
|
11606
|
+
throw new TypeError('resetAttributeFilters(addDefault): addDefault must be a boolean');
|
|
11607
|
+
this.#attributeFilter.clear();
|
|
11608
|
+
if (!addDefault) return;
|
|
11609
|
+
['class', 'style', 'src', 'data-*', 'height', 'width'].forEach((attr) =>
|
|
11610
|
+
this.#attributeFilter.add(attr),
|
|
11611
|
+
);
|
|
11612
|
+
}
|
|
11613
|
+
|
|
11614
|
+
/**
|
|
11615
|
+
* Registers a custom node size change handler to the internal size filter set.
|
|
11616
|
+
*
|
|
11617
|
+
* @param {NodeSizesEvent} handler - Function that compares old and new sizes.
|
|
11618
|
+
*/
|
|
11619
|
+
onSize(handler) {
|
|
11620
|
+
if (this.#destroyed) return;
|
|
11621
|
+
if (typeof handler !== 'function')
|
|
11622
|
+
throw new TypeError('onSize(handler): handler must be a function');
|
|
11623
|
+
this.#sizeFilter.add(handler);
|
|
11624
|
+
}
|
|
11625
|
+
|
|
11626
|
+
/**
|
|
11627
|
+
* Unregisters a previously registered size handler from the internal filter set.
|
|
11628
|
+
*
|
|
11629
|
+
* @param {NodeSizesEvent} handler - The handler function to remove.
|
|
11630
|
+
*/
|
|
11631
|
+
offSize(handler) {
|
|
11632
|
+
if (this.#destroyed) return;
|
|
11633
|
+
if (typeof handler !== 'function')
|
|
11634
|
+
throw new TypeError('offSize(handler): handler must be a function');
|
|
11635
|
+
this.#sizeFilter.delete(handler);
|
|
11636
|
+
}
|
|
11637
|
+
|
|
11638
|
+
/**
|
|
11639
|
+
* Checks which elements inside the target are currently visible and updates internal maps.
|
|
11640
|
+
*
|
|
11641
|
+
* @returns {Map<Element, { oldIsVisible: boolean; isVisible: boolean }>} Visibility comparison results.
|
|
11642
|
+
*/
|
|
11643
|
+
_scrollDataUpdater() {
|
|
11644
|
+
const results = new Map();
|
|
11645
|
+
this.#target.querySelectorAll(this.#querySelector || '*').forEach((target) => {
|
|
11646
|
+
const oldIsVisible = this.#newVisibles.get(target) ?? false;
|
|
11647
|
+
this.#oldVisibles.set(target, oldIsVisible);
|
|
11648
|
+
|
|
11649
|
+
const isVisible = libs_TinyHtml.isInContainer(this.#target, target);
|
|
11650
|
+
this.#newVisibles.set(target, isVisible);
|
|
11651
|
+
|
|
11652
|
+
results.set(target, { oldIsVisible, isVisible });
|
|
11653
|
+
});
|
|
11654
|
+
return results;
|
|
11655
|
+
}
|
|
11656
|
+
|
|
11657
|
+
/**
|
|
11658
|
+
* Emits a scroll-related event to all registered listeners.
|
|
11659
|
+
*
|
|
11660
|
+
* @param {string} event - Event name.
|
|
11661
|
+
* @param {*} [payload] - Optional event data payload.
|
|
11662
|
+
* @deprecated - Use emit() instead.
|
|
11663
|
+
*/
|
|
11664
|
+
_emit(event, payload) {
|
|
11665
|
+
this.emit(event, payload);
|
|
11666
|
+
}
|
|
11667
|
+
|
|
11668
|
+
/**
|
|
11669
|
+
* Handles scroll events, calculates position-related statuses, and emits appropriate events.
|
|
11670
|
+
*/
|
|
11671
|
+
_onScroll() {
|
|
11672
|
+
if (this.#destroyed) return;
|
|
11673
|
+
// Get values
|
|
11674
|
+
const scrollCache = this._scrollDataUpdater();
|
|
11675
|
+
const el = this.#target;
|
|
11676
|
+
const scrollTop = el.scrollTop;
|
|
11677
|
+
const scrollHeight = el.scrollHeight;
|
|
11678
|
+
const clientHeight = el.clientHeight;
|
|
11679
|
+
|
|
11680
|
+
// Prepare sroll values
|
|
11681
|
+
const scrollResult = { scrollTop, scrollHeight, clientHeight };
|
|
11682
|
+
let atResult = null;
|
|
11683
|
+
let atCustomResult = null;
|
|
11684
|
+
|
|
11685
|
+
const atTop = scrollTop === 0;
|
|
11686
|
+
const atBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
11687
|
+
|
|
11688
|
+
const atCustomTop = scrollTop <= 0 + this.#extraScrollBoundary;
|
|
11689
|
+
const atCustomBottom = scrollTop + clientHeight >= scrollHeight - 1 - this.#extraScrollBoundary;
|
|
11690
|
+
|
|
11691
|
+
// Scroll results
|
|
11692
|
+
if (atTop && atBottom) atResult = 'all';
|
|
11693
|
+
else if (atTop) atResult = 'top';
|
|
11694
|
+
else if (atBottom) atResult = 'bottom';
|
|
11695
|
+
|
|
11696
|
+
if (atCustomTop && atCustomBottom) atCustomResult = 'all';
|
|
11697
|
+
else if (atCustomTop) atCustomResult = 'top';
|
|
11698
|
+
else if (atCustomBottom) atCustomResult = 'bottom';
|
|
11699
|
+
|
|
11700
|
+
this.#isAtTop = atTop;
|
|
11701
|
+
this.#isAtBottom = atBottom;
|
|
11702
|
+
|
|
11703
|
+
this.#isAtCustomTop = atCustomTop;
|
|
11704
|
+
this.#isAtCustomBottom = atCustomBottom;
|
|
11705
|
+
|
|
11706
|
+
this.#scrollPaused = !(this.#autoScrollBottom && this.#isAtBottom);
|
|
11707
|
+
|
|
11708
|
+
this.#lastKnownScrollBottomOffset = scrollHeight - scrollTop - clientHeight;
|
|
11709
|
+
|
|
11710
|
+
// Send results
|
|
11711
|
+
this.emit('onScrollBoundary', { status: atResult, ...scrollResult, scrollCache });
|
|
11712
|
+
this.emit('onExtraScrollBoundary', { status: atCustomResult, ...scrollResult, scrollCache });
|
|
11713
|
+
|
|
11714
|
+
if (!this.#scrollPaused) {
|
|
11715
|
+
this.emit('onAutoScroll', { ...scrollResult, scrollCache });
|
|
11716
|
+
} else {
|
|
11717
|
+
this.emit('onScrollPause', { ...scrollResult, scrollCache });
|
|
11718
|
+
}
|
|
11719
|
+
}
|
|
11720
|
+
|
|
11721
|
+
/**
|
|
11722
|
+
* Attempts to correct the scroll position when layout shifts happen, preserving the user position if needed.
|
|
11723
|
+
*
|
|
11724
|
+
* @param {Element[]} [targets=[]] - List of elements involved in the size change.
|
|
11725
|
+
*/
|
|
11726
|
+
_fixScroll(targets = []) {
|
|
11727
|
+
if (this.#destroyed) return;
|
|
11728
|
+
// === Validation ===
|
|
11729
|
+
if (!Array.isArray(targets))
|
|
11730
|
+
throw new TypeError('_fixScroll: targets must be an array of Elements');
|
|
11731
|
+
|
|
11732
|
+
// Get Scroll data
|
|
11733
|
+
const prevScrollHeight = this.#target.scrollHeight;
|
|
11734
|
+
const prevScrollTop = this.#target.scrollTop;
|
|
11735
|
+
const prevBottomOffset =
|
|
11736
|
+
this.#target.scrollHeight - this.#target.scrollTop - this.#target.clientHeight;
|
|
11737
|
+
|
|
11738
|
+
// Get new size
|
|
11739
|
+
const newScrollHeight = this.#target.scrollHeight;
|
|
11740
|
+
const heightDelta = newScrollHeight - prevScrollHeight;
|
|
11741
|
+
|
|
11742
|
+
/** @type {() => NodeSizes} */
|
|
11743
|
+
const calculateScrollSize = () => {
|
|
11744
|
+
// Run size getter
|
|
11745
|
+
const scrollSize = { height: 0, width: 0 };
|
|
11746
|
+
for (const target of targets) {
|
|
11747
|
+
const tgOs = this.#oldSizes.get(target) || { height: 0, width: 0 };
|
|
11748
|
+
const tgNs = this.#newSizes.get(target) || { height: 0, width: 0 };
|
|
11749
|
+
this.#sizeFilter.forEach((fn) => {
|
|
11750
|
+
/** @type {NodeSizes| undefined} */
|
|
11751
|
+
const sizes = fn(
|
|
11752
|
+
target,
|
|
11753
|
+
{ old: tgOs, now: tgNs },
|
|
11754
|
+
{ old: this.#elemOldAmount, now: this.#elemAmount },
|
|
11755
|
+
);
|
|
11756
|
+
|
|
11757
|
+
// Fix size
|
|
11758
|
+
if (this.#newVisibles.get(target) || this.#newVisiblesByTime.get(target)) {
|
|
11759
|
+
if (typeof sizes !== 'undefined' && typeof sizes !== 'object')
|
|
11760
|
+
throw new Error('_fixScroll: size filter must return an object or undefined');
|
|
11761
|
+
if (typeof sizes === 'undefined') return;
|
|
11762
|
+
scrollSize.height = sizes.height;
|
|
11763
|
+
scrollSize.width = sizes.width;
|
|
11764
|
+
}
|
|
11765
|
+
});
|
|
11766
|
+
}
|
|
11767
|
+
|
|
11768
|
+
// Checker
|
|
11769
|
+
if (typeof scrollSize.height !== 'number' && scrollSize.height < 0)
|
|
11770
|
+
throw new Error('_fixScroll: invalid scrollSize.height value');
|
|
11771
|
+
if (typeof scrollSize.width !== 'number' && scrollSize.width < 0)
|
|
11772
|
+
throw new Error('_fixScroll: invalid scrollSize.width value');
|
|
11773
|
+
|
|
11774
|
+
if (scrollSize.height !== 0 || scrollSize.width !== 0) {
|
|
11775
|
+
for (const target of targets) {
|
|
11776
|
+
this.#newVisiblesByTime.set(target, this.#newVisibles.get(target) ?? false);
|
|
11777
|
+
this.#oldVisiblesByTime.set(target, this.#oldVisibles.get(target) ?? false);
|
|
11778
|
+
}
|
|
11779
|
+
}
|
|
11780
|
+
|
|
11781
|
+
return scrollSize;
|
|
11782
|
+
};
|
|
11677
11783
|
|
|
11678
11784
|
// Fix scroll size
|
|
11679
11785
|
if (
|
|
@@ -11691,398 +11797,1250 @@ class TinySmartScroller {
|
|
|
11691
11797
|
this.#target.scrollLeft = this.#target.scrollLeft + scrollSize.width;
|
|
11692
11798
|
}
|
|
11693
11799
|
|
|
11694
|
-
// Normal stuff
|
|
11695
|
-
else if (!this.#scrollPaused && this.#autoScrollBottom) {
|
|
11696
|
-
calculateScrollSize();
|
|
11697
|
-
this.scrollToBottom();
|
|
11698
|
-
} else if (!this.#autoScrollBottom && !this.#isAtBottom) {
|
|
11699
|
-
calculateScrollSize();
|
|
11700
|
-
this.#target.scrollTop =
|
|
11701
|
-
this.#target.scrollHeight - this.#target.clientHeight - prevBottomOffset;
|
|
11800
|
+
// Normal stuff
|
|
11801
|
+
else if (!this.#scrollPaused && this.#autoScrollBottom) {
|
|
11802
|
+
calculateScrollSize();
|
|
11803
|
+
this.scrollToBottom();
|
|
11804
|
+
} else if (!this.#autoScrollBottom && !this.#isAtBottom) {
|
|
11805
|
+
calculateScrollSize();
|
|
11806
|
+
this.#target.scrollTop =
|
|
11807
|
+
this.#target.scrollHeight - this.#target.clientHeight - prevBottomOffset;
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
/**
|
|
11811
|
+
* Sets up a MutationObserver to watch for DOM changes and react accordingly to maintain scroll consistency.
|
|
11812
|
+
*/
|
|
11813
|
+
_observeMutations() {
|
|
11814
|
+
this.#mutationObserver = new MutationObserver((mutations) => {
|
|
11815
|
+
if (this.#destroyed) return;
|
|
11816
|
+
this._scrollDataUpdater();
|
|
11817
|
+
this.#elemOldAmount = this.#elemAmount;
|
|
11818
|
+
this.#elemAmount = this.#target.childElementCount;
|
|
11819
|
+
|
|
11820
|
+
mutations.forEach((mutation) => {
|
|
11821
|
+
mutation.addedNodes.forEach((node) => {
|
|
11822
|
+
if (!(node instanceof Element) || node.nodeType !== 1) return;
|
|
11823
|
+
|
|
11824
|
+
this._observeResizes([node]);
|
|
11825
|
+
this._listenLoadEvents(node);
|
|
11826
|
+
|
|
11827
|
+
if (this.#querySelector) {
|
|
11828
|
+
const children = node.querySelectorAll(this.#querySelector);
|
|
11829
|
+
this._observeResizes(children);
|
|
11830
|
+
this._listenLoadEvents(children);
|
|
11831
|
+
}
|
|
11832
|
+
});
|
|
11833
|
+
});
|
|
11834
|
+
|
|
11835
|
+
this._fixScroll();
|
|
11836
|
+
});
|
|
11837
|
+
|
|
11838
|
+
// Install observer
|
|
11839
|
+
this.#mutationObserver.observe(this.#target, {
|
|
11840
|
+
childList: true,
|
|
11841
|
+
subtree: true,
|
|
11842
|
+
attributes: true,
|
|
11843
|
+
attributeFilter:
|
|
11844
|
+
this.#attributeFilter.size > 0 ? Array.from(this.#attributeFilter) : undefined,
|
|
11845
|
+
});
|
|
11846
|
+
}
|
|
11847
|
+
|
|
11848
|
+
/**
|
|
11849
|
+
* Adds a ResizeObserver to monitor elements' size changes and trigger layout adjustments.
|
|
11850
|
+
*
|
|
11851
|
+
* @param {NodeListOf<Element>|Element[]|HTMLCollection} elements - Elements to observe.
|
|
11852
|
+
*/
|
|
11853
|
+
_observeResizes(elements) {
|
|
11854
|
+
// Add resize observer
|
|
11855
|
+
if (!this.#resizeObserver) {
|
|
11856
|
+
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
11857
|
+
if (this.#destroyed) return;
|
|
11858
|
+
this._scrollDataUpdater();
|
|
11859
|
+
/** @type {Element[]} */
|
|
11860
|
+
const targets = [];
|
|
11861
|
+
for (const entry of entries) {
|
|
11862
|
+
// Target
|
|
11863
|
+
const target = entry.target;
|
|
11864
|
+
|
|
11865
|
+
// Update old size
|
|
11866
|
+
const oldSize = this.#newSizes.get(target);
|
|
11867
|
+
if (oldSize) this.#oldSizes.set(target, oldSize);
|
|
11868
|
+
|
|
11869
|
+
// Set new size
|
|
11870
|
+
const { width, height } = entry.contentRect;
|
|
11871
|
+
this.#newSizes.set(target, { width, height });
|
|
11872
|
+
targets.push(target);
|
|
11873
|
+
}
|
|
11874
|
+
|
|
11875
|
+
// Animation frame
|
|
11876
|
+
this._fixScroll(targets);
|
|
11877
|
+
});
|
|
11878
|
+
}
|
|
11879
|
+
|
|
11880
|
+
// Execute observer
|
|
11881
|
+
Array.from(elements).forEach((el) => {
|
|
11882
|
+
if (!this.#resizeObserver)
|
|
11883
|
+
throw new Error('_observeResizes: ResizeObserver instance is not initialized');
|
|
11884
|
+
this.#resizeObserver.observe(el);
|
|
11885
|
+
});
|
|
11886
|
+
}
|
|
11887
|
+
|
|
11888
|
+
/**
|
|
11889
|
+
* Listens for media/content load events (e.g., images, iframes, videos) to trigger scroll updates.
|
|
11890
|
+
*
|
|
11891
|
+
* @param {NodeListOf<Element>|Element} elements - Target element(s) to listen on.
|
|
11892
|
+
*/
|
|
11893
|
+
_listenLoadEvents(elements) {
|
|
11894
|
+
if (this.#destroyed) return;
|
|
11895
|
+
const list = elements instanceof NodeList ? Array.from(elements) : [elements];
|
|
11896
|
+
|
|
11897
|
+
list.forEach((el) => {
|
|
11898
|
+
if (this.#loadTags.has(el.tagName)) {
|
|
11899
|
+
// @ts-ignore
|
|
11900
|
+
if (!el.complete) {
|
|
11901
|
+
el.addEventListener('load', () => {
|
|
11902
|
+
this._scrollDataUpdater();
|
|
11903
|
+
if (!this.#scrollPaused && this.#autoScrollBottom) {
|
|
11904
|
+
this.scrollToBottom();
|
|
11905
|
+
}
|
|
11906
|
+
});
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
});
|
|
11910
|
+
}
|
|
11911
|
+
|
|
11912
|
+
/**
|
|
11913
|
+
* Returns the internal scroll container element being monitored.
|
|
11914
|
+
*
|
|
11915
|
+
* @returns {Element} The DOM element used as the scroll container target.
|
|
11916
|
+
*/
|
|
11917
|
+
get target() {
|
|
11918
|
+
return this.#target;
|
|
11919
|
+
}
|
|
11920
|
+
|
|
11921
|
+
/**
|
|
11922
|
+
* Returns the previous size of a given element, or undefined if not tracked.
|
|
11923
|
+
*
|
|
11924
|
+
* @param {Element} el - The DOM element to query.
|
|
11925
|
+
* @returns {NodeSizes|null} The old size, or undefined.
|
|
11926
|
+
*/
|
|
11927
|
+
getOldSize(el) {
|
|
11928
|
+
return this.#oldSizes.get(el) ?? null;
|
|
11929
|
+
}
|
|
11930
|
+
|
|
11931
|
+
/**
|
|
11932
|
+
* Returns the current size of a given element, or undefined if not tracked.
|
|
11933
|
+
*
|
|
11934
|
+
* @param {Element} el - The DOM element to query.
|
|
11935
|
+
* @returns {NodeSizes|null} The new size, or undefined.
|
|
11936
|
+
*/
|
|
11937
|
+
getNewSize(el) {
|
|
11938
|
+
return this.#newSizes.get(el) ?? null;
|
|
11939
|
+
}
|
|
11940
|
+
|
|
11941
|
+
/**
|
|
11942
|
+
* Returns whether the given element was visible in the last scroll update.
|
|
11943
|
+
*
|
|
11944
|
+
* @param {Element} el - The DOM element to check.
|
|
11945
|
+
* @returns {boolean} True if visible, false if not, or undefined if not tracked.
|
|
11946
|
+
*/
|
|
11947
|
+
wasVisible(el) {
|
|
11948
|
+
return this.#oldVisibles.get(el) ?? false;
|
|
11949
|
+
}
|
|
11950
|
+
|
|
11951
|
+
/**
|
|
11952
|
+
* Returns whether the given element is currently visible.
|
|
11953
|
+
*
|
|
11954
|
+
* @param {Element} el - The DOM element to check.
|
|
11955
|
+
* @returns {boolean} True if visible, false if not, or undefined if not tracked.
|
|
11956
|
+
*/
|
|
11957
|
+
isVisible(el) {
|
|
11958
|
+
return this.#newVisibles.get(el) ?? false;
|
|
11959
|
+
}
|
|
11960
|
+
|
|
11961
|
+
/**
|
|
11962
|
+
* Returns whether the element was visible in the last time-based visibility check.
|
|
11963
|
+
*
|
|
11964
|
+
* @param {Element} el - The DOM element to check.
|
|
11965
|
+
* @returns {boolean} Visibility state from the previous timed check.
|
|
11966
|
+
*/
|
|
11967
|
+
wasTimedVisible(el) {
|
|
11968
|
+
return this.#oldVisiblesByTime.get(el) ?? false;
|
|
11969
|
+
}
|
|
11970
|
+
|
|
11971
|
+
/**
|
|
11972
|
+
* Returns whether the element is currently visible in the time-based check.
|
|
11973
|
+
*
|
|
11974
|
+
* @param {Element} el - The DOM element to check.
|
|
11975
|
+
* @returns {boolean} Visibility state from the current timed check.
|
|
11976
|
+
*/
|
|
11977
|
+
isTimedVisible(el) {
|
|
11978
|
+
return this.#newVisiblesByTime.get(el) ?? false;
|
|
11979
|
+
}
|
|
11980
|
+
|
|
11981
|
+
/**
|
|
11982
|
+
* Sets the extra scroll boundary margin used when determining if the user is at a "custom" bottom or top.
|
|
11983
|
+
*
|
|
11984
|
+
* @param {number} value - Pixels of additional margin to use.
|
|
11985
|
+
*/
|
|
11986
|
+
setExtraScrollBoundary(value) {
|
|
11987
|
+
if (typeof value !== 'number' || Number.isNaN(value))
|
|
11988
|
+
throw new TypeError('setExtraScrollBoundary(value): value must be a valid number');
|
|
11989
|
+
this.#extraScrollBoundary = value;
|
|
11990
|
+
}
|
|
11991
|
+
|
|
11992
|
+
/**
|
|
11993
|
+
* Returns the current extra scroll boundary setting.
|
|
11994
|
+
*
|
|
11995
|
+
* @returns {number}
|
|
11996
|
+
*/
|
|
11997
|
+
getExtraScrollBoundary() {
|
|
11998
|
+
return this.#extraScrollBoundary;
|
|
11999
|
+
}
|
|
12000
|
+
|
|
12001
|
+
/**
|
|
12002
|
+
* Returns the last known distance (in pixels) from the bottom of the scroll container.
|
|
12003
|
+
*
|
|
12004
|
+
* @returns {number}
|
|
12005
|
+
*/
|
|
12006
|
+
getLastKnownScrollBottomOffset() {
|
|
12007
|
+
return this.#lastKnownScrollBottomOffset;
|
|
12008
|
+
}
|
|
12009
|
+
|
|
12010
|
+
/**
|
|
12011
|
+
* Forces the scroll position to move to the very bottom of the target.
|
|
12012
|
+
*/
|
|
12013
|
+
scrollToBottom() {
|
|
12014
|
+
this.#target.scrollTop = this.#target.scrollHeight;
|
|
12015
|
+
}
|
|
12016
|
+
|
|
12017
|
+
/**
|
|
12018
|
+
* Forces the scroll position to move to the very top of the target.
|
|
12019
|
+
*/
|
|
12020
|
+
scrollToTop() {
|
|
12021
|
+
this.#target.scrollTop = 0;
|
|
12022
|
+
}
|
|
12023
|
+
|
|
12024
|
+
/**
|
|
12025
|
+
* Checks if the user is within the defined extra scroll boundary from the bottom.
|
|
12026
|
+
*
|
|
12027
|
+
* @returns {boolean}
|
|
12028
|
+
*/
|
|
12029
|
+
isUserAtCustomBottom() {
|
|
12030
|
+
return this.#isAtCustomBottom;
|
|
12031
|
+
}
|
|
12032
|
+
|
|
12033
|
+
/**
|
|
12034
|
+
* Checks if the user is within the defined extra scroll boundary from the top.
|
|
12035
|
+
*
|
|
12036
|
+
* @returns {boolean}
|
|
12037
|
+
*/
|
|
12038
|
+
isUserAtCustomTop() {
|
|
12039
|
+
return this.#isAtCustomTop;
|
|
12040
|
+
}
|
|
12041
|
+
|
|
12042
|
+
/**
|
|
12043
|
+
* Returns true if the user is currently scrolled to the bottom of the element.
|
|
12044
|
+
*
|
|
12045
|
+
* @returns {boolean}
|
|
12046
|
+
*/
|
|
12047
|
+
isUserAtBottom() {
|
|
12048
|
+
return this.#isAtBottom;
|
|
12049
|
+
}
|
|
12050
|
+
|
|
12051
|
+
/**
|
|
12052
|
+
* Returns true if the user is currently scrolled to the top of the element.
|
|
12053
|
+
*
|
|
12054
|
+
* @returns {boolean}
|
|
12055
|
+
*/
|
|
12056
|
+
isUserAtTop() {
|
|
12057
|
+
return this.#isAtTop;
|
|
12058
|
+
}
|
|
12059
|
+
|
|
12060
|
+
/**
|
|
12061
|
+
* Returns true if automatic scrolling is currently paused.
|
|
12062
|
+
*
|
|
12063
|
+
* @returns {boolean}
|
|
12064
|
+
*/
|
|
12065
|
+
isScrollPaused() {
|
|
12066
|
+
return this.#scrollPaused;
|
|
12067
|
+
}
|
|
12068
|
+
|
|
12069
|
+
/**
|
|
12070
|
+
* Returns whether the target is the window object.
|
|
12071
|
+
*
|
|
12072
|
+
* @returns {boolean} True if the scroll target is window, false otherwise.
|
|
12073
|
+
*/
|
|
12074
|
+
isWindow() {
|
|
12075
|
+
return this.#useWindow;
|
|
12076
|
+
}
|
|
12077
|
+
|
|
12078
|
+
/**
|
|
12079
|
+
* Returns whether the instance has been destroyed.
|
|
12080
|
+
*
|
|
12081
|
+
* @returns {boolean} True if the instance is destroyed, false otherwise.
|
|
12082
|
+
*/
|
|
12083
|
+
isDestroyed() {
|
|
12084
|
+
return this.#destroyed;
|
|
12085
|
+
}
|
|
12086
|
+
|
|
12087
|
+
/**
|
|
12088
|
+
* Returns whether auto-scroll-to-bottom is enabled.
|
|
12089
|
+
*
|
|
12090
|
+
* @returns {boolean} True if auto-scroll is active, false otherwise.
|
|
12091
|
+
*/
|
|
12092
|
+
getAutoScrollBottom() {
|
|
12093
|
+
return this.#autoScrollBottom;
|
|
12094
|
+
}
|
|
12095
|
+
|
|
12096
|
+
/**
|
|
12097
|
+
* Returns whether MutationObserver is enabled.
|
|
12098
|
+
*
|
|
12099
|
+
* @returns {boolean} True if mutation observation is active, false otherwise.
|
|
12100
|
+
*/
|
|
12101
|
+
getObserveMutations() {
|
|
12102
|
+
return this.#observeMutations;
|
|
12103
|
+
}
|
|
12104
|
+
|
|
12105
|
+
/**
|
|
12106
|
+
* Returns whether layout shift protection is enabled.
|
|
12107
|
+
*
|
|
12108
|
+
* @returns {boolean} True if scroll preservation is active, false otherwise.
|
|
12109
|
+
*/
|
|
12110
|
+
getPreserveScrollOnLayoutShift() {
|
|
12111
|
+
return this.#preserveScrollOnLayoutShift;
|
|
12112
|
+
}
|
|
12113
|
+
|
|
12114
|
+
/**
|
|
12115
|
+
* Returns the debounce delay in milliseconds used for scroll events.
|
|
12116
|
+
*
|
|
12117
|
+
* @returns {number} Debounce delay time.
|
|
12118
|
+
*/
|
|
12119
|
+
getDebounceTime() {
|
|
12120
|
+
return this.#debounceTime;
|
|
12121
|
+
}
|
|
12122
|
+
|
|
12123
|
+
/**
|
|
12124
|
+
* Returns the current number of matching elements observed inside the scroll target.
|
|
12125
|
+
*
|
|
12126
|
+
* @returns {number} Current count of matching elements.
|
|
12127
|
+
*/
|
|
12128
|
+
getElemAmount() {
|
|
12129
|
+
return this.#elemAmount;
|
|
12130
|
+
}
|
|
12131
|
+
|
|
12132
|
+
/**
|
|
12133
|
+
* Returns the previous known count of matching elements from the last update.
|
|
12134
|
+
*
|
|
12135
|
+
* @returns {number} Previous count of matching elements.
|
|
12136
|
+
*/
|
|
12137
|
+
getPrevElemAmount() {
|
|
12138
|
+
return this.#elemOldAmount;
|
|
12139
|
+
}
|
|
12140
|
+
|
|
12141
|
+
/**
|
|
12142
|
+
* Returns the query selector string used to filter observed elements.
|
|
12143
|
+
*
|
|
12144
|
+
* @returns {string} The CSS selector string, or an empty string if none was provided.
|
|
12145
|
+
*/
|
|
12146
|
+
getQuerySelector() {
|
|
12147
|
+
return this.#querySelector;
|
|
12148
|
+
}
|
|
12149
|
+
|
|
12150
|
+
/**
|
|
12151
|
+
* Disconnects all listeners, observers, and clears memory structures.
|
|
12152
|
+
* Once destroyed, this instance should no longer be used.
|
|
12153
|
+
*/
|
|
12154
|
+
destroy() {
|
|
12155
|
+
if (this.#destroyed) return;
|
|
12156
|
+
this.#destroyed = true;
|
|
12157
|
+
|
|
12158
|
+
// Disconnects MutationObserver
|
|
12159
|
+
if (this.#mutationObserver) {
|
|
12160
|
+
this.#mutationObserver.disconnect();
|
|
12161
|
+
this.#mutationObserver = null;
|
|
12162
|
+
}
|
|
12163
|
+
|
|
12164
|
+
// Disconnect ResizeObserver
|
|
12165
|
+
if (this.#resizeObserver) {
|
|
12166
|
+
this.#resizeObserver.disconnect();
|
|
12167
|
+
this.#resizeObserver = null;
|
|
11702
12168
|
}
|
|
12169
|
+
|
|
12170
|
+
// Removes scroll listener
|
|
12171
|
+
const target = this.#useWindow ? window : this.#target;
|
|
12172
|
+
if (this.#handler) target.removeEventListener('scroll', this.#handler);
|
|
12173
|
+
|
|
12174
|
+
// Clean the WeakMaps
|
|
12175
|
+
this.#oldSizes = new WeakMap();
|
|
12176
|
+
this.#newSizes = new WeakMap();
|
|
12177
|
+
this.#newVisibles = new WeakMap();
|
|
12178
|
+
this.#oldVisibles = new WeakMap();
|
|
12179
|
+
this.#newVisiblesByTime = new WeakMap();
|
|
12180
|
+
this.#oldVisiblesByTime = new WeakMap();
|
|
12181
|
+
|
|
12182
|
+
// Cleans listeners and filters
|
|
12183
|
+
this.#events.offAllTypes();
|
|
12184
|
+
this.#sizeFilter.clear();
|
|
12185
|
+
this.#loadTags.clear();
|
|
11703
12186
|
}
|
|
12187
|
+
}
|
|
12188
|
+
|
|
12189
|
+
/* harmony default export */ const libs_TinySmartScroller = ((/* unused pure expression or super */ null && (TinySmartScroller)));
|
|
12190
|
+
|
|
12191
|
+
;// ./src/v1/libs/TinyLocalStorage.mjs
|
|
12192
|
+
|
|
12193
|
+
|
|
12194
|
+
|
|
12195
|
+
/** @type {Map<any, EncodeFn>} */
|
|
12196
|
+
const customEncoders = new Map();
|
|
12197
|
+
|
|
12198
|
+
/** @type {Map<any, DecodeFn>} */
|
|
12199
|
+
const customDecoders = new Map();
|
|
12200
|
+
|
|
12201
|
+
/**
|
|
12202
|
+
* A function that encodes a value into a serializable JSON-compatible format.
|
|
12203
|
+
*
|
|
12204
|
+
* @callback EncodeFn
|
|
12205
|
+
* @param {any} value - The value to encode.
|
|
12206
|
+
* @param {encodeSpecialJson} encodeSpecialJson - Recursive encoder helper.
|
|
12207
|
+
* @returns {any} The encoded value.
|
|
12208
|
+
*/
|
|
12209
|
+
|
|
12210
|
+
/**
|
|
12211
|
+
* An object that defines how to check and decode a specific serialized type.
|
|
12212
|
+
*
|
|
12213
|
+
* @typedef {Object} DecodeFn
|
|
12214
|
+
* @property {(value: any) => any} check - Checks if the value matches the custom encoded structure.
|
|
12215
|
+
* @property {(value: any, decodeSpecialJson: decodeSpecialJson) => any} decode - Decodes the structure back into its original form.
|
|
12216
|
+
*/
|
|
12217
|
+
|
|
12218
|
+
/**
|
|
12219
|
+
* Encodes extended JSON-compatible structures recursively.
|
|
12220
|
+
* @callback encodeSpecialJson
|
|
12221
|
+
* @param {any} value
|
|
12222
|
+
* @returns {any}
|
|
12223
|
+
*/
|
|
12224
|
+
|
|
12225
|
+
/**
|
|
12226
|
+
* Decodes extended JSON-compatible structures recursively.
|
|
12227
|
+
* @callback decodeSpecialJson
|
|
12228
|
+
* @param {any} value
|
|
12229
|
+
* @returns {any}
|
|
12230
|
+
*/
|
|
12231
|
+
|
|
12232
|
+
/**
|
|
12233
|
+
* Represents a value that can be safely stored and restored using JSON in `localStorage`,
|
|
12234
|
+
* including structures like arrays, plain objects, Map and Set.
|
|
12235
|
+
*
|
|
12236
|
+
* - `Record<string|number|symbol, any>` → plain object (e.g., `{ key: value }`)
|
|
12237
|
+
* - `any[]` → array of any JSON-serializable values
|
|
12238
|
+
* - `Map<string|number|symbol, any>` → converted to `{ __map__: true, data: [[k, v], ...] }`
|
|
12239
|
+
* - `Set<any>` → converted to `{ __set__: true, data: [v1, v2, ...] }`
|
|
12240
|
+
*
|
|
12241
|
+
* These conversions allow complex structures to be restored after JSON serialization.
|
|
12242
|
+
*
|
|
12243
|
+
* @typedef {(Record<string|number|symbol, any> | any[] | Map<string|number|symbol, any> | Set<any>)} LocalStorageJsonValue
|
|
12244
|
+
*/
|
|
12245
|
+
|
|
12246
|
+
/**
|
|
12247
|
+
* A powerful wrapper for Web Storage (`localStorage` or `sessionStorage`) that supports
|
|
12248
|
+
* type-safe methods and full JSON-like structure encoding and decoding.
|
|
12249
|
+
*
|
|
12250
|
+
* `TinyLocalStorage` allows storing and retrieving complex types such as:
|
|
12251
|
+
* - `Map`, `Set`
|
|
12252
|
+
* - `Date`, `RegExp`
|
|
12253
|
+
* - `BigInt`, `Symbol`
|
|
12254
|
+
* - `undefined`, `null`
|
|
12255
|
+
* - Plain objects and arrays
|
|
12256
|
+
*
|
|
12257
|
+
* Includes:
|
|
12258
|
+
* - Type-specific `set` and `get` methods (`setDate`, `getBool`, etc.)
|
|
12259
|
+
* - `getValue()` to retrieve any structure regardless of type
|
|
12260
|
+
* - Auto-encoding/decoding with support for custom types via `registerJsonType`
|
|
12261
|
+
* - Built-in event system (`TinyEvents`) to listen for changes
|
|
12262
|
+
* - Optional fallback values on decoding errors
|
|
12263
|
+
*
|
|
12264
|
+
* Supports registering and unregistering custom types via:
|
|
12265
|
+
* - `registerJsonType(...)`
|
|
12266
|
+
* - `deleteJsonType(...)`
|
|
12267
|
+
*
|
|
12268
|
+
* This class is suitable for applications that require structured persistence in the browser.
|
|
12269
|
+
*/
|
|
12270
|
+
class TinyLocalStorage {
|
|
12271
|
+
/** @typedef {import('./TinyEvents.mjs').handler} handler */
|
|
12272
|
+
|
|
12273
|
+
#events = new libs_TinyEvents();
|
|
12274
|
+
|
|
11704
12275
|
/**
|
|
11705
|
-
*
|
|
12276
|
+
* Enables or disables throwing an error when the maximum number of listeners is exceeded.
|
|
12277
|
+
*
|
|
12278
|
+
* @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
|
|
11706
12279
|
*/
|
|
11707
|
-
|
|
11708
|
-
this.#
|
|
11709
|
-
|
|
11710
|
-
this._scrollDataUpdater();
|
|
11711
|
-
this.#elemOldAmount = this.#elemAmount;
|
|
11712
|
-
this.#elemAmount = this.#target.childElementCount;
|
|
12280
|
+
setThrowOnMaxListeners(shouldThrow) {
|
|
12281
|
+
return this.#events.setThrowOnMaxListeners(shouldThrow);
|
|
12282
|
+
}
|
|
11713
12283
|
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
12284
|
+
/**
|
|
12285
|
+
* Checks whether an error will be thrown when the max listener limit is exceeded.
|
|
12286
|
+
*
|
|
12287
|
+
* @returns {boolean} True if an error will be thrown, false if only a warning is shown.
|
|
12288
|
+
*/
|
|
12289
|
+
getThrowOnMaxListeners() {
|
|
12290
|
+
return this.#events.getThrowOnMaxListeners();
|
|
12291
|
+
}
|
|
11717
12292
|
|
|
11718
|
-
|
|
11719
|
-
this._listenLoadEvents(node);
|
|
12293
|
+
/////////////////////////////////////////////////////////////
|
|
11720
12294
|
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
12295
|
+
/**
|
|
12296
|
+
* Adds a listener to the beginning of the listeners array for the specified event.
|
|
12297
|
+
*
|
|
12298
|
+
* @param {string} event - Event name.
|
|
12299
|
+
* @param {handler} handler - The callback function.
|
|
12300
|
+
*/
|
|
12301
|
+
prependListener(event, handler) {
|
|
12302
|
+
return this.#events.prependListener(event, handler);
|
|
12303
|
+
}
|
|
11728
12304
|
|
|
11729
|
-
|
|
11730
|
-
|
|
12305
|
+
/**
|
|
12306
|
+
* Adds a one-time listener to the beginning of the listeners array for the specified event.
|
|
12307
|
+
*
|
|
12308
|
+
* @param {string} event - Event name.
|
|
12309
|
+
* @param {handler} handler - The callback function.
|
|
12310
|
+
* @returns {handler} - The wrapped handler used internally.
|
|
12311
|
+
*/
|
|
12312
|
+
prependListenerOnce(event, handler) {
|
|
12313
|
+
return this.#events.prependListenerOnce(event, handler);
|
|
12314
|
+
}
|
|
11731
12315
|
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
12316
|
+
//////////////////////////////////////////////////////////////////////
|
|
12317
|
+
|
|
12318
|
+
/**
|
|
12319
|
+
* Adds a event listener.
|
|
12320
|
+
*
|
|
12321
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
12322
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
12323
|
+
*/
|
|
12324
|
+
appendListener(event, handler) {
|
|
12325
|
+
return this.#events.appendListener(event, handler);
|
|
11740
12326
|
}
|
|
11741
12327
|
|
|
11742
12328
|
/**
|
|
11743
|
-
*
|
|
12329
|
+
* Registers an event listener that runs only once, then is removed.
|
|
11744
12330
|
*
|
|
11745
|
-
* @param {
|
|
12331
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
12332
|
+
* @param {handler} handler - The callback function to run on event.
|
|
12333
|
+
* @returns {handler} - The wrapped version of the handler.
|
|
11746
12334
|
*/
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
12335
|
+
appendListenerOnce(event, handler) {
|
|
12336
|
+
return this.#events.appendListenerOnce(event, handler);
|
|
12337
|
+
}
|
|
12338
|
+
|
|
12339
|
+
/**
|
|
12340
|
+
* Adds a event listener.
|
|
12341
|
+
*
|
|
12342
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
12343
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
12344
|
+
*/
|
|
12345
|
+
on(event, handler) {
|
|
12346
|
+
return this.#events.on(event, handler);
|
|
12347
|
+
}
|
|
12348
|
+
|
|
12349
|
+
/**
|
|
12350
|
+
* Registers an event listener that runs only once, then is removed.
|
|
12351
|
+
*
|
|
12352
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
12353
|
+
* @param {handler} handler - The callback function to run on event.
|
|
12354
|
+
* @returns {handler} - The wrapped version of the handler.
|
|
12355
|
+
*/
|
|
12356
|
+
once(event, handler) {
|
|
12357
|
+
return this.#events.once(event, handler);
|
|
12358
|
+
}
|
|
12359
|
+
|
|
12360
|
+
////////////////////////////////////////////////////////////////////
|
|
12361
|
+
|
|
12362
|
+
/**
|
|
12363
|
+
* Removes a previously registered event listener.
|
|
12364
|
+
*
|
|
12365
|
+
* @param {string} event - The name of the event to remove the handler from.
|
|
12366
|
+
* @param {handler} handler - The specific callback function to remove.
|
|
12367
|
+
*/
|
|
12368
|
+
off(event, handler) {
|
|
12369
|
+
return this.#events.off(event, handler);
|
|
12370
|
+
}
|
|
12371
|
+
|
|
12372
|
+
/**
|
|
12373
|
+
* Removes all event listeners of a specific type from the element.
|
|
12374
|
+
*
|
|
12375
|
+
* @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
|
|
12376
|
+
*/
|
|
12377
|
+
offAll(event) {
|
|
12378
|
+
return this.#events.offAll(event);
|
|
12379
|
+
}
|
|
12380
|
+
|
|
12381
|
+
/**
|
|
12382
|
+
* Removes all event listeners of all types from the element.
|
|
12383
|
+
*/
|
|
12384
|
+
offAllTypes() {
|
|
12385
|
+
return this.#events.offAllTypes();
|
|
12386
|
+
}
|
|
12387
|
+
|
|
12388
|
+
////////////////////////////////////////////////////////////
|
|
12389
|
+
|
|
12390
|
+
/**
|
|
12391
|
+
* Returns the number of listeners for a given event.
|
|
12392
|
+
*
|
|
12393
|
+
* @param {string} event - The name of the event.
|
|
12394
|
+
* @returns {number} Number of listeners for the event.
|
|
12395
|
+
*/
|
|
12396
|
+
listenerCount(event) {
|
|
12397
|
+
return this.#events.listenerCount(event);
|
|
12398
|
+
}
|
|
12399
|
+
|
|
12400
|
+
/**
|
|
12401
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
12402
|
+
*
|
|
12403
|
+
* @param {string} event - The name of the event.
|
|
12404
|
+
* @returns {handler[]} Array of listener functions.
|
|
12405
|
+
*/
|
|
12406
|
+
listeners(event) {
|
|
12407
|
+
return this.#events.listeners(event);
|
|
12408
|
+
}
|
|
12409
|
+
|
|
12410
|
+
/**
|
|
12411
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
12412
|
+
*
|
|
12413
|
+
* @param {string} event - The name of the event.
|
|
12414
|
+
* @returns {handler[]} Array of listener functions.
|
|
12415
|
+
*/
|
|
12416
|
+
onceListeners(event) {
|
|
12417
|
+
return this.#events.onceListeners(event);
|
|
12418
|
+
}
|
|
12419
|
+
|
|
12420
|
+
/**
|
|
12421
|
+
* Returns a copy of the internal listeners array for the specified event,
|
|
12422
|
+
* including wrapper functions like those used by `.once()`.
|
|
12423
|
+
* @param {string | symbol} event - The event name.
|
|
12424
|
+
* @returns {handler[]} An array of raw listener functions.
|
|
12425
|
+
*/
|
|
12426
|
+
allListeners(event) {
|
|
12427
|
+
return this.#events.allListeners(event);
|
|
12428
|
+
}
|
|
12429
|
+
|
|
12430
|
+
/**
|
|
12431
|
+
* Returns an array of event names for which there are registered listeners.
|
|
12432
|
+
*
|
|
12433
|
+
* @returns {string[]} Array of registered event names.
|
|
12434
|
+
*/
|
|
12435
|
+
eventNames() {
|
|
12436
|
+
return this.#events.eventNames();
|
|
12437
|
+
}
|
|
12438
|
+
|
|
12439
|
+
//////////////////////////////////////////////////////
|
|
12440
|
+
|
|
12441
|
+
/**
|
|
12442
|
+
* Emits an event, triggering all registered handlers for that event.
|
|
12443
|
+
*
|
|
12444
|
+
* @param {string} event - The event name to emit.
|
|
12445
|
+
* @param {...any} payload - Optional data to pass to each handler.
|
|
12446
|
+
* @returns {boolean} True if any listeners were called, false otherwise.
|
|
12447
|
+
*/
|
|
12448
|
+
emit(event, ...payload) {
|
|
12449
|
+
return this.#events.emit(event, ...payload);
|
|
12450
|
+
}
|
|
12451
|
+
|
|
12452
|
+
/**
|
|
12453
|
+
* Sets the maximum number of listeners per event before a warning is shown.
|
|
12454
|
+
*
|
|
12455
|
+
* @param {number} n - The maximum number of listeners.
|
|
12456
|
+
*/
|
|
12457
|
+
setMaxListeners(n) {
|
|
12458
|
+
return this.#events.setMaxListeners(n);
|
|
12459
|
+
}
|
|
12460
|
+
|
|
12461
|
+
/**
|
|
12462
|
+
* Gets the maximum number of listeners allowed per event.
|
|
12463
|
+
*
|
|
12464
|
+
* @returns {number} The maximum number of listeners.
|
|
12465
|
+
*/
|
|
12466
|
+
getMaxListeners() {
|
|
12467
|
+
return this.#events.getMaxListeners();
|
|
12468
|
+
}
|
|
12469
|
+
|
|
12470
|
+
///////////////////////////////////////////////////
|
|
12471
|
+
|
|
12472
|
+
/**
|
|
12473
|
+
* Registers a new JSON-serializable type with its encoder and decoder.
|
|
12474
|
+
*
|
|
12475
|
+
* @param {any} type - The type or primitive type name (e.g. `"bigint"`, `"symbol"`, etc).
|
|
12476
|
+
* @param {EncodeFn} encodeFn - The function that encodes the value.
|
|
12477
|
+
* @param {DecodeFn} decodeFn - An object with `check` and `decode` methods for restoring the value.
|
|
12478
|
+
*/
|
|
12479
|
+
static registerJsonType(type, encodeFn, decodeFn) {
|
|
12480
|
+
customEncoders.set(type, encodeFn);
|
|
12481
|
+
customDecoders.set(type, decodeFn);
|
|
12482
|
+
}
|
|
11758
12483
|
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
12484
|
+
/**
|
|
12485
|
+
* Removes a previously registered custom type from the encoding/decoding system.
|
|
12486
|
+
*
|
|
12487
|
+
* @param {string} type - The primitive name or constructor reference used in registration.
|
|
12488
|
+
*/
|
|
12489
|
+
static deleteJsonType(type) {
|
|
12490
|
+
customEncoders.delete(type);
|
|
12491
|
+
customDecoders.delete(type);
|
|
12492
|
+
}
|
|
11762
12493
|
|
|
11763
|
-
|
|
11764
|
-
const { width, height } = entry.contentRect;
|
|
11765
|
-
this.#newSizes.set(target, { width, height });
|
|
11766
|
-
targets.push(target);
|
|
11767
|
-
}
|
|
12494
|
+
//////////////////////////////////////////////////////
|
|
11768
12495
|
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
12496
|
+
/**
|
|
12497
|
+
* Recursively serializes a value to a JSON-compatible format.
|
|
12498
|
+
*
|
|
12499
|
+
* This includes custom types (via `registerJsonType`), plus support for:
|
|
12500
|
+
* - `undefined` → `{ __undefined__: true }`
|
|
12501
|
+
* - `null` → `{ __null__: true }`
|
|
12502
|
+
*
|
|
12503
|
+
* @type {encodeSpecialJson}
|
|
12504
|
+
*/
|
|
12505
|
+
static encodeSpecialJson(value) {
|
|
12506
|
+
if (typeof value === 'undefined') return { __undefined__: true };
|
|
12507
|
+
if (value === null) return { __null__: true };
|
|
12508
|
+
for (const [type, encoder] of customEncoders.entries()) {
|
|
12509
|
+
if ((typeof type !== 'string' && value instanceof type) || typeof value === type) {
|
|
12510
|
+
return encoder(value, TinyLocalStorage.encodeSpecialJson);
|
|
12511
|
+
}
|
|
11772
12512
|
}
|
|
11773
12513
|
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
12514
|
+
if (Array.isArray(value)) {
|
|
12515
|
+
return value.map(TinyLocalStorage.encodeSpecialJson);
|
|
12516
|
+
}
|
|
12517
|
+
|
|
12518
|
+
if (isJsonObject(value)) {
|
|
12519
|
+
const encoded = {};
|
|
12520
|
+
for (const key in value) {
|
|
12521
|
+
// @ts-ignore
|
|
12522
|
+
encoded[key] = TinyLocalStorage.encodeSpecialJson(value[key]);
|
|
12523
|
+
}
|
|
12524
|
+
return encoded;
|
|
12525
|
+
}
|
|
12526
|
+
|
|
12527
|
+
return value;
|
|
11780
12528
|
}
|
|
11781
12529
|
|
|
11782
12530
|
/**
|
|
11783
|
-
*
|
|
12531
|
+
* Recursively deserializes a JSON-compatible value into its original structure.
|
|
11784
12532
|
*
|
|
11785
|
-
*
|
|
12533
|
+
* Automatically handles:
|
|
12534
|
+
* - `__undefined__` → `undefined`
|
|
12535
|
+
* - `__null__` → `null`
|
|
12536
|
+
* - Any type registered via `registerJsonType`
|
|
12537
|
+
*
|
|
12538
|
+
* @type {decodeSpecialJson}
|
|
11786
12539
|
*/
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
12540
|
+
static decodeSpecialJson(value) {
|
|
12541
|
+
const isJson = isJsonObject(value);
|
|
12542
|
+
if (isJson) {
|
|
12543
|
+
if (value.__undefined__) return undefined;
|
|
12544
|
+
if (value.__null__) return null;
|
|
12545
|
+
}
|
|
11790
12546
|
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
}
|
|
11800
|
-
});
|
|
12547
|
+
if (Array.isArray(value)) {
|
|
12548
|
+
return value.map(TinyLocalStorage.decodeSpecialJson);
|
|
12549
|
+
}
|
|
12550
|
+
|
|
12551
|
+
if (isJson) {
|
|
12552
|
+
for (const [type, decoder] of customDecoders.entries()) {
|
|
12553
|
+
if (decoder.check && decoder.check(value)) {
|
|
12554
|
+
return decoder.decode(value, TinyLocalStorage.decodeSpecialJson);
|
|
11801
12555
|
}
|
|
11802
12556
|
}
|
|
11803
|
-
|
|
12557
|
+
|
|
12558
|
+
const decoded = {};
|
|
12559
|
+
for (const key in value) {
|
|
12560
|
+
// @ts-ignore
|
|
12561
|
+
decoded[key] = TinyLocalStorage.decodeSpecialJson(value[key]);
|
|
12562
|
+
}
|
|
12563
|
+
return decoded;
|
|
12564
|
+
}
|
|
12565
|
+
|
|
12566
|
+
return value;
|
|
11804
12567
|
}
|
|
11805
12568
|
|
|
12569
|
+
//////////////////////////////////////////////////////
|
|
12570
|
+
|
|
12571
|
+
/** @type {Storage} */
|
|
12572
|
+
#localStorage = window.localStorage;
|
|
12573
|
+
|
|
12574
|
+
/** @type {(ev: StorageEvent) => any} */
|
|
12575
|
+
#storageEvent = (ev) => this.emit('storage', ev);
|
|
12576
|
+
|
|
11806
12577
|
/**
|
|
11807
|
-
*
|
|
12578
|
+
* Initializes the TinyLocalStorage instance and sets up cross-tab sync.
|
|
11808
12579
|
*
|
|
11809
|
-
*
|
|
12580
|
+
* Adds listener for the native `storage` event to support tab synchronization.
|
|
11810
12581
|
*/
|
|
11811
|
-
|
|
11812
|
-
|
|
12582
|
+
constructor() {
|
|
12583
|
+
window.addEventListener('storage', this.#storageEvent);
|
|
11813
12584
|
}
|
|
11814
12585
|
|
|
11815
12586
|
/**
|
|
11816
|
-
*
|
|
12587
|
+
* Defines a custom storage interface (e.g. `sessionStorage`).
|
|
11817
12588
|
*
|
|
11818
|
-
* @param {
|
|
11819
|
-
* @returns {NodeSizes|null} The old size, or undefined.
|
|
12589
|
+
* @param {Storage} localstorage - A valid Storage object (localStorage or sessionStorage).
|
|
11820
12590
|
*/
|
|
11821
|
-
|
|
11822
|
-
|
|
12591
|
+
setLocalStorage(localstorage) {
|
|
12592
|
+
if (!(localstorage instanceof Storage))
|
|
12593
|
+
throw new Error('Argument must be a valid instance of Storage.');
|
|
12594
|
+
this.#localStorage = localstorage;
|
|
11823
12595
|
}
|
|
11824
12596
|
|
|
11825
12597
|
/**
|
|
11826
|
-
*
|
|
12598
|
+
* Checks if `localStorage` is supported by the current environment.
|
|
11827
12599
|
*
|
|
11828
|
-
* @
|
|
11829
|
-
* @returns {NodeSizes|null} The new size, or undefined.
|
|
12600
|
+
* @returns {boolean} True if `localStorage` exists, false otherwise.
|
|
11830
12601
|
*/
|
|
11831
|
-
|
|
11832
|
-
return this.#
|
|
12602
|
+
localStorageExists() {
|
|
12603
|
+
return this.#localStorage instanceof Storage;
|
|
11833
12604
|
}
|
|
11834
12605
|
|
|
11835
12606
|
/**
|
|
11836
|
-
*
|
|
12607
|
+
* Automatically serializes nested instances.
|
|
11837
12608
|
*
|
|
11838
|
-
* @param {
|
|
11839
|
-
* @
|
|
12609
|
+
* @param {string} name - The key under which to store the data.
|
|
12610
|
+
* @param {*} data - The data to be serialized.
|
|
12611
|
+
* @returns {*}
|
|
11840
12612
|
*/
|
|
11841
|
-
|
|
11842
|
-
|
|
12613
|
+
#setJson(name, data) {
|
|
12614
|
+
if (typeof name !== 'string' || !name.length)
|
|
12615
|
+
throw new Error('Key must be a non-empty string.');
|
|
12616
|
+
return TinyLocalStorage.encodeSpecialJson(data);
|
|
11843
12617
|
}
|
|
11844
12618
|
|
|
11845
12619
|
/**
|
|
11846
|
-
*
|
|
12620
|
+
* Stores a JSON-compatible value in `localStorage`.
|
|
11847
12621
|
*
|
|
11848
|
-
*
|
|
11849
|
-
*
|
|
12622
|
+
* Automatically serializes nested `Map` and `Set` instances.
|
|
12623
|
+
*
|
|
12624
|
+
* @param {string} name - The key under which to store the data.
|
|
12625
|
+
* @param {LocalStorageJsonValue} data - The data to be serialized and stored.
|
|
11850
12626
|
*/
|
|
11851
|
-
|
|
11852
|
-
|
|
12627
|
+
setJson(name, data) {
|
|
12628
|
+
if (
|
|
12629
|
+
!isJsonObject(data) &&
|
|
12630
|
+
!Array.isArray(data) &&
|
|
12631
|
+
!(data instanceof Map) &&
|
|
12632
|
+
!(data instanceof Set)
|
|
12633
|
+
) {
|
|
12634
|
+
throw new Error('The storage value is not a valid JSON-compatible structure.');
|
|
12635
|
+
}
|
|
12636
|
+
const encoded = this.#setJson(name, data);
|
|
12637
|
+
this.emit('setJson', name, data);
|
|
12638
|
+
this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
11853
12639
|
}
|
|
11854
12640
|
|
|
11855
12641
|
/**
|
|
11856
|
-
*
|
|
12642
|
+
* Retrieves a value from `localStorage`.
|
|
11857
12643
|
*
|
|
11858
|
-
*
|
|
11859
|
-
*
|
|
12644
|
+
* Automatically restores nested instances.
|
|
12645
|
+
*
|
|
12646
|
+
* @param {string} name - The key to retrieve.
|
|
12647
|
+
* @param {'array'|'obj'|'map'|'set'|'null'} [defaultData] - Default fallback format if value is invalid.
|
|
12648
|
+
* @returns {{ decoded: any, fallback: any }} The parsed object or fallback.
|
|
11860
12649
|
*/
|
|
11861
|
-
|
|
11862
|
-
|
|
12650
|
+
#getJson(name, defaultData) {
|
|
12651
|
+
if (typeof name !== 'string' || !name.length)
|
|
12652
|
+
throw new Error('Key must be a non-empty string.');
|
|
12653
|
+
|
|
12654
|
+
const raw = this.#localStorage.getItem(name);
|
|
12655
|
+
const fallbackTypes = {
|
|
12656
|
+
obj: () => ({}),
|
|
12657
|
+
array: () => [],
|
|
12658
|
+
map: () => new Map(),
|
|
12659
|
+
set: () => new Set(),
|
|
12660
|
+
};
|
|
12661
|
+
|
|
12662
|
+
const fallback =
|
|
12663
|
+
// @ts-ignore
|
|
12664
|
+
typeof fallbackTypes[defaultData] === 'function' ? fallbackTypes[defaultData]() : null;
|
|
12665
|
+
|
|
12666
|
+
let parsed;
|
|
12667
|
+
|
|
12668
|
+
try {
|
|
12669
|
+
// @ts-ignore
|
|
12670
|
+
parsed = JSON.parse(raw);
|
|
12671
|
+
} catch {
|
|
12672
|
+
// @ts-ignore
|
|
12673
|
+
return fallback;
|
|
12674
|
+
}
|
|
12675
|
+
|
|
12676
|
+
return { decoded: TinyLocalStorage.decodeSpecialJson(parsed), fallback };
|
|
11863
12677
|
}
|
|
11864
12678
|
|
|
11865
12679
|
/**
|
|
11866
|
-
*
|
|
12680
|
+
* Retrieves and parses a JSON value from `localStorage`.
|
|
11867
12681
|
*
|
|
11868
|
-
*
|
|
11869
|
-
*
|
|
12682
|
+
* Automatically restores nested `Map` and `Set` instances.
|
|
12683
|
+
*
|
|
12684
|
+
* @param {string} name - The key to retrieve.
|
|
12685
|
+
* @param {'array'|'obj'|'map'|'set'|'null'} [defaultData] - Default fallback format if value is invalid.
|
|
12686
|
+
* @returns {LocalStorageJsonValue|null} The parsed object or fallback.
|
|
11870
12687
|
*/
|
|
11871
|
-
|
|
11872
|
-
|
|
12688
|
+
getJson(name, defaultData) {
|
|
12689
|
+
const { decoded, fallback } = this.#getJson(name, defaultData);
|
|
12690
|
+
if (
|
|
12691
|
+
decoded instanceof Map ||
|
|
12692
|
+
decoded instanceof Set ||
|
|
12693
|
+
Array.isArray(decoded) ||
|
|
12694
|
+
isJsonObject(decoded)
|
|
12695
|
+
)
|
|
12696
|
+
return decoded;
|
|
12697
|
+
return fallback;
|
|
11873
12698
|
}
|
|
11874
12699
|
|
|
11875
12700
|
/**
|
|
11876
|
-
*
|
|
11877
|
-
*
|
|
11878
|
-
* @param {
|
|
12701
|
+
* Stores a Date in localStorage.
|
|
12702
|
+
* @param {string} name
|
|
12703
|
+
* @param {Date} data
|
|
11879
12704
|
*/
|
|
11880
|
-
|
|
11881
|
-
if (
|
|
11882
|
-
|
|
11883
|
-
this.#
|
|
12705
|
+
setDate(name, data) {
|
|
12706
|
+
if (!(data instanceof Date)) throw new Error('Value must be a Date.');
|
|
12707
|
+
const encoded = this.#setJson(name, data);
|
|
12708
|
+
this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
12709
|
+
this.emit('setDate', name, data);
|
|
11884
12710
|
}
|
|
11885
12711
|
|
|
11886
12712
|
/**
|
|
11887
|
-
*
|
|
11888
|
-
*
|
|
11889
|
-
* @returns {
|
|
12713
|
+
* Retrieves a Date from localStorage.
|
|
12714
|
+
* @param {string} name
|
|
12715
|
+
* @returns {Date|null}
|
|
11890
12716
|
*/
|
|
11891
|
-
|
|
11892
|
-
|
|
12717
|
+
getDate(name) {
|
|
12718
|
+
const value = this.#getJson(name).decoded;
|
|
12719
|
+
return value instanceof Date ? value : null;
|
|
11893
12720
|
}
|
|
11894
12721
|
|
|
11895
12722
|
/**
|
|
11896
|
-
*
|
|
11897
|
-
*
|
|
11898
|
-
* @
|
|
12723
|
+
* Stores a RegExp in localStorage.
|
|
12724
|
+
* @param {string} name
|
|
12725
|
+
* @param {RegExp} data
|
|
11899
12726
|
*/
|
|
11900
|
-
|
|
11901
|
-
|
|
12727
|
+
setRegExp(name, data) {
|
|
12728
|
+
if (!(data instanceof RegExp)) throw new Error('Value must be a RegExp.');
|
|
12729
|
+
const encoded = this.#setJson(name, data);
|
|
12730
|
+
this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
12731
|
+
this.emit('setRegExp', name, data);
|
|
11902
12732
|
}
|
|
11903
12733
|
|
|
11904
12734
|
/**
|
|
11905
|
-
*
|
|
12735
|
+
* Retrieves a RegExp from localStorage.
|
|
12736
|
+
* @param {string} name
|
|
12737
|
+
* @returns {RegExp|null}
|
|
11906
12738
|
*/
|
|
11907
|
-
|
|
11908
|
-
|
|
12739
|
+
getRegExp(name) {
|
|
12740
|
+
const value = this.#getJson(name).decoded;
|
|
12741
|
+
return value instanceof RegExp ? value : null;
|
|
11909
12742
|
}
|
|
11910
12743
|
|
|
11911
12744
|
/**
|
|
11912
|
-
*
|
|
12745
|
+
* Stores a BigInt in localStorage.
|
|
12746
|
+
* @param {string} name
|
|
12747
|
+
* @param {bigint} data
|
|
11913
12748
|
*/
|
|
11914
|
-
|
|
11915
|
-
|
|
12749
|
+
setBigInt(name, data) {
|
|
12750
|
+
if (typeof data !== 'bigint') throw new Error('Value must be a BigInt.');
|
|
12751
|
+
const encoded = this.#setJson(name, data);
|
|
12752
|
+
this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
12753
|
+
this.emit('setBigInt', name, data);
|
|
11916
12754
|
}
|
|
11917
12755
|
|
|
11918
12756
|
/**
|
|
11919
|
-
*
|
|
11920
|
-
*
|
|
11921
|
-
* @returns {
|
|
12757
|
+
* Retrieves a BigInt from localStorage.
|
|
12758
|
+
* @param {string} name
|
|
12759
|
+
* @returns {bigint|null}
|
|
11922
12760
|
*/
|
|
11923
|
-
|
|
11924
|
-
|
|
12761
|
+
getBigInt(name) {
|
|
12762
|
+
const value = this.#getJson(name).decoded;
|
|
12763
|
+
return typeof value === 'bigint' ? value : null;
|
|
11925
12764
|
}
|
|
11926
12765
|
|
|
11927
12766
|
/**
|
|
11928
|
-
*
|
|
11929
|
-
*
|
|
11930
|
-
* @
|
|
12767
|
+
* Stores a Symbol in localStorage.
|
|
12768
|
+
* Only global symbols (`Symbol.for`) will preserve the key.
|
|
12769
|
+
* @param {string} name
|
|
12770
|
+
* @param {symbol} data
|
|
11931
12771
|
*/
|
|
11932
|
-
|
|
11933
|
-
|
|
12772
|
+
setSymbol(name, data) {
|
|
12773
|
+
if (typeof data !== 'symbol') throw new Error('Value must be a Symbol.');
|
|
12774
|
+
const encoded = this.#setJson(name, data);
|
|
12775
|
+
this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
12776
|
+
this.emit('setSymbol', name, data);
|
|
11934
12777
|
}
|
|
11935
12778
|
|
|
11936
12779
|
/**
|
|
11937
|
-
*
|
|
11938
|
-
*
|
|
11939
|
-
* @returns {
|
|
12780
|
+
* Retrieves a Symbol from localStorage.
|
|
12781
|
+
* @param {string} name
|
|
12782
|
+
* @returns {symbol|null}
|
|
11940
12783
|
*/
|
|
11941
|
-
|
|
11942
|
-
|
|
12784
|
+
getSymbol(name) {
|
|
12785
|
+
const value = this.#getJson(name).decoded;
|
|
12786
|
+
return typeof value === 'symbol' ? value : null;
|
|
11943
12787
|
}
|
|
11944
12788
|
|
|
11945
12789
|
/**
|
|
11946
|
-
*
|
|
12790
|
+
* Retrieves a value from `localStorage`.
|
|
11947
12791
|
*
|
|
11948
|
-
* @
|
|
12792
|
+
* @param {string} name - The key to retrieve.
|
|
12793
|
+
* @returns {any} The stored value or null if not found.
|
|
11949
12794
|
*/
|
|
11950
|
-
|
|
11951
|
-
return this.#
|
|
12795
|
+
getValue(name) {
|
|
12796
|
+
return this.#getJson(name).decoded ?? null;
|
|
11952
12797
|
}
|
|
11953
12798
|
|
|
11954
12799
|
/**
|
|
11955
|
-
*
|
|
12800
|
+
* Stores a raw string value in `localStorage`.
|
|
11956
12801
|
*
|
|
11957
|
-
* @
|
|
12802
|
+
* @param {string} name - The key to use.
|
|
12803
|
+
* @param {any} data - The data to store.
|
|
11958
12804
|
*/
|
|
11959
|
-
|
|
11960
|
-
|
|
12805
|
+
setItem(name, data) {
|
|
12806
|
+
if (typeof name !== 'string' || !name.length)
|
|
12807
|
+
throw new Error('Key must be a non-empty string.');
|
|
12808
|
+
this.emit('setItem', name, data);
|
|
12809
|
+
return this.#localStorage.setItem(name, data);
|
|
11961
12810
|
}
|
|
11962
12811
|
|
|
11963
12812
|
/**
|
|
11964
|
-
*
|
|
12813
|
+
* Retrieves a raw string value from `localStorage`.
|
|
11965
12814
|
*
|
|
11966
|
-
* @
|
|
12815
|
+
* @param {string} name - The key to retrieve.
|
|
12816
|
+
* @returns {string|null} The stored value or null if not found.
|
|
11967
12817
|
*/
|
|
11968
|
-
|
|
11969
|
-
|
|
12818
|
+
getItem(name) {
|
|
12819
|
+
if (typeof name !== 'string' || !name.length)
|
|
12820
|
+
throw new Error('Key must be a non-empty string.');
|
|
12821
|
+
return this.#localStorage.getItem(name);
|
|
11970
12822
|
}
|
|
11971
12823
|
|
|
11972
12824
|
/**
|
|
11973
|
-
*
|
|
12825
|
+
* Stores a string in `localStorage`, ensuring the data is a valid string.
|
|
11974
12826
|
*
|
|
11975
|
-
* @
|
|
12827
|
+
* @param {string} name - The key to store the string under.
|
|
12828
|
+
* @param {string} data - The string to store.
|
|
11976
12829
|
*/
|
|
11977
|
-
|
|
11978
|
-
|
|
12830
|
+
setString(name, data) {
|
|
12831
|
+
if (typeof name !== 'string' || !name.length)
|
|
12832
|
+
throw new Error('Key must be a non-empty string.');
|
|
12833
|
+
if (typeof data !== 'string') throw new Error('Value must be a string.');
|
|
12834
|
+
|
|
12835
|
+
this.emit('setString', name, data);
|
|
12836
|
+
return this.#localStorage.setItem(name, data);
|
|
11979
12837
|
}
|
|
11980
12838
|
|
|
11981
12839
|
/**
|
|
11982
|
-
*
|
|
12840
|
+
* Retrieves a string value from `localStorage`.
|
|
11983
12841
|
*
|
|
11984
|
-
* @
|
|
12842
|
+
* @param {string} name - The key to retrieve.
|
|
12843
|
+
* @returns {string|null} The string if valid, or null.
|
|
11985
12844
|
*/
|
|
11986
|
-
|
|
11987
|
-
|
|
12845
|
+
getString(name) {
|
|
12846
|
+
if (typeof name !== 'string' || !name.length)
|
|
12847
|
+
throw new Error('Key must be a non-empty string.');
|
|
12848
|
+
let value = this.#localStorage.getItem(name);
|
|
12849
|
+
if (typeof value === 'string') return value;
|
|
12850
|
+
return null;
|
|
11988
12851
|
}
|
|
11989
12852
|
|
|
11990
12853
|
/**
|
|
11991
|
-
*
|
|
12854
|
+
* Stores a number value in `localStorage`.
|
|
11992
12855
|
*
|
|
11993
|
-
* @
|
|
12856
|
+
* @param {string} name - The key to use.
|
|
12857
|
+
* @param {number} data - The number to store.
|
|
11994
12858
|
*/
|
|
11995
|
-
|
|
11996
|
-
|
|
12859
|
+
setNumber(name, data) {
|
|
12860
|
+
if (typeof name !== 'string' || !name.length)
|
|
12861
|
+
throw new Error('Key must be a non-empty string.');
|
|
12862
|
+
if (typeof data !== 'number') throw new Error('Value must be a number.');
|
|
12863
|
+
this.emit('setNumber', name, data);
|
|
12864
|
+
return this.#localStorage.setItem(name, String(data));
|
|
11997
12865
|
}
|
|
11998
12866
|
|
|
11999
12867
|
/**
|
|
12000
|
-
*
|
|
12868
|
+
* Retrieves a number from `localStorage`.
|
|
12001
12869
|
*
|
|
12002
|
-
* @
|
|
12870
|
+
* @param {string} name - The key to retrieve.
|
|
12871
|
+
* @returns {number|null} The number or null if invalid.
|
|
12003
12872
|
*/
|
|
12004
|
-
|
|
12005
|
-
|
|
12873
|
+
getNumber(name) {
|
|
12874
|
+
if (typeof name !== 'string' || !name.length)
|
|
12875
|
+
throw new Error('Key must be a non-empty string.');
|
|
12876
|
+
|
|
12877
|
+
/** @type {number|string|null} */
|
|
12878
|
+
let number = this.#localStorage.getItem(name);
|
|
12879
|
+
if (typeof number === 'number') return number;
|
|
12880
|
+
if (typeof number === 'string' && number.length > 0) {
|
|
12881
|
+
number = parseFloat(number);
|
|
12882
|
+
if (!Number.isNaN(number)) return number;
|
|
12883
|
+
}
|
|
12884
|
+
return null;
|
|
12006
12885
|
}
|
|
12007
12886
|
|
|
12008
12887
|
/**
|
|
12009
|
-
*
|
|
12888
|
+
* Stores a boolean value in `localStorage`.
|
|
12010
12889
|
*
|
|
12011
|
-
* @
|
|
12890
|
+
* @param {string} name - The key to use.
|
|
12891
|
+
* @param {boolean} data - The boolean value to store.
|
|
12012
12892
|
*/
|
|
12013
|
-
|
|
12014
|
-
|
|
12893
|
+
setBool(name, data) {
|
|
12894
|
+
if (typeof name !== 'string' || !name.length)
|
|
12895
|
+
throw new Error('Key must be a non-empty string.');
|
|
12896
|
+
if (typeof data !== 'boolean') throw new Error('Value must be a boolean.');
|
|
12897
|
+
this.emit('setBool', name, data);
|
|
12898
|
+
return this.#localStorage.setItem(name, String(data));
|
|
12015
12899
|
}
|
|
12016
12900
|
|
|
12017
12901
|
/**
|
|
12018
|
-
*
|
|
12902
|
+
* Retrieves a boolean value from `localStorage`.
|
|
12019
12903
|
*
|
|
12020
|
-
* @
|
|
12904
|
+
* @param {string} name - The key to retrieve.
|
|
12905
|
+
* @returns {boolean|null} The boolean or null if invalid.
|
|
12021
12906
|
*/
|
|
12022
|
-
|
|
12023
|
-
|
|
12907
|
+
getBool(name) {
|
|
12908
|
+
if (typeof name !== 'string' || !name.length)
|
|
12909
|
+
throw new Error('Key must be a non-empty string.');
|
|
12910
|
+
|
|
12911
|
+
const value = this.#localStorage.getItem(name);
|
|
12912
|
+
if (typeof value === 'boolean') return value;
|
|
12913
|
+
if (typeof value === 'string') {
|
|
12914
|
+
if (value === 'true') return true;
|
|
12915
|
+
if (value === 'false') return false;
|
|
12916
|
+
}
|
|
12917
|
+
|
|
12918
|
+
return null;
|
|
12024
12919
|
}
|
|
12025
12920
|
|
|
12026
12921
|
/**
|
|
12027
|
-
*
|
|
12922
|
+
* Removes a value from `localStorage`.
|
|
12028
12923
|
*
|
|
12029
|
-
* @
|
|
12924
|
+
* @param {string} name - The key to remove.
|
|
12030
12925
|
*/
|
|
12031
|
-
|
|
12032
|
-
|
|
12926
|
+
removeItem(name) {
|
|
12927
|
+
if (typeof name !== 'string' || !name.length)
|
|
12928
|
+
throw new Error('Key must be a non-empty string.');
|
|
12929
|
+
|
|
12930
|
+
this.emit('removeItem', name);
|
|
12931
|
+
return this.#localStorage.removeItem(name);
|
|
12033
12932
|
}
|
|
12034
12933
|
|
|
12035
12934
|
/**
|
|
12036
|
-
*
|
|
12037
|
-
*
|
|
12038
|
-
* @returns {string} The CSS selector string, or an empty string if none was provided.
|
|
12935
|
+
* Clears all data from `localStorage`.
|
|
12039
12936
|
*/
|
|
12040
|
-
|
|
12041
|
-
|
|
12937
|
+
clearLocalStorage() {
|
|
12938
|
+
this.#localStorage.clear();
|
|
12042
12939
|
}
|
|
12043
12940
|
|
|
12044
12941
|
/**
|
|
12045
|
-
*
|
|
12046
|
-
* Once destroyed, this instance should no longer be used.
|
|
12942
|
+
* Destroys the storage instance by removing the storage event listener.
|
|
12047
12943
|
*/
|
|
12048
12944
|
destroy() {
|
|
12049
|
-
|
|
12050
|
-
this.#
|
|
12945
|
+
window.removeEventListener('storage', this.#storageEvent);
|
|
12946
|
+
this.#events.offAllTypes();
|
|
12947
|
+
}
|
|
12948
|
+
}
|
|
12051
12949
|
|
|
12052
|
-
|
|
12053
|
-
if (this.#mutationObserver) {
|
|
12054
|
-
this.#mutationObserver.disconnect();
|
|
12055
|
-
this.#mutationObserver = null;
|
|
12056
|
-
}
|
|
12950
|
+
// First registers
|
|
12057
12951
|
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12952
|
+
// Map
|
|
12953
|
+
TinyLocalStorage.registerJsonType(
|
|
12954
|
+
Map,
|
|
12955
|
+
(value, encodeSpecialJson) => ({
|
|
12956
|
+
__map__: true,
|
|
12957
|
+
data: Array.from(value.entries()).map(([k, v]) => [k, encodeSpecialJson(v)]),
|
|
12958
|
+
}),
|
|
12959
|
+
{
|
|
12960
|
+
check: (value) => value.__map__,
|
|
12961
|
+
/** @param {{ data: any[] }} value */
|
|
12962
|
+
decode: (value, decodeSpecialJson) =>
|
|
12963
|
+
new Map(value.data.map(([k, v]) => [k, decodeSpecialJson(v)])),
|
|
12964
|
+
},
|
|
12965
|
+
);
|
|
12966
|
+
|
|
12967
|
+
// Set
|
|
12968
|
+
TinyLocalStorage.registerJsonType(
|
|
12969
|
+
Set,
|
|
12970
|
+
(value, encodeSpecialJson) => ({
|
|
12971
|
+
__set__: true,
|
|
12972
|
+
data: Array.from(value).map(encodeSpecialJson),
|
|
12973
|
+
}),
|
|
12974
|
+
{
|
|
12975
|
+
check: (value) => value.__set__,
|
|
12976
|
+
decode: (value, decodeSpecialJson) => new Set(value.data.map(decodeSpecialJson)),
|
|
12977
|
+
},
|
|
12978
|
+
);
|
|
12979
|
+
|
|
12980
|
+
// Date
|
|
12981
|
+
TinyLocalStorage.registerJsonType(
|
|
12982
|
+
Date,
|
|
12983
|
+
(value) => ({
|
|
12984
|
+
__date__: true,
|
|
12985
|
+
value: value.toISOString(),
|
|
12986
|
+
}),
|
|
12987
|
+
{
|
|
12988
|
+
check: (value) => value.__date__,
|
|
12989
|
+
decode: (value) => new Date(value.value),
|
|
12990
|
+
},
|
|
12991
|
+
);
|
|
12992
|
+
|
|
12993
|
+
// Regex
|
|
12994
|
+
TinyLocalStorage.registerJsonType(
|
|
12995
|
+
RegExp,
|
|
12996
|
+
(value) => ({
|
|
12997
|
+
__regexp__: true,
|
|
12998
|
+
source: value.source,
|
|
12999
|
+
flags: value.flags,
|
|
13000
|
+
}),
|
|
13001
|
+
{
|
|
13002
|
+
check: (value) => value.__regexp__,
|
|
13003
|
+
decode: (value) => new RegExp(value.source, value.flags),
|
|
13004
|
+
},
|
|
13005
|
+
);
|
|
13006
|
+
|
|
13007
|
+
// Big Int
|
|
13008
|
+
TinyLocalStorage.registerJsonType(
|
|
13009
|
+
'bigint',
|
|
13010
|
+
(value) => ({
|
|
13011
|
+
__bigint__: true,
|
|
13012
|
+
value: value.toString(),
|
|
13013
|
+
}),
|
|
13014
|
+
{
|
|
13015
|
+
check: (value) => value.__bigint__,
|
|
13016
|
+
decode: (value) => BigInt(value.value),
|
|
13017
|
+
},
|
|
13018
|
+
);
|
|
13019
|
+
|
|
13020
|
+
// Symbol
|
|
13021
|
+
TinyLocalStorage.registerJsonType(
|
|
13022
|
+
'symbol',
|
|
13023
|
+
(value) => ({
|
|
13024
|
+
__symbol__: true,
|
|
13025
|
+
key: Symbol.keyFor(value) ?? value.description ?? null,
|
|
13026
|
+
}),
|
|
13027
|
+
{
|
|
13028
|
+
check: (value) => value.__symbol__,
|
|
13029
|
+
decode: (value) => {
|
|
13030
|
+
const key = value.key;
|
|
13031
|
+
return key != null ? Symbol.for(key) : Symbol();
|
|
13032
|
+
},
|
|
13033
|
+
},
|
|
13034
|
+
);
|
|
13035
|
+
|
|
13036
|
+
/* harmony default export */ const libs_TinyLocalStorage = ((/* unused pure expression or super */ null && (TinyLocalStorage)));
|
|
13037
|
+
|
|
13038
|
+
;// ./src/v1/index.mjs
|
|
12063
13039
|
|
|
12064
|
-
// Removes scroll listener
|
|
12065
|
-
const target = this.#useWindow ? window : this.#target;
|
|
12066
|
-
if (this.#handler) target.removeEventListener('scroll', this.#handler);
|
|
12067
13040
|
|
|
12068
|
-
// Clean the WeakMaps
|
|
12069
|
-
this.#oldSizes = new WeakMap();
|
|
12070
|
-
this.#newSizes = new WeakMap();
|
|
12071
|
-
this.#newVisibles = new WeakMap();
|
|
12072
|
-
this.#oldVisibles = new WeakMap();
|
|
12073
|
-
this.#newVisiblesByTime = new WeakMap();
|
|
12074
|
-
this.#oldVisiblesByTime = new WeakMap();
|
|
12075
13041
|
|
|
12076
|
-
// Cleans listeners and filters
|
|
12077
|
-
this.#scrollListeners = {};
|
|
12078
|
-
this.#sizeFilter.clear();
|
|
12079
|
-
this.#loadTags.clear();
|
|
12080
|
-
}
|
|
12081
|
-
}
|
|
12082
13042
|
|
|
12083
|
-
/* harmony default export */ const libs_TinySmartScroller = ((/* unused pure expression or super */ null && (TinySmartScroller)));
|
|
12084
13043
|
|
|
12085
|
-
;// ./src/v1/index.mjs
|
|
12086
13044
|
|
|
12087
13045
|
|
|
12088
13046
|
|
|
@@ -12193,7 +13151,7 @@ class TinyUploadClicker {
|
|
|
12193
13151
|
* @throws {TypeError} If the config is invalid or required options are missing.
|
|
12194
13152
|
*/
|
|
12195
13153
|
constructor(options) {
|
|
12196
|
-
if (!
|
|
13154
|
+
if (!isJsonObject(options))
|
|
12197
13155
|
throw new TypeError('TinyUploadClicker: "options" must be a valid object.');
|
|
12198
13156
|
|
|
12199
13157
|
this.#config = {
|
|
@@ -12259,10 +13217,10 @@ class TinyUploadClicker {
|
|
|
12259
13217
|
)
|
|
12260
13218
|
throw new TypeError('TinyUploadClicker: "onFileLoad" must be a function or null.');
|
|
12261
13219
|
|
|
12262
|
-
if (options.inputAttributes !== undefined && !
|
|
13220
|
+
if (options.inputAttributes !== undefined && !isJsonObject(options.inputAttributes))
|
|
12263
13221
|
throw new TypeError('TinyUploadClicker: "inputAttributes" must be an object.');
|
|
12264
13222
|
|
|
12265
|
-
if (options.inputStyles !== undefined && !
|
|
13223
|
+
if (options.inputStyles !== undefined && !isJsonObject(options.inputStyles))
|
|
12266
13224
|
throw new TypeError('TinyUploadClicker: "inputStyles" must be an object.');
|
|
12267
13225
|
|
|
12268
13226
|
this.#boundClick = this.#handleClick.bind(this);
|