@arex95/vue-core 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,2460 +1,2700 @@
1
+ import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize } from '@vueuse/core';
1
2
  import axios from 'axios';
3
+ import { useRouter } from 'vue-router';
2
4
  import { useQuery } from '@tanstack/vue-query';
3
5
  import { ref, watch, onServerPrefetch, onMounted, computed } from 'vue';
4
- import { useBreakpoints, breakpointsTailwind, useWindowSize, useTimeoutFn } from '@vueuse/core';
5
6
 
6
7
  /**
7
- * A standard REST API interface for handling basic CRUD operations.
8
- * This class can be instantiated with a resource endpoint and a fetch composable for API requests.
9
- */
10
- class RestStd {
11
- static resource;
12
- static fetchComposable;
13
- /**
14
- * Fetch a list of items from the server.
15
- *
16
- * @param params Query parameters for filtering the results.
17
- * @param options Additional options for the fetch composable.
18
- * @returns The result of the fetch composable (typically a promise).
19
- */
20
- static getMany(params, options = {}) {
21
- return this.fetchComposable({
22
- method: 'GET',
23
- url: this.resource,
24
- params,
25
- }, options);
26
- }
27
- /**
28
- * Fetch a single item by ID from the server.
29
- *
30
- * @param id The ID of the item to fetch.
31
- * @param params Additional query parameters for the request.
32
- * @param options Additional options for the fetch composable.
33
- * @returns The result of the fetch composable (typically a promise).
34
- */
35
- static getOne(id, params, options = {}) {
36
- return this.fetchComposable({
37
- method: 'GET',
38
- url: `${this.resource}/${id}`,
39
- params,
40
- }, options);
41
- }
42
- /**
43
- * Create a new item on the server.
44
- *
45
- * @param data The data for the new item to create.
46
- * @param options Additional options for the fetch composable.
47
- * @returns The result of the fetch composable (typically a promise).
48
- */
49
- static create(data, options = {}) {
50
- return this.fetchComposable({
51
- method: 'POST',
52
- url: this.resource,
53
- data,
54
- }, options);
55
- }
56
- /**
57
- * Update an existing item on the server.
58
- *
59
- * @param id The ID of the item to update.
60
- * @param data The updated data for the item.
61
- * @param options Additional options for the fetch composable.
62
- * @returns The result of the fetch composable (typically a promise).
63
- */
64
- static update(id, data, options = {}) {
65
- return this.fetchComposable({
66
- method: 'PUT',
67
- url: `${this.resource}/${id}`,
68
- data,
69
- }, options);
70
- }
71
- /**
72
- * Partially update an existing item on the server.
73
- *
74
- * @param id The ID of the item to update.
75
- * @param data The updated data for the item.
76
- * @param options Additional options for the fetch composable.
77
- * @returns The result of the fetch composable (typically a promise).
78
- */
79
- static patch(id, data, options = {}) {
80
- return this.fetchComposable({
81
- method: 'PATCH',
82
- url: `${this.resource}/${id}`,
83
- data,
84
- }, options);
85
- }
86
- /**
87
- * Delete an item from the server.
88
- *
89
- * @param id The ID of the item to delete.
90
- * @param options Additional options for the fetch composable.
91
- * @returns The result of the fetch composable (typically a promise).
92
- */
93
- static delete(id, options = {}) {
94
- return this.fetchComposable({
95
- method: 'DELETE',
96
- url: `${this.resource}/${id}`,
97
- }, options);
98
- }
99
- /**
100
- * Custom request method for more flexibility.
101
- *
102
- * @param method HTTP method (GET, POST, etc.).
103
- * @param params Query parameters.
104
- * @param data Request body data.
105
- * @param token Authorization token (optional).
106
- * @param options Additional options for the fetch composable.
107
- * @returns The result of the fetch composable (typically a promise).
108
- */
109
- static customRequest(method, params, data, options = {}) {
110
- return this.fetchComposable({
111
- method: method,
112
- url: this.resource,
113
- params: params,
114
- data: data,
115
- }, options);
116
- }
117
- }
118
-
119
- /**
120
- * Custom composable function for making API requests with Axios.
121
- *
122
- * @template T The expected return type of the API request.
123
- * @param {AxiosInstance} axios - The Axios instance for making HTTP requests.
124
- * @param {AxiosRequestConfig} axiosRequest - Configuration for the Axios request.
125
- * @returns {Promise<T>} A promise with the result of the API request.
126
- */
127
- async function axiosFetch(axios, axiosRequest) {
128
- return axios(axiosRequest)
129
- .then(response => response.data)
130
- .catch(error => {
131
- throw error;
132
- });
133
- }
134
-
135
- /**
136
- * AxiosService class encapsulates the Axios configuration and logic.
137
- * It manages request and response interceptors and provides methods for making HTTP requests.
138
- */
139
- class AxiosService {
140
- instance;
141
- cancelTokenSource;
142
- activeRequests = 0; // Track active requests
143
- /**
144
- * Initializes the AxiosService instance by creating an Axios instance with default configuration
145
- * and setting up request and response interceptors.
146
- */
147
- constructor(url) {
148
- this.cancelTokenSource = axios.CancelToken.source();
149
- this.instance = axios.create({
150
- baseURL: url ?? '',
151
- timeout: 300000,
152
- headers: {
153
- Accept: 'application/json',
154
- 'Content-Type': 'application/json',
155
- },
156
- withCredentials: false,
157
- });
158
- this.initializeInterceptors();
159
- }
160
- /**
161
- * Initializes request and response interceptors for the Axios instance.
162
- */
163
- initializeInterceptors() {
164
- this.instance.interceptors.request.use((config) => {
165
- const token = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null;
166
- if (token && config.headers) {
167
- config.headers.Authorization = `Bearer ${token}`;
168
- }
169
- config.cancelToken = this.cancelTokenSource.token;
170
- this.activeRequests++;
171
- return config;
172
- }, (error) => {
173
- // handleError(error);
174
- console.error('Request error:', error.message);
175
- this.activeRequests++;
176
- return Promise.reject(error);
177
- });
178
- this.instance.interceptors.response.use((response) => {
179
- this.activeRequests--;
180
- return response;
181
- }, (error) => {
182
- if (axios.isCancel(error)) {
183
- console.warn('Request canceled:', error.message);
184
- }
185
- else if (axios.isAxiosError(error)) {
186
- // handleError(error);
187
- console.error('Response error:', error.response?.status, error.message);
188
- if (error.response?.status === 401) {
189
- this.handleUnauthorized();
190
- }
191
- }
192
- else {
193
- console.error('Unexpected error:', error);
194
- }
195
- this.activeRequests--;
196
- return Promise.reject(error);
197
- });
198
- }
199
- /**
200
- * Returns the number of active requests.
201
- */
202
- getActiveRequests() {
203
- return this.activeRequests;
204
- }
205
- /**
206
- * Handles unauthorized access errors (401).
207
- */
208
- handleUnauthorized() {
209
- console.warn('Unauthorized access - redirecting to login.');
210
- }
211
- /**
212
- * Returns the Axios instance with the configured settings and interceptors.
213
- * @returns {AxiosInstance} The configured Axios instance.
214
- */
215
- getAxiosInstance() {
216
- return this.instance;
217
- }
218
- /**
219
- * Cancels all ongoing requests.
220
- */
221
- cancelAllRequests() {
222
- this.cancelTokenSource.cancel('Operation canceled by the user.');
223
- this.cancelTokenSource = axios.CancelToken.source();
224
- }
225
- /**
226
- * Sets a new header for the Axios instance.
227
- * @param {string} key - The header key.
228
- * @param {string} value - The header value.
229
- */
230
- setHeader(key, value) {
231
- this.instance.defaults.headers.common[key] = value;
232
- }
233
- /**
234
- * Removes a header from the Axios instance.
235
- * @param {string} key - The header key to remove.
236
- */
237
- removeHeader(key) {
238
- delete this.instance.defaults.headers.common[key];
239
- }
240
- }
241
-
242
- let axiosInstance;
243
- const configureAxios = (baseURL) => {
244
- axiosInstance = new AxiosService(baseURL);
245
- };
246
- const getAxiosInstance = () => {
247
- if (!axiosInstance) {
248
- throw new Error('Axios instance not configured. Call configureAxios first.');
249
- }
250
- return axiosInstance.getAxiosInstance();
251
- };
252
-
253
- /**
254
- * Creates a fetchComposable for Axios requests with an optional custom instance.
255
- * @param fetchComposable - The composable to use for the request (can be any fetch function).
256
- * @param axiosCustomInstance - An optional custom Axios instance to use for the request.
257
- * @returns A function that handles the request using the provided composable.
258
- */
259
- function createFetch(fetchComposable, axiosCustomInstance) {
260
- const instance = axiosCustomInstance || getAxiosInstance();
261
- return (axiosRequestConfig, options) => {
262
- return fetchComposable(instance, axiosRequestConfig, options);
263
- };
264
- }
265
-
266
- /**
267
- * Custom composable for integrating Axios requests with Vue Query.
268
- *
269
- * @template T - The type of data expected from the query.
270
- * @param {AxiosInstance} axios - Axios instance used for making HTTP requests.
271
- * @param {AxiosRequestConfig} axiosRequest - Initial Axios request configuration.
272
- * @param {ExtendedQueryOptions<T>} [queryOptions] - Optional Vue Query options with server execution flag.
273
- * @returns {object} Composable functions and query state for managing API requests.
274
- */
275
- function useVueQuery(axios, axiosRequest, queryOptions) {
276
- const key = ref([
277
- queryOptions?.queryKey || `${axiosRequest.url}-${JSON.stringify(axiosRequest)}`
278
- ]);
279
- const axiosRequestRef = ref(axiosRequest);
280
- const queryOptionsRef = ref(queryOptions);
281
- const isInitialized = ref(false);
282
- const errorCallbacks = ref([]);
283
- const resultCallbacks = ref([]);
284
- /**
285
- * Vue Query instance for managing API requests.
286
- */
287
- const query = useQuery({
288
- queryKey: key.value,
289
- queryFn: async () => {
290
- const response = await axios(axiosRequestRef.value);
291
- return response.data;
292
- },
293
- ...(queryOptionsRef.value?.options ?? {})
294
- });
295
- /**
296
- * Executes the query manually with optional new key and request parameters.
297
- *
298
- * @param {Array<any>} [newKey] - New query key to use.
299
- * @param {object} [newRequestParams] - New request parameters to use.
300
- * @param {ExtendedQueryOptions<T>} [newQueryOptions] - New Vue Query options to use.
301
- */
302
- function execute(newKey, newRequestParams, newQueryOptions) {
303
- if (newKey) {
304
- key.value = newKey;
305
- }
306
- if (newRequestParams) {
307
- axiosRequestRef.value.data = newRequestParams;
308
- }
309
- if (newQueryOptions) {
310
- queryOptionsRef.value = newQueryOptions;
311
- }
312
- query.refetch();
313
- }
314
- /**
315
- * Registers a callback to be executed on successful request result.
316
- * If data is already available, the callback will be executed immediately.
317
- *
318
- * @param {(data: T) => void} cb - Callback function to handle the result.
319
- */
320
- function onResult(cb) {
321
- resultCallbacks.value.push(cb);
322
- if (query.data.value !== undefined && query.data.value !== null) {
323
- cb(query.data.value);
324
- }
325
- }
326
- /**
327
- * Registers a callback to be executed when the request results in an error.
328
- * If an error is already present, the callback will be executed immediately.
329
- *
330
- * @param {(e: unknown) => void} callback - Callback function to handle errors.
331
- */
332
- function onError(callback) {
333
- errorCallbacks.value.push(callback);
334
- if (query.error.value !== undefined) {
335
- callback(query.error.value);
336
- }
8
+ * Creates and downloads a file from Blob data.
9
+ * @param {Blob} blob The Blob containing the file data.
10
+ * @param {string} fileName The name of the file to create.
11
+ */
12
+ function downloadFile(blob, fileName) {
13
+ const link = document.createElement('a');
14
+ const url = URL.createObjectURL(blob);
15
+ link.setAttribute('href', url);
16
+ link.setAttribute('download', fileName);
17
+ // Append link to the body and trigger a click
18
+ document.body.appendChild(link);
19
+ link.click();
20
+ // Clean up
21
+ document.body.removeChild(link);
22
+ URL.revokeObjectURL(url);
23
+ }
24
+ /**
25
+ * Exports data to a CSV file.
26
+ * @param {string[]} headers The headers for the CSV.
27
+ * @param {any[][]} data The data to export, as an array of arrays.
28
+ * @param {string} fileName The name of the file to create.
29
+ */
30
+ function exportToCSV(headers, data, fileName) {
31
+ const csvRows = [];
32
+ // Add the headers
33
+ csvRows.push(headers.map(header => `"${header.replace(/"/g, '""')}"`).join(','));
34
+ // Add the data rows
35
+ for (const row of data) {
36
+ csvRows.push(row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','));
337
37
  }
338
- /**
339
- * Watches for changes in the query result and triggers result callbacks.
340
- */
341
- watch(query.data, (newData) => {
342
- if (newData !== undefined && newData !== null && isInitialized.value) {
343
- resultCallbacks.value.forEach((cb) => cb(newData));
344
- }
345
- });
346
- /**
347
- * Watches for changes in query errors and triggers error callbacks.
348
- */
349
- watch(query.error, (newError) => {
350
- if (newError !== undefined && isInitialized.value) {
351
- errorCallbacks.value.forEach((cb) => cb(newError));
352
- }
353
- });
354
- /**
355
- * Handles server-side prefetching for SSR.
356
- * Ensures data is fetched before rendering on the server.
357
- */
358
- onServerPrefetch(async () => {
359
- if (queryOptionsRef.value?.server !== false) {
360
- await query.suspense();
361
- if (query.data.value != null) {
362
- resultCallbacks.value.forEach((cb) => cb(query.data.value));
363
- }
364
- }
38
+ // Create a Blob with CSV data
39
+ const csvContent = csvRows.join('\n');
40
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
41
+ // Use the download utility function
42
+ downloadFile(blob, fileName);
43
+ }
44
+ /**
45
+ * Exports data to an Excel file (.xls) using HTML table.
46
+ * @param {string[]} headers The headers for the Excel file.
47
+ * @param {any[][]} data The data to export, as an array of arrays.
48
+ * @param {string} fileName The name of the file to create.
49
+ */
50
+ function exportToExcel(headers, data, fileName) {
51
+ // Create a table element
52
+ const table = document.createElement('table');
53
+ const thead = table.createTHead();
54
+ const tbody = table.createTBody();
55
+ // Create the header row
56
+ const headerRow = thead.insertRow();
57
+ headers.forEach(header => {
58
+ const th = document.createElement('th');
59
+ th.textContent = header;
60
+ headerRow.appendChild(th);
365
61
  });
366
- /**
367
- * Ensures callbacks are executed after the component is mounted on the client-side.
368
- */
369
- onMounted(() => {
370
- isInitialized.value = true;
371
- if (query.data.value != null) {
372
- resultCallbacks.value.forEach((cb) => cb(query.data.value));
373
- }
374
- if (query.error.value !== undefined) {
375
- errorCallbacks.value.forEach((cb) => cb(query.error.value));
376
- }
62
+ // Create the data rows
63
+ data.forEach(row => {
64
+ const tr = tbody.insertRow();
65
+ row.forEach(cell => {
66
+ const td = tr.insertCell();
67
+ td.textContent = String(cell);
68
+ });
377
69
  });
378
- return {
379
- execute,
380
- onResult,
381
- onError,
382
- ...query
383
- };
70
+ // Create a Blob with the table HTML
71
+ const html = table.outerHTML;
72
+ const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8;' });
73
+ // Use the download utility function
74
+ downloadFile(blob, fileName);
384
75
  }
385
-
386
- let notified = false;
387
76
  /**
388
- * Composable to manage breakpoints using Tailwind CSS and shared window resize listener.
389
- *
390
- * @returns {Object} An object containing the breakpoints and their states.
77
+ * Exports data to a JSON file.
78
+ * @param {any[]} data The data to export.
79
+ * @param {string} fileName The name of the file to create.
391
80
  */
392
- function useBreakpoint() {
393
- const breakpoints = useBreakpoints(breakpointsTailwind);
394
- const current = breakpoints.current();
395
- const active = breakpoints.active();
396
- // Define breakpoints
397
- const sm_S = breakpoints.smaller('sm');
398
- const sm_SE = breakpoints.smallerOrEqual('sm');
399
- const sm_GE = breakpoints.greaterOrEqual('sm');
400
- const sm_md = breakpoints.between('sm', 'md');
401
- const sm_xl = breakpoints.between('sm', 'xl');
402
- const sm_2xl = breakpoints.between('sm', '2xl');
403
- const md_S = breakpoints.smaller('md');
404
- const md_SE = breakpoints.smallerOrEqual('md');
405
- const md_GE = breakpoints.greaterOrEqual('md');
406
- const md_lg = breakpoints.between('md', 'lg');
407
- const md_xl = breakpoints.between('md', 'xl');
408
- const md_2xl = breakpoints.between('md', '2xl');
409
- const lg_S = breakpoints.smaller('lg');
410
- const lg_SE = breakpoints.smallerOrEqual('lg');
411
- const lg_GE = breakpoints.greaterOrEqual('lg');
412
- const lg_xl = breakpoints.between('lg', 'xl');
413
- const lg_2xl = breakpoints.between('lg', '2xl');
414
- const xl_S = breakpoints.smaller('xl');
415
- const xl_SE = breakpoints.smallerOrEqual('xl');
416
- const xl_GE = breakpoints.greaterOrEqual('xl');
417
- const xl_2xl = breakpoints.between('xl', '2xl');
418
- const _2xl_S = breakpoints.smaller('2xl');
419
- const _2xl_SE = breakpoints.smallerOrEqual('2xl');
420
- const _2xl_GE = breakpoints.greaterOrEqual('2xl');
421
- const reactiveBreakpoint = ref('sm');
422
- const isGreaterThanBreakpoint = breakpoints.greaterOrEqual(() => reactiveBreakpoint.value);
423
- // Define device ranges
424
- const mobile = breakpoints.smaller('md'); // <768px
425
- const tablet = breakpoints.between('md', 'lg'); // 768px - 1024px
426
- const laptop = breakpoints.between('lg', 'xl'); // 1024px - 1280px
427
- const desktop = breakpoints.greaterOrEqual('xl'); // >=1280px
428
- // Use VueUse for window size
429
- const { width: windowWidth, height: windowHeight } = useWindowSize();
430
- /**
431
- * Logs a styled "chip" message to the console.
432
- *
433
- * @param {string} label - The label to display inside the chip.
434
- * @param {string} backgroundColor - The background color of the chip.
435
- * @param {string} textColor - The text color of the chip.
436
- */
437
- const logChip = (label, backgroundColor, textColor = 'white') => {
438
- console.log(`%c ${label} `, `background-color: ${backgroundColor}; color: ${textColor}; border-radius: 16px; padding: 4px 8px;`);
439
- };
440
- // Singleton logic: Only notify once
441
- const notifyOnce = () => {
442
- if (notified)
443
- return;
444
- watch([mobile, tablet, laptop, desktop], ([isMobile, isTablet, isLaptop, isDesktop]) => {
445
- if (isMobile) {
446
- logChip('Mobile', '#ff6b6b');
447
- }
448
- else if (isTablet) {
449
- logChip('Tablet', '#4ecdc4');
450
- }
451
- else if (isLaptop) {
452
- logChip('Laptop', '#1a535c');
453
- }
454
- else if (isDesktop) {
455
- logChip('Desktop', '#f7fff7', 'black');
456
- }
457
- notified = true; // Mark as notified
458
- }, { immediate: true });
459
- };
460
- notifyOnce(); // Ensure the notification happens only once globally
461
- return {
462
- current,
463
- active,
464
- sm_S,
465
- sm_SE,
466
- sm_GE,
467
- sm_md,
468
- sm_xl,
469
- sm_2xl,
470
- md_S,
471
- md_SE,
472
- md_GE,
473
- md_lg,
474
- md_xl,
475
- md_2xl,
476
- lg_S,
477
- lg_SE,
478
- lg_GE,
479
- lg_xl,
480
- lg_2xl,
481
- xl_S,
482
- xl_SE,
483
- xl_GE,
484
- xl_2xl,
485
- _2xl_S,
486
- _2xl_SE,
487
- _2xl_GE,
488
- isGreaterThanBreakpoint,
489
- reactiveBreakpoint,
490
- breakpointsTailwind,
491
- windowWidth,
492
- windowHeight,
493
- mobile,
494
- tablet,
495
- laptop,
496
- desktop,
497
- breakpoints,
498
- };
81
+ function exportToJSON(data, fileName) {
82
+ const jsonContent = JSON.stringify(data, null, 2); // Pretty print with 2 spaces
83
+ const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' });
84
+ // Use the download utility function
85
+ downloadFile(blob, fileName);
499
86
  }
500
-
501
87
  /**
502
- * A composable function that filters objects based on a specific field and criteria.
503
- *
504
- * @param {Object[]} items - The list of objects to filter.
505
- * @param {Object} filterConfig - Configuration for the filter, specifying the field, the data type to filter, and the criteria.
506
- * @param {string} filterConfig.field - The field in the objects to filter.
507
- * @param {string} filterConfig.type - The data type of the field ('date', 'string', 'number', 'boolean').
508
- * @param {any} filterConfig.criteria - The criteria for filtering. Can be a date range, a string to search, a number range, or a boolean value.
509
- *
510
- * @returns {Object[]} - The filtered list of objects.
88
+ * Exports data to an XML file.
89
+ * @param {string[]} headers The headers for the XML.
90
+ * @param {any[][]} data The data to export, as an array of arrays.
91
+ * @param {string} fileName The name of the file to create.
511
92
  */
512
- function useFilter(items, filterConfig) {
513
- // Ensure the filterConfig is correctly structured
514
- const { field, type, criteria } = filterConfig;
515
- // Define the filter functions
516
- const filterByDate = (objects, field, { startDate, endDate }) => objects.filter(item => {
517
- const date = new Date(item[field]);
518
- return date >= new Date(startDate) && date <= new Date(endDate);
93
+ function exportToXML(headers, data, fileName) {
94
+ let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<rows>\n';
95
+ // Add headers
96
+ xmlContent += ' <header>\n';
97
+ headers.forEach(header => {
98
+ xmlContent += ` <column>${header}</column>\n`;
519
99
  });
520
- const filterByString = (objects, field, searchTerm) => {
521
- const normalize = (str) => str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
522
- const words = normalize(searchTerm).split(/\s+/).filter(Boolean);
523
- return objects.filter(item => {
524
- const normalizedFieldValue = normalize(item[field]);
525
- return words.every(word => normalizedFieldValue.includes(word));
100
+ xmlContent += ' </header>\n';
101
+ // Add data rows
102
+ data.forEach(row => {
103
+ xmlContent += ' <row>\n';
104
+ row.forEach(cell => {
105
+ xmlContent += ` <cell>${cell}</cell>\n`;
526
106
  });
527
- };
528
- const filterByNumber = (objects, field, { min, max }) => objects.filter(item => {
529
- const value = item[field];
530
- return value >= min && value <= max;
531
- });
532
- const filterByBoolean = (objects, field, expectedValue) => objects.filter(item => item[field] === expectedValue);
533
- // Apply the appropriate filter and return the filtered objects
534
- const filteredObjects = computed(() => {
535
- if (!criteria ||
536
- (type === 'date' && (!criteria.startDate || !criteria.endDate)) ||
537
- (type === 'number' && (criteria.min == null || criteria.max == null)) ||
538
- (type === 'boolean' && typeof criteria !== 'boolean')) {
539
- return items; // Return all objects if criteria is not valid
540
- }
541
- // Apply the filter based on the type
542
- switch (type) {
543
- case 'date':
544
- return filterByDate(items, field, criteria);
545
- case 'string':
546
- return filterByString(items, field, criteria);
547
- case 'number':
548
- return filterByNumber(items, field, criteria);
549
- case 'boolean':
550
- return filterByBoolean(items, field, criteria);
551
- default:
552
- return items;
553
- }
107
+ xmlContent += ' </row>\n';
554
108
  });
555
- return filteredObjects.value;
109
+ xmlContent += '</rows>';
110
+ const blob = new Blob([xmlContent], { type: 'application/xml;charset=utf-8;' });
111
+ // Use the download utility function
112
+ downloadFile(blob, fileName);
556
113
  }
557
-
558
114
  /**
559
- * Composable to handle pagination logic.
560
- *
561
- * @param page - Reactive ref for the current page number.
562
- * @param total - Reactive ref for total number of items.
563
- * @param pageSize - Reactive ref for number of items per page.
564
- * @returns An object with pagination controls.
115
+ * Exports data to a plain text file.
116
+ * @param {string[]} headers The headers for the text file.
117
+ * @param {any[][]} data The data to export, as an array of arrays.
118
+ * @param {string} fileName The name of the file to create.
565
119
  */
566
- function usePagination(page, total, pageSize) {
567
- // Calculate the total number of pages
568
- const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
569
- // Determine if the next page can be fetched
570
- const canFetchNextPage = () => page.value < totalPages.value;
571
- // Determine if the previous page can be fetched
572
- const canFetchPreviousPage = () => page.value > 1;
573
- return {
574
- totalPages,
575
- canFetchNextPage,
576
- canFetchPreviousPage
577
- };
120
+ function exportToText(headers, data, fileName) {
121
+ const textRows = [];
122
+ // Add headers
123
+ textRows.push(headers.join('\t'));
124
+ // Add data rows
125
+ data.forEach(row => {
126
+ textRows.push(row.join('\t'));
127
+ });
128
+ // Create a Blob with text data
129
+ const textContent = textRows.join('\n');
130
+ const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8;' });
131
+ // Use the download utility function
132
+ downloadFile(blob, fileName);
578
133
  }
579
134
 
580
135
  /**
581
- * Custom composable for sorting products based on a selected criterion.
582
- * @param {Array} items - The array of products to sort.
583
- * @param {Array} criteriaList - The list of available sorting criteria.
584
- * @param {Number} selectedCriteria - The currently selected sorting criterion.
585
- * @returns {ComputedRef<Array>} - The sorted array of products.
136
+ * Opens a new window with the specified URL and options.
137
+ * @param {string} url The URL to open.
138
+ * @param {Object} [opt] Options for the new window.
139
+ * @param {string} [opt.target='__blank'] The target window name.
140
+ * @param {boolean} [opt.noopener=true] Whether to add 'noopener' attribute.
141
+ * @param {boolean} [opt.noreferrer=true] Whether to add 'noreferrer' attribute.
586
142
  */
587
- function useSorter(items, criteriaList, selectedCriteria) {
588
- return computed(() => {
589
- const criteria = criteriaList.find(item => item.value === Number(selectedCriteria));
590
- if (!criteria)
591
- return items;
592
- return [...items].sort((a, b) => {
593
- const aValue = a[criteria.field];
594
- const bValue = b[criteria.field];
595
- if (criteria.type === 'number') {
596
- const numA = typeof aValue === 'number' ? aValue : parseFloat(aValue) || 0;
597
- const numB = typeof bValue === 'number' ? bValue : parseFloat(bValue) || 0;
598
- return criteria.order === 'asc' ? numA - numB : numB - numA;
599
- }
600
- else if (criteria.type === 'date') {
601
- const dateA = new Date(aValue).getTime();
602
- const dateB = new Date(bValue).getTime();
603
- return criteria.order === 'asc' ? dateA - dateB : dateB - dateA;
604
- }
605
- else if (criteria.type === 'boolean') {
606
- const boolA = !!aValue;
607
- const boolB = !!bValue;
608
- return criteria.order === 'asc' ? (boolA === boolB ? 0 : boolA ? 1 : -1) : (boolA === boolB ? 0 : boolA ? -1 : 1);
609
- }
610
- else {
611
- // Fallback for string sorting
612
- const strA = (aValue || '').toString().toLowerCase();
613
- const strB = (bValue || '').toString().toLowerCase();
614
- return criteria.order === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
615
- }
616
- });
617
- }).value;
143
+ function openWindow(url, opt) {
144
+ const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
145
+ const features = [];
146
+ // Append noopener and noreferrer attributes if enabled
147
+ if (noopener)
148
+ features.push('noopener=yes');
149
+ if (noreferrer)
150
+ features.push('noreferrer=yes');
151
+ // Open the window with the specified features
152
+ window.open(url, target, features.join(','));
618
153
  }
619
-
620
154
  /**
621
- * Object defining available screen sizes as constants.
622
- * @readonly
155
+ * Copies text to the clipboard.
156
+ * @param {string} text The text to copy.
157
+ * @returns {Promise<void>} A promise that resolves when the text has been copied.
623
158
  */
624
- const sizeEnum = {
625
- XS: 'XS',
626
- SM: 'SM',
627
- MD: 'MD',
628
- LG: 'LG',
629
- XL: 'XL',
630
- XXL: 'XXL',
631
- };
159
+ async function copyToClipboard(text) {
160
+ if (navigator.clipboard) {
161
+ await navigator.clipboard.writeText(text);
162
+ }
163
+ else {
164
+ // Fallback for browsers that do not support the Clipboard API
165
+ const textArea = document.createElement('textarea');
166
+ textArea.value = text;
167
+ document.body.appendChild(textArea);
168
+ textArea.select();
169
+ document.execCommand('copy');
170
+ document.body.removeChild(textArea);
171
+ }
172
+ }
632
173
  /**
633
- * Object defining breakpoints for design based on screen width.
634
- * Values are in pixels.
635
- * @readonly
174
+ * Scrolls the window to the top smoothly.
175
+ * @param {number} [duration=300] Duration of the scroll animation in milliseconds.
636
176
  */
637
- const screenEnum = {
638
- XS: 480,
639
- SM: 576,
640
- MD: 768,
641
- LG: 992,
642
- XL: 1200,
643
- XXL: 1600,
644
- };
177
+ function scrollToTop(duration = 300) {
178
+ const start = window.scrollY;
179
+ const startTime = performance.now();
180
+ function scroll() {
181
+ const elapsed = performance.now() - startTime;
182
+ const progress = Math.min(elapsed / duration, 1);
183
+ window.scrollTo(0, start * (1 - progress));
184
+ if (progress < 1) {
185
+ requestAnimationFrame(scroll);
186
+ }
187
+ }
188
+ requestAnimationFrame(scroll);
189
+ }
645
190
  /**
646
- * Map that associates each screen size defined in `sizeEnum` with its corresponding pixel value from `screenEnum`.
647
- * @type {Map<typeof sizeEnum[keyof typeof sizeEnum], number>}
191
+ * Gets the value of a query parameter from the URL.
192
+ * @param {string} paramName The name of the query parameter.
193
+ * @returns {string | null} The value of the query parameter, or null if it does not exist.
648
194
  */
649
- const screenMap = new Map();
650
- screenMap.set(sizeEnum.XS, screenEnum.XS);
651
- screenMap.set(sizeEnum.SM, screenEnum.SM);
652
- screenMap.set(sizeEnum.MD, screenEnum.MD);
653
- screenMap.set(sizeEnum.LG, screenEnum.LG);
654
- screenMap.set(sizeEnum.XL, screenEnum.XL);
655
- screenMap.set(sizeEnum.XXL, screenEnum.XXL);
195
+ function getQueryParam(paramName) {
196
+ const urlParams = new URLSearchParams(window.location.search);
197
+ return urlParams.get(paramName);
198
+ }
656
199
 
657
200
  /**
658
- * Object defining various exception codes.
659
- * @readonly
201
+ * Disables the right-click context menu on the window.
660
202
  */
661
- const ExceptionEnum = {
662
- /**
663
- * HTTP status code for page access forbidden.
664
- */
665
- PAGE_NOT_ACCESS: 403,
666
- /**
667
- * HTTP status code for page not found.
668
- */
669
- PAGE_NOT_FOUND: 404,
670
- /**
671
- * HTTP status code for general server error.
672
- */
673
- ERROR: 500,
674
- /**
675
- * HTTP status code for bad request.
676
- */
677
- BAD_REQUEST: 406,
678
- /**
679
- * Custom error code for network errors.
680
- */
681
- NET_WORK_ERROR: 10000,
682
- /**
683
- * Custom code indicating no data on the page, not actually an exception page.
684
- */
685
- PAGE_NOT_DATA: 10100,
686
- };
687
-
203
+ function disableRightClick() {
204
+ const handler = (event) => event.preventDefault();
205
+ window.addEventListener('contextmenu', handler);
206
+ // Store handler to allow removal
207
+ disableRightClick.handler = handler;
208
+ }
209
+ disableRightClick.handler = null;
210
+ /**
211
+ * Enables the right-click context menu on the window.
212
+ */
213
+ function enableRightClick() {
214
+ if (disableRightClick.handler) {
215
+ window.removeEventListener('contextmenu', disableRightClick.handler);
216
+ }
217
+ }
218
+ /**
219
+ * Disables specific mouse buttons.
220
+ * @param {Array<number>} buttons Array of mouse button codes to disable (0 for left, 1 for middle, 2 for right).
221
+ */
222
+ function disableMouseButtons(buttons) {
223
+ const handler = (event) => {
224
+ if (buttons.includes(event.button)) {
225
+ event.preventDefault();
226
+ }
227
+ };
228
+ window.addEventListener('mousedown', handler);
229
+ window.addEventListener('contextmenu', handler); // Also handles right-click context menu
230
+ // Store handler to allow removal later
231
+ disableMouseButtons.handlers.push(handler);
232
+ }
233
+ disableMouseButtons.handlers = [];
688
234
  /**
689
- * Object defining various image MIME types.
690
- * @readonly
235
+ * Enables all previously disabled mouse buttons.
691
236
  */
692
- const ImageTypes = {
693
- apng: 'image/apng',
694
- bmp: 'image/bmp',
695
- gif: 'image/gif',
696
- jpeg: 'image/jpeg',
697
- pjpeg: 'image/pjpeg',
698
- png: 'image/png',
699
- svg: 'image/svg+xml',
700
- tiff: 'image/tiff',
701
- webp: 'image/webp',
702
- xicon: 'image/x-icon',
703
- };
237
+ function enableMouseButtons() {
238
+ for (const handler of disableMouseButtons.handlers) {
239
+ window.removeEventListener('mousedown', handler);
240
+ window.removeEventListener('contextmenu', handler);
241
+ }
242
+ disableMouseButtons.handlers = [];
243
+ }
704
244
  /**
705
- * Object defining various file meta types.
706
- * @readonly
245
+ * Adds a double-click event listener to a specific element.
246
+ * @param {HTMLElement} element The target element.
247
+ * @param {(event: MouseEvent) => void} callback The callback function to execute on double click.
707
248
  */
708
- const FileMetaTypes = {
709
- images: 'image/*',
710
- audios: 'audio/*',
711
- videos: 'video/*',
712
- };
713
-
249
+ function addDoubleClickListener(element, callback) {
250
+ element.addEventListener('dblclick', callback);
251
+ }
714
252
  /**
715
- * Object representing different content types for HTTP headers.
716
- * @readonly
253
+ * Removes a double-click event listener from a specific element.
254
+ * @param {HTMLElement} element The target element.
255
+ * @param {(event: MouseEvent) => void} callback The callback function to remove.
717
256
  */
718
- const ContentTypeEnum = {
719
- /**
720
- * Content type for JSON.
721
- * @constant
722
- */
723
- JSON: 'application/json;charset=UTF-8',
724
- /**
725
- * Content type for URL-encoded form data.
726
- * @constant
727
- */
728
- FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
729
- /**
730
- * Content type for multipart form data with file uploads.
731
- * @constant
732
- */
733
- FORM_DATA: 'multipart/form-data;charset=UTF-8',
734
- };
735
-
257
+ function removeDoubleClickListener(element, callback) {
258
+ element.removeEventListener('dblclick', callback);
259
+ }
260
+ // Example usage:
261
+ // addDoubleClickListener(document.body, () => alert('Double clicked!'));
736
262
  /**
737
- * Object defining various key codes for keyboard events.
738
- * @readonly
263
+ * Detects a click outside a specific element and triggers a callback.
264
+ * @param {HTMLElement} element The element to detect clicks outside of.
265
+ * @param {() => void} callback The callback function to execute when a click outside is detected.
739
266
  */
740
- const KeyCodeEnum = {
741
- UP: 38,
742
- DOWN: 40,
743
- ENTER: 13,
744
- ESC: 27,
745
- };
746
-
267
+ function clickOutside(element, callback) {
268
+ const handler = (event) => {
269
+ if (!element.contains(event.target)) {
270
+ callback();
271
+ }
272
+ };
273
+ document.addEventListener('click', handler);
274
+ // Store handler to allow removal later
275
+ clickOutside.handlers.push({ element, handler });
276
+ }
277
+ clickOutside.handlers = [];
747
278
  /**
748
- * Key used for storing the authentication token.
749
- * @constant {string}
279
+ * Removes the click outside listener for a specific element.
280
+ * @param {HTMLElement} element The element to stop detecting clicks outside of.
750
281
  */
751
- const TOKEN_KEY = 'TOKEN__';
282
+ function removeClickOutside(element) {
283
+ const index = clickOutside.handlers.findIndex(h => h.element === element);
284
+ if (index !== -1) {
285
+ document.removeEventListener('click', clickOutside.handlers[index].handler);
286
+ clickOutside.handlers.splice(index, 1);
287
+ }
288
+ }
289
+ // Example usage: Close a menu when clicking outside of it
290
+ // const menu = document.getElementById('menu');
291
+ // if (menu) {
292
+ // clickOutside(menu, () => menu.style.display = 'none');
293
+ // }
752
294
  /**
753
- * Key used for storing the locale information.
754
- * @constant {string}
295
+ * Disables the F12 key and certain key combinations for developer tools.
755
296
  */
756
- const LOCALE_KEY = 'LOCALE__';
297
+ function disableF12Key() {
298
+ const handler = function (event) {
299
+ return true;
300
+ };
301
+ document.addEventListener('keydown', handler);
302
+ }
757
303
  /**
758
- * Key used for storing user information.
759
- * @constant {string}
304
+ * Enables or disables the tab navigation (Tab key) on the page.
305
+ * @param {boolean} enable Whether to enable or disable tab navigation.
760
306
  */
761
- const USER_INFO_KEY = 'USER__INFO__';
307
+ function toggleTabNavigation(enable) {
308
+ if (enable) {
309
+ document.onkeydown = null; // Removes any previously set handler
310
+ }
311
+ else {
312
+ document.onkeydown = (event) => {
313
+ if (event.key === 'Tab') {
314
+ event.preventDefault();
315
+ return false;
316
+ }
317
+ return true;
318
+ };
319
+ }
320
+ }
762
321
  /**
763
- * Key used for storing role information.
764
- * @constant {string}
322
+ * Disables the copy (Ctrl + C) functionality on the page.
765
323
  */
766
- const ROLES_KEY = 'ROLES__KEY__';
324
+ function disableCopy() {
325
+ document.addEventListener('copy', (event) => {
326
+ event.preventDefault();
327
+ alert('Copying text is disabled on this page.');
328
+ });
329
+ }
767
330
  /**
768
- * Key used for storing project configuration.
769
- * @constant {string}
331
+ * Adds a custom keyboard shortcut to execute a given callback function.
332
+ * @param {string} key The key to trigger the callback.
333
+ * @param {Function} callback The function to execute on the key press.
334
+ * @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
335
+ * @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
770
336
  */
771
- const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
337
+ function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = false) {
338
+ document.addEventListener('keydown', (event) => {
339
+ if (event.key === key &&
340
+ event.ctrlKey === ctrlKey &&
341
+ event.shiftKey === shiftKey) {
342
+ event.preventDefault();
343
+ callback();
344
+ }
345
+ });
346
+ }
772
347
  /**
773
- * Key used for storing lock information.
774
- * @constant {string}
348
+ * Removes a custom keyboard shortcut by key and modifiers.
349
+ * @param {string} key The key to trigger the callback.
350
+ * @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
351
+ * @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
775
352
  */
776
- const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
353
+ function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
354
+ const handler = (event) => {
355
+ if (event.key === key &&
356
+ event.ctrlKey === ctrlKey &&
357
+ event.shiftKey === shiftKey) {
358
+ event.preventDefault();
359
+ }
360
+ };
361
+ document.removeEventListener('keydown', handler);
362
+ }
777
363
  /**
778
- * Key used for base global local cache.
779
- * @constant {string}
364
+ * Disables specific keys or key combinations.
365
+ * @param {Array<string>} keys Array of key names to disable (e.g., ['F1', 'F5', 'Control+S']).
780
366
  */
781
- const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY';
367
+ function disableSpecificKeys(keys) {
368
+ const handler = (event) => {
369
+ const keyCombination = `${event.ctrlKey ? 'Control+' : ''}${event.shiftKey ? 'Shift+' : ''}${event.altKey ? 'Alt+' : ''}${event.key}`;
370
+ if (keys.includes(event.key) || keys.includes(keyCombination)) {
371
+ event.preventDefault();
372
+ }
373
+ };
374
+ document.addEventListener('keydown', handler);
375
+ // Storing the handler to allow removal later
376
+ disableSpecificKeys.handlers.push(handler);
377
+ }
378
+ disableSpecificKeys.handlers = [];
782
379
  /**
783
- * Key used for base global session cache.
784
- * @constant {string}
380
+ * Enables keys that were previously disabled using disableSpecificKeys.
785
381
  */
786
- const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY';
382
+ function enableSpecificKeys() {
383
+ for (const handler of disableSpecificKeys.handlers) {
384
+ document.removeEventListener('keydown', handler);
385
+ }
386
+ disableSpecificKeys.handlers = [];
387
+ }
787
388
  /**
788
- * Object defining types of cache storage.
789
- * @readonly
389
+ * Registers multiple keyboard shortcuts with their respective callback functions.
390
+ * @param {Array<{ key: string, ctrlKey?: boolean, shiftKey?: boolean, altKey?: boolean, callback: Function }>} shortcuts Array of shortcut objects.
790
391
  */
791
- const CacheTypeEnum = {
792
- /** Represents session storage */
793
- SESSION: 0,
794
- /** Represents local storage */
795
- LOCAL: 1,
796
- };
797
-
392
+ function registerKeyboardShortcuts(shortcuts) {
393
+ const handler = (event) => {
394
+ for (const shortcut of shortcuts) {
395
+ if (event.key === shortcut.key &&
396
+ (shortcut.ctrlKey ? event.ctrlKey : true) &&
397
+ (shortcut.shiftKey ? event.shiftKey : true) &&
398
+ (shortcut.altKey ? event.altKey : true)) {
399
+ event.preventDefault();
400
+ shortcut.callback();
401
+ }
402
+ }
403
+ };
404
+ document.addEventListener('keydown', handler);
405
+ // Storing the handler to allow removal later
406
+ registerKeyboardShortcuts.handlers.push(handler);
407
+ }
408
+ registerKeyboardShortcuts.handlers = [];
798
409
  /**
799
- * Creates and downloads a file from Blob data.
800
- * @param {Blob} blob The Blob containing the file data.
801
- * @param {string} fileName The name of the file to create.
410
+ * Unregisters all keyboard shortcuts that were registered with registerKeyboardShortcuts.
411
+ */
412
+ function unregisterKeyboardShortcuts() {
413
+ for (const handler of registerKeyboardShortcuts.handlers) {
414
+ document.removeEventListener('keydown', handler);
415
+ }
416
+ registerKeyboardShortcuts.handlers = [];
417
+ }
418
+ /**
419
+ * Adds a listener for a specific key to trigger a custom event.
420
+ * @param {string} key The key to listen for (e.g., 'Enter', 'Escape').
421
+ * @param {Function} callback The function to execute when the key is pressed.
802
422
  */
803
- function downloadFile(blob, fileName) {
804
- const link = document.createElement('a');
805
- const url = URL.createObjectURL(blob);
806
- link.setAttribute('href', url);
807
- link.setAttribute('download', fileName);
808
- // Append link to the body and trigger a click
809
- document.body.appendChild(link);
810
- link.click();
811
- // Clean up
812
- document.body.removeChild(link);
813
- URL.revokeObjectURL(url);
423
+ function addKeyListener(key, callback) {
424
+ const handler = (event) => {
425
+ if (event.key === key) {
426
+ event.preventDefault();
427
+ callback();
428
+ }
429
+ };
430
+ document.addEventListener('keydown', handler);
431
+ // Storing the handler to allow removal later
432
+ addKeyListener.handlers.push(handler);
814
433
  }
434
+ addKeyListener.handlers = [];
815
435
  /**
816
- * Exports data to a CSV file.
817
- * @param {string[]} headers The headers for the CSV.
818
- * @param {any[][]} data The data to export, as an array of arrays.
819
- * @param {string} fileName The name of the file to create.
436
+ * Removes all custom key listeners added by addKeyListener.
820
437
  */
821
- function exportToCSV(headers, data, fileName) {
822
- const csvRows = [];
823
- // Add the headers
824
- csvRows.push(headers.map(header => `"${header.replace(/"/g, '""')}"`).join(','));
825
- // Add the data rows
826
- for (const row of data) {
827
- csvRows.push(row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','));
438
+ function removeKeyListeners() {
439
+ for (const handler of addKeyListener.handlers) {
440
+ document.removeEventListener('keydown', handler);
828
441
  }
829
- // Create a Blob with CSV data
830
- const csvContent = csvRows.join('\n');
831
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
832
- // Use the download utility function
833
- downloadFile(blob, fileName);
442
+ addKeyListener.handlers = [];
834
443
  }
835
444
  /**
836
- * Exports data to an Excel file (.xls) using HTML table.
837
- * @param {string[]} headers The headers for the Excel file.
838
- * @param {any[][]} data The data to export, as an array of arrays.
839
- * @param {string} fileName The name of the file to create.
445
+ * Detects if a specific key is held down.
446
+ * @param {string} key The key to detect (e.g., 'Shift', 'Control', 'Alt', 'a').
447
+ * @param {Function} onHold Callback function to execute while the key is held down.
840
448
  */
841
- function exportToExcel(headers, data, fileName) {
842
- // Create a table element
843
- const table = document.createElement('table');
844
- const thead = table.createTHead();
845
- const tbody = table.createTBody();
846
- // Create the header row
847
- const headerRow = thead.insertRow();
848
- headers.forEach(header => {
849
- const th = document.createElement('th');
850
- th.textContent = header;
851
- headerRow.appendChild(th);
852
- });
853
- // Create the data rows
854
- data.forEach(row => {
855
- const tr = tbody.insertRow();
856
- row.forEach(cell => {
857
- const td = tr.insertCell();
858
- td.textContent = String(cell);
859
- });
860
- });
861
- // Create a Blob with the table HTML
862
- const html = table.outerHTML;
863
- const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8;' });
864
- // Use the download utility function
865
- downloadFile(blob, fileName);
449
+ function detectKeyHold(key, onHold) {
450
+ const keyDownHandler = (event) => {
451
+ if (event.key === key) {
452
+ onHold();
453
+ }
454
+ };
455
+ document.addEventListener('keydown', keyDownHandler);
456
+ // Storing the handler to allow removal later
457
+ detectKeyHold.handlers.push(keyDownHandler);
866
458
  }
459
+ detectKeyHold.handlers = [];
867
460
  /**
868
- * Exports data to a JSON file.
869
- * @param {any[]} data The data to export.
870
- * @param {string} fileName The name of the file to create.
461
+ * Stops detecting if a specific key is held down.
871
462
  */
872
- function exportToJSON(data, fileName) {
873
- const jsonContent = JSON.stringify(data, null, 2); // Pretty print with 2 spaces
874
- const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' });
875
- // Use the download utility function
876
- downloadFile(blob, fileName);
463
+ function stopDetectingKeyHold() {
464
+ for (const handler of detectKeyHold.handlers) {
465
+ document.removeEventListener('keydown', handler);
466
+ }
467
+ detectKeyHold.handlers = [];
877
468
  }
878
469
  /**
879
- * Exports data to an XML file.
880
- * @param {string[]} headers The headers for the XML.
881
- * @param {any[][]} data The data to export, as an array of arrays.
882
- * @param {string} fileName The name of the file to create.
470
+ * Tracks currently pressed keys and provides a map of active keys.
471
+ * @returns {Set<string>} A set of currently pressed keys.
883
472
  */
884
- function exportToXML(headers, data, fileName) {
885
- let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<rows>\n';
886
- // Add headers
887
- xmlContent += ' <header>\n';
888
- headers.forEach(header => {
889
- xmlContent += ` <column>${header}</column>\n`;
890
- });
891
- xmlContent += ' </header>\n';
892
- // Add data rows
893
- data.forEach(row => {
894
- xmlContent += ' <row>\n';
895
- row.forEach(cell => {
896
- xmlContent += ` <cell>${cell}</cell>\n`;
897
- });
898
- xmlContent += ' </row>\n';
899
- });
900
- xmlContent += '</rows>';
901
- const blob = new Blob([xmlContent], { type: 'application/xml;charset=utf-8;' });
902
- // Use the download utility function
903
- downloadFile(blob, fileName);
473
+ function createKeyMap() {
474
+ const pressedKeys = new Set();
475
+ const keyDownHandler = (event) => {
476
+ pressedKeys.add(event.key);
477
+ };
478
+ const keyUpHandler = (event) => {
479
+ pressedKeys.delete(event.key);
480
+ };
481
+ document.addEventListener('keydown', keyDownHandler);
482
+ document.addEventListener('keyup', keyUpHandler);
483
+ // Function to remove listeners
484
+ createKeyMap.clearListeners = () => {
485
+ document.removeEventListener('keydown', keyDownHandler);
486
+ document.removeEventListener('keyup', keyUpHandler);
487
+ };
488
+ return pressedKeys;
904
489
  }
905
490
  /**
906
- * Exports data to a plain text file.
907
- * @param {string[]} headers The headers for the text file.
908
- * @param {any[][]} data The data to export, as an array of arrays.
909
- * @param {string} fileName The name of the file to create.
491
+ * Clears listeners for the key map tracking.
910
492
  */
911
- function exportToText(headers, data, fileName) {
912
- const textRows = [];
913
- // Add headers
914
- textRows.push(headers.join('\t'));
915
- // Add data rows
916
- data.forEach(row => {
917
- textRows.push(row.join('\t'));
918
- });
919
- // Create a Blob with text data
920
- const textContent = textRows.join('\n');
921
- const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8;' });
922
- // Use the download utility function
923
- downloadFile(blob, fileName);
924
- }
925
-
493
+ createKeyMap.clearListeners = () => { };
926
494
  /**
927
- * Opens a new window with the specified URL and options.
928
- * @param {string} url The URL to open.
929
- * @param {Object} [opt] Options for the new window.
930
- * @param {string} [opt.target='__blank'] The target window name.
931
- * @param {boolean} [opt.noopener=true] Whether to add 'noopener' attribute.
932
- * @param {boolean} [opt.noreferrer=true] Whether to add 'noreferrer' attribute.
495
+ * Sets up custom keyboard shortcuts with flexible order.
496
+ * @param {Array<string>} keys The combination of keys for the shortcut.
497
+ * @param {Function} callback The callback function to execute when the combination is detected.
933
498
  */
934
- function openWindow(url, opt) {
935
- const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
936
- const features = [];
937
- // Append noopener and noreferrer attributes if enabled
938
- if (noopener)
939
- features.push('noopener=yes');
940
- if (noreferrer)
941
- features.push('noreferrer=yes');
942
- // Open the window with the specified features
943
- window.open(url, target, features.join(','));
499
+ function customShortcut(keys, callback) {
500
+ const pressedKeys = new Set();
501
+ const keyDownHandler = (event) => {
502
+ pressedKeys.add(event.key);
503
+ if (keys.every(key => pressedKeys.has(key))) {
504
+ callback();
505
+ }
506
+ };
507
+ const keyUpHandler = (event) => {
508
+ pressedKeys.delete(event.key);
509
+ };
510
+ document.addEventListener('keydown', keyDownHandler);
511
+ document.addEventListener('keyup', keyUpHandler);
512
+ // Store the handlers for later removal
513
+ customShortcut.handlers.push({ keyDownHandler, keyUpHandler });
944
514
  }
515
+ customShortcut.handlers = [];
945
516
  /**
946
- * Copies text to the clipboard.
947
- * @param {string} text The text to copy.
948
- * @returns {Promise<void>} A promise that resolves when the text has been copied.
517
+ * Removes all custom keyboard shortcuts added by customShortcut.
949
518
  */
950
- async function copyToClipboard(text) {
951
- if (navigator.clipboard) {
952
- await navigator.clipboard.writeText(text);
953
- }
954
- else {
955
- // Fallback for browsers that do not support the Clipboard API
956
- const textArea = document.createElement('textarea');
957
- textArea.value = text;
958
- document.body.appendChild(textArea);
959
- textArea.select();
960
- document.execCommand('copy');
961
- document.body.removeChild(textArea);
519
+ function removeCustomShortcuts() {
520
+ for (const { keyDownHandler, keyUpHandler } of customShortcut.handlers) {
521
+ document.removeEventListener('keydown', keyDownHandler);
522
+ document.removeEventListener('keyup', keyUpHandler);
962
523
  }
524
+ customShortcut.handlers = [];
963
525
  }
964
526
  /**
965
- * Scrolls the window to the top smoothly.
966
- * @param {number} [duration=300] Duration of the scroll animation in milliseconds.
527
+ * Simulates a key press event.
528
+ * @param {string} key The key to simulate (e.g., 'Enter', 'a').
529
+ * @param {boolean} ctrlKey If true, include Ctrl key in the event.
530
+ * @param {boolean} shiftKey If true, include Shift key in the event.
531
+ * @param {boolean} altKey If true, include Alt key in the event.
967
532
  */
968
- function scrollToTop(duration = 300) {
969
- const start = window.scrollY;
970
- const startTime = performance.now();
971
- function scroll() {
972
- const elapsed = performance.now() - startTime;
973
- const progress = Math.min(elapsed / duration, 1);
974
- window.scrollTo(0, start * (1 - progress));
975
- if (progress < 1) {
976
- requestAnimationFrame(scroll);
533
+ function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false) {
534
+ const event = new KeyboardEvent('keydown', {
535
+ key,
536
+ ctrlKey,
537
+ shiftKey,
538
+ altKey,
539
+ bubbles: true,
540
+ cancelable: true
541
+ });
542
+ document.dispatchEvent(event);
543
+ }
544
+
545
+ /**
546
+ * Parses a date string into a Date object.
547
+ * @param {string} dateString The date string in 'YYYY-MM-DD' format.
548
+ * @returns {Date | null} The parsed Date object or null if the format is invalid.
549
+ */
550
+ function parseDate(dateString) {
551
+ const parts = dateString.split('-');
552
+ if (parts.length === 3) {
553
+ const year = parseInt(parts[0], 10);
554
+ const month = parseInt(parts[1], 10) - 1; // Months are zero-based
555
+ const day = parseInt(parts[2], 10);
556
+ const date = new Date(year, month, day);
557
+ if (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day) {
558
+ return date;
977
559
  }
978
560
  }
979
- requestAnimationFrame(scroll);
561
+ return null;
980
562
  }
981
563
  /**
982
- * Gets the value of a query parameter from the URL.
983
- * @param {string} paramName The name of the query parameter.
984
- * @returns {string | null} The value of the query parameter, or null if it does not exist.
564
+ * Formats a Date object into a string.
565
+ * @param {Date} date The date to format.
566
+ * @param {string} format The format string (e.g., 'YYYY-MM-DD').
567
+ * @returns {string} The formatted date string.
985
568
  */
986
- function getQueryParam(paramName) {
987
- const urlParams = new URLSearchParams(window.location.search);
988
- return urlParams.get(paramName);
569
+ function formatDate(date, format) {
570
+ const map = {
571
+ 'YYYY': date.getFullYear(),
572
+ 'MM': String(date.getMonth() + 1).padStart(2, '0'),
573
+ 'DD': String(date.getDate()).padStart(2, '0'),
574
+ 'HH': String(date.getHours()).padStart(2, '0'),
575
+ 'mm': String(date.getMinutes()).padStart(2, '0'),
576
+ 'ss': String(date.getSeconds()).padStart(2, '0')
577
+ };
578
+ return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (matched) => (map[matched] || matched).toString());
989
579
  }
990
-
991
580
  /**
992
- * Disables the right-click context menu on the window.
581
+ * Calculates the number of days between two dates.
582
+ * @param {Date} startDate The start date.
583
+ * @param {Date} endDate The end date.
584
+ * @returns {number} The number of days between the two dates.
993
585
  */
994
- function disableRightClick() {
995
- const handler = (event) => event.preventDefault();
996
- window.addEventListener('contextmenu', handler);
997
- // Store handler to allow removal
998
- disableRightClick.handler = handler;
586
+ function daysBetween(startDate, endDate) {
587
+ const millisecondsPerDay = 86400000; // Number of milliseconds in one day
588
+ return Math.round((endDate.getTime() - startDate.getTime()) / millisecondsPerDay);
999
589
  }
1000
- disableRightClick.handler = null;
1001
590
  /**
1002
- * Enables the right-click context menu on the window.
591
+ * Adds a specified number of days to a date.
592
+ * @param {Date} date The date to modify.
593
+ * @param {number} days The number of days to add.
594
+ * @returns {Date} The new date with days added.
1003
595
  */
1004
- function enableRightClick() {
1005
- if (disableRightClick.handler) {
1006
- window.removeEventListener('contextmenu', disableRightClick.handler);
1007
- }
596
+ function addDays(date, days) {
597
+ const result = new Date(date);
598
+ result.setDate(result.getDate() + days);
599
+ return result;
1008
600
  }
1009
601
  /**
1010
- * Disables specific mouse buttons.
1011
- * @param {Array<number>} buttons Array of mouse button codes to disable (0 for left, 1 for middle, 2 for right).
602
+ * Subtracts a specified number of days from a date.
603
+ * @param {Date} date The date to modify.
604
+ * @param {number} days The number of days to subtract.
605
+ * @returns {Date} The new date with days subtracted.
1012
606
  */
1013
- function disableMouseButtons(buttons) {
1014
- const handler = (event) => {
1015
- if (buttons.includes(event.button)) {
1016
- event.preventDefault();
1017
- }
1018
- };
1019
- window.addEventListener('mousedown', handler);
1020
- window.addEventListener('contextmenu', handler); // Also handles right-click context menu
1021
- // Store handler to allow removal later
1022
- disableMouseButtons.handlers.push(handler);
607
+ function subtractDays(date, days) {
608
+ return addDays(date, -days);
1023
609
  }
1024
- disableMouseButtons.handlers = [];
1025
610
  /**
1026
- * Enables all previously disabled mouse buttons.
611
+ * Determines if a year is a leap year.
612
+ * @param {number} year The year to check.
613
+ * @returns {boolean} True if the year is a leap year, false otherwise.
1027
614
  */
1028
- function enableMouseButtons() {
1029
- for (const handler of disableMouseButtons.handlers) {
1030
- window.removeEventListener('mousedown', handler);
1031
- window.removeEventListener('contextmenu', handler);
1032
- }
1033
- disableMouseButtons.handlers = [];
615
+ function isLeapYear(year) {
616
+ return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
1034
617
  }
1035
618
  /**
1036
- * Adds a double-click event listener to a specific element.
1037
- * @param {HTMLElement} element The target element.
1038
- * @param {(event: MouseEvent) => void} callback The callback function to execute on double click.
619
+ * Gets the first day of the month for a given date.
620
+ * @param {Date} date The date to use.
621
+ * @returns {Date} The first day of the month.
1039
622
  */
1040
- function addDoubleClickListener(element, callback) {
1041
- element.addEventListener('dblclick', callback);
623
+ function getStartOfMonth(date) {
624
+ return new Date(date.getFullYear(), date.getMonth(), 1);
1042
625
  }
1043
626
  /**
1044
- * Removes a double-click event listener from a specific element.
1045
- * @param {HTMLElement} element The target element.
1046
- * @param {(event: MouseEvent) => void} callback The callback function to remove.
627
+ * Gets the last day of the month for a given date.
628
+ * @param {Date} date The date to use.
629
+ * @returns {Date} The last day of the month.
1047
630
  */
1048
- function removeDoubleClickListener(element, callback) {
1049
- element.removeEventListener('dblclick', callback);
631
+ function getEndOfMonth(date) {
632
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0);
1050
633
  }
1051
- // Example usage:
1052
- // addDoubleClickListener(document.body, () => alert('Double clicked!'));
1053
634
  /**
1054
- * Detects a click outside a specific element and triggers a callback.
1055
- * @param {HTMLElement} element The element to detect clicks outside of.
1056
- * @param {() => void} callback The callback function to execute when a click outside is detected.
635
+ * Calculates age from a given birth date.
636
+ * @param {Date} birthDate The birth date.
637
+ * @returns {number} The calculated age.
1057
638
  */
1058
- function clickOutside(element, callback) {
1059
- const handler = (event) => {
1060
- if (!element.contains(event.target)) {
1061
- callback();
1062
- }
1063
- };
1064
- document.addEventListener('click', handler);
1065
- // Store handler to allow removal later
1066
- clickOutside.handlers.push({ element, handler });
639
+ function calculateAge(birthDate) {
640
+ const today = new Date();
641
+ let age = today.getFullYear() - birthDate.getFullYear();
642
+ const m = today.getMonth() - birthDate.getMonth();
643
+ if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
644
+ age--;
645
+ }
646
+ return age;
1067
647
  }
1068
- clickOutside.handlers = [];
1069
648
  /**
1070
- * Removes the click outside listener for a specific element.
1071
- * @param {HTMLElement} element The element to stop detecting clicks outside of.
649
+ * Calculates the number of days until the next birthday.
650
+ * @param {Date} birthDate The birth date.
651
+ * @returns {number} The number of days until the next birthday.
1072
652
  */
1073
- function removeClickOutside(element) {
1074
- const index = clickOutside.handlers.findIndex(h => h.element === element);
1075
- if (index !== -1) {
1076
- document.removeEventListener('click', clickOutside.handlers[index].handler);
1077
- clickOutside.handlers.splice(index, 1);
653
+ function daysToNextBirthday(birthDate) {
654
+ const today = new Date();
655
+ const currentYear = today.getFullYear();
656
+ const nextBirthday = new Date(birthDate);
657
+ nextBirthday.setFullYear(currentYear);
658
+ if (nextBirthday < today) {
659
+ nextBirthday.setFullYear(currentYear + 1);
1078
660
  }
661
+ return daysBetween(today, nextBirthday);
1079
662
  }
1080
- // Example usage: Close a menu when clicking outside of it
1081
- // const menu = document.getElementById('menu');
1082
- // if (menu) {
1083
- // clickOutside(menu, () => menu.style.display = 'none');
1084
- // }
1085
663
  /**
1086
- * Disables the F12 key and certain key combinations for developer tools.
664
+ * Calculates the age at a specific date.
665
+ * @param {Date} birthDate The birth date.
666
+ * @param {Date} atDate The date to calculate the age at.
667
+ * @returns {number} The calculated age.
1087
668
  */
1088
- function disableF12Key() {
1089
- const handler = function (event) {
1090
- return true;
1091
- };
1092
- document.addEventListener('keydown', handler);
669
+ function ageAtDate(birthDate, atDate) {
670
+ let age = atDate.getFullYear() - birthDate.getFullYear();
671
+ const m = atDate.getMonth() - birthDate.getMonth();
672
+ if (m < 0 || (m === 0 && atDate.getDate() < birthDate.getDate())) {
673
+ age--;
674
+ }
675
+ return age;
1093
676
  }
677
+
1094
678
  /**
1095
- * Enables or disables the tab navigation (Tab key) on the page.
1096
- * @param {boolean} enable Whether to enable or disable tab navigation.
679
+ * Creates a debounced asynchronous validator function.
680
+ *
681
+ * @param validator - The async validator function to debounce.
682
+ * @param delay - The debounce delay in milliseconds.
683
+ * @returns A debounced version of the validator function.
1097
684
  */
1098
- function toggleTabNavigation(enable) {
1099
- if (enable) {
1100
- document.onkeydown = null; // Removes any previously set handler
1101
- }
1102
- else {
1103
- document.onkeydown = (event) => {
1104
- if (event.key === 'Tab') {
1105
- event.preventDefault();
1106
- return false;
685
+ function debounceAsyncValidator(validator, delay) {
686
+ let currentPromiseReject = null;
687
+ /**
688
+ * Creates a debounce delay.
689
+ *
690
+ * @returns A promise that resolves after the debounce delay.
691
+ */
692
+ function debounce() {
693
+ return new Promise((resolve, reject) => {
694
+ const { start, stop } = useTimeoutFn(() => {
695
+ currentPromiseReject = null;
696
+ resolve();
697
+ }, delay);
698
+ if (currentPromiseReject) {
699
+ currentPromiseReject(new Error('replaced'));
1107
700
  }
1108
- return true;
1109
- };
701
+ currentPromiseReject = reject;
702
+ start();
703
+ });
1110
704
  }
705
+ return function (value) {
706
+ if (currentPromiseReject) {
707
+ currentPromiseReject(new Error('replaced'));
708
+ currentPromiseReject = null;
709
+ }
710
+ return validator.call(this, value, debounce);
711
+ };
1111
712
  }
1112
713
  /**
1113
- * Disables the copy (Ctrl + C) functionality on the page.
1114
- */
1115
- function disableCopy() {
1116
- document.addEventListener('copy', (event) => {
1117
- event.preventDefault();
1118
- alert('Copying text is disabled on this page.');
1119
- });
1120
- }
1121
- /**
1122
- * Adds a custom keyboard shortcut to execute a given callback function.
1123
- * @param {string} key The key to trigger the callback.
1124
- * @param {Function} callback The function to execute on the key press.
1125
- * @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
1126
- * @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
714
+ * Creates a debounced version of an asynchronous function.
715
+ * @param {Function} func The asynchronous function to debounce.
716
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
717
+ * @returns {Function} The debounced function.
1127
718
  */
1128
- function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = false) {
1129
- document.addEventListener('keydown', (event) => {
1130
- if (event.key === key &&
1131
- event.ctrlKey === ctrlKey &&
1132
- event.shiftKey === shiftKey) {
1133
- event.preventDefault();
1134
- callback();
719
+ function debounceAsync(func, wait) {
720
+ let timeoutReject = null;
721
+ return function (...args) {
722
+ if (timeoutReject) {
723
+ timeoutReject(new Error('replaced'));
724
+ timeoutReject = null;
1135
725
  }
1136
- });
726
+ return new Promise((resolve, reject) => {
727
+ const { start } = useTimeoutFn(() => {
728
+ func.apply(this, args).then(resolve).catch(reject);
729
+ }, wait);
730
+ timeoutReject = reject;
731
+ start();
732
+ });
733
+ };
1137
734
  }
1138
735
  /**
1139
- * Removes a custom keyboard shortcut by key and modifiers.
1140
- * @param {string} key The key to trigger the callback.
1141
- * @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
1142
- * @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
736
+ * Creates a debounced asynchronous function that executes immediately on the first call.
737
+ * @param {Function} func The asynchronous function to debounce.
738
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
739
+ * @returns {Function} The debounced function with immediate execution on the first call.
1143
740
  */
1144
- function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
1145
- const handler = (event) => {
1146
- if (event.key === key &&
1147
- event.ctrlKey === ctrlKey &&
1148
- event.shiftKey === shiftKey) {
1149
- event.preventDefault();
741
+ function debounceAsyncWithImmediate(func, wait) {
742
+ let timeoutReject = null;
743
+ let callNow = true;
744
+ return function (...args) {
745
+ const context = this;
746
+ if (callNow) {
747
+ callNow = false;
748
+ return func.apply(context, args).finally(() => {
749
+ const { start } = useTimeoutFn(() => {
750
+ callNow = true;
751
+ }, wait);
752
+ start();
753
+ });
754
+ }
755
+ else {
756
+ if (timeoutReject) {
757
+ timeoutReject(new Error('replaced'));
758
+ timeoutReject = null;
759
+ }
760
+ return new Promise((resolve, reject) => {
761
+ const { start } = useTimeoutFn(() => {
762
+ func.apply(context, args).then(resolve).catch(reject);
763
+ }, wait);
764
+ timeoutReject = reject;
765
+ start();
766
+ });
1150
767
  }
1151
768
  };
1152
- document.removeEventListener('keydown', handler);
1153
769
  }
1154
770
  /**
1155
- * Disables specific keys or key combinations.
1156
- * @param {Array<string>} keys Array of key names to disable (e.g., ['F1', 'F5', 'Control+S']).
771
+ * Creates a debounced version of a function that executes on the leading edge.
772
+ * @param {Function} func The function to debounce.
773
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
774
+ * @returns {Function} The debounced function.
1157
775
  */
1158
- function disableSpecificKeys(keys) {
1159
- const handler = (event) => {
1160
- const keyCombination = `${event.ctrlKey ? 'Control+' : ''}${event.shiftKey ? 'Shift+' : ''}${event.altKey ? 'Alt+' : ''}${event.key}`;
1161
- if (keys.includes(event.key) || keys.includes(keyCombination)) {
1162
- event.preventDefault();
776
+ function debounceLeading(func, wait) {
777
+ let timeoutReject = null;
778
+ return function (...args) {
779
+ const context = this;
780
+ if (!timeoutReject) {
781
+ func.apply(context, args);
782
+ }
783
+ if (timeoutReject) {
784
+ timeoutReject(new Error('replaced'));
785
+ timeoutReject = null;
1163
786
  }
787
+ const { start } = useTimeoutFn(() => {
788
+ timeoutReject = null;
789
+ }, wait);
790
+ timeoutReject = () => { };
791
+ start();
1164
792
  };
1165
- document.addEventListener('keydown', handler);
1166
- // Storing the handler to allow removal later
1167
- disableSpecificKeys.handlers.push(handler);
1168
793
  }
1169
- disableSpecificKeys.handlers = [];
1170
794
  /**
1171
- * Enables keys that were previously disabled using disableSpecificKeys.
795
+ * Creates a debounced version of a function that executes on the trailing edge.
796
+ * @param {Function} func The function to debounce.
797
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
798
+ * @returns {Function} The debounced function.
1172
799
  */
1173
- function enableSpecificKeys() {
1174
- for (const handler of disableSpecificKeys.handlers) {
1175
- document.removeEventListener('keydown', handler);
1176
- }
1177
- disableSpecificKeys.handlers = [];
800
+ function debounceTrailing(func, wait) {
801
+ let timeoutReject = null;
802
+ return function (...args) {
803
+ const context = this;
804
+ if (timeoutReject) {
805
+ timeoutReject(new Error('replaced'));
806
+ timeoutReject = null;
807
+ }
808
+ const { start } = useTimeoutFn(() => {
809
+ func.apply(context, args);
810
+ }, wait);
811
+ timeoutReject = () => { };
812
+ start();
813
+ };
1178
814
  }
1179
815
  /**
1180
- * Registers multiple keyboard shortcuts with their respective callback functions.
1181
- * @param {Array<{ key: string, ctrlKey?: boolean, shiftKey?: boolean, altKey?: boolean, callback: Function }>} shortcuts Array of shortcut objects.
816
+ * Creates a debounced version of a function that executes on both leading and trailing edges.
817
+ * @param {Function} func The function to debounce.
818
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
819
+ * @returns {Function} The debounced function.
1182
820
  */
1183
- function registerKeyboardShortcuts(shortcuts) {
1184
- const handler = (event) => {
1185
- for (const shortcut of shortcuts) {
1186
- if (event.key === shortcut.key &&
1187
- (shortcut.ctrlKey ? event.ctrlKey : true) &&
1188
- (shortcut.shiftKey ? event.shiftKey : true) &&
1189
- (shortcut.altKey ? event.altKey : true)) {
1190
- event.preventDefault();
1191
- shortcut.callback();
1192
- }
821
+ function debounceLeadingTrailing(func, wait) {
822
+ let timeoutReject = null;
823
+ let lastCall = 0;
824
+ return function (...args) {
825
+ const context = this;
826
+ const now = Date.now();
827
+ if (timeoutReject) {
828
+ timeoutReject(new Error('replaced'));
829
+ timeoutReject = null;
830
+ }
831
+ if (now - lastCall >= wait) {
832
+ func.apply(context, args);
833
+ lastCall = now;
834
+ }
835
+ else {
836
+ const { start } = useTimeoutFn(() => {
837
+ func.apply(context, args);
838
+ }, wait - (now - lastCall));
839
+ timeoutReject = () => { };
840
+ start();
1193
841
  }
1194
842
  };
1195
- document.addEventListener('keydown', handler);
1196
- // Storing the handler to allow removal later
1197
- registerKeyboardShortcuts.handlers.push(handler);
1198
843
  }
1199
- registerKeyboardShortcuts.handlers = [];
1200
844
  /**
1201
- * Unregisters all keyboard shortcuts that were registered with registerKeyboardShortcuts.
845
+ * Creates a debounced version of a function.
846
+ * @param {Function} func The function to debounce.
847
+ * @param {number} wait The number of milliseconds to wait before invoking the function.
848
+ * @returns {Function} The debounced function.
1202
849
  */
1203
- function unregisterKeyboardShortcuts() {
1204
- for (const handler of registerKeyboardShortcuts.handlers) {
1205
- document.removeEventListener('keydown', handler);
1206
- }
1207
- registerKeyboardShortcuts.handlers = [];
850
+ function debounce(func, wait) {
851
+ let timeoutReject = null;
852
+ return function (...args) {
853
+ if (timeoutReject) {
854
+ timeoutReject(new Error('replaced'));
855
+ timeoutReject = null;
856
+ }
857
+ const { start } = useTimeoutFn(() => {
858
+ func.apply(this, args);
859
+ }, wait);
860
+ timeoutReject = () => { };
861
+ start();
862
+ };
1208
863
  }
1209
864
  /**
1210
- * Adds a listener for a specific key to trigger a custom event.
1211
- * @param {string} key The key to listen for (e.g., 'Enter', 'Escape').
1212
- * @param {Function} callback The function to execute when the key is pressed.
865
+ * Creates a throttled version of a function.
866
+ * @param {Function} func The function to throttle.
867
+ * @param {number} limit The number of milliseconds to wait between function calls.
868
+ * @returns {Function} The throttled function.
1213
869
  */
1214
- function addKeyListener(key, callback) {
1215
- const handler = (event) => {
1216
- if (event.key === key) {
1217
- event.preventDefault();
1218
- callback();
870
+ function throttle(func, limit) {
871
+ let lastCall = 0;
872
+ return function (...args) {
873
+ const now = Date.now();
874
+ if (now - lastCall >= limit) {
875
+ lastCall = now;
876
+ func.apply(this, args);
1219
877
  }
1220
878
  };
1221
- document.addEventListener('keydown', handler);
1222
- // Storing the handler to allow removal later
1223
- addKeyListener.handlers.push(handler);
1224
879
  }
1225
- addKeyListener.handlers = [];
880
+
1226
881
  /**
1227
- * Removes all custom key listeners added by addKeyListener.
882
+ * Converts a FormData object to a plain JavaScript object.
883
+ * @param {FormData} formData The FormData object to convert.
884
+ * @returns {Record<string, any>} The plain JavaScript object.
1228
885
  */
1229
- function removeKeyListeners() {
1230
- for (const handler of addKeyListener.handlers) {
1231
- document.removeEventListener('keydown', handler);
1232
- }
1233
- addKeyListener.handlers = [];
886
+ function formDataToObject(formData) {
887
+ const obj = {};
888
+ formData.forEach((value, key) => {
889
+ // Handle multiple values for the same key
890
+ if (obj[key]) {
891
+ if (!Array.isArray(obj[key])) {
892
+ obj[key] = [obj[key]];
893
+ }
894
+ obj[key].push(value);
895
+ }
896
+ else {
897
+ obj[key] = value;
898
+ }
899
+ });
900
+ return obj;
1234
901
  }
1235
902
  /**
1236
- * Detects if a specific key is held down.
1237
- * @param {string} key The key to detect (e.g., 'Shift', 'Control', 'Alt', 'a').
1238
- * @param {Function} onHold Callback function to execute while the key is held down.
903
+ * Reads a file as text.
904
+ * @param {File} file The file to read.
905
+ * @returns {Promise<string>} A promise that resolves with the file content.
1239
906
  */
1240
- function detectKeyHold(key, onHold) {
1241
- const keyDownHandler = (event) => {
1242
- if (event.key === key) {
1243
- onHold();
1244
- }
1245
- };
1246
- document.addEventListener('keydown', keyDownHandler);
1247
- // Storing the handler to allow removal later
1248
- detectKeyHold.handlers.push(keyDownHandler);
907
+ function readFileAsText(file) {
908
+ return new Promise((resolve, reject) => {
909
+ const reader = new FileReader();
910
+ reader.onload = () => resolve(reader.result);
911
+ reader.onerror = reject;
912
+ reader.readAsText(file);
913
+ });
1249
914
  }
1250
- detectKeyHold.handlers = [];
1251
915
  /**
1252
- * Stops detecting if a specific key is held down.
916
+ * Reads a file as a Data URL.
917
+ * @param {File} file The file to read.
918
+ * @returns {Promise<string>} A promise that resolves with the Data URL.
1253
919
  */
1254
- function stopDetectingKeyHold() {
1255
- for (const handler of detectKeyHold.handlers) {
1256
- document.removeEventListener('keydown', handler);
1257
- }
1258
- detectKeyHold.handlers = [];
920
+ function readFileAsDataURL(file) {
921
+ return new Promise((resolve, reject) => {
922
+ const reader = new FileReader();
923
+ reader.onload = () => resolve(reader.result);
924
+ reader.onerror = reject;
925
+ reader.readAsDataURL(file);
926
+ });
1259
927
  }
1260
928
  /**
1261
- * Tracks currently pressed keys and provides a map of active keys.
1262
- * @returns {Set<string>} A set of currently pressed keys.
929
+ * Creates a Blob from a string.
930
+ * @param {string} content The string content for the Blob.
931
+ * @param {string} [type='text/plain'] The MIME type of the Blob.
932
+ * @returns {Blob} The Blob object.
933
+ */
934
+ function stringToBlob(content, type = 'text/plain') {
935
+ return new Blob([content], { type });
936
+ }
937
+ /**
938
+ * Creates a Blob from an ArrayBuffer.
939
+ * @param {ArrayBuffer} buffer The ArrayBuffer to convert.
940
+ * @param {string} [type='application/octet-stream'] The MIME type of the Blob.
941
+ * @returns {Blob} The Blob object.
1263
942
  */
1264
- function createKeyMap() {
1265
- const pressedKeys = new Set();
1266
- const keyDownHandler = (event) => {
1267
- pressedKeys.add(event.key);
1268
- };
1269
- const keyUpHandler = (event) => {
1270
- pressedKeys.delete(event.key);
1271
- };
1272
- document.addEventListener('keydown', keyDownHandler);
1273
- document.addEventListener('keyup', keyUpHandler);
1274
- // Function to remove listeners
1275
- createKeyMap.clearListeners = () => {
1276
- document.removeEventListener('keydown', keyDownHandler);
1277
- document.removeEventListener('keyup', keyUpHandler);
1278
- };
1279
- return pressedKeys;
943
+ function bufferToBlob(buffer, type = 'application/octet-stream') {
944
+ return new Blob([buffer], { type });
1280
945
  }
1281
946
  /**
1282
- * Clears listeners for the key map tracking.
947
+ * Creates and downloads a file from Blob data.
948
+ * @param {Blob} blob The Blob containing the file data.
949
+ * @param {string} fileName The name of the file to create.
1283
950
  */
1284
- createKeyMap.clearListeners = () => { };
951
+ function downloadBlob(blob, fileName) {
952
+ const link = document.createElement('a');
953
+ const url = URL.createObjectURL(blob);
954
+ link.setAttribute('href', url);
955
+ link.setAttribute('download', fileName);
956
+ // Append link to the body and trigger a click
957
+ document.body.appendChild(link);
958
+ link.click();
959
+ // Clean up
960
+ document.body.removeChild(link);
961
+ URL.revokeObjectURL(url);
962
+ }
1285
963
  /**
1286
- * Sets up custom keyboard shortcuts with flexible order.
1287
- * @param {Array<string>} keys The combination of keys for the shortcut.
1288
- * @param {Function} callback The callback function to execute when the combination is detected.
964
+ * Creates a FormData object containing a Blob.
965
+ * @param {Blob} blob The Blob to include in the FormData.
966
+ * @param {string} name The name of the form field.
967
+ * @param {string} [fileName='file'] The file name for the Blob.
968
+ * @returns {FormData} The FormData object.
1289
969
  */
1290
- function customShortcut(keys, callback) {
1291
- const pressedKeys = new Set();
1292
- const keyDownHandler = (event) => {
1293
- pressedKeys.add(event.key);
1294
- if (keys.every(key => pressedKeys.has(key))) {
1295
- callback();
1296
- }
1297
- };
1298
- const keyUpHandler = (event) => {
1299
- pressedKeys.delete(event.key);
1300
- };
1301
- document.addEventListener('keydown', keyDownHandler);
1302
- document.addEventListener('keyup', keyUpHandler);
1303
- // Store the handlers for later removal
1304
- customShortcut.handlers.push({ keyDownHandler, keyUpHandler });
970
+ function blobToFormData(blob, name, fileName = 'file') {
971
+ const formData = new FormData();
972
+ formData.append(name, blob, fileName);
973
+ return formData;
1305
974
  }
1306
- customShortcut.handlers = [];
975
+
1307
976
  /**
1308
- * Removes all custom keyboard shortcuts added by customShortcut.
977
+ * Converts a Proxy object to a plain object.
978
+ * @param {ProxyConstructor} proxy The Proxy object to convert.
979
+ * @returns {Object} The plain object.
1309
980
  */
1310
- function removeCustomShortcuts() {
1311
- for (const { keyDownHandler, keyUpHandler } of customShortcut.handlers) {
1312
- document.removeEventListener('keydown', keyDownHandler);
1313
- document.removeEventListener('keyup', keyUpHandler);
981
+ function proxyToPlainObject(proxy) {
982
+ if (!proxy)
983
+ return {};
984
+ const plainObject = {};
985
+ for (const property of Object.keys(proxy)) {
986
+ plainObject[property] = proxy[property];
1314
987
  }
1315
- customShortcut.handlers = [];
988
+ return plainObject;
1316
989
  }
1317
990
  /**
1318
- * Simulates a key press event.
1319
- * @param {string} key The key to simulate (e.g., 'Enter', 'a').
1320
- * @param {boolean} ctrlKey If true, include Ctrl key in the event.
1321
- * @param {boolean} shiftKey If true, include Shift key in the event.
1322
- * @param {boolean} altKey If true, include Alt key in the event.
991
+ * Compares two objects to check if they have the same keys.
992
+ * @param {Object} object1 The first object to compare.
993
+ * @param {Object} object2 The second object to compare.
994
+ * @returns {boolean} True if the objects have the same keys, otherwise false.
1323
995
  */
1324
- function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false) {
1325
- const event = new KeyboardEvent('keydown', {
1326
- key,
1327
- ctrlKey,
1328
- shiftKey,
1329
- altKey,
1330
- bubbles: true,
1331
- cancelable: true
996
+ function compareObject(object1, object2) {
997
+ return Object.keys(object1).every(function (element) {
998
+ return Object.keys(object2).includes(element);
1332
999
  });
1333
- document.dispatchEvent(event);
1334
1000
  }
1335
-
1336
1001
  /**
1337
- * Parses a date string into a Date object.
1338
- * @param {string} dateString The date string in 'YYYY-MM-DD' format.
1339
- * @returns {Date | null} The parsed Date object or null if the format is invalid.
1002
+ * Deeply compares two objects to check if they are equal.
1003
+ * @param {Object} object1 The first object to compare.
1004
+ * @param {Object} object2 The second object to compare.
1005
+ * @returns {boolean} True if the objects are deeply equal, otherwise false.
1340
1006
  */
1341
- function parseDate(dateString) {
1342
- const parts = dateString.split('-');
1343
- if (parts.length === 3) {
1344
- const year = parseInt(parts[0], 10);
1345
- const month = parseInt(parts[1], 10) - 1; // Months are zero-based
1346
- const day = parseInt(parts[2], 10);
1347
- const date = new Date(year, month, day);
1348
- if (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day) {
1349
- return date;
1007
+ function deepEqual(object1, object2) {
1008
+ if (object1 === object2)
1009
+ return true;
1010
+ if (typeof object1 !== 'object' || typeof object2 !== 'object' || object1 === null || object2 === null) {
1011
+ return false;
1012
+ }
1013
+ const keys1 = Object.keys(object1);
1014
+ const keys2 = Object.keys(object2);
1015
+ if (keys1.length !== keys2.length) {
1016
+ return false;
1017
+ }
1018
+ for (const key of keys1) {
1019
+ if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
1020
+ return false;
1350
1021
  }
1351
1022
  }
1352
- return null;
1023
+ return true;
1353
1024
  }
1354
1025
  /**
1355
- * Formats a Date object into a string.
1356
- * @param {Date} date The date to format.
1357
- * @param {string} format The format string (e.g., 'YYYY-MM-DD').
1358
- * @returns {string} The formatted date string.
1026
+ * Deeply clones an object.
1027
+ * @param {Object} obj The object to clone.
1028
+ * @returns {Object} The cloned object.
1359
1029
  */
1360
- function formatDate(date, format) {
1361
- const map = {
1362
- 'YYYY': date.getFullYear(),
1363
- 'MM': String(date.getMonth() + 1).padStart(2, '0'),
1364
- 'DD': String(date.getDate()).padStart(2, '0'),
1365
- 'HH': String(date.getHours()).padStart(2, '0'),
1366
- 'mm': String(date.getMinutes()).padStart(2, '0'),
1367
- 'ss': String(date.getSeconds()).padStart(2, '0')
1368
- };
1369
- return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (matched) => (map[matched] || matched).toString());
1030
+ function deepClone(obj) {
1031
+ if (obj === null || typeof obj !== 'object') {
1032
+ return obj;
1033
+ }
1034
+ if (obj instanceof Date) {
1035
+ return new Date(obj.getTime());
1036
+ }
1037
+ if (obj instanceof Array) {
1038
+ return obj.map(item => deepClone(item));
1039
+ }
1040
+ if (obj instanceof Object) {
1041
+ const copy = {};
1042
+ for (const key in obj) {
1043
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
1044
+ copy[key] = deepClone(obj[key]);
1045
+ }
1046
+ }
1047
+ return copy;
1048
+ }
1049
+ throw new Error('Unable to clone object! Its type is not supported.');
1370
1050
  }
1371
1051
  /**
1372
- * Calculates the number of days between two dates.
1373
- * @param {Date} startDate The start date.
1374
- * @param {Date} endDate The end date.
1375
- * @returns {number} The number of days between the two dates.
1052
+ * Converts an object to a query string.
1053
+ * @param {Object} obj The object to convert.
1054
+ * @returns {string} The query string.
1376
1055
  */
1377
- function daysBetween(startDate, endDate) {
1378
- const millisecondsPerDay = 86400000; // Number of milliseconds in one day
1379
- return Math.round((endDate.getTime() - startDate.getTime()) / millisecondsPerDay);
1056
+ function objectToQueryString(obj) {
1057
+ return Object.keys(obj)
1058
+ .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
1059
+ .join('&');
1060
+ }
1061
+ // Example usage:
1062
+ // objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
1063
+ /**
1064
+ * Gets the differences between two objects.
1065
+ * @param {Object} object1 The first object.
1066
+ * @param {Object} object2 The second object.
1067
+ * @returns {Object} An object containing the differences.
1068
+ */
1069
+ function getObjectDifferences(object1, object2) {
1070
+ const differences = {};
1071
+ const keys = new Set([...Object.keys(object1), ...Object.keys(object2)]);
1072
+ for (const key of keys) {
1073
+ if (object1[key] !== object2[key]) {
1074
+ differences[key] = { object1: object1[key], object2: object2[key] };
1075
+ }
1076
+ }
1077
+ return differences;
1380
1078
  }
1381
1079
  /**
1382
- * Adds a specified number of days to a date.
1383
- * @param {Date} date The date to modify.
1384
- * @param {number} days The number of days to add.
1385
- * @returns {Date} The new date with days added.
1080
+ * Filters an object by a list of keys.
1081
+ * @param {Object} obj The object to filter.
1082
+ * @param {Array<string>} keys The keys to keep.
1083
+ * @returns {Object} The filtered object.
1386
1084
  */
1387
- function addDays(date, days) {
1388
- const result = new Date(date);
1389
- result.setDate(result.getDate() + days);
1390
- return result;
1085
+ function filterObjectByKeys(obj, keys) {
1086
+ const filteredObject = {};
1087
+ for (const key of keys) {
1088
+ if (key in obj) {
1089
+ filteredObject[key] = obj[key];
1090
+ }
1091
+ }
1092
+ return filteredObject;
1391
1093
  }
1094
+ // Example usage:
1095
+ // filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
1392
1096
  /**
1393
- * Subtracts a specified number of days from a date.
1394
- * @param {Date} date The date to modify.
1395
- * @param {number} days The number of days to subtract.
1396
- * @returns {Date} The new date with days subtracted.
1097
+ * Deeply merges two objects.
1098
+ * @param {Object} target The target object to merge into.
1099
+ * @param {Object} source The source object to merge from.
1100
+ * @returns {Object} The merged object.
1397
1101
  */
1398
- function subtractDays(date, days) {
1399
- return addDays(date, -days);
1102
+ function deepMerge(target, source) {
1103
+ if (target === null || typeof target !== 'object' || typeof source !== 'object') {
1104
+ return target;
1105
+ }
1106
+ for (const key in source) {
1107
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1108
+ if (source[key] && typeof source[key] === 'object') {
1109
+ if (target && !target[key]) {
1110
+ Object.assign(target, { [key]: {} });
1111
+ }
1112
+ deepMerge(target[key], source[key]);
1113
+ }
1114
+ else {
1115
+ Object.assign(target, { [key]: source[key] });
1116
+ }
1117
+ }
1118
+ }
1119
+ return target;
1400
1120
  }
1401
1121
  /**
1402
- * Determines if a year is a leap year.
1403
- * @param {number} year The year to check.
1404
- * @returns {boolean} True if the year is a leap year, false otherwise.
1122
+ * Checks if an object is empty.
1123
+ * @param {Object} obj The object to check.
1124
+ * @returns {boolean} True if the object is empty, otherwise false.
1405
1125
  */
1406
- function isLeapYear(year) {
1407
- return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
1126
+ function isEmptyObject(obj) {
1127
+ return Object.keys(obj).length === 0;
1408
1128
  }
1409
1129
  /**
1410
- * Gets the first day of the month for a given date.
1411
- * @param {Date} date The date to use.
1412
- * @returns {Date} The first day of the month.
1130
+ * Safely accesses nested properties in an object.
1131
+ * @param {Object} obj The object to access.
1132
+ * @param {Array<string>} keys The array of keys representing the path.
1133
+ * @returns {any} The value at the nested path, or undefined if not found.
1413
1134
  */
1414
- function getStartOfMonth(date) {
1415
- return new Date(date.getFullYear(), date.getMonth(), 1);
1135
+ function safeGet(obj, keys) {
1136
+ return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
1416
1137
  }
1138
+ // Example usage:
1139
+ // safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
1140
+ // safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
1417
1141
  /**
1418
- * Gets the last day of the month for a given date.
1419
- * @param {Date} date The date to use.
1420
- * @returns {Date} The last day of the month.
1142
+ * Removes empty properties (null, undefined, or empty string) from an object.
1143
+ * @param {Object} obj The object to clean.
1144
+ * @returns {Object} A new object without empty properties.
1421
1145
  */
1422
- function getEndOfMonth(date) {
1423
- return new Date(date.getFullYear(), date.getMonth() + 1, 0);
1146
+ function removeEmptyProperties(obj) {
1147
+ return Object.keys(obj)
1148
+ .filter(key => obj[key] !== null && obj[key] !== undefined && obj[key] !== '')
1149
+ .reduce((acc, key) => {
1150
+ acc[key] = obj[key];
1151
+ return acc;
1152
+ }, {});
1424
1153
  }
1154
+ // Example usage:
1155
+ // removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
1425
1156
  /**
1426
- * Calculates age from a given birth date.
1427
- * @param {Date} birthDate The birth date.
1428
- * @returns {number} The calculated age.
1157
+ * Retrieves all keys of an object as an array.
1158
+ * @param {Object} obj The object to retrieve keys from.
1159
+ * @returns {Array<string>} The array of keys.
1429
1160
  */
1430
- function calculateAge(birthDate) {
1431
- const today = new Date();
1432
- let age = today.getFullYear() - birthDate.getFullYear();
1433
- const m = today.getMonth() - birthDate.getMonth();
1434
- if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
1435
- age--;
1436
- }
1437
- return age;
1161
+ function getObjectKeys(obj) {
1162
+ return Object.keys(obj);
1438
1163
  }
1164
+ // Example usage:
1165
+ // getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
1439
1166
  /**
1440
- * Calculates the number of days until the next birthday.
1441
- * @param {Date} birthDate The birth date.
1442
- * @returns {number} The number of days until the next birthday.
1167
+ * Checks if an object has nested properties.
1168
+ * @param {Object} obj The object to check.
1169
+ * @returns {boolean} True if there are nested properties, false otherwise.
1443
1170
  */
1444
- function daysToNextBirthday(birthDate) {
1445
- const today = new Date();
1446
- const currentYear = today.getFullYear();
1447
- const nextBirthday = new Date(birthDate);
1448
- nextBirthday.setFullYear(currentYear);
1449
- if (nextBirthday < today) {
1450
- nextBirthday.setFullYear(currentYear + 1);
1451
- }
1452
- return daysBetween(today, nextBirthday);
1171
+ function hasNestedProperties(obj) {
1172
+ return Object.values(obj).some(value => typeof value === 'object' && value !== null);
1453
1173
  }
1174
+ // Example usage:
1175
+ // hasNestedProperties({ a: 1, b: { c: 2 } }); // true
1176
+ // hasNestedProperties({ a: 1, b: 2 }); // false
1454
1177
  /**
1455
- * Calculates the age at a specific date.
1456
- * @param {Date} birthDate The birth date.
1457
- * @param {Date} atDate The date to calculate the age at.
1458
- * @returns {number} The calculated age.
1178
+ * Converts an object to FormData, handling nested objects.
1179
+ * @param {Object} obj The object to convert.
1180
+ * @param {FormData} [formData] The FormData object to append to.
1181
+ * @param {string} [parentKey] The parent key for nested objects.
1182
+ * @returns {FormData} The FormData object.
1459
1183
  */
1460
- function ageAtDate(birthDate, atDate) {
1461
- let age = atDate.getFullYear() - birthDate.getFullYear();
1462
- const m = atDate.getMonth() - birthDate.getMonth();
1463
- if (m < 0 || (m === 0 && atDate.getDate() < birthDate.getDate())) {
1464
- age--;
1465
- }
1466
- return age;
1184
+ function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
1185
+ Object.entries(obj).forEach(([key, value]) => {
1186
+ const finalKey = parentKey ? `${parentKey}[${key}]` : key;
1187
+ if (value && typeof value === 'object' && !(value instanceof File)) {
1188
+ objectToFormDataEnhanced(value, formData, finalKey);
1189
+ }
1190
+ else {
1191
+ formData.append(finalKey, value);
1192
+ }
1193
+ });
1194
+ return formData;
1467
1195
  }
1468
-
1196
+ // Example usage:
1197
+ // objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
1469
1198
  /**
1470
- * Creates a debounced asynchronous validator function.
1199
+ * Converts a JavaScript object into FormData.
1471
1200
  *
1472
- * @param validator - The async validator function to debounce.
1473
- * @param delay - The debounce delay in milliseconds.
1474
- * @returns A debounced version of the validator function.
1201
+ * @param obj - The object to be converted.
1202
+ * @param form - An optional FormData instance to use.
1203
+ * @param namespace - An optional namespace to use for nested objects.
1204
+ * @returns The FormData instance with the object's key-value pairs.
1475
1205
  */
1476
- function debounceAsyncValidator(validator, delay) {
1477
- let currentPromiseReject = null;
1478
- /**
1479
- * Creates a debounce delay.
1480
- *
1481
- * @returns A promise that resolves after the debounce delay.
1482
- */
1483
- function debounce() {
1484
- return new Promise((resolve, reject) => {
1485
- const { start, stop } = useTimeoutFn(() => {
1486
- currentPromiseReject = null;
1487
- resolve();
1488
- }, delay);
1489
- if (currentPromiseReject) {
1490
- currentPromiseReject(new Error('replaced'));
1206
+ const objectToFormData = function (obj, form, namespace) {
1207
+ const fd = form || new FormData();
1208
+ let formKey;
1209
+ for (const property in obj) {
1210
+ if (obj[property] === undefined) {
1211
+ continue;
1212
+ }
1213
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
1214
+ if (namespace) {
1215
+ formKey = `${namespace}[${property}]`;
1491
1216
  }
1492
- currentPromiseReject = reject;
1493
- start();
1494
- });
1217
+ else {
1218
+ formKey = property;
1219
+ }
1220
+ if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
1221
+ // Recursively handle nested objects
1222
+ objectToFormData(obj[property], fd, formKey);
1223
+ }
1224
+ else {
1225
+ // Convert boolean values to 1/0
1226
+ const value = obj[property] === true || obj[property] === false ? Number(obj[property]) : obj[property];
1227
+ fd.append(formKey, value);
1228
+ }
1229
+ }
1495
1230
  }
1496
- return function (value) {
1497
- if (currentPromiseReject) {
1498
- currentPromiseReject(new Error('replaced'));
1499
- currentPromiseReject = null;
1231
+ return fd;
1232
+ };
1233
+ /**
1234
+ * Flattens a nested object, bringing all properties to the top level.
1235
+ * @param {Object} obj The object to flatten.
1236
+ * @param {string} [parentKey] The parent key for nested properties.
1237
+ * @param {Object} [result] The resulting flattened object.
1238
+ * @returns {Object} The flattened object.
1239
+ */
1240
+ function flattenObject(obj, parentKey = '', result = {}) {
1241
+ for (const key in obj) {
1242
+ if (obj.hasOwnProperty(key)) {
1243
+ const propName = parentKey ? `${parentKey}.${key}` : key;
1244
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
1245
+ flattenObject(obj[key], propName, result);
1246
+ }
1247
+ else {
1248
+ result[propName] = obj[key];
1249
+ }
1500
1250
  }
1501
- return validator.call(this, value, debounce);
1502
- };
1251
+ }
1252
+ return result;
1503
1253
  }
1254
+ // Example usage:
1255
+ // flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
1256
+
1504
1257
  /**
1505
- * Creates a debounced version of an asynchronous function.
1506
- * @param {Function} func The asynchronous function to debounce.
1507
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1508
- * @returns {Function} The debounced function.
1258
+ * Capitalizes the first character of a string.
1259
+ * @param {string} s The string to capitalize.
1260
+ * @returns {string} The capitalized string.
1509
1261
  */
1510
- function debounceAsync(func, wait) {
1511
- let timeoutReject = null;
1512
- return function (...args) {
1513
- if (timeoutReject) {
1514
- timeoutReject(new Error('replaced'));
1515
- timeoutReject = null;
1516
- }
1517
- return new Promise((resolve, reject) => {
1518
- const { start } = useTimeoutFn(() => {
1519
- func.apply(this, args).then(resolve).catch(reject);
1520
- }, wait);
1521
- timeoutReject = reject;
1522
- start();
1523
- });
1524
- };
1262
+ function upperFirst(s) {
1263
+ return s.charAt(0).toUpperCase() + s.slice(1);
1525
1264
  }
1265
+ // Example usage:
1266
+ // console.log(upperFirst('hello')); // 'Hello'
1526
1267
  /**
1527
- * Creates a debounced asynchronous function that executes immediately on the first call.
1528
- * @param {Function} func The asynchronous function to debounce.
1529
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1530
- * @returns {Function} The debounced function with immediate execution on the first call.
1268
+ * Lowercases the first character of a string.
1269
+ * @param {string} s The string to lowercase.
1270
+ * @returns {string} The lowercased string.
1531
1271
  */
1532
- function debounceAsyncWithImmediate(func, wait) {
1533
- let timeoutReject = null;
1534
- let callNow = true;
1535
- return function (...args) {
1536
- const context = this;
1537
- if (callNow) {
1538
- callNow = false;
1539
- return func.apply(context, args).finally(() => {
1540
- const { start } = useTimeoutFn(() => {
1541
- callNow = true;
1542
- }, wait);
1543
- start();
1544
- });
1545
- }
1546
- else {
1547
- if (timeoutReject) {
1548
- timeoutReject(new Error('replaced'));
1549
- timeoutReject = null;
1550
- }
1551
- return new Promise((resolve, reject) => {
1552
- const { start } = useTimeoutFn(() => {
1553
- func.apply(context, args).then(resolve).catch(reject);
1554
- }, wait);
1555
- timeoutReject = reject;
1556
- start();
1557
- });
1558
- }
1559
- };
1272
+ function lowerFirst(s) {
1273
+ return s.charAt(0).toLowerCase() + s.slice(1);
1274
+ }
1275
+ // Example usage:
1276
+ // console.log(lowerFirst('Hello')); // 'hello'
1277
+ /**
1278
+ * Removes accents and special characters from a string and converts it to a URL-friendly format.
1279
+ * @param {string} input The string to process.
1280
+ * @returns {string} The processed string.
1281
+ */
1282
+ function removeAccent(input) {
1283
+ return input
1284
+ .toLowerCase()
1285
+ .trim()
1286
+ .replace(/[\s_-]+/g, "-")
1287
+ .replace(/^-+|-+$/g, "")
1288
+ .normalize("NFD")
1289
+ .replace(/[\u0300-\u036f]/g, "");
1560
1290
  }
1291
+ // Example usage:
1292
+ // console.log(removeAccent('Café du Nord')); // 'cafe-du-nord'
1561
1293
  /**
1562
- * Creates a debounced version of a function that executes on the leading edge.
1563
- * @param {Function} func The function to debounce.
1564
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1565
- * @returns {Function} The debounced function.
1294
+ * Reverses a string.
1295
+ * @param {string} str The string to reverse.
1296
+ * @returns {string} The reversed string.
1566
1297
  */
1567
- function debounceLeading(func, wait) {
1568
- let timeoutReject = null;
1569
- return function (...args) {
1570
- const context = this;
1571
- if (!timeoutReject) {
1572
- func.apply(context, args);
1573
- }
1574
- if (timeoutReject) {
1575
- timeoutReject(new Error('replaced'));
1576
- timeoutReject = null;
1577
- }
1578
- const { start } = useTimeoutFn(() => {
1579
- timeoutReject = null;
1580
- }, wait);
1581
- timeoutReject = () => { };
1582
- start();
1583
- };
1298
+ function reverseString(str) {
1299
+ return str.split('').reverse().join('');
1584
1300
  }
1301
+ // Example usage:
1302
+ // console.log(reverseString('hello')); // 'olleh'
1585
1303
  /**
1586
- * Creates a debounced version of a function that executes on the trailing edge.
1587
- * @param {Function} func The function to debounce.
1588
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1589
- * @returns {Function} The debounced function.
1304
+ * Counts the number of words in a string.
1305
+ * @param {string} str The string to analyze.
1306
+ * @returns {number} The word count.
1590
1307
  */
1591
- function debounceTrailing(func, wait) {
1592
- let timeoutReject = null;
1593
- return function (...args) {
1594
- const context = this;
1595
- if (timeoutReject) {
1596
- timeoutReject(new Error('replaced'));
1597
- timeoutReject = null;
1598
- }
1599
- const { start } = useTimeoutFn(() => {
1600
- func.apply(context, args);
1601
- }, wait);
1602
- timeoutReject = () => { };
1603
- start();
1604
- };
1308
+ function countWords(str) {
1309
+ return str.trim().split(/\s+/).length;
1605
1310
  }
1311
+ // Example usage:
1312
+ // console.log(countWords('Hello world!')); // 2
1606
1313
  /**
1607
- * Creates a debounced version of a function that executes on both leading and trailing edges.
1608
- * @param {Function} func The function to debounce.
1609
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1610
- * @returns {Function} The debounced function.
1314
+ * Truncates a string to the specified length and adds ellipsis if necessary.
1315
+ * @param {string} str The string to truncate.
1316
+ * @param {number} maxLength The maximum length of the string.
1317
+ * @returns {string} The truncated string.
1611
1318
  */
1612
- function debounceLeadingTrailing(func, wait) {
1613
- let timeoutReject = null;
1614
- let lastCall = 0;
1615
- return function (...args) {
1616
- const context = this;
1617
- const now = Date.now();
1618
- if (timeoutReject) {
1619
- timeoutReject(new Error('replaced'));
1620
- timeoutReject = null;
1621
- }
1622
- if (now - lastCall >= wait) {
1623
- func.apply(context, args);
1624
- lastCall = now;
1625
- }
1626
- else {
1627
- const { start } = useTimeoutFn(() => {
1628
- func.apply(context, args);
1629
- }, wait - (now - lastCall));
1630
- timeoutReject = () => { };
1631
- start();
1632
- }
1633
- };
1319
+ function truncateString(str, maxLength) {
1320
+ return str.length > maxLength ? str.slice(0, maxLength) + '...' : str;
1634
1321
  }
1322
+ // Example usage:
1323
+ // console.log(truncateString('This is a long string', 10)); // 'This is a...'
1635
1324
  /**
1636
- * Creates a debounced version of a function.
1637
- * @param {Function} func The function to debounce.
1638
- * @param {number} wait The number of milliseconds to wait before invoking the function.
1639
- * @returns {Function} The debounced function.
1325
+ * Converts a string to camel case.
1326
+ * @param {string} str The string to convert.
1327
+ * @returns {string} The camel cased string.
1640
1328
  */
1641
- function debounce(func, wait) {
1642
- let timeoutReject = null;
1643
- return function (...args) {
1644
- if (timeoutReject) {
1645
- timeoutReject(new Error('replaced'));
1646
- timeoutReject = null;
1647
- }
1648
- const { start } = useTimeoutFn(() => {
1649
- func.apply(this, args);
1650
- }, wait);
1651
- timeoutReject = () => { };
1652
- start();
1653
- };
1329
+ function toCamelCase(str) {
1330
+ return str
1331
+ .toLowerCase()
1332
+ .replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
1333
+ }
1334
+ // Example usage:
1335
+ // console.log(toCamelCase('hello world example')); // 'helloWorldExample'
1336
+ /**
1337
+ * Converts a string to kebab case.
1338
+ * @param {string} str The string to convert.
1339
+ * @returns {string} The kebab cased string.
1340
+ */
1341
+ function toKebabCase(str) {
1342
+ return str
1343
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
1344
+ .replace(/[\s_]+/g, '-')
1345
+ .toLowerCase();
1654
1346
  }
1347
+ // Example usage:
1348
+ // console.log(toKebabCase('Hello World Example')); // 'hello-world-example'
1655
1349
  /**
1656
- * Creates a throttled version of a function.
1657
- * @param {Function} func The function to throttle.
1658
- * @param {number} limit The number of milliseconds to wait between function calls.
1659
- * @returns {Function} The throttled function.
1350
+ * Replaces all instances of a substring within a string.
1351
+ * @param {string} str The original string.
1352
+ * @param {string} find The substring to find.
1353
+ * @param {string} replace The substring to replace with.
1354
+ * @returns {string} The modified string.
1660
1355
  */
1661
- function throttle(func, limit) {
1662
- let lastCall = 0;
1663
- return function (...args) {
1664
- const now = Date.now();
1665
- if (now - lastCall >= limit) {
1666
- lastCall = now;
1667
- func.apply(this, args);
1668
- }
1669
- };
1356
+ function replaceAll(str, find, replace) {
1357
+ return str.split(find).join(replace);
1670
1358
  }
1671
-
1359
+ // Example usage:
1360
+ // console.log(replaceAll('hello world', 'o', 'a')); // 'hella warld'
1672
1361
  /**
1673
- * Converts a FormData object to a plain JavaScript object.
1674
- * @param {FormData} formData The FormData object to convert.
1675
- * @returns {Record<string, any>} The plain JavaScript object.
1362
+ * Generates a random string of a specific length.
1363
+ * @param {number} length The length of the string to generate.
1364
+ * @returns {string} The random string.
1676
1365
  */
1677
- function formDataToObject(formData) {
1678
- const obj = {};
1679
- formData.forEach((value, key) => {
1680
- // Handle multiple values for the same key
1681
- if (obj[key]) {
1682
- if (!Array.isArray(obj[key])) {
1683
- obj[key] = [obj[key]];
1684
- }
1685
- obj[key].push(value);
1686
- }
1687
- else {
1688
- obj[key] = value;
1689
- }
1690
- });
1691
- return obj;
1366
+ function generateRandomString(length) {
1367
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
1368
+ return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
1692
1369
  }
1370
+ // Example usage:
1371
+ // console.log(generateRandomString(10)); // 'A1b2C3d4E5'
1372
+
1693
1373
  /**
1694
- * Reads a file as text.
1695
- * @param {File} file The file to read.
1696
- * @returns {Promise<string>} A promise that resolves with the file content.
1374
+ * Validates if the key pressed is a valid letter or special character.
1375
+ * @param {KeyboardEvent} e The keyboard event.
1376
+ * @returns {boolean} True if the key is valid, otherwise false.
1697
1377
  */
1698
- function readFileAsText(file) {
1699
- return new Promise((resolve, reject) => {
1700
- const reader = new FileReader();
1701
- reader.onload = () => resolve(reader.result);
1702
- reader.onerror = reject;
1703
- reader.readAsText(file);
1704
- });
1378
+ function validateLetters(e) {
1379
+ const key = e.keyCode;
1380
+ const validKeys = [
1381
+ ...Array.from({ length: 26 }, (_, i) => i + 65), // Letters A-Z
1382
+ ...Array.from({ length: 26 }, (_, i) => i + 97), // Letters a-z
1383
+ 45, // Hyphen
1384
+ 32, // Space
1385
+ 241, // ñ
1386
+ 209, // Ñ
1387
+ 225, // á
1388
+ 233, // é
1389
+ 237, // í
1390
+ 243, // ó
1391
+ 250, // ú
1392
+ 193, // Á
1393
+ 201, // É
1394
+ 205, // Í
1395
+ 211, // Ó
1396
+ 218 // Ú
1397
+ ];
1398
+ if (validKeys.includes(key)) {
1399
+ return true;
1400
+ }
1401
+ e.preventDefault();
1402
+ return false;
1705
1403
  }
1404
+ // Example usage:
1405
+ // document.addEventListener('keydown', validateLetters);
1706
1406
  /**
1707
- * Reads a file as a Data URL.
1708
- * @param {File} file The file to read.
1709
- * @returns {Promise<string>} A promise that resolves with the Data URL.
1407
+ * Validates if the key pressed is a valid alphanumeric character.
1408
+ * @param {KeyboardEvent} e The keyboard event.
1409
+ * @returns {boolean} True if the key is valid, otherwise false.
1710
1410
  */
1711
- function readFileAsDataURL(file) {
1712
- return new Promise((resolve, reject) => {
1713
- const reader = new FileReader();
1714
- reader.onload = () => resolve(reader.result);
1715
- reader.onerror = reject;
1716
- reader.readAsDataURL(file);
1717
- });
1411
+ function validateAlphanumeric(e) {
1412
+ const key = e.keyCode;
1413
+ const validKeys = [
1414
+ ...Array.from({ length: 10 }, (_, i) => i + 48), // Numbers 0-9
1415
+ ...Array.from({ length: 26 }, (_, i) => i + 65), // Letters A-Z
1416
+ ...Array.from({ length: 26 }, (_, i) => i + 97), // Letters a-z
1417
+ 45, // Hyphen
1418
+ 95, // Underscore
1419
+ 32 // Space
1420
+ ];
1421
+ if (validKeys.includes(key)) {
1422
+ return true;
1423
+ }
1424
+ e.preventDefault();
1425
+ return false;
1718
1426
  }
1427
+ // Example usage:
1428
+ // document.addEventListener('keydown', validateAlphanumeric);
1719
1429
  /**
1720
- * Creates a Blob from a string.
1721
- * @param {string} content The string content for the Blob.
1722
- * @param {string} [type='text/plain'] The MIME type of the Blob.
1723
- * @returns {Blob} The Blob object.
1430
+ * Validates if the key pressed is a number.
1431
+ * @param {KeyboardEvent} e The keyboard event.
1432
+ * @returns {boolean} True if the key is a number, otherwise false.
1724
1433
  */
1725
- function stringToBlob(content, type = 'text/plain') {
1726
- return new Blob([content], { type });
1434
+ function validateNumbers(e) {
1435
+ const key = e.keyCode;
1436
+ if ((key >= 48 && key <= 57) || key === 46 || key === 8 || key === 37 || key === 39) {
1437
+ return true;
1438
+ }
1439
+ e.preventDefault();
1440
+ return false;
1727
1441
  }
1442
+ // Example usage:
1443
+ // document.addEventListener('keydown', validateNumbers);
1728
1444
  /**
1729
- * Creates a Blob from an ArrayBuffer.
1730
- * @param {ArrayBuffer} buffer The ArrayBuffer to convert.
1731
- * @param {string} [type='application/octet-stream'] The MIME type of the Blob.
1732
- * @returns {Blob} The Blob object.
1445
+ * Validates if a phone number is valid.
1446
+ * @param {string} phoneNumber The phone number to validate.
1447
+ * @returns {boolean} True if the phone number is valid, otherwise false.
1733
1448
  */
1734
- function bufferToBlob(buffer, type = 'application/octet-stream') {
1735
- return new Blob([buffer], { type });
1449
+ function isValidPhoneNumber(phoneNumber) {
1450
+ const phonePattern = /^[0-9]{10}$/; // Example pattern for 10-digit phone numbers
1451
+ return phonePattern.test(phoneNumber);
1736
1452
  }
1453
+ // Example usage:
1454
+ // console.log(isValidPhoneNumber('1234567890')); // true
1455
+ // console.log(isValidPhoneNumber('123-456-7890')); // false
1737
1456
  /**
1738
- * Creates and downloads a file from Blob data.
1739
- * @param {Blob} blob The Blob containing the file data.
1740
- * @param {string} fileName The name of the file to create.
1457
+ * Validates if a string is a valid email address.
1458
+ * @param {string} email The email address to validate.
1459
+ * @returns {boolean} True if the email address is valid, otherwise false.
1741
1460
  */
1742
- function downloadBlob(blob, fileName) {
1743
- const link = document.createElement('a');
1744
- const url = URL.createObjectURL(blob);
1745
- link.setAttribute('href', url);
1746
- link.setAttribute('download', fileName);
1747
- // Append link to the body and trigger a click
1748
- document.body.appendChild(link);
1749
- link.click();
1750
- // Clean up
1751
- document.body.removeChild(link);
1752
- URL.revokeObjectURL(url);
1461
+ function isValidEmail(email) {
1462
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1463
+ return emailPattern.test(email);
1753
1464
  }
1465
+ // Example usage:
1466
+ // console.log(isValidEmail('example@domain.com')); // true
1467
+ // console.log(isValidEmail('invalid-email')); // false
1754
1468
  /**
1755
- * Creates a FormData object containing a Blob.
1756
- * @param {Blob} blob The Blob to include in the FormData.
1757
- * @param {string} name The name of the form field.
1758
- * @param {string} [fileName='file'] The file name for the Blob.
1759
- * @returns {FormData} The FormData object.
1469
+ * Validates if a string is a valid URL.
1470
+ * @param {string} url The URL to validate.
1471
+ * @returns {boolean} True if the URL is valid, otherwise false.
1760
1472
  */
1761
- function blobToFormData(blob, name, fileName = 'file') {
1762
- const formData = new FormData();
1763
- formData.append(name, blob, fileName);
1764
- return formData;
1473
+ function isValidURL(url) {
1474
+ try {
1475
+ new URL(url);
1476
+ return true;
1477
+ }
1478
+ catch {
1479
+ return false;
1480
+ }
1765
1481
  }
1766
-
1482
+ // Example usage:
1483
+ // console.log(isValidURL('https://www.example.com')); // true
1484
+ // console.log(isValidURL('invalid-url')); // false
1767
1485
  /**
1768
- * Converts a Proxy object to a plain object.
1769
- * @param {ProxyConstructor} proxy The Proxy object to convert.
1770
- * @returns {Object} The plain object.
1486
+ * Validates if a string is a valid date in YYYY-MM-DD format.
1487
+ * @param {string} date The date string to validate.
1488
+ * @returns {boolean} True if the date is valid, otherwise false.
1771
1489
  */
1772
- function proxyToPlainObject(proxy) {
1773
- if (!proxy)
1774
- return {};
1775
- const plainObject = {};
1776
- for (const property of Object.keys(proxy)) {
1777
- plainObject[property] = proxy[property];
1778
- }
1779
- return plainObject;
1490
+ function isValidDate(date) {
1491
+ const datePattern = /^\d{4}-\d{2}-\d{2}$/;
1492
+ if (!datePattern.test(date))
1493
+ return false;
1494
+ const [year, month, day] = date.split('-').map(Number);
1495
+ const dateObj = new Date(year, month - 1, day);
1496
+ return dateObj.getFullYear() === year && dateObj.getMonth() === month - 1 && dateObj.getDate() === day;
1780
1497
  }
1498
+ // Example usage:
1499
+ // console.log(isValidDate('2024-08-31')); // true
1500
+ // console.log(isValidDate('2024-02-30')); // false
1781
1501
  /**
1782
- * Compares two objects to check if they have the same keys.
1783
- * @param {Object} object1 The first object to compare.
1784
- * @param {Object} object2 The second object to compare.
1785
- * @returns {boolean} True if the objects have the same keys, otherwise false.
1502
+ * Validates if a password meets certain strength criteria.
1503
+ * @param {string} password The password to validate.
1504
+ * @returns {boolean} True if the password is strong, otherwise false.
1786
1505
  */
1787
- function compareObject(object1, object2) {
1788
- return Object.keys(object1).every(function (element) {
1789
- return Object.keys(object2).includes(element);
1790
- });
1506
+ function isStrongPassword(password) {
1507
+ const minLength = 8;
1508
+ const hasUpperCase = /[A-Z]/.test(password);
1509
+ const hasLowerCase = /[a-z]/.test(password);
1510
+ const hasNumbers = /[0-9]/.test(password);
1511
+ const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
1512
+ return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChars;
1791
1513
  }
1514
+ // Example usage:
1515
+ // console.log(isStrongPassword('Strong1@password')); // true
1516
+ // console.log(isStrongPassword('weakpass')); // false
1792
1517
  /**
1793
- * Deeply compares two objects to check if they are equal.
1794
- * @param {Object} object1 The first object to compare.
1795
- * @param {Object} object2 The second object to compare.
1796
- * @returns {boolean} True if the objects are deeply equal, otherwise false.
1518
+ * Validates a credit card number using the Luhn algorithm.
1519
+ * @param {string} cardNumber The credit card number to validate.
1520
+ * @returns {boolean} True if the credit card number is valid, otherwise false.
1797
1521
  */
1798
- function deepEqual(object1, object2) {
1799
- if (object1 === object2)
1800
- return true;
1801
- if (typeof object1 !== 'object' || typeof object2 !== 'object' || object1 === null || object2 === null) {
1802
- return false;
1803
- }
1804
- const keys1 = Object.keys(object1);
1805
- const keys2 = Object.keys(object2);
1806
- if (keys1.length !== keys2.length) {
1807
- return false;
1808
- }
1809
- for (const key of keys1) {
1810
- if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
1811
- return false;
1522
+ function isValidCreditCard(cardNumber) {
1523
+ const sanitized = cardNumber.replace(/\D/g, '');
1524
+ let sum = 0;
1525
+ let shouldDouble = false;
1526
+ for (let i = sanitized.length - 1; i >= 0; i--) {
1527
+ let digit = parseInt(sanitized.charAt(i), 10);
1528
+ if (shouldDouble) {
1529
+ digit *= 2;
1530
+ if (digit > 9)
1531
+ digit -= 9;
1812
1532
  }
1533
+ sum += digit;
1534
+ shouldDouble = !shouldDouble;
1813
1535
  }
1814
- return true;
1536
+ return sum % 10 === 0;
1815
1537
  }
1538
+ // Example usage:
1539
+ // console.log(isValidCreditCard('4111111111111111')); // true
1540
+ // console.log(isValidCreditCard('1234567812345670')); // false
1816
1541
  /**
1817
- * Deeply clones an object.
1818
- * @param {Object} obj The object to clone.
1819
- * @returns {Object} The cloned object.
1542
+ * Validates if a string is a valid hex color code.
1543
+ * @param {string} color The color code to validate.
1544
+ * @returns {boolean} True if the color code is valid, otherwise false.
1820
1545
  */
1821
- function deepClone(obj) {
1822
- if (obj === null || typeof obj !== 'object') {
1823
- return obj;
1824
- }
1825
- if (obj instanceof Date) {
1826
- return new Date(obj.getTime());
1827
- }
1828
- if (obj instanceof Array) {
1829
- return obj.map(item => deepClone(item));
1830
- }
1831
- if (obj instanceof Object) {
1832
- const copy = {};
1833
- for (const key in obj) {
1834
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
1835
- copy[key] = deepClone(obj[key]);
1836
- }
1837
- }
1838
- return copy;
1839
- }
1840
- throw new Error('Unable to clone object! Its type is not supported.');
1546
+ function isValidHexColor(color) {
1547
+ const hexPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/;
1548
+ return hexPattern.test(color);
1841
1549
  }
1550
+ // Example usage:
1551
+ // console.log(isValidHexColor('#FFFFFF')); // true
1552
+ // console.log(isValidHexColor('#FFF')); // true
1553
+ // console.log(isValidHexColor('#12345G')); // false
1842
1554
  /**
1843
- * Converts an object to a query string.
1844
- * @param {Object} obj The object to convert.
1845
- * @returns {string} The query string.
1555
+ * Validates if a string is a valid time in HH:MM format.
1556
+ * @param {string} time The time string to validate.
1557
+ * @returns {boolean} True if the time is valid, otherwise false.
1846
1558
  */
1847
- function objectToQueryString(obj) {
1848
- return Object.keys(obj)
1849
- .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
1850
- .join('&');
1559
+ function isValidTime(time) {
1560
+ const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
1561
+ return timePattern.test(time);
1851
1562
  }
1852
1563
  // Example usage:
1853
- // objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
1564
+ // console.log(isValidTime('14:30')); // true
1565
+ // console.log(isValidTime('25:00')); // false
1854
1566
  /**
1855
- * Gets the differences between two objects.
1856
- * @param {Object} object1 The first object.
1857
- * @param {Object} object2 The second object.
1858
- * @returns {Object} An object containing the differences.
1567
+ * Validates if a string is a valid IPv4 address.
1568
+ * @param {string} ip The IP address to validate.
1569
+ * @returns {boolean} True if the IP address is valid, otherwise false.
1859
1570
  */
1860
- function getObjectDifferences(object1, object2) {
1861
- const differences = {};
1862
- const keys = new Set([...Object.keys(object1), ...Object.keys(object2)]);
1863
- for (const key of keys) {
1864
- if (object1[key] !== object2[key]) {
1865
- differences[key] = { object1: object1[key], object2: object2[key] };
1866
- }
1867
- }
1868
- return differences;
1571
+ function isValidIP(ip) {
1572
+ const ipPattern = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$/;
1573
+ return ipPattern.test(ip);
1869
1574
  }
1575
+ // Example usage:
1576
+ // console.log(isValidIP('192.168.1.1')); // true
1577
+ // console.log(isValidIP('999.999.999.999')); // false
1870
1578
  /**
1871
- * Filters an object by a list of keys.
1872
- * @param {Object} obj The object to filter.
1873
- * @param {Array<string>} keys The keys to keep.
1874
- * @returns {Object} The filtered object.
1875
- */
1876
- function filterObjectByKeys(obj, keys) {
1877
- const filteredObject = {};
1878
- for (const key of keys) {
1879
- if (key in obj) {
1880
- filteredObject[key] = obj[key];
1881
- }
1882
- }
1883
- return filteredObject;
1579
+ * Validates if a string is a valid U.S. Social Security Number (SSN).
1580
+ * @param {string} ssn The SSN to validate.
1581
+ * @returns {boolean} True if the SSN is valid, otherwise false.
1582
+ */
1583
+ function isValidSSN(ssn) {
1584
+ const ssnPattern = /^\d{3}-\d{2}-\d{4}$/;
1585
+ return ssnPattern.test(ssn);
1884
1586
  }
1885
1587
  // Example usage:
1886
- // filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
1588
+ // console.log(isValidSSN('123-45-6789')); // true
1589
+ // console.log(isValidSSN('123-45-678')); // false
1887
1590
  /**
1888
- * Deeply merges two objects.
1889
- * @param {Object} target The target object to merge into.
1890
- * @param {Object} source The source object to merge from.
1891
- * @returns {Object} The merged object.
1591
+ * Validates if a string is a valid U.S. ZIP code.
1592
+ * @param {string} zip The ZIP code to validate.
1593
+ * @returns {boolean} True if the ZIP code is valid, otherwise false.
1892
1594
  */
1893
- function deepMerge(target, source) {
1894
- if (target === null || typeof target !== 'object' || typeof source !== 'object') {
1895
- return target;
1896
- }
1897
- for (const key in source) {
1898
- if (Object.prototype.hasOwnProperty.call(source, key)) {
1899
- if (source[key] && typeof source[key] === 'object') {
1900
- if (target && !target[key]) {
1901
- Object.assign(target, { [key]: {} });
1902
- }
1903
- deepMerge(target[key], source[key]);
1904
- }
1905
- else {
1906
- Object.assign(target, { [key]: source[key] });
1907
- }
1908
- }
1909
- }
1910
- return target;
1595
+ function isValidZIP(zip) {
1596
+ const zipPattern = /^\d{5}(-\d{4})?$/;
1597
+ return zipPattern.test(zip);
1911
1598
  }
1599
+ // Example usage:
1600
+ // console.log(isValidZIP('12345')); // true
1601
+ // console.log(isValidZIP('12345-6789')); // true
1602
+ // console.log(isValidZIP('1234')); // false
1912
1603
  /**
1913
- * Checks if an object is empty.
1914
- * @param {Object} obj The object to check.
1915
- * @returns {boolean} True if the object is empty, otherwise false.
1604
+ * Validates if a string is a valid credit card expiry date in MM/YY format.
1605
+ * @param {string} expiryDate The expiry date to validate.
1606
+ * @returns {boolean} True if the expiry date is valid, otherwise false.
1916
1607
  */
1917
- function isEmptyObject(obj) {
1918
- return Object.keys(obj).length === 0;
1608
+ function isValidExpiryDate(expiryDate) {
1609
+ const expiryPattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
1610
+ if (!expiryPattern.test(expiryDate))
1611
+ return false;
1612
+ const [month, year] = expiryDate.split('/').map(Number);
1613
+ const currentYear = new Date().getFullYear() % 100;
1614
+ const currentMonth = new Date().getMonth() + 1;
1615
+ return (year > currentYear) || (year === currentYear && month >= currentMonth);
1919
1616
  }
1617
+ // Example usage:
1618
+ // console.log(isValidExpiryDate('08/24')); // true
1619
+ // console.log(isValidExpiryDate('12/22')); // false
1920
1620
  /**
1921
- * Safely accesses nested properties in an object.
1922
- * @param {Object} obj The object to access.
1923
- * @param {Array<string>} keys The array of keys representing the path.
1924
- * @returns {any} The value at the nested path, or undefined if not found.
1621
+ * Validates if a string is a valid 8-character hexadecimal color code (including alpha).
1622
+ * @param {string} color The color code to validate.
1623
+ * @returns {boolean} True if the color code is valid, otherwise false.
1925
1624
  */
1926
- function safeGet(obj, keys) {
1927
- return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
1625
+ function isValidHexColorAlpha(color) {
1626
+ const hexPattern = /^#([0-9A-Fa-f]{8})$/;
1627
+ return hexPattern.test(color);
1928
1628
  }
1929
1629
  // Example usage:
1930
- // safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
1931
- // safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
1630
+ // console.log(isValidHexColorAlpha('#RRGGBBAA')); // true
1631
+ // console.log(isValidHexColorAlpha('#FFFFFF')); // false
1932
1632
  /**
1933
- * Removes empty properties (null, undefined, or empty string) from an object.
1934
- * @param {Object} obj The object to clean.
1935
- * @returns {Object} A new object without empty properties.
1633
+ * Validates if a username meets specific criteria.
1634
+ * @param {string} username The username to validate.
1635
+ * @returns {boolean} True if the username is valid, otherwise false.
1936
1636
  */
1937
- function removeEmptyProperties(obj) {
1938
- return Object.keys(obj)
1939
- .filter(key => obj[key] !== null && obj[key] !== undefined && obj[key] !== '')
1940
- .reduce((acc, key) => {
1941
- acc[key] = obj[key];
1942
- return acc;
1943
- }, {});
1637
+ function isValidUsername(username) {
1638
+ const usernamePattern = /^[a-zA-Z0-9_]{3,16}$/; // 3 to 16 characters, letters, numbers, and underscores only
1639
+ return usernamePattern.test(username);
1944
1640
  }
1945
1641
  // Example usage:
1946
- // removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
1642
+ // console.log(isValidUsername('user_name123')); // true
1643
+ // console.log(isValidUsername('us')); // false
1947
1644
  /**
1948
- * Retrieves all keys of an object as an array.
1949
- * @param {Object} obj The object to retrieve keys from.
1950
- * @returns {Array<string>} The array of keys.
1645
+ * Validates if a string represents a valid age between 0 and 120.
1646
+ * @param {string} age The age to validate.
1647
+ * @returns {boolean} True if the age is valid, otherwise false.
1951
1648
  */
1952
- function getObjectKeys(obj) {
1953
- return Object.keys(obj);
1649
+ function isValidAge(age) {
1650
+ const ageNumber = parseInt(age, 10);
1651
+ return !isNaN(ageNumber) && ageNumber >= 0 && ageNumber <= 120;
1954
1652
  }
1955
1653
  // Example usage:
1956
- // getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
1654
+ // console.log(isValidAge('25')); // true
1655
+ // console.log(isValidAge('121')); // false
1957
1656
  /**
1958
- * Checks if an object has nested properties.
1959
- * @param {Object} obj The object to check.
1960
- * @returns {boolean} True if there are nested properties, false otherwise.
1657
+ * Validates if a string is a valid hexadecimal number.
1658
+ * @param {string} hex The hexadecimal number to validate.
1659
+ * @returns {boolean} True if the number is valid, otherwise false.
1961
1660
  */
1962
- function hasNestedProperties(obj) {
1963
- return Object.values(obj).some(value => typeof value === 'object' && value !== null);
1661
+ function isValidHexNumber(hex) {
1662
+ const hexPattern = /^[0-9A-Fa-f]+$/;
1663
+ return hexPattern.test(hex);
1964
1664
  }
1965
1665
  // Example usage:
1966
- // hasNestedProperties({ a: 1, b: { c: 2 } }); // true
1967
- // hasNestedProperties({ a: 1, b: 2 }); // false
1666
+ // console.log(isValidHexNumber('1A3F')); // true
1667
+ // console.log(isValidHexNumber('GHIJ')); // false
1668
+
1968
1669
  /**
1969
- * Converts an object to FormData, handling nested objects.
1970
- * @param {Object} obj The object to convert.
1971
- * @param {FormData} [formData] The FormData object to append to.
1972
- * @param {string} [parentKey] The parent key for nested objects.
1973
- * @returns {FormData} The FormData object.
1670
+ * Object defining available screen sizes as constants.
1671
+ * @readonly
1974
1672
  */
1975
- function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
1976
- Object.entries(obj).forEach(([key, value]) => {
1977
- const finalKey = parentKey ? `${parentKey}[${key}]` : key;
1978
- if (value && typeof value === 'object' && !(value instanceof File)) {
1979
- objectToFormDataEnhanced(value, formData, finalKey);
1980
- }
1981
- else {
1982
- formData.append(finalKey, value);
1983
- }
1984
- });
1985
- return formData;
1986
- }
1987
- // Example usage:
1988
- // objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
1673
+ const sizeEnum = {
1674
+ XS: 'XS',
1675
+ SM: 'SM',
1676
+ MD: 'MD',
1677
+ LG: 'LG',
1678
+ XL: 'XL',
1679
+ XXL: 'XXL',
1680
+ };
1989
1681
  /**
1990
- * Converts a JavaScript object into FormData.
1991
- *
1992
- * @param obj - The object to be converted.
1993
- * @param form - An optional FormData instance to use.
1994
- * @param namespace - An optional namespace to use for nested objects.
1995
- * @returns The FormData instance with the object's key-value pairs.
1682
+ * Object defining breakpoints for design based on screen width.
1683
+ * Values are in pixels.
1684
+ * @readonly
1996
1685
  */
1997
- const objectToFormData = function (obj, form, namespace) {
1998
- const fd = form || new FormData();
1999
- let formKey;
2000
- for (const property in obj) {
2001
- if (obj[property] === undefined) {
2002
- continue;
2003
- }
2004
- if (Object.prototype.hasOwnProperty.call(obj, property)) {
2005
- if (namespace) {
2006
- formKey = `${namespace}[${property}]`;
2007
- }
2008
- else {
2009
- formKey = property;
2010
- }
2011
- if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
2012
- // Recursively handle nested objects
2013
- objectToFormData(obj[property], fd, formKey);
2014
- }
2015
- else {
2016
- // Convert boolean values to 1/0
2017
- const value = obj[property] === true || obj[property] === false ? Number(obj[property]) : obj[property];
2018
- fd.append(formKey, value);
2019
- }
2020
- }
2021
- }
2022
- return fd;
1686
+ const screenEnum = {
1687
+ XS: 480,
1688
+ SM: 576,
1689
+ MD: 768,
1690
+ LG: 992,
1691
+ XL: 1200,
1692
+ XXL: 1600,
1693
+ };
1694
+ /**
1695
+ * Map that associates each screen size defined in `sizeEnum` with its corresponding pixel value from `screenEnum`.
1696
+ * @type {Map<typeof sizeEnum[keyof typeof sizeEnum], number>}
1697
+ */
1698
+ const screenMap = new Map();
1699
+ screenMap.set(sizeEnum.XS, screenEnum.XS);
1700
+ screenMap.set(sizeEnum.SM, screenEnum.SM);
1701
+ screenMap.set(sizeEnum.MD, screenEnum.MD);
1702
+ screenMap.set(sizeEnum.LG, screenEnum.LG);
1703
+ screenMap.set(sizeEnum.XL, screenEnum.XL);
1704
+ screenMap.set(sizeEnum.XXL, screenEnum.XXL);
1705
+
1706
+ /**
1707
+ * Object defining various exception codes.
1708
+ * @readonly
1709
+ */
1710
+ const ExceptionEnum = {
1711
+ /**
1712
+ * HTTP status code for page access forbidden.
1713
+ */
1714
+ PAGE_NOT_ACCESS: 403,
1715
+ /**
1716
+ * HTTP status code for page not found.
1717
+ */
1718
+ PAGE_NOT_FOUND: 404,
1719
+ /**
1720
+ * HTTP status code for general server error.
1721
+ */
1722
+ ERROR: 500,
1723
+ /**
1724
+ * HTTP status code for bad request.
1725
+ */
1726
+ BAD_REQUEST: 406,
1727
+ /**
1728
+ * Custom error code for network errors.
1729
+ */
1730
+ NET_WORK_ERROR: 10000,
1731
+ /**
1732
+ * Custom code indicating no data on the page, not actually an exception page.
1733
+ */
1734
+ PAGE_NOT_DATA: 10100,
1735
+ };
1736
+
1737
+ /**
1738
+ * Object defining various image MIME types.
1739
+ * @readonly
1740
+ */
1741
+ const ImageTypes = {
1742
+ apng: 'image/apng',
1743
+ bmp: 'image/bmp',
1744
+ gif: 'image/gif',
1745
+ jpeg: 'image/jpeg',
1746
+ pjpeg: 'image/pjpeg',
1747
+ png: 'image/png',
1748
+ svg: 'image/svg+xml',
1749
+ tiff: 'image/tiff',
1750
+ webp: 'image/webp',
1751
+ xicon: 'image/x-icon',
1752
+ };
1753
+ /**
1754
+ * Object defining various file meta types.
1755
+ * @readonly
1756
+ */
1757
+ const FileMetaTypes = {
1758
+ images: 'image/*',
1759
+ audios: 'audio/*',
1760
+ videos: 'video/*',
2023
1761
  };
1762
+
2024
1763
  /**
2025
- * Flattens a nested object, bringing all properties to the top level.
2026
- * @param {Object} obj The object to flatten.
2027
- * @param {string} [parentKey] The parent key for nested properties.
2028
- * @param {Object} [result] The resulting flattened object.
2029
- * @returns {Object} The flattened object.
1764
+ * Object representing different content types for HTTP headers.
1765
+ * @readonly
2030
1766
  */
2031
- function flattenObject(obj, parentKey = '', result = {}) {
2032
- for (const key in obj) {
2033
- if (obj.hasOwnProperty(key)) {
2034
- const propName = parentKey ? `${parentKey}.${key}` : key;
2035
- if (typeof obj[key] === 'object' && obj[key] !== null) {
2036
- flattenObject(obj[key], propName, result);
2037
- }
2038
- else {
2039
- result[propName] = obj[key];
2040
- }
2041
- }
2042
- }
2043
- return result;
2044
- }
2045
- // Example usage:
2046
- // flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
1767
+ const ContentTypeEnum = {
1768
+ /**
1769
+ * Content type for JSON.
1770
+ * @constant
1771
+ */
1772
+ JSON: 'application/json;charset=UTF-8',
1773
+ /**
1774
+ * Content type for URL-encoded form data.
1775
+ * @constant
1776
+ */
1777
+ FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
1778
+ /**
1779
+ * Content type for multipart form data with file uploads.
1780
+ * @constant
1781
+ */
1782
+ FORM_DATA: 'multipart/form-data;charset=UTF-8',
1783
+ };
2047
1784
 
2048
1785
  /**
2049
- * Capitalizes the first character of a string.
2050
- * @param {string} s The string to capitalize.
2051
- * @returns {string} The capitalized string.
1786
+ * Object defining various key codes for keyboard events.
1787
+ * @readonly
2052
1788
  */
2053
- function upperFirst(s) {
2054
- return s.charAt(0).toUpperCase() + s.slice(1);
2055
- }
2056
- // Example usage:
2057
- // console.log(upperFirst('hello')); // 'Hello'
1789
+ const KeyCodeEnum = {
1790
+ UP: 38,
1791
+ DOWN: 40,
1792
+ ENTER: 13,
1793
+ ESC: 27,
1794
+ };
1795
+
2058
1796
  /**
2059
- * Lowercases the first character of a string.
2060
- * @param {string} s The string to lowercase.
2061
- * @returns {string} The lowercased string.
1797
+ * Key used for storing the authentication token.
1798
+ * @constant {string}
2062
1799
  */
2063
- function lowerFirst(s) {
2064
- return s.charAt(0).toLowerCase() + s.slice(1);
2065
- }
2066
- // Example usage:
2067
- // console.log(lowerFirst('Hello')); // 'hello'
1800
+ const TOKEN_KEY = 'TOKEN__';
2068
1801
  /**
2069
- * Removes accents and special characters from a string and converts it to a URL-friendly format.
2070
- * @param {string} input The string to process.
2071
- * @returns {string} The processed string.
1802
+ * Key used for storing the locale information.
1803
+ * @constant {string}
2072
1804
  */
2073
- function removeAccent(input) {
2074
- return input
2075
- .toLowerCase()
2076
- .trim()
2077
- .replace(/[\s_-]+/g, "-")
2078
- .replace(/^-+|-+$/g, "")
2079
- .normalize("NFD")
2080
- .replace(/[\u0300-\u036f]/g, "");
2081
- }
2082
- // Example usage:
2083
- // console.log(removeAccent('Café du Nord')); // 'cafe-du-nord'
1805
+ const LOCALE_KEY = 'LOCALE__';
2084
1806
  /**
2085
- * Reverses a string.
2086
- * @param {string} str The string to reverse.
2087
- * @returns {string} The reversed string.
1807
+ * Key used for storing user information.
1808
+ * @constant {string}
2088
1809
  */
2089
- function reverseString(str) {
2090
- return str.split('').reverse().join('');
2091
- }
2092
- // Example usage:
2093
- // console.log(reverseString('hello')); // 'olleh'
1810
+ const USER_INFO_KEY = 'USER__INFO__';
2094
1811
  /**
2095
- * Counts the number of words in a string.
2096
- * @param {string} str The string to analyze.
2097
- * @returns {number} The word count.
1812
+ * Key used for storing role information.
1813
+ * @constant {string}
2098
1814
  */
2099
- function countWords(str) {
2100
- return str.trim().split(/\s+/).length;
2101
- }
2102
- // Example usage:
2103
- // console.log(countWords('Hello world!')); // 2
1815
+ const ROLES_KEY = 'ROLES__KEY__';
2104
1816
  /**
2105
- * Truncates a string to the specified length and adds ellipsis if necessary.
2106
- * @param {string} str The string to truncate.
2107
- * @param {number} maxLength The maximum length of the string.
2108
- * @returns {string} The truncated string.
1817
+ * Key used for storing project configuration.
1818
+ * @constant {string}
2109
1819
  */
2110
- function truncateString(str, maxLength) {
2111
- return str.length > maxLength ? str.slice(0, maxLength) + '...' : str;
2112
- }
2113
- // Example usage:
2114
- // console.log(truncateString('This is a long string', 10)); // 'This is a...'
1820
+ const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
2115
1821
  /**
2116
- * Converts a string to camel case.
2117
- * @param {string} str The string to convert.
2118
- * @returns {string} The camel cased string.
1822
+ * Key used for storing lock information.
1823
+ * @constant {string}
2119
1824
  */
2120
- function toCamelCase(str) {
2121
- return str
2122
- .toLowerCase()
2123
- .replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
2124
- }
2125
- // Example usage:
2126
- // console.log(toCamelCase('hello world example')); // 'helloWorldExample'
1825
+ const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
2127
1826
  /**
2128
- * Converts a string to kebab case.
2129
- * @param {string} str The string to convert.
2130
- * @returns {string} The kebab cased string.
1827
+ * Key used for base global local cache.
1828
+ * @constant {string}
2131
1829
  */
2132
- function toKebabCase(str) {
2133
- return str
2134
- .replace(/([a-z])([A-Z])/g, '$1-$2')
2135
- .replace(/[\s_]+/g, '-')
2136
- .toLowerCase();
2137
- }
2138
- // Example usage:
2139
- // console.log(toKebabCase('Hello World Example')); // 'hello-world-example'
1830
+ const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY';
2140
1831
  /**
2141
- * Replaces all instances of a substring within a string.
2142
- * @param {string} str The original string.
2143
- * @param {string} find The substring to find.
2144
- * @param {string} replace The substring to replace with.
2145
- * @returns {string} The modified string.
1832
+ * Key used for base global session cache.
1833
+ * @constant {string}
2146
1834
  */
2147
- function replaceAll(str, find, replace) {
2148
- return str.split(find).join(replace);
2149
- }
2150
- // Example usage:
2151
- // console.log(replaceAll('hello world', 'o', 'a')); // 'hella warld'
1835
+ const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY';
2152
1836
  /**
2153
- * Generates a random string of a specific length.
2154
- * @param {number} length The length of the string to generate.
2155
- * @returns {string} The random string.
1837
+ * Object defining types of cache storage.
1838
+ * @readonly
2156
1839
  */
2157
- function generateRandomString(length) {
2158
- const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
2159
- return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
1840
+ const CacheTypeEnum = {
1841
+ /** Represents session storage */
1842
+ SESSION: 0,
1843
+ /** Represents local storage */
1844
+ LOCAL: 1,
1845
+ };
1846
+
1847
+ const ERROR_MESSAGES = {
1848
+ warning: 'Algo no está del todo bien. Verifica y vuelve a intentar.',
1849
+ error: 'Ocurrió un error inesperado.',
1850
+ critical: 'Error crítico. Contacta al soporte técnico.',
1851
+ validation: 'Hay errores en el formulario. Revisa los campos.',
1852
+ component: 'Error en la carga del componente. Intenta recargar.',
1853
+ network: 'Problema de conexión. Verifica tu internet.',
1854
+ authentication: 'No tienes permisos para realizar esta acción.',
1855
+ runtime: 'Error de ejecución. Intenta nuevamente.',
1856
+ type: 'Tipo de error no identificado.',
1857
+ reference: 'Referencia no válida.',
1858
+ syntax: 'Error de sintaxis en el código.',
1859
+ range: 'Error en el rango de valores.',
1860
+ eval: 'Error en la evaluación.',
1861
+ uri: 'URI no válida.',
1862
+ };
1863
+ const ERROR_STYLES = {
1864
+ warning: 'color: orange; font-weight: bold;',
1865
+ error: 'color: red; font-weight: bold;',
1866
+ critical: 'color: white; background-color: red; font-weight: bold; padding: 2px;',
1867
+ validation: 'color: blue; font-weight: bold;',
1868
+ component: 'color: purple; font-weight: bold;',
1869
+ network: 'color: cyan; font-weight: bold;',
1870
+ authentication: 'color: brown; font-weight: bold;',
1871
+ runtime: 'color: darkred; font-weight: bold;',
1872
+ type: 'color: gray; font-weight: bold;',
1873
+ reference: 'color: darkorange; font-weight: bold;',
1874
+ syntax: 'color: darkblue; font-weight: bold;',
1875
+ range: 'color: darkgreen; font-weight: bold;',
1876
+ eval: 'color: darkviolet; font-weight: bold;',
1877
+ uri: 'color: darkcyan; font-weight: bold;',
1878
+ };
1879
+
1880
+ class RestStd {
1881
+ static resource;
1882
+ static isFormData = false;
1883
+ static headers = {};
1884
+ static fetchComposable;
1885
+ /**
1886
+ * Set global headers for all requests.
1887
+ * @param headers Object containing headers to be set globally.
1888
+ */
1889
+ static setHeaders(headers) {
1890
+ this.headers = { ...this.headers, ...headers };
1891
+ }
1892
+ /**
1893
+ * Convert data to FormData if isFormData is true, otherwise return the data as is.
1894
+ * @param data Data to be converted.
1895
+ * @returns Data in FormData format or as is.
1896
+ */
1897
+ static transformData(data) {
1898
+ if (this.isFormData) {
1899
+ const formData = objectToFormData(data);
1900
+ return formData;
1901
+ }
1902
+ return data;
1903
+ }
1904
+ /**
1905
+ * Fetch a list of items from the server.
1906
+ *
1907
+ * @param params Query parameters for filtering the results.
1908
+ * @param options Additional options for the fetch composable.
1909
+ * @returns The result of the fetch composable (typically a promise).
1910
+ */
1911
+ static getMany(params, options = {}) {
1912
+ return this.fetchComposable({
1913
+ method: 'GET',
1914
+ url: this.resource,
1915
+ params,
1916
+ headers: this.headers,
1917
+ }, options);
1918
+ }
1919
+ /**
1920
+ * Fetch a single item by ID from the server.
1921
+ *
1922
+ * @param id The ID of the item to fetch.
1923
+ * @param params Additional query parameters for the request.
1924
+ * @param options Additional options for the fetch composable.
1925
+ * @returns The result of the fetch composable (typically a promise).
1926
+ */
1927
+ static getOne(id, params, options = {}) {
1928
+ return this.fetchComposable({
1929
+ method: 'GET',
1930
+ url: `${this.resource}/${id}`,
1931
+ params,
1932
+ headers: this.headers,
1933
+ }, options);
1934
+ }
1935
+ /**
1936
+ * Create a new item on the server.
1937
+ *
1938
+ * @param data The data for the new item to create.
1939
+ * @param options Additional options for the fetch composable.
1940
+ * @returns The result of the fetch composable (typically a promise).
1941
+ */
1942
+ static create(data, options = {}) {
1943
+ const transformedData = this.transformData(data);
1944
+ return this.fetchComposable({
1945
+ method: 'POST',
1946
+ url: this.resource,
1947
+ data: transformedData,
1948
+ headers: {
1949
+ ...this.headers,
1950
+ 'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
1951
+ },
1952
+ }, options);
1953
+ }
1954
+ /**
1955
+ * Update an existing item on the server.
1956
+ *
1957
+ * @param id The ID of the item to update.
1958
+ * @param data The updated data for the item.
1959
+ * @param options Additional options for the fetch composable.
1960
+ * @returns The result of the fetch composable (typically a promise).
1961
+ */
1962
+ static update(id, data, options = {}) {
1963
+ const transformedData = this.transformData(data);
1964
+ return this.fetchComposable({
1965
+ method: 'PUT',
1966
+ url: `${this.resource}/${id}`,
1967
+ data: transformedData,
1968
+ headers: {
1969
+ ...this.headers,
1970
+ 'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
1971
+ },
1972
+ }, options);
1973
+ }
1974
+ /**
1975
+ * Partially update an existing item on the server.
1976
+ *
1977
+ * @param id The ID of the item to update.
1978
+ * @param data The updated data for the item.
1979
+ * @param options Additional options for the fetch composable.
1980
+ * @returns The result of the fetch composable (typically a promise).
1981
+ */
1982
+ static patch(id, data, options = {}) {
1983
+ const transformedData = this.transformData(data);
1984
+ return this.fetchComposable({
1985
+ method: 'PATCH',
1986
+ url: `${this.resource}/${id}`,
1987
+ data: transformedData,
1988
+ headers: {
1989
+ ...this.headers,
1990
+ 'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
1991
+ },
1992
+ }, options);
1993
+ }
1994
+ /**
1995
+ * Delete an item from the server.
1996
+ *
1997
+ * @param id The ID of the item to delete.
1998
+ * @param options Additional options for the fetch composable.
1999
+ * @returns The result of the fetch composable (typically a promise).
2000
+ */
2001
+ static delete(id, options = {}) {
2002
+ return this.fetchComposable({
2003
+ method: 'DELETE',
2004
+ url: `${this.resource}/${id}`,
2005
+ headers: this.headers,
2006
+ }, options);
2007
+ }
2008
+ /**
2009
+ * Custom request method for more flexibility.
2010
+ *
2011
+ * @param method HTTP method (GET, POST, etc.).
2012
+ * @param params Query parameters.
2013
+ * @param data Request body data.
2014
+ * @param options Additional options for the fetch composable.
2015
+ * @returns The result of the fetch composable (typically a promise).
2016
+ */
2017
+ static customRequest(method, params, data, options = {}) {
2018
+ const transformedData = this.transformData(data);
2019
+ return this.fetchComposable({
2020
+ method: method,
2021
+ url: this.resource,
2022
+ params: params,
2023
+ data: transformedData,
2024
+ headers: {
2025
+ ...this.headers,
2026
+ 'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
2027
+ },
2028
+ }, options);
2029
+ }
2030
+ /**
2031
+ * Save method that decides whether to create or update based on the presence of 'id'.
2032
+ * It calls either create or update based on the data.
2033
+ *
2034
+ * @param data The data for the item to create or update.
2035
+ * @param options Additional options for the fetch composable.
2036
+ * @returns The result of the fetch composable (typically a promise).
2037
+ */
2038
+ static save(data, options = {}) {
2039
+ if (data.id) {
2040
+ return this.update(data.id, data, options);
2041
+ }
2042
+ else {
2043
+ return this.create(data, options);
2044
+ }
2045
+ }
2160
2046
  }
2161
- // Example usage:
2162
- // console.log(generateRandomString(10)); // 'A1b2C3d4E5'
2163
2047
 
2164
2048
  /**
2165
- * Validates if the key pressed is a valid letter or special character.
2166
- * @param {KeyboardEvent} e The keyboard event.
2167
- * @returns {boolean} True if the key is valid, otherwise false.
2049
+ * Custom composable function for making API requests with Axios.
2050
+ *
2051
+ * @template T The expected return type of the API request.
2052
+ * @param {AxiosInstance} axios - The Axios instance for making HTTP requests.
2053
+ * @param {AxiosRequestConfig} axiosRequest - Configuration for the Axios request.
2054
+ * @returns {Promise<T>} A promise with the result of the API request.
2168
2055
  */
2169
- function validateLetters(e) {
2170
- const key = e.keyCode;
2171
- const validKeys = [
2172
- ...Array.from({ length: 26 }, (_, i) => i + 65), // Letters A-Z
2173
- ...Array.from({ length: 26 }, (_, i) => i + 97), // Letters a-z
2174
- 45, // Hyphen
2175
- 32, // Space
2176
- 241, // ñ
2177
- 209, // Ñ
2178
- 225, // á
2179
- 233, // é
2180
- 237, // í
2181
- 243, // ó
2182
- 250, // ú
2183
- 193, // Á
2184
- 201, // É
2185
- 205, // Í
2186
- 211, // Ó
2187
- 218 // Ú
2188
- ];
2189
- if (validKeys.includes(key)) {
2190
- return true;
2191
- }
2192
- e.preventDefault();
2193
- return false;
2056
+ async function axiosFetch(axios, axiosRequest) {
2057
+ return axios(axiosRequest)
2058
+ .then(response => response.data)
2059
+ .catch(error => {
2060
+ throw error;
2061
+ });
2194
2062
  }
2195
- // Example usage:
2196
- // document.addEventListener('keydown', validateLetters);
2063
+
2197
2064
  /**
2198
- * Validates if the key pressed is a valid alphanumeric character.
2199
- * @param {KeyboardEvent} e The keyboard event.
2200
- * @returns {boolean} True if the key is valid, otherwise false.
2201
- */
2202
- function validateAlphanumeric(e) {
2203
- const key = e.keyCode;
2204
- const validKeys = [
2205
- ...Array.from({ length: 10 }, (_, i) => i + 48), // Numbers 0-9
2206
- ...Array.from({ length: 26 }, (_, i) => i + 65), // Letters A-Z
2207
- ...Array.from({ length: 26 }, (_, i) => i + 97), // Letters a-z
2208
- 45, // Hyphen
2209
- 95, // Underscore
2210
- 32 // Space
2211
- ];
2212
- if (validKeys.includes(key)) {
2213
- return true;
2065
+ * Maneja y registra errores en la consola, además de permitir redirecciones configurables.
2066
+ * @param error - Error capturado (string o Error)
2067
+ * @param redirect - `true` si se quiere redirigir a una página de error
2068
+ * @param route - Ruta personalizada para la página de error (por defecto: `/error`)
2069
+ * @param passMethod - Método para pasar el error a la ruta (`query`, `localStorage`, `sessionStorage`)
2070
+ * @returns Mensaje user-friendly del error
2071
+ */
2072
+ function handleError(error, redirect = false, route = '/error', passMethod = 'query') {
2073
+ if (!(error instanceof Error)) {
2074
+ console.error("Unrecognized error:", error);
2075
+ return;
2214
2076
  }
2215
- e.preventDefault();
2216
- return false;
2217
- }
2218
- // Example usage:
2219
- // document.addEventListener('keydown', validateAlphanumeric);
2220
- /**
2221
- * Validates if the key pressed is a number.
2222
- * @param {KeyboardEvent} e The keyboard event.
2223
- * @returns {boolean} True if the key is a number, otherwise false.
2224
- */
2225
- function validateNumbers(e) {
2226
- const key = e.keyCode;
2227
- if ((key >= 48 && key <= 57) || key === 46 || key === 8 || key === 37 || key === 39) {
2228
- return true;
2077
+ const router = useRouter();
2078
+ const type = inferErrorType(error);
2079
+ const message = ERROR_MESSAGES[type] || 'Error desconocido.';
2080
+ // Mostrar el error en la consola con estilos
2081
+ console.log(`%c[${type.toUpperCase()}] ${message}`, ERROR_STYLES[type]);
2082
+ // Pasar el error según el método elegido
2083
+ if (redirect) {
2084
+ if (passMethod === 'query') {
2085
+ router.push({ path: route, query: { errorMessage: encodeURIComponent(message) } });
2086
+ }
2087
+ else if (passMethod === 'localStorage') {
2088
+ localStorage.setItem('errorMessage', message);
2089
+ router.push(route);
2090
+ }
2091
+ else if (passMethod === 'sessionStorage') {
2092
+ sessionStorage.setItem('errorMessage', message);
2093
+ router.push(route);
2094
+ }
2229
2095
  }
2230
- e.preventDefault();
2231
- return false;
2232
- }
2233
- // Example usage:
2234
- // document.addEventListener('keydown', validateNumbers);
2235
- /**
2236
- * Validates if a phone number is valid.
2237
- * @param {string} phoneNumber The phone number to validate.
2238
- * @returns {boolean} True if the phone number is valid, otherwise false.
2239
- */
2240
- function isValidPhoneNumber(phoneNumber) {
2241
- const phonePattern = /^[0-9]{10}$/; // Example pattern for 10-digit phone numbers
2242
- return phonePattern.test(phoneNumber);
2096
+ return message;
2097
+ }
2098
+ /**
2099
+ * Infiera el tipo de error basado en su mensaje o nombre.
2100
+ * @param error - El error que se desea analizar.
2101
+ * @returns El tipo de error determinado.
2102
+ */
2103
+ function inferErrorType(error) {
2104
+ const msg = typeof error === 'string' ? error : error.message;
2105
+ if (typeof error === 'string')
2106
+ return 'error';
2107
+ if (error.name === 'ValidationError')
2108
+ return 'validation';
2109
+ if (error.name === 'NetworkError' || msg.includes('Network'))
2110
+ return 'network';
2111
+ if (error.name === 'AuthenticationError' || msg.includes('Unauthorized'))
2112
+ return 'authentication';
2113
+ if (msg.includes('Component'))
2114
+ return 'component';
2115
+ if (msg.includes('Runtime'))
2116
+ return 'runtime';
2117
+ return 'error';
2243
2118
  }
2244
- // Example usage:
2245
- // console.log(isValidPhoneNumber('1234567890')); // true
2246
- // console.log(isValidPhoneNumber('123-456-7890')); // false
2119
+
2120
+ let tokenConfig = {
2121
+ ACCESS_TOKEN: 'authToken',
2122
+ REFRESH_TOKEN: 'refreshToken',
2123
+ };
2247
2124
  /**
2248
- * Validates if a string is a valid email address.
2249
- * @param {string} email The email address to validate.
2250
- * @returns {boolean} True if the email address is valid, otherwise false.
2125
+ * Función para configurar las claves globalmente.
2126
+ * Esta función congela el objeto para evitar modificaciones posteriores.
2127
+ * @param {string} accessTokenKey - Nombre de la clave del token de acceso
2128
+ * @param {string} refreshTokenKey - Nombre de la clave del refresh token
2251
2129
  */
2252
- function isValidEmail(email) {
2253
- const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2254
- return emailPattern.test(email);
2130
+ function configureTokenKeys(accessTokenKey, refreshTokenKey) {
2131
+ tokenConfig = Object.freeze({
2132
+ ACCESS_TOKEN: accessTokenKey,
2133
+ REFRESH_TOKEN: refreshTokenKey,
2134
+ });
2255
2135
  }
2256
- // Example usage:
2257
- // console.log(isValidEmail('example@domain.com')); // true
2258
- // console.log(isValidEmail('invalid-email')); // false
2259
2136
  /**
2260
- * Validates if a string is a valid URL.
2261
- * @param {string} url The URL to validate.
2262
- * @returns {boolean} True if the URL is valid, otherwise false.
2137
+ * Obtiene las claves configuradas para los tokens.
2138
+ * El objeto está congelado, por lo que no se puede modificar directamente.
2263
2139
  */
2264
- function isValidURL(url) {
2265
- try {
2266
- new URL(url);
2267
- return true;
2268
- }
2269
- catch {
2270
- return false;
2271
- }
2140
+ function getTokenConfig() {
2141
+ return tokenConfig;
2272
2142
  }
2273
- // Example usage:
2274
- // console.log(isValidURL('https://www.example.com')); // true
2275
- // console.log(isValidURL('invalid-url')); // false
2143
+
2144
+ let endpointsConfig = {
2145
+ LOGIN: '/login',
2146
+ REFRESH: '/refresh',
2147
+ LOGOUT: '/logout',
2148
+ };
2276
2149
  /**
2277
- * Validates if a string is a valid date in YYYY-MM-DD format.
2278
- * @param {string} date The date string to validate.
2279
- * @returns {boolean} True if the date is valid, otherwise false.
2150
+ * Configura las URLs de los endpoints de autenticación globalmente.
2151
+ * Esta función congela el objeto para evitar modificaciones posteriores.
2152
+ *
2153
+ * @param {string} loginEndpoint - URL del endpoint para el login.
2154
+ * @param {string} refreshEndpoint - URL del endpoint para el refresh token.
2155
+ * @param {string} logoutEndpoint - URL del endpoint para el logout.
2156
+ *
2157
+ * @returns {void} No retorna nada, pero congela el objeto de configuración de endpoints.
2280
2158
  */
2281
- function isValidDate(date) {
2282
- const datePattern = /^\d{4}-\d{2}-\d{2}$/;
2283
- if (!datePattern.test(date))
2284
- return false;
2285
- const [year, month, day] = date.split('-').map(Number);
2286
- const dateObj = new Date(year, month - 1, day);
2287
- return dateObj.getFullYear() === year && dateObj.getMonth() === month - 1 && dateObj.getDate() === day;
2159
+ function configureEndpoints(loginEndpoint, refreshEndpoint, logoutEndpoint) {
2160
+ endpointsConfig = Object.freeze({
2161
+ LOGIN: loginEndpoint,
2162
+ REFRESH: refreshEndpoint,
2163
+ LOGOUT: logoutEndpoint,
2164
+ });
2288
2165
  }
2289
- // Example usage:
2290
- // console.log(isValidDate('2024-08-31')); // true
2291
- // console.log(isValidDate('2024-02-30')); // false
2292
2166
  /**
2293
- * Validates if a password meets certain strength criteria.
2294
- * @param {string} password The password to validate.
2295
- * @returns {boolean} True if the password is strong, otherwise false.
2167
+ * Obtiene las URLs de los endpoints de autenticación configurados.
2168
+ *
2169
+ * @returns {Object} Objeto con las URLs de los endpoints configurados.
2170
+ * @returns {string} returns.LOGIN - URL del endpoint de login.
2171
+ * @returns {string} returns.REFRESH - URL del endpoint de refresh token.
2172
+ * @returns {string} returns.LOGOUT - URL del endpoint de logout.
2296
2173
  */
2297
- function isStrongPassword(password) {
2298
- const minLength = 8;
2299
- const hasUpperCase = /[A-Z]/.test(password);
2300
- const hasLowerCase = /[a-z]/.test(password);
2301
- const hasNumbers = /[0-9]/.test(password);
2302
- const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
2303
- return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChars;
2174
+ function getEndpointsConfig() {
2175
+ return endpointsConfig;
2304
2176
  }
2305
- // Example usage:
2306
- // console.log(isStrongPassword('Strong1@password')); // true
2307
- // console.log(isStrongPassword('weakpass')); // false
2177
+
2308
2178
  /**
2309
- * Validates a credit card number using the Luhn algorithm.
2310
- * @param {string} cardNumber The credit card number to validate.
2311
- * @returns {boolean} True if the credit card number is valid, otherwise false.
2179
+ * AxiosService class encapsulates the Axios configuration and logic.
2180
+ * It manages request and response interceptors and provides methods for making HTTP requests.
2312
2181
  */
2313
- function isValidCreditCard(cardNumber) {
2314
- const sanitized = cardNumber.replace(/\D/g, '');
2315
- let sum = 0;
2316
- let shouldDouble = false;
2317
- for (let i = sanitized.length - 1; i >= 0; i--) {
2318
- let digit = parseInt(sanitized.charAt(i), 10);
2319
- if (shouldDouble) {
2320
- digit *= 2;
2321
- if (digit > 9)
2322
- digit -= 9;
2323
- }
2324
- sum += digit;
2325
- shouldDouble = !shouldDouble;
2182
+ class AxiosService {
2183
+ instance;
2184
+ cancelTokenSource;
2185
+ activeRequests = 0;
2186
+ refreshTokenInProgress = false;
2187
+ refreshTokenUrl;
2188
+ /**
2189
+ * Initializes the AxiosService instance by creating an Axios instance with default configuration
2190
+ * and setting up request and response interceptors.
2191
+ */
2192
+ constructor(url, headers = {}) {
2193
+ this.cancelTokenSource = axios.CancelToken.source();
2194
+ this.instance = axios.create({
2195
+ baseURL: url ?? '',
2196
+ timeout: 300000,
2197
+ headers: {
2198
+ 'Accept': 'application/json',
2199
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
2200
+ 'Pragma': 'no-cache',
2201
+ 'Expires': '0',
2202
+ ...headers,
2203
+ },
2204
+ withCredentials: false,
2205
+ });
2206
+ const endpointsConfig = getEndpointsConfig();
2207
+ this.refreshTokenUrl = endpointsConfig.REFRESH;
2208
+ this.initializeInterceptors();
2209
+ }
2210
+ /**
2211
+ * Initializes request and response interceptors for the Axios instance.
2212
+ */
2213
+ initializeInterceptors() {
2214
+ const { ACCESS_TOKEN, REFRESH_TOKEN } = getTokenConfig();
2215
+ this.instance.interceptors.request.use((config) => {
2216
+ const token = typeof window !== 'undefined' ? localStorage.getItem(ACCESS_TOKEN) : null;
2217
+ if (token && config.headers) {
2218
+ config.headers.Authorization = `Bearer ${token}`;
2219
+ }
2220
+ config.cancelToken = this.cancelTokenSource.token;
2221
+ this.activeRequests++;
2222
+ return config;
2223
+ }, (error) => {
2224
+ handleError(error, false);
2225
+ this.activeRequests++;
2226
+ return Promise.reject(error);
2227
+ });
2228
+ this.instance.interceptors.response.use((response) => {
2229
+ this.activeRequests--;
2230
+ return response;
2231
+ }, async (error) => {
2232
+ this.activeRequests--;
2233
+ // Check for unauthorized access (401)
2234
+ if (axios.isAxiosError(error) && error.response?.status === 401 && !this.refreshTokenInProgress) {
2235
+ const refreshToken = localStorage.getItem(REFRESH_TOKEN);
2236
+ if (refreshToken) {
2237
+ this.refreshTokenInProgress = true;
2238
+ try {
2239
+ // Attempt to refresh the token
2240
+ const refreshResponse = await axios.post(this.refreshTokenUrl, { refreshToken });
2241
+ const { token: newAccessToken } = refreshResponse.data;
2242
+ if (newAccessToken) {
2243
+ // Save the new access token
2244
+ localStorage.setItem(ACCESS_TOKEN, newAccessToken);
2245
+ // Retry the original request with the new token
2246
+ if (error.config && error.config.headers) {
2247
+ error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
2248
+ }
2249
+ this.refreshTokenInProgress = false;
2250
+ if (error.config) {
2251
+ return this.instance(error.config);
2252
+ }
2253
+ }
2254
+ }
2255
+ catch (refreshError) {
2256
+ handleError(refreshError, false);
2257
+ this.refreshTokenInProgress = false;
2258
+ localStorage.removeItem(ACCESS_TOKEN);
2259
+ localStorage.removeItem(REFRESH_TOKEN);
2260
+ }
2261
+ }
2262
+ }
2263
+ handleError(error, false);
2264
+ return Promise.reject(error);
2265
+ });
2266
+ }
2267
+ /**
2268
+ * Returns the number of active requests.
2269
+ */
2270
+ getActiveRequests() {
2271
+ return this.activeRequests;
2272
+ }
2273
+ /**
2274
+ * Returns the Axios instance with the configured settings and interceptors.
2275
+ * @returns {AxiosInstance} The configured Axios instance.
2276
+ */
2277
+ getAxiosInstance() {
2278
+ return this.instance;
2279
+ }
2280
+ /**
2281
+ * Cancels all ongoing requests.
2282
+ */
2283
+ cancelAllRequests() {
2284
+ this.cancelTokenSource.cancel('Operation canceled by the user.');
2285
+ this.cancelTokenSource = axios.CancelToken.source();
2286
+ }
2287
+ /**
2288
+ * Sets a new header for the Axios instance.
2289
+ * @param {string} key - The header key.
2290
+ * @param {string} value - The header value.
2291
+ */
2292
+ setHeader(key, value) {
2293
+ this.instance.defaults.headers.common[key] = value;
2294
+ }
2295
+ /**
2296
+ * Removes a header from the Axios instance.
2297
+ * @param {string} key - The header key to remove.
2298
+ */
2299
+ removeHeader(key) {
2300
+ delete this.instance.defaults.headers.common[key];
2326
2301
  }
2327
- return sum % 10 === 0;
2328
- }
2329
- // Example usage:
2330
- // console.log(isValidCreditCard('4111111111111111')); // true
2331
- // console.log(isValidCreditCard('1234567812345670')); // false
2332
- /**
2333
- * Validates if a string is a valid hex color code.
2334
- * @param {string} color The color code to validate.
2335
- * @returns {boolean} True if the color code is valid, otherwise false.
2336
- */
2337
- function isValidHexColor(color) {
2338
- const hexPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/;
2339
- return hexPattern.test(color);
2340
2302
  }
2341
- // Example usage:
2342
- // console.log(isValidHexColor('#FFFFFF')); // true
2343
- // console.log(isValidHexColor('#FFF')); // true
2344
- // console.log(isValidHexColor('#12345G')); // false
2303
+
2304
+ let axiosInstance;
2345
2305
  /**
2346
- * Validates if a string is a valid time in HH:MM format.
2347
- * @param {string} time The time string to validate.
2348
- * @returns {boolean} True if the time is valid, otherwise false.
2306
+ * Configures the global Axios instance with a base URL.
2307
+ * @param {string} baseURL - The base URL for the Axios instance.
2349
2308
  */
2350
- function isValidTime(time) {
2351
- const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
2352
- return timePattern.test(time);
2353
- }
2354
- // Example usage:
2355
- // console.log(isValidTime('14:30')); // true
2356
- // console.log(isValidTime('25:00')); // false
2309
+ const configureAxios = (baseURL) => {
2310
+ axiosInstance = new AxiosService(baseURL);
2311
+ };
2357
2312
  /**
2358
- * Validates if a string is a valid IPv4 address.
2359
- * @param {string} ip The IP address to validate.
2360
- * @returns {boolean} True if the IP address is valid, otherwise false.
2313
+ * Retrieves the configured Axios instance.
2314
+ * @returns {AxiosService} The configured Axios instance.
2315
+ * @throws Will throw an error if the Axios instance is not configured.
2361
2316
  */
2362
- function isValidIP(ip) {
2363
- const ipPattern = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$/;
2364
- return ipPattern.test(ip);
2365
- }
2366
- // Example usage:
2367
- // console.log(isValidIP('192.168.1.1')); // true
2368
- // console.log(isValidIP('999.999.999.999')); // false
2317
+ const getAxiosInstance = () => {
2318
+ if (!axiosInstance) {
2319
+ throw new Error('Axios instance not configured. Call configureAxios first.');
2320
+ }
2321
+ return axiosInstance.getAxiosInstance();
2322
+ };
2369
2323
  /**
2370
- * Validates if a string is a valid U.S. Social Security Number (SSN).
2371
- * @param {string} ssn The SSN to validate.
2372
- * @returns {boolean} True if the SSN is valid, otherwise false.
2324
+ * Creates a new AxiosService instance with custom headers.
2325
+ * @param {string} baseURL - The base URL for the Axios instance.
2326
+ * @param {Record<string, string>} headers - Custom headers to set.
2327
+ * @returns {AxiosService} The new AxiosService instance.
2373
2328
  */
2374
- function isValidSSN(ssn) {
2375
- const ssnPattern = /^\d{3}-\d{2}-\d{4}$/;
2376
- return ssnPattern.test(ssn);
2377
- }
2378
- // Example usage:
2379
- // console.log(isValidSSN('123-45-6789')); // true
2380
- // console.log(isValidSSN('123-45-678')); // false
2329
+ const createCustomAxiosInstance = (baseURL, headers) => {
2330
+ return new AxiosService(baseURL, headers).getAxiosInstance();
2331
+ };
2332
+
2381
2333
  /**
2382
- * Validates if a string is a valid U.S. ZIP code.
2383
- * @param {string} zip The ZIP code to validate.
2384
- * @returns {boolean} True if the ZIP code is valid, otherwise false.
2334
+ * Creates a fetchComposable for Axios requests with an optional custom instance.
2335
+ * @param fetchComposable - The composable to use for the request (can be any fetch function).
2336
+ * @param axiosCustomInstance - An optional custom Axios instance to use for the request.
2337
+ * @returns A function that handles the request using the provided composable.
2385
2338
  */
2386
- function isValidZIP(zip) {
2387
- const zipPattern = /^\d{5}(-\d{4})?$/;
2388
- return zipPattern.test(zip);
2339
+ function createFetch(fetchComposable, axiosCustomInstance) {
2340
+ const instance = axiosCustomInstance || getAxiosInstance();
2341
+ return (axiosRequestConfig, options) => {
2342
+ return fetchComposable(instance, axiosRequestConfig, options);
2343
+ };
2389
2344
  }
2390
- // Example usage:
2391
- // console.log(isValidZIP('12345')); // true
2392
- // console.log(isValidZIP('12345-6789')); // true
2393
- // console.log(isValidZIP('1234')); // false
2345
+
2394
2346
  /**
2395
- * Validates if a string is a valid credit card expiry date in MM/YY format.
2396
- * @param {string} expiryDate The expiry date to validate.
2397
- * @returns {boolean} True if the expiry date is valid, otherwise false.
2347
+ * Custom composable for integrating Axios requests with Vue Query.
2348
+ *
2349
+ * @template T - The type of data expected from the query.
2350
+ * @param {AxiosInstance} axios - Axios instance used for making HTTP requests.
2351
+ * @param {AxiosRequestConfig} axiosRequest - Initial Axios request configuration.
2352
+ * @param {ExtendedQueryOptions<T>} [queryOptions] - Optional Vue Query options with server execution flag.
2353
+ * @returns {object} Composable functions and query state for managing API requests.
2398
2354
  */
2399
- function isValidExpiryDate(expiryDate) {
2400
- const expiryPattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
2401
- if (!expiryPattern.test(expiryDate))
2402
- return false;
2403
- const [month, year] = expiryDate.split('/').map(Number);
2404
- const currentYear = new Date().getFullYear() % 100;
2405
- const currentMonth = new Date().getMonth() + 1;
2406
- return (year > currentYear) || (year === currentYear && month >= currentMonth);
2355
+ function useVueQuery(axios, axiosRequest, queryOptions) {
2356
+ const key = ref([
2357
+ queryOptions?.queryKey || `${axiosRequest.url}-${JSON.stringify(axiosRequest)}`
2358
+ ]);
2359
+ const axiosRequestRef = ref(axiosRequest);
2360
+ const queryOptionsRef = ref(queryOptions);
2361
+ const isInitialized = ref(false);
2362
+ const errorCallbacks = ref([]);
2363
+ const resultCallbacks = ref([]);
2364
+ /**
2365
+ * Vue Query instance for managing API requests.
2366
+ */
2367
+ const query = useQuery({
2368
+ queryKey: key.value,
2369
+ queryFn: async () => {
2370
+ const response = await axios(axiosRequestRef.value);
2371
+ return response.data;
2372
+ },
2373
+ ...(queryOptionsRef.value?.options ?? {})
2374
+ });
2375
+ /**
2376
+ * Executes the query manually with optional new key and request parameters.
2377
+ *
2378
+ * @param {Array<any>} [newKey] - New query key to use.
2379
+ * @param {object} [newRequestParams] - New request parameters to use.
2380
+ * @param {ExtendedQueryOptions<T>} [newQueryOptions] - New Vue Query options to use.
2381
+ */
2382
+ function execute(newKey, newRequestParams, newQueryOptions) {
2383
+ if (newKey) {
2384
+ key.value = newKey;
2385
+ }
2386
+ if (newRequestParams) {
2387
+ axiosRequestRef.value.data = newRequestParams;
2388
+ }
2389
+ if (newQueryOptions) {
2390
+ queryOptionsRef.value = newQueryOptions;
2391
+ }
2392
+ query.refetch();
2393
+ }
2394
+ /**
2395
+ * Registers a callback to be executed on successful request result.
2396
+ * If data is already available, the callback will be executed immediately.
2397
+ *
2398
+ * @param {(data: T) => void} cb - Callback function to handle the result.
2399
+ */
2400
+ function onResult(cb) {
2401
+ resultCallbacks.value.push(cb);
2402
+ if (query.data.value !== undefined && query.data.value !== null) {
2403
+ cb(query.data.value);
2404
+ }
2405
+ }
2406
+ /**
2407
+ * Registers a callback to be executed when the request results in an error.
2408
+ * If an error is already present, the callback will be executed immediately.
2409
+ *
2410
+ * @param {(e: unknown) => void} callback - Callback function to handle errors.
2411
+ */
2412
+ function onError(callback) {
2413
+ errorCallbacks.value.push(callback);
2414
+ if (query.error.value !== undefined) {
2415
+ callback(query.error.value);
2416
+ }
2417
+ }
2418
+ /**
2419
+ * Watches for changes in the query result and triggers result callbacks.
2420
+ */
2421
+ watch(query.data, (newData) => {
2422
+ if (newData !== undefined && newData !== null && isInitialized.value) {
2423
+ resultCallbacks.value.forEach((cb) => cb(newData));
2424
+ }
2425
+ });
2426
+ /**
2427
+ * Watches for changes in query errors and triggers error callbacks.
2428
+ */
2429
+ watch(query.error, (newError) => {
2430
+ if (newError !== undefined && isInitialized.value) {
2431
+ errorCallbacks.value.forEach((cb) => cb(newError));
2432
+ }
2433
+ });
2434
+ /**
2435
+ * Handles server-side prefetching for SSR.
2436
+ * Ensures data is fetched before rendering on the server.
2437
+ */
2438
+ onServerPrefetch(async () => {
2439
+ if (queryOptionsRef.value?.server !== false) {
2440
+ await query.suspense();
2441
+ if (query.data.value != null) {
2442
+ resultCallbacks.value.forEach((cb) => cb(query.data.value));
2443
+ }
2444
+ }
2445
+ });
2446
+ /**
2447
+ * Ensures callbacks are executed after the component is mounted on the client-side.
2448
+ */
2449
+ onMounted(() => {
2450
+ isInitialized.value = true;
2451
+ if (query.data.value != null) {
2452
+ resultCallbacks.value.forEach((cb) => cb(query.data.value));
2453
+ }
2454
+ if (query.error.value !== undefined) {
2455
+ errorCallbacks.value.forEach((cb) => cb(query.error.value));
2456
+ }
2457
+ });
2458
+ return {
2459
+ execute,
2460
+ onResult,
2461
+ onError,
2462
+ ...query
2463
+ };
2407
2464
  }
2408
- // Example usage:
2409
- // console.log(isValidExpiryDate('08/24')); // true
2410
- // console.log(isValidExpiryDate('12/22')); // false
2465
+
2466
+ let notified = false;
2411
2467
  /**
2412
- * Validates if a string is a valid 8-character hexadecimal color code (including alpha).
2413
- * @param {string} color The color code to validate.
2414
- * @returns {boolean} True if the color code is valid, otherwise false.
2468
+ * Composable to manage breakpoints using Tailwind CSS and shared window resize listener.
2469
+ *
2470
+ * @returns {Object} An object containing the breakpoints and their states.
2415
2471
  */
2416
- function isValidHexColorAlpha(color) {
2417
- const hexPattern = /^#([0-9A-Fa-f]{8})$/;
2418
- return hexPattern.test(color);
2472
+ function useBreakpoint() {
2473
+ const breakpoints = useBreakpoints(breakpointsTailwind);
2474
+ const current = breakpoints.current();
2475
+ const active = breakpoints.active();
2476
+ // Define breakpoints
2477
+ const sm_S = breakpoints.smaller('sm');
2478
+ const sm_SE = breakpoints.smallerOrEqual('sm');
2479
+ const sm_GE = breakpoints.greaterOrEqual('sm');
2480
+ const sm_md = breakpoints.between('sm', 'md');
2481
+ const sm_xl = breakpoints.between('sm', 'xl');
2482
+ const sm_2xl = breakpoints.between('sm', '2xl');
2483
+ const md_S = breakpoints.smaller('md');
2484
+ const md_SE = breakpoints.smallerOrEqual('md');
2485
+ const md_GE = breakpoints.greaterOrEqual('md');
2486
+ const md_lg = breakpoints.between('md', 'lg');
2487
+ const md_xl = breakpoints.between('md', 'xl');
2488
+ const md_2xl = breakpoints.between('md', '2xl');
2489
+ const lg_S = breakpoints.smaller('lg');
2490
+ const lg_SE = breakpoints.smallerOrEqual('lg');
2491
+ const lg_GE = breakpoints.greaterOrEqual('lg');
2492
+ const lg_xl = breakpoints.between('lg', 'xl');
2493
+ const lg_2xl = breakpoints.between('lg', '2xl');
2494
+ const xl_S = breakpoints.smaller('xl');
2495
+ const xl_SE = breakpoints.smallerOrEqual('xl');
2496
+ const xl_GE = breakpoints.greaterOrEqual('xl');
2497
+ const xl_2xl = breakpoints.between('xl', '2xl');
2498
+ const _2xl_S = breakpoints.smaller('2xl');
2499
+ const _2xl_SE = breakpoints.smallerOrEqual('2xl');
2500
+ const _2xl_GE = breakpoints.greaterOrEqual('2xl');
2501
+ const reactiveBreakpoint = ref('sm');
2502
+ const isGreaterThanBreakpoint = breakpoints.greaterOrEqual(() => reactiveBreakpoint.value);
2503
+ // Define device ranges
2504
+ const mobile = breakpoints.smaller('md'); // <768px
2505
+ const tablet = breakpoints.between('md', 'lg'); // 768px - 1024px
2506
+ const laptop = breakpoints.between('lg', 'xl'); // 1024px - 1280px
2507
+ const desktop = breakpoints.greaterOrEqual('xl'); // >=1280px
2508
+ // Use VueUse for window size
2509
+ const { width: windowWidth, height: windowHeight } = useWindowSize();
2510
+ /**
2511
+ * Logs a styled "chip" message to the console.
2512
+ *
2513
+ * @param {string} label - The label to display inside the chip.
2514
+ * @param {string} backgroundColor - The background color of the chip.
2515
+ * @param {string} textColor - The text color of the chip.
2516
+ */
2517
+ const logChip = (label, backgroundColor, textColor = 'white') => {
2518
+ console.log(`%c ${label} `, `background-color: ${backgroundColor}; color: ${textColor}; border-radius: 16px; padding: 4px 8px;`);
2519
+ };
2520
+ // Singleton logic: Only notify once
2521
+ const notifyOnce = () => {
2522
+ if (notified)
2523
+ return;
2524
+ watch([mobile, tablet, laptop, desktop], ([isMobile, isTablet, isLaptop, isDesktop]) => {
2525
+ if (isMobile) {
2526
+ logChip('Mobile', '#ff6b6b');
2527
+ }
2528
+ else if (isTablet) {
2529
+ logChip('Tablet', '#4ecdc4');
2530
+ }
2531
+ else if (isLaptop) {
2532
+ logChip('Laptop', '#1a535c');
2533
+ }
2534
+ else if (isDesktop) {
2535
+ logChip('Desktop', '#f7fff7', 'black');
2536
+ }
2537
+ notified = true; // Mark as notified
2538
+ }, { immediate: true });
2539
+ };
2540
+ notifyOnce(); // Ensure the notification happens only once globally
2541
+ return {
2542
+ current,
2543
+ active,
2544
+ sm_S,
2545
+ sm_SE,
2546
+ sm_GE,
2547
+ sm_md,
2548
+ sm_xl,
2549
+ sm_2xl,
2550
+ md_S,
2551
+ md_SE,
2552
+ md_GE,
2553
+ md_lg,
2554
+ md_xl,
2555
+ md_2xl,
2556
+ lg_S,
2557
+ lg_SE,
2558
+ lg_GE,
2559
+ lg_xl,
2560
+ lg_2xl,
2561
+ xl_S,
2562
+ xl_SE,
2563
+ xl_GE,
2564
+ xl_2xl,
2565
+ _2xl_S,
2566
+ _2xl_SE,
2567
+ _2xl_GE,
2568
+ isGreaterThanBreakpoint,
2569
+ reactiveBreakpoint,
2570
+ breakpointsTailwind,
2571
+ windowWidth,
2572
+ windowHeight,
2573
+ mobile,
2574
+ tablet,
2575
+ laptop,
2576
+ desktop,
2577
+ breakpoints,
2578
+ };
2419
2579
  }
2420
- // Example usage:
2421
- // console.log(isValidHexColorAlpha('#RRGGBBAA')); // true
2422
- // console.log(isValidHexColorAlpha('#FFFFFF')); // false
2580
+
2423
2581
  /**
2424
- * Validates if a username meets specific criteria.
2425
- * @param {string} username The username to validate.
2426
- * @returns {boolean} True if the username is valid, otherwise false.
2582
+ * A composable function that filters objects based on a specific field and criteria.
2583
+ *
2584
+ * @param {Object[]} items - The list of objects to filter.
2585
+ * @param {Object} filterConfig - Configuration for the filter, specifying the field, the data type to filter, and the criteria.
2586
+ * @param {string} filterConfig.field - The field in the objects to filter.
2587
+ * @param {string} filterConfig.type - The data type of the field ('date', 'string', 'number', 'boolean').
2588
+ * @param {any} filterConfig.criteria - The criteria for filtering. Can be a date range, a string to search, a number range, or a boolean value.
2589
+ *
2590
+ * @returns {Object[]} - The filtered list of objects.
2427
2591
  */
2428
- function isValidUsername(username) {
2429
- const usernamePattern = /^[a-zA-Z0-9_]{3,16}$/; // 3 to 16 characters, letters, numbers, and underscores only
2430
- return usernamePattern.test(username);
2592
+ function useFilter(items, filterConfig) {
2593
+ // Ensure the filterConfig is correctly structured
2594
+ const { field, type, criteria } = filterConfig;
2595
+ // Define the filter functions
2596
+ const filterByDate = (objects, field, { startDate, endDate }) => objects.filter(item => {
2597
+ const date = new Date(item[field]);
2598
+ return date >= new Date(startDate) && date <= new Date(endDate);
2599
+ });
2600
+ const filterByString = (objects, field, searchTerm) => {
2601
+ const normalize = (str) => str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
2602
+ const words = normalize(searchTerm).split(/\s+/).filter(Boolean);
2603
+ return objects.filter(item => {
2604
+ const normalizedFieldValue = normalize(item[field]);
2605
+ return words.every(word => normalizedFieldValue.includes(word));
2606
+ });
2607
+ };
2608
+ const filterByNumber = (objects, field, { min, max }) => objects.filter(item => {
2609
+ const value = item[field];
2610
+ return value >= min && value <= max;
2611
+ });
2612
+ const filterByBoolean = (objects, field, expectedValue) => objects.filter(item => item[field] === expectedValue);
2613
+ // Apply the appropriate filter and return the filtered objects
2614
+ const filteredObjects = computed(() => {
2615
+ if (!criteria ||
2616
+ (type === 'date' && (!criteria.startDate || !criteria.endDate)) ||
2617
+ (type === 'number' && (criteria.min == null || criteria.max == null)) ||
2618
+ (type === 'boolean' && typeof criteria !== 'boolean')) {
2619
+ return items; // Return all objects if criteria is not valid
2620
+ }
2621
+ // Apply the filter based on the type
2622
+ switch (type) {
2623
+ case 'date':
2624
+ return filterByDate(items, field, criteria);
2625
+ case 'string':
2626
+ return filterByString(items, field, criteria);
2627
+ case 'number':
2628
+ return filterByNumber(items, field, criteria);
2629
+ case 'boolean':
2630
+ return filterByBoolean(items, field, criteria);
2631
+ default:
2632
+ return items;
2633
+ }
2634
+ });
2635
+ return filteredObjects.value;
2431
2636
  }
2432
- // Example usage:
2433
- // console.log(isValidUsername('user_name123')); // true
2434
- // console.log(isValidUsername('us')); // false
2637
+
2435
2638
  /**
2436
- * Validates if a string represents a valid age between 0 and 120.
2437
- * @param {string} age The age to validate.
2438
- * @returns {boolean} True if the age is valid, otherwise false.
2639
+ * Composable to handle pagination logic.
2640
+ *
2641
+ * @param page - Reactive ref for the current page number.
2642
+ * @param total - Reactive ref for total number of items.
2643
+ * @param pageSize - Reactive ref for number of items per page.
2644
+ * @returns An object with pagination controls.
2439
2645
  */
2440
- function isValidAge(age) {
2441
- const ageNumber = parseInt(age, 10);
2442
- return !isNaN(ageNumber) && ageNumber >= 0 && ageNumber <= 120;
2646
+ function usePagination(page, total, pageSize) {
2647
+ // Calculate the total number of pages
2648
+ const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
2649
+ // Determine if the next page can be fetched
2650
+ const canFetchNextPage = () => page.value < totalPages.value;
2651
+ // Determine if the previous page can be fetched
2652
+ const canFetchPreviousPage = () => page.value > 1;
2653
+ return {
2654
+ totalPages,
2655
+ canFetchNextPage,
2656
+ canFetchPreviousPage
2657
+ };
2443
2658
  }
2444
- // Example usage:
2445
- // console.log(isValidAge('25')); // true
2446
- // console.log(isValidAge('121')); // false
2659
+
2447
2660
  /**
2448
- * Validates if a string is a valid hexadecimal number.
2449
- * @param {string} hex The hexadecimal number to validate.
2450
- * @returns {boolean} True if the number is valid, otherwise false.
2661
+ * Custom composable for sorting products based on a selected criterion.
2662
+ * @param {Array} items - The array of products to sort.
2663
+ * @param {Array} criteriaList - The list of available sorting criteria.
2664
+ * @param {Number} selectedCriteria - The currently selected sorting criterion.
2665
+ * @returns {ComputedRef<Array>} - The sorted array of products.
2451
2666
  */
2452
- function isValidHexNumber(hex) {
2453
- const hexPattern = /^[0-9A-Fa-f]+$/;
2454
- return hexPattern.test(hex);
2667
+ function useSorter(items, criteriaList, selectedCriteria) {
2668
+ return computed(() => {
2669
+ const criteria = criteriaList.find(item => item.value === Number(selectedCriteria));
2670
+ if (!criteria)
2671
+ return items;
2672
+ return [...items].sort((a, b) => {
2673
+ const aValue = a[criteria.field];
2674
+ const bValue = b[criteria.field];
2675
+ if (criteria.type === 'number') {
2676
+ const numA = typeof aValue === 'number' ? aValue : parseFloat(aValue) || 0;
2677
+ const numB = typeof bValue === 'number' ? bValue : parseFloat(bValue) || 0;
2678
+ return criteria.order === 'asc' ? numA - numB : numB - numA;
2679
+ }
2680
+ else if (criteria.type === 'date') {
2681
+ const dateA = new Date(aValue).getTime();
2682
+ const dateB = new Date(bValue).getTime();
2683
+ return criteria.order === 'asc' ? dateA - dateB : dateB - dateA;
2684
+ }
2685
+ else if (criteria.type === 'boolean') {
2686
+ const boolA = !!aValue;
2687
+ const boolB = !!bValue;
2688
+ return criteria.order === 'asc' ? (boolA === boolB ? 0 : boolA ? 1 : -1) : (boolA === boolB ? 0 : boolA ? -1 : 1);
2689
+ }
2690
+ else {
2691
+ // Fallback for string sorting
2692
+ const strA = (aValue || '').toString().toLowerCase();
2693
+ const strB = (bValue || '').toString().toLowerCase();
2694
+ return criteria.order === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
2695
+ }
2696
+ });
2697
+ }).value;
2455
2698
  }
2456
- // Example usage:
2457
- // console.log(isValidHexNumber('1A3F')); // true
2458
- // console.log(isValidHexNumber('GHIJ')); // false
2459
2699
 
2460
- export { APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY, AxiosService, CacheTypeEnum, ContentTypeEnum, ExceptionEnum, FileMetaTypes, ImageTypes, KeyCodeEnum, LOCALE_KEY, LOCK_INFO_KEY, PROJ_CFG_KEY, ROLES_KEY, RestStd, TOKEN_KEY, USER_INFO_KEY, addCustomKeyboardShortcut, addDays, addDoubleClickListener, addKeyListener, ageAtDate, axiosFetch, blobToFormData, bufferToBlob, calculateAge, clickOutside, compareObject, configureAxios, copyToClipboard, countWords, createFetch, createKeyMap, customShortcut, daysBetween, daysToNextBirthday, debounce, debounceAsync, debounceAsyncValidator, debounceAsyncWithImmediate, debounceLeading, debounceLeadingTrailing, debounceTrailing, deepClone, deepEqual, deepMerge, detectKeyHold, disableCopy, disableF12Key, disableMouseButtons, disableRightClick, disableSpecificKeys, downloadBlob, enableMouseButtons, enableRightClick, enableSpecificKeys, exportToCSV, exportToExcel, exportToJSON, exportToText, exportToXML, filterObjectByKeys, flattenObject, formDataToObject, formatDate, generateRandomString, getAxiosInstance, getEndOfMonth, getObjectDifferences, getObjectKeys, getQueryParam, getStartOfMonth, hasNestedProperties, isEmptyObject, isLeapYear, isStrongPassword, isValidAge, isValidCreditCard, isValidDate, isValidEmail, isValidExpiryDate, isValidHexColor, isValidHexColorAlpha, isValidHexNumber, isValidIP, isValidPhoneNumber, isValidSSN, isValidTime, isValidURL, isValidUsername, isValidZIP, lowerFirst, objectToFormData, objectToFormDataEnhanced, objectToQueryString, openWindow, parseDate, proxyToPlainObject, readFileAsDataURL, readFileAsText, registerKeyboardShortcuts, removeAccent, removeClickOutside, removeCustomKeyboardShortcut, removeCustomShortcuts, removeDoubleClickListener, removeEmptyProperties, removeKeyListeners, replaceAll, reverseString, safeGet, screenEnum, screenMap, scrollToTop, simulateKeyPress, sizeEnum, stopDetectingKeyHold, stringToBlob, subtractDays, throttle, toCamelCase, toKebabCase, toggleTabNavigation, truncateString, unregisterKeyboardShortcuts, upperFirst, useBreakpoint, useFilter, usePagination, useSorter, useVueQuery, validateAlphanumeric, validateLetters, validateNumbers };
2700
+ export { APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY, AxiosService, CacheTypeEnum, ContentTypeEnum, ERROR_MESSAGES, ERROR_STYLES, ExceptionEnum, FileMetaTypes, ImageTypes, KeyCodeEnum, LOCALE_KEY, LOCK_INFO_KEY, PROJ_CFG_KEY, ROLES_KEY, RestStd, TOKEN_KEY, USER_INFO_KEY, addCustomKeyboardShortcut, addDays, addDoubleClickListener, addKeyListener, ageAtDate, axiosFetch, blobToFormData, bufferToBlob, calculateAge, clickOutside, compareObject, configureAxios, configureEndpoints, configureTokenKeys, copyToClipboard, countWords, createCustomAxiosInstance, createFetch, createKeyMap, customShortcut, daysBetween, daysToNextBirthday, debounce, debounceAsync, debounceAsyncValidator, debounceAsyncWithImmediate, debounceLeading, debounceLeadingTrailing, debounceTrailing, deepClone, deepEqual, deepMerge, detectKeyHold, disableCopy, disableF12Key, disableMouseButtons, disableRightClick, disableSpecificKeys, downloadBlob, enableMouseButtons, enableRightClick, enableSpecificKeys, exportToCSV, exportToExcel, exportToJSON, exportToText, exportToXML, filterObjectByKeys, flattenObject, formDataToObject, formatDate, generateRandomString, getAxiosInstance, getEndOfMonth, getEndpointsConfig, getObjectDifferences, getObjectKeys, getQueryParam, getStartOfMonth, getTokenConfig, hasNestedProperties, isEmptyObject, isLeapYear, isStrongPassword, isValidAge, isValidCreditCard, isValidDate, isValidEmail, isValidExpiryDate, isValidHexColor, isValidHexColorAlpha, isValidHexNumber, isValidIP, isValidPhoneNumber, isValidSSN, isValidTime, isValidURL, isValidUsername, isValidZIP, lowerFirst, objectToFormData, objectToFormDataEnhanced, objectToQueryString, openWindow, parseDate, proxyToPlainObject, readFileAsDataURL, readFileAsText, registerKeyboardShortcuts, removeAccent, removeClickOutside, removeCustomKeyboardShortcut, removeCustomShortcuts, removeDoubleClickListener, removeEmptyProperties, removeKeyListeners, replaceAll, reverseString, safeGet, screenEnum, screenMap, scrollToTop, simulateKeyPress, sizeEnum, stopDetectingKeyHold, stringToBlob, subtractDays, throttle, toCamelCase, toKebabCase, toggleTabNavigation, truncateString, unregisterKeyboardShortcuts, upperFirst, useBreakpoint, useFilter, usePagination, useSorter, useVueQuery, validateAlphanumeric, validateLetters, validateNumbers };