tiny-essentials 1.20.2 → 1.21.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 +13 -11
- package/dist/_/numbers.cjs +136 -0
- package/dist/_/numbers.d.mts +24 -0
- package/dist/_/numbers.mjs +102 -0
- package/dist/v1/TinyAdvancedRaffle.min.js +1 -0
- package/dist/v1/TinyArrayPaginator.min.js +1 -0
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyCookieConsent.min.js +1 -0
- package/dist/v1/TinyDayNightCycle.min.js +1 -0
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyGamepad.min.js +1 -0
- package/dist/v1/TinyI18.min.js +1 -0
- package/dist/v1/TinyInventory.min.js +1 -0
- package/dist/v1/TinyInventoryTrader.min.js +1 -0
- package/dist/v1/TinyLocalStorage.min.js +1 -1
- package/dist/v1/TinyRateLimiter.min.js +1 -1
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextarea.min.js +1 -0
- package/dist/v1/TinyToastNotify.min.js +1 -1
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/basics/array.cjs +21 -0
- package/dist/v1/basics/array.d.mts +8 -0
- package/dist/v1/basics/array.mjs +18 -0
- package/dist/v1/basics/index.cjs +2 -8
- package/dist/v1/basics/index.d.mts +3 -8
- package/dist/v1/basics/index.mjs +2 -3
- package/dist/v1/basics/simpleMath.cjs +46 -0
- package/dist/v1/basics/simpleMath.d.mts +27 -0
- package/dist/v1/basics/simpleMath.mjs +42 -0
- package/dist/v1/build/TinyAdvancedRaffle.cjs +7 -0
- package/dist/v1/build/TinyAdvancedRaffle.d.mts +3 -0
- package/dist/v1/build/TinyAdvancedRaffle.mjs +2 -0
- package/dist/v1/build/TinyArrayPaginator.cjs +7 -0
- package/dist/v1/build/TinyArrayPaginator.d.mts +3 -0
- package/dist/v1/build/TinyArrayPaginator.mjs +2 -0
- package/dist/v1/build/TinyCookieConsent.cjs +7 -0
- package/dist/v1/build/TinyCookieConsent.d.mts +3 -0
- package/dist/v1/build/TinyCookieConsent.mjs +2 -0
- package/dist/v1/build/TinyDayNightCycle.cjs +7 -0
- package/dist/v1/build/TinyDayNightCycle.d.mts +3 -0
- package/dist/v1/build/TinyDayNightCycle.mjs +2 -0
- package/dist/v1/build/TinyGamepad.cjs +7 -0
- package/dist/v1/build/TinyGamepad.d.mts +3 -0
- package/dist/v1/build/TinyGamepad.mjs +2 -0
- package/dist/v1/build/TinyI18.cjs +7 -0
- package/dist/v1/build/TinyI18.d.mts +3 -0
- package/dist/v1/build/TinyI18.mjs +2 -0
- package/dist/v1/build/TinyInventory.cjs +7 -0
- package/dist/v1/build/TinyInventory.d.mts +3 -0
- package/dist/v1/build/TinyInventory.mjs +2 -0
- package/dist/v1/build/TinyInventoryTrader.cjs +7 -0
- package/dist/v1/build/TinyInventoryTrader.d.mts +3 -0
- package/dist/v1/build/TinyInventoryTrader.mjs +2 -0
- package/dist/v1/build/TinyTextarea.cjs +7 -0
- package/dist/v1/build/TinyTextarea.d.mts +3 -0
- package/dist/v1/build/TinyTextarea.mjs +2 -0
- package/dist/v1/css/TinyCookieConsent.min.css +1 -0
- package/dist/v1/index.cjs +20 -8
- package/dist/v1/index.d.mts +12 -8
- package/dist/v1/index.mjs +11 -3
- package/dist/v1/libs/TinyAdvancedRaffle.cjs +1654 -0
- package/dist/v1/libs/TinyAdvancedRaffle.d.mts +851 -0
- package/dist/v1/libs/TinyAdvancedRaffle.mjs +1477 -0
- package/dist/v1/libs/TinyArrayPaginator.cjs +132 -0
- package/dist/v1/libs/TinyArrayPaginator.d.mts +78 -0
- package/dist/v1/libs/TinyArrayPaginator.mjs +119 -0
- package/dist/v1/libs/TinyCookieConsent.cjs +278 -0
- package/dist/v1/libs/TinyCookieConsent.d.mts +142 -0
- package/dist/v1/libs/TinyCookieConsent.mjs +248 -0
- package/dist/v1/libs/TinyDayNightCycle.cjs +1389 -0
- package/dist/v1/libs/TinyDayNightCycle.d.mts +673 -0
- package/dist/v1/libs/TinyDayNightCycle.mjs +1232 -0
- package/dist/v1/libs/TinyGamepad.cjs +2690 -0
- package/dist/v1/libs/TinyGamepad.d.mts +1286 -0
- package/dist/v1/libs/TinyGamepad.mjs +2386 -0
- package/dist/v1/libs/TinyHtml.d.mts +2 -2
- package/dist/v1/libs/TinyI18.cjs +946 -0
- package/dist/v1/libs/TinyI18.d.mts +418 -0
- package/dist/v1/libs/TinyI18.mjs +873 -0
- package/dist/v1/libs/TinyInventory.cjs +1788 -0
- package/dist/v1/libs/TinyInventory.d.mts +871 -0
- package/dist/v1/libs/TinyInventory.mjs +1650 -0
- package/dist/v1/libs/TinyInventoryTrader.cjs +242 -0
- package/dist/v1/libs/TinyInventoryTrader.d.mts +95 -0
- package/dist/v1/libs/TinyInventoryTrader.mjs +212 -0
- package/dist/v1/libs/TinyLocalStorage.cjs +14 -1
- package/dist/v1/libs/TinyLocalStorage.mjs +8 -1
- package/dist/v1/libs/TinyRateLimiter.cjs +0 -13
- package/dist/v1/libs/TinyRateLimiter.d.mts +0 -8
- package/dist/v1/libs/TinyRateLimiter.mjs +0 -12
- package/dist/v1/libs/TinySmartScroller.cjs +51 -15
- package/dist/v1/libs/TinySmartScroller.d.mts +28 -12
- package/dist/v1/libs/TinySmartScroller.mjs +44 -14
- package/dist/v1/libs/TinyTextarea.cjs +230 -0
- package/dist/v1/libs/TinyTextarea.d.mts +128 -0
- package/dist/v1/libs/TinyTextarea.mjs +198 -0
- package/dist/v1/libs/TinyToastNotify.cjs +2 -2
- package/dist/v1/libs/TinyToastNotify.mjs +2 -2
- package/dist/v1/libs/TinyUploadClicker.cjs +1 -1
- package/docs/v1/Ai-Tips.md +51 -0
- package/docs/v1/Personal-Ai-Prompts(portuguese).md +134 -0
- package/docs/v1/Personal-Ai-Prompts.md +113 -0
- package/docs/v1/README.md +24 -0
- package/docs/v1/basics/array.md +17 -0
- package/docs/v1/basics/simpleMath.md +73 -0
- package/docs/v1/libs/TinyAdvancedRaffle.md +674 -0
- package/docs/v1/libs/TinyArrayPaginator.md +150 -0
- package/docs/v1/libs/TinyCookieConsent.md +152 -0
- package/docs/v1/libs/TinyDayNightCycle.md +711 -0
- package/docs/v1/libs/TinyGamepad.md +980 -0
- package/docs/v1/libs/TinyI18.md +515 -0
- package/docs/v1/libs/TinyInventory.md +777 -0
- package/docs/v1/libs/TinyInventoryTrader.md +114 -0
- package/docs/v1/libs/TinySmartScroller.md +4 -4
- package/docs/v1/libs/TinyTextarea.md +156 -0
- package/package.json +7 -5
- package/dist/v1/basics/html_deprecated.cjs +0 -124
- package/dist/v1/basics/html_deprecated.d.mts +0 -40
- package/dist/v1/basics/html_deprecated.mjs +0 -97
- package/dist/v1/css/TinyDraggerExample.css +0 -21
- package/dist/v1/css/TinyNotify.css +0 -350
- package/dist/v1/css/aiMarker.css +0 -4
- package/docs/v1/basics/html_deprecated.md +0 -127
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A predicate function used to determine whether an item should be included in the filtered results.
|
|
5
|
+
* Works similarly to the callback function of `Array.prototype.filter`.
|
|
6
|
+
*
|
|
7
|
+
* @callback GetFilter
|
|
8
|
+
* @param {any} value - The current element being processed in the array.
|
|
9
|
+
* @param {number} index - The index of the current element within the array.
|
|
10
|
+
* @param {any[]} array - The full array being processed.
|
|
11
|
+
* @returns {boolean} Returns `true` to include the element in the results, or `false` to exclude it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A encapsulated wrapper for array pagination.
|
|
16
|
+
* Provides methods to retrieve paginated results with metadata,
|
|
17
|
+
* while keeping the source array safe from direct modifications.
|
|
18
|
+
*/
|
|
19
|
+
class TinyArrayPaginator {
|
|
20
|
+
/**
|
|
21
|
+
* Internal storage for the paginated data source.
|
|
22
|
+
* @type {any[]|Set<any>}
|
|
23
|
+
*/
|
|
24
|
+
#data;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets current stored array.
|
|
28
|
+
* @returns {any[]|Set<any>}
|
|
29
|
+
*/
|
|
30
|
+
get data() {
|
|
31
|
+
return this.#data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Replaces the current data array.
|
|
36
|
+
* @param {any[]|Set<any>} value - The new array to be used as the data source.
|
|
37
|
+
* @throws {TypeError} If the provided value is not an array.
|
|
38
|
+
*/
|
|
39
|
+
set data(value) {
|
|
40
|
+
if (!Array.isArray(value) && !(value instanceof Set))
|
|
41
|
+
throw new TypeError('Paginator expects an array or Set as data source.');
|
|
42
|
+
this.#data = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Gets the total number of items in the current data array.
|
|
47
|
+
* @returns {number} Total number of stored items.
|
|
48
|
+
*/
|
|
49
|
+
get size() {
|
|
50
|
+
return Array.isArray(this.#data) ? this.#data.length : this.#data.size;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new paginator instance for the given data array.
|
|
55
|
+
* @param {any[]|Set<any>} data - The array to be paginated.
|
|
56
|
+
* @throws {TypeError} If the provided data is not an array.
|
|
57
|
+
*/
|
|
58
|
+
constructor(data) {
|
|
59
|
+
if (!Array.isArray(data) && !(data instanceof Set))
|
|
60
|
+
throw new TypeError('Paginator expects an array or Set as data source.');
|
|
61
|
+
this.#data = data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Filters data according to a search query and returns paginated results.
|
|
66
|
+
* @param {Object} settings
|
|
67
|
+
* @param {number} settings.page - The page number (1-based index).
|
|
68
|
+
* @param {number} settings.perPage - Items per page.
|
|
69
|
+
* @param {Record<string, any> | GetFilter} [settings.filter=null] - Filtering criteria:
|
|
70
|
+
* - Object: key-value pairs for exact match, substring match, or RegExp
|
|
71
|
+
* - Function: custom filter function returning true for items to include
|
|
72
|
+
* @returns {{
|
|
73
|
+
* items: any[], // The subset of items for the requested page.
|
|
74
|
+
* page: number, // The current (validated) page number.
|
|
75
|
+
* perPage: number, // Number of items per page used in the calculation.
|
|
76
|
+
* totalItems: number, // Total number of items in the filtered data.
|
|
77
|
+
* totalPages: number, // Total number of pages available.
|
|
78
|
+
* hasPrev: boolean, // Whether a previous page exists.
|
|
79
|
+
* hasNext: boolean // Whether a next page exists.
|
|
80
|
+
* }}
|
|
81
|
+
*/
|
|
82
|
+
get({ page, perPage, filter }) {
|
|
83
|
+
if (!Number.isInteger(page) || page < 1)
|
|
84
|
+
throw new RangeError('Page number must be a positive integer.');
|
|
85
|
+
if (!Number.isInteger(perPage) || perPage < 1)
|
|
86
|
+
throw new RangeError('Items per page must be a positive integer.');
|
|
87
|
+
|
|
88
|
+
const data = Array.isArray(this.#data) ? this.#data : Array.from(this.#data);
|
|
89
|
+
let dataToUse = data;
|
|
90
|
+
|
|
91
|
+
if (filter) {
|
|
92
|
+
if (typeof filter === 'function') {
|
|
93
|
+
dataToUse = data.filter((item, idx, arr) => filter(item, idx, arr));
|
|
94
|
+
} else if (typeof filter === 'object') {
|
|
95
|
+
dataToUse = data.filter((item) =>
|
|
96
|
+
Object.entries(filter).every(([key, value]) => {
|
|
97
|
+
const v = item[key];
|
|
98
|
+
if (value instanceof RegExp) return value.test(v);
|
|
99
|
+
return v === value;
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
throw new TypeError('Filter must be an object or a function');
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
dataToUse = data;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const totalItems = dataToUse.length;
|
|
110
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / perPage));
|
|
111
|
+
|
|
112
|
+
// Ensure page is within valid range
|
|
113
|
+
const safePage = Math.min(page, totalPages);
|
|
114
|
+
|
|
115
|
+
const start = (safePage - 1) * perPage;
|
|
116
|
+
const end = start + perPage;
|
|
117
|
+
const hasPrev = safePage > 1;
|
|
118
|
+
const hasNext = safePage < totalPages;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
items: dataToUse.slice(start, end),
|
|
122
|
+
page: safePage,
|
|
123
|
+
perPage,
|
|
124
|
+
totalItems,
|
|
125
|
+
totalPages,
|
|
126
|
+
hasPrev,
|
|
127
|
+
hasNext,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = TinyArrayPaginator;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export default TinyArrayPaginator;
|
|
2
|
+
/**
|
|
3
|
+
* A predicate function used to determine whether an item should be included in the filtered results.
|
|
4
|
+
* Works similarly to the callback function of `Array.prototype.filter`.
|
|
5
|
+
*/
|
|
6
|
+
export type GetFilter = (value: any, index: number, array: any[]) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* A predicate function used to determine whether an item should be included in the filtered results.
|
|
9
|
+
* Works similarly to the callback function of `Array.prototype.filter`.
|
|
10
|
+
*
|
|
11
|
+
* @callback GetFilter
|
|
12
|
+
* @param {any} value - The current element being processed in the array.
|
|
13
|
+
* @param {number} index - The index of the current element within the array.
|
|
14
|
+
* @param {any[]} array - The full array being processed.
|
|
15
|
+
* @returns {boolean} Returns `true` to include the element in the results, or `false` to exclude it.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A encapsulated wrapper for array pagination.
|
|
19
|
+
* Provides methods to retrieve paginated results with metadata,
|
|
20
|
+
* while keeping the source array safe from direct modifications.
|
|
21
|
+
*/
|
|
22
|
+
declare class TinyArrayPaginator {
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new paginator instance for the given data array.
|
|
25
|
+
* @param {any[]|Set<any>} data - The array to be paginated.
|
|
26
|
+
* @throws {TypeError} If the provided data is not an array.
|
|
27
|
+
*/
|
|
28
|
+
constructor(data: any[] | Set<any>);
|
|
29
|
+
/**
|
|
30
|
+
* Replaces the current data array.
|
|
31
|
+
* @param {any[]|Set<any>} value - The new array to be used as the data source.
|
|
32
|
+
* @throws {TypeError} If the provided value is not an array.
|
|
33
|
+
*/
|
|
34
|
+
set data(value: any[] | Set<any>);
|
|
35
|
+
/**
|
|
36
|
+
* Gets current stored array.
|
|
37
|
+
* @returns {any[]|Set<any>}
|
|
38
|
+
*/
|
|
39
|
+
get data(): any[] | Set<any>;
|
|
40
|
+
/**
|
|
41
|
+
* Gets the total number of items in the current data array.
|
|
42
|
+
* @returns {number} Total number of stored items.
|
|
43
|
+
*/
|
|
44
|
+
get size(): number;
|
|
45
|
+
/**
|
|
46
|
+
* Filters data according to a search query and returns paginated results.
|
|
47
|
+
* @param {Object} settings
|
|
48
|
+
* @param {number} settings.page - The page number (1-based index).
|
|
49
|
+
* @param {number} settings.perPage - Items per page.
|
|
50
|
+
* @param {Record<string, any> | GetFilter} [settings.filter=null] - Filtering criteria:
|
|
51
|
+
* - Object: key-value pairs for exact match, substring match, or RegExp
|
|
52
|
+
* - Function: custom filter function returning true for items to include
|
|
53
|
+
* @returns {{
|
|
54
|
+
* items: any[], // The subset of items for the requested page.
|
|
55
|
+
* page: number, // The current (validated) page number.
|
|
56
|
+
* perPage: number, // Number of items per page used in the calculation.
|
|
57
|
+
* totalItems: number, // Total number of items in the filtered data.
|
|
58
|
+
* totalPages: number, // Total number of pages available.
|
|
59
|
+
* hasPrev: boolean, // Whether a previous page exists.
|
|
60
|
+
* hasNext: boolean // Whether a next page exists.
|
|
61
|
+
* }}
|
|
62
|
+
*/
|
|
63
|
+
get({ page, perPage, filter }: {
|
|
64
|
+
page: number;
|
|
65
|
+
perPage: number;
|
|
66
|
+
filter?: Record<string, any> | GetFilter | undefined;
|
|
67
|
+
}): {
|
|
68
|
+
items: any[];
|
|
69
|
+
page: number;
|
|
70
|
+
perPage: number;
|
|
71
|
+
totalItems: number;
|
|
72
|
+
totalPages: number;
|
|
73
|
+
hasPrev: boolean;
|
|
74
|
+
hasNext: boolean;
|
|
75
|
+
};
|
|
76
|
+
#private;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=TinyArrayPaginator.d.mts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A predicate function used to determine whether an item should be included in the filtered results.
|
|
3
|
+
* Works similarly to the callback function of `Array.prototype.filter`.
|
|
4
|
+
*
|
|
5
|
+
* @callback GetFilter
|
|
6
|
+
* @param {any} value - The current element being processed in the array.
|
|
7
|
+
* @param {number} index - The index of the current element within the array.
|
|
8
|
+
* @param {any[]} array - The full array being processed.
|
|
9
|
+
* @returns {boolean} Returns `true` to include the element in the results, or `false` to exclude it.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* A encapsulated wrapper for array pagination.
|
|
13
|
+
* Provides methods to retrieve paginated results with metadata,
|
|
14
|
+
* while keeping the source array safe from direct modifications.
|
|
15
|
+
*/
|
|
16
|
+
class TinyArrayPaginator {
|
|
17
|
+
/**
|
|
18
|
+
* Internal storage for the paginated data source.
|
|
19
|
+
* @type {any[]|Set<any>}
|
|
20
|
+
*/
|
|
21
|
+
#data;
|
|
22
|
+
/**
|
|
23
|
+
* Gets current stored array.
|
|
24
|
+
* @returns {any[]|Set<any>}
|
|
25
|
+
*/
|
|
26
|
+
get data() {
|
|
27
|
+
return this.#data;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Replaces the current data array.
|
|
31
|
+
* @param {any[]|Set<any>} value - The new array to be used as the data source.
|
|
32
|
+
* @throws {TypeError} If the provided value is not an array.
|
|
33
|
+
*/
|
|
34
|
+
set data(value) {
|
|
35
|
+
if (!Array.isArray(value) && !(value instanceof Set))
|
|
36
|
+
throw new TypeError('Paginator expects an array or Set as data source.');
|
|
37
|
+
this.#data = value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets the total number of items in the current data array.
|
|
41
|
+
* @returns {number} Total number of stored items.
|
|
42
|
+
*/
|
|
43
|
+
get size() {
|
|
44
|
+
return Array.isArray(this.#data) ? this.#data.length : this.#data.size;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new paginator instance for the given data array.
|
|
48
|
+
* @param {any[]|Set<any>} data - The array to be paginated.
|
|
49
|
+
* @throws {TypeError} If the provided data is not an array.
|
|
50
|
+
*/
|
|
51
|
+
constructor(data) {
|
|
52
|
+
if (!Array.isArray(data) && !(data instanceof Set))
|
|
53
|
+
throw new TypeError('Paginator expects an array or Set as data source.');
|
|
54
|
+
this.#data = data;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Filters data according to a search query and returns paginated results.
|
|
58
|
+
* @param {Object} settings
|
|
59
|
+
* @param {number} settings.page - The page number (1-based index).
|
|
60
|
+
* @param {number} settings.perPage - Items per page.
|
|
61
|
+
* @param {Record<string, any> | GetFilter} [settings.filter=null] - Filtering criteria:
|
|
62
|
+
* - Object: key-value pairs for exact match, substring match, or RegExp
|
|
63
|
+
* - Function: custom filter function returning true for items to include
|
|
64
|
+
* @returns {{
|
|
65
|
+
* items: any[], // The subset of items for the requested page.
|
|
66
|
+
* page: number, // The current (validated) page number.
|
|
67
|
+
* perPage: number, // Number of items per page used in the calculation.
|
|
68
|
+
* totalItems: number, // Total number of items in the filtered data.
|
|
69
|
+
* totalPages: number, // Total number of pages available.
|
|
70
|
+
* hasPrev: boolean, // Whether a previous page exists.
|
|
71
|
+
* hasNext: boolean // Whether a next page exists.
|
|
72
|
+
* }}
|
|
73
|
+
*/
|
|
74
|
+
get({ page, perPage, filter }) {
|
|
75
|
+
if (!Number.isInteger(page) || page < 1)
|
|
76
|
+
throw new RangeError('Page number must be a positive integer.');
|
|
77
|
+
if (!Number.isInteger(perPage) || perPage < 1)
|
|
78
|
+
throw new RangeError('Items per page must be a positive integer.');
|
|
79
|
+
const data = Array.isArray(this.#data) ? this.#data : Array.from(this.#data);
|
|
80
|
+
let dataToUse = data;
|
|
81
|
+
if (filter) {
|
|
82
|
+
if (typeof filter === 'function') {
|
|
83
|
+
dataToUse = data.filter((item, idx, arr) => filter(item, idx, arr));
|
|
84
|
+
}
|
|
85
|
+
else if (typeof filter === 'object') {
|
|
86
|
+
dataToUse = data.filter((item) => Object.entries(filter).every(([key, value]) => {
|
|
87
|
+
const v = item[key];
|
|
88
|
+
if (value instanceof RegExp)
|
|
89
|
+
return value.test(v);
|
|
90
|
+
return v === value;
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new TypeError('Filter must be an object or a function');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
dataToUse = data;
|
|
99
|
+
}
|
|
100
|
+
const totalItems = dataToUse.length;
|
|
101
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / perPage));
|
|
102
|
+
// Ensure page is within valid range
|
|
103
|
+
const safePage = Math.min(page, totalPages);
|
|
104
|
+
const start = (safePage - 1) * perPage;
|
|
105
|
+
const end = start + perPage;
|
|
106
|
+
const hasPrev = safePage > 1;
|
|
107
|
+
const hasNext = safePage < totalPages;
|
|
108
|
+
return {
|
|
109
|
+
items: dataToUse.slice(start, end),
|
|
110
|
+
page: safePage,
|
|
111
|
+
perPage,
|
|
112
|
+
totalItems,
|
|
113
|
+
totalPages,
|
|
114
|
+
hasPrev,
|
|
115
|
+
hasNext,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export default TinyArrayPaginator;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} Category
|
|
5
|
+
* @property {string} label - Category label displayed to the user
|
|
6
|
+
* @property {boolean} required - If true, cannot be disabled
|
|
7
|
+
* @property {boolean} default - Default state (true = enabled, false = disabled)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} Config
|
|
12
|
+
* @property {string} message - Consent message
|
|
13
|
+
* @property {string} acceptText - Text for "Accept All" button
|
|
14
|
+
* @property {string} rejectText - Text for "Reject All" button
|
|
15
|
+
* @property {string} settingsText - Text for "Manage Settings" button
|
|
16
|
+
* @property {Category[]} categories - List of cookie categories
|
|
17
|
+
* @property {string} storageKey - Key used to store preferences
|
|
18
|
+
* @property {(preferences: Object) => void} onSave - Callback when preferences are saved
|
|
19
|
+
* @property {number} [animationDuration=400] - Animation duration in ms
|
|
20
|
+
* @property {((config: Config) => string)|null} [renderBar] - Optional custom HTML renderer for consent bar
|
|
21
|
+
* @property {((config: Config) => string)|null} [renderModal] - Optional custom HTML renderer for modal
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* CookieConsent
|
|
26
|
+
*
|
|
27
|
+
* A flexible and customizable cookie consent manager.
|
|
28
|
+
* Features:
|
|
29
|
+
* - Displays a consent bar with customizable text and buttons.
|
|
30
|
+
* - Allows defining multiple cookie categories (required, analytics, ads, etc.).
|
|
31
|
+
* - Stores user preferences in localStorage.
|
|
32
|
+
* - Provides callbacks for when user accepts or denies categories.
|
|
33
|
+
* - Fully customizable via configuration.
|
|
34
|
+
*/
|
|
35
|
+
class TinyCookieConsent {
|
|
36
|
+
/** @type {Record<string, boolean>} */
|
|
37
|
+
#preferences;
|
|
38
|
+
|
|
39
|
+
/** @type {Config} */
|
|
40
|
+
#config = {
|
|
41
|
+
message: 'We use cookies to improve your experience.',
|
|
42
|
+
acceptText: 'Accept All',
|
|
43
|
+
rejectText: 'Reject All',
|
|
44
|
+
settingsText: 'Manage Settings',
|
|
45
|
+
categories: [],
|
|
46
|
+
storageKey: 'cookie-consent-preferences',
|
|
47
|
+
onSave: (prefs) => console.log('Preferences saved:', prefs),
|
|
48
|
+
animationDuration: 400,
|
|
49
|
+
renderBar: null,
|
|
50
|
+
renderModal: null,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** @type {Record<string, boolean>} */
|
|
54
|
+
get preferences() {
|
|
55
|
+
const saved = localStorage.getItem(this.#config.storageKey);
|
|
56
|
+
return saved ? JSON.parse(saved) : {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @type {Config} */
|
|
60
|
+
get config() {
|
|
61
|
+
return { ...this.#config, categories: [...this.#config.categories] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @param {Config} value */
|
|
65
|
+
set config(value) {
|
|
66
|
+
this.validateConfig(value);
|
|
67
|
+
this.#config = Object.assign(this.#config, value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Config} config
|
|
72
|
+
*/
|
|
73
|
+
constructor(config) {
|
|
74
|
+
if (!config || typeof config !== 'object') throw new TypeError('Config must be an object.');
|
|
75
|
+
this.config = config;
|
|
76
|
+
const prefs = this.loadPreferences();
|
|
77
|
+
this.#preferences = prefs ?? {};
|
|
78
|
+
if (!prefs) this.showConsentBar();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validates config object against jsDoc typedefs
|
|
83
|
+
* @param {Partial<Config>} config
|
|
84
|
+
*/
|
|
85
|
+
validateConfig(config) {
|
|
86
|
+
if (typeof config.message !== 'undefined' && typeof config.message !== 'string')
|
|
87
|
+
throw new TypeError('Config.message must be a string.');
|
|
88
|
+
|
|
89
|
+
['acceptText', 'rejectText', 'settingsText', 'storageKey'].forEach((key) => {
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
if (typeof config[key] !== 'undefined' && typeof config[key] !== 'string') {
|
|
92
|
+
throw new TypeError(`Config.${key} must be a string.`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (typeof config.categories !== 'undefined') {
|
|
97
|
+
if (!Array.isArray(config.categories)) {
|
|
98
|
+
throw new TypeError('Config.categories must be an array of Category objects.');
|
|
99
|
+
}
|
|
100
|
+
config.categories.forEach((cat, i) => this.validateCategory(cat, i));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof config.onSave !== 'undefined' && typeof config.onSave !== 'function')
|
|
104
|
+
throw new TypeError('Config.onSave must be a function.');
|
|
105
|
+
|
|
106
|
+
if (typeof config.animationDuration !== 'undefined') {
|
|
107
|
+
if (typeof config.animationDuration !== 'number' || config.animationDuration < 0) {
|
|
108
|
+
throw new TypeError('Config.animationDuration must be a positive number.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
typeof config.renderBar !== 'undefined' &&
|
|
114
|
+
config.renderBar !== null &&
|
|
115
|
+
typeof config.renderBar !== 'function'
|
|
116
|
+
)
|
|
117
|
+
throw new TypeError('Config.renderBar must be a function or null.');
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
typeof config.renderModal !== 'undefined' &&
|
|
121
|
+
config.renderModal !== null &&
|
|
122
|
+
typeof config.renderModal !== 'function'
|
|
123
|
+
)
|
|
124
|
+
throw new TypeError('Config.renderModal must be a function or null.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validates a single category object
|
|
129
|
+
* @param {Category} cat
|
|
130
|
+
* @param {number} index
|
|
131
|
+
*/
|
|
132
|
+
validateCategory(cat, index) {
|
|
133
|
+
if (!cat || typeof cat !== 'object')
|
|
134
|
+
throw new TypeError(`Category at index ${index} must be an object.`);
|
|
135
|
+
if (typeof cat.label !== 'string')
|
|
136
|
+
throw new TypeError(`Category.label at index ${index} must be a string.`);
|
|
137
|
+
if (typeof cat.required !== 'boolean')
|
|
138
|
+
throw new TypeError(`Category.required at index ${index} must be a boolean.`);
|
|
139
|
+
if (typeof cat.default !== 'boolean')
|
|
140
|
+
throw new TypeError(`Category.default at index ${index} must be a boolean.`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Loads saved preferences from localStorage
|
|
145
|
+
* @returns {Record<string, boolean>|null}
|
|
146
|
+
*/
|
|
147
|
+
loadPreferences() {
|
|
148
|
+
const saved = localStorage.getItem(this.#config.storageKey);
|
|
149
|
+
return saved ? JSON.parse(saved) : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Smoothly removes an element with fade-out/slide-out animation
|
|
154
|
+
* @param {HTMLElement} el
|
|
155
|
+
*/
|
|
156
|
+
removeWithAnimation(el) {
|
|
157
|
+
if (!(el instanceof HTMLElement))
|
|
158
|
+
throw new TypeError('removeWithAnimation expects an HTMLElement.');
|
|
159
|
+
el.classList.add('closing');
|
|
160
|
+
setTimeout(() => el.remove(), this.#config.animationDuration);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Saves preferences to localStorage
|
|
165
|
+
*
|
|
166
|
+
* @param {Record<string, boolean>} prefs
|
|
167
|
+
*/
|
|
168
|
+
savePreferences(prefs) {
|
|
169
|
+
if (!prefs || typeof prefs !== 'object') throw new TypeError('Preferences must be an object.');
|
|
170
|
+
localStorage.setItem(this.#config.storageKey, JSON.stringify(prefs));
|
|
171
|
+
this.#config.onSave(prefs);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Shows the initial consent bar */
|
|
175
|
+
showConsentBar() {
|
|
176
|
+
const bar = document.createElement('div');
|
|
177
|
+
bar.className = 'cookie-consent-bar';
|
|
178
|
+
bar.innerHTML = this.#config.renderBar
|
|
179
|
+
? this.#config.renderBar(this.#config)
|
|
180
|
+
: `
|
|
181
|
+
<div><p>${this.#config.message}</p></div>
|
|
182
|
+
<div><button class="accept">${this.#config.acceptText}</button></div>
|
|
183
|
+
<div><button class="reject">${this.#config.rejectText}</button></div>
|
|
184
|
+
<div><button class="settings">${this.#config.settingsText}</button></div>
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
document.body.appendChild(bar);
|
|
188
|
+
|
|
189
|
+
const accept = bar.querySelector('.accept');
|
|
190
|
+
if (accept instanceof HTMLElement) {
|
|
191
|
+
accept.onclick = () => {
|
|
192
|
+
/** @type {Record<string, boolean>} */
|
|
193
|
+
const prefs = {};
|
|
194
|
+
this.#config.categories.forEach((cat) => (prefs[cat.label] = true));
|
|
195
|
+
this.savePreferences(prefs);
|
|
196
|
+
this.removeWithAnimation(bar);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const reject = bar.querySelector('.reject');
|
|
201
|
+
if (reject instanceof HTMLElement) {
|
|
202
|
+
reject.onclick = () => {
|
|
203
|
+
/** @type {Record<string, boolean>} */
|
|
204
|
+
const prefs = {};
|
|
205
|
+
this.#config.categories.forEach((cat) => (prefs[cat.label] = cat.required));
|
|
206
|
+
this.savePreferences(prefs);
|
|
207
|
+
this.removeWithAnimation(bar);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const settings = bar.querySelector('.settings');
|
|
212
|
+
if (settings instanceof HTMLElement) {
|
|
213
|
+
settings.onclick = () => this.showSettingsModal(bar);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Shows settings modal for fine-grained control
|
|
219
|
+
*
|
|
220
|
+
* @param {HTMLElement} bar
|
|
221
|
+
*/
|
|
222
|
+
showSettingsModal(bar) {
|
|
223
|
+
const modal = document.createElement('div');
|
|
224
|
+
modal.className = 'cookie-consent-modal';
|
|
225
|
+
modal.innerHTML = this.#config.renderModal
|
|
226
|
+
? this.#config.renderModal(this.#config)
|
|
227
|
+
: `
|
|
228
|
+
<div class="modal-content">
|
|
229
|
+
<h2>Cookie Settings</h2>
|
|
230
|
+
<form class="settings-form">
|
|
231
|
+
${this.#config.categories
|
|
232
|
+
.map(
|
|
233
|
+
(cat) => `
|
|
234
|
+
<label>
|
|
235
|
+
<input type="checkbox" name="${cat.label}"
|
|
236
|
+
${cat.default ? 'checked' : ''}
|
|
237
|
+
${cat.required ? 'disabled' : ''}>
|
|
238
|
+
${cat.label}
|
|
239
|
+
</label>
|
|
240
|
+
`,
|
|
241
|
+
)
|
|
242
|
+
.join('')}
|
|
243
|
+
</form>
|
|
244
|
+
<button class="save">Save Preferences</button>
|
|
245
|
+
</div>
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
document.body.appendChild(modal);
|
|
249
|
+
|
|
250
|
+
const save = modal.querySelector('.save');
|
|
251
|
+
if (save instanceof HTMLElement) {
|
|
252
|
+
save.onclick = () => {
|
|
253
|
+
/** @type {Record<string, boolean>} */
|
|
254
|
+
const prefs = {};
|
|
255
|
+
modal.querySelectorAll('input[type=checkbox]').forEach((input) => {
|
|
256
|
+
if (input instanceof HTMLInputElement) prefs[input.name] = input.checked;
|
|
257
|
+
});
|
|
258
|
+
this.savePreferences(prefs);
|
|
259
|
+
this.removeWithAnimation(modal);
|
|
260
|
+
if (bar) this.removeWithAnimation(bar);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Checks if a category is allowed
|
|
267
|
+
* @param {string} category
|
|
268
|
+
* @returns {boolean}
|
|
269
|
+
*/
|
|
270
|
+
isAllowed(category) {
|
|
271
|
+
if (typeof category !== 'string')
|
|
272
|
+
throw new TypeError('isAllowed expects a string as category.');
|
|
273
|
+
if (!this.#preferences) return false;
|
|
274
|
+
return !!this.#preferences[category];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = TinyCookieConsent;
|