my-utils-kit
Version:
A lightweight and type-safe utility library for working with strings, objects, array Performance methods in TypeScript. Includes helpful methods for deep cloning, object transformations, safe access, query string handling, and more — designed for modern J
257 lines (209 loc) • 6.05 kB
text/typescript
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number,
immediate = false
): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | null;
return function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
const context = this;
const callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) fn.apply(context, args);
}, delay);
if (callNow) fn.apply(context, args);
};
}
export function throttle<T extends (...args: any[]) => any>(
fn: T,
delay: number,
immediate = false
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null;
let lastArgs: Parameters<T> | null = null;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
const context = this;
if (!timer) {
if (immediate) {
fn.apply(context, args);
} else {
lastArgs = args;
}
timer = setTimeout(() => {
if (!immediate && lastArgs) {
fn.apply(context, lastArgs);
lastArgs = null;
}
timer = null;
}, delay);
}
};
}
export function memoize<T extends (...args: any[]) => any>(fn: T): T {
const cache: Map<string, ReturnType<T>> = new Map();
return function (this: any, ...args: Parameters<T>): ReturnType<T> {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("Returning from cache:", key);
return cache.get(key)!;
}
const result = fn.apply(this, args);
cache.set(key, result);
console.log("Calculating new result:", key);
return result;
} as T;
}
export const asyncMemoize = async <T extends (...args: any[]) => Promise<any>>(
fn: T
): Promise<T> => {
const cache = new Map<string, any>();
return async function (...args: Parameters<T>) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = await fn(...args);
cache.set(key, result);
return result;
} as T;
};
export class OperationBatcher {
private operations: (() => Promise<any>)[] = [];
private executing = false;
enqueueOperation(fn: () => Promise<any>) {
this.operations.push(fn);
if (!this.executing) {
this.execute();
}
}
private async execute() {
this.executing = true;
while (this.operations.length > 0) {
const operation = this.operations.shift();
if (operation) {
try {
await operation();
} catch (err) {
console.error("Operation failed", err);
}
}
}
this.executing = false;
}
}
export function runCallbackOnUserEvent(
callback: () => void,
options?: {
events?: (keyof DocumentEventMap)[];
timeout?: number;
}
): void {
if (typeof callback !== "function") {
throw new Error("Callback must be a function");
}
const { events = ["mousemove", "scroll", "touchstart"], timeout } =
options || {};
let executed = false;
function runOnce(): void {
if (executed) return;
executed = true;
for (const event of events) {
document.removeEventListener(event, runOnce);
}
callback();
}
for (const event of events) {
document.addEventListener(event, runOnce, { once: true, passive: true });
}
if (typeof timeout === "number" && timeout > 0) {
setTimeout(runOnce, timeout);
}
}
export function loadScriptOnUserEvent(
src: string,
options?: {
events?: (keyof DocumentEventMap)[];
timeout?: number;
}
): void {
if (typeof src !== "string" || !src.trim()) {
throw new Error("Script source must be a non-empty string");
}
const { events = ["mousemove", "scroll", "touchstart"], timeout } =
options || {};
let executed = false;
function loadScript(): void {
if (executed) return;
executed = true;
for (const event of events) {
document.removeEventListener(event, loadScript);
}
const script = document.createElement("script");
script.src = src;
script.async = true;
document.head.appendChild(script);
}
for (const event of events) {
document.addEventListener(event, loadScript, { once: true, passive: true });
}
if (typeof timeout === "number" && timeout > 0) {
setTimeout(loadScript, timeout);
}
}
export function observeElementOnIntersect(
selector: string,
options: IntersectionObserverInit,
callback: (entry: IntersectionObserverEntry) => void
): void {
if (typeof selector !== "string") {
throw new Error("Selector must be a string");
}
const elements = document.querySelectorAll<HTMLElement>(selector);
if (!elements.length) return;
const observer = new IntersectionObserver((entries, obs) => {
for (const entry of entries) {
if (entry.isIntersecting) {
callback(entry);
obs.unobserve(entry.target);
}
}
}, options);
elements.forEach((el) => observer.observe(el));
}
type BreakpointRange = {
min: number;
max: number;
};
type Breakpoints = Record<string, BreakpointRange>;
export function getScreenSize(
ranges: Breakpoints = {
xs: { min: 0, max: 480 },
sm: { min: 481, max: 640 },
md: { min: 641, max: 768 },
lg: { min: 769, max: 1024 },
xl: { min: 1025, max: 1280 },
"2xl": { min: 1281, max: 1536 },
"3xl": { min: 1537, max: Infinity }
}
): string | undefined {
const width = window.innerWidth;
for (const [label, { min, max }] of Object.entries(ranges)) {
if (width >= min && width <= max) {
return label;
}
}
return undefined; // fallback
}
export function watchScreenSize(
callback: (size: string | undefined) => void,
ranges?: Breakpoints
): () => void {
function handler() {
const size = getScreenSize(ranges || undefined);
callback(size);
}
window.addEventListener('resize', handler);
handler(); // Call immediately on setup
return () => window.removeEventListener('resize', handler);
}