@sohanemon/utils 6.2.7 → 6.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/index.d.mts +665 -0
  2. package/dist/index.mjs +664 -0
  3. package/package.json +42 -18
  4. package/dist/components/html-injector.d.ts +0 -50
  5. package/dist/components/html-injector.js +0 -108
  6. package/dist/components/index.d.ts +0 -5
  7. package/dist/components/index.js +0 -7
  8. package/dist/components/media-wrapper.d.ts +0 -10
  9. package/dist/components/media-wrapper.js +0 -14
  10. package/dist/components/responsive-indicator.d.ts +0 -2
  11. package/dist/components/responsive-indicator.js +0 -68
  12. package/dist/components/scrollable-marker.d.ts +0 -1
  13. package/dist/components/scrollable-marker.js +0 -56
  14. package/dist/functions/cookie.d.ts +0 -6
  15. package/dist/functions/cookie.js +0 -22
  16. package/dist/functions/deepmerge.d.ts +0 -59
  17. package/dist/functions/deepmerge.js +0 -116
  18. package/dist/functions/hydrate.d.ts +0 -15
  19. package/dist/functions/hydrate.js +0 -31
  20. package/dist/functions/index.d.ts +0 -8
  21. package/dist/functions/index.js +0 -8
  22. package/dist/functions/object.d.ts +0 -93
  23. package/dist/functions/object.js +0 -67
  24. package/dist/functions/poll.d.ts +0 -38
  25. package/dist/functions/poll.js +0 -69
  26. package/dist/functions/schedule.d.ts +0 -12
  27. package/dist/functions/schedule.js +0 -29
  28. package/dist/functions/shield.d.ts +0 -18
  29. package/dist/functions/shield.js +0 -15
  30. package/dist/functions/utils.d.ts +0 -243
  31. package/dist/functions/utils.js +0 -439
  32. package/dist/hooks/action.d.ts +0 -20
  33. package/dist/hooks/action.js +0 -84
  34. package/dist/hooks/async.d.ts +0 -30
  35. package/dist/hooks/async.js +0 -82
  36. package/dist/hooks/index.d.ts +0 -192
  37. package/dist/hooks/index.js +0 -533
  38. package/dist/hooks/schedule.d.ts +0 -36
  39. package/dist/hooks/schedule.js +0 -68
  40. package/dist/index.d.ts +0 -2
  41. package/dist/index.js +0 -2
  42. package/dist/types/gates.d.ts +0 -133
  43. package/dist/types/gates.js +0 -1
  44. package/dist/types/guards.d.ts +0 -6
  45. package/dist/types/guards.js +0 -29
  46. package/dist/types/index.d.ts +0 -3
  47. package/dist/types/index.js +0 -3
  48. package/dist/types/utilities.d.ts +0 -62
  49. package/dist/types/utilities.js +0 -1
package/dist/index.mjs ADDED
@@ -0,0 +1,664 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ //#region src/functions/cookie.ts
5
+ const setClientSideCookie = (name, value, days, path = "/") => {
6
+ let expires = "";
7
+ if (days) {
8
+ const date = /* @__PURE__ */ new Date();
9
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1e3);
10
+ expires = `; expires=${date.toUTCString()}`;
11
+ }
12
+ document.cookie = `${name}=${value || ""}${expires}; path=${path}`;
13
+ };
14
+ const deleteClientSideCookie = (name, path = "/") => {
15
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
16
+ };
17
+ const hasClientSideCookie = (name) => {
18
+ return document.cookie.split("; ").some((row) => row.startsWith(`${name}=`));
19
+ };
20
+ const getClientSideCookie = (name) => {
21
+ return { value: document.cookie.split("; ").find((row) => row.startsWith(`${name}=`))?.split("=")[1] };
22
+ };
23
+
24
+ //#endregion
25
+ //#region src/types/guards.ts
26
+ const isFalsy = (val) => !val;
27
+ const isNullish = (val) => val == null;
28
+ const isPrimitive = (val) => {
29
+ if (val === null || val === void 0) return true;
30
+ switch (typeof val) {
31
+ case "string":
32
+ case "number":
33
+ case "bigint":
34
+ case "boolean":
35
+ case "symbol": return true;
36
+ default: return false;
37
+ }
38
+ };
39
+ function isPlainObject(value) {
40
+ if (typeof value !== "object" || value === null) return false;
41
+ if (Object.prototype.toString.call(value) !== "[object Object]") return false;
42
+ const proto = Object.getPrototypeOf(value);
43
+ return proto === null || proto === Object.prototype;
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/functions/deepmerge.ts
48
+ function deepmerge(target, ...args) {
49
+ let sources;
50
+ let options = {};
51
+ const lastArg = args[args.length - 1];
52
+ if (lastArg && typeof lastArg === "object" && !Array.isArray(lastArg) && (lastArg.arrayMerge !== void 0 || lastArg.clone !== void 0 || lastArg.customMerge !== void 0 || lastArg.maxDepth !== void 0)) {
53
+ options = {
54
+ ...options,
55
+ ...lastArg
56
+ };
57
+ sources = args.slice(0, -1);
58
+ } else sources = args;
59
+ const { arrayMerge = "replace", clone = true, customMerge, maxDepth = 100 } = options;
60
+ const visited = /* @__PURE__ */ new WeakMap();
61
+ return mergeObjects(target, sources, 0);
62
+ function mergeObjects(target$1, sources$1, depth) {
63
+ if (depth >= maxDepth) {
64
+ console.warn(`[deepmerge] Maximum depth ${maxDepth} exceeded. Returning target as-is.`);
65
+ return target$1;
66
+ }
67
+ if (!isPlainObject(target$1) && !Array.isArray(target$1)) {
68
+ for (const source of sources$1) if (source !== void 0) return source;
69
+ return target$1;
70
+ }
71
+ let result = clone ? Array.isArray(target$1) ? [...target$1] : { ...target$1 } : target$1;
72
+ for (const source of sources$1) {
73
+ if (source == null) continue;
74
+ if (visited.has(source)) continue;
75
+ visited.set(source, result);
76
+ if (Array.isArray(result) && Array.isArray(source)) result = mergeArrays(result, source, arrayMerge);
77
+ else if (isPlainObject(result) && isPlainObject(source)) {
78
+ const keys = new Set([
79
+ ...Object.keys(result),
80
+ ...Object.keys(source),
81
+ ...Object.getOwnPropertySymbols(result),
82
+ ...Object.getOwnPropertySymbols(source)
83
+ ]);
84
+ for (const key of keys) {
85
+ const targetValue = result[key];
86
+ const sourceValue = source[key];
87
+ if (customMerge && customMerge(key, targetValue, sourceValue) !== void 0) result[key] = customMerge(key, targetValue, sourceValue);
88
+ else if (isPlainObject(targetValue) && isPlainObject(sourceValue)) result[key] = mergeObjects(targetValue, [sourceValue], depth + 1);
89
+ else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) result[key] = mergeArrays(targetValue, sourceValue, arrayMerge);
90
+ else if (sourceValue !== void 0) result[key] = sourceValue;
91
+ }
92
+ } else result = source;
93
+ }
94
+ return result;
95
+ }
96
+ function mergeArrays(target$1, source, strategy) {
97
+ if (typeof strategy === "function") return strategy(target$1, source);
98
+ switch (strategy) {
99
+ case "concat": return [...target$1, ...source];
100
+ case "merge":
101
+ const maxLength = Math.max(target$1.length, source.length);
102
+ const merged = [];
103
+ for (let i = 0; i < maxLength; i++) if (i < target$1.length && i < source.length) if (isPlainObject(target$1[i]) && isPlainObject(source[i])) merged[i] = mergeObjects(target$1[i], [source[i]], 0);
104
+ else merged[i] = source[i];
105
+ else if (i < target$1.length) merged[i] = target$1[i];
106
+ else merged[i] = source[i];
107
+ return merged;
108
+ case "replace":
109
+ default: return [...source];
110
+ }
111
+ }
112
+ }
113
+
114
+ //#endregion
115
+ //#region src/functions/hydrate.ts
116
+ /**
117
+ * Converts all `null` values to `undefined` in the data structure recursively.
118
+ *
119
+ * @param data - Any input data (object, array, primitive)
120
+ * @returns Same type as input, but with all nulls replaced by undefined
121
+ *
122
+ * @example
123
+ * hydrate({ a: null, b: 'test' }) // { a: undefined, b: 'test' }
124
+ * hydrate([null, 1, { c: null }]) // [undefined, 1, { c: undefined }]
125
+ */
126
+ function hydrate(data) {
127
+ return convertNulls(data);
128
+ }
129
+ function convertNulls(value) {
130
+ if (value === null) return void 0;
131
+ if (typeof value !== "object" || value === null) return value;
132
+ if (Array.isArray(value)) return value.map(convertNulls);
133
+ if (isPlainObject(value)) {
134
+ const result = {};
135
+ for (const key in value) result[key] = convertNulls(value[key]);
136
+ return result;
137
+ }
138
+ return value;
139
+ }
140
+
141
+ //#endregion
142
+ //#region src/functions/object.ts
143
+ /**
144
+ * Implementation of deep object value retrieval with type safety
145
+ * @param obj - Source object
146
+ * @param path - Path specification (string or array)
147
+ * @param defaultValue - Optional fallback value
148
+ * @returns Value at path or default/undefined
149
+ */
150
+ function getObjectValue(obj, path, defaultValue) {
151
+ if (typeof path !== "string" && !Array.isArray(path)) return defaultValue;
152
+ const pathArray = (() => {
153
+ if (Array.isArray(path)) return path;
154
+ if (path === "") return [];
155
+ return String(path).split(".").filter((segment) => segment !== "");
156
+ })();
157
+ if (!Array.isArray(pathArray)) return defaultValue;
158
+ let current = obj;
159
+ for (const key of pathArray) {
160
+ if (current === null || current === void 0) return defaultValue;
161
+ const actualKey = typeof key === "string" && Array.isArray(current) && /^\d+$/.test(key) ? Number.parseInt(key, 10) : key;
162
+ current = current[actualKey];
163
+ }
164
+ return current !== void 0 ? current : defaultValue;
165
+ }
166
+ /**
167
+ * Extend an object or function with additional properties while
168
+ * preserving the original type information.
169
+ *
170
+ * Works with both plain objects and callable functions since
171
+ * functions in JavaScript are objects too.
172
+ *
173
+ * @template T The base object or function type
174
+ * @template P The additional properties type
175
+ *
176
+ * @param base - The object or function to extend
177
+ * @param props - An object containing properties to attach
178
+ *
179
+ * @returns The same object/function, augmented with the given properties
180
+ *
181
+ * @example
182
+ * // Extend an object
183
+ * const obj = extendProps({ a: 1 }, { b: "hello" });
184
+ * // obj has { a: number; b: string }
185
+ *
186
+ * // Extend a function
187
+ * const fn = (x: number) => x * 2;
188
+ * const enhanced = extendProps(fn, { name: "doubler" });
189
+ * // enhanced is callable and also has { name: string }
190
+ */
191
+ function extendProps(base, props) {
192
+ return Object.assign(base, props);
193
+ }
194
+
195
+ //#endregion
196
+ //#region src/functions/utils.ts
197
+ /**
198
+ * Utility to merge class names with Tailwind CSS and additional custom merging logic.
199
+ *
200
+ * @param {...ClassValue[]} inputs - Class names to merge.
201
+ * @returns {string} - A string of merged class names.
202
+ */
203
+ function cn(...inputs) {
204
+ return twMerge(clsx(inputs));
205
+ }
206
+ /**
207
+ * @deprecated Use isLinkActive instead.
208
+ *
209
+ * Determines if a navigation link is active based on the current path.
210
+ *
211
+ * @param href - The target URL.
212
+ * @param path - The current browser path.
213
+ * @returns - True if the navigation is active, false otherwise.
214
+ */
215
+ function isNavActive(href, path) {
216
+ console.warn("isNavActive is deprecated. Use isLinkActive instead.");
217
+ return (/* @__PURE__ */ new RegExp(`^/?${href}(/|$)`)).test(path);
218
+ }
219
+ /**
220
+ * Checks if a link is active, considering optional localization prefixes.
221
+ *
222
+ * @param {Object} params - Parameters object.
223
+ * @param {string} params.path - The target path of the link.
224
+ * @param {string} params.currentPath - The current browser path.
225
+ * @param {string[]} [params.locales=['en', 'es', 'de', 'zh', 'bn', 'fr', 'it', 'nl']] - Supported locale prefixes.
226
+ * @returns {boolean} - True if the link is active, false otherwise.
227
+ */
228
+ function isLinkActive({ path, currentPath, locales = [
229
+ "en",
230
+ "es",
231
+ "de",
232
+ "zh",
233
+ "bn",
234
+ "fr",
235
+ "it",
236
+ "nl"
237
+ ], exact = true }) {
238
+ const localeRegex = /* @__PURE__ */ new RegExp(`^/?(${locales.join("|")})/`);
239
+ const normalizePath = (p) => {
240
+ return p.replace(localeRegex, "").replace(/^\/+|\/+$/g, "");
241
+ };
242
+ const normalizedPath = normalizePath(path);
243
+ const normalizedCurrentPath = normalizePath(currentPath);
244
+ return exact ? normalizedPath === normalizedCurrentPath : normalizedCurrentPath.startsWith(normalizedPath);
245
+ }
246
+ /**
247
+ * Cleans a file path by removing the `/public/` prefix if present.
248
+ *
249
+ * @param src - The source path to clean.
250
+ * @returns - The cleaned path.
251
+ */
252
+ function cleanSrc(src) {
253
+ let cleanedSrc = src;
254
+ if (src.includes("/public/")) cleanedSrc = src.replace("/public/", "/");
255
+ return cleanedSrc.trim();
256
+ }
257
+ /**
258
+ * Smoothly scrolls to the top or bottom of a specified container.
259
+ *
260
+ * @param containerSelector - The CSS selector or React ref for the container.
261
+ * @param to - Specifies whether to scroll to the top or bottom.
262
+ */
263
+ const scrollTo = (containerSelector, to$1) => {
264
+ let container;
265
+ if (typeof containerSelector === "string") container = document.querySelector(containerSelector);
266
+ else if (containerSelector.current) container = containerSelector.current;
267
+ else return;
268
+ if (container) container.scrollTo({
269
+ top: to$1 === "top" ? 0 : container.scrollHeight - container.clientHeight,
270
+ behavior: "smooth"
271
+ });
272
+ };
273
+ /**
274
+ * Copies a given string to the clipboard.
275
+ *
276
+ * @param value - The value to copy to the clipboard.
277
+ * @param [onSuccess=() => {}] - Optional callback executed after successful copy.
278
+ */
279
+ const copyToClipboard = (value, onSuccess = () => {}) => {
280
+ if (typeof window === "undefined" || !navigator.clipboard?.writeText) return;
281
+ if (!value) return;
282
+ navigator.clipboard.writeText(value).then(onSuccess);
283
+ };
284
+ /**
285
+ * Converts camelCase, PascalCase, kebab-case, snake_case into normal case.
286
+ *
287
+ * @param inputString - The string need to be converted into normal case
288
+ * @returns - Normal Case
289
+ */
290
+ function convertToNormalCase(inputString) {
291
+ return (inputString.split(".").pop() || inputString).replace(/([a-z])([A-Z])/g, "$1 $2").split(/[-_|�\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
292
+ }
293
+ const from = "àáãäâèéëêìíïîòóöôùúüûñç·/_,:;";
294
+ const to = "aaaaaeeeeiiiioooouuuunc------";
295
+ /**
296
+ * Converts a string to a URL-friendly slug by trimming, converting to lowercase,
297
+ * replacing diacritics, removing invalid characters, and replacing spaces with hyphens.
298
+ * @param {string} [str] - The input string to convert.
299
+ * @returns {string} The generated slug.
300
+ * @example
301
+ * convertToSlug("Hello World!"); // "hello-world"
302
+ * convertToSlug("Déjà Vu"); // "deja-vu"
303
+ */
304
+ const convertToSlug = (str) => {
305
+ if (typeof str !== "string") throw new TypeError("Input must be a string");
306
+ let slug = str.trim().toLowerCase();
307
+ const charMap = {};
308
+ for (let i = 0; i < 29; i++) charMap[from.charAt(i)] = to.charAt(i);
309
+ slug = slug.replace(new RegExp(`[${from}]`, "g"), (match) => charMap[match] || match);
310
+ return slug.replace(/[^a-z0-9 -]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+/, "").replace(/-+$/, "") || "";
311
+ };
312
+ /**
313
+ * Checks if the code is running in a server-side environment.
314
+ *
315
+ * @returns - True if the code is executed in SSR (Server-Side Rendering) context, false otherwise
316
+ */
317
+ const isSSR = typeof window === "undefined";
318
+ /**
319
+ * Converts an SVG string to a Base64-encoded string.
320
+ *
321
+ * @param str - The SVG string to encode
322
+ * @returns - Base64-encoded string representation of the SVG
323
+ */
324
+ const svgToBase64 = (str) => isSSR ? Buffer.from(str).toString("base64") : window.btoa(str);
325
+ /**
326
+ * Pauses execution for the specified time.
327
+ *
328
+ * `signal` allows cancelling the sleep via AbortSignal.
329
+ *
330
+ * @param time - Time in milliseconds to sleep (default is 1000ms)
331
+ * @param signal - Optional AbortSignal to cancel the sleep early
332
+ * @returns - A Promise that resolves after the specified time or when aborted
333
+ */
334
+ const sleep = (time = 1e3, signal) => new Promise((resolve) => {
335
+ if (signal?.aborted) return resolve();
336
+ const id = setTimeout(() => {
337
+ cleanup();
338
+ resolve();
339
+ }, time);
340
+ function onAbort() {
341
+ clearTimeout(id);
342
+ cleanup();
343
+ resolve();
344
+ }
345
+ function cleanup() {
346
+ signal?.removeEventListener("abort", onAbort);
347
+ }
348
+ if (signal) signal.addEventListener("abort", onAbort, { once: true });
349
+ });
350
+ /**
351
+ * Creates a debounced function that delays invoking the provided function until
352
+ * after the specified `wait` time has elapsed since the last invocation.
353
+ *
354
+ * If the `immediate` option is set to `true`, the function will be invoked immediately
355
+ * on the leading edge of the wait interval. Subsequent calls during the wait interval
356
+ * will reset the timer but not invoke the function until the interval elapses again.
357
+ *
358
+ * The returned function includes the `isPending` property to check if the debounce
359
+ * timer is currently active.
360
+ *
361
+ * @typeParam F - The type of the function to debounce.
362
+ *
363
+ * @param function_ - The function to debounce.
364
+ * @param wait - The number of milliseconds to delay (default is 100ms).
365
+ * @param options - An optional object with the following properties:
366
+ * - `immediate` (boolean): If `true`, invokes the function on the leading edge
367
+ * of the wait interval instead of the trailing edge.
368
+ *
369
+ * @returns A debounced version of the provided function, enhanced with the `isPending` property.
370
+ *
371
+ * @throws {TypeError} If the first parameter is not a function.
372
+ * @throws {RangeError} If the `wait` parameter is negative.
373
+ *
374
+ * @example
375
+ * const log = debounce((message: string) => console.log(message), 200);
376
+ * log('Hello'); // Logs "Hello" after 200ms if no other call is made.
377
+ * console.log(log.isPending); // true if the timer is active.
378
+ */
379
+ function debounce(function_, wait = 100, options) {
380
+ if (typeof function_ !== "function") throw new TypeError(`Expected the first parameter to be a function, got \`${typeof function_}\`.`);
381
+ if (wait < 0) throw new RangeError("`wait` must not be negative.");
382
+ const immediate = options?.immediate ?? false;
383
+ let timeoutId;
384
+ let lastArgs;
385
+ let lastContext;
386
+ let result;
387
+ function run() {
388
+ result = function_.apply(lastContext, lastArgs);
389
+ lastArgs = void 0;
390
+ lastContext = void 0;
391
+ return result;
392
+ }
393
+ const debounced = function(...args) {
394
+ lastArgs = args;
395
+ lastContext = this;
396
+ if (timeoutId === void 0 && immediate) result = run.call(this);
397
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
398
+ timeoutId = setTimeout(run.bind(this), wait);
399
+ return result;
400
+ };
401
+ Object.defineProperty(debounced, "isPending", { get() {
402
+ return timeoutId !== void 0;
403
+ } });
404
+ return debounced;
405
+ }
406
+ /**
407
+ * Creates a throttled function that invokes the provided function at most once
408
+ * every `wait` milliseconds.
409
+ *
410
+ * If the `leading` option is set to `true`, the function will be invoked immediately
411
+ * on the leading edge of the throttle interval. If the `trailing` option is set to `true`,
412
+ * the function will also be invoked at the end of the throttle interval if additional
413
+ * calls were made during the interval.
414
+ *
415
+ * The returned function includes the `isPending` property to check if the throttle
416
+ * timer is currently active.
417
+ *
418
+ * @typeParam F - The type of the function to throttle.
419
+ *
420
+ * @param function_ - The function to throttle.
421
+ * @param wait - The number of milliseconds to wait between invocations (default is 100ms).
422
+ * @param options - An optional object with the following properties:
423
+ * - `leading` (boolean): If `true`, invokes the function on the leading edge of the interval.
424
+ * - `trailing` (boolean): If `true`, invokes the function on the trailing edge of the interval.
425
+ *
426
+ * @returns A throttled version of the provided function, enhanced with the `isPending` property.
427
+ *
428
+ * @throws {TypeError} If the first parameter is not a function.
429
+ * @throws {RangeError} If the `wait` parameter is negative.
430
+ *
431
+ * @example
432
+ * const log = throttle((message: string) => console.log(message), 200);
433
+ * log('Hello'); // Logs "Hello" immediately if leading is true.
434
+ * console.log(log.isPending); // true if the timer is active.
435
+ */
436
+ function throttle(function_, wait = 100, options) {
437
+ if (typeof function_ !== "function") throw new TypeError(`Expected the first parameter to be a function, got \`${typeof function_}\`.`);
438
+ if (wait < 0) throw new RangeError("`wait` must not be negative.");
439
+ const leading = options?.leading ?? true;
440
+ const trailing = options?.trailing ?? true;
441
+ let timeoutId;
442
+ let lastArgs;
443
+ let lastContext;
444
+ let lastCallTime;
445
+ let result;
446
+ function invoke() {
447
+ lastCallTime = Date.now();
448
+ result = function_.apply(lastContext, lastArgs);
449
+ lastArgs = void 0;
450
+ lastContext = void 0;
451
+ }
452
+ function later() {
453
+ timeoutId = void 0;
454
+ if (trailing && lastArgs) invoke();
455
+ }
456
+ const throttled = function(...args) {
457
+ const timeSinceLastCall = lastCallTime ? Date.now() - lastCallTime : Number.POSITIVE_INFINITY;
458
+ lastArgs = args;
459
+ lastContext = this;
460
+ if (timeSinceLastCall >= wait) if (leading) invoke();
461
+ else timeoutId = setTimeout(later, wait);
462
+ else if (!timeoutId && trailing) timeoutId = setTimeout(later, wait - timeSinceLastCall);
463
+ return result;
464
+ };
465
+ Object.defineProperty(throttled, "isPending", { get() {
466
+ return timeoutId !== void 0;
467
+ } });
468
+ return throttled;
469
+ }
470
+ /**
471
+ * Formats a string by replacing each '%s' placeholder with the corresponding argument.
472
+ * This function mimics the basic behavior of C's printf for %s substitution.
473
+ *
474
+ * It supports both calls like `printf(format, ...args)` and `printf(format, argsArray)`.
475
+ *
476
+ * @param format - The format string containing '%s' placeholders.
477
+ * @param args - The values to substitute into the placeholders, either as separate arguments or as a single array.
478
+ * @returns The formatted string with all '%s' replaced by the provided arguments.
479
+ *
480
+ * @example
481
+ * ```ts
482
+ * const message = printf("%s love %s", "I", "Bangladesh");
483
+ * // message === "I love Bangladesh"
484
+ *
485
+ * const arr = ["I", "Bangladesh"];
486
+ * const message2 = printf("%s love %s", arr);
487
+ * // message2 === "I love Bangladesh"
488
+ *
489
+ * // If there are too few arguments:
490
+ * const incomplete = printf("Bangladesh is %s %s", "beautiful");
491
+ * // incomplete === "Bangladesh is beautiful"
492
+ * ```
493
+ */
494
+ function printf(format, ...args) {
495
+ const replacements = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
496
+ let idx = 0;
497
+ return format.replace(/%s/g, () => {
498
+ const arg = replacements[idx++];
499
+ return arg === void 0 ? "" : String(arg);
500
+ });
501
+ }
502
+ const mergeRefs = (...refs) => {
503
+ return (value) => {
504
+ for (const ref of refs) {
505
+ if (!ref) continue;
506
+ if (typeof ref === "function") ref(value);
507
+ else ref.current = value;
508
+ }
509
+ };
510
+ };
511
+ /**
512
+ * Navigates to the specified client-side hash without ssr.
513
+ * use `scroll-margin-top` with css to add margins
514
+ *
515
+ * @param id - The ID of the element without # to navigate to.
516
+ *
517
+ * @example goToClientSideHash('my-element');
518
+ */
519
+ function goToClientSideHash(id, opts) {
520
+ const el = document.getElementById(id);
521
+ if (!el) return;
522
+ el.scrollIntoView({
523
+ behavior: "smooth",
524
+ block: "start",
525
+ ...opts
526
+ });
527
+ window.history.pushState(null, "", `#${id}`);
528
+ }
529
+ /**
530
+ * Escapes a string for use in a regular expression.
531
+ *
532
+ * @param str - The string to escape
533
+ * @returns - The escaped string
534
+ *
535
+ * @example
536
+ * const escapedString = escapeRegExp('Hello, world!');
537
+ * // escapedString === 'Hello\\, world!'
538
+ */
539
+ function escapeRegExp(str) {
540
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
541
+ }
542
+ /**
543
+ * Normalizes a string by:
544
+ * - Applying Unicode normalization (NFC)
545
+ * - Optionally removing diacritic marks (accents)
546
+ * - Optionally trimming leading/trailing non-alphanumeric characters
547
+ * - Optionally converting to lowercase
548
+ *
549
+ * @param str - The string to normalize
550
+ * @param options - Normalization options
551
+ * @param options.lowercase - Whether to convert the result to lowercase (default: true)
552
+ * @param options.removeAccents - Whether to remove diacritic marks like accents (default: true)
553
+ * @param options.removeNonAlphanumeric - Whether to trim non-alphanumeric characters from the edges (default: true)
554
+ * @returns The normalized string
555
+ */
556
+ function normalizeText(str, options = {}) {
557
+ if (!str) return "";
558
+ const { lowercase = true, removeAccents = true, removeNonAlphanumeric = true } = options;
559
+ let result = str.normalize("NFC");
560
+ if (removeAccents) result = result.normalize("NFD").replace(/\p{M}/gu, "");
561
+ if (removeNonAlphanumeric) result = result.replace(/^[^\p{L}\p{N}]*|[^\p{L}\p{N}]*$/gu, "");
562
+ if (lowercase) result = result.toLocaleLowerCase();
563
+ return result;
564
+ }
565
+
566
+ //#endregion
567
+ //#region src/functions/poll.ts
568
+ /**
569
+ * Repeatedly polls an async `cond` function UNTIL it returns a TRUTHY value,
570
+ * or until the operation times out or is aborted.
571
+ *
572
+ * Designed for waiting on async jobs, external state, or delayed availability.
573
+ *
574
+ * @template T The type of the successful result.
575
+ *
576
+ * @param cond
577
+ * A function returning a Promise that resolves to:
578
+ * - a truthy value `T` → stop polling and return it
579
+ * - falsy/null/undefined → continue polling
580
+ *
581
+ * @param options
582
+ * Configuration options:
583
+ * - `interval` (number) — Time between polls in ms (default: 5000 ms)
584
+ * - `timeout` (number) — Max total duration before failing (default: 5 min)
585
+ * - `jitter` (boolean) — Add small random offset (±10%) to intervals to avoid sync bursts (default: true)
586
+ * - `signal` (AbortSignal) — Optional abort signal to cancel polling
587
+ *
588
+ * @returns
589
+ * Resolves with the truthy value `T` when successful.
590
+ * Throws `AbortError` if aborted
591
+ *
592
+ * @example
593
+ * ```ts
594
+ * const job = await poll(async () => {
595
+ * const status = await getJobStatus();
596
+ * return status === 'done' ? status : null;
597
+ * }, { interval: 3000, timeout: 60000 });
598
+ * ```
599
+ */
600
+ async function poll(cond, { interval = 5e3, timeout = 300 * 1e3, jitter = true, signal } = {}) {
601
+ const start = Date.now();
602
+ let aborted = signal?.aborted ?? false;
603
+ const abortListener = () => {
604
+ aborted = true;
605
+ };
606
+ signal?.addEventListener("abort", abortListener, { once: true });
607
+ try {
608
+ for (let attempt = 0;; attempt++) {
609
+ if (aborted) throw new Error("Polling aborted");
610
+ const result = await cond();
611
+ if (result) return result;
612
+ if (Date.now() - start >= timeout) throw new Error("Polling timed out", { cause: `Polling timed out after ${timeout}ms` });
613
+ await sleep(jitter ? interval + (Math.random() - .5) * interval * .2 : interval, signal);
614
+ }
615
+ } catch (err) {
616
+ throw err;
617
+ } finally {
618
+ signal?.removeEventListener("abort", abortListener);
619
+ }
620
+ }
621
+
622
+ //#endregion
623
+ //#region src/functions/schedule.ts
624
+ /**
625
+ * Runs a function asynchronously in the background.
626
+ * Returns immediately, retries on failure if configured.
627
+ * Logs total time taken.
628
+ */
629
+ function schedule(task, options = {}) {
630
+ const { retry = 0, delay = 0 } = options;
631
+ const start = Date.now();
632
+ const attempt = async (triesLeft) => {
633
+ try {
634
+ await task();
635
+ const total = Date.now() - start;
636
+ console.log(`⚡[schedule.ts] Completed in ${total}ms`);
637
+ } catch (err) {
638
+ console.log("⚡[schedule.ts] err:", err);
639
+ if (triesLeft > 0) {
640
+ console.log(`⚡[schedule.ts] Retrying in ${delay}ms...`);
641
+ setTimeout(() => attempt(triesLeft - 1), delay);
642
+ } else {
643
+ const total = Date.now() - start;
644
+ console.log(`⚡[schedule.ts] Failed after ${total}ms`);
645
+ }
646
+ }
647
+ };
648
+ setTimeout(() => attempt(retry), 0);
649
+ }
650
+
651
+ //#endregion
652
+ //#region src/functions/shield.ts
653
+ function shield(operation) {
654
+ if (operation instanceof Promise) return operation.then((value) => [null, value]).catch((error) => [error, null]);
655
+ try {
656
+ return [null, operation()];
657
+ } catch (error) {
658
+ console.log(`\x1b[31m🛡 [shield]\x1b[0m ${operation.name} failed →`, error);
659
+ return [error, null];
660
+ }
661
+ }
662
+
663
+ //#endregion
664
+ export { cleanSrc, cn, convertToNormalCase, convertToSlug, copyToClipboard, debounce, deepmerge, deleteClientSideCookie, escapeRegExp, extendProps, getClientSideCookie, getObjectValue, goToClientSideHash, hasClientSideCookie, hydrate, isFalsy, isLinkActive, isNavActive, isNullish, isPlainObject, isPrimitive, isSSR, mergeRefs, normalizeText, poll, printf, schedule, scrollTo, setClientSideCookie, shield, sleep, svgToBase64, throttle };